sequel 3.39.0 → 3.40.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 +30 -0
- data/README.rdoc +4 -3
- data/doc/active_record.rdoc +1 -1
- data/doc/opening_databases.rdoc +7 -0
- data/doc/release_notes/3.40.0.txt +73 -0
- data/lib/sequel/adapters/ado.rb +29 -3
- data/lib/sequel/adapters/ado/access.rb +334 -0
- data/lib/sequel/adapters/ado/mssql.rb +0 -6
- data/lib/sequel/adapters/cubrid.rb +143 -0
- data/lib/sequel/adapters/jdbc.rb +26 -18
- data/lib/sequel/adapters/jdbc/cubrid.rb +52 -0
- data/lib/sequel/adapters/jdbc/derby.rb +7 -7
- data/lib/sequel/adapters/jdbc/hsqldb.rb +5 -0
- data/lib/sequel/adapters/jdbc/mysql.rb +9 -4
- data/lib/sequel/adapters/mysql.rb +0 -3
- data/lib/sequel/adapters/mysql2.rb +0 -3
- data/lib/sequel/adapters/oracle.rb +4 -1
- data/lib/sequel/adapters/postgres.rb +4 -4
- data/lib/sequel/adapters/shared/access.rb +205 -3
- data/lib/sequel/adapters/shared/cubrid.rb +216 -0
- data/lib/sequel/adapters/shared/db2.rb +7 -2
- data/lib/sequel/adapters/shared/mssql.rb +3 -34
- data/lib/sequel/adapters/shared/mysql.rb +4 -33
- data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +11 -0
- data/lib/sequel/adapters/shared/oracle.rb +5 -0
- data/lib/sequel/adapters/shared/postgres.rb +2 -1
- data/lib/sequel/adapters/utils/split_alter_table.rb +36 -0
- data/lib/sequel/database/connecting.rb +1 -1
- data/lib/sequel/database/query.rb +30 -7
- data/lib/sequel/database/schema_methods.rb +7 -2
- data/lib/sequel/dataset/query.rb +9 -10
- data/lib/sequel/dataset/sql.rb +14 -26
- data/lib/sequel/extensions/pg_hstore.rb +19 -0
- data/lib/sequel/extensions/pg_row.rb +5 -5
- data/lib/sequel/plugins/association_pks.rb +121 -18
- data/lib/sequel/plugins/json_serializer.rb +19 -0
- data/lib/sequel/sql.rb +11 -0
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +42 -0
- data/spec/core/database_spec.rb +17 -0
- data/spec/core/dataset_spec.rb +11 -0
- data/spec/core/expression_filters_spec.rb +13 -0
- data/spec/extensions/association_pks_spec.rb +163 -3
- data/spec/extensions/pg_hstore_spec.rb +6 -0
- data/spec/extensions/pg_row_spec.rb +17 -0
- data/spec/integration/associations_test.rb +1 -1
- data/spec/integration/dataset_test.rb +13 -13
- data/spec/integration/plugin_test.rb +232 -7
- data/spec/integration/schema_test.rb +8 -12
- data/spec/integration/spec_helper.rb +1 -1
- data/spec/integration/type_test.rb +6 -0
- metadata +9 -2
@@ -22,7 +22,7 @@ module Sequel
|
|
22
22
|
:serializable=>'SERIALIZABLE'.freeze}
|
23
23
|
|
24
24
|
STRING_DEFAULT_RE = /\A'(.*)'\z/
|
25
|
-
CURRENT_TIMESTAMP_RE = /now|CURRENT|getdate/io
|
25
|
+
CURRENT_TIMESTAMP_RE = /now|CURRENT|getdate|\ADate\(\)\z/io
|
26
26
|
COLUMN_SCHEMA_DATETIME_TYPES = [:date, :datetime]
|
27
27
|
COLUMN_SCHEMA_STRING_TYPES = [:string, :blob, :date, :datetime, :time, :enum, :set, :interval]
|
28
28
|
|
@@ -195,7 +195,9 @@ module Sequel
|
|
195
195
|
opts[:schema] = sch if sch && !opts.include?(:schema)
|
196
196
|
|
197
197
|
Sequel.synchronize{@schemas.delete(quoted_name)} if opts[:reload]
|
198
|
-
|
198
|
+
if v = Sequel.synchronize{@schemas[quoted_name]}
|
199
|
+
return v
|
200
|
+
end
|
199
201
|
|
200
202
|
cols = schema_parse_table(table_name, opts)
|
201
203
|
raise(Error, 'schema parsing returned no columns, table probably doesn\'t exist') if cols.nil? || cols.empty?
|
@@ -234,6 +236,14 @@ module Sequel
|
|
234
236
|
#
|
235
237
|
# The following general options are respected:
|
236
238
|
#
|
239
|
+
# :disconnect :: Can be set to :retry to automatically retry the transaction with
|
240
|
+
# a new connection object if it detects a disconnect on the connection.
|
241
|
+
# Note that this should not be used unless the entire transaction
|
242
|
+
# block is idempotent, as otherwise it can cause non-idempotent
|
243
|
+
# behavior to execute multiple times. This does no checking for
|
244
|
+
# infinite loops, so if your transaction will repeatedly raise a
|
245
|
+
# disconnection error, this will cause the transaction block to loop
|
246
|
+
# indefinitely.
|
237
247
|
# :isolation :: The transaction isolation level to use for this transaction,
|
238
248
|
# should be :uncommitted, :committed, :repeatable, or :serializable,
|
239
249
|
# used if given and the database/adapter supports customizable
|
@@ -258,9 +268,22 @@ module Sequel
|
|
258
268
|
# appropriately. Valid values true, :on, false, :off, :local (9.1+),
|
259
269
|
# and :remote_write (9.2+).
|
260
270
|
def transaction(opts={}, &block)
|
261
|
-
|
262
|
-
|
263
|
-
|
271
|
+
if opts[:disconnect] == :retry
|
272
|
+
begin
|
273
|
+
transaction(opts.merge(:disconnect=>:disallow), &block)
|
274
|
+
rescue Sequel::DatabaseDisconnectError
|
275
|
+
retry
|
276
|
+
end
|
277
|
+
else
|
278
|
+
synchronize(opts[:server]) do |conn|
|
279
|
+
if already_in_transaction?(conn, opts)
|
280
|
+
if opts[:disconnect] == :disallow
|
281
|
+
raise Sequel::Error, "cannot set :disconnect=>:retry if you are already inside a transaction"
|
282
|
+
end
|
283
|
+
return yield(conn)
|
284
|
+
end
|
285
|
+
_transaction(conn, opts, &block)
|
286
|
+
end
|
264
287
|
end
|
265
288
|
end
|
266
289
|
|
@@ -440,7 +463,7 @@ module Sequel
|
|
440
463
|
# Convert the given default, which should be a database specific string, into
|
441
464
|
# a ruby object.
|
442
465
|
def column_schema_to_ruby_default(default, type)
|
443
|
-
return
|
466
|
+
return default unless default.is_a?(String)
|
444
467
|
if COLUMN_SCHEMA_DATETIME_TYPES.include?(type)
|
445
468
|
if CURRENT_TIMESTAMP_RE.match(default)
|
446
469
|
if type == :date
|
@@ -582,7 +605,7 @@ module Sequel
|
|
582
605
|
# such as :integer or :string.
|
583
606
|
def schema_column_type(db_type)
|
584
607
|
case db_type
|
585
|
-
when /\A(character( varying)?|n?(var)?char|n?text)/io
|
608
|
+
when /\A(character( varying)?|n?(var)?char|n?text|string)/io
|
586
609
|
:string
|
587
610
|
when /\A(int(eger)?|(big|small|tiny)int)/io
|
588
611
|
:integer
|
@@ -354,7 +354,7 @@ module Sequel
|
|
354
354
|
# SQL fragment for given alter table operation.
|
355
355
|
def alter_table_op_sql(table, op)
|
356
356
|
quoted_name = quote_identifier(op[:name]) if op[:name]
|
357
|
-
|
357
|
+
case op[:op]
|
358
358
|
when :add_column
|
359
359
|
"ADD COLUMN #{column_definition_sql(op)}"
|
360
360
|
when :drop_column
|
@@ -783,7 +783,7 @@ module Sequel
|
|
783
783
|
# :text option is used, Sequel uses the :text type.
|
784
784
|
def type_literal_generic_string(column)
|
785
785
|
if column[:text]
|
786
|
-
:text
|
786
|
+
uses_clob_for_text? ? :clob : :text
|
787
787
|
elsif column[:fixed]
|
788
788
|
"char(#{column[:size]||255})"
|
789
789
|
else
|
@@ -811,5 +811,10 @@ module Sequel
|
|
811
811
|
elements = column[:size] || column[:elements]
|
812
812
|
"#{type}#{literal(Array(elements)) if elements}#{UNSIGNED if column[:unsigned]}"
|
813
813
|
end
|
814
|
+
|
815
|
+
# Whether clob should be used for String :text=>true columns.
|
816
|
+
def uses_clob_for_text?
|
817
|
+
false
|
818
|
+
end
|
814
819
|
end
|
815
820
|
end
|
data/lib/sequel/dataset/query.rb
CHANGED
@@ -974,17 +974,16 @@ module Sequel
|
|
974
974
|
# :args :: Specify the arguments/columns for the CTE, should be an array of symbols.
|
975
975
|
# :union_all :: Set to false to use UNION instead of UNION ALL combining the nonrecursive and recursive parts.
|
976
976
|
#
|
977
|
-
# DB[:t].
|
978
|
-
#
|
979
|
-
#
|
980
|
-
#
|
981
|
-
#
|
982
|
-
# # WITH RECURSIVE t(
|
983
|
-
# # SELECT
|
977
|
+
# DB[:t].with_recursive(:t,
|
978
|
+
# DB[:i1].select(:id, :parent_id).filter(:parent_id=>nil),
|
979
|
+
# DB[:i1].join(:t, :id=>:parent_id).select(:i1__id, :i1__parent_id),
|
980
|
+
# :args=>[:id, :parent_id])
|
981
|
+
#
|
982
|
+
# # WITH RECURSIVE "t"("id", "parent_id") AS (
|
983
|
+
# # SELECT "id", "parent_id" FROM "i1" WHERE ("parent_id" IS NULL)
|
984
984
|
# # UNION ALL
|
985
|
-
# # SELECT i1.id, i1.parent_id FROM
|
986
|
-
# # )
|
987
|
-
# # SELECT i AS id, pi AS parent_id FROM t
|
985
|
+
# # SELECT "i1"."id", "i1"."parent_id" FROM "i1" INNER JOIN "t" ON ("t"."id" = "i1"."parent_id")
|
986
|
+
# # ) SELECT * FROM "t"
|
988
987
|
def with_recursive(name, nonrecursive, recursive, opts={})
|
989
988
|
raise(Error, 'This datatset does not support common table expressions') unless supports_cte?
|
990
989
|
if hoist_cte?(nonrecursive)
|
data/lib/sequel/dataset/sql.rb
CHANGED
@@ -306,7 +306,6 @@ module Sequel
|
|
306
306
|
window_function_sql
|
307
307
|
END
|
308
308
|
PRIVATE_APPEND_METHODS = (<<-END).split.map{|x| x.to_sym}
|
309
|
-
argument_list
|
310
309
|
as_sql
|
311
310
|
column_list
|
312
311
|
compound_dataset_sql
|
@@ -783,16 +782,6 @@ module Sequel
|
|
783
782
|
options_overlap(COUNT_FROM_SELF_OPTS) ? from_self : unordered
|
784
783
|
end
|
785
784
|
|
786
|
-
def argument_list_append(sql, args)
|
787
|
-
c = false
|
788
|
-
comma = COMMA
|
789
|
-
args.each do |a|
|
790
|
-
sql << comma if c
|
791
|
-
sql << a.to_s
|
792
|
-
c ||= true
|
793
|
-
end
|
794
|
-
end
|
795
|
-
|
796
785
|
# SQL fragment for specifying an alias. expression should already be literalized.
|
797
786
|
def as_sql_append(sql, aliaz)
|
798
787
|
sql << AS
|
@@ -940,6 +929,17 @@ module Sequel
|
|
940
929
|
end
|
941
930
|
alias table_ref_append identifier_append
|
942
931
|
|
932
|
+
# Append all identifiers in args interspersed by commas.
|
933
|
+
def identifier_list_append(sql, args)
|
934
|
+
c = false
|
935
|
+
comma = COMMA
|
936
|
+
args.each do |a|
|
937
|
+
sql << comma if c
|
938
|
+
identifier_append(sql, a)
|
939
|
+
c ||= true
|
940
|
+
end
|
941
|
+
end
|
942
|
+
|
943
943
|
# Modify the identifier returned from the database based on the
|
944
944
|
# identifier_output_method.
|
945
945
|
def input_identifier(v)
|
@@ -962,13 +962,7 @@ module Sequel
|
|
962
962
|
columns = opts[:columns]
|
963
963
|
if columns && !columns.empty?
|
964
964
|
sql << PAREN_SPACE_OPEN
|
965
|
-
|
966
|
-
co = COMMA
|
967
|
-
columns.each do |col|
|
968
|
-
sql << co if c
|
969
|
-
identifier_append(sql, col)
|
970
|
-
c ||= true
|
971
|
-
end
|
965
|
+
identifier_list_append(sql, columns)
|
972
966
|
sql << PAREN_CLOSE
|
973
967
|
end
|
974
968
|
end
|
@@ -1311,7 +1305,7 @@ module Sequel
|
|
1311
1305
|
quote_identifier_append(sql, w[:name])
|
1312
1306
|
if args = w[:args]
|
1313
1307
|
sql << PAREN_OPEN
|
1314
|
-
|
1308
|
+
identifier_list_append(sql, args)
|
1315
1309
|
sql << PAREN_CLOSE
|
1316
1310
|
end
|
1317
1311
|
sql << AS
|
@@ -1332,13 +1326,7 @@ module Sequel
|
|
1332
1326
|
# Converts an array of source names into into a comma separated list.
|
1333
1327
|
def source_list_append(sql, sources)
|
1334
1328
|
raise(Error, 'No source specified for query') if sources.nil? || sources == []
|
1335
|
-
|
1336
|
-
co = COMMA
|
1337
|
-
sources.each do |s|
|
1338
|
-
sql << co if c
|
1339
|
-
identifier_append(sql, s)
|
1340
|
-
c ||= true
|
1341
|
-
end
|
1329
|
+
identifier_list_append(sql, sources)
|
1342
1330
|
end
|
1343
1331
|
|
1344
1332
|
# Delegate to Sequel.split_symbol.
|
@@ -182,6 +182,20 @@ module Sequel
|
|
182
182
|
ESCAPE_REPLACE = '\\\\\1'.freeze
|
183
183
|
HSTORE_CAST = '::hstore'.freeze
|
184
184
|
|
185
|
+
if RUBY_VERSION >= '1.9'
|
186
|
+
# Undef 1.9 marshal_{dump,load} methods in the delegate class,
|
187
|
+
# so that ruby 1.9 uses the old style _dump/_load methods defined
|
188
|
+
# in the delegate class, instead of the marshal_{dump,load} methods
|
189
|
+
# in the Hash class.
|
190
|
+
undef_method :marshal_load
|
191
|
+
undef_method :marshal_dump
|
192
|
+
end
|
193
|
+
|
194
|
+
# Use custom marshal loading, since underlying hash uses a default proc.
|
195
|
+
def self._load(args)
|
196
|
+
new(Hash[Marshal.load(args)])
|
197
|
+
end
|
198
|
+
|
185
199
|
# Parse the given string into an HStore, assuming the str is in PostgreSQL
|
186
200
|
# hstore output format.
|
187
201
|
def self.parse(str)
|
@@ -209,6 +223,11 @@ module Sequel
|
|
209
223
|
class_eval("def #{m}(h, &block) super(convert_hash(h), &block) end", __FILE__, __LINE__)
|
210
224
|
end
|
211
225
|
|
226
|
+
# Use custom marshal dumping, since underlying hash uses a default proc.
|
227
|
+
def _dump(*)
|
228
|
+
Marshal.dump(to_a)
|
229
|
+
end
|
230
|
+
|
212
231
|
# Override to force the key argument to a string.
|
213
232
|
def fetch(key, *args, &block)
|
214
233
|
super(key.to_s, *args, &block)
|
@@ -321,7 +321,7 @@ module Sequel
|
|
321
321
|
# ruby objects, one for each converter.
|
322
322
|
def convert_columns(arr)
|
323
323
|
if ccs = @column_converters
|
324
|
-
arr.zip(ccs).map{|v, pr| pr ? pr.call(v) : v}
|
324
|
+
arr.zip(ccs).map{|v, pr| (v && pr) ? pr.call(v) : v}
|
325
325
|
else
|
326
326
|
arr
|
327
327
|
end
|
@@ -379,10 +379,10 @@ module Sequel
|
|
379
379
|
def bound_variable_arg(arg, conn)
|
380
380
|
case arg
|
381
381
|
when ArrayRow
|
382
|
-
"(#{arg.map{|v| bound_variable_array(v)}.join(COMMA)})"
|
382
|
+
"(#{arg.map{|v| bound_variable_array(v) if v}.join(COMMA)})"
|
383
383
|
when HashRow
|
384
384
|
arg.check_columns!
|
385
|
-
"(#{arg.values_at(*arg.columns).map{|v| bound_variable_array(v)}.join(COMMA)})"
|
385
|
+
"(#{arg.values_at(*arg.columns).map{|v| bound_variable_array(v) if v}.join(COMMA)})"
|
386
386
|
else
|
387
387
|
super
|
388
388
|
end
|
@@ -528,10 +528,10 @@ module Sequel
|
|
528
528
|
def bound_variable_array(arg)
|
529
529
|
case arg
|
530
530
|
when ArrayRow
|
531
|
-
"\"(#{arg.map{|v| bound_variable_array(v)}.join(COMMA).gsub(ESCAPE_RE, ESCAPE_REPLACEMENT)})\""
|
531
|
+
"\"(#{arg.map{|v| bound_variable_array(v) if v}.join(COMMA).gsub(ESCAPE_RE, ESCAPE_REPLACEMENT)})\""
|
532
532
|
when HashRow
|
533
533
|
arg.check_columns!
|
534
|
-
"\"(#{arg.values_at(*arg.columns).map{|v| bound_variable_array(v)}.join(COMMA).gsub(ESCAPE_RE, ESCAPE_REPLACEMENT)})\""
|
534
|
+
"\"(#{arg.values_at(*arg.columns).map{|v| bound_variable_array(v) if v}.join(COMMA).gsub(ESCAPE_RE, ESCAPE_REPLACEMENT)})\""
|
535
535
|
else
|
536
536
|
super
|
537
537
|
end
|
@@ -21,9 +21,6 @@ module Sequel
|
|
21
21
|
# not call any callbacks. If you have any association callbacks,
|
22
22
|
# you probably should not use the setter methods.
|
23
23
|
#
|
24
|
-
# This plugin only works with singular primary keys, it does not work
|
25
|
-
# with composite primary keys.
|
26
|
-
#
|
27
24
|
# Usage:
|
28
25
|
#
|
29
26
|
# # Make all model subclass *_to_many associations have association_pks
|
@@ -52,16 +49,85 @@ module Sequel
|
|
52
49
|
# a setter that deletes from or inserts into the join table.
|
53
50
|
def def_many_to_many(opts)
|
54
51
|
super
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
52
|
+
# Grab values from the reflection so that the hash lookup only needs to be
|
53
|
+
# done once instead of inside ever method call.
|
54
|
+
lk, lpk, rk = opts.values_at(:left_key, :left_primary_key, :right_key)
|
55
|
+
|
56
|
+
# Add 2 separate implementations of the getter method optimized for the
|
57
|
+
# composite and singular left key cases, and 4 separate implementations of the setter
|
58
|
+
# method optimized for each combination of composite and singular keys for both
|
59
|
+
# the left and right keys.
|
60
|
+
if lpk.is_a?(Array)
|
61
|
+
def_association_pks_getter(opts) do
|
62
|
+
h = {}
|
63
|
+
lk.zip(lpk).each{|k, pk| h[k] = send(pk)}
|
64
|
+
_join_table_dataset(opts).filter(h).select_map(rk)
|
65
|
+
end
|
66
|
+
|
67
|
+
if rk.is_a?(Array)
|
68
|
+
def_association_pks_setter(opts) do |pks|
|
69
|
+
pks = convert_cpk_array(opts, pks)
|
70
|
+
checked_transaction do
|
71
|
+
lpkv = lpk.map{|k| send(k)}
|
72
|
+
ds = _join_table_dataset(opts).filter(lk.zip(lpkv))
|
73
|
+
ds.exclude(rk=>pks).delete
|
74
|
+
pks -= ds.select_map(rk)
|
75
|
+
h = {}
|
76
|
+
lk.zip(lpkv).each{|k, v| h[k] = v}
|
77
|
+
pks.each do |pk|
|
78
|
+
ih = h.dup
|
79
|
+
rk.zip(pk).each{|k, v| ih[k] = v}
|
80
|
+
ds.insert(ih)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
else
|
85
|
+
def_association_pks_setter(opts) do |pks|
|
86
|
+
pks = convert_pk_array(opts, pks)
|
87
|
+
checked_transaction do
|
88
|
+
lpkv = lpk.map{|k| send(k)}
|
89
|
+
ds = _join_table_dataset(opts).filter(lk.zip(lpkv))
|
90
|
+
ds.exclude(rk=>pks).delete
|
91
|
+
pks -= ds.select_map(rk)
|
92
|
+
h = {}
|
93
|
+
lk.zip(lpkv).each{|k, v| h[k] = v}
|
94
|
+
pks.each do |pk|
|
95
|
+
ds.insert(h.merge(rk=>pk))
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
else
|
101
|
+
def_association_pks_getter(opts) do
|
102
|
+
_join_table_dataset(opts).filter(lk=>send(lpk)).select_map(rk)
|
103
|
+
end
|
104
|
+
|
105
|
+
if rk.is_a?(Array)
|
106
|
+
def_association_pks_setter(opts) do |pks|
|
107
|
+
pks = convert_cpk_array(opts, pks)
|
108
|
+
checked_transaction do
|
109
|
+
lpkv = send(lpk)
|
110
|
+
ds = _join_table_dataset(opts).filter(lk=>lpkv)
|
111
|
+
ds.exclude(rk=>pks).delete
|
112
|
+
pks -= ds.select_map(rk)
|
113
|
+
pks.each do |pk|
|
114
|
+
h = {lk=>lpkv}
|
115
|
+
rk.zip(pk).each{|k, v| h[k] = v}
|
116
|
+
ds.insert(h)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
else
|
121
|
+
def_association_pks_setter(opts) do |pks|
|
122
|
+
pks = convert_pk_array(opts, pks)
|
123
|
+
checked_transaction do
|
124
|
+
lpkv = send(lpk)
|
125
|
+
ds = _join_table_dataset(opts).filter(lk=>lpkv)
|
126
|
+
ds.exclude(rk=>pks).delete
|
127
|
+
pks -= ds.select_map(rk)
|
128
|
+
pks.each{|pk| ds.insert(lk=>lpkv, rk=>pk)}
|
129
|
+
end
|
130
|
+
end
|
65
131
|
end
|
66
132
|
end
|
67
133
|
end
|
@@ -71,17 +137,40 @@ module Sequel
|
|
71
137
|
def def_one_to_many(opts)
|
72
138
|
super
|
73
139
|
return if opts[:type] == :one_to_one
|
140
|
+
|
141
|
+
key = opts[:key]
|
142
|
+
|
74
143
|
def_association_pks_getter(opts) do
|
75
144
|
send(opts.dataset_method).select_map(opts.associated_class.primary_key)
|
76
145
|
end
|
146
|
+
|
77
147
|
def_association_pks_setter(opts) do |pks|
|
78
|
-
|
148
|
+
primary_key = opts.associated_class.primary_key
|
149
|
+
|
150
|
+
pks = if primary_key.is_a?(Array)
|
151
|
+
convert_cpk_array(opts, pks)
|
152
|
+
else
|
153
|
+
convert_pk_array(opts, pks)
|
154
|
+
end
|
155
|
+
|
156
|
+
pkh = {primary_key=>pks}
|
157
|
+
|
158
|
+
if key.is_a?(Array)
|
159
|
+
h = {}
|
160
|
+
nh = {}
|
161
|
+
key.zip(pk).each do|k, v|
|
162
|
+
h[k] = v
|
163
|
+
nh[k] = nil
|
164
|
+
end
|
165
|
+
else
|
166
|
+
h = {key=>pk}
|
167
|
+
nh = {key=>nil}
|
168
|
+
end
|
169
|
+
|
79
170
|
checked_transaction do
|
80
171
|
ds = send(opts.dataset_method)
|
81
|
-
|
82
|
-
|
83
|
-
ds.unfiltered.filter(primary_key=>pks).update(key=>pk)
|
84
|
-
ds.exclude(primary_key=>pks).update(key=>nil)
|
172
|
+
ds.unfiltered.filter(pkh).update(h)
|
173
|
+
ds.exclude(pkh).update(nh)
|
85
174
|
end
|
86
175
|
end
|
87
176
|
end
|
@@ -90,6 +179,20 @@ module Sequel
|
|
90
179
|
module InstanceMethods
|
91
180
|
private
|
92
181
|
|
182
|
+
# If any of associated class's composite primary key column types is integer,
|
183
|
+
# typecast the appropriate values to integer before using them.
|
184
|
+
def convert_cpk_array(opts, cpks)
|
185
|
+
if klass = opts.associated_class and sch = klass.db_schema and (cols = sch.values_at(*klass.primary_key)).all? and (convs = cols.map{|c| c[:type] == :integer}).any?
|
186
|
+
cpks.map do |cpk|
|
187
|
+
cpk.zip(convs).map do |pk, conv|
|
188
|
+
conv ? model.db.typecast_value(:integer, pk) : pk
|
189
|
+
end
|
190
|
+
end
|
191
|
+
else
|
192
|
+
cpks
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
93
196
|
# If the associated class's primary key column type is integer,
|
94
197
|
# typecast all provided values to integer before using them.
|
95
198
|
def convert_pk_array(opts, pks)
|