sequel 5.93.0 → 5.95.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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/lib/sequel/adapters/ado/access.rb +2 -2
  3. data/lib/sequel/adapters/ado.rb +1 -1
  4. data/lib/sequel/adapters/ibmdb.rb +1 -1
  5. data/lib/sequel/adapters/jdbc/mysql.rb +2 -2
  6. data/lib/sequel/adapters/jdbc/oracle.rb +1 -1
  7. data/lib/sequel/adapters/jdbc/sqlite.rb +1 -1
  8. data/lib/sequel/adapters/jdbc/sqlserver.rb +1 -1
  9. data/lib/sequel/adapters/jdbc.rb +21 -7
  10. data/lib/sequel/adapters/mysql.rb +1 -1
  11. data/lib/sequel/adapters/mysql2.rb +2 -2
  12. data/lib/sequel/adapters/odbc.rb +1 -1
  13. data/lib/sequel/adapters/shared/db2.rb +8 -3
  14. data/lib/sequel/adapters/shared/mssql.rb +1 -1
  15. data/lib/sequel/adapters/shared/mysql.rb +2 -2
  16. data/lib/sequel/adapters/shared/postgres.rb +191 -22
  17. data/lib/sequel/adapters/shared/sqlite.rb +3 -3
  18. data/lib/sequel/adapters/tinytds.rb +1 -1
  19. data/lib/sequel/adapters/trilogy.rb +1 -1
  20. data/lib/sequel/connection_pool/sharded_timed_queue.rb +6 -0
  21. data/lib/sequel/connection_pool/timed_queue.rb +6 -1
  22. data/lib/sequel/core.rb +1 -1
  23. data/lib/sequel/database/misc.rb +3 -3
  24. data/lib/sequel/database/query.rb +1 -1
  25. data/lib/sequel/database/schema_generator.rb +66 -10
  26. data/lib/sequel/database/schema_methods.rb +79 -28
  27. data/lib/sequel/dataset/query.rb +10 -2
  28. data/lib/sequel/dataset/sql.rb +1 -1
  29. data/lib/sequel/extensions/async_thread_pool.rb +1 -1
  30. data/lib/sequel/extensions/caller_logging.rb +1 -3
  31. data/lib/sequel/extensions/eval_inspect.rb +1 -1
  32. data/lib/sequel/extensions/inflector.rb +2 -2
  33. data/lib/sequel/extensions/migration.rb +1 -1
  34. data/lib/sequel/extensions/pg_hstore.rb +1 -1
  35. data/lib/sequel/extensions/provenance.rb +1 -3
  36. data/lib/sequel/extensions/schema_dumper.rb +1 -1
  37. data/lib/sequel/plugins/class_table_inheritance_constraint_validations.rb +82 -0
  38. data/lib/sequel/plugins/constraint_validations.rb +15 -10
  39. data/lib/sequel/plugins/json_serializer.rb +3 -3
  40. data/lib/sequel/plugins/unused_associations.rb +4 -2
  41. data/lib/sequel/plugins/validation_class_methods.rb +1 -1
  42. data/lib/sequel/version.rb +1 -1
  43. metadata +3 -2
@@ -36,13 +36,13 @@ module Sequel
36
36
 
37
37
  # Register a hook that will be run when a new Database is instantiated. It is
38
38
  # called with the new database handle.
39
- def self.after_initialize(&block)
40
- raise Error, "must provide block to after_initialize" unless block
39
+ def self.after_initialize
40
+ raise Error, "must provide block to after_initialize" unless defined?(yield)
41
41
  Sequel.synchronize do
42
42
  previous = @initialize_hook
43
43
  @initialize_hook = proc do |db|
44
44
  previous.call(db)
45
- block.call(db)
45
+ yield(db)
46
46
  end
47
47
  end
48
48
  end
@@ -269,7 +269,7 @@ module Sequel
269
269
  def column_schema_to_ruby_default(default, type)
270
270
  return default unless default.is_a?(String)
271
271
  if COLUMN_SCHEMA_DATETIME_TYPES.include?(type)
272
- if /now|today|CURRENT|getdate|\ADate\(\)\z/i.match(default)
272
+ if /now|today|CURRENT|getdate|\ADate\(\)\z/i =~ default
273
273
  if type == :date
274
274
  return Sequel::CURRENT_DATE
275
275
  else
@@ -118,6 +118,11 @@ module Sequel
118
118
  # that this column references. Unnecessary if this column
119
119
  # references the primary key of the associated table, except if you are
120
120
  # using MySQL.
121
+ # :not_null :: Similar to setting <tt>null: false</tt>, but you can provide a hash value
122
+ # to specify options for the NOT NULL constraint on PostgreSQL 18+:
123
+ # :name :: The name of the NOT NULL constraint
124
+ # :no_inherit :: Set NO INHERIT on the constraint, so it will not propogate to
125
+ # child tables.
121
126
  # :null :: Mark the column as allowing NULL values (if true),
122
127
  # or not allowing NULL values (if false). The default is to allow NULL values.
123
128
  # :on_delete :: Specify the behavior of this column when being deleted
@@ -126,17 +131,27 @@ module Sequel
126
131
  # (:restrict, :cascade, :set_null, :set_default, :no_action).
127
132
  # :primary_key :: Make the column as a single primary key column. This should not
128
133
  # be used if you want a single autoincrementing primary key column
129
- # (use the primary_key method in that case).
130
- # :primary_key_constraint_name :: The name to give the primary key constraint
131
- # :primary_key_deferrable :: Similar to :deferrable, but for the primary key constraint
132
- # if :primary_key is used.
134
+ # (use the primary_key method in that case). Can be a hash to provide
135
+ # options for the constraint:
136
+ # :name :: The name to give the primary key constraint.
137
+ # :deferrable :: Sets whether the primary key constraint is deferrable.
138
+ # :include :: Include additional columns in the underlying index, to
139
+ # allow for index-only scans in more cases (PostgreSQL 11+).
140
+ # :primary_key_constraint_name :: Older option to name primary key constraint.
141
+ # :primary_key_deferrable :: Older option to set primary key constraint as deferrable.
133
142
  # :type :: Overrides the type given as the argument. Generally not used by column
134
143
  # itself, but can be passed as an option to other methods that call column.
135
144
  # :unique :: Mark the column as unique, generally has the same effect as
136
- # creating a unique index on the column.
137
- # :unique_constraint_name :: The name to give the unique key constraint
138
- # :unique_deferrable :: Similar to :deferrable, but for the unique constraint if :unique
139
- # is used.
145
+ # creating a unique index on the column. Can be a hash to provide options for
146
+ # the constraint:
147
+ # :name :: The name to give the unique constraint.
148
+ # :deferrable :: Sets whether the unique constraint is deferrable.
149
+ # :include :: Include additional columns in the underlying index, to
150
+ # allow for index-only scans in more cases (PostgreSQL 11+).
151
+ # :nulls_not_distinct :: Use NULLS NOT DISTINCT to setup a constraint where NULL
152
+ # entries are considered distinct (PostgreSQL 15+)
153
+ # :unique_constraint_name :: Older option to name unique constraint.
154
+ # :unique_deferrable :: Older option to set unique constraint as deferrable.
140
155
  #
141
156
  # PostgreSQL specific options:
142
157
  #
@@ -178,6 +193,9 @@ module Sequel
178
193
  # :deferrable :: Whether the CHECK constraint should be marked DEFERRABLE.
179
194
  #
180
195
  # PostgreSQL specific options:
196
+ # :no_inherit :: Set NO INHERIT on the constraint, so it will not propogate to
197
+ # child tables.
198
+ # :not_enforced :: Whether the CHECK constraint should be marked NOT ENFORCED.
181
199
  # :not_valid :: Whether the CHECK constraint should be marked NOT VALID.
182
200
  def constraint(name, *args, &block)
183
201
  opts = name.is_a?(Hash) ? name : {:name=>name}
@@ -197,9 +215,21 @@ module Sequel
197
215
  #
198
216
  # :foreign_key_constraint_name :: The name to give the foreign key constraint
199
217
  #
218
+ # PostgreSQL specific options:
219
+ #
220
+ # :not_enforced :: Whether the foreign key constraint should be marked NOT ENFORCED.
221
+ #
200
222
  # If you want a foreign key constraint without adding a column (usually because it is a
201
- # composite foreign key), you can provide an array of columns as the first argument, and
202
- # you can provide the :name option to name the constraint:
223
+ # composite foreign key), you can provide an array of columns as the first argument.
224
+ # This changes the method to accept constraint options instead of column options.
225
+ # You can provide the :name option to name the constraint.
226
+ #
227
+ # PostgreSQL specific options:
228
+ #
229
+ # :not_enforced :: Whether the foreign key constraint should be marked NOT ENFORCED.
230
+ # :period :: Use PERIOD to setup a constraint where the final column is a range
231
+ # or multirange column, and the range or multirange is covered by
232
+ # existing ranges in the referenced table (which can be in separate rows).
203
233
  #
204
234
  # foreign_key([:artist_name, :artist_location], :artists, name: :artist_fk)
205
235
  # # ADD CONSTRAINT artist_fk FOREIGN KEY (artist_name, artist_location) REFERENCES artists
@@ -303,6 +333,13 @@ module Sequel
303
333
  # :keep_order :: For non-composite primary keys, respects the existing order of
304
334
  # columns, overriding the default behavior of making the primary
305
335
  # key the first column.
336
+ #
337
+ # PostgreSQL specific options:
338
+ #
339
+ # :include :: Include additional columns in the underlying index, to
340
+ # allow for index-only scans in more cases (PostgreSQL 11+).
341
+ # :without_overlaps :: Use WITHOUT OVERLAPS clause to specify an exclusion constraint
342
+ # on the final column (PostgreSQL 18+, composite primary keys only).
306
343
  #
307
344
  # Examples:
308
345
  # primary_key(:id)
@@ -346,6 +383,15 @@ module Sequel
346
383
  #
347
384
  # Supports the same :deferrable option as #column. The :name option can be used
348
385
  # to name the constraint.
386
+ #
387
+ # PostgreSQL specific options:
388
+ #
389
+ # :include :: Include additional columns in the underlying index, to
390
+ # allow for index-only scans in more cases (PostgreSQL 11+).
391
+ # :nulls_not_distinct :: Use NULLS NOT DISTINCT to setup a constraint where NULL
392
+ # entries are considered distinct (PostgreSQL 15+)
393
+ # :without_overlaps :: Use WITHOUT OVERLAPS clause to specify an exclusion constraint
394
+ # on the final column (PostgreSQL 18+, composite unique only).
349
395
  def unique(columns, opts = OPTS)
350
396
  constraints << {:type => :unique, :columns => Array(columns)}.merge!(opts)
351
397
  nil
@@ -433,7 +479,13 @@ module Sequel
433
479
  #
434
480
  # PostgreSQL specific options:
435
481
  #
482
+ # :include :: Include additional columns in the underlying index, to
483
+ # allow for index-only scans in more cases (PostgreSQL 11+).
484
+ # :nulls_not_distinct :: Use NULLS NOT DISTINCT to setup a constraint where NULL
485
+ # entries are considered distinct (PostgreSQL 15+)
436
486
  # :using_index :: Use the USING INDEX clause to specify an existing unique index
487
+ # :without_overlaps :: Use WITHOUT OVERLAPS clause to specify an exclusion constraint
488
+ # on the final column (PostgreSQL 18+, composite unique constraints only).
437
489
  def add_unique_constraint(columns, opts = OPTS)
438
490
  @operations << {:op => :add_constraint, :type => :unique, :columns => Array(columns)}.merge!(opts)
439
491
  nil
@@ -490,7 +542,11 @@ module Sequel
490
542
  #
491
543
  # PostgreSQL specific options:
492
544
  #
545
+ # :include :: Include additional columns in the underlying index, to
546
+ # allow for index-only scans in more cases (PostgreSQL 11+).
493
547
  # :using_index :: Use the USING INDEX clause to specify an existing unique index
548
+ # :without_overlaps :: Use WITHOUT OVERLAPS clause to specify an exclusion constraint
549
+ # on the final column (PostgreSQL 18+, composite primary keys only).
494
550
  def add_primary_key(name, opts = OPTS)
495
551
  return add_composite_primary_key(name, opts) if name.is_a?(Array)
496
552
  opts = @db.serial_primary_key_options.merge(opts)
@@ -592,7 +592,12 @@ module Sequel
592
592
 
593
593
  # Add null/not null SQL fragment to column creation SQL.
594
594
  def column_definition_null_sql(sql, column)
595
- null = column.fetch(:null, column[:allow_null])
595
+ null = if column[:not_null]
596
+ false
597
+ else
598
+ column.fetch(:null, column[:allow_null])
599
+ end
600
+
596
601
  if null.nil? && !can_add_primary_key_constraint_on_nullable_columns? && column[:primary_key]
597
602
  null = false
598
603
  end
@@ -605,36 +610,71 @@ module Sequel
605
610
  end
606
611
  end
607
612
 
608
- # Add primary key SQL fragment to column creation SQL.
613
+ # Add primary key SQL fragment to column creation SQL if column is a primary key.
609
614
  def column_definition_primary_key_sql(sql, column)
610
- if column[:primary_key]
611
- if name = column[:primary_key_constraint_name]
612
- sql << " CONSTRAINT #{quote_identifier(name)}"
613
- end
614
- sql << " " << primary_key_constraint_sql_fragment(column)
615
- constraint_deferrable_sql_append(sql, column[:primary_key_deferrable])
616
- end
615
+ column_definition_add_primary_key_sql(sql, column) if column[:primary_key]
616
+ end
617
+
618
+ # Add primary key SQL fragment to column creation SQL (column should be a primary key).
619
+ def column_definition_add_primary_key_sql(sql, column)
620
+ constraint = column_definition_constraint_hash(column, :primary_key)
621
+ append_named_constraint_prefix_sql(sql, constraint[:name])
622
+ column_definition_append_primary_key_sql(sql, constraint)
623
+ constraint_deferrable_sql_append(sql, constraint[:deferrable])
624
+ end
625
+
626
+ def column_definition_append_primary_key_sql(sql, constraint)
627
+ sql << " " << primary_key_constraint_sql_fragment(constraint)
617
628
  end
618
629
 
619
- # Add foreign key reference SQL fragment to column creation SQL.
630
+ # Add foreign key reference SQL fragment to column creation SQL if column is a foreign key.
620
631
  def column_definition_references_sql(sql, column)
621
- if column[:table]
622
- if name = column[:foreign_key_constraint_name]
623
- sql << " CONSTRAINT #{quote_identifier(name)}"
624
- end
625
- sql << column_references_column_constraint_sql(column)
626
- end
632
+ column_definition_add_references_sql(sql, column) if column[:table]
633
+ end
634
+
635
+ # Add foreign key reference SQL fragment to column creation SQL (column should be a foreign key).
636
+ def column_definition_add_references_sql(sql, column)
637
+ append_named_constraint_prefix_sql(sql, column[:foreign_key_constraint_name])
638
+ sql << column_references_column_constraint_sql(column)
627
639
  end
628
640
 
629
- # Add unique constraint SQL fragment to column creation SQL.
641
+ # Add unique constraint SQL fragment to column creation SQL if column has a unique constraint.
630
642
  def column_definition_unique_sql(sql, column)
631
- if column[:unique]
632
- if name = column[:unique_constraint_name]
633
- sql << " CONSTRAINT #{quote_identifier(name)}"
634
- end
635
- sql << ' ' << unique_constraint_sql_fragment(column)
636
- constraint_deferrable_sql_append(sql, column[:unique_deferrable])
643
+ column_definition_add_unique_sql(sql, column) if column[:unique]
644
+ end
645
+
646
+ # Add unique constraint SQL fragment to column creation SQL (column should have unique constraint).
647
+ def column_definition_add_unique_sql(sql, column)
648
+ constraint = column_definition_constraint_hash(column, :unique)
649
+ append_named_constraint_prefix_sql(sql, constraint[:name])
650
+ column_definition_append_unique_sql(sql, constraint)
651
+ constraint_deferrable_sql_append(sql, constraint[:deferrable])
652
+ end
653
+
654
+ def column_definition_append_unique_sql(sql, constraint)
655
+ sql << ' ' << unique_constraint_sql_fragment(constraint)
656
+ end
657
+
658
+ # Add the name of the constraint to the column creation SQL.
659
+ def append_named_constraint_prefix_sql(sql, name)
660
+ sql << " CONSTRAINT #{quote_identifier(name)}" if name
661
+ end
662
+
663
+ # Return a hash of constraint options for the primary key or column
664
+ # unique constraint.
665
+ def column_definition_constraint_hash(column, prefix)
666
+ constraint = column[prefix]
667
+ constraint = constraint.is_a?(Hash) ? constraint.dup : {}
668
+
669
+ if name = column[:"#{prefix}_constraint_name"]
670
+ constraint[:name] = name
671
+ end
672
+
673
+ if column.has_key?(:"#{prefix}_deferrable")
674
+ constraint[:deferrable] = column[:"#{prefix}_deferrable"]
637
675
  end
676
+
677
+ constraint
638
678
  end
639
679
 
640
680
  # SQL for all given columns, used inside a CREATE TABLE block.
@@ -651,12 +691,16 @@ module Sequel
651
691
  def column_references_sql(column)
652
692
  sql = String.new
653
693
  sql << " REFERENCES #{quote_schema_table(column[:table])}"
654
- sql << "(#{Array(column[:key]).map{|x| quote_identifier(x)}.join(', ')})" if column[:key]
694
+ column_references_append_key_sql(sql, column) if column[:key]
655
695
  sql << " ON DELETE #{on_delete_clause(column[:on_delete])}" if column[:on_delete]
656
696
  sql << " ON UPDATE #{on_update_clause(column[:on_update])}" if column[:on_update]
657
697
  constraint_deferrable_sql_append(sql, column[:deferrable])
658
698
  sql
659
699
  end
700
+
701
+ def column_references_append_key_sql(sql, column)
702
+ sql << "(#{Array(column[:key]).map{|x| quote_identifier(x)}.join(', ')})"
703
+ end
660
704
 
661
705
  # SQL fragment for table foreign key references (table constraints)
662
706
  def column_references_table_constraint_sql(constraint)
@@ -677,7 +721,7 @@ module Sequel
677
721
  check = constraint[:check]
678
722
  check = check.first if check.is_a?(Array) && check.length == 1
679
723
  check = filter_expr(check)
680
- check = "(#{check})" unless check[0..0] == '(' && check[-1..-1] == ')'
724
+ check = "(#{check})" unless check.start_with?('(') && check.end_with?(')')
681
725
  sql << "CHECK #{check}"
682
726
  when :primary_key
683
727
  sql << "#{primary_key_constraint_sql_fragment(constraint)} #{literal(constraint[:columns])}"
@@ -733,17 +777,24 @@ module Sequel
733
777
  def create_table_sql(name, generator, options)
734
778
  unless supports_named_column_constraints?
735
779
  # Split column constraints into table constraints if they have a name
780
+ fk_opt_keys = [:key, :on_delete, :on_update, :deferrable]
736
781
  generator.columns.each do |c|
737
782
  if (constraint_name = c.delete(:foreign_key_constraint_name)) && (table = c.delete(:table))
738
783
  opts = {}
739
784
  opts[:name] = constraint_name
740
- [:key, :on_delete, :on_update, :deferrable].each{|k| opts[k] = c[k]}
785
+ fk_opt_keys.each{|k| opts[k] = c[k]}
741
786
  generator.foreign_key([c[:name]], table, opts)
742
787
  end
743
- if (constraint_name = c.delete(:unique_constraint_name)) && c.delete(:unique)
788
+
789
+ if c[:unique].is_a?(Hash) && c[:unique][:name]
790
+ generator.unique(c[:name], c.delete(:unique))
791
+ elsif (constraint_name = c.delete(:unique_constraint_name)) && c.delete(:unique)
744
792
  generator.unique(c[:name], :name=>constraint_name)
745
793
  end
746
- if (constraint_name = c.delete(:primary_key_constraint_name)) && c.delete(:primary_key)
794
+
795
+ if c[:primary_key].is_a?(Hash) && c[:primary_key][:name]
796
+ generator.primary_key([c[:name]], c.delete(:primary_key))
797
+ elsif (constraint_name = c.delete(:primary_key_constraint_name)) && c.delete(:primary_key)
747
798
  generator.primary_key([c[:name]], :name=>constraint_name)
748
799
  end
749
800
  end
@@ -231,8 +231,7 @@ module Sequel
231
231
  #
232
232
  # DB[:table].for_update # SELECT * FROM table FOR UPDATE
233
233
  def for_update
234
- return self if opts[:lock] == :update
235
- cached_dataset(:_for_update_ds){lock_style(:update)}
234
+ cached_lock_style_dataset(:_for_update_ds, :update)
236
235
  end
237
236
 
238
237
  # Returns a copy of the dataset with the source changed. If no
@@ -1462,6 +1461,15 @@ module Sequel
1462
1461
  end
1463
1462
  end
1464
1463
 
1464
+ # Internals of for_update and adapter-specific lock methods.
1465
+ # Returns receiver if it already uses this lock style, and a cached
1466
+ # dataset using the given key otherwise. The key could be derived from
1467
+ # the style, but doing so would require allocation, so pass it in as
1468
+ # an argument.
1469
+ def cached_lock_style_dataset(key, style)
1470
+ opts[:lock] == style ? self : cached_dataset(key){lock_style(style)}
1471
+ end
1472
+
1465
1473
  # The default :qualify option to use for join tables if one is not specified.
1466
1474
  def default_join_table_qualification
1467
1475
  :symbol
@@ -1339,7 +1339,7 @@ module Sequel
1339
1339
  # SQL fragment specifying a JOIN type, converts underscores to
1340
1340
  # spaces and upcases.
1341
1341
  def join_type_sql(join_type)
1342
- "#{join_type.to_s.gsub('_', ' ').upcase} JOIN"
1342
+ "#{join_type.to_s.tr('_', ' ').upcase} JOIN"
1343
1343
  end
1344
1344
 
1345
1345
  # Append USING clause for JOIN USING
@@ -357,7 +357,7 @@ module Sequel
357
357
  define_singleton_method(:async_job_class){proxy_klass}
358
358
 
359
359
  queue = @async_thread_queue = Queue.new
360
- pool = @async_thread_pool = num_async_threads.times.map{JobProcessor.new(queue)}
360
+ pool = @async_thread_pool = Array.new(num_async_threads){JobProcessor.new(queue)}
361
361
  ObjectSpace.define_finalizer(db, JobProcessor.create_finalizer(queue, pool))
362
362
 
363
363
  extend_datasets(DatasetMethods)
@@ -60,9 +60,7 @@ module Sequel
60
60
  def external_caller_for_log
61
61
  ignore = caller_logging_ignore
62
62
  c = caller.find do |line|
63
- !(line.start_with?(SEQUEL_LIB_PATH) ||
64
- line.start_with?(RUBY_STDLIB) ||
65
- line.start_with?(INTERNAL) ||
63
+ !(line.start_with?(SEQUEL_LIB_PATH, RUBY_STDLIB, INTERNAL) ||
66
64
  (ignore && line =~ ignore))
67
65
  end
68
66
 
@@ -66,7 +66,7 @@ module Sequel
66
66
  # in the order they were defined.
67
67
  klass = self.class
68
68
  args = inspect_args.map do |arg|
69
- if arg.is_a?(String) && arg =~ /\A\*/
69
+ if arg.is_a?(String) && arg.start_with?('*')
70
70
  # Special case string arguments starting with *, indicating that
71
71
  # they should return an array to be splatted as the remaining arguments.
72
72
  # Allow calling private methods to get inspect output.
@@ -159,7 +159,7 @@ class String
159
159
  # Example
160
160
  # "puni_puni".dasherize #=> "puni-puni"
161
161
  def dasherize
162
- gsub('_', '-')
162
+ tr('_', '-')
163
163
  end
164
164
 
165
165
  # Removes the module part from the expression in the string
@@ -189,7 +189,7 @@ class String
189
189
  # "employee_salary" #=> "Employee salary"
190
190
  # "author_id" #=> "Author"
191
191
  def humanize
192
- gsub(/_id$/, "").gsub('_', " ").capitalize
192
+ gsub(/_id$/, "").tr('_', " ").capitalize
193
193
  end
194
194
 
195
195
  # Returns the plural form of the word in the string.
@@ -845,7 +845,7 @@ module Sequel
845
845
  begin
846
846
  db.create_table(table){String c, :primary_key=>true}
847
847
  rescue Sequel::DatabaseError => e
848
- if db.database_type == :mysql && e.message =~ /max key length/
848
+ if db.database_type == :mysql && e.message.include?('max key length')
849
849
  # Handle case where MySQL is used with utf8mb4 charset default, which
850
850
  # only allows a maximum length of about 190 characters for string
851
851
  # primary keys due to InnoDB limitations.
@@ -136,7 +136,7 @@ module Sequel
136
136
  module DatabaseMethods
137
137
  def self.extended(db)
138
138
  db.instance_exec do
139
- add_named_conversion_proc(:hstore, &HStore.method(:parse))
139
+ add_named_conversion_proc(:hstore){|v| HStore.parse(v)}
140
140
  @schema_type_classes[:hstore] = HStore
141
141
  end
142
142
  end
@@ -97,9 +97,7 @@ module Sequel
97
97
  def provenance_source
98
98
  ignore = db.opts[:provenance_caller_ignore]
99
99
  caller.find do |line|
100
- !(line.start_with?(SEQUEL_LIB_PATH) ||
101
- line.start_with?(RUBY_STDLIB) ||
102
- line.start_with?(INTERNAL) ||
100
+ !(line.start_with?(SEQUEL_LIB_PATH, RUBY_STDLIB, INTERNAL) ||
103
101
  (ignore && line =~ ignore))
104
102
  end
105
103
  end
@@ -221,7 +221,7 @@ END_MIG
221
221
  else
222
222
  col_opts = if options[:same_db]
223
223
  h = {:type=>schema[:db_type]}
224
- if database_type == :mysql && h[:type] =~ /\Atimestamp/
224
+ if database_type == :mysql && h[:type].start_with?("timestamp")
225
225
  h[:null] = true
226
226
  end
227
227
  if database_type == :mssql && schema[:max_length]
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'constraint_validations'
4
+ require_relative 'class_table_inheritance'
5
+
6
+ module Sequel
7
+ module Plugins
8
+ # = Overview
9
+ #
10
+ # The class_table_inheritance_constraint_validations plugin extends the
11
+ # constraint_validations plugin to work correctly with the
12
+ # class_table_inheritance plugin. It ensures that constraint_validations
13
+ # are loaded from all tables in the class table inheritance hierarchy,
14
+ # not just the base table.
15
+ #
16
+ # = Example
17
+ #
18
+ # For example, with this hierarchy, where each model has its own table with
19
+ # constraint validations:
20
+ #
21
+ # Employee
22
+ # / \
23
+ # Staff Manager
24
+ # |
25
+ # Executive
26
+ #
27
+ # # Loads constraint_validations from the employees table
28
+ # class Employee < Sequel::Model
29
+ # plugin :class_table_inheritance
30
+ # plugin :constraint_validations
31
+ # plugin :class_table_inheritance_constraint_validations
32
+ # end
33
+ #
34
+ # # Loads constraint_validations from managers and employees tables
35
+ # class Manager < Employee
36
+ # end
37
+ #
38
+ # # Loads constraint_validations from executives, managers, and
39
+ # # employees tables
40
+ # class Executive < Manager
41
+ # end
42
+ #
43
+ # # Loads constraint_validations from staff and employees tables
44
+ # class Staff < Employee
45
+ # end
46
+ module ClassTableInheritanceConstraintValidations
47
+ def self.apply(model)
48
+ unless ConstraintValidations::InstanceMethods > model && ClassTableInheritance::InstanceMethods > model
49
+ raise Error, "must load the constraint_validations and class_table_inheritance plugins into #{model} before loading class_table_inheritance_constraint_validations plugin"
50
+ end
51
+ end
52
+
53
+ module ClassMethods
54
+ private
55
+
56
+ def inherited(subclass)
57
+ super
58
+
59
+ # constraint_validations will parse_constraint_validations in the
60
+ # classes after_set_dataset hook. That runs before cti_tables are
61
+ # updated for subclasses in class_table_inheritance's inherited
62
+ # so re-parsing them here.
63
+ subclass.send(:parse_constraint_validations)
64
+ end
65
+
66
+ def parse_constraint_validations_dataset
67
+ reflections = {}
68
+ allow_missing_columns = db_schema.select{|col, sch| sch[:allow_null] == false && nil != sch[:default]}.map(&:first)
69
+ hash = Sequel.synchronize{db.constraint_validations}
70
+ cv = []
71
+ ds = @dataset.with_quote_identifiers(false)
72
+ cti_tables.each do |table_name|
73
+ table_name = ds.literal(table_name)
74
+ cv += (Sequel.synchronize{hash[table_name]} || []).map{|r| constraint_validation_array(r, reflections, allow_missing_columns)}
75
+ end
76
+ @constraint_validations = cv
77
+ @constraint_validation_reflections = reflections
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -113,7 +113,7 @@ module Sequel
113
113
  def parse_constraint_validations
114
114
  db.extension(:_model_constraint_validations)
115
115
 
116
- unless hash = Sequel.synchronize{db.constraint_validations}
116
+ unless Sequel.synchronize{db.constraint_validations}
117
117
  hash = {}
118
118
  db.from(constraint_validations_table).each do |r|
119
119
  (hash[r[:table]] ||= []) << r
@@ -121,14 +121,7 @@ module Sequel
121
121
  Sequel.synchronize{db.constraint_validations = hash}
122
122
  end
123
123
 
124
- if @dataset
125
- ds = @dataset.with_quote_identifiers(false)
126
- table_name = ds.literal(ds.first_source_table)
127
- reflections = {}
128
- allow_missing_columns = db_schema.select{|col, sch| sch[:allow_null] == false && nil != sch[:default]}.map(&:first)
129
- @constraint_validations = (Sequel.synchronize{hash[table_name]} || []).map{|r| constraint_validation_array(r, reflections, allow_missing_columns)}
130
- @constraint_validation_reflections = reflections
131
- end
124
+ parse_constraint_validations_dataset if @dataset
132
125
  end
133
126
 
134
127
  # Given a specific database constraint validation metadata row hash, transform
@@ -164,7 +157,7 @@ module Sequel
164
157
  arg = constraint_validation_int_range(arg)
165
158
  type = :includes
166
159
  when *OPERATOR_MAP.keys
167
- arg = arg.to_i if type.to_s =~ /\Aint_/
160
+ arg = arg.to_i if type.to_s.start_with?('int_')
168
161
  operator = OPERATOR_MAP[type]
169
162
  type = :operator
170
163
  end
@@ -236,6 +229,18 @@ module Sequel
236
229
  Regexp.new(arg)
237
230
  end
238
231
  end
232
+
233
+ # If this model has associated dataset, use the model's table name
234
+ # to get the validations for just this model.
235
+ def parse_constraint_validations_dataset
236
+ ds = @dataset.with_quote_identifiers(false)
237
+ table_name = ds.literal(ds.first_source_table)
238
+ reflections = {}
239
+ allow_missing_columns = db_schema.select{|col, sch| sch[:allow_null] == false && nil != sch[:default]}.map(&:first)
240
+ hash = Sequel.synchronize{db.constraint_validations}
241
+ @constraint_validations = (Sequel.synchronize{hash[table_name]} || []).map{|r| constraint_validation_array(r, reflections, allow_missing_columns)}
242
+ @constraint_validation_reflections = reflections
243
+ end
239
244
  end
240
245
 
241
246
  module InstanceMethods
@@ -370,9 +370,9 @@ module Sequel
370
370
  end
371
371
 
372
372
  # Convert the receiver to a JSON data structure using the given arguments.
373
- def to_json_data(*args, &block)
374
- if block
375
- to_json(*args){|x| return block.call(x)}
373
+ def to_json_data(*args)
374
+ if defined?(yield)
375
+ to_json(*args){|x| return yield(x)}
376
376
  else
377
377
  to_json(*args){|x| return x}
378
378
  end
@@ -347,6 +347,8 @@ module Sequel
347
347
  coverage_data = options[:coverage_data] || Sequel.parse_json(File.binread(@unused_associations_coverage_file))
348
348
 
349
349
  unused_associations_data = {}
350
+ to_many_modification_methods = [:adder, :remover, :clearer]
351
+ modification_methods = [:setter, :adder, :remover, :clearer]
350
352
 
351
353
  ([self] + descendants).each do |sc|
352
354
  next unless cov_data = coverage_data[sc.name]
@@ -383,8 +385,8 @@ module Sequel
383
385
  if !info[:used]
384
386
  (unused_associations_data[sc.name] ||= {})[assoc.to_s] = 'unused'
385
387
  elsif unused = info[:unused]
386
- if unused.include?(:setter) || [:adder, :remover, :clearer].all?{|k| unused.include?(k)}
387
- [:setter, :adder, :remover, :clearer].each do |k|
388
+ if unused.include?(:setter) || to_many_modification_methods.all?{|k| unused.include?(k)}
389
+ modification_methods.each do |k|
388
390
  unused.delete(k)
389
391
  end
390
392
  unused << :read_only
@@ -208,7 +208,7 @@ module Sequel
208
208
  next if an && Array(v).all?(&:nil?)
209
209
  next if ab && Array(v).all?(&blank_meth)
210
210
  next if am && Array(a).all?{|x| !o.values.has_key?(x)}
211
- block.call(o,a,v)
211
+ yield(o, a, v)
212
212
  end
213
213
  else
214
214
  block
@@ -6,7 +6,7 @@ module Sequel
6
6
 
7
7
  # The minor version of Sequel. Bumped for every non-patch level
8
8
  # release, generally around once a month.
9
- MINOR = 93
9
+ MINOR = 95
10
10
 
11
11
  # The tiny version of Sequel. Usually 0, only bumped for bugfix
12
12
  # releases that fix regressions from previous versions.