sequel 5.38.0 → 5.43.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|