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.
Files changed (65) hide show
  1. data/CHANGELOG +76 -0
  2. data/Rakefile +2 -2
  3. data/bin/sequel +9 -4
  4. data/doc/opening_databases.rdoc +279 -0
  5. data/doc/release_notes/3.2.0.txt +268 -0
  6. data/doc/virtual_rows.rdoc +42 -51
  7. data/lib/sequel/adapters/ado.rb +2 -5
  8. data/lib/sequel/adapters/db2.rb +5 -0
  9. data/lib/sequel/adapters/do.rb +3 -0
  10. data/lib/sequel/adapters/firebird.rb +6 -4
  11. data/lib/sequel/adapters/informix.rb +5 -3
  12. data/lib/sequel/adapters/jdbc.rb +10 -8
  13. data/lib/sequel/adapters/jdbc/h2.rb +17 -4
  14. data/lib/sequel/adapters/mysql.rb +6 -19
  15. data/lib/sequel/adapters/odbc.rb +14 -18
  16. data/lib/sequel/adapters/openbase.rb +8 -0
  17. data/lib/sequel/adapters/shared/mssql.rb +14 -8
  18. data/lib/sequel/adapters/shared/mysql.rb +53 -28
  19. data/lib/sequel/adapters/shared/oracle.rb +21 -12
  20. data/lib/sequel/adapters/shared/postgres.rb +46 -26
  21. data/lib/sequel/adapters/shared/progress.rb +10 -5
  22. data/lib/sequel/adapters/shared/sqlite.rb +28 -12
  23. data/lib/sequel/adapters/sqlite.rb +4 -3
  24. data/lib/sequel/adapters/utils/stored_procedures.rb +18 -9
  25. data/lib/sequel/connection_pool.rb +4 -3
  26. data/lib/sequel/database.rb +110 -10
  27. data/lib/sequel/database/schema_sql.rb +12 -3
  28. data/lib/sequel/dataset.rb +40 -3
  29. data/lib/sequel/dataset/convenience.rb +0 -11
  30. data/lib/sequel/dataset/graph.rb +25 -11
  31. data/lib/sequel/dataset/sql.rb +176 -68
  32. data/lib/sequel/extensions/migration.rb +37 -21
  33. data/lib/sequel/extensions/schema_dumper.rb +8 -61
  34. data/lib/sequel/model.rb +3 -3
  35. data/lib/sequel/model/associations.rb +9 -1
  36. data/lib/sequel/model/base.rb +8 -1
  37. data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
  38. data/lib/sequel/sql.rb +125 -18
  39. data/lib/sequel/version.rb +1 -1
  40. data/spec/adapters/ado_spec.rb +1 -0
  41. data/spec/adapters/firebird_spec.rb +1 -0
  42. data/spec/adapters/informix_spec.rb +1 -0
  43. data/spec/adapters/mysql_spec.rb +23 -8
  44. data/spec/adapters/oracle_spec.rb +1 -0
  45. data/spec/adapters/postgres_spec.rb +52 -4
  46. data/spec/adapters/spec_helper.rb +2 -2
  47. data/spec/adapters/sqlite_spec.rb +2 -1
  48. data/spec/core/connection_pool_spec.rb +16 -0
  49. data/spec/core/database_spec.rb +174 -0
  50. data/spec/core/dataset_spec.rb +121 -26
  51. data/spec/core/expression_filters_spec.rb +156 -0
  52. data/spec/core/object_graph_spec.rb +20 -1
  53. data/spec/core/schema_spec.rb +5 -5
  54. data/spec/extensions/migration_spec.rb +140 -74
  55. data/spec/extensions/schema_dumper_spec.rb +3 -69
  56. data/spec/extensions/single_table_inheritance_spec.rb +6 -0
  57. data/spec/integration/dataset_test.rb +84 -2
  58. data/spec/integration/schema_test.rb +24 -5
  59. data/spec/integration/spec_helper.rb +8 -6
  60. data/spec/model/eager_loading_spec.rb +9 -0
  61. data/spec/model/record_spec.rb +35 -8
  62. metadata +8 -7
  63. data/lib/sequel/adapters/utils/date_format.rb +0 -21
  64. data/lib/sequel/adapters/utils/savepoint_transactions.rb +0 -80
  65. data/lib/sequel/adapters/utils/unsupported.rb +0 -50
@@ -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
- v = IS_LITERALS[args.at(1)] || raise(Error, 'Invalid argument used for IS operator')
67
- "(#{literal(args.at(0))} #{op} #{v})"
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(Sequel::Error, "invalid operator #{op}")
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. If all is true the
125
- # clause used is EXCEPT ALL, which may return duplicate rows.
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, all = false)
130
- compound_clone(:except, dataset, all)
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 first_source
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 Hash
214
- s.values.first
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
- clone(:from=>source.empty? ? nil : source)
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[:from] = [self]
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. If all is true
343
- # the clause used is INTERSECT ALL, which may return duplicate rows.
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, all = false)
348
- compound_clone(:intersect, dataset, all)
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 = "t#{table_alias_num}"
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] || (first_source.is_a?(Dataset) ? 't1' : first_source)
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. If all is true the
734
- # clause used is UNION ALL, which may return duplicate rows.
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, all = false)
739
- compound_clone(:union, dataset, all)
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, all)
847
- compound_from_self.clone(:compounds=>Array(@opts[:compounds]).map{|x| x.dup} + [[type, dataset.compound_from_self, all]]).from_self
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
- raise Error, 'No source specified for query'
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
- case t
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
- # Migrates the supplied database in the specified directory from the
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
- # determine current and target version and direction
143
- current ||= get_current_migration_version(db)
144
- target ||= latest_migration_version(directory)
145
- raise Error, "No current version available" if current.nil?
146
- raise Error, "No target version available" if target.nil?
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
- r = schema_info_dataset(db).first
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
- db.create_table(:schema_info) {integer :version} unless db.table_exists?(:schema_info)
208
- db[:schema_info]
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
- dataset = schema_info_dataset(db)
214
- dataset.send(dataset.first ? :update : :<<, :version => version)
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