sequel 3.38.0 → 3.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +62 -0
- data/README.rdoc +2 -2
- data/bin/sequel +12 -2
- data/doc/advanced_associations.rdoc +1 -1
- data/doc/association_basics.rdoc +13 -0
- data/doc/release_notes/3.39.0.txt +237 -0
- data/doc/schema_modification.rdoc +4 -4
- data/lib/sequel/adapters/jdbc/derby.rb +1 -0
- data/lib/sequel/adapters/mock.rb +5 -0
- data/lib/sequel/adapters/mysql.rb +8 -1
- data/lib/sequel/adapters/mysql2.rb +10 -3
- data/lib/sequel/adapters/postgres.rb +72 -8
- data/lib/sequel/adapters/shared/db2.rb +1 -0
- data/lib/sequel/adapters/shared/mssql.rb +57 -0
- data/lib/sequel/adapters/shared/mysql.rb +95 -19
- data/lib/sequel/adapters/shared/oracle.rb +14 -0
- data/lib/sequel/adapters/shared/postgres.rb +63 -24
- data/lib/sequel/adapters/shared/sqlite.rb +6 -9
- data/lib/sequel/connection_pool/sharded_threaded.rb +8 -3
- data/lib/sequel/connection_pool/threaded.rb +9 -4
- data/lib/sequel/database/query.rb +60 -48
- data/lib/sequel/database/schema_generator.rb +13 -6
- data/lib/sequel/database/schema_methods.rb +65 -12
- data/lib/sequel/dataset/actions.rb +22 -4
- data/lib/sequel/dataset/features.rb +5 -0
- data/lib/sequel/dataset/graph.rb +2 -3
- data/lib/sequel/dataset/misc.rb +2 -2
- data/lib/sequel/dataset/query.rb +0 -2
- data/lib/sequel/dataset/sql.rb +33 -12
- data/lib/sequel/extensions/constraint_validations.rb +451 -0
- data/lib/sequel/extensions/eval_inspect.rb +17 -2
- data/lib/sequel/extensions/pg_array_ops.rb +15 -5
- data/lib/sequel/extensions/pg_interval.rb +2 -2
- data/lib/sequel/extensions/pg_row_ops.rb +18 -0
- data/lib/sequel/extensions/schema_dumper.rb +3 -11
- data/lib/sequel/model/associations.rb +3 -2
- data/lib/sequel/model/base.rb +57 -13
- data/lib/sequel/model/exceptions.rb +20 -2
- data/lib/sequel/plugins/constraint_validations.rb +198 -0
- data/lib/sequel/plugins/defaults_setter.rb +15 -1
- data/lib/sequel/plugins/dirty.rb +2 -2
- data/lib/sequel/plugins/identity_map.rb +12 -8
- data/lib/sequel/plugins/subclasses.rb +19 -1
- data/lib/sequel/plugins/tree.rb +3 -3
- data/lib/sequel/plugins/validation_helpers.rb +24 -4
- data/lib/sequel/sql.rb +64 -24
- data/lib/sequel/timezones.rb +10 -2
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +26 -25
- data/spec/adapters/mysql_spec.rb +57 -23
- data/spec/adapters/oracle_spec.rb +34 -49
- data/spec/adapters/postgres_spec.rb +226 -128
- data/spec/adapters/sqlite_spec.rb +50 -49
- data/spec/core/connection_pool_spec.rb +22 -0
- data/spec/core/database_spec.rb +53 -47
- data/spec/core/dataset_spec.rb +36 -32
- data/spec/core/expression_filters_spec.rb +14 -2
- data/spec/core/mock_adapter_spec.rb +4 -0
- data/spec/core/object_graph_spec.rb +0 -13
- data/spec/core/schema_spec.rb +64 -5
- data/spec/core_extensions_spec.rb +1 -0
- data/spec/extensions/constraint_validations_plugin_spec.rb +196 -0
- data/spec/extensions/constraint_validations_spec.rb +316 -0
- data/spec/extensions/defaults_setter_spec.rb +24 -0
- data/spec/extensions/eval_inspect_spec.rb +9 -0
- data/spec/extensions/identity_map_spec.rb +11 -2
- data/spec/extensions/pg_array_ops_spec.rb +9 -0
- data/spec/extensions/pg_row_ops_spec.rb +11 -1
- data/spec/extensions/pg_row_plugin_spec.rb +4 -0
- data/spec/extensions/schema_dumper_spec.rb +8 -5
- data/spec/extensions/subclasses_spec.rb +14 -0
- data/spec/extensions/validation_helpers_spec.rb +15 -2
- data/spec/integration/dataset_test.rb +75 -1
- data/spec/integration/plugin_test.rb +146 -0
- data/spec/integration/schema_test.rb +34 -0
- data/spec/model/dataset_methods_spec.rb +38 -0
- data/spec/model/hooks_spec.rb +6 -0
- data/spec/model/validations_spec.rb +27 -2
- metadata +8 -2
|
@@ -21,6 +21,11 @@ module Sequel
|
|
|
21
21
|
# The default options for join table columns.
|
|
22
22
|
DEFAULT_JOIN_TABLE_COLUMN_OPTIONS = {:null=>false}
|
|
23
23
|
|
|
24
|
+
# The alter table operations that are combinable.
|
|
25
|
+
COMBINABLE_ALTER_TABLE_OPS = [:add_column, :drop_column, :rename_column,
|
|
26
|
+
:set_column_type, :set_column_default, :set_column_null,
|
|
27
|
+
:add_constraint, :drop_constraint]
|
|
28
|
+
|
|
24
29
|
# Adds a column to the specified table. This method expects a column name,
|
|
25
30
|
# a datatype and optionally a hash with additional constraints and options:
|
|
26
31
|
#
|
|
@@ -70,7 +75,7 @@ module Sequel
|
|
|
70
75
|
def alter_table(name, generator=nil, &block)
|
|
71
76
|
generator ||= alter_table_generator(&block)
|
|
72
77
|
remove_cached_schema(name)
|
|
73
|
-
|
|
78
|
+
apply_alter_table_generator(name, generator)
|
|
74
79
|
nil
|
|
75
80
|
end
|
|
76
81
|
|
|
@@ -333,17 +338,21 @@ module Sequel
|
|
|
333
338
|
|
|
334
339
|
# Apply the changes in the given alter table ops to the table given by name.
|
|
335
340
|
def apply_alter_table(name, ops)
|
|
336
|
-
alter_table_sql_list(name, ops).
|
|
341
|
+
alter_table_sql_list(name, ops).each{|sql| execute_ddl(sql)}
|
|
337
342
|
end
|
|
338
343
|
|
|
344
|
+
# Apply the operations in the given generator to the table given by name.
|
|
345
|
+
def apply_alter_table_generator(name, generator)
|
|
346
|
+
apply_alter_table(name, generator.operations)
|
|
347
|
+
end
|
|
348
|
+
|
|
339
349
|
# The class used for alter_table generators.
|
|
340
350
|
def alter_table_generator_class
|
|
341
351
|
Schema::AlterTableGenerator
|
|
342
352
|
end
|
|
343
353
|
|
|
344
|
-
#
|
|
345
|
-
|
|
346
|
-
def alter_table_sql(table, op)
|
|
354
|
+
# SQL fragment for given alter table operation.
|
|
355
|
+
def alter_table_op_sql(table, op)
|
|
347
356
|
quoted_name = quote_identifier(op[:name]) if op[:name]
|
|
348
357
|
alter_table_op = case op[:op]
|
|
349
358
|
when :add_column
|
|
@@ -358,24 +367,56 @@ module Sequel
|
|
|
358
367
|
"ALTER COLUMN #{quoted_name} SET DEFAULT #{literal(op[:default])}"
|
|
359
368
|
when :set_column_null
|
|
360
369
|
"ALTER COLUMN #{quoted_name} #{op[:null] ? 'DROP' : 'SET'} NOT NULL"
|
|
361
|
-
when :add_index
|
|
362
|
-
return index_definition_sql(table, op)
|
|
363
|
-
when :drop_index
|
|
364
|
-
return drop_index_sql(table, op)
|
|
365
370
|
when :add_constraint
|
|
366
371
|
"ADD #{constraint_definition_sql(op)}"
|
|
367
372
|
when :drop_constraint
|
|
368
373
|
"DROP CONSTRAINT #{quoted_name}#{' CASCADE' if op[:cascade]}"
|
|
369
374
|
else
|
|
370
|
-
raise Error, "Unsupported ALTER TABLE operation"
|
|
375
|
+
raise Error, "Unsupported ALTER TABLE operation: #{op[:op]}"
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
# The SQL to execute to modify the DDL for the given table name. op
|
|
380
|
+
# should be one of the operations returned by the AlterTableGenerator.
|
|
381
|
+
def alter_table_sql(table, op)
|
|
382
|
+
case op[:op]
|
|
383
|
+
when :add_index
|
|
384
|
+
index_definition_sql(table, op)
|
|
385
|
+
when :drop_index
|
|
386
|
+
drop_index_sql(table, op)
|
|
387
|
+
else
|
|
388
|
+
"ALTER TABLE #{quote_schema_table(table)} #{alter_table_op_sql(table, op)}"
|
|
371
389
|
end
|
|
372
|
-
"ALTER TABLE #{quote_schema_table(table)} #{alter_table_op}"
|
|
373
390
|
end
|
|
374
391
|
|
|
375
392
|
# Array of SQL DDL modification statements for the given table,
|
|
376
393
|
# corresponding to the DDL changes specified by the operations.
|
|
377
394
|
def alter_table_sql_list(table, operations)
|
|
378
|
-
|
|
395
|
+
if supports_combining_alter_table_ops?
|
|
396
|
+
grouped_ops = []
|
|
397
|
+
last_combinable = false
|
|
398
|
+
operations.each do |op|
|
|
399
|
+
if combinable_alter_table_op?(op)
|
|
400
|
+
if sql = alter_table_op_sql(table, op)
|
|
401
|
+
grouped_ops << [] unless last_combinable
|
|
402
|
+
grouped_ops.last << sql
|
|
403
|
+
last_combinable = true
|
|
404
|
+
end
|
|
405
|
+
elsif sql = alter_table_sql(table, op)
|
|
406
|
+
grouped_ops << sql
|
|
407
|
+
last_combinable = false
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
grouped_ops.map do |gop|
|
|
411
|
+
if gop.is_a?(Array)
|
|
412
|
+
"ALTER TABLE #{quote_schema_table(table)} #{gop.join(', ')}"
|
|
413
|
+
else
|
|
414
|
+
gop
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
else
|
|
418
|
+
operations.map{|op| alter_table_sql(table, op)}.flatten.compact
|
|
419
|
+
end
|
|
379
420
|
end
|
|
380
421
|
|
|
381
422
|
# The SQL string specify the autoincrement property, generally used by
|
|
@@ -458,6 +499,12 @@ module Sequel
|
|
|
458
499
|
"FOREIGN KEY #{literal(constraint[:columns])}#{column_references_sql(constraint)}"
|
|
459
500
|
end
|
|
460
501
|
|
|
502
|
+
# Whether the given alter table operation is combinable.
|
|
503
|
+
def combinable_alter_table_op?(op)
|
|
504
|
+
# Use a dynamic lookup for easier overriding in adapters
|
|
505
|
+
COMBINABLE_ALTER_TABLE_OPS.include?(op[:op])
|
|
506
|
+
end
|
|
507
|
+
|
|
461
508
|
# SQL DDL fragment specifying a constraint on a table.
|
|
462
509
|
def constraint_definition_sql(constraint)
|
|
463
510
|
sql = constraint[:name] ? "CONSTRAINT #{quote_identifier(constraint[:name])} " : ""
|
|
@@ -651,6 +698,12 @@ module Sequel
|
|
|
651
698
|
@schema_utility_dataset ||= dataset
|
|
652
699
|
end
|
|
653
700
|
|
|
701
|
+
# Whether the database supports combining multiple alter table
|
|
702
|
+
# operations into a single query, false by default.
|
|
703
|
+
def supports_combining_alter_table_ops?
|
|
704
|
+
false
|
|
705
|
+
end
|
|
706
|
+
|
|
654
707
|
# SQL DDL fragment for temporary table
|
|
655
708
|
def temporary_table_sql
|
|
656
709
|
self.class.const_get(:TEMPORARY)
|
|
@@ -93,12 +93,30 @@ module Sequel
|
|
|
93
93
|
columns
|
|
94
94
|
end
|
|
95
95
|
|
|
96
|
-
# Returns the number of records in the dataset.
|
|
96
|
+
# Returns the number of records in the dataset. If an argument is provided,
|
|
97
|
+
# it is used as the argument to count. If a block is provided, it is
|
|
98
|
+
# treated as a virtual row, and the result is used as the argument to
|
|
99
|
+
# count.
|
|
97
100
|
#
|
|
98
101
|
# DB[:table].count # SELECT COUNT(*) AS count FROM table LIMIT 1
|
|
99
102
|
# # => 3
|
|
100
|
-
|
|
101
|
-
|
|
103
|
+
# DB[:table].count(:column) # SELECT COUNT(column) AS count FROM table LIMIT 1
|
|
104
|
+
# # => 2
|
|
105
|
+
# DB[:table].count{foo(column)} # SELECT COUNT(foo(column)) AS count FROM table LIMIT 1
|
|
106
|
+
# # => 1
|
|
107
|
+
def count(arg=(no_arg=true), &block)
|
|
108
|
+
if no_arg
|
|
109
|
+
if block
|
|
110
|
+
arg = Sequel.virtual_row(&block)
|
|
111
|
+
aggregate_dataset.get{COUNT(arg).as(count)}
|
|
112
|
+
else
|
|
113
|
+
aggregate_dataset.get{COUNT(:*){}.as(count)}.to_i
|
|
114
|
+
end
|
|
115
|
+
elsif block
|
|
116
|
+
raise Error, 'cannot provide both argument and block to Dataset#count'
|
|
117
|
+
else
|
|
118
|
+
aggregate_dataset.get{COUNT(arg).as(count)}
|
|
119
|
+
end
|
|
102
120
|
end
|
|
103
121
|
|
|
104
122
|
# Deletes the records in the dataset. The returned value should be
|
|
@@ -311,7 +329,7 @@ module Sequel
|
|
|
311
329
|
# # INSERT INTO table (x) VALUES (1)
|
|
312
330
|
# # INSERT INTO table (x) VALUES (2)
|
|
313
331
|
#
|
|
314
|
-
# DB[:table].insert_multiple([{:x=>1}, {:x=>2}]){|row| row[:y] = row[:x] * 2}
|
|
332
|
+
# DB[:table].insert_multiple([{:x=>1}, {:x=>2}]){|row| row[:y] = row[:x] * 2; row }
|
|
315
333
|
# # => [6, 7]
|
|
316
334
|
# # INSERT INTO table (x, y) VALUES (1, 2)
|
|
317
335
|
# # INSERT INTO table (x, y) VALUES (2, 4)
|
|
@@ -114,6 +114,11 @@ module Sequel
|
|
|
114
114
|
supports_distinct_on?
|
|
115
115
|
end
|
|
116
116
|
|
|
117
|
+
# Whether the dataset supports pattern matching by regular expressions.
|
|
118
|
+
def supports_regexp?
|
|
119
|
+
false
|
|
120
|
+
end
|
|
121
|
+
|
|
117
122
|
# Whether the RETURNING clause is supported for the given type of query.
|
|
118
123
|
# +type+ can be :insert, :update, or :delete.
|
|
119
124
|
def supports_returning?(type)
|
data/lib/sequel/dataset/graph.rb
CHANGED
|
@@ -73,9 +73,8 @@ module Sequel
|
|
|
73
73
|
# alias the table. You will get an error if the the alias (or table) name is
|
|
74
74
|
# used more than once.
|
|
75
75
|
def graph(dataset, join_conditions = nil, options = {}, &block)
|
|
76
|
-
# Allow the use of a
|
|
76
|
+
# Allow the use of a dataset or symbol as the first argument
|
|
77
77
|
# Find the table name/dataset based on the argument
|
|
78
|
-
dataset = dataset.dataset if dataset.respond_to?(:dataset)
|
|
79
78
|
table_alias = options[:table_alias]
|
|
80
79
|
case dataset
|
|
81
80
|
when Symbol
|
|
@@ -91,7 +90,7 @@ module Sequel
|
|
|
91
90
|
table_alias ||= dataset_alias((@opts[:num_dataset_sources] || 0)+1)
|
|
92
91
|
end
|
|
93
92
|
else
|
|
94
|
-
raise Error, "The dataset argument should be a symbol
|
|
93
|
+
raise Error, "The dataset argument should be a symbol or dataset"
|
|
95
94
|
end
|
|
96
95
|
|
|
97
96
|
# Raise Sequel::Error with explanation that the table alias has been used
|
data/lib/sequel/dataset/misc.rb
CHANGED
|
@@ -31,9 +31,9 @@ module Sequel
|
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
# Define a hash value such that datasets with the same DB, opts, and SQL
|
|
34
|
-
# will be
|
|
34
|
+
# will be considered equal.
|
|
35
35
|
def ==(o)
|
|
36
|
-
o.is_a?(self.class) && db == o.db
|
|
36
|
+
o.is_a?(self.class) && db == o.db && opts == o.opts && sql == o.sql
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
# Alias for ==
|
data/lib/sequel/dataset/query.rb
CHANGED
|
@@ -459,7 +459,6 @@ module Sequel
|
|
|
459
459
|
# * type - The type of join to do (e.g. :inner)
|
|
460
460
|
# * table - Depends on type:
|
|
461
461
|
# * Dataset - a subselect is performed with an alias of tN for some value of N
|
|
462
|
-
# * Model (or anything responding to :table_name) - table.table_name
|
|
463
462
|
# * String, Symbol: table
|
|
464
463
|
# * expr - specifies conditions, depends on type:
|
|
465
464
|
# * Hash, Array of two element arrays - Assumes key (1st arg) is column of joined table (unless already
|
|
@@ -536,7 +535,6 @@ module Sequel
|
|
|
536
535
|
end
|
|
537
536
|
table_name = table_alias
|
|
538
537
|
else
|
|
539
|
-
table = table.table_name if table.respond_to?(:table_name)
|
|
540
538
|
table, implicit_table_alias = split_alias(table)
|
|
541
539
|
table_alias ||= implicit_table_alias
|
|
542
540
|
table_name = table_alias || table
|
data/lib/sequel/dataset/sql.rb
CHANGED
|
@@ -49,10 +49,6 @@ module Sequel
|
|
|
49
49
|
end
|
|
50
50
|
when Dataset, Array, LiteralString
|
|
51
51
|
values = vals
|
|
52
|
-
else
|
|
53
|
-
if vals.respond_to?(:values) && (v = vals.values).is_a?(Hash)
|
|
54
|
-
return insert_sql(v)
|
|
55
|
-
end
|
|
56
52
|
end
|
|
57
53
|
when 2
|
|
58
54
|
if (v0 = values.at(0)).is_a?(Array) && ((v1 = values.at(1)).is_a?(Array) || v1.is_a?(Dataset) || v1.is_a?(LiteralString))
|
|
@@ -182,6 +178,9 @@ module Sequel
|
|
|
182
178
|
clauses.map{|clause| :"#{type}_#{clause}_sql"}.freeze
|
|
183
179
|
end
|
|
184
180
|
|
|
181
|
+
# Map of emulated function names to native function names.
|
|
182
|
+
EMULATED_FUNCTION_MAP = {}
|
|
183
|
+
|
|
185
184
|
WILDCARD = LiteralString.new('*').freeze
|
|
186
185
|
ALL = ' ALL'.freeze
|
|
187
186
|
AND_SEPARATOR = " AND ".freeze
|
|
@@ -272,6 +271,7 @@ module Sequel
|
|
|
272
271
|
TIMESTAMP_FORMAT = "'%Y-%m-%d %H:%M:%S%N%z'".freeze
|
|
273
272
|
STANDARD_TIMESTAMP_FORMAT = "TIMESTAMP #{TIMESTAMP_FORMAT}".freeze
|
|
274
273
|
TWO_ARITY_OPERATORS = ::Sequel::SQL::ComplexExpression::TWO_ARITY_OPERATORS
|
|
274
|
+
REGEXP_OPERATORS = ::Sequel::SQL::ComplexExpression::REGEXP_OPERATORS
|
|
275
275
|
UNDERSCORE = '_'.freeze
|
|
276
276
|
UPDATE = 'UPDATE'.freeze
|
|
277
277
|
UPDATE_CLAUSE_METHODS = clause_methods(:update, %w'update table set where')
|
|
@@ -456,6 +456,9 @@ module Sequel
|
|
|
456
456
|
sql << PAREN_CLOSE
|
|
457
457
|
end
|
|
458
458
|
when *TWO_ARITY_OPERATORS
|
|
459
|
+
if REGEXP_OPERATORS.include?(op) && !supports_regexp?
|
|
460
|
+
raise InvalidOperation, "Pattern matching via regular expressions is not supported on #{db.database_type}"
|
|
461
|
+
end
|
|
459
462
|
sql << PAREN_OPEN
|
|
460
463
|
literal_append(sql, args.at(0))
|
|
461
464
|
sql << SPACE << op.to_s << SPACE
|
|
@@ -493,15 +496,18 @@ module Sequel
|
|
|
493
496
|
sql << constant.to_s
|
|
494
497
|
end
|
|
495
498
|
|
|
496
|
-
# SQL fragment specifying an SQL function call
|
|
499
|
+
# SQL fragment specifying an emulated SQL function call.
|
|
500
|
+
# By default, assumes just the function name may need to
|
|
501
|
+
# be emulated, adapters should set an EMULATED_FUNCTION_MAP
|
|
502
|
+
# hash mapping emulated functions to native functions in
|
|
503
|
+
# their dataset class to setup the emulation.
|
|
504
|
+
def emulated_function_sql_append(sql, f)
|
|
505
|
+
_function_sql_append(sql, native_function_name(f.f), f.args)
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
# SQL fragment specifying an SQL function call without emulation.
|
|
497
509
|
def function_sql_append(sql, f)
|
|
498
|
-
sql
|
|
499
|
-
args = f.args
|
|
500
|
-
if args.empty?
|
|
501
|
-
sql << FUNCTION_EMPTY
|
|
502
|
-
else
|
|
503
|
-
literal_append(sql, args)
|
|
504
|
-
end
|
|
510
|
+
_function_sql_append(sql, f.f, f.args)
|
|
505
511
|
end
|
|
506
512
|
|
|
507
513
|
# SQL fragment specifying a JOIN clause without ON or USING.
|
|
@@ -718,6 +724,16 @@ module Sequel
|
|
|
718
724
|
|
|
719
725
|
private
|
|
720
726
|
|
|
727
|
+
# Backbone of function_sql_append and emulated_function_sql_append.
|
|
728
|
+
def _function_sql_append(sql, name, args)
|
|
729
|
+
sql << name.to_s
|
|
730
|
+
if args.empty?
|
|
731
|
+
sql << FUNCTION_EMPTY
|
|
732
|
+
else
|
|
733
|
+
literal_append(sql, args)
|
|
734
|
+
end
|
|
735
|
+
end
|
|
736
|
+
|
|
721
737
|
# Formats the truncate statement. Assumes the table given has already been
|
|
722
738
|
# literalized.
|
|
723
739
|
def _truncate_sql(table)
|
|
@@ -1127,6 +1143,11 @@ module Sequel
|
|
|
1127
1143
|
BOOL_TRUE
|
|
1128
1144
|
end
|
|
1129
1145
|
|
|
1146
|
+
# Get the native function name given the emulated function name.
|
|
1147
|
+
def native_function_name(emulated_function)
|
|
1148
|
+
self.class.const_get(:EMULATED_FUNCTION_MAP).fetch(emulated_function, emulated_function)
|
|
1149
|
+
end
|
|
1150
|
+
|
|
1130
1151
|
# Returns a qualified column name (including a table name) if the column
|
|
1131
1152
|
# name isn't already qualified.
|
|
1132
1153
|
def qualified_column_name(column, table)
|
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
# The constraint_validations extension is designed to easily create database
|
|
2
|
+
# constraints inside create_table and alter_table blocks. It also adds
|
|
3
|
+
# relevant metadata about the constraints to a separate table, which the
|
|
4
|
+
# constraint_validations model plugin uses to setup automatic validations.
|
|
5
|
+
#
|
|
6
|
+
# To use this extension, you first need to load it into the database:
|
|
7
|
+
#
|
|
8
|
+
# DB.extension(:constraint_validations)
|
|
9
|
+
#
|
|
10
|
+
# Note that you should only need to do this when modifying the constraint
|
|
11
|
+
# validations (i.e. when migrating). You should probably not load this
|
|
12
|
+
# extension in general application code.
|
|
13
|
+
#
|
|
14
|
+
# You also need to make sure to add the metadata table for the automatic
|
|
15
|
+
# validations. By default, this table is called sequel_constraint_validations.
|
|
16
|
+
#
|
|
17
|
+
# DB.create_constraint_validations_table
|
|
18
|
+
#
|
|
19
|
+
# This table should only be created once. For new applications, you
|
|
20
|
+
# generally want to create it first, before creating any other application
|
|
21
|
+
# tables.
|
|
22
|
+
#
|
|
23
|
+
# Because migrations instance_eval the up and down blocks on a database,
|
|
24
|
+
# using this extension in a migration can be done via:
|
|
25
|
+
#
|
|
26
|
+
# Sequel.migration do
|
|
27
|
+
# up do
|
|
28
|
+
# extension(:constraint_validations)
|
|
29
|
+
# # ...
|
|
30
|
+
# end
|
|
31
|
+
# down do
|
|
32
|
+
# extension(:constraint_validations)
|
|
33
|
+
# # ...
|
|
34
|
+
# end
|
|
35
|
+
# end
|
|
36
|
+
#
|
|
37
|
+
# However, note that you cannot use change migrations with this extension,
|
|
38
|
+
# you need to use separate up/down migrations.
|
|
39
|
+
#
|
|
40
|
+
# The API for creating the constraints with automatic validations is
|
|
41
|
+
# similar to the validation_helpers model plugin API. However,
|
|
42
|
+
# instead of having separate validates_* methods, it just adds a validate
|
|
43
|
+
# method that accepts a block to the schema generators. Like the
|
|
44
|
+
# create_table and alter_table blocks, this block is instance_evaled and
|
|
45
|
+
# offers its own DSL. Example:
|
|
46
|
+
#
|
|
47
|
+
# DB.create_table(:table) do
|
|
48
|
+
# Integer :id
|
|
49
|
+
# String :name
|
|
50
|
+
#
|
|
51
|
+
# validate do
|
|
52
|
+
# presence :id
|
|
53
|
+
# min_length 5, :name
|
|
54
|
+
# end
|
|
55
|
+
# end
|
|
56
|
+
#
|
|
57
|
+
# instance_eval is used in this case because create_table and alter_table
|
|
58
|
+
# already use instance_eval, so losing access to the surrounding receiver
|
|
59
|
+
# is not an issue.
|
|
60
|
+
#
|
|
61
|
+
# Here's a breakdown of the constraints created for each constraint validation
|
|
62
|
+
# method:
|
|
63
|
+
#
|
|
64
|
+
# All constraints except unique unless :allow_nil is true :: CHECK column IS NOT NULL
|
|
65
|
+
# presence (String column) :: CHECK trim(column) != ''
|
|
66
|
+
# exact_length 5 :: CHECK char_length(column) = 5
|
|
67
|
+
# min_length 5 :: CHECK char_length(column) >= 5
|
|
68
|
+
# max_length 5 :: CHECK char_length(column) <= 5
|
|
69
|
+
# length_range 3..5 :: CHECK char_length(column) >= 3 AND char_length(column) <= 5
|
|
70
|
+
# length_range 3...5 :: CHECK char_length(column) >= 3 AND char_length(column) < 5
|
|
71
|
+
# format /foo\\d+/ :: CHECK column ~ 'foo\\d+'
|
|
72
|
+
# format /foo\\d+/i :: CHECK column ~* 'foo\\d+'
|
|
73
|
+
# like 'foo%' :: CHECK column LIKE 'foo%'
|
|
74
|
+
# ilike 'foo%' :: CHECK column ILIKE 'foo%'
|
|
75
|
+
# includes ['a', 'b'] :: CHECK column IN ('a', 'b')
|
|
76
|
+
# includes [1, 2] :: CHECK column IN (1, 2)
|
|
77
|
+
# includes 3..5 :: CHECK column >= 3 AND column <= 5
|
|
78
|
+
# includes 3...5 :: CHECK column >= 3 AND column < 5
|
|
79
|
+
# unique :: UNIQUE (column)
|
|
80
|
+
#
|
|
81
|
+
# There are some additional API differences:
|
|
82
|
+
#
|
|
83
|
+
# * Only the :message and :allow_nil options are respected. The :allow_blank
|
|
84
|
+
# and :allow_missing options are not respected.
|
|
85
|
+
# * A new option, :name, is respected, for providing the name of the constraint. It is highly
|
|
86
|
+
# recommended that you provide a name for all constraint validations, as
|
|
87
|
+
# otherwise, it is difficult to drop the constraints later.
|
|
88
|
+
# * The includes validation only supports an array of strings, and array of
|
|
89
|
+
# integers, and a range of integers.
|
|
90
|
+
# * There are like and ilike validations, which are similar to the format
|
|
91
|
+
# validation but use a case sensitive or case insensitive LIKE pattern. LIKE
|
|
92
|
+
# patters are very simple, so many regexp patterns cannot be expressed by
|
|
93
|
+
# them, but only a couple databases (PostgreSQL and MySQL) support regexp
|
|
94
|
+
# patterns.
|
|
95
|
+
# * When using the unique validation, column names cannot have embedded commas.
|
|
96
|
+
# For similar reasons, when using an includes validation with an array of
|
|
97
|
+
# strings, none of the strings in the array can have embedded commas.
|
|
98
|
+
# * The unique validation does not support an arbitrary number of columns.
|
|
99
|
+
# For a single column, just the symbol should be used, and for an array
|
|
100
|
+
# of columns, an array of symbols should be used. There is no support
|
|
101
|
+
# for creating two separate unique validations for separate columns in
|
|
102
|
+
# a single call.
|
|
103
|
+
# * A drop method can be called with a constraint name in a alter_table
|
|
104
|
+
# validate block to drop an existing constraint and the related
|
|
105
|
+
# validation metadata.
|
|
106
|
+
# * While it is allowed to create a presence constraint with :allow_nil
|
|
107
|
+
# set to true, doing so does not create a constraint unless the column
|
|
108
|
+
# has String type.
|
|
109
|
+
#
|
|
110
|
+
# Note that this extension has the following issues on certain databases:
|
|
111
|
+
#
|
|
112
|
+
# * MySQL does not support check constraints (they are parsed but ignored),
|
|
113
|
+
# so using this extension does not actually set up constraints on MySQL,
|
|
114
|
+
# except for the unique constraint. It can still be used on MySQL to
|
|
115
|
+
# add the validation metadata so that the plugin can setup automatic
|
|
116
|
+
# validations.
|
|
117
|
+
# * On SQLite, adding constraints to a table is not supported, so it must
|
|
118
|
+
# be emulated by dropping the table and recreating it with the constraints.
|
|
119
|
+
# If you want to use this plugin on SQLite with an alter_table block,
|
|
120
|
+
# you should drop all constraint validation metadata using
|
|
121
|
+
# <tt>drop_constraint_validations_for(:table=>'table')</tt>, and then
|
|
122
|
+
# readd all constraints you want to use inside the alter table block,
|
|
123
|
+
# making no other changes inside the alter_table block.
|
|
124
|
+
|
|
125
|
+
module Sequel
|
|
126
|
+
module ConstraintValidations
|
|
127
|
+
# The default table name used for the validation metadata.
|
|
128
|
+
DEFAULT_CONSTRAINT_VALIDATIONS_TABLE = :sequel_constraint_validations
|
|
129
|
+
|
|
130
|
+
# Set the default validation metadata table name if it has not already
|
|
131
|
+
# been set.
|
|
132
|
+
def self.extended(db)
|
|
133
|
+
db.constraint_validations_table ||= DEFAULT_CONSTRAINT_VALIDATIONS_TABLE
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# This is the DSL class used for the validate block inside create_table and
|
|
137
|
+
# alter_table.
|
|
138
|
+
class Generator
|
|
139
|
+
# Store the schema generator that encloses this validates block.
|
|
140
|
+
def initialize(generator)
|
|
141
|
+
@generator = generator
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Create constraint validation methods that don't take an argument
|
|
145
|
+
%w'presence unique'.each do |v|
|
|
146
|
+
class_eval(<<-END, __FILE__, __LINE__+1)
|
|
147
|
+
def #{v}(columns, opts={})
|
|
148
|
+
@generator.validation({:type=>:#{v}, :columns=>Array(columns)}.merge(opts))
|
|
149
|
+
end
|
|
150
|
+
END
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Create constraint validation methods that take an argument
|
|
154
|
+
%w'exact_length min_length max_length length_range format like ilike includes'.each do |v|
|
|
155
|
+
class_eval(<<-END, __FILE__, __LINE__+1)
|
|
156
|
+
def #{v}(arg, columns, opts={})
|
|
157
|
+
@generator.validation({:type=>:#{v}, :columns=>Array(columns), :arg=>arg}.merge(opts))
|
|
158
|
+
end
|
|
159
|
+
END
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Given the name of a constraint, drop that constraint from the database,
|
|
163
|
+
# and remove the related validation metadata.
|
|
164
|
+
def drop(constraint)
|
|
165
|
+
@generator.validation({:type=>:drop, :name=>constraint})
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Alias of instance_eval for a nicer API.
|
|
169
|
+
def process(&block)
|
|
170
|
+
instance_eval(&block)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Additional methods for the create_table generator to support constraint validations.
|
|
175
|
+
module CreateTableGeneratorMethods
|
|
176
|
+
# An array of stored validation metadata, used later by the database to create
|
|
177
|
+
# constraints.
|
|
178
|
+
attr_reader :validations
|
|
179
|
+
|
|
180
|
+
# Add a validation metadata hash to the stored array.
|
|
181
|
+
def validation(opts)
|
|
182
|
+
@validations << opts
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Call into the validate DSL for creating constraint validations.
|
|
186
|
+
def validate(&block)
|
|
187
|
+
Generator.new(self).process(&block)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Additional methods for the alter_table generator to support constraint validations,
|
|
192
|
+
# used to give it a more similar API to the create_table generator.
|
|
193
|
+
module AlterTableGeneratorMethods
|
|
194
|
+
include CreateTableGeneratorMethods
|
|
195
|
+
|
|
196
|
+
# Alias of add_constraint for similarity to create_table generator.
|
|
197
|
+
def constraint(*args)
|
|
198
|
+
add_constraint(*args)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Alias of add_unique_constraint for similarity to create_table generator.
|
|
202
|
+
def unique(*args)
|
|
203
|
+
add_unique_constraint(*args)
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# The name of the table storing the validation metadata. If modifying this
|
|
208
|
+
# from the default, this should be changed directly after loading the
|
|
209
|
+
# extension into the database
|
|
210
|
+
attr_accessor :constraint_validations_table
|
|
211
|
+
|
|
212
|
+
# Create the table storing the validation metadata for all of the
|
|
213
|
+
# constraints created by this extension.
|
|
214
|
+
def create_constraint_validations_table
|
|
215
|
+
create_table(constraint_validations_table) do
|
|
216
|
+
String :table, :null=>false
|
|
217
|
+
String :constraint_name
|
|
218
|
+
String :validation_type, :null=>false
|
|
219
|
+
String :column, :null=>false
|
|
220
|
+
String :argument
|
|
221
|
+
String :message
|
|
222
|
+
TrueClass :allow_nil
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Drop the constraint validations table.
|
|
227
|
+
def drop_constraint_validations_table
|
|
228
|
+
drop_table(constraint_validations_table)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# Delete validation metadata for specific constraints. At least
|
|
232
|
+
# one of the following options should be specified:
|
|
233
|
+
#
|
|
234
|
+
# :table :: The table containing the constraint
|
|
235
|
+
# :column :: The column affected by the constraint
|
|
236
|
+
# :constraint :: The name of the related constraint
|
|
237
|
+
#
|
|
238
|
+
# The main reason for this method is when dropping tables
|
|
239
|
+
# or columns. If you have previously defined a constraint
|
|
240
|
+
# validation on the table or column, you should delete the
|
|
241
|
+
# related metadata when dropping the table or column.
|
|
242
|
+
# For a table, this isn't a big issue, as it will just result
|
|
243
|
+
# in some wasted space, but for columns, if you don't drop
|
|
244
|
+
# the related metadata, it could make it impossible to save
|
|
245
|
+
# rows, since a validation for a nonexistent column will be
|
|
246
|
+
# created.
|
|
247
|
+
def drop_constraint_validations_for(opts={})
|
|
248
|
+
ds = from(constraint_validations_table)
|
|
249
|
+
if table = opts[:table]
|
|
250
|
+
ds = ds.where(:table=>constraint_validations_literal_table(table))
|
|
251
|
+
end
|
|
252
|
+
if column = opts[:column]
|
|
253
|
+
ds = ds.where(:column=>column.to_s)
|
|
254
|
+
end
|
|
255
|
+
if constraint = opts[:constraint]
|
|
256
|
+
ds = ds.where(:constraint_name=>constraint.to_s)
|
|
257
|
+
end
|
|
258
|
+
unless table || column || constraint
|
|
259
|
+
raise Error, "must specify :table, :column, or :constraint when dropping constraint validations"
|
|
260
|
+
end
|
|
261
|
+
ds.delete
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
private
|
|
265
|
+
|
|
266
|
+
# Modify the default create_table generator to include
|
|
267
|
+
# the constraint validation methods.
|
|
268
|
+
def alter_table_generator(&block)
|
|
269
|
+
super do
|
|
270
|
+
extend AlterTableGeneratorMethods
|
|
271
|
+
@validations = []
|
|
272
|
+
instance_eval(&block) if block
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# After running all of the table alteration statements,
|
|
277
|
+
# if there were any constraint validations, run table alteration
|
|
278
|
+
# statements to create related constraints. This is purposely
|
|
279
|
+
# run after the other statements, as the presence validation
|
|
280
|
+
# in alter table requires introspecting the modified model
|
|
281
|
+
# schema.
|
|
282
|
+
def apply_alter_table_generator(name, generator)
|
|
283
|
+
super
|
|
284
|
+
unless generator.validations.empty?
|
|
285
|
+
gen = alter_table_generator
|
|
286
|
+
process_generator_validations(name, gen, generator.validations)
|
|
287
|
+
apply_alter_table(name, gen.operations)
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# The value of a blank string. An empty string by default, but nil
|
|
292
|
+
# on Oracle as Oracle treats the empty string as NULL.
|
|
293
|
+
def blank_string_value
|
|
294
|
+
if database_type == :oracle
|
|
295
|
+
nil
|
|
296
|
+
else
|
|
297
|
+
''
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Return an unquoted literal form of the table name.
|
|
302
|
+
# This allows the code to handle schema qualified tables,
|
|
303
|
+
# without quoting all table names.
|
|
304
|
+
def constraint_validations_literal_table(table)
|
|
305
|
+
ds = dataset
|
|
306
|
+
ds.quote_identifiers = false
|
|
307
|
+
ds.literal(table)
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# Before creating the table, add constraints for all of the
|
|
311
|
+
# generators validations to the generator.
|
|
312
|
+
def create_table_from_generator(name, generator, options)
|
|
313
|
+
unless generator.validations.empty?
|
|
314
|
+
process_generator_validations(name, generator, generator.validations)
|
|
315
|
+
end
|
|
316
|
+
super
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# Modify the default create_table generator to include
|
|
320
|
+
# the constraint validation methods.
|
|
321
|
+
def create_table_generator(&block)
|
|
322
|
+
super do
|
|
323
|
+
extend CreateTableGeneratorMethods
|
|
324
|
+
@validations = []
|
|
325
|
+
instance_eval(&block) if block
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
# For the given table, generator, and validations, add constraints
|
|
330
|
+
# to the generator for each of the validations, as well as adding
|
|
331
|
+
# validation metadata to the constraint validations table.
|
|
332
|
+
def process_generator_validations(table, generator, validations)
|
|
333
|
+
drop_rows = []
|
|
334
|
+
rows = validations.map do |val|
|
|
335
|
+
columns, arg, constraint, validation_type, message, allow_nil = val.values_at(:columns, :arg, :name, :type, :message, :allow_nil)
|
|
336
|
+
|
|
337
|
+
case validation_type
|
|
338
|
+
when :presence
|
|
339
|
+
string_check = columns.select{|c| generator_string_column?(generator, table, c)}.map{|c| [Sequel.trim(c), blank_string_value]}
|
|
340
|
+
generator_add_constraint_from_validation(generator, val, (Sequel.negate(string_check) unless string_check.empty?))
|
|
341
|
+
when :exact_length
|
|
342
|
+
generator_add_constraint_from_validation(generator, val, Sequel.&(*columns.map{|c| {Sequel.char_length(c) => arg}}))
|
|
343
|
+
when :min_length
|
|
344
|
+
generator_add_constraint_from_validation(generator, val, Sequel.&(*columns.map{|c| Sequel.char_length(c) >= arg}))
|
|
345
|
+
when :max_length
|
|
346
|
+
generator_add_constraint_from_validation(generator, val, Sequel.&(*columns.map{|c| Sequel.char_length(c) <= arg}))
|
|
347
|
+
when :length_range
|
|
348
|
+
op = arg.exclude_end? ? :< : :<=
|
|
349
|
+
generator_add_constraint_from_validation(generator, val, Sequel.&(*columns.map{|c| (Sequel.char_length(c) >= arg.begin) & Sequel.char_length(c).send(op, arg.end)}))
|
|
350
|
+
arg = "#{arg.begin}..#{'.' if arg.exclude_end?}#{arg.end}"
|
|
351
|
+
when :format
|
|
352
|
+
generator_add_constraint_from_validation(generator, val, Sequel.&(*columns.map{|c| {c => arg}}))
|
|
353
|
+
if arg.casefold?
|
|
354
|
+
validation_type = :iformat
|
|
355
|
+
end
|
|
356
|
+
arg = arg.source
|
|
357
|
+
when :includes
|
|
358
|
+
generator_add_constraint_from_validation(generator, val, Sequel.&(*columns.map{|c| {c => arg}}))
|
|
359
|
+
if arg.is_a?(Range)
|
|
360
|
+
if (b = arg.begin).is_a?(Integer) && (e = arg.end).is_a?(Integer)
|
|
361
|
+
validation_type = :includes_int_range
|
|
362
|
+
arg = "#{arg.begin}..#{'.' if arg.exclude_end?}#{arg.end}"
|
|
363
|
+
else
|
|
364
|
+
raise Error, "validates includes with a range only supports integers currently, cannot handle: #{arg.inspect}"
|
|
365
|
+
end
|
|
366
|
+
elsif arg.is_a?(Array)
|
|
367
|
+
if arg.all?{|x| x.is_a?(Integer)}
|
|
368
|
+
validation_type = :includes_int_array
|
|
369
|
+
elsif arg.all?{|x| x.is_a?(String)}
|
|
370
|
+
validation_type = :includes_str_array
|
|
371
|
+
else
|
|
372
|
+
raise Error, "validates includes with an array only supports strings and integers currently, cannot handle: #{arg.inspect}"
|
|
373
|
+
end
|
|
374
|
+
arg = arg.join(',')
|
|
375
|
+
else
|
|
376
|
+
raise Error, "validates includes only supports arrays and ranges currently, cannot handle: #{arg.inspect}"
|
|
377
|
+
end
|
|
378
|
+
when :like, :ilike
|
|
379
|
+
generator_add_constraint_from_validation(generator, val, Sequel.&(*columns.map{|c| Sequel.send(validation_type, c, arg)}))
|
|
380
|
+
when :unique
|
|
381
|
+
generator.unique(columns, :name=>constraint)
|
|
382
|
+
columns = [columns.join(',')]
|
|
383
|
+
when :drop
|
|
384
|
+
if generator.is_a?(Sequel::Schema::AlterTableGenerator)
|
|
385
|
+
unless constraint
|
|
386
|
+
raise Error, 'cannot drop a constraint validation without a constraint name'
|
|
387
|
+
end
|
|
388
|
+
generator.drop_constraint(constraint)
|
|
389
|
+
drop_rows << [constraint_validations_literal_table(table), constraint.to_s]
|
|
390
|
+
columns = []
|
|
391
|
+
else
|
|
392
|
+
raise Error, 'cannot drop a constraint validation in a create_table generator'
|
|
393
|
+
end
|
|
394
|
+
else
|
|
395
|
+
raise Error, "invalid or missing validation type: #{val.inspect}"
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
columns.map do |column|
|
|
399
|
+
{:table=>constraint_validations_literal_table(table), :constraint_name=>(constraint.to_s if constraint), :validation_type=>validation_type.to_s, :column=>column.to_s, :argument=>(arg.to_s if arg), :message=>(message.to_s if message), :allow_nil=>allow_nil}
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
ds = from(:sequel_constraint_validations)
|
|
404
|
+
ds.multi_insert(rows.flatten)
|
|
405
|
+
unless drop_rows.empty?
|
|
406
|
+
ds.where([:table, :constraint_name]=>drop_rows).delete
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
# Add the constraint to the generator, including a NOT NULL constraint
|
|
411
|
+
# for all columns unless the :allow_nil option is given.
|
|
412
|
+
def generator_add_constraint_from_validation(generator, val, cons)
|
|
413
|
+
unless val[:allow_nil]
|
|
414
|
+
nil_cons = Sequel.negate(val[:columns].map{|c| [c, nil]})
|
|
415
|
+
cons = cons ? Sequel.&(nil_cons, cons) : nil_cons
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
if cons
|
|
419
|
+
generator.constraint(val[:name], cons)
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
# Introspect the generator to determine if column
|
|
425
|
+
# created is a string or not.
|
|
426
|
+
def generator_string_column?(generator, table, c)
|
|
427
|
+
if generator.is_a?(Sequel::Schema::AlterTableGenerator)
|
|
428
|
+
# This is the alter table case, which runs after the
|
|
429
|
+
# table has been altered, so just check the database
|
|
430
|
+
# schema for the column.
|
|
431
|
+
schema(table).each do |col, sch|
|
|
432
|
+
if col == c
|
|
433
|
+
return sch[:type] == :string
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
false
|
|
437
|
+
else
|
|
438
|
+
# This is the create table case, check the metadata
|
|
439
|
+
# for the column to be created to see if it is a string.
|
|
440
|
+
generator.columns.each do |col|
|
|
441
|
+
if col[:name] == c
|
|
442
|
+
return [String, :text, :varchar].include?(col[:type])
|
|
443
|
+
end
|
|
444
|
+
end
|
|
445
|
+
false
|
|
446
|
+
end
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
Database.register_extension(:constraint_validations, ConstraintValidations)
|
|
451
|
+
end
|