sequel 3.1.0 → 3.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|