sequel 4.9.0 → 4.10.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 (110) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +79 -1
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/Rakefile +2 -12
  6. data/bin/sequel +1 -0
  7. data/doc/advanced_associations.rdoc +82 -25
  8. data/doc/association_basics.rdoc +21 -22
  9. data/doc/core_extensions.rdoc +1 -1
  10. data/doc/opening_databases.rdoc +7 -0
  11. data/doc/release_notes/4.10.0.txt +226 -0
  12. data/doc/security.rdoc +1 -0
  13. data/doc/testing.rdoc +7 -7
  14. data/doc/transactions.rdoc +8 -0
  15. data/lib/sequel/adapters/jdbc.rb +160 -168
  16. data/lib/sequel/adapters/jdbc/db2.rb +17 -18
  17. data/lib/sequel/adapters/jdbc/derby.rb +5 -28
  18. data/lib/sequel/adapters/jdbc/h2.rb +11 -22
  19. data/lib/sequel/adapters/jdbc/hsqldb.rb +31 -18
  20. data/lib/sequel/adapters/jdbc/jtds.rb +0 -15
  21. data/lib/sequel/adapters/jdbc/oracle.rb +36 -35
  22. data/lib/sequel/adapters/jdbc/postgresql.rb +72 -90
  23. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +18 -16
  24. data/lib/sequel/adapters/jdbc/sqlite.rb +7 -0
  25. data/lib/sequel/adapters/jdbc/sqlserver.rb +10 -30
  26. data/lib/sequel/adapters/jdbc/transactions.rb +5 -6
  27. data/lib/sequel/adapters/openbase.rb +1 -7
  28. data/lib/sequel/adapters/postgres.rb +1 -1
  29. data/lib/sequel/adapters/shared/access.rb +3 -6
  30. data/lib/sequel/adapters/shared/cubrid.rb +24 -9
  31. data/lib/sequel/adapters/shared/db2.rb +13 -5
  32. data/lib/sequel/adapters/shared/firebird.rb +16 -16
  33. data/lib/sequel/adapters/shared/informix.rb +2 -5
  34. data/lib/sequel/adapters/shared/mssql.rb +72 -63
  35. data/lib/sequel/adapters/shared/mysql.rb +72 -40
  36. data/lib/sequel/adapters/shared/oracle.rb +27 -15
  37. data/lib/sequel/adapters/shared/postgres.rb +24 -44
  38. data/lib/sequel/adapters/shared/progress.rb +1 -5
  39. data/lib/sequel/adapters/shared/sqlanywhere.rb +26 -18
  40. data/lib/sequel/adapters/shared/sqlite.rb +21 -6
  41. data/lib/sequel/adapters/utils/emulate_offset_with_reverse_and_count.rb +8 -1
  42. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +8 -2
  43. data/lib/sequel/adapters/utils/split_alter_table.rb +8 -0
  44. data/lib/sequel/core.rb +14 -9
  45. data/lib/sequel/database/dataset_defaults.rb +1 -0
  46. data/lib/sequel/database/misc.rb +12 -0
  47. data/lib/sequel/database/query.rb +4 -1
  48. data/lib/sequel/database/schema_methods.rb +3 -2
  49. data/lib/sequel/database/transactions.rb +47 -17
  50. data/lib/sequel/dataset/features.rb +12 -2
  51. data/lib/sequel/dataset/mutation.rb +2 -0
  52. data/lib/sequel/dataset/placeholder_literalizer.rb +12 -4
  53. data/lib/sequel/dataset/prepared_statements.rb +6 -0
  54. data/lib/sequel/dataset/query.rb +1 -1
  55. data/lib/sequel/dataset/sql.rb +132 -70
  56. data/lib/sequel/extensions/columns_introspection.rb +1 -1
  57. data/lib/sequel/extensions/null_dataset.rb +8 -4
  58. data/lib/sequel/extensions/pg_array.rb +4 -4
  59. data/lib/sequel/extensions/pg_row.rb +1 -0
  60. data/lib/sequel/model/associations.rb +468 -188
  61. data/lib/sequel/model/base.rb +88 -13
  62. data/lib/sequel/plugins/association_pks.rb +23 -64
  63. data/lib/sequel/plugins/auto_validations.rb +3 -2
  64. data/lib/sequel/plugins/dataset_associations.rb +1 -3
  65. data/lib/sequel/plugins/many_through_many.rb +18 -65
  66. data/lib/sequel/plugins/pg_array_associations.rb +97 -86
  67. data/lib/sequel/plugins/prepared_statements.rb +2 -1
  68. data/lib/sequel/plugins/prepared_statements_associations.rb +36 -27
  69. data/lib/sequel/plugins/rcte_tree.rb +12 -16
  70. data/lib/sequel/plugins/sharding.rb +21 -3
  71. data/lib/sequel/plugins/single_table_inheritance.rb +2 -1
  72. data/lib/sequel/plugins/subclasses.rb +1 -9
  73. data/lib/sequel/plugins/tactical_eager_loading.rb +9 -0
  74. data/lib/sequel/plugins/tree.rb +2 -2
  75. data/lib/sequel/plugins/validation_class_methods.rb +1 -1
  76. data/lib/sequel/version.rb +1 -1
  77. data/spec/adapters/mssql_spec.rb +57 -15
  78. data/spec/adapters/mysql_spec.rb +11 -0
  79. data/spec/bin_spec.rb +2 -2
  80. data/spec/core/database_spec.rb +38 -4
  81. data/spec/core/dataset_spec.rb +45 -7
  82. data/spec/core/placeholder_literalizer_spec.rb +17 -0
  83. data/spec/core/schema_spec.rb +6 -1
  84. data/spec/extensions/active_model_spec.rb +18 -9
  85. data/spec/extensions/association_pks_spec.rb +20 -18
  86. data/spec/extensions/association_proxies_spec.rb +9 -9
  87. data/spec/extensions/auto_validations_spec.rb +6 -0
  88. data/spec/extensions/columns_introspection_spec.rb +1 -0
  89. data/spec/extensions/constraint_validations_spec.rb +3 -1
  90. data/spec/extensions/many_through_many_spec.rb +191 -111
  91. data/spec/extensions/pg_array_associations_spec.rb +133 -103
  92. data/spec/extensions/prepared_statements_associations_spec.rb +23 -4
  93. data/spec/extensions/rcte_tree_spec.rb +35 -27
  94. data/spec/extensions/sequel_3_dataset_methods_spec.rb +0 -1
  95. data/spec/extensions/sharding_spec.rb +2 -2
  96. data/spec/extensions/tactical_eager_loading_spec.rb +4 -0
  97. data/spec/extensions/to_dot_spec.rb +1 -0
  98. data/spec/extensions/touch_spec.rb +2 -2
  99. data/spec/integration/associations_test.rb +130 -37
  100. data/spec/integration/dataset_test.rb +17 -0
  101. data/spec/integration/model_test.rb +17 -0
  102. data/spec/integration/schema_test.rb +14 -0
  103. data/spec/integration/transaction_test.rb +25 -1
  104. data/spec/model/association_reflection_spec.rb +63 -24
  105. data/spec/model/associations_spec.rb +104 -57
  106. data/spec/model/base_spec.rb +14 -1
  107. data/spec/model/class_dataset_methods_spec.rb +1 -0
  108. data/spec/model/eager_loading_spec.rb +221 -74
  109. data/spec/model/model_spec.rb +119 -1
  110. metadata +4 -2
@@ -399,28 +399,59 @@ module Sequel
399
399
  end
400
400
 
401
401
  type = opts.fetch(:type, :first)
402
- raise Error, ":type option to Model.finder must be :first, :all, :each, or :get" unless FINDER_TYPES.include?(type)
402
+ unless prepare = opts[:prepare]
403
+ raise Error, ":type option to Model.finder must be :first, :all, :each, or :get" unless FINDER_TYPES.include?(type)
404
+ end
403
405
  limit1 = type == :first || type == :get
404
406
  meth_name ||= opts[:name] || :"#{type}_#{meth}"
405
407
 
406
- loader_proc = proc do |model|
407
- unless block
408
- argn = opts[:arity]
409
- block = lambda do |pl, model2|
410
- method = model2.method(meth)
411
- argn ||= (method.arity < 0 ? method.arity.abs - 1 : method.arity)
412
- args = (0...argn).map{pl.arg}
413
- ds = method.call(*args)
408
+ argn = lambda do |model|
409
+ if arity = opts[:arity]
410
+ arity
411
+ else
412
+ method = block || model.method(meth)
413
+ (method.arity < 0 ? method.arity.abs - 1 : method.arity)
414
+ end
415
+ end
416
+
417
+ loader_proc = if prepare
418
+ proc do |model|
419
+ args = prepare_method_args('$a', argn.call(model))
420
+ ds = if block
421
+ model.instance_exec(*args, &block)
422
+ else
423
+ model.send(meth, *args)
424
+ end
425
+ ds = ds.limit(1) if limit1
426
+ model_name = model.name
427
+ if model_name.to_s.empty?
428
+ model_name = model.object_id
429
+ else
430
+ model_name = model_name.gsub(/\W/, '_')
431
+ end
432
+ ds.prepare(type, :"#{model_name}_#{meth_name}")
433
+ end
434
+ else
435
+ proc do |model|
436
+ n = argn.call(model)
437
+ block ||= lambda do |pl, model2|
438
+ args = (0...n).map{pl.arg}
439
+ ds = model2.send(meth, *args)
414
440
  ds = ds.limit(1) if limit1
415
441
  ds
416
442
  end
417
- end
418
443
 
419
- Sequel::Dataset::PlaceholderLiteralizer.loader(model, &block)
444
+ Sequel::Dataset::PlaceholderLiteralizer.loader(model, &block)
445
+ end
420
446
  end
447
+
421
448
  Sequel.synchronize{@finder_loaders[meth_name] = loader_proc}
422
449
  mod = opts[:mod] || (class << self; self; end)
423
- def_finder_method(mod, meth_name, type)
450
+ if prepare
451
+ def_prepare_method(mod, meth_name)
452
+ else
453
+ def_finder_method(mod, meth_name, type)
454
+ end
424
455
  end
425
456
 
426
457
  # An alias for calling first on the model's dataset, but with
@@ -442,7 +473,7 @@ module Sequel
442
473
 
443
474
  # Clear the setter_methods cache when a module is included, as it
444
475
  # may contain setter methods.
445
- def include(mod)
476
+ def include(*mods)
446
477
  clear_setter_methods_cache
447
478
  super
448
479
  end
@@ -573,6 +604,27 @@ module Sequel
573
604
  h
574
605
  end
575
606
 
607
+ # Similar to finder, but uses a prepared statement instead of a placeholder
608
+ # literalizer. This makes the SQL used static (cannot vary per call), but
609
+ # allows binding argument values instead of literalizing them into the SQL
610
+ # query string.
611
+ #
612
+ # If a block is used with this method, it is instance_execed by the model,
613
+ # and should accept the desired number of placeholder arguments.
614
+ #
615
+ # The options are the same as the options for finder, with the following
616
+ # exception:
617
+ # :type :: Specifies the type of prepared statement to create
618
+ def prepared_finder(meth=OPTS, opts=OPTS, &block)
619
+ if block
620
+ raise Error, "cannot pass both a method name argument and a block of Model.finder" unless meth.is_a?(Hash)
621
+ meth = meth.merge(:prepare=>true)
622
+ else
623
+ opts = opts.merge(:prepare=>true)
624
+ end
625
+ finder(meth, opts, &block)
626
+ end
627
+
576
628
  # Restrict the setting of the primary key(s) when using mass assignment (e.g. +set+). Because
577
629
  # this is the default, this only make sense to use in a subclass where the
578
630
  # parent class has used +unrestrict_primary_key+.
@@ -818,6 +870,12 @@ module Sequel
818
870
  mod.send(:define_method, meth){|*args, &block| finder_for(meth).send(type, *args, &block)}
819
871
  end
820
872
 
873
+ # Define a prepared_finder method in the given module that will call the associated prepared
874
+ # statement.
875
+ def def_prepare_method(mod, meth)
876
+ mod.send(:define_method, meth){|*args, &block| finder_for(meth).call(prepare_method_arg_hash(args), &block)}
877
+ end
878
+
821
879
  # Find the finder to use for the give method. If a finder has not been loaded
822
880
  # for the method, load the finder and set correctly in the finders hash, then
823
881
  # return the finder.
@@ -948,6 +1006,23 @@ module Sequel
948
1006
  end
949
1007
  end
950
1008
 
1009
+ # An hash of prepared argument values for the given arguments, with keys
1010
+ # starting at a. Used by the methods created by prepared_finder.
1011
+ def prepare_method_arg_hash(args)
1012
+ h = {}
1013
+ prepare_method_args('a', args.length).zip(args).each{|k, v| h[k] = v}
1014
+ h
1015
+ end
1016
+
1017
+ # An array of prepared statement argument names, of length n and starting with base.
1018
+ def prepare_method_args(base, n)
1019
+ (0...n).map do
1020
+ s = base.to_sym
1021
+ base = base.next
1022
+ s
1023
+ end
1024
+ end
1025
+
951
1026
  # Find the row in the dataset that matches the primary key. Uses
952
1027
  # a static SQL optimization if the table and primary key are simple.
953
1028
  #
@@ -49,85 +49,44 @@ module Sequel
49
49
  # a setter that deletes from or inserts into the join table.
50
50
  def def_many_to_many(opts)
51
51
  super
52
+
53
+ return if opts[:type] == :one_through_one
54
+
52
55
  # Grab values from the reflection so that the hash lookup only needs to be
53
56
  # done once instead of inside ever method call.
54
57
  lk, lpk, rk = opts.values_at(:left_key, :left_primary_key, :right_key)
58
+ clpk = lpk.is_a?(Array)
59
+ crk = rk.is_a?(Array)
55
60
 
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
+ if clpk
61
62
  def_association_pks_getter(opts) do
62
63
  h = {}
63
64
  lk.zip(lpk).each{|k, pk| h[k] = send(pk)}
64
65
  _join_table_dataset(opts).filter(h).select_map(rk)
65
66
  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
67
  else
101
68
  def_association_pks_getter(opts) do
102
69
  _join_table_dataset(opts).filter(lk=>send(lpk)).select_map(rk)
103
70
  end
71
+ end
104
72
 
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
73
+ def_association_pks_setter(opts) do |pks|
74
+ pks = send(crk ? :convert_cpk_array : :convert_pk_array, opts, pks)
75
+ checked_transaction do
76
+ if clpk
77
+ lpkv = lpk.map{|k| send(k)}
78
+ cond = lk.zip(lpkv)
79
+ else
80
+ lpkv = send(lpk)
81
+ cond = {lk=>lpkv}
130
82
  end
83
+ ds = _join_table_dataset(opts).filter(cond)
84
+ ds.exclude(rk=>pks).delete
85
+ pks -= ds.select_map(rk)
86
+ lpkv = Array(lpkv)
87
+ key_array = crk ? pks.map{|pk| lpkv + pk} : pks.map{|pk| lpkv + [pk]}
88
+ key_columns = Array(lk) + Array(rk)
89
+ ds.import(key_columns, key_array)
131
90
  end
132
91
  end
133
92
  end
@@ -107,8 +107,9 @@ module Sequel
107
107
  @auto_validate_not_null_columns = not_null_cols - Array(primary_key)
108
108
  explicit_not_null_cols += Array(primary_key)
109
109
  @auto_validate_explicit_not_null_columns = explicit_not_null_cols.uniq
110
- @auto_validate_unique_columns = if db.supports_index_parsing?
111
- db.indexes(dataset.first_source_table).select{|name, idx| idx[:unique] == true}.map{|name, idx| idx[:columns]}
110
+ table = dataset.first_source_table
111
+ @auto_validate_unique_columns = if db.supports_index_parsing? && [Symbol, SQL::QualifiedIdentifier, SQL::Identifier, String].any?{|c| table.is_a?(c)}
112
+ db.indexes(table).select{|name, idx| idx[:unique] == true}.map{|name, idx| idx[:columns]}
112
113
  else
113
114
  []
114
115
  end
@@ -99,9 +99,7 @@ module Sequel
99
99
  else
100
100
  raise Error, "unrecognized association type for association #{name.inspect}: #{r[:type].inspect}"
101
101
  end
102
- ds = model.apply_association_dataset_opts(r, ds)
103
- r[:extend].each{|m| ds.extend(m)}
104
- ds
102
+ r.apply_eager_dataset_changes(ds).unlimited
105
103
  end
106
104
  end
107
105
  end
@@ -101,6 +101,11 @@ module Sequel
101
101
  END
102
102
  end
103
103
 
104
+ # The alias for the first join table.
105
+ def join_table_alias
106
+ final_reverse_edge[:alias]
107
+ end
108
+
104
109
  # Many through many associations don't have a reciprocal
105
110
  def reciprocal
106
111
  nil
@@ -108,6 +113,13 @@ module Sequel
108
113
 
109
114
  private
110
115
 
116
+ def _associated_dataset
117
+ ds = associated_class
118
+ reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias], :qualify=>:deep)}
119
+ ft = final_reverse_edge
120
+ ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])), :table_alias=>ft[:alias], :qualify=>:deep)
121
+ end
122
+
111
123
  # Make sure to use unique table aliases when lazy loading or eager loading
112
124
  def calculate_reverse_edge_aliases(reverse_edges)
113
125
  aliases = [associated_class.table_name]
@@ -156,26 +168,10 @@ module Sequel
156
168
  h
157
169
  end
158
170
 
159
- def filter_by_associations_add_conditions_dataset_filter(ds)
160
- reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias], :qualify=>:deep)}
161
- ft = final_reverse_edge
162
- k = qualify(ft[:alias], Array(self[:left_key]))
163
- ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])), :table_alias=>ft[:alias], :qualify=>:deep).
164
- where(Sequel.negate(k.zip([]))).
165
- select(*k)
166
- end
167
-
168
171
  def filter_by_associations_limit_key
169
172
  fe = edges.first
170
173
  Array(qualify(fe[:table], fe[:right])) + Array(qualify(associated_class.table_name, associated_class.primary_key))
171
174
  end
172
-
173
- def filter_by_associations_limit_subquery
174
- subquery = associated_eager_dataset.unlimited
175
- reverse_edges.each{|t| subquery = subquery.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias], :qualify=>:deep)}
176
- ft = final_reverse_edge
177
- subquery.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])), :table_alias=>ft[:alias], :qualify=>:deep)
178
- end
179
175
  end
180
176
 
181
177
  class OneThroughManyAssociationReflection < ManyThroughManyAssociationReflection
@@ -214,8 +210,6 @@ module Sequel
214
210
  # Create the association methods and :eager_loader and :eager_grapher procs.
215
211
  def def_many_through_many(opts)
216
212
  one_through_many = opts[:type] == :one_through_many
217
- name = opts[:name]
218
- model = self
219
213
  opts[:read_only] = true
220
214
  opts[:after_load].unshift(:array_uniq!) if opts[:uniq]
221
215
  opts[:cartesian_product_number] ||= one_through_many ? 0 : 2
@@ -233,56 +227,17 @@ module Sequel
233
227
  end
234
228
 
235
229
  left_key = opts[:left_key] = opts[:through].first[:left]
236
- uses_lcks = opts[:uses_left_composite_keys] = left_key.is_a?(Array)
237
- left_keys = Array(left_key)
230
+ opts[:left_keys] = Array(left_key)
231
+ opts[:uses_left_composite_keys] = left_key.is_a?(Array)
238
232
  left_pk = (opts[:left_primary_key] ||= self.primary_key)
239
233
  opts[:eager_loader_key] = left_pk unless opts.has_key?(:eager_loader_key)
240
- left_pks = opts[:left_primary_keys] = Array(left_pk)
234
+ opts[:left_primary_keys] = Array(left_pk)
241
235
  lpkc = opts[:left_primary_key_column] ||= left_pk
242
236
  lpkcs = opts[:left_primary_key_columns] ||= Array(lpkc)
243
- opts[:dataset] ||= lambda do
244
- ds = opts.associated_dataset
245
- opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias], :qualify=>:deep)}
246
- ft = opts.final_reverse_edge
247
- ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + opts.predicate_keys.zip(left_pks.map{|k| send(k)}), :table_alias=>ft[:alias], :qualify=>:deep)
248
- end
249
-
250
- slice_range = opts.slice_range
251
- left_key_alias = opts[:left_key_alias] ||= opts.default_associated_key_alias
252
- opts[:eager_loader] ||= lambda do |eo|
253
- h = eo[:id_map]
254
- rows = eo[:rows]
255
- ds = opts.associated_class
256
- opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias], :qualify=>:deep)}
257
- ft = opts.final_reverse_edge
258
-
259
- ds = ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + [[opts.predicate_key, h.keys]], :table_alias=>ft[:alias], :qualify=>:deep)
260
- ds = model.eager_loading_dataset(opts, ds, nil, eo[:associations], eo)
261
- ds = opts.apply_eager_limit_strategy(ds)
262
- opts.initialize_association_cache(rows)
237
+ opts[:dataset] ||= opts.association_dataset_proc
263
238
 
264
- assign_singular = opts.assign_singular?
265
- delete_rn = opts.delete_row_number_column(ds)
266
- ds.all do |assoc_record|
267
- assoc_record.values.delete(delete_rn) if delete_rn
268
- hash_key = if uses_lcks
269
- left_key_alias.map{|k| assoc_record.values.delete(k)}
270
- else
271
- assoc_record.values.delete(left_key_alias)
272
- end
273
- next unless objects = h[hash_key]
274
- if assign_singular
275
- objects.each do |object|
276
- object.associations[name] ||= assoc_record
277
- end
278
- else
279
- objects.each do |object|
280
- object.associations[name].push(assoc_record)
281
- end
282
- end
283
- end
284
- opts.apply_ruby_eager_limit_strategy(rows)
285
- end
239
+ opts[:left_key_alias] ||= opts.default_associated_key_alias
240
+ opts[:eager_loader] ||= opts.method(:default_eager_loader)
286
241
 
287
242
  join_type = opts[:graph_join_type]
288
243
  select = opts[:graph_select]
@@ -313,8 +268,6 @@ module Sequel
313
268
  ds.graph(opts.associated_class, use_only_conditions ? only_conditions : (Array(opts.right_primary_key).zip(Array(fe[:left])) + conditions), :select=>select, :table_alias=>eo[:table_alias], :qualify=>:deep, :join_type=>eo[:join_type]||join_type, &graph_block)
314
269
  end
315
270
  end
316
-
317
- def_association_dataset_methods(opts)
318
271
  end
319
272
 
320
273
  # Use def_many_through_many, since they share pretty much the same code.
@@ -47,11 +47,9 @@ module Sequel
47
47
  #
48
48
  # They support some additional options specific to this plugin:
49
49
  #
50
- # :array_type :: This allows you to specify the type of the array. This
51
- # is only necessary to set in very narrow circumstances,
52
- # such as when this plugin needs to create an array type,
53
- # and typecasting is turned off or not setup correctly
54
- # for the model object.
50
+ # :array_type :: This overrides the type of the array. By default, the type
51
+ # is determined by looking at the db_schema for the model, and if that fails,
52
+ # it defaults to :integer.
55
53
  # :raise_on_save_failure :: Do not raise exceptions for hook or validation failures when saving associated
56
54
  # objects in the add/remove methods (return nil instead).
57
55
  # :save_after_modify :: For pg_array_to_many associations, this makes the
@@ -77,6 +75,16 @@ module Sequel
77
75
  class ManyToPgArrayAssociationReflection < Sequel::Model::Associations::AssociationReflection
78
76
  Sequel::Model::Associations::ASSOCIATION_TYPES[:many_to_pg_array] = self
79
77
 
78
+ def array_type
79
+ cached_fetch(:array_type) do
80
+ if (sch = associated_class.db_schema) && (s = sch[self[:key]]) && (t = s[:db_type])
81
+ t
82
+ else
83
+ :integer
84
+ end
85
+ end
86
+ end
87
+
80
88
  # The array column in the associated model containing foreign keys to
81
89
  # the current model.
82
90
  def associated_object_keys
@@ -107,6 +115,11 @@ module Sequel
107
115
  end
108
116
  end
109
117
 
118
+ # Don't use a filter by associations limit strategy
119
+ def filter_by_associations_limit_strategy
120
+ nil
121
+ end
122
+
110
123
  # Handle silent failure of add/remove methods if raise_on_save_failure is false.
111
124
  def handle_silent_modification_failure?
112
125
  self[:raise_on_save_failure] == false
@@ -131,6 +144,11 @@ module Sequel
131
144
 
132
145
  private
133
146
 
147
+ # The predicate condition to use for the eager_loader.
148
+ def eager_loading_predicate_condition(keys)
149
+ Sequel.pg_array_op(predicate_key).overlaps(Sequel.pg_array(keys, array_type))
150
+ end
151
+
134
152
  def filter_by_associations_add_conditions_dataset_filter(ds)
135
153
  key = qualify(associated_class.table_name, self[:key])
136
154
  ds.select{unnest(key)}.exclude(key=>nil)
@@ -149,12 +167,26 @@ module Sequel
149
167
  def reciprocal_type
150
168
  :pg_array_to_many
151
169
  end
170
+
171
+ def use_placeholder_loader?
172
+ false
173
+ end
152
174
  end
153
175
 
154
176
  # The AssociationReflection subclass for pg_array_to_many associations.
155
177
  class PgArrayToManyAssociationReflection < Sequel::Model::Associations::AssociationReflection
156
178
  Sequel::Model::Associations::ASSOCIATION_TYPES[:pg_array_to_many] = self
157
179
 
180
+ def array_type
181
+ cached_fetch(:array_type) do
182
+ if (sch = self[:model].db_schema) && (s = sch[self[:key]]) && (t = s[:db_type])
183
+ t
184
+ else
185
+ :integer
186
+ end
187
+ end
188
+ end
189
+
158
190
  # An array containing the primary key for the associated model.
159
191
  def associated_object_keys
160
192
  Array(primary_key)
@@ -190,6 +222,11 @@ module Sequel
190
222
  end
191
223
  end
192
224
 
225
+ # Don't use a filter by associations limit strategy
226
+ def filter_by_associations_limit_strategy
227
+ nil
228
+ end
229
+
193
230
  # Handle silent failure of add/remove methods if raise_on_save_failure is false
194
231
  # and save_after_modify is true.
195
232
  def handle_silent_modification_failure?
@@ -236,6 +273,10 @@ module Sequel
236
273
  def reciprocal_type
237
274
  :many_to_pg_array
238
275
  end
276
+
277
+ def use_placeholder_loader?
278
+ false
279
+ end
239
280
  end
240
281
 
241
282
  module ClassMethods
@@ -264,18 +305,13 @@ module Sequel
264
305
  key = opts[:key]
265
306
  key_column = opts[:key_column] ||= opts[:key]
266
307
  opts[:after_load].unshift(:array_uniq!) if opts[:uniq]
267
- slice_range = opts.slice_range
268
308
  opts[:dataset] ||= lambda do
269
- opts.associated_dataset.where(Sequel.pg_array_op(opts.predicate_key).contains([send(pk)]))
309
+ opts.associated_dataset.where(Sequel.pg_array_op(opts.predicate_key).contains(Sequel.pg_array([send(pk)], opts.array_type)))
270
310
  end
271
311
  opts[:eager_loader] ||= proc do |eo|
272
312
  id_map = eo[:id_map]
273
- rows = eo[:rows]
274
- opts.initialize_association_cache(rows)
275
313
 
276
- klass = opts.associated_class
277
- ds = model.eager_loading_dataset(opts, klass.where(Sequel.pg_array_op(opts.predicate_key).overlaps(id_map.keys)), nil, eo[:associations], eo)
278
- ds.all do |assoc_record|
314
+ eager_load_results(opts, eo.merge(:loader=>false)) do |assoc_record|
279
315
  if pks ||= assoc_record.send(key)
280
316
  pks.each do |pkv|
281
317
  next unless objects = id_map[pkv]
@@ -285,7 +321,6 @@ module Sequel
285
321
  end
286
322
  end
287
323
  end
288
- opts.apply_ruby_eager_limit_strategy(rows)
289
324
  end
290
325
 
291
326
  join_type = opts[:graph_join_type]
@@ -316,51 +351,40 @@ module Sequel
316
351
  ds
317
352
  end
318
353
 
319
- def_association_dataset_methods(opts)
354
+ return if opts[:read_only]
320
355
 
321
- unless opts[:read_only]
322
- save_opts = {:validate=>opts[:validate]}
323
- save_opts[:raise_on_failure] = opts[:raise_on_save_failure] != false
356
+ save_opts = {:validate=>opts[:validate]}
357
+ save_opts[:raise_on_failure] = opts[:raise_on_save_failure] != false
324
358
 
325
- array_type = opts[:array_type] ||= :integer
326
- adder = opts[:adder] || proc do |o|
327
- if array = o.send(key)
328
- array << send(pk)
329
- else
330
- o.send("#{key}=", Sequel.pg_array([send(pk)], array_type))
331
- end
332
- o.save(save_opts)
333
- end
334
- association_module_private_def(opts._add_method, opts, &adder)
335
-
336
- remover = opts[:remover] || proc do |o|
337
- if (array = o.send(key)) && !array.empty?
338
- array.delete(send(pk))
339
- o.save(save_opts)
340
- end
359
+ opts[:adder] ||= proc do |o|
360
+ if array = o.send(key)
361
+ array << send(pk)
362
+ else
363
+ o.send("#{key}=", Sequel.pg_array([send(pk)], opts.array_type))
341
364
  end
342
- association_module_private_def(opts._remove_method, opts, &remover)
343
-
344
- clearer = opts[:clearer] || proc do
345
- opts.associated_dataset.where(Sequel.pg_array_op(key).contains([send(pk)])).update(key=>Sequel.function(:array_remove, key, send(pk)))
365
+ o.save(save_opts)
366
+ end
367
+
368
+ opts[:remover] ||= proc do |o|
369
+ if (array = o.send(key)) && !array.empty?
370
+ array.delete(send(pk))
371
+ o.save(save_opts)
346
372
  end
347
- association_module_private_def(opts._remove_all_method, opts, &clearer)
373
+ end
348
374
 
349
- def_add_method(opts)
350
- def_remove_methods(opts)
375
+ opts[:clearer] ||= proc do
376
+ opts.associated_dataset.where(Sequel.pg_array_op(key).contains([send(pk)])).update(key=>Sequel.function(:array_remove, key, send(pk)))
351
377
  end
352
378
  end
353
379
 
354
380
  # Setup the pg_array_to_many-specific datasets, eager loaders, and modification methods.
355
381
  def def_pg_array_to_many(opts)
356
382
  name = opts[:name]
357
- model = self
358
383
  opts[:key] = opts.default_key unless opts.has_key?(:key)
359
384
  key = opts[:key]
360
385
  key_column = opts[:key_column] ||= key
361
386
  opts[:eager_loader_key] = nil
362
387
  opts[:after_load].unshift(:array_uniq!) if opts[:uniq]
363
- slice_range = opts.slice_range
364
388
  opts[:dataset] ||= lambda do
365
389
  opts.associated_dataset.where(opts.predicate_key=>send(key).to_a)
366
390
  end
@@ -368,7 +392,6 @@ module Sequel
368
392
  rows = eo[:rows]
369
393
  id_map = {}
370
394
  pkm = opts.primary_key_method
371
- opts.initialize_association_cache(rows)
372
395
 
373
396
  rows.each do |object|
374
397
  if associated_pks = object.send(key)
@@ -378,16 +401,13 @@ module Sequel
378
401
  end
379
402
  end
380
403
 
381
- klass = opts.associated_class
382
- ds = model.eager_loading_dataset(opts, klass.where(opts.predicate_key=>id_map.keys), nil, eo[:associations], eo)
383
- ds.all do |assoc_record|
404
+ eager_load_results(opts, eo.merge(:id_map=>id_map)) do |assoc_record|
384
405
  if objects = id_map[assoc_record.send(pkm)]
385
406
  objects.each do |object|
386
407
  object.associations[name].push(assoc_record)
387
408
  end
388
409
  end
389
410
  end
390
- opts.apply_ruby_eager_limit_strategy(rows)
391
411
  end
392
412
 
393
413
  join_type = opts[:graph_join_type]
@@ -418,51 +438,42 @@ module Sequel
418
438
  ds
419
439
  end
420
440
 
421
- def_association_dataset_methods(opts)
441
+ return if opts[:read_only]
422
442
 
423
- unless opts[:read_only]
424
- save_opts = {:validate=>opts[:validate]}
425
- save_opts[:raise_on_failure] = opts[:raise_on_save_failure] != false
426
- array_type = opts[:array_type] ||= :integer
443
+ save_opts = {:validate=>opts[:validate]}
444
+ save_opts[:raise_on_failure] = opts[:raise_on_save_failure] != false
427
445
 
428
- if opts[:save_after_modify]
429
- save_after_modify = proc do |obj|
430
- obj.save(save_opts)
431
- end
446
+ if opts[:save_after_modify]
447
+ save_after_modify = proc do |obj|
448
+ obj.save(save_opts)
432
449
  end
450
+ end
433
451
 
434
- adder = opts[:adder] || proc do |o|
435
- opk = o.send(opts.primary_key)
436
- if array = send(key)
437
- modified!(key)
438
- array << opk
439
- else
440
- send("#{key}=", Sequel.pg_array([opk], array_type))
441
- end
442
- save_after_modify.call(self) if save_after_modify
452
+ opts[:adder] ||= proc do |o|
453
+ opk = o.send(opts.primary_key)
454
+ if array = send(key)
455
+ modified!(key)
456
+ array << opk
457
+ else
458
+ send("#{key}=", Sequel.pg_array([opk], opts.array_type))
443
459
  end
444
- association_module_private_def(opts._add_method, opts, &adder)
445
-
446
- remover = opts[:remover] || proc do |o|
447
- if (array = send(key)) && !array.empty?
448
- modified!(key)
449
- array.delete(o.send(opts.primary_key))
450
- save_after_modify.call(self) if save_after_modify
451
- end
460
+ save_after_modify.call(self) if save_after_modify
461
+ end
462
+
463
+ opts[:remover] ||= proc do |o|
464
+ if (array = send(key)) && !array.empty?
465
+ modified!(key)
466
+ array.delete(o.send(opts.primary_key))
467
+ save_after_modify.call(self) if save_after_modify
452
468
  end
453
- association_module_private_def(opts._remove_method, opts, &remover)
469
+ end
454
470
 
455
- clearer = opts[:clearer] || proc do
456
- if (array = send(key)) && !array.empty?
457
- modified!(key)
458
- array.clear
459
- save_after_modify.call(self) if save_after_modify
460
- end
471
+ opts[:clearer] ||= proc do
472
+ if (array = send(key)) && !array.empty?
473
+ modified!(key)
474
+ array.clear
475
+ save_after_modify.call(self) if save_after_modify
461
476
  end
462
- association_module_private_def(opts._remove_all_method, opts, &clearer)
463
-
464
- def_add_method(opts)
465
- def_remove_methods(opts)
466
477
  end
467
478
  end
468
479
  end
@@ -497,11 +508,11 @@ module Sequel
497
508
  expr = case obj
498
509
  when Sequel::Model
499
510
  if pkv = obj.send(ref.primary_key_method)
500
- Sequel.pg_array_op(key).contains([pkv])
511
+ Sequel.pg_array_op(key).contains(Sequel.pg_array([pkv], ref.array_type))
501
512
  end
502
513
  when Array
503
514
  if (pkvs = obj.map{|o| o.send(ref.primary_key_method)}.compact) && !pkvs.empty?
504
- Sequel.pg_array(key).overlaps(pkvs)
515
+ Sequel.pg_array(key).overlaps(Sequel.pg_array(pkvs, ref.array_type))
505
516
  end
506
517
  when Sequel::Dataset
507
518
  Sequel.function(:coalesce, Sequel.pg_array_op(key).overlaps(obj.select{array_agg(ref.qualify(obj.model.table_name, ref.primary_key))}), Sequel::SQL::Constants::FALSE)