sequel 3.1.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +76 -0
- data/Rakefile +2 -2
- data/bin/sequel +9 -4
- data/doc/opening_databases.rdoc +279 -0
- data/doc/release_notes/3.2.0.txt +268 -0
- data/doc/virtual_rows.rdoc +42 -51
- data/lib/sequel/adapters/ado.rb +2 -5
- data/lib/sequel/adapters/db2.rb +5 -0
- data/lib/sequel/adapters/do.rb +3 -0
- data/lib/sequel/adapters/firebird.rb +6 -4
- data/lib/sequel/adapters/informix.rb +5 -3
- data/lib/sequel/adapters/jdbc.rb +10 -8
- data/lib/sequel/adapters/jdbc/h2.rb +17 -4
- data/lib/sequel/adapters/mysql.rb +6 -19
- data/lib/sequel/adapters/odbc.rb +14 -18
- data/lib/sequel/adapters/openbase.rb +8 -0
- data/lib/sequel/adapters/shared/mssql.rb +14 -8
- data/lib/sequel/adapters/shared/mysql.rb +53 -28
- data/lib/sequel/adapters/shared/oracle.rb +21 -12
- data/lib/sequel/adapters/shared/postgres.rb +46 -26
- data/lib/sequel/adapters/shared/progress.rb +10 -5
- data/lib/sequel/adapters/shared/sqlite.rb +28 -12
- data/lib/sequel/adapters/sqlite.rb +4 -3
- data/lib/sequel/adapters/utils/stored_procedures.rb +18 -9
- data/lib/sequel/connection_pool.rb +4 -3
- data/lib/sequel/database.rb +110 -10
- data/lib/sequel/database/schema_sql.rb +12 -3
- data/lib/sequel/dataset.rb +40 -3
- data/lib/sequel/dataset/convenience.rb +0 -11
- data/lib/sequel/dataset/graph.rb +25 -11
- data/lib/sequel/dataset/sql.rb +176 -68
- data/lib/sequel/extensions/migration.rb +37 -21
- data/lib/sequel/extensions/schema_dumper.rb +8 -61
- data/lib/sequel/model.rb +3 -3
- data/lib/sequel/model/associations.rb +9 -1
- data/lib/sequel/model/base.rb +8 -1
- data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
- data/lib/sequel/sql.rb +125 -18
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/ado_spec.rb +1 -0
- data/spec/adapters/firebird_spec.rb +1 -0
- data/spec/adapters/informix_spec.rb +1 -0
- data/spec/adapters/mysql_spec.rb +23 -8
- data/spec/adapters/oracle_spec.rb +1 -0
- data/spec/adapters/postgres_spec.rb +52 -4
- data/spec/adapters/spec_helper.rb +2 -2
- data/spec/adapters/sqlite_spec.rb +2 -1
- data/spec/core/connection_pool_spec.rb +16 -0
- data/spec/core/database_spec.rb +174 -0
- data/spec/core/dataset_spec.rb +121 -26
- data/spec/core/expression_filters_spec.rb +156 -0
- data/spec/core/object_graph_spec.rb +20 -1
- data/spec/core/schema_spec.rb +5 -5
- data/spec/extensions/migration_spec.rb +140 -74
- data/spec/extensions/schema_dumper_spec.rb +3 -69
- data/spec/extensions/single_table_inheritance_spec.rb +6 -0
- data/spec/integration/dataset_test.rb +84 -2
- data/spec/integration/schema_test.rb +24 -5
- data/spec/integration/spec_helper.rb +8 -6
- data/spec/model/eager_loading_spec.rb +9 -0
- data/spec/model/record_spec.rb +35 -8
- metadata +8 -7
- data/lib/sequel/adapters/utils/date_format.rb +0 -21
- data/lib/sequel/adapters/utils/savepoint_transactions.rb +0 -80
- data/lib/sequel/adapters/utils/unsupported.rb +0 -50
data/lib/sequel/dataset/sql.rb
CHANGED
@@ -7,6 +7,7 @@ module Sequel
|
|
7
7
|
COLUMN_REF_RE2 = /\A([\w ]+)___([\w ]+)\z/.freeze
|
8
8
|
COLUMN_REF_RE3 = /\A([\w ]+)__([\w ]+)\z/.freeze
|
9
9
|
COUNT_FROM_SELF_OPTS = [:distinct, :group, :sql, :limit, :compounds]
|
10
|
+
DATASET_ALIAS_BASE_NAME = 't'.freeze
|
10
11
|
INSERT_SQL_BASE="INSERT INTO ".freeze
|
11
12
|
IS_LITERALS = {nil=>'NULL'.freeze, true=>'TRUE'.freeze, false=>'FALSE'.freeze}.freeze
|
12
13
|
IS_OPERATORS = ::Sequel::SQL::ComplexExpression::IS_OPERATORS
|
@@ -15,9 +16,10 @@ module Sequel
|
|
15
16
|
QUALIFY_KEYS = [:select, :where, :having, :order, :group]
|
16
17
|
QUESTION_MARK = '?'.freeze
|
17
18
|
STOCK_COUNT_OPTS = {:select => [SQL::AliasedExpression.new(LiteralString.new("COUNT(*)").freeze, :count)], :order => nil}.freeze
|
18
|
-
SELECT_CLAUSE_ORDER = %w'distinct columns from join where group having compounds order limit'.freeze
|
19
|
+
SELECT_CLAUSE_ORDER = %w'with distinct columns from join where group having compounds order limit'.freeze
|
19
20
|
TWO_ARITY_OPERATORS = ::Sequel::SQL::ComplexExpression::TWO_ARITY_OPERATORS
|
20
21
|
WILDCARD = '*'.freeze
|
22
|
+
SQL_WITH = "WITH ".freeze
|
21
23
|
|
22
24
|
# Adds an further filter to an existing filter using AND. If no filter
|
23
25
|
# exists an error is raised. This method is identical to #filter except
|
@@ -63,8 +65,15 @@ module Sequel
|
|
63
65
|
def complex_expression_sql(op, args)
|
64
66
|
case op
|
65
67
|
when *IS_OPERATORS
|
66
|
-
|
67
|
-
|
68
|
+
r = args.at(1)
|
69
|
+
if r.nil? || supports_is_true?
|
70
|
+
raise(InvalidOperation, 'Invalid argument used for IS operator') unless v = IS_LITERALS[r]
|
71
|
+
"(#{literal(args.at(0))} #{op} #{v})"
|
72
|
+
elsif op == :IS
|
73
|
+
complex_expression_sql(:"=", args)
|
74
|
+
else
|
75
|
+
complex_expression_sql(:OR, [SQL::BooleanExpression.new(:"!=", *args), SQL::BooleanExpression.new(:IS, args.at(0), nil)])
|
76
|
+
end
|
68
77
|
when *TWO_ARITY_OPERATORS
|
69
78
|
"(#{literal(args.at(0))} #{op} #{literal(args.at(1))})"
|
70
79
|
when *N_ARITY_OPERATORS
|
@@ -76,7 +85,7 @@ module Sequel
|
|
76
85
|
when :'B~'
|
77
86
|
"~#{literal(args.at(0))}"
|
78
87
|
else
|
79
|
-
raise(
|
88
|
+
raise(InvalidOperation, "invalid operator #{op}")
|
80
89
|
end
|
81
90
|
end
|
82
91
|
|
@@ -113,21 +122,31 @@ module Sequel
|
|
113
122
|
# The DISTINCT clause is used to remove duplicate rows from the
|
114
123
|
# output. If arguments are provided, uses a DISTINCT ON clause,
|
115
124
|
# in which case it will only be distinct on those columns, instead
|
116
|
-
# of all returned columns.
|
125
|
+
# of all returned columns. Raises an error if arguments
|
126
|
+
# are given and DISTINCT ON is not supported.
|
117
127
|
#
|
118
128
|
# dataset.distinct # SQL: SELECT DISTINCT * FROM items
|
119
129
|
# dataset.order(:id).distinct(:id) # SQL: SELECT DISTINCT ON (id) * FROM items ORDER BY id
|
120
130
|
def distinct(*args)
|
131
|
+
raise(InvalidOperation, "DISTINCT ON not supported") if !args.empty? && !supports_distinct_on?
|
121
132
|
clone(:distinct => args)
|
122
133
|
end
|
123
134
|
|
124
|
-
# Adds an EXCEPT clause using a second dataset object.
|
125
|
-
#
|
135
|
+
# Adds an EXCEPT clause using a second dataset object.
|
136
|
+
# An EXCEPT compound dataset returns all rows in the current dataset
|
137
|
+
# that are not in the given dataset.
|
138
|
+
# Raises an InvalidOperation if the operation is not supported.
|
139
|
+
# Options:
|
140
|
+
# * :all - Set to true to use EXCEPT ALL instead of EXCEPT, so duplicate rows can occur
|
141
|
+
# * :from_self - Set to false to not wrap the returned dataset in a from_self, use with care.
|
126
142
|
#
|
127
143
|
# DB[:items].except(DB[:other_items]).sql
|
128
144
|
# #=> "SELECT * FROM items EXCEPT SELECT * FROM other_items"
|
129
|
-
def except(dataset,
|
130
|
-
|
145
|
+
def except(dataset, opts={})
|
146
|
+
opts = {:all=>opts} unless opts.is_a?(Hash)
|
147
|
+
raise(InvalidOperation, "EXCEPT not supported") unless supports_intersect_except?
|
148
|
+
raise(InvalidOperation, "EXCEPT ALL not supported") if opts[:all] && !supports_intersect_except_all?
|
149
|
+
compound_clone(:except, dataset, opts)
|
131
150
|
end
|
132
151
|
|
133
152
|
# Performs the inverse of Dataset#filter.
|
@@ -204,14 +223,14 @@ module Sequel
|
|
204
223
|
|
205
224
|
# The first source (primary table) for this dataset. If the dataset doesn't
|
206
225
|
# have a table, raises an error. If the table is aliased, returns the aliased name.
|
207
|
-
def
|
226
|
+
def first_source_alias
|
208
227
|
source = @opts[:from]
|
209
228
|
if source.nil? || source.empty?
|
210
229
|
raise Error, 'No source specified for query'
|
211
230
|
end
|
212
231
|
case s = source.first
|
213
|
-
when
|
214
|
-
s.
|
232
|
+
when SQL::AliasedExpression
|
233
|
+
s.aliaz
|
215
234
|
when Symbol
|
216
235
|
sch, table, aliaz = split_symbol(s)
|
217
236
|
aliaz ? aliaz.to_sym : s
|
@@ -219,6 +238,7 @@ module Sequel
|
|
219
238
|
s
|
220
239
|
end
|
221
240
|
end
|
241
|
+
alias first_source first_source_alias
|
222
242
|
|
223
243
|
# Returns a copy of the dataset with the source changed.
|
224
244
|
#
|
@@ -226,9 +246,31 @@ module Sequel
|
|
226
246
|
# dataset.from(:blah) # SQL: SELECT * FROM blah
|
227
247
|
# dataset.from(:blah, :foo) # SQL: SELECT * FROM blah, foo
|
228
248
|
def from(*source)
|
229
|
-
|
249
|
+
table_alias_num = 0
|
250
|
+
sources = []
|
251
|
+
source.each do |s|
|
252
|
+
case s
|
253
|
+
when Hash
|
254
|
+
s.each{|k,v| sources << SQL::AliasedExpression.new(k,v)}
|
255
|
+
when Dataset
|
256
|
+
sources << SQL::AliasedExpression.new(s, dataset_alias(table_alias_num+=1))
|
257
|
+
when Symbol
|
258
|
+
sch, table, aliaz = split_symbol(s)
|
259
|
+
if aliaz
|
260
|
+
s = sch ? SQL::QualifiedIdentifier.new(sch.to_sym, table.to_sym) : SQL::Identifier.new(table.to_sym)
|
261
|
+
sources << SQL::AliasedExpression.new(s, aliaz.to_sym)
|
262
|
+
else
|
263
|
+
sources << s
|
264
|
+
end
|
265
|
+
else
|
266
|
+
sources << s
|
267
|
+
end
|
268
|
+
end
|
269
|
+
o = {:from=>sources.empty? ? nil : sources}
|
270
|
+
o[:num_dataset_sources] = table_alias_num if table_alias_num > 0
|
271
|
+
clone(o)
|
230
272
|
end
|
231
|
-
|
273
|
+
|
232
274
|
# Returns a dataset selecting from the current dataset.
|
233
275
|
#
|
234
276
|
# ds = DB[:items].order(:name)
|
@@ -237,8 +279,7 @@ module Sequel
|
|
237
279
|
def from_self
|
238
280
|
fs = {}
|
239
281
|
@opts.keys.each{|k| fs[k] = nil}
|
240
|
-
fs
|
241
|
-
clone(fs)
|
282
|
+
clone(fs).from(self)
|
242
283
|
end
|
243
284
|
|
244
285
|
# SQL fragment specifying an SQL function call
|
@@ -319,7 +360,7 @@ module Sequel
|
|
319
360
|
if values.empty?
|
320
361
|
insert_default_values_sql
|
321
362
|
else
|
322
|
-
"#{insert_sql_base}#{from} VALUES #{literal(values)}"
|
363
|
+
"#{insert_sql_base}#{from} VALUES #{literal(values)}#{insert_sql_suffix}"
|
323
364
|
end
|
324
365
|
when Hash
|
325
366
|
values = @opts[:defaults].merge(values) if @opts[:defaults]
|
@@ -332,20 +373,28 @@ module Sequel
|
|
332
373
|
fl << literal(String === k ? k.to_sym : k)
|
333
374
|
vl << literal(v)
|
334
375
|
end
|
335
|
-
"#{insert_sql_base}#{from} (#{fl.join(COMMA_SEPARATOR)}) VALUES (#{vl.join(COMMA_SEPARATOR)})"
|
376
|
+
"#{insert_sql_base}#{from} (#{fl.join(COMMA_SEPARATOR)}) VALUES (#{vl.join(COMMA_SEPARATOR)})#{insert_sql_suffix}"
|
336
377
|
end
|
337
378
|
when Dataset
|
338
|
-
"#{insert_sql_base}#{from} #{literal(values)}"
|
379
|
+
"#{insert_sql_base}#{from} #{literal(values)}#{insert_sql_suffix}"
|
339
380
|
end
|
340
381
|
end
|
341
382
|
|
342
|
-
# Adds an INTERSECT clause using a second dataset object.
|
343
|
-
#
|
383
|
+
# Adds an INTERSECT clause using a second dataset object.
|
384
|
+
# An INTERSECT compound dataset returns all rows in both the current dataset
|
385
|
+
# and the given dataset.
|
386
|
+
# Raises an InvalidOperation if the operation is not supported.
|
387
|
+
# Options:
|
388
|
+
# * :all - Set to true to use INTERSECT ALL instead of INTERSECT, so duplicate rows can occur
|
389
|
+
# * :from_self - Set to false to not wrap the returned dataset in a from_self, use with care.
|
344
390
|
#
|
345
391
|
# DB[:items].intersect(DB[:other_items]).sql
|
346
392
|
# #=> "SELECT * FROM items INTERSECT SELECT * FROM other_items"
|
347
|
-
def intersect(dataset,
|
348
|
-
|
393
|
+
def intersect(dataset, opts={})
|
394
|
+
opts = {:all=>opts} unless opts.is_a?(Hash)
|
395
|
+
raise(InvalidOperation, "INTERSECT not supported") unless supports_intersect_except?
|
396
|
+
raise(InvalidOperation, "INTERSECT ALL not supported") if opts[:all] && !supports_intersect_except_all?
|
397
|
+
compound_clone(:intersect, dataset, opts)
|
349
398
|
end
|
350
399
|
|
351
400
|
# Inverts the current filter
|
@@ -421,7 +470,7 @@ module Sequel
|
|
421
470
|
if Dataset === table
|
422
471
|
if table_alias.nil?
|
423
472
|
table_alias_num = (@opts[:num_dataset_sources] || 0) + 1
|
424
|
-
table_alias =
|
473
|
+
table_alias = dataset_alias(table_alias_num)
|
425
474
|
end
|
426
475
|
table_name = table_alias
|
427
476
|
else
|
@@ -435,7 +484,7 @@ module Sequel
|
|
435
484
|
raise(Sequel::Error, "can't use a block if providing an array of symbols as expr") if block_given?
|
436
485
|
SQL::JoinUsingClause.new(expr, type, table, table_alias)
|
437
486
|
else
|
438
|
-
last_alias ||= @opts[:last_joined_table] ||
|
487
|
+
last_alias ||= @opts[:last_joined_table] || first_source_alias
|
439
488
|
if Sequel.condition_specifier?(expr)
|
440
489
|
expr = expr.collect do |k, v|
|
441
490
|
k = qualified_column_name(k, table_name) if k.is_a?(Symbol)
|
@@ -597,6 +646,11 @@ module Sequel
|
|
597
646
|
[qcr.table, qcr.column].map{|x| [SQL::QualifiedIdentifier, SQL::Identifier, Symbol].any?{|c| x.is_a?(c)} ? literal(x) : quote_identifier(x)}.join('.')
|
598
647
|
end
|
599
648
|
|
649
|
+
# Qualify to the given table, or first source if not table is given.
|
650
|
+
def qualify(table=first_source)
|
651
|
+
qualify_to(table)
|
652
|
+
end
|
653
|
+
|
600
654
|
# Return a copy of the dataset with unqualified identifiers in the
|
601
655
|
# SELECT, WHERE, GROUP, HAVING, and ORDER clauses qualified by the
|
602
656
|
# given table. If no columns are currently selected, select all
|
@@ -730,13 +784,18 @@ module Sequel
|
|
730
784
|
clone(:where => nil, :having => nil)
|
731
785
|
end
|
732
786
|
|
733
|
-
# Adds a UNION clause using a second dataset object.
|
734
|
-
#
|
787
|
+
# Adds a UNION clause using a second dataset object.
|
788
|
+
# A UNION compound dataset returns all rows in either the current dataset
|
789
|
+
# or the given dataset.
|
790
|
+
# Options:
|
791
|
+
# * :all - Set to true to use UNION ALL instead of UNION, so duplicate rows can occur
|
792
|
+
# * :from_self - Set to false to not wrap the returned dataset in a from_self, use with care.
|
735
793
|
#
|
736
794
|
# DB[:items].union(DB[:other_items]).sql
|
737
795
|
# #=> "SELECT * FROM items UNION SELECT * FROM other_items"
|
738
|
-
def union(dataset,
|
739
|
-
|
796
|
+
def union(dataset, opts={})
|
797
|
+
opts = {:all=>opts} unless opts.is_a?(Hash)
|
798
|
+
compound_clone(:union, dataset, opts)
|
740
799
|
end
|
741
800
|
|
742
801
|
# Returns a copy of the dataset with no order.
|
@@ -783,7 +842,7 @@ module Sequel
|
|
783
842
|
|
784
843
|
sql
|
785
844
|
end
|
786
|
-
|
845
|
+
|
787
846
|
# Add a condition to the WHERE clause. See #filter for argument types.
|
788
847
|
#
|
789
848
|
# dataset.group(:a).having(:a).filter(:b) # SELECT * FROM items GROUP BY a HAVING a AND b
|
@@ -792,6 +851,50 @@ module Sequel
|
|
792
851
|
_filter(:where, *cond, &block)
|
793
852
|
end
|
794
853
|
|
854
|
+
# The SQL fragment for the given window's options.
|
855
|
+
def window_sql(opts)
|
856
|
+
raise(Error, 'This dataset does not support window functions') unless supports_window_functions?
|
857
|
+
window = literal(opts[:window]) if opts[:window]
|
858
|
+
partition = "PARTITION BY #{expression_list(Array(opts[:partition]))}" if opts[:partition]
|
859
|
+
order = "ORDER BY #{expression_list(Array(opts[:order]))}" if opts[:order]
|
860
|
+
frame = case opts[:frame]
|
861
|
+
when nil
|
862
|
+
nil
|
863
|
+
when :all
|
864
|
+
"ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING"
|
865
|
+
when :rows
|
866
|
+
"ROWS UNBOUNDED PRECEDING"
|
867
|
+
else
|
868
|
+
raise Error, "invalid window frame clause, should be :all, :rows, or nil"
|
869
|
+
end
|
870
|
+
"(#{[window, partition, order, frame].compact.join(' ')})"
|
871
|
+
end
|
872
|
+
|
873
|
+
# The SQL fragment for the given window function's function and window.
|
874
|
+
def window_function_sql(function, window)
|
875
|
+
"#{literal(function)} OVER #{literal(window)}"
|
876
|
+
end
|
877
|
+
|
878
|
+
# Add a simple common table expression (CTE) with the given name and a dataset that defines the CTE.
|
879
|
+
# A common table expression acts as an inline view for the query.
|
880
|
+
# Options:
|
881
|
+
# * :args - Specify the arguments/columns for the CTE, should be an array of symbols.
|
882
|
+
# * :recursive - Specify that this is a recursive CTE
|
883
|
+
def with(name, dataset, opts={})
|
884
|
+
raise(Error, 'This datatset does not support common table expressions') unless supports_cte?
|
885
|
+
clone(:with=>(@opts[:with]||[]) + [opts.merge(:name=>name, :dataset=>dataset)])
|
886
|
+
end
|
887
|
+
|
888
|
+
# Add a recursive common table expression (CTE) with the given name, a dataset that
|
889
|
+
# defines the nonrecursive part of the CTE, and a dataset that defines the recursive part
|
890
|
+
# of the CTE. Options:
|
891
|
+
# * :args - Specify the arguments/columns for the CTE, should be an array of symbols.
|
892
|
+
# * :union_all - Set to false to use UNION instead of UNION ALL combining the nonrecursive and recursive parts.
|
893
|
+
def with_recursive(name, nonrecursive, recursive, opts={})
|
894
|
+
raise(Error, 'This datatset does not support common table expressions') unless supports_cte?
|
895
|
+
clone(:with=>(@opts[:with]||[]) + [opts.merge(:recursive=>true, :name=>name, :dataset=>nonrecursive.union(recursive, {:all=>opts[:union_all] != false, :from_self=>false}))])
|
896
|
+
end
|
897
|
+
|
795
898
|
# Returns a copy of the dataset with the static SQL used. This is useful if you want
|
796
899
|
# to keep the same row_proc/graph, but change the SQL used to custom SQL.
|
797
900
|
#
|
@@ -814,13 +917,6 @@ module Sequel
|
|
814
917
|
(@opts[:limit] || @opts[:order]) ? from_self : self
|
815
918
|
end
|
816
919
|
|
817
|
-
# Returns a table reference for use in the FROM clause. Returns an SQL subquery
|
818
|
-
# frgament with an optional table alias.
|
819
|
-
def to_table_reference(table_alias=nil)
|
820
|
-
s = "(#{sql})"
|
821
|
-
table_alias ? as_sql(s, table_alias) : s
|
822
|
-
end
|
823
|
-
|
824
920
|
private
|
825
921
|
|
826
922
|
# Internal filter method so it works on either the having or where clauses.
|
@@ -830,6 +926,11 @@ module Sequel
|
|
830
926
|
cond = SQL::BooleanExpression.new(:AND, @opts[clause], cond) if @opts[clause]
|
831
927
|
clone(clause => cond)
|
832
928
|
end
|
929
|
+
|
930
|
+
# Do a simple join of the arguments (which should be strings or symbols) separated by commas
|
931
|
+
def argument_list(args)
|
932
|
+
args.join(COMMA_SEPARATOR)
|
933
|
+
end
|
833
934
|
|
834
935
|
# SQL fragment for specifying an alias. expression should already be literalized.
|
835
936
|
def as_sql(expression, aliaz)
|
@@ -843,10 +944,16 @@ module Sequel
|
|
843
944
|
end
|
844
945
|
|
845
946
|
# Add the dataset to the list of compounds
|
846
|
-
def compound_clone(type, dataset,
|
847
|
-
compound_from_self.clone(:compounds=>Array(@opts[:compounds]).map{|x| x.dup} + [[type, dataset.compound_from_self, all]])
|
947
|
+
def compound_clone(type, dataset, opts)
|
948
|
+
ds = compound_from_self.clone(:compounds=>Array(@opts[:compounds]).map{|x| x.dup} + [[type, dataset.compound_from_self, opts[:all]]])
|
949
|
+
opts[:from_self] == false ? ds : ds.from_self
|
848
950
|
end
|
849
951
|
|
952
|
+
# The alias to use for datasets, takes a number to make sure the name is unique.
|
953
|
+
def dataset_alias(number)
|
954
|
+
:"#{DATASET_ALIAS_BASE_NAME}#{number}"
|
955
|
+
end
|
956
|
+
|
850
957
|
# Converts an array of expressions into a comma separated string of
|
851
958
|
# expressions.
|
852
959
|
def expression_list(columns)
|
@@ -902,6 +1009,11 @@ module Sequel
|
|
902
1009
|
"#{insert_sql_base}#{source_list(@opts[:from])} DEFAULT VALUES"
|
903
1010
|
end
|
904
1011
|
|
1012
|
+
# SQL statement for end of an INSERT statement
|
1013
|
+
def insert_sql_suffix
|
1014
|
+
nil
|
1015
|
+
end
|
1016
|
+
|
905
1017
|
# Inverts the given order by breaking it into a list of column references
|
906
1018
|
# and inverting them.
|
907
1019
|
#
|
@@ -950,12 +1062,12 @@ module Sequel
|
|
950
1062
|
|
951
1063
|
# SQL fragment for Date, using the ISO8601 format.
|
952
1064
|
def literal_date(v)
|
953
|
-
"'#{v}'"
|
1065
|
+
requires_sql_standard_datetimes? ? v.strftime("DATE '%Y-%m-%d'") : "'#{v}'"
|
954
1066
|
end
|
955
1067
|
|
956
1068
|
# SQL fragment for DateTime, using the ISO8601 format.
|
957
1069
|
def literal_datetime(v)
|
958
|
-
"'#{v}'"
|
1070
|
+
requires_sql_standard_datetimes? ? v.strftime("TIMESTAMP '%Y-%m-%d %H:%M:%S'") : "'#{v}'"
|
959
1071
|
end
|
960
1072
|
|
961
1073
|
# SQL fragment for SQL::Expression, result depends on the specific type of expression.
|
@@ -1009,7 +1121,7 @@ module Sequel
|
|
1009
1121
|
|
1010
1122
|
# SQL fragment for Time, uses the ISO8601 format.
|
1011
1123
|
def literal_time(v)
|
1012
|
-
"'#{v.iso8601}'"
|
1124
|
+
requires_sql_standard_datetimes? ? v.strftime("TIMESTAMP '%Y-%m-%d %H:%M:%S'") : "'#{v.iso8601}'"
|
1013
1125
|
end
|
1014
1126
|
|
1015
1127
|
# SQL fragment for true.
|
@@ -1074,6 +1186,15 @@ module Sequel
|
|
1074
1186
|
SQL::SQLArray.new(qualified_expression(e.array, table))
|
1075
1187
|
when SQL::Subscript
|
1076
1188
|
SQL::Subscript.new(qualified_expression(e.f, table), qualified_expression(e.sub, table))
|
1189
|
+
when SQL::WindowFunction
|
1190
|
+
SQL::WindowFunction.new(qualified_expression(e.function, table), qualified_expression(e.window, table))
|
1191
|
+
when SQL::Window
|
1192
|
+
o = e.opts.dup
|
1193
|
+
o[:partition] = qualified_expression(o[:partition], table) if o[:partition]
|
1194
|
+
o[:order] = qualified_expression(o[:order], table) if o[:order]
|
1195
|
+
SQL::Window.new(o)
|
1196
|
+
when SQL::PlaceholderLiteralString
|
1197
|
+
SQL::PlaceholderLiteralString.new(e.str, qualified_expression(e.args, table), e.parens)
|
1077
1198
|
else
|
1078
1199
|
e
|
1079
1200
|
end
|
@@ -1142,23 +1263,21 @@ module Sequel
|
|
1142
1263
|
def select_where_sql(sql)
|
1143
1264
|
sql << " WHERE #{literal(@opts[:where])}" if @opts[:where]
|
1144
1265
|
end
|
1266
|
+
|
1267
|
+
def select_with_sql(sql)
|
1268
|
+
ws = opts[:with]
|
1269
|
+
return if !ws || ws.empty?
|
1270
|
+
sql.replace("#{select_with_sql_base}#{ws.map{|w| "#{w[:name]}#{"(#{argument_list(w[:args])})" if w[:args]} AS (#{subselect_sql(w[:dataset])})"}.join(COMMA_SEPARATOR)} #{sql}")
|
1271
|
+
end
|
1272
|
+
|
1273
|
+
def select_with_sql_base
|
1274
|
+
SQL_WITH
|
1275
|
+
end
|
1145
1276
|
|
1146
1277
|
# Converts an array of source names into into a comma separated list.
|
1147
1278
|
def source_list(source)
|
1148
|
-
if source.nil? || source.empty?
|
1149
|
-
|
1150
|
-
end
|
1151
|
-
auto_alias_count = @opts[:num_dataset_sources] || 0
|
1152
|
-
m = source.map do |s|
|
1153
|
-
case s
|
1154
|
-
when Dataset
|
1155
|
-
auto_alias_count += 1
|
1156
|
-
s.to_table_reference("t#{auto_alias_count}")
|
1157
|
-
else
|
1158
|
-
table_ref(s)
|
1159
|
-
end
|
1160
|
-
end
|
1161
|
-
m.join(COMMA_SEPARATOR)
|
1279
|
+
raise(Error, 'No source specified for query') if source.nil? || source.empty?
|
1280
|
+
source.map{|s| table_ref(s)}.join(COMMA_SEPARATOR)
|
1162
1281
|
end
|
1163
1282
|
|
1164
1283
|
# Splits the symbol into three parts. Each part will
|
@@ -1193,18 +1312,7 @@ module Sequel
|
|
1193
1312
|
|
1194
1313
|
# SQL fragment specifying a table name.
|
1195
1314
|
def table_ref(t)
|
1196
|
-
|
1197
|
-
when Symbol
|
1198
|
-
literal_symbol(t)
|
1199
|
-
when Dataset
|
1200
|
-
t.to_table_reference
|
1201
|
-
when Hash
|
1202
|
-
t.map{|k, v| as_sql(table_ref(k), v)}.join(COMMA_SEPARATOR)
|
1203
|
-
when String
|
1204
|
-
quote_identifier(t)
|
1205
|
-
else
|
1206
|
-
literal(t)
|
1207
|
-
end
|
1315
|
+
t.is_a?(String) ? quote_identifier(t) : literal(t)
|
1208
1316
|
end
|
1209
1317
|
end
|
1210
1318
|
end
|
@@ -131,27 +131,40 @@ module Sequel
|
|
131
131
|
#
|
132
132
|
# Sequel::Migrator.apply(DB, '.', 5, 1)
|
133
133
|
module Migrator
|
134
|
+
DEFAULT_SCHEMA_COLUMN = :version
|
135
|
+
DEFAULT_SCHEMA_TABLE = :schema_info
|
134
136
|
MIGRATION_FILE_PATTERN = /\A\d+_.+\.rb\z/.freeze
|
135
137
|
MIGRATION_SPLITTER = '_'.freeze
|
136
138
|
|
137
|
-
#
|
138
|
-
# current version to the target version. If no current version is
|
139
|
-
# supplied, it is extracted from a schema_info table. The schema_info
|
140
|
-
# table is automatically created and maintained by the apply function.
|
139
|
+
# Wrapper for run, maintaining backwards API compatibility
|
141
140
|
def self.apply(db, directory, target = nil, current = nil)
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
141
|
+
run(db, directory, :target => target, :current => current)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Migrates the supplied database using the migration files in the the specified directory. Options:
|
145
|
+
# * :column - The column in the :table argument storing the migration version (default: :version).
|
146
|
+
# * :current - The current version of the database. If not given, it is retrieved from the database
|
147
|
+
# using the :table and :column options.
|
148
|
+
# * :table - The table containing the schema version (default: :schema_info).
|
149
|
+
# * :target - The target version to which to migrate. If not given, migrates to the maximum version.
|
150
|
+
#
|
151
|
+
# Examples:
|
152
|
+
# Sequel::Migrator.run(DB, "migrations")
|
153
|
+
# Sequel::Migrator.run(DB, "migrations", :target=>15, :current=>10)
|
154
|
+
# Sequel::Migrator.run(DB, "app1/migrations", :column=> :app2_version)
|
155
|
+
# Sequel::Migrator.run(DB, "app2/migrations", :column => :app2_version, :table=>:schema_info2)
|
156
|
+
def self.run(db, directory, opts={})
|
157
|
+
raise(Error, "Must supply a valid migration path") unless directory and File.directory?(directory)
|
158
|
+
raise(Error, "No current version available") unless current = opts[:current] || get_current_migration_version(db, opts)
|
159
|
+
raise(Error, "No target version available") unless target = opts[:target] || latest_migration_version(directory)
|
147
160
|
|
148
161
|
direction = current < target ? :up : :down
|
149
162
|
|
150
163
|
classes = migration_classes(directory, target, current, direction)
|
151
|
-
|
164
|
+
|
152
165
|
db.transaction do
|
153
166
|
classes.each {|c| c.apply(db, direction)}
|
154
|
-
set_current_migration_version(db, target)
|
167
|
+
set_current_migration_version(db, target, opts)
|
155
168
|
end
|
156
169
|
|
157
170
|
target
|
@@ -159,9 +172,8 @@ module Sequel
|
|
159
172
|
|
160
173
|
# Gets the current migration version stored in the database. If no version
|
161
174
|
# number is stored, 0 is returned.
|
162
|
-
def self.get_current_migration_version(db)
|
163
|
-
|
164
|
-
r ? r[:version] : 0
|
175
|
+
def self.get_current_migration_version(db, opts={})
|
176
|
+
(schema_info_dataset(db, opts).first || {})[opts[:column] || DEFAULT_SCHEMA_COLUMN] || 0
|
165
177
|
end
|
166
178
|
|
167
179
|
# Returns the latest version available in the specified directory.
|
@@ -203,19 +215,23 @@ module Sequel
|
|
203
215
|
|
204
216
|
# Returns the dataset for the schema_info table. If no such table
|
205
217
|
# exists, it is automatically created.
|
206
|
-
def self.schema_info_dataset(db)
|
207
|
-
|
208
|
-
|
218
|
+
def self.schema_info_dataset(db, opts={})
|
219
|
+
column = opts[:column] || DEFAULT_SCHEMA_COLUMN
|
220
|
+
table = opts[:table] || DEFAULT_SCHEMA_TABLE
|
221
|
+
db.create_table?(table){Integer column}
|
222
|
+
db.alter_table(table){add_column column, Integer} unless db.from(table).columns.include?(column)
|
223
|
+
db.from(table)
|
209
224
|
end
|
210
225
|
|
211
226
|
# Sets the current migration version stored in the database.
|
212
|
-
def self.set_current_migration_version(db, version)
|
213
|
-
|
214
|
-
dataset
|
227
|
+
def self.set_current_migration_version(db, version, opts={})
|
228
|
+
column = opts[:column] || DEFAULT_SCHEMA_COLUMN
|
229
|
+
dataset = schema_info_dataset(db, opts)
|
230
|
+
dataset.send(dataset.first ? :update : :insert, column => version)
|
215
231
|
end
|
216
232
|
|
217
233
|
# Return the integer migration version based on the filename.
|
218
|
-
def self.migration_version_from_file(filename)
|
234
|
+
def self.migration_version_from_file(filename) # :nodoc:
|
219
235
|
filename.split(MIGRATION_SPLITTER, 2).first.to_i
|
220
236
|
end
|
221
237
|
private_class_method :migration_version_from_file
|