sequel 5.38.0 → 5.43.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.
- checksums.yaml +4 -4
- data/CHANGELOG +54 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +1 -1
- data/doc/cheat_sheet.rdoc +5 -5
- data/doc/code_order.rdoc +0 -12
- data/doc/fork_safety.rdoc +84 -0
- data/doc/postgresql.rdoc +1 -1
- data/doc/querying.rdoc +3 -3
- data/doc/release_notes/5.39.0.txt +19 -0
- data/doc/release_notes/5.40.0.txt +40 -0
- data/doc/release_notes/5.41.0.txt +25 -0
- data/doc/release_notes/5.42.0.txt +136 -0
- data/doc/release_notes/5.43.0.txt +98 -0
- data/doc/sql.rdoc +1 -1
- data/doc/testing.rdoc +2 -0
- data/lib/sequel/adapters/ado.rb +16 -16
- data/lib/sequel/adapters/jdbc.rb +2 -2
- data/lib/sequel/adapters/shared/mssql.rb +21 -1
- data/lib/sequel/adapters/shared/postgres.rb +5 -3
- data/lib/sequel/adapters/shared/sqlite.rb +35 -1
- data/lib/sequel/database/misc.rb +1 -2
- data/lib/sequel/database/schema_generator.rb +16 -1
- data/lib/sequel/database/schema_methods.rb +19 -5
- data/lib/sequel/database/transactions.rb +1 -1
- data/lib/sequel/dataset/features.rb +10 -0
- data/lib/sequel/dataset/prepared_statements.rb +2 -0
- data/lib/sequel/dataset/sql.rb +32 -10
- data/lib/sequel/extensions/async_thread_pool.rb +438 -0
- data/lib/sequel/extensions/blank.rb +8 -0
- data/lib/sequel/extensions/date_arithmetic.rb +7 -9
- data/lib/sequel/extensions/eval_inspect.rb +2 -0
- data/lib/sequel/extensions/inflector.rb +8 -0
- data/lib/sequel/extensions/migration.rb +2 -0
- data/lib/sequel/extensions/named_timezones.rb +5 -1
- data/lib/sequel/extensions/pg_array.rb +1 -0
- data/lib/sequel/extensions/pg_interval.rb +34 -8
- data/lib/sequel/extensions/pg_row.rb +1 -0
- data/lib/sequel/extensions/query.rb +2 -0
- data/lib/sequel/model/associations.rb +28 -4
- data/lib/sequel/model/base.rb +23 -6
- data/lib/sequel/model/plugins.rb +5 -0
- data/lib/sequel/plugins/association_proxies.rb +2 -0
- data/lib/sequel/plugins/async_thread_pool.rb +39 -0
- data/lib/sequel/plugins/auto_validations.rb +15 -1
- data/lib/sequel/plugins/column_encryption.rb +711 -0
- data/lib/sequel/plugins/composition.rb +7 -2
- data/lib/sequel/plugins/constraint_validations.rb +2 -1
- data/lib/sequel/plugins/dataset_associations.rb +4 -1
- data/lib/sequel/plugins/json_serializer.rb +37 -22
- data/lib/sequel/plugins/nested_attributes.rb +8 -3
- data/lib/sequel/plugins/pg_array_associations.rb +4 -0
- data/lib/sequel/plugins/pg_auto_constraint_validations.rb +2 -0
- data/lib/sequel/plugins/serialization.rb +8 -3
- data/lib/sequel/plugins/serialization_modification_detection.rb +1 -1
- data/lib/sequel/plugins/single_table_inheritance.rb +2 -0
- data/lib/sequel/plugins/tree.rb +9 -4
- data/lib/sequel/plugins/validation_helpers.rb +6 -2
- data/lib/sequel/timezones.rb +8 -3
- data/lib/sequel/version.rb +1 -1
- metadata +36 -21
@@ -0,0 +1,98 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* A column_encryption plugin has been added to support encrypting the
|
4
|
+
content of individual columns in a table.
|
5
|
+
|
6
|
+
Column values are encrypted with AES-256-GCM using a per-value
|
7
|
+
cipher key derived from a key provided in the configuration using
|
8
|
+
HMAC-SHA256.
|
9
|
+
|
10
|
+
If you would like to support encryption of columns in more than one
|
11
|
+
model, you should probably load the plugin into the parent class of
|
12
|
+
your models and specify the keys:
|
13
|
+
|
14
|
+
Sequel::Model.plugin :column_encryption do |enc|
|
15
|
+
enc.key 0, ENV["SEQUEL_COLUMN_ENCRYPTION_KEY"]
|
16
|
+
end
|
17
|
+
|
18
|
+
This specifies a single master encryption key. Unless you are
|
19
|
+
actively rotating keys, it is best to use a single master key.
|
20
|
+
|
21
|
+
In the above call, 0 is the id of the key, and
|
22
|
+
ENV["SEQUEL_COLUMN_ENCRYPTION_KEY"] is the content of the key, which
|
23
|
+
must be a string with exactly 32 bytes. As indicated, this key
|
24
|
+
should not be hardcoded or otherwise committed to the source control
|
25
|
+
repository.
|
26
|
+
|
27
|
+
For models that need encrypted columns, you load the plugin again,
|
28
|
+
but specify the columns to encrypt:
|
29
|
+
|
30
|
+
ConfidentialModel.plugin :column_encryption do |enc|
|
31
|
+
enc.column :encrypted_column_name
|
32
|
+
enc.column :searchable_column_name, searchable: true
|
33
|
+
enc.column :ci_searchable_column_name, searchable: :case_insensitive
|
34
|
+
end
|
35
|
+
|
36
|
+
With this, all three specified columns (encrypted_column_name,
|
37
|
+
searchable_column_name, and ci_searchable_column_name) will be
|
38
|
+
marked as encrypted columns. When you run the following code:
|
39
|
+
|
40
|
+
ConfidentialModel.create(
|
41
|
+
encrypted_column_name: 'These',
|
42
|
+
searchable_column_name: 'will be',
|
43
|
+
ci_searchable_column_name: 'Encrypted'
|
44
|
+
)
|
45
|
+
|
46
|
+
It will save encrypted versions to the database.
|
47
|
+
encrypted_column_name will not be searchable, searchable_column_name
|
48
|
+
will be searchable with an exact match, and
|
49
|
+
ci_searchable_column_name will be searchable with a case insensitive
|
50
|
+
match.
|
51
|
+
|
52
|
+
To search searchable encrypted columns, use with_encrypted_value.
|
53
|
+
This example code will return the model instance created in the code
|
54
|
+
example in the previous section:
|
55
|
+
|
56
|
+
ConfidentialModel.
|
57
|
+
with_encrypted_value(:searchable_column_name, "will be")
|
58
|
+
with_encrypted_value(:ci_searchable_column_name, "encrypted").
|
59
|
+
first
|
60
|
+
|
61
|
+
To rotate encryption keys, add a new key above the existing key,
|
62
|
+
with a new key ID:
|
63
|
+
|
64
|
+
Sequel::Model.plugin :column_encryption do |enc|
|
65
|
+
enc.key 1, ENV["SEQUEL_COLUMN_ENCRYPTION_KEY"]
|
66
|
+
enc.key 0, ENV["SEQUEL_OLD_COLUMN_ENCRYPTION_KEY"]
|
67
|
+
end
|
68
|
+
|
69
|
+
Newly encrypted data will then use the new key. Records encrypted
|
70
|
+
with the older key will still be decrypted correctly.
|
71
|
+
|
72
|
+
To force reencryption for existing records that are using the older
|
73
|
+
key, you can use the needing_reencryption dataset method and the
|
74
|
+
reencrypt instance method. For a small number of records, you can
|
75
|
+
probably do:
|
76
|
+
|
77
|
+
ConfidentialModel.needing_reencryption.all(&:reencrypt)
|
78
|
+
|
79
|
+
With more than a small number of records, you'll want to do this in
|
80
|
+
batches. It's possible you could use an approach such as:
|
81
|
+
|
82
|
+
ds = ConfidentialModel.needing_reencryption.limit(100)
|
83
|
+
true until ds.all(&:reencrypt).empty?
|
84
|
+
|
85
|
+
After all values have been reencrypted for all models, and no models
|
86
|
+
use the older encryption key, you can remove it from the
|
87
|
+
configuration:
|
88
|
+
|
89
|
+
Sequel::Model.plugin :column_encryption do |enc|
|
90
|
+
enc.key 1, ENV["SEQUEL_COLUMN_ENCRYPTION_KEY"]
|
91
|
+
end
|
92
|
+
|
93
|
+
The column_encryption plugin supports encrypting serialized data,
|
94
|
+
as well as enforcing uniquenss of searchable encrypted columns
|
95
|
+
(in the absence of key rotation). By design, it does not support
|
96
|
+
compression, mixing encrypted and unencrypted data in the same
|
97
|
+
column, or support arbitrary encryption ciphers. See the plugin
|
98
|
+
documentation for more details.
|
data/doc/sql.rdoc
CHANGED
@@ -544,7 +544,7 @@ On some databases, you can specify null ordering:
|
|
544
544
|
|
545
545
|
=== All Columns (.*)
|
546
546
|
|
547
|
-
To select all columns in a table, Sequel supports the * method on identifiers and qualified without an argument:
|
547
|
+
To select all columns in a table, Sequel supports the * method on identifiers and qualified identifiers without an argument:
|
548
548
|
|
549
549
|
Sequel[:table].* # "table".*
|
550
550
|
Sequel[:schema][:table].* # "schema"."table".*
|
data/doc/testing.rdoc
CHANGED
@@ -157,6 +157,8 @@ The SEQUEL_INTEGRATION_URL environment variable specifies the Database connectio
|
|
157
157
|
|
158
158
|
=== Other
|
159
159
|
|
160
|
+
SEQUEL_ASYNC_THREAD_POOL :: Use the async_thread_pool extension when running the specs
|
161
|
+
SEQUEL_ASYNC_THREAD_POOL_PREEMPT :: Use the async_thread_pool extension when running the specs, with the :preempt_async_thread option
|
160
162
|
SEQUEL_COLUMNS_INTROSPECTION :: Use the columns_introspection extension when running the specs
|
161
163
|
SEQUEL_CONNECTION_VALIDATOR :: Use the connection validator extension when running the specs
|
162
164
|
SEQUEL_DUPLICATE_COLUMNS_HANDLER :: Use the duplicate columns handler extension with value given when running the specs
|
data/lib/sequel/adapters/ado.rb
CHANGED
@@ -195,10 +195,25 @@ module Sequel
|
|
195
195
|
end
|
196
196
|
|
197
197
|
@conversion_procs = CONVERSION_PROCS.dup
|
198
|
+
@conversion_procs[AdDBTimeStamp] = method(:adb_timestamp_to_application_timestamp)
|
198
199
|
|
199
200
|
super
|
200
201
|
end
|
201
202
|
|
203
|
+
def adb_timestamp_to_application_timestamp(v)
|
204
|
+
# This hard codes a timestamp_precision of 6 when converting.
|
205
|
+
# That is the default timestamp_precision, but the ado/mssql adapter uses a timestamp_precision
|
206
|
+
# of 3. However, timestamps returned by ado/mssql have nsec values that end up rounding to a
|
207
|
+
# the same value as if a timestamp_precision of 3 was hard coded (either xxx999yzz, where y is
|
208
|
+
# 5-9 or xxx000yzz where y is 0-4).
|
209
|
+
#
|
210
|
+
# ADO subadapters should override this they would like a different timestamp precision and the
|
211
|
+
# this code does not work for them (for example, if they provide full nsec precision).
|
212
|
+
#
|
213
|
+
# Note that fractional second handling for WIN32OLE objects is not correct on ruby <2.2
|
214
|
+
to_application_timestamp([v.year, v.month, v.day, v.hour, v.min, v.sec, (v.nsec/1000.0).round * 1000])
|
215
|
+
end
|
216
|
+
|
202
217
|
def dataset_class_default
|
203
218
|
Dataset
|
204
219
|
end
|
@@ -233,23 +248,8 @@ module Sequel
|
|
233
248
|
cols = []
|
234
249
|
conversion_procs = db.conversion_procs
|
235
250
|
|
236
|
-
ts_cp = nil
|
237
251
|
recordset.Fields.each do |field|
|
238
|
-
|
239
|
-
cp = if type == AdDBTimeStamp
|
240
|
-
ts_cp ||= begin
|
241
|
-
nsec_div = 1000000000.0/(10**(timestamp_precision))
|
242
|
-
nsec_mul = 10**(timestamp_precision+3)
|
243
|
-
meth = db.method(:to_application_timestamp)
|
244
|
-
lambda do |v|
|
245
|
-
# Fractional second handling is not correct on ruby <2.2
|
246
|
-
meth.call([v.year, v.month, v.day, v.hour, v.min, v.sec, (v.nsec/nsec_div).round * nsec_mul])
|
247
|
-
end
|
248
|
-
end
|
249
|
-
else
|
250
|
-
conversion_procs[type]
|
251
|
-
end
|
252
|
-
cols << [output_identifier(field.Name), cp]
|
252
|
+
cols << [output_identifier(field.Name), conversion_procs[field.Type]]
|
253
253
|
end
|
254
254
|
|
255
255
|
self.columns = cols.map(&:first)
|
data/lib/sequel/adapters/jdbc.rb
CHANGED
@@ -71,11 +71,11 @@ module Sequel
|
|
71
71
|
class TypeConvertor
|
72
72
|
CONVERTORS = convertors = {}
|
73
73
|
%w'Boolean Float Double Int Long Short'.each do |meth|
|
74
|
-
x = convertors[meth.to_sym] = Object.new
|
74
|
+
x = x = convertors[meth.to_sym] = Object.new
|
75
75
|
class_eval("def x.call(r, i) v = r.get#{meth}(i); v unless r.wasNull end", __FILE__, __LINE__)
|
76
76
|
end
|
77
77
|
%w'Object Array String Time Date Timestamp BigDecimal Blob Bytes Clob'.each do |meth|
|
78
|
-
x = convertors[meth.to_sym] = Object.new
|
78
|
+
x = x = convertors[meth.to_sym] = Object.new
|
79
79
|
class_eval("def x.call(r, i) r.get#{meth}(i) end", __FILE__, __LINE__)
|
80
80
|
end
|
81
81
|
x = convertors[:RubyTime] = Object.new
|
@@ -244,6 +244,16 @@ module Sequel
|
|
244
244
|
|
245
245
|
private
|
246
246
|
|
247
|
+
# Add CLUSTERED or NONCLUSTERED as needed
|
248
|
+
def add_clustered_sql_fragment(sql, opts)
|
249
|
+
clustered = opts[:clustered]
|
250
|
+
unless clustered.nil?
|
251
|
+
sql += " #{'NON' unless clustered}CLUSTERED"
|
252
|
+
end
|
253
|
+
|
254
|
+
sql
|
255
|
+
end
|
256
|
+
|
247
257
|
# Add dropping of the default constraint to the list of SQL queries.
|
248
258
|
# This is necessary before dropping the column or changing its type.
|
249
259
|
def add_drop_default_constraint_sql(sqls, table, column)
|
@@ -284,7 +294,7 @@ module Sequel
|
|
284
294
|
when :set_column_null
|
285
295
|
sch = schema(table).find{|k,v| k.to_s == op[:name].to_s}.last
|
286
296
|
type = sch[:db_type]
|
287
|
-
if [:string, :decimal].include?(sch[:type]) && !["text", "ntext"].include?(type) && (size = (sch[:max_chars] || sch[:column_size]))
|
297
|
+
if [:string, :decimal, :blob].include?(sch[:type]) && !["text", "ntext"].include?(type) && (size = (sch[:max_chars] || sch[:column_size]))
|
288
298
|
size = "MAX" if size == -1
|
289
299
|
type += "(#{size}#{", #{sch[:scale]}" if sch[:scale] && sch[:scale].to_i > 0})"
|
290
300
|
end
|
@@ -396,6 +406,11 @@ module Sequel
|
|
396
406
|
super.with_quote_identifiers(true)
|
397
407
|
end
|
398
408
|
|
409
|
+
# Handle clustered and nonclustered primary keys
|
410
|
+
def primary_key_constraint_sql_fragment(opts)
|
411
|
+
add_clustered_sql_fragment(super, opts)
|
412
|
+
end
|
413
|
+
|
399
414
|
# Use sp_rename to rename the table
|
400
415
|
def rename_table_sql(name, new_name)
|
401
416
|
"sp_rename #{literal(quote_schema_table(name))}, #{quote_identifier(schema_and_table(new_name).pop)}"
|
@@ -492,6 +507,11 @@ module Sequel
|
|
492
507
|
:'varbinary(max)'
|
493
508
|
end
|
494
509
|
|
510
|
+
# Handle clustered and nonclustered unique constraints
|
511
|
+
def unique_constraint_sql_fragment(opts)
|
512
|
+
add_clustered_sql_fragment(super, opts)
|
513
|
+
end
|
514
|
+
|
495
515
|
# MSSQL supports views with check option, but not local.
|
496
516
|
def view_with_check_option_support
|
497
517
|
true
|
@@ -846,7 +846,7 @@ module Sequel
|
|
846
846
|
# :schema :: The schema to search
|
847
847
|
# :server :: The server to use
|
848
848
|
def tables(opts=OPTS, &block)
|
849
|
-
pg_class_relname('r', opts, &block)
|
849
|
+
pg_class_relname(['r', 'p'], opts, &block)
|
850
850
|
end
|
851
851
|
|
852
852
|
# Check whether the given type name string/symbol (e.g. :hstore) is supported by
|
@@ -1500,9 +1500,11 @@ module Sequel
|
|
1500
1500
|
# disallowed or there is a size specified, use the varchar type.
|
1501
1501
|
# Otherwise use the text type.
|
1502
1502
|
def type_literal_generic_string(column)
|
1503
|
-
if column[:
|
1503
|
+
if column[:text]
|
1504
|
+
:text
|
1505
|
+
elsif column[:fixed]
|
1504
1506
|
"char(#{column[:size]||255})"
|
1505
|
-
elsif column[:text] == false
|
1507
|
+
elsif column[:text] == false || column[:size]
|
1506
1508
|
"varchar(#{column[:size]||255})"
|
1507
1509
|
else
|
1508
1510
|
:text
|
@@ -561,7 +561,7 @@ module Sequel
|
|
561
561
|
Dataset.def_sql_method(self, :delete, [['if db.sqlite_version >= 30803', %w'with delete from where'], ["else", %w'delete from where']])
|
562
562
|
Dataset.def_sql_method(self, :insert, [['if db.sqlite_version >= 30803', %w'with insert conflict into columns values on_conflict'], ["else", %w'insert conflict into columns values']])
|
563
563
|
Dataset.def_sql_method(self, :select, [['if opts[:values]', %w'with values compounds'], ['else', %w'with select distinct columns from join where group having window compounds order limit lock']])
|
564
|
-
Dataset.def_sql_method(self, :update, [['if db.sqlite_version >= 30803', %w'with update table set where'], ["else", %w'update table set where']])
|
564
|
+
Dataset.def_sql_method(self, :update, [['if db.sqlite_version >= 33300', %w'with update table set from where'], ['elsif db.sqlite_version >= 30803', %w'with update table set where'], ["else", %w'update table set where']])
|
565
565
|
|
566
566
|
def cast_sql_append(sql, expr, type)
|
567
567
|
if type == Time or type == DateTime
|
@@ -753,6 +753,11 @@ module Sequel
|
|
753
753
|
false
|
754
754
|
end
|
755
755
|
|
756
|
+
# SQLite does not support deleting from a joined dataset
|
757
|
+
def supports_deleting_joins?
|
758
|
+
false
|
759
|
+
end
|
760
|
+
|
756
761
|
# SQLite does not support INTERSECT ALL or EXCEPT ALL
|
757
762
|
def supports_intersect_except_all?
|
758
763
|
false
|
@@ -763,6 +768,11 @@ module Sequel
|
|
763
768
|
false
|
764
769
|
end
|
765
770
|
|
771
|
+
# SQLite 3.33.0 supports modifying joined datasets
|
772
|
+
def supports_modifying_joins?
|
773
|
+
db.sqlite_version >= 33300
|
774
|
+
end
|
775
|
+
|
766
776
|
# SQLite does not support multiple columns for the IN/NOT IN operators
|
767
777
|
def supports_multiple_column_in?
|
768
778
|
false
|
@@ -825,6 +835,13 @@ module Sequel
|
|
825
835
|
end
|
826
836
|
end
|
827
837
|
|
838
|
+
# Raise an InvalidOperation exception if insert is not allowed for this dataset.
|
839
|
+
def check_insert_allowed!
|
840
|
+
raise(InvalidOperation, "Grouped datasets cannot be modified") if opts[:group]
|
841
|
+
raise(InvalidOperation, "Joined datasets cannot be modified") if joined_dataset?
|
842
|
+
end
|
843
|
+
alias check_delete_allowed! check_insert_allowed!
|
844
|
+
|
828
845
|
# SQLite supports a maximum of 500 rows in a VALUES clause.
|
829
846
|
def default_import_slice
|
830
847
|
500
|
@@ -944,6 +961,23 @@ module Sequel
|
|
944
961
|
def _truncate_sql(table)
|
945
962
|
"DELETE FROM #{table}"
|
946
963
|
end
|
964
|
+
|
965
|
+
# Use FROM to specify additional tables in an update query
|
966
|
+
def update_from_sql(sql)
|
967
|
+
if(from = @opts[:from][1..-1]).empty?
|
968
|
+
raise(Error, 'Need multiple FROM tables if updating/deleting a dataset with JOINs') if @opts[:join]
|
969
|
+
else
|
970
|
+
sql << ' FROM '
|
971
|
+
source_list_append(sql, from)
|
972
|
+
select_join_sql(sql)
|
973
|
+
end
|
974
|
+
end
|
975
|
+
|
976
|
+
# Only include the primary table in the main update clause
|
977
|
+
def update_table_sql(sql)
|
978
|
+
sql << ' '
|
979
|
+
source_list_append(sql, @opts[:from][0..0])
|
980
|
+
end
|
947
981
|
end
|
948
982
|
end
|
949
983
|
end
|
data/lib/sequel/database/misc.rb
CHANGED
@@ -213,8 +213,7 @@ module Sequel
|
|
213
213
|
Sequel.extension(*exts)
|
214
214
|
exts.each do |ext|
|
215
215
|
if pr = Sequel.synchronize{EXTENSIONS[ext]}
|
216
|
-
|
217
|
-
Sequel.synchronize{@loaded_extensions << ext}
|
216
|
+
if Sequel.synchronize{@loaded_extensions.include?(ext) ? false : (@loaded_extensions << ext)}
|
218
217
|
pr.call(self)
|
219
218
|
end
|
220
219
|
else
|
@@ -143,8 +143,14 @@ module Sequel
|
|
143
143
|
# :identity :: Create an identity column.
|
144
144
|
#
|
145
145
|
# MySQL specific options:
|
146
|
+
#
|
146
147
|
# :generated_type :: Set the type of column when using :generated_always_as,
|
147
148
|
# should be :virtual or :stored to force a type.
|
149
|
+
#
|
150
|
+
# Microsoft SQL Server specific options:
|
151
|
+
#
|
152
|
+
# :clustered :: When using :primary_key or :unique, marks the primary key or unique
|
153
|
+
# constraint as CLUSTERED (if true), or NONCLUSTERED (if false).
|
148
154
|
def column(name, type, opts = OPTS)
|
149
155
|
columns << {:name => name, :type => type}.merge!(opts)
|
150
156
|
if index_opts = opts[:index]
|
@@ -153,7 +159,7 @@ module Sequel
|
|
153
159
|
nil
|
154
160
|
end
|
155
161
|
|
156
|
-
# Adds a named constraint (or unnamed if name is nil),
|
162
|
+
# Adds a named CHECK constraint (or unnamed if name is nil),
|
157
163
|
# with the given block or args. To provide options for the constraint, pass
|
158
164
|
# a hash as the first argument.
|
159
165
|
#
|
@@ -161,6 +167,15 @@ module Sequel
|
|
161
167
|
# # CONSTRAINT blah CHECK num >= 1 AND num <= 5
|
162
168
|
# constraint({name: :blah, deferrable: true}, num: 1..5)
|
163
169
|
# # CONSTRAINT blah CHECK num >= 1 AND num <= 5 DEFERRABLE INITIALLY DEFERRED
|
170
|
+
#
|
171
|
+
# If the first argument is a hash, the following options are supported:
|
172
|
+
#
|
173
|
+
# Options:
|
174
|
+
# :name :: The name of the CHECK constraint
|
175
|
+
# :deferrable :: Whether the CHECK constraint should be marked DEFERRABLE.
|
176
|
+
#
|
177
|
+
# PostgreSQL specific options:
|
178
|
+
# :not_valid :: Whether the CHECK constraint should be marked NOT VALID.
|
164
179
|
def constraint(name, *args, &block)
|
165
180
|
opts = name.is_a?(Hash) ? name : {:name=>name}
|
166
181
|
constraints << opts.merge(:type=>:check, :check=>block || args)
|
@@ -262,6 +262,10 @@ module Sequel
|
|
262
262
|
# # SELECT * FROM items WHERE foo
|
263
263
|
# # WITH CHECK OPTION
|
264
264
|
#
|
265
|
+
# DB.create_view(:bar_items, DB[:items].select(:foo), columns: [:bar])
|
266
|
+
# # CREATE VIEW bar_items (bar) AS
|
267
|
+
# # SELECT foo FROM items
|
268
|
+
#
|
265
269
|
# Options:
|
266
270
|
# :columns :: The column names to use for the view. If not given,
|
267
271
|
# automatically determined based on the input dataset.
|
@@ -580,14 +584,14 @@ module Sequel
|
|
580
584
|
sql << ' NULL'
|
581
585
|
end
|
582
586
|
end
|
583
|
-
|
587
|
+
|
584
588
|
# Add primary key SQL fragment to column creation SQL.
|
585
589
|
def column_definition_primary_key_sql(sql, column)
|
586
590
|
if column[:primary_key]
|
587
591
|
if name = column[:primary_key_constraint_name]
|
588
592
|
sql << " CONSTRAINT #{quote_identifier(name)}"
|
589
593
|
end
|
590
|
-
sql <<
|
594
|
+
sql << " " << primary_key_constraint_sql_fragment(column)
|
591
595
|
constraint_deferrable_sql_append(sql, column[:primary_key_deferrable])
|
592
596
|
end
|
593
597
|
end
|
@@ -608,7 +612,7 @@ module Sequel
|
|
608
612
|
if name = column[:unique_constraint_name]
|
609
613
|
sql << " CONSTRAINT #{quote_identifier(name)}"
|
610
614
|
end
|
611
|
-
sql << '
|
615
|
+
sql << ' ' << unique_constraint_sql_fragment(column)
|
612
616
|
constraint_deferrable_sql_append(sql, column[:unique_deferrable])
|
613
617
|
end
|
614
618
|
end
|
@@ -656,11 +660,11 @@ module Sequel
|
|
656
660
|
check = "(#{check})" unless check[0..0] == '(' && check[-1..-1] == ')'
|
657
661
|
sql << "CHECK #{check}"
|
658
662
|
when :primary_key
|
659
|
-
sql << "
|
663
|
+
sql << "#{primary_key_constraint_sql_fragment(constraint)} #{literal(constraint[:columns])}"
|
660
664
|
when :foreign_key
|
661
665
|
sql << column_references_table_constraint_sql(constraint.merge(:deferrable=>nil))
|
662
666
|
when :unique
|
663
|
-
sql << "
|
667
|
+
sql << "#{unique_constraint_sql_fragment(constraint)} #{literal(constraint[:columns])}"
|
664
668
|
else
|
665
669
|
raise Error, "Invalid constraint type #{constraint[:type]}, should be :check, :primary_key, :foreign_key, or :unique"
|
666
670
|
end
|
@@ -892,6 +896,11 @@ module Sequel
|
|
892
896
|
on_delete_clause(action)
|
893
897
|
end
|
894
898
|
|
899
|
+
# Add fragment for primary key specification, separated for easier overridding.
|
900
|
+
def primary_key_constraint_sql_fragment(_)
|
901
|
+
'PRIMARY KEY'
|
902
|
+
end
|
903
|
+
|
895
904
|
# Proxy the quote_schema_table method to the dataset
|
896
905
|
def quote_schema_table(table)
|
897
906
|
schema_utility_dataset.quote_schema_table(table)
|
@@ -1047,6 +1056,11 @@ module Sequel
|
|
1047
1056
|
"#{type}#{literal(Array(elements)) if elements}#{' UNSIGNED' if column[:unsigned]}"
|
1048
1057
|
end
|
1049
1058
|
|
1059
|
+
# Add fragment for unique specification, separated for easier overridding.
|
1060
|
+
def unique_constraint_sql_fragment(_)
|
1061
|
+
'UNIQUE'
|
1062
|
+
end
|
1063
|
+
|
1050
1064
|
# Whether clob should be used for String text: true columns.
|
1051
1065
|
def uses_clob_for_text?
|
1052
1066
|
false
|