sequel 3.39.0 → 3.40.0

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