sequel 4.40.0 → 4.41.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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +30 -0
  3. data/Rakefile +6 -3
  4. data/doc/association_basics.rdoc +3 -3
  5. data/doc/opening_databases.rdoc +3 -3
  6. data/doc/release_notes/4.41.0.txt +77 -0
  7. data/doc/schema_modification.rdoc +11 -11
  8. data/lib/sequel/adapters/ado.rb +137 -9
  9. data/lib/sequel/adapters/ado/mssql.rb +1 -1
  10. data/lib/sequel/adapters/jdbc.rb +1 -1
  11. data/lib/sequel/adapters/mysql2.rb +0 -1
  12. data/lib/sequel/adapters/shared/db2.rb +30 -10
  13. data/lib/sequel/adapters/shared/mssql.rb +11 -6
  14. data/lib/sequel/database/query.rb +3 -6
  15. data/lib/sequel/database/schema_generator.rb +7 -1
  16. data/lib/sequel/database/schema_methods.rb +0 -6
  17. data/lib/sequel/dataset/actions.rb +4 -4
  18. data/lib/sequel/dataset/graph.rb +3 -2
  19. data/lib/sequel/dataset/misc.rb +23 -0
  20. data/lib/sequel/dataset/mutation.rb +15 -14
  21. data/lib/sequel/dataset/query.rb +25 -5
  22. data/lib/sequel/extensions/constraint_validations.rb +1 -3
  23. data/lib/sequel/extensions/sql_comments.rb +6 -1
  24. data/lib/sequel/model/associations.rb +7 -1
  25. data/lib/sequel/model/base.rb +1 -1
  26. data/lib/sequel/plugins/class_table_inheritance.rb +6 -6
  27. data/lib/sequel/plugins/hook_class_methods.rb +2 -2
  28. data/lib/sequel/plugins/json_serializer.rb +29 -7
  29. data/lib/sequel/version.rb +1 -1
  30. data/spec/adapters/firebird_spec.rb +2 -8
  31. data/spec/adapters/mssql_spec.rb +12 -12
  32. data/spec/adapters/mysql_spec.rb +11 -11
  33. data/spec/adapters/postgres_spec.rb +8 -9
  34. data/spec/adapters/spec_helper.rb +1 -0
  35. data/spec/adapters/sqlite_spec.rb +1 -3
  36. data/spec/core/database_spec.rb +3 -2
  37. data/spec/core/dataset_spec.rb +66 -22
  38. data/spec/core/expression_filters_spec.rb +4 -0
  39. data/spec/core/mock_adapter_spec.rb +1 -1
  40. data/spec/core/schema_generator_spec.rb +1 -1
  41. data/spec/core/schema_spec.rb +10 -1
  42. data/spec/core/spec_helper.rb +1 -0
  43. data/spec/extensions/class_table_inheritance_spec.rb +3 -0
  44. data/spec/extensions/connection_expiration_spec.rb +1 -1
  45. data/spec/extensions/graph_each_spec.rb +6 -0
  46. data/spec/extensions/hook_class_methods_spec.rb +46 -0
  47. data/spec/extensions/json_serializer_spec.rb +8 -3
  48. data/spec/extensions/set_overrides_spec.rb +4 -0
  49. data/spec/extensions/single_table_inheritance_spec.rb +2 -2
  50. data/spec/extensions/spec_helper.rb +1 -0
  51. data/spec/extensions/string_agg_spec.rb +4 -0
  52. data/spec/extensions/uuid_spec.rb +1 -2
  53. data/spec/integration/associations_test.rb +14 -0
  54. data/spec/integration/dataset_test.rb +17 -22
  55. data/spec/integration/schema_test.rb +3 -3
  56. data/spec/integration/spec_helper.rb +1 -0
  57. data/spec/integration/type_test.rb +1 -7
  58. data/spec/model/associations_spec.rb +26 -1
  59. data/spec/model/model_spec.rb +7 -1
  60. data/spec/model/spec_helper.rb +2 -0
  61. data/spec/sequel_warning.rb +4 -0
  62. metadata +6 -3
@@ -223,7 +223,7 @@ module Sequel
223
223
  raise(Sequel::DatabaseError, 'driver.new.connect returned nil: probably bad JDBC connection string') unless c
224
224
  c
225
225
  rescue JavaSQL::SQLException, NativeException, StandardError => e2
226
- unless e2.message == e.message
226
+ if e2.respond_to?(:message=) && e2.message != e.message
227
227
  e2.message = "#{e2.message}\n#{e.class.name}: #{e.message}"
228
228
  end
229
229
  raise e2
@@ -86,7 +86,6 @@ module Sequel
86
86
  if NativePreparedStatements
87
87
  # Use a native mysql2 prepared statement to implement prepared statements.
88
88
  def execute_prepared_statement(ps_name, opts, &block)
89
- args = opts[:arguments]
90
89
  ps = prepared_statement(ps_name)
91
90
  sql = ps.prepared_sql
92
91
 
@@ -85,6 +85,17 @@ module Sequel
85
85
  indexes
86
86
  end
87
87
 
88
+ def offset_strategy
89
+ return @offset_strategy if defined?(@offset_strategy)
90
+
91
+ @offset_strategy = case strategy = opts[:offset_strategy].to_s
92
+ when "limit_offset", "offset_fetch"
93
+ opts[:offset_strategy] = strategy.to_sym
94
+ else
95
+ opts[:offset_strategy] = :emulate
96
+ end
97
+ end
98
+
88
99
  # DB2 supports transaction isolation levels.
89
100
  def supports_transaction_isolation_levels?
90
101
  true
@@ -371,6 +382,13 @@ module Sequel
371
382
  EMPTY_FROM_TABLE
372
383
  end
373
384
 
385
+ # Emulate offset with row number by default, and also when the limit_offset
386
+ # strategy is used without a limit, as DB2 doesn't support that syntax with
387
+ # no limit.
388
+ def emulate_offset_with_row_number?
389
+ super && (db.offset_strategy == :emulate || (db.offset_strategy == :limit_offset && !@opts[:limit]))
390
+ end
391
+
374
392
  # DB2 needs the standard workaround to insert all default values into
375
393
  # a table with more than one column.
376
394
  def insert_supports_empty_values?
@@ -406,17 +424,19 @@ module Sequel
406
424
  false
407
425
  end
408
426
 
409
- # Modify the sql to limit the number of rows returned
410
- # Note:
411
- #
412
- # After db2 v9.7, MySQL flavored "LIMIT X OFFSET Y" can be enabled using
413
- #
414
- # db2set DB2_COMPATIBILITY_VECTOR=MYSQL
415
- # db2stop
416
- # db2start
417
- #
418
- # Support for this feature is not used in this adapter however.
427
+ # Modify the sql to limit the number of rows returned.
428
+ # Uses :offset_strategy Database option to determine how to format the
429
+ # limit and offset.
419
430
  def select_limit_sql(sql)
431
+ strategy = db.offset_strategy
432
+ return super if strategy == :limit_offset
433
+
434
+ if strategy == :offset_fetch && (o = @opts[:offset])
435
+ sql << " OFFSET "
436
+ literal_append(sql, o)
437
+ sql << " ROWS"
438
+ end
439
+
420
440
  if l = @opts[:limit]
421
441
  if l == 1
422
442
  sql << FETCH_FIRST_ROW_ONLY
@@ -405,9 +405,7 @@ module Sequel
405
405
 
406
406
  # Always quote identifiers in the metadata_dataset, so schema parsing works.
407
407
  def metadata_dataset
408
- ds = super
409
- ds.quote_identifiers = true
410
- ds
408
+ super.with_quote_identifiers(true)
411
409
  end
412
410
 
413
411
  # Use sp_rename to rename the table
@@ -574,7 +572,7 @@ module Sequel
574
572
  ROWS_ONLY = " ROWS ONLY".freeze
575
573
  FETCH_NEXT = " FETCH NEXT ".freeze
576
574
 
577
- NON_SQL_OPTIONS = (Dataset::NON_SQL_OPTIONS + [:disable_insert_output]).freeze
575
+ NON_SQL_OPTIONS = (Dataset::NON_SQL_OPTIONS + [:disable_insert_output, :mssql_unicode_strings]).freeze
578
576
 
579
577
  Dataset.def_mutation_method(:disable_insert_output, :output, :module=>self)
580
578
  Dataset.def_sql_method(self, :delete, %w'with delete from output from2 where')
@@ -582,11 +580,18 @@ module Sequel
582
580
  Dataset.def_sql_method(self, :update, [['if is_2005_or_later?', %w'with update limit table set output from where'], ['else', %w'update table set output from where']])
583
581
 
584
582
  # Allow overriding of the mssql_unicode_strings option at the dataset level.
585
- attr_writer :mssql_unicode_strings
583
+ def mssql_unicode_strings=(v)
584
+ @opts[:mssql_unicode_strings] = v
585
+ end
586
586
 
587
587
  # Use the database's mssql_unicode_strings setting if the dataset hasn't overridden it.
588
588
  def mssql_unicode_strings
589
- defined?(@mssql_unicode_strings) ? @mssql_unicode_strings : (@mssql_unicode_strings = db.mssql_unicode_strings)
589
+ opts.has_key?(:mssql_unicode_strings) ? opts[:mssql_unicode_strings] : db.mssql_unicode_strings
590
+ end
591
+
592
+ # Return a cloned dataset with the mssql_unicode_strings option set.
593
+ def with_mssql_unicode_strings(v)
594
+ clone(:mssql_unicode_strings=>v)
590
595
  end
591
596
 
592
597
  # MSSQL uses + for string concatenation, and LIKE is case insensitive by default.
@@ -291,12 +291,9 @@ module Sequel
291
291
  # for this database. Used when parsing metadata so that column symbols are
292
292
  # returned as expected.
293
293
  def metadata_dataset
294
- @metadata_dataset ||= (
295
- ds = dataset;
296
- ds.identifier_input_method = identifier_input_method_default;
297
- ds.identifier_output_method = identifier_output_method_default;
298
- ds
299
- )
294
+ @metadata_dataset ||= dataset.
295
+ with_identifier_input_method(identifier_input_method_default).
296
+ with_identifier_output_method(identifier_output_method_default)
300
297
  end
301
298
 
302
299
  # Return a Method object for the dataset's output_identifier_method.
@@ -18,7 +18,7 @@ module Sequel
18
18
  # the {"Schema Modification" guide}[rdoc-ref:doc/schema_modification.rdoc].
19
19
  class CreateTableGenerator
20
20
  # Classes specifying generic types that Sequel will convert to database-specific types.
21
- GENERIC_TYPES=%w'String Integer Fixnum Float Numeric BigDecimal Date DateTime Time File TrueClass FalseClass'
21
+ GENERIC_TYPES=%w'String Integer Float Numeric BigDecimal Date DateTime Time File TrueClass FalseClass'.freeze
22
22
 
23
23
  # Return the column hashes created by this generator
24
24
  attr_reader :columns
@@ -47,6 +47,12 @@ module Sequel
47
47
  column(name, :Bignum, opts)
48
48
  end
49
49
 
50
+ # Use custom Fixnum method to use Integer instead of Fixnum class, to avoid
51
+ # warnings on ruby 2.4+.
52
+ def Fixnum(name, opts=OPTS)
53
+ column(name, Integer, opts)
54
+ end
55
+
50
56
  # Add a method for each of the given types that creates a column
51
57
  # with that type as a constant. Types given should either already
52
58
  # be constants/classes or a capitalized string/symbol with the same name
@@ -937,12 +937,6 @@ module Sequel
937
937
  type_literal_generic_numeric(column)
938
938
  end
939
939
 
940
- # Sequel uses the bigint type by default for Bignums.
941
- def type_literal_generic_bignum(column)
942
- Sequel::Deprecation.deprecate("Using the Bignum class as a generic type is deprecated and will be removed in Sequel 4.41.0, as the behavior will change in ruby 2.4. Switch to using the :Bignum symbol.")
943
- type_literal_generic_bignum_symbol(column)
944
- end
945
-
946
940
  # Sequel uses the bigint type by default for :Bignum symbol.
947
941
  def type_literal_generic_bignum_symbol(column)
948
942
  :bigint
@@ -135,8 +135,8 @@ module Sequel
135
135
  # running queries inside the block, you should use +all+ instead of +each+
136
136
  # for the outer queries, or use a separate thread or shard inside +each+.
137
137
  def each
138
- if row_proc = @row_proc
139
- fetch_rows(select_sql){|r| yield row_proc.call(r)}
138
+ if rp = row_proc
139
+ fetch_rows(select_sql){|r| yield rp.call(r)}
140
140
  else
141
141
  fetch_rows(select_sql){|r| yield r}
142
142
  end
@@ -847,8 +847,8 @@ module Sequel
847
847
  # This method should not be called on a shared dataset if the columns selected
848
848
  # in the given SQL do not match the columns in the receiver.
849
849
  def with_sql_each(sql)
850
- if row_proc = @row_proc
851
- fetch_rows(sql){|r| yield row_proc.call(r)}
850
+ if rp = row_proc
851
+ fetch_rows(sql){|r| yield rp.call(r)}
852
852
  else
853
853
  fetch_rows(sql){|r| yield r}
854
854
  end
@@ -117,7 +117,7 @@ module Sequel
117
117
  add_columns = !ds.opts.include?(:graph_aliases)
118
118
 
119
119
  if graph = opts[:graph]
120
- opts[:graph] = graph = graph.dup
120
+ graph = graph.dup
121
121
  select = opts[:select].dup
122
122
  [:column_aliases, :table_aliases, :column_alias_num].each{|k| graph[k] = graph[k].dup}
123
123
  else
@@ -127,7 +127,7 @@ module Sequel
127
127
  raise_alias_error.call if master == table_alias
128
128
 
129
129
  # Master hash storing all .graph related information
130
- graph = opts[:graph] = {}
130
+ graph = {}
131
131
 
132
132
  # Associates column aliases back to tables and columns
133
133
  column_aliases = graph[:column_aliases] = {}
@@ -200,6 +200,7 @@ module Sequel
200
200
  select.push(identifier)
201
201
  end
202
202
  end
203
+ ds = ds.clone(:graph=>graph)
203
204
  add_columns ? ds.select(*select) : ds
204
205
  end
205
206
 
@@ -248,6 +248,29 @@ module Sequel
248
248
  end
249
249
  end
250
250
 
251
+ # Return a modified dataset with quote_identifiers set.
252
+ def with_quote_identifiers(v)
253
+ c = clone
254
+ c.send(:skip_symbol_cache!)
255
+ c.instance_variable_set(:@quote_identifiers, v)
256
+ c
257
+ end
258
+
259
+ # Return a modified dataset with identifier_input_method set.
260
+ def with_identifier_input_method(meth)
261
+ c = clone
262
+ c.send(:skip_symbol_cache!)
263
+ c.instance_variable_set(:@identifier_input_method, meth)
264
+ c
265
+ end
266
+
267
+ # Return a modified dataset with identifier_output_method set.
268
+ def with_identifier_output_method(meth)
269
+ c = clone
270
+ c.instance_variable_set(:@identifier_output_method, meth)
271
+ c
272
+ end
273
+
251
274
  private
252
275
 
253
276
  # Internal recursive version of unqualified_column_for, handling Strings inside
@@ -32,22 +32,10 @@ module Sequel
32
32
  # a single hash argument and returns the object you want #each to return.
33
33
  attr_reader :row_proc
34
34
 
35
- # Load an extension into the receiver. In addition to requiring the extension file, this
36
- # also modifies the dataset to work with the extension (usually extending it with a
37
- # module defined in the extension file). If no related extension file exists or the
38
- # extension does not have specific support for Database objects, an Error will be raised.
39
- # Returns self.
35
+ # Like #extension, but modifies and returns the receiver instead of returning a modified clone.
40
36
  def extension!(*exts)
41
37
  raise_if_frozen!
42
- Sequel.extension(*exts)
43
- exts.each do |ext|
44
- if pr = Sequel.synchronize{EXTENSIONS[ext]}
45
- pr.call(self)
46
- else
47
- raise(Error, "Extension #{ext} does not have specific support handling individual datasets (try: Sequel.extension #{ext.inspect})")
48
- end
49
- end
50
- self
38
+ _extension!(exts)
51
39
  end
52
40
 
53
41
  # Avoid self-referential dataset by cloning.
@@ -92,6 +80,19 @@ module Sequel
92
80
 
93
81
  private
94
82
 
83
+ # Load the extensions into the receiver, without checking if the receiver is frozen.
84
+ def _extension!(exts)
85
+ Sequel.extension(*exts)
86
+ exts.each do |ext|
87
+ if pr = Sequel.synchronize{EXTENSIONS[ext]}
88
+ pr.call(self)
89
+ else
90
+ raise(Error, "Extension #{ext} does not have specific support handling individual datasets (try: Sequel.extension #{ext.inspect})")
91
+ end
92
+ end
93
+ self
94
+ end
95
+
95
96
  # Modify the receiver with the results of sending the meth, args, and block
96
97
  # to the receiver and merging the options of the resulting dataset into
97
98
  # the receiver's options.
@@ -148,9 +148,13 @@ module Sequel
148
148
  exclude(*cond, &block)
149
149
  end
150
150
 
151
- # Return a clone of the dataset loaded with the extensions, see #extension!.
151
+ # Return a clone of the dataset loaded with the given dataset extensions.
152
+ # If no related extension file exists or the extension does not have
153
+ # specific support for Dataset objects, an Error will be raised.
152
154
  def extension(*exts)
153
- clone.extension!(*exts)
155
+ c = clone
156
+ c.send(:_extension!, exts)
157
+ c
154
158
  end
155
159
 
156
160
  # Alias for where.
@@ -611,9 +615,7 @@ module Sequel
611
615
  # ds.all # => [{2=>:id}]
612
616
  # ds.naked.all # => [{:id=>2}]
613
617
  def naked
614
- ds = clone
615
- ds.row_proc = nil
616
- ds
618
+ with_row_proc(nil)
617
619
  end
618
620
 
619
621
  # Returns a copy of the dataset with a specified order. Can be safely combined with limit.
@@ -1001,6 +1003,24 @@ module Sequel
1001
1003
  end
1002
1004
  end
1003
1005
 
1006
+ # Return a clone of the dataset extended with the given modules.
1007
+ def with_extend(*mods)
1008
+ c = clone
1009
+ c.extend(*mods)
1010
+ c
1011
+ end
1012
+
1013
+ # Returns a cloned dataset with the given row_proc.
1014
+ #
1015
+ # ds = DB[:items]
1016
+ # ds.all # => [{:id=>2}]
1017
+ # ds.with_row_proc(proc(&:invert)).all # => [{2=>:id}]
1018
+ def with_row_proc(callable)
1019
+ c = clone
1020
+ c.instance_variable_set(:@row_proc, callable)
1021
+ c
1022
+ end
1023
+
1004
1024
  # Returns a copy of the dataset with the static SQL used. This is useful if you want
1005
1025
  # to keep the same row_proc/graph, but change the SQL used to custom SQL.
1006
1026
  #
@@ -343,9 +343,7 @@ module Sequel
343
343
  # This allows the code to handle schema qualified tables,
344
344
  # without quoting all table names.
345
345
  def constraint_validations_literal_table(table)
346
- ds = dataset
347
- ds.quote_identifiers = false
348
- ds.literal(table)
346
+ dataset.with_quote_identifiers(false).literal(table)
349
347
  end
350
348
 
351
349
  # Before creating the table, add constraints for all of the
@@ -71,7 +71,12 @@ module Sequel
71
71
  # SQL is appened to the query after the comment is added,
72
72
  # it will become part of the comment unless it is preceded
73
73
  # by a newline.
74
- sql << comment
74
+ if sql.frozen?
75
+ sql += comment
76
+ sql.freeze
77
+ else
78
+ sql << comment
79
+ end
75
80
  end
76
81
  sql
77
82
  end
@@ -197,7 +197,13 @@ module Sequel
197
197
  # Return an dataset that will load the appropriate associated objects for
198
198
  # the given object using this association.
199
199
  def association_dataset_for(object)
200
- associated_dataset.where(predicate_keys.zip(predicate_key_values(object)))
200
+ condition = if can_have_associated_objects?(object)
201
+ predicate_keys.zip(predicate_key_values(object))
202
+ else
203
+ false
204
+ end
205
+
206
+ associated_dataset.where(condition)
201
207
  end
202
208
 
203
209
  ASSOCIATION_DATASET_PROC = proc{|r| r.association_dataset_for(self)}
@@ -1043,7 +1043,7 @@ module Sequel
1043
1043
  set_columns(cols)
1044
1044
  # Also set the columns for the dataset, so the dataset
1045
1045
  # doesn't have to do a query to get them.
1046
- dataset.instance_variable_set(:@columns, cols)
1046
+ dataset.send(:columns=, cols)
1047
1047
  end
1048
1048
  else
1049
1049
  # If the dataset uses multiple tables or custom sql or getting
@@ -88,7 +88,7 @@ module Sequel
88
88
  #
89
89
  # a = Employee.first
90
90
  # a.values # {:id=>1, name=>'S', :kind=>'CEO'}
91
- # a.refresh.values # {:id=>1, name=>'S', :kind=>'Executive', :num_staff=>4, :num_managers=>2}
91
+ # a.refresh.values # {:id=>1, name=>'S', :kind=>'CEO', :num_staff=>4, :num_managers=>2}
92
92
  #
93
93
  # You can also load directly from a subclass:
94
94
  #
@@ -315,6 +315,11 @@ module Sequel
315
315
  self
316
316
  end
317
317
 
318
+ # Don't allow use of prepared statements.
319
+ def use_prepared_statements_for?(type)
320
+ false
321
+ end
322
+
318
323
  private
319
324
 
320
325
  def cti_this(model)
@@ -360,11 +365,6 @@ module Sequel
360
365
  cti_this(m).update(h) unless h.empty?
361
366
  end
362
367
  end
363
-
364
- # Don't allow use of prepared statements.
365
- def use_prepared_statements_for?(type)
366
- false
367
- end
368
368
  end
369
369
  end
370
370
  end
@@ -126,7 +126,7 @@ module Sequel
126
126
  super
127
127
  model.hook_blocks(:after_destroy){|b| instance_eval(&b)}
128
128
  if model.has_hooks?(:after_destroy_commit)
129
- db.after_rollback{model.hook_blocks(:after_destroy_commit){|b| instance_eval(&b)}}
129
+ db.after_commit{model.hook_blocks(:after_destroy_commit){|b| instance_eval(&b)}}
130
130
  end
131
131
  end
132
132
 
@@ -134,7 +134,7 @@ module Sequel
134
134
  super
135
135
  model.hook_blocks(:after_save){|b| instance_eval(&b)}
136
136
  if model.has_hooks?(:after_commit)
137
- db.after_rollback{model.hook_blocks(:after_commit){|b| instance_eval(&b)}}
137
+ db.after_commit{model.hook_blocks(:after_commit){|b| instance_eval(&b)}}
138
138
  end
139
139
  end
140
140