sequel 4.9.0 → 4.10.0

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