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.
Files changed (52) hide show
  1. data/CHANGELOG +30 -0
  2. data/README.rdoc +4 -3
  3. data/doc/active_record.rdoc +1 -1
  4. data/doc/opening_databases.rdoc +7 -0
  5. data/doc/release_notes/3.40.0.txt +73 -0
  6. data/lib/sequel/adapters/ado.rb +29 -3
  7. data/lib/sequel/adapters/ado/access.rb +334 -0
  8. data/lib/sequel/adapters/ado/mssql.rb +0 -6
  9. data/lib/sequel/adapters/cubrid.rb +143 -0
  10. data/lib/sequel/adapters/jdbc.rb +26 -18
  11. data/lib/sequel/adapters/jdbc/cubrid.rb +52 -0
  12. data/lib/sequel/adapters/jdbc/derby.rb +7 -7
  13. data/lib/sequel/adapters/jdbc/hsqldb.rb +5 -0
  14. data/lib/sequel/adapters/jdbc/mysql.rb +9 -4
  15. data/lib/sequel/adapters/mysql.rb +0 -3
  16. data/lib/sequel/adapters/mysql2.rb +0 -3
  17. data/lib/sequel/adapters/oracle.rb +4 -1
  18. data/lib/sequel/adapters/postgres.rb +4 -4
  19. data/lib/sequel/adapters/shared/access.rb +205 -3
  20. data/lib/sequel/adapters/shared/cubrid.rb +216 -0
  21. data/lib/sequel/adapters/shared/db2.rb +7 -2
  22. data/lib/sequel/adapters/shared/mssql.rb +3 -34
  23. data/lib/sequel/adapters/shared/mysql.rb +4 -33
  24. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +11 -0
  25. data/lib/sequel/adapters/shared/oracle.rb +5 -0
  26. data/lib/sequel/adapters/shared/postgres.rb +2 -1
  27. data/lib/sequel/adapters/utils/split_alter_table.rb +36 -0
  28. data/lib/sequel/database/connecting.rb +1 -1
  29. data/lib/sequel/database/query.rb +30 -7
  30. data/lib/sequel/database/schema_methods.rb +7 -2
  31. data/lib/sequel/dataset/query.rb +9 -10
  32. data/lib/sequel/dataset/sql.rb +14 -26
  33. data/lib/sequel/extensions/pg_hstore.rb +19 -0
  34. data/lib/sequel/extensions/pg_row.rb +5 -5
  35. data/lib/sequel/plugins/association_pks.rb +121 -18
  36. data/lib/sequel/plugins/json_serializer.rb +19 -0
  37. data/lib/sequel/sql.rb +11 -0
  38. data/lib/sequel/version.rb +1 -1
  39. data/spec/adapters/postgres_spec.rb +42 -0
  40. data/spec/core/database_spec.rb +17 -0
  41. data/spec/core/dataset_spec.rb +11 -0
  42. data/spec/core/expression_filters_spec.rb +13 -0
  43. data/spec/extensions/association_pks_spec.rb +163 -3
  44. data/spec/extensions/pg_hstore_spec.rb +6 -0
  45. data/spec/extensions/pg_row_spec.rb +17 -0
  46. data/spec/integration/associations_test.rb +1 -1
  47. data/spec/integration/dataset_test.rb +13 -13
  48. data/spec/integration/plugin_test.rb +232 -7
  49. data/spec/integration/schema_test.rb +8 -12
  50. data/spec/integration/spec_helper.rb +1 -1
  51. data/spec/integration/type_test.rb +6 -0
  52. 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
- return Sequel.synchronize{@schemas[quoted_name]} if @schemas[quoted_name]
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
- synchronize(opts[:server]) do |conn|
262
- return yield(conn) if already_in_transaction?(conn, opts)
263
- _transaction(conn, opts, &block)
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 if default.nil?
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
- alter_table_op = case op[:op]
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
@@ -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].select(:i___id, :pi___parent_id).
978
- # with_recursive(:t,
979
- # DB[:i1].filter(:parent_id=>nil),
980
- # DB[:t].join(:t, :i=>:parent_id).select(:i1__id, :i1__parent_id),
981
- # :args=>[:i, :pi])
982
- # # WITH RECURSIVE t(i, pi) AS (
983
- # # SELECT * FROM i1 WHERE (parent_id IS NULL)
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 t INNER JOIN t ON (t.i = t.parent_id)
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)
@@ -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
- c = false
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
- argument_list_append(sql, args)
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
- c = false
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
- def_association_pks_getter(opts) do
56
- _join_table_dataset(opts).filter(opts[:left_key]=>send(opts[:left_primary_key])).select_map(opts[:right_key])
57
- end
58
- def_association_pks_setter(opts) do |pks|
59
- pks = convert_pk_array(opts, pks)
60
- checked_transaction do
61
- ds = _join_table_dataset(opts).filter(opts[:left_key]=>send(opts[:left_primary_key]))
62
- ds.exclude(opts[:right_key]=>pks).delete
63
- pks -= ds.select_map(opts[:right_key])
64
- pks.each{|pk| ds.insert(opts[:left_key]=>send(opts[:left_primary_key]), opts[:right_key]=>pk)}
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
- pks = convert_pk_array(opts, pks)
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
- primary_key = opts.associated_class.primary_key
82
- key = opts[:key]
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)