sequel 3.48.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (267) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +114 -0
  3. data/Rakefile +10 -7
  4. data/doc/association_basics.rdoc +25 -23
  5. data/doc/code_order.rdoc +7 -0
  6. data/doc/core_extensions.rdoc +0 -10
  7. data/doc/object_model.rdoc +4 -1
  8. data/doc/querying.rdoc +3 -3
  9. data/doc/release_notes/4.0.0.txt +262 -0
  10. data/doc/security.rdoc +0 -28
  11. data/doc/testing.rdoc +8 -14
  12. data/lib/sequel/adapters/ado.rb +7 -11
  13. data/lib/sequel/adapters/ado/access.rb +8 -8
  14. data/lib/sequel/adapters/ado/mssql.rb +4 -4
  15. data/lib/sequel/adapters/amalgalite.rb +6 -6
  16. data/lib/sequel/adapters/cubrid.rb +7 -7
  17. data/lib/sequel/adapters/db2.rb +5 -9
  18. data/lib/sequel/adapters/dbi.rb +2 -6
  19. data/lib/sequel/adapters/do.rb +4 -4
  20. data/lib/sequel/adapters/firebird.rb +4 -4
  21. data/lib/sequel/adapters/ibmdb.rb +8 -8
  22. data/lib/sequel/adapters/informix.rb +2 -10
  23. data/lib/sequel/adapters/jdbc.rb +17 -17
  24. data/lib/sequel/adapters/jdbc/as400.rb +2 -2
  25. data/lib/sequel/adapters/jdbc/cubrid.rb +1 -1
  26. data/lib/sequel/adapters/jdbc/db2.rb +1 -1
  27. data/lib/sequel/adapters/jdbc/derby.rb +1 -1
  28. data/lib/sequel/adapters/jdbc/h2.rb +2 -2
  29. data/lib/sequel/adapters/jdbc/hsqldb.rb +1 -1
  30. data/lib/sequel/adapters/jdbc/informix.rb +1 -1
  31. data/lib/sequel/adapters/jdbc/mssql.rb +2 -2
  32. data/lib/sequel/adapters/jdbc/mysql.rb +1 -1
  33. data/lib/sequel/adapters/jdbc/oracle.rb +5 -1
  34. data/lib/sequel/adapters/jdbc/postgresql.rb +3 -3
  35. data/lib/sequel/adapters/jdbc/sqlite.rb +3 -3
  36. data/lib/sequel/adapters/jdbc/transactions.rb +3 -3
  37. data/lib/sequel/adapters/mock.rb +7 -7
  38. data/lib/sequel/adapters/mysql.rb +3 -3
  39. data/lib/sequel/adapters/mysql2.rb +4 -4
  40. data/lib/sequel/adapters/odbc.rb +2 -6
  41. data/lib/sequel/adapters/odbc/mssql.rb +1 -1
  42. data/lib/sequel/adapters/openbase.rb +1 -5
  43. data/lib/sequel/adapters/oracle.rb +13 -17
  44. data/lib/sequel/adapters/postgres.rb +20 -25
  45. data/lib/sequel/adapters/shared/cubrid.rb +3 -3
  46. data/lib/sequel/adapters/shared/db2.rb +2 -2
  47. data/lib/sequel/adapters/shared/firebird.rb +7 -7
  48. data/lib/sequel/adapters/shared/mssql.rb +9 -9
  49. data/lib/sequel/adapters/shared/mysql.rb +29 -13
  50. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +7 -7
  51. data/lib/sequel/adapters/shared/oracle.rb +22 -13
  52. data/lib/sequel/adapters/shared/postgres.rb +61 -46
  53. data/lib/sequel/adapters/shared/sqlite.rb +9 -9
  54. data/lib/sequel/adapters/sqlite.rb +17 -11
  55. data/lib/sequel/adapters/swift.rb +3 -3
  56. data/lib/sequel/adapters/swift/mysql.rb +1 -1
  57. data/lib/sequel/adapters/swift/sqlite.rb +1 -1
  58. data/lib/sequel/adapters/tinytds.rb +8 -8
  59. data/lib/sequel/ast_transformer.rb +3 -1
  60. data/lib/sequel/connection_pool.rb +4 -2
  61. data/lib/sequel/connection_pool/sharded_single.rb +2 -2
  62. data/lib/sequel/connection_pool/sharded_threaded.rb +5 -5
  63. data/lib/sequel/connection_pool/threaded.rb +7 -7
  64. data/lib/sequel/core.rb +4 -67
  65. data/lib/sequel/database.rb +1 -0
  66. data/lib/sequel/database/connecting.rb +2 -8
  67. data/lib/sequel/database/dataset.rb +2 -7
  68. data/lib/sequel/database/dataset_defaults.rb +0 -18
  69. data/lib/sequel/database/features.rb +4 -4
  70. data/lib/sequel/database/misc.rb +6 -8
  71. data/lib/sequel/database/query.rb +5 -61
  72. data/lib/sequel/database/schema_generator.rb +22 -20
  73. data/lib/sequel/database/schema_methods.rb +48 -20
  74. data/lib/sequel/database/transactions.rb +7 -17
  75. data/lib/sequel/dataset.rb +2 -0
  76. data/lib/sequel/dataset/actions.rb +23 -91
  77. data/lib/sequel/dataset/features.rb +1 -4
  78. data/lib/sequel/dataset/graph.rb +3 -47
  79. data/lib/sequel/dataset/misc.rb +4 -33
  80. data/lib/sequel/dataset/prepared_statements.rb +3 -1
  81. data/lib/sequel/dataset/query.rb +116 -240
  82. data/lib/sequel/dataset/sql.rb +19 -97
  83. data/lib/sequel/deprecated.rb +0 -16
  84. data/lib/sequel/exceptions.rb +0 -3
  85. data/lib/sequel/extensions/_pretty_table.rb +1 -1
  86. data/lib/sequel/extensions/columns_introspection.rb +1 -12
  87. data/lib/sequel/extensions/constraint_validations.rb +3 -3
  88. data/lib/sequel/extensions/core_extensions.rb +0 -9
  89. data/lib/sequel/extensions/date_arithmetic.rb +1 -2
  90. data/lib/sequel/extensions/graph_each.rb +11 -0
  91. data/lib/sequel/extensions/migration.rb +5 -5
  92. data/lib/sequel/extensions/null_dataset.rb +11 -13
  93. data/lib/sequel/extensions/pagination.rb +3 -6
  94. data/lib/sequel/extensions/pg_array.rb +6 -4
  95. data/lib/sequel/extensions/pg_array_ops.rb +35 -1
  96. data/lib/sequel/extensions/pg_json.rb +12 -2
  97. data/lib/sequel/extensions/pg_json_ops.rb +266 -0
  98. data/lib/sequel/extensions/pg_range.rb +2 -2
  99. data/lib/sequel/extensions/pg_range_ops.rb +0 -8
  100. data/lib/sequel/extensions/pg_row.rb +2 -2
  101. data/lib/sequel/extensions/pretty_table.rb +0 -4
  102. data/lib/sequel/extensions/query.rb +3 -8
  103. data/lib/sequel/extensions/schema_caching.rb +0 -7
  104. data/lib/sequel/extensions/schema_dumper.rb +10 -17
  105. data/lib/sequel/extensions/select_remove.rb +0 -4
  106. data/lib/sequel/extensions/set_overrides.rb +28 -0
  107. data/lib/sequel/extensions/to_dot.rb +6 -10
  108. data/lib/sequel/model.rb +6 -7
  109. data/lib/sequel/model/associations.rb +127 -182
  110. data/lib/sequel/model/base.rb +88 -211
  111. data/lib/sequel/model/errors.rb +0 -13
  112. data/lib/sequel/model/plugins.rb +2 -2
  113. data/lib/sequel/no_core_ext.rb +0 -1
  114. data/lib/sequel/plugins/after_initialize.rb +11 -17
  115. data/lib/sequel/plugins/association_autoreloading.rb +1 -47
  116. data/lib/sequel/plugins/association_dependencies.rb +2 -2
  117. data/lib/sequel/plugins/auto_validations.rb +2 -8
  118. data/lib/sequel/plugins/blacklist_security.rb +32 -2
  119. data/lib/sequel/plugins/caching.rb +1 -1
  120. data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
  121. data/lib/sequel/plugins/composition.rb +10 -8
  122. data/lib/sequel/plugins/constraint_validations.rb +2 -2
  123. data/lib/sequel/plugins/dataset_associations.rb +4 -0
  124. data/lib/sequel/plugins/defaults_setter.rb +8 -6
  125. data/lib/sequel/plugins/dirty.rb +6 -6
  126. data/lib/sequel/plugins/force_encoding.rb +13 -8
  127. data/lib/sequel/plugins/hook_class_methods.rb +1 -7
  128. data/lib/sequel/plugins/json_serializer.rb +13 -74
  129. data/lib/sequel/plugins/lazy_attributes.rb +2 -4
  130. data/lib/sequel/plugins/list.rb +1 -1
  131. data/lib/sequel/plugins/many_through_many.rb +4 -11
  132. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +1 -49
  133. data/lib/sequel/plugins/nested_attributes.rb +1 -1
  134. data/lib/sequel/plugins/optimistic_locking.rb +3 -5
  135. data/lib/sequel/plugins/pg_array_associations.rb +453 -0
  136. data/lib/sequel/plugins/pg_typecast_on_load.rb +23 -7
  137. data/lib/sequel/plugins/prepared_statements.rb +1 -1
  138. data/lib/sequel/plugins/prepared_statements_associations.rb +20 -14
  139. data/lib/sequel/plugins/prepared_statements_safe.rb +2 -2
  140. data/lib/sequel/plugins/rcte_tree.rb +1 -1
  141. data/lib/sequel/plugins/serialization.rb +5 -4
  142. data/lib/sequel/plugins/serialization_modification_detection.rb +1 -1
  143. data/lib/sequel/plugins/sharding.rb +7 -1
  144. data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
  145. data/lib/sequel/plugins/timestamps.rb +1 -1
  146. data/lib/sequel/plugins/touch.rb +2 -2
  147. data/lib/sequel/plugins/tree.rb +1 -1
  148. data/lib/sequel/plugins/typecast_on_load.rb +19 -4
  149. data/lib/sequel/plugins/validation_class_methods.rb +0 -30
  150. data/lib/sequel/plugins/validation_helpers.rb +13 -31
  151. data/lib/sequel/plugins/xml_serializer.rb +18 -57
  152. data/lib/sequel/sql.rb +20 -22
  153. data/lib/sequel/version.rb +2 -2
  154. data/spec/adapters/db2_spec.rb +14 -23
  155. data/spec/adapters/firebird_spec.rb +25 -29
  156. data/spec/adapters/informix_spec.rb +11 -14
  157. data/spec/adapters/mssql_spec.rb +71 -77
  158. data/spec/adapters/mysql_spec.rb +165 -172
  159. data/spec/adapters/oracle_spec.rb +36 -39
  160. data/spec/adapters/postgres_spec.rb +175 -100
  161. data/spec/adapters/spec_helper.rb +13 -11
  162. data/spec/adapters/sqlite_spec.rb +36 -44
  163. data/spec/core/connection_pool_spec.rb +2 -1
  164. data/spec/core/database_spec.rb +55 -55
  165. data/spec/core/dataset_spec.rb +45 -249
  166. data/spec/core/deprecated_spec.rb +0 -8
  167. data/spec/core/expression_filters_spec.rb +23 -5
  168. data/spec/core/object_graph_spec.rb +4 -66
  169. data/spec/core/schema_spec.rb +35 -12
  170. data/spec/core/spec_helper.rb +3 -2
  171. data/spec/core_extensions_spec.rb +17 -19
  172. data/spec/extensions/arbitrary_servers_spec.rb +2 -3
  173. data/spec/extensions/association_dependencies_spec.rb +14 -14
  174. data/spec/extensions/auto_validations_spec.rb +7 -0
  175. data/spec/extensions/blacklist_security_spec.rb +5 -5
  176. data/spec/extensions/blank_spec.rb +2 -0
  177. data/spec/extensions/class_table_inheritance_spec.rb +2 -2
  178. data/spec/extensions/columns_introspection_spec.rb +2 -29
  179. data/spec/extensions/composition_spec.rb +10 -17
  180. data/spec/extensions/core_refinements_spec.rb +5 -1
  181. data/spec/extensions/dataset_associations_spec.rb +18 -0
  182. data/spec/extensions/date_arithmetic_spec.rb +2 -2
  183. data/spec/extensions/defaults_setter_spec.rb +9 -9
  184. data/spec/extensions/dirty_spec.rb +0 -5
  185. data/spec/extensions/eval_inspect_spec.rb +2 -0
  186. data/spec/extensions/force_encoding_spec.rb +2 -18
  187. data/spec/extensions/hash_aliases_spec.rb +8 -0
  188. data/spec/extensions/hook_class_methods_spec.rb +39 -58
  189. data/spec/extensions/inflector_spec.rb +2 -0
  190. data/spec/extensions/instance_filters_spec.rb +8 -8
  191. data/spec/extensions/json_serializer_spec.rb +1 -41
  192. data/spec/extensions/list_spec.rb +1 -1
  193. data/spec/extensions/many_through_many_spec.rb +106 -109
  194. data/spec/extensions/migration_spec.rb +2 -0
  195. data/spec/extensions/named_timezones_spec.rb +1 -0
  196. data/spec/extensions/pg_array_associations_spec.rb +603 -0
  197. data/spec/extensions/pg_array_ops_spec.rb +25 -0
  198. data/spec/extensions/pg_array_spec.rb +9 -1
  199. data/spec/extensions/pg_hstore_ops_spec.rb +13 -0
  200. data/spec/extensions/pg_hstore_spec.rb +1 -0
  201. data/spec/extensions/pg_json_ops_spec.rb +131 -0
  202. data/spec/extensions/pg_json_spec.rb +10 -4
  203. data/spec/extensions/pg_range_ops_spec.rb +2 -5
  204. data/spec/extensions/pg_range_spec.rb +6 -2
  205. data/spec/extensions/pg_row_ops_spec.rb +2 -0
  206. data/spec/extensions/prepared_statements_associations_spec.rb +26 -5
  207. data/spec/extensions/rcte_tree_spec.rb +15 -15
  208. data/spec/extensions/schema_dumper_spec.rb +0 -1
  209. data/spec/extensions/schema_spec.rb +9 -9
  210. data/spec/extensions/serialization_modification_detection_spec.rb +1 -1
  211. data/spec/extensions/serialization_spec.rb +18 -29
  212. data/spec/extensions/set_overrides_spec.rb +4 -0
  213. data/spec/extensions/{many_to_one_pk_lookup_spec.rb → shared_caching_spec.rb} +1 -4
  214. data/spec/extensions/single_table_inheritance_spec.rb +4 -4
  215. data/spec/extensions/spec_helper.rb +8 -9
  216. data/spec/extensions/sql_expr_spec.rb +2 -0
  217. data/spec/extensions/string_date_time_spec.rb +2 -0
  218. data/spec/extensions/string_stripper_spec.rb +2 -0
  219. data/spec/extensions/tactical_eager_loading_spec.rb +12 -12
  220. data/spec/extensions/thread_local_timezones_spec.rb +2 -0
  221. data/spec/extensions/timestamps_spec.rb +1 -1
  222. data/spec/extensions/to_dot_spec.rb +1 -1
  223. data/spec/extensions/touch_spec.rb +24 -24
  224. data/spec/extensions/tree_spec.rb +7 -7
  225. data/spec/extensions/typecast_on_load_spec.rb +8 -1
  226. data/spec/extensions/update_primary_key_spec.rb +10 -10
  227. data/spec/extensions/validation_class_methods_spec.rb +10 -39
  228. data/spec/extensions/validation_helpers_spec.rb +29 -47
  229. data/spec/extensions/xml_serializer_spec.rb +1 -23
  230. data/spec/integration/associations_test.rb +231 -40
  231. data/spec/integration/database_test.rb +1 -1
  232. data/spec/integration/dataset_test.rb +64 -64
  233. data/spec/integration/eager_loader_test.rb +28 -28
  234. data/spec/integration/migrator_test.rb +1 -1
  235. data/spec/integration/model_test.rb +2 -2
  236. data/spec/integration/plugin_test.rb +21 -21
  237. data/spec/integration/prepared_statement_test.rb +7 -7
  238. data/spec/integration/schema_test.rb +115 -110
  239. data/spec/integration/spec_helper.rb +17 -27
  240. data/spec/integration/timezone_test.rb +1 -1
  241. data/spec/integration/transaction_test.rb +10 -10
  242. data/spec/integration/type_test.rb +2 -2
  243. data/spec/model/association_reflection_spec.rb +2 -28
  244. data/spec/model/associations_spec.rb +239 -188
  245. data/spec/model/base_spec.rb +27 -68
  246. data/spec/model/dataset_methods_spec.rb +4 -4
  247. data/spec/model/eager_loading_spec.rb +160 -172
  248. data/spec/model/hooks_spec.rb +62 -79
  249. data/spec/model/model_spec.rb +36 -51
  250. data/spec/model/plugins_spec.rb +5 -19
  251. data/spec/model/record_spec.rb +125 -151
  252. data/spec/model/spec_helper.rb +8 -6
  253. data/spec/model/validations_spec.rb +4 -17
  254. data/spec/spec_config.rb +2 -10
  255. metadata +50 -56
  256. data/lib/sequel/deprecated_core_extensions.rb +0 -135
  257. data/lib/sequel/extensions/pg_auto_parameterize.rb +0 -185
  258. data/lib/sequel/extensions/pg_statement_cache.rb +0 -318
  259. data/lib/sequel/plugins/identity_map.rb +0 -260
  260. data/lib/sequel_core.rb +0 -2
  261. data/lib/sequel_model.rb +0 -2
  262. data/spec/extensions/association_autoreloading_spec.rb +0 -102
  263. data/spec/extensions/identity_map_spec.rb +0 -337
  264. data/spec/extensions/pg_auto_parameterize_spec.rb +0 -70
  265. data/spec/extensions/pg_statement_cache_spec.rb +0 -208
  266. data/spec/rcov.opts +0 -8
  267. data/spec/spec_config.rb.example +0 -10
@@ -11,10 +11,8 @@ module Sequel
11
11
  # get the reviews for all of those albums:
12
12
  #
13
13
  # Album.plugin :lazy_attributes, :review
14
- # Sequel::Model.with_identity_map do
15
- # Album.filter{id<100}.all do |a|
16
- # a.review
17
- # end
14
+ # Album.filter{id<100}.all do |a|
15
+ # a.review
18
16
  # end
19
17
  #
20
18
  # # You can specify multiple columns to lazily load:
@@ -53,7 +53,7 @@ module Sequel
53
53
  # The <tt>:scope</tt> option can be a symbol, array of symbols, or a proc that
54
54
  # accepts a model instance and returns a dataset representing the list.
55
55
  # Also, modify the model dataset's order to order by the position and scope fields.
56
- def self.configure(model, opts = {})
56
+ def self.configure(model, opts = OPTS)
57
57
  model.position_field = opts[:field] || :position
58
58
  model.dataset = model.dataset.order_prepend(model.position_field)
59
59
 
@@ -157,7 +157,7 @@ module Sequel
157
157
  # :join_type :: The join type to use for the join, defaults to :left_outer.
158
158
  # :only_conditions :: Conditions to use for the join instead of the ones specified by the keys.
159
159
  # * opts - The options for the associaion. Takes the same options as many_to_many.
160
- def many_through_many(name, through, opts={}, &block)
160
+ def many_through_many(name, through, opts=OPTS, &block)
161
161
  associate(:many_through_many, name, opts.merge(through.is_a?(Hash) ? through : {:through=>through}), &block)
162
162
  end
163
163
 
@@ -198,6 +198,7 @@ module Sequel
198
198
  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)
199
199
  end
200
200
 
201
+ slice_range = opts.slice_range
201
202
  left_key_alias = opts[:left_key_alias] ||= opts.default_associated_key_alias
202
203
  opts[:eager_loader] ||= lambda do |eo|
203
204
  h = eo[:id_map]
@@ -208,17 +209,10 @@ module Sequel
208
209
  ft = opts.final_reverse_edge
209
210
  ds = ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + [[opts.predicate_key, h.keys]], :table_alias=>ft[:alias], :qualify=>:deep)
210
211
  ds = model.eager_loading_dataset(opts, ds, nil, eo[:associations], eo)
211
- case opts.eager_limit_strategy
212
- when :window_function
212
+ if opts.eager_limit_strategy == :window_function
213
213
  delete_rn = true
214
214
  rn = ds.row_number_column
215
215
  ds = apply_window_function_eager_limit_strategy(ds, opts)
216
- when :correlated_subquery
217
- ds = apply_correlated_subquery_eager_limit_strategy(ds, opts) do |xds|
218
- dsa = ds.send(:dataset_alias, 2)
219
- opts.reverse_edges.each{|t| xds = xds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
220
- xds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + left_keys.map{|k| [k, SQL::QualifiedIdentifier.new(ft[:table], k)]}, :table_alias=>dsa, :qualify=>:deep)
221
- end
222
216
  end
223
217
  ds.all do |assoc_record|
224
218
  assoc_record.values.delete(rn) if delete_rn
@@ -231,8 +225,7 @@ module Sequel
231
225
  objects.each{|object| object.associations[name].push(assoc_record)}
232
226
  end
233
227
  if opts.eager_limit_strategy == :ruby
234
- limit, offset = opts.limit_and_offset
235
- rows.each{|o| o.associations[name] = o.associations[name].slice(offset||0, limit) || []}
228
+ rows.each{|o| o.associations[name] = o.associations[name][slice_range] || []}
236
229
  end
237
230
  end
238
231
 
@@ -1,55 +1,7 @@
1
1
  module Sequel
2
2
  module Plugins
3
- # The ManyToOnePkLookup plugin that modifies the internal association loading logic
4
- # for many_to_one associations to use a simple primary key lookup on the associated
5
- # class, which is generally faster as it uses mostly static SQL. Additional, if the
6
- # associated class is caching primary key lookups, you get the benefit of a cached
7
- # lookup.
8
- #
9
- # This plugin attempts to determine cases where the primary key lookup would have
10
- # different results than the regular lookup, and use the regular lookup in that case.
11
- # If you want to explicitly force whether or not to use primary key lookups for
12
- # a given association, set the :many_to_one_pk_lookup association option.
13
- #
14
- # Usage:
15
- #
16
- # # Make all model subclass instances use primary key lookups for many_to_one
17
- # # association loading
18
- # Sequel::Model.plugin :many_to_one_pk_lookup
19
- #
20
- # # Do so for just the album class.
21
- # Album.plugin :many_to_one_pk_lookup
3
+ # Empty plugin module for backwards compatibility
22
4
  module ManyToOnePkLookup
23
- module ClassMethods
24
- # Disable primary key lookup in cases where it will result in a different
25
- # query than the association query.
26
- def def_many_to_one(opts)
27
- if !opts.has_key?(:many_to_one_pk_lookup) &&
28
- (opts[:dataset] || opts[:conditions] || opts[:block] || opts[:select] ||
29
- (opts.has_key?(:key) && opts[:key] == nil))
30
- opts[:many_to_one_pk_lookup] = false
31
- end
32
- super
33
- end
34
- end
35
-
36
- module InstanceMethods
37
- private
38
-
39
- # If the current association is a simple many_to_one association, use
40
- # a simple primary key lookup on the associated model, which can benefit from
41
- # caching if the associated model is using caching.
42
- def _load_associated_objects(opts, dynamic_opts={})
43
- return super unless opts.can_have_associated_objects?(self) && opts[:type] == :many_to_one
44
- klass = opts.associated_class
45
- if !dynamic_opts[:callback] &&
46
- opts.send(:cached_fetch, :many_to_one_pk_lookup){opts.primary_key == klass.primary_key}
47
- klass.send(:primary_key_lookup, ((fk = opts[:key]).is_a?(Array) ? fk.map{|c| send(c)} : send(fk)))
48
- else
49
- super
50
- end
51
- end
52
- end
53
5
  end
54
6
  end
55
7
  end
@@ -196,7 +196,7 @@ module Sequel
196
196
  # :destroy option is given, destroy the object after disassociating it
197
197
  # (unless destroying the object would automatically disassociate it).
198
198
  # Returns the object removed.
199
- def nested_attributes_remove(reflection, obj, opts={})
199
+ def nested_attributes_remove(reflection, obj, opts=OPTS)
200
200
  if !opts[:destroy] || reflection.remove_before_destroy?
201
201
  before_save_hook do
202
202
  if reflection.returns_array?
@@ -1,6 +1,4 @@
1
1
  module Sequel
2
- require 'plugins/instance_filters'
3
-
4
2
  module Plugins
5
3
  # This plugin implements a simple database-independent locking mechanism
6
4
  # to ensure that concurrent updates do not override changes. This is
@@ -21,16 +19,16 @@ module Sequel
21
19
  # This plugin relies on the instance_filters plugin.
22
20
  module OptimisticLocking
23
21
  # Exception class raised when trying to update or destroy a stale object.
24
- Error = InstanceFilters::Error
22
+ Error = Sequel::NoExistingObject
25
23
 
26
24
  # Load the instance_filters plugin into the model.
27
- def self.apply(model, opts={})
25
+ def self.apply(model, opts=OPTS)
28
26
  model.plugin :instance_filters
29
27
  end
30
28
 
31
29
  # Set the lock_column to the :lock_column option, or :lock_version if
32
30
  # that option is not given.
33
- def self.configure(model, opts={})
31
+ def self.configure(model, opts=OPTS)
34
32
  model.lock_column = opts[:lock_column] || :lock_version
35
33
  end
36
34
 
@@ -0,0 +1,453 @@
1
+ module Sequel
2
+ extension :pg_array, :pg_array_ops
3
+
4
+ module Plugins
5
+ # This plugin allows you to create associations where the foreign keys
6
+ # are stored in a PostgreSQL array column in one of the tables. The
7
+ # model with the table containing the array column has a
8
+ # pg_array_to_many association to the associated model, and the
9
+ # model with the table containing the primary key referenced by
10
+ # elements in the array column has a many_to_pg_array association
11
+ # to the associated model.
12
+ #
13
+ # # Database schema:
14
+ # # tags albums
15
+ # # :id (int4) <--\ :id
16
+ # # :name \-- :tag_ids (int4[])
17
+ # # :name
18
+ #
19
+ # class Album
20
+ # plugin :pg_array_associations
21
+ # pg_array_to_many :tags
22
+ # end
23
+ # class Tag
24
+ # plugin :pg_array_associations
25
+ # many_to_pg_array :albums
26
+ # end
27
+ #
28
+ # These association types work similarly to Sequel's other association
29
+ # types, so you can use them as you would any other association. Unlike
30
+ # other associations, they do not support composite keys.
31
+ #
32
+ # One thing that is different is that the modification methods for
33
+ # pg_array_to_many associations do not affect the database, since they
34
+ # operate purely on the receiver. For example:
35
+ #
36
+ # album = Album[1]
37
+ # album.add_tag(Tag[2])
38
+ #
39
+ # does not save the album. This allows you to call add_tag repeatedly
40
+ # and the save after to combine all changes into a single query. Note
41
+ # that the many_to_pg_array association modification methods do save, so:
42
+ #
43
+ # tag = Tag[2]
44
+ # tag.add_album(Album[1])
45
+ #
46
+ # will save the changes to the album.
47
+ #
48
+ # They support some additional options specific to this plugin:
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.
55
+ # :save_after_modify :: For pg_array_to_many associations, this makes the
56
+ # the modification methods save the current object,
57
+ # so they operate more similarly to the one_to_many
58
+ # and many_to_many association modification methods.
59
+ # :uniq :: Similar to many_to_many associations, this can be used to
60
+ # make sure the returned associated object array has uniq values.
61
+ #
62
+ # Note that until PostgreSQL gains the ability to enforce foreign key
63
+ # constraints in array columns, this plugin is not recommended for
64
+ # production use unless you plan on emulating referential integrity
65
+ # constraints via triggers.
66
+ #
67
+ # This plugin should work on all supported PostgreSQL versions, except
68
+ # the remove_all modification method for many_to_pg_array associations, which
69
+ # requires the array_remove method added in PostgreSQL 9.3.
70
+ module PgArrayAssociations
71
+ # The AssociationReflection subclass for many_to_pg_array associations.
72
+ class ManyToPgArrayAssociationReflection < Sequel::Model::Associations::AssociationReflection
73
+ Sequel::Model::Associations::ASSOCIATION_TYPES[:many_to_pg_array] = self
74
+
75
+ # The array column in the associated model containing foreign keys to
76
+ # the current model.
77
+ def associated_object_keys
78
+ [self[:key]]
79
+ end
80
+
81
+ # many_to_pg_array associations can have associated objects as long as they have
82
+ # a primary key.
83
+ def can_have_associated_objects?(obj)
84
+ obj.send(self[:primary_key])
85
+ end
86
+
87
+ # Assume that the key in the associated table uses a version of the current
88
+ # model's name suffixed with _ids.
89
+ def default_key
90
+ :"#{underscore(demodulize(self[:model].name))}_ids"
91
+ end
92
+
93
+ # The hash key to use for the eager loading predicate (left side of IN (1, 2, 3))
94
+ def predicate_key
95
+ cached_fetch(:predicate_key){qualify_assoc(self[:key_column])}
96
+ end
97
+
98
+ # The column in the current table that the keys in the array column in the
99
+ # associated table reference.
100
+ def primary_key
101
+ self[:primary_key]
102
+ end
103
+
104
+ # Destroying the associated object automatically removes the association,
105
+ # since the association is stored in the associated object.
106
+ def remove_before_destroy?
107
+ false
108
+ end
109
+
110
+ private
111
+
112
+ # Only consider an association as a reciprocal if it has matching keys
113
+ # and primary keys.
114
+ def reciprocal_association?(assoc_reflect)
115
+ super && self[:key] == assoc_reflect[:key] && primary_key == assoc_reflect.primary_key
116
+ end
117
+
118
+ def reciprocal_type
119
+ :pg_array_to_many
120
+ end
121
+ end
122
+
123
+ # The AssociationReflection subclass for pg_array_to_many associations.
124
+ class PgArrayToManyAssociationReflection < Sequel::Model::Associations::AssociationReflection
125
+ Sequel::Model::Associations::ASSOCIATION_TYPES[:pg_array_to_many] = self
126
+
127
+ # An array containing the primary key for the associated model.
128
+ def associated_object_keys
129
+ Array(primary_key)
130
+ end
131
+
132
+ # pg_array_to_many associations can only have associated objects if
133
+ # the array field is not nil or empty.
134
+ def can_have_associated_objects?(obj)
135
+ v = obj.send(self[:key])
136
+ v && !v.empty?
137
+ end
138
+
139
+ # pg_array_to_many associations do not need a primary key.
140
+ def dataset_need_primary_key?
141
+ false
142
+ end
143
+
144
+ # Use a default key name of *_ids, for similarity to other association types
145
+ # that use *_id for single keys.
146
+ def default_key
147
+ :"#{singularize(self[:name])}_ids"
148
+ end
149
+
150
+ # A qualified version of the associated primary key.
151
+ def predicate_key
152
+ cached_fetch(:predicate_key){qualify_assoc(primary_key)}
153
+ end
154
+
155
+ # The primary key of the associated model.
156
+ def primary_key
157
+ cached_fetch(:primary_key){associated_class.primary_key}
158
+ end
159
+
160
+ # The method to call to get value of the primary key of the associated model.
161
+ def primary_key_method
162
+ cached_fetch(:primary_key_method){primary_key}
163
+ end
164
+
165
+ private
166
+
167
+ # Only consider an association as a reciprocal if it has matching keys
168
+ # and primary keys.
169
+ def reciprocal_association?(assoc_reflect)
170
+ super && self[:key] == assoc_reflect[:key] && primary_key == assoc_reflect.primary_key
171
+ end
172
+
173
+ def reciprocal_type
174
+ :many_to_pg_array
175
+ end
176
+ end
177
+
178
+ module ClassMethods
179
+ # Create a many_to_pg_array association, for the case where the associated
180
+ # table contains the array with foreign keys pointing to the current table.
181
+ # See associate for options.
182
+ def many_to_pg_array(name, opts=OPTS, &block)
183
+ associate(:many_to_pg_array, name, opts, &block)
184
+ end
185
+
186
+ # Create a pg_array_to_many association, for the case where the current
187
+ # table contains the array with foreign keys pointing to the associated table.
188
+ # See associate for options.
189
+ def pg_array_to_many(name, opts=OPTS, &block)
190
+ associate(:pg_array_to_many, name, opts, &block)
191
+ end
192
+
193
+ private
194
+
195
+ # Setup the many_to_pg_array-specific datasets, eager loaders, and modification methods.
196
+ def def_many_to_pg_array(opts)
197
+ name = opts[:name]
198
+ model = self
199
+ pk = opts[:eager_loader_key] = opts[:primary_key] ||= model.primary_key
200
+ opts[:key] = opts.default_key unless opts.has_key?(:key)
201
+ key = opts[:key]
202
+ key_column = opts[:key_column] ||= opts[:key]
203
+ opts[:after_load].unshift(:array_uniq!) if opts[:uniq]
204
+ slice_range = opts.slice_range
205
+ opts[:dataset] ||= lambda do
206
+ opts.associated_dataset.where(Sequel.pg_array_op(opts.predicate_key).contains([send(pk)]))
207
+ end
208
+ opts[:eager_loader] ||= proc do |eo|
209
+ id_map = eo[:id_map]
210
+ rows = eo[:rows]
211
+ rows.each do |object|
212
+ object.associations[name] = []
213
+ end
214
+
215
+ klass = opts.associated_class
216
+ ds = model.eager_loading_dataset(opts, klass.where(Sequel.pg_array_op(opts.predicate_key).overlaps(id_map.keys)), nil, eo[:associations], eo)
217
+ ds.all do |assoc_record|
218
+ if pks ||= assoc_record.send(key)
219
+ pks.each do |pkv|
220
+ next unless objects = id_map[pkv]
221
+ objects.each do |object|
222
+ object.associations[name].push(assoc_record)
223
+ end
224
+ end
225
+ end
226
+ end
227
+ if slice_range
228
+ rows.each{|o| o.associations[name] = o.associations[name][slice_range] || []}
229
+ end
230
+ end
231
+
232
+ join_type = opts[:graph_join_type]
233
+ select = opts[:graph_select]
234
+ opts[:cartesian_product_number] ||= 1
235
+
236
+ if opts.include?(:graph_only_conditions)
237
+ conditions = opts[:graph_only_conditions]
238
+ graph_block = opts[:graph_block]
239
+ else
240
+ conditions = opts[:graph_conditions]
241
+ conditions = nil if conditions.empty?
242
+ graph_block = proc do |j, lj, js|
243
+ Sequel.pg_array_op(Sequel.deep_qualify(j, key_column)).contains([Sequel.deep_qualify(lj, opts.primary_key)])
244
+ end
245
+
246
+ if orig_graph_block = opts[:graph_block]
247
+ pg_array_graph_block = graph_block
248
+ graph_block = proc do |j, lj, js|
249
+ Sequel.&(orig_graph_block.call(j,lj,js), pg_array_graph_block.call(j, lj, js))
250
+ end
251
+ end
252
+ end
253
+
254
+ opts[:eager_grapher] ||= proc do |eo|
255
+ ds = eo[:self]
256
+ ds = ds.graph(eager_graph_dataset(opts, eo), conditions, eo.merge(:select=>select, :join_type=>join_type, :qualify=>:deep, :from_self_alias=>ds.opts[:eager_graph][:master]), &graph_block)
257
+ ds
258
+ end
259
+
260
+ def_association_dataset_methods(opts)
261
+
262
+ unless opts[:read_only]
263
+ validate = opts[:validate]
264
+
265
+ array_type = opts[:array_type] ||= :integer
266
+ adder = opts[:adder] || proc do |o|
267
+ if array = o.send(key)
268
+ array << send(pk)
269
+ else
270
+ o.send("#{key}=", Sequel.pg_array([send(pk)], array_type))
271
+ end
272
+ o.save(:validate=>validate) || raise(Sequel::Error, "invalid associated object, cannot save")
273
+ end
274
+ association_module_private_def(opts._add_method, opts, &adder)
275
+
276
+ remover = opts[:remover] || proc do |o|
277
+ if (array = o.send(key)) && !array.empty?
278
+ array.delete(send(pk))
279
+ o.save(:validate=>validate) || raise(Sequel::Error, "invalid associated object, cannot save")
280
+ end
281
+ end
282
+ association_module_private_def(opts._remove_method, opts, &remover)
283
+
284
+ clearer = opts[:clearer] || proc do
285
+ opts.associated_dataset.where(Sequel.pg_array_op(key).contains([send(pk)])).update(key=>Sequel.function(:array_remove, key, send(pk)))
286
+ end
287
+ association_module_private_def(opts._remove_all_method, opts, &clearer)
288
+
289
+ def_add_method(opts)
290
+ def_remove_methods(opts)
291
+ end
292
+ end
293
+
294
+ # Setup the pg_array_to_many-specific datasets, eager loaders, and modification methods.
295
+ def def_pg_array_to_many(opts)
296
+ name = opts[:name]
297
+ model = self
298
+ opts[:key] = opts.default_key unless opts.has_key?(:key)
299
+ key = opts[:key]
300
+ key_column = opts[:key_column] ||= key
301
+ opts[:eager_loader_key] = nil
302
+ opts[:after_load].unshift(:array_uniq!) if opts[:uniq]
303
+ slice_range = opts.slice_range
304
+ opts[:dataset] ||= lambda do
305
+ opts.associated_dataset.where(opts.predicate_key=>send(key).to_a)
306
+ end
307
+ opts[:eager_loader] ||= proc do |eo|
308
+ rows = eo[:rows]
309
+ id_map = {}
310
+ pkm = opts.primary_key_method
311
+ rows.each do |object|
312
+ object.associations[name] = []
313
+ if associated_pks = object.send(key)
314
+ associated_pks.each do |apk|
315
+ (id_map[apk] ||= []) << object
316
+ end
317
+ end
318
+ end
319
+
320
+ klass = opts.associated_class
321
+ ds = model.eager_loading_dataset(opts, klass.where(opts.predicate_key=>id_map.keys), nil, eo[:associations], eo)
322
+ ds.all do |assoc_record|
323
+ if objects = id_map[assoc_record.send(pkm)]
324
+ objects.each do |object|
325
+ object.associations[name].push(assoc_record)
326
+ end
327
+ end
328
+ end
329
+ if slice_range
330
+ rows.each{|o| o.associations[name] = o.associations[name][slice_range] || []}
331
+ end
332
+ end
333
+
334
+ join_type = opts[:graph_join_type]
335
+ select = opts[:graph_select]
336
+ opts[:cartesian_product_number] ||= 1
337
+
338
+ if opts.include?(:graph_only_conditions)
339
+ conditions = opts[:graph_only_conditions]
340
+ graph_block = opts[:graph_block]
341
+ else
342
+ conditions = opts[:graph_conditions]
343
+ conditions = nil if conditions.empty?
344
+ graph_block = proc do |j, lj, js|
345
+ Sequel.pg_array_op(Sequel.deep_qualify(lj, key_column)).contains([Sequel.deep_qualify(j, opts.primary_key)])
346
+ end
347
+
348
+ if orig_graph_block = opts[:graph_block]
349
+ pg_array_graph_block = graph_block
350
+ graph_block = proc do |j, lj, js|
351
+ Sequel.&(orig_graph_block.call(j,lj,js), pg_array_graph_block.call(j, lj, js))
352
+ end
353
+ end
354
+ end
355
+
356
+ opts[:eager_grapher] ||= proc do |eo|
357
+ ds = eo[:self]
358
+ ds = ds.graph(eager_graph_dataset(opts, eo), conditions, eo.merge(:select=>select, :join_type=>join_type, :qualify=>:deep, :from_self_alias=>ds.opts[:eager_graph][:master]), &graph_block)
359
+ ds
360
+ end
361
+
362
+ def_association_dataset_methods(opts)
363
+
364
+ unless opts[:read_only]
365
+ validate = opts[:validate]
366
+ array_type = opts[:array_type] ||= :integer
367
+ if opts[:save_after_modify]
368
+ save_after_modify = proc do |obj|
369
+ obj.save(:validate=>validate) || raise(Sequel::Error, "invalid associated object, cannot save")
370
+ end
371
+ end
372
+
373
+ adder = opts[:adder] || proc do |o|
374
+ opk = o.send(opts.primary_key)
375
+ if array = send(key)
376
+ modified!(key)
377
+ array << opk
378
+ else
379
+ send("#{key}=", Sequel.pg_array([opk], array_type))
380
+ end
381
+ save_after_modify.call(self) if save_after_modify
382
+ end
383
+ association_module_private_def(opts._add_method, opts, &adder)
384
+
385
+ remover = opts[:remover] || proc do |o|
386
+ if (array = send(key)) && !array.empty?
387
+ modified!(key)
388
+ array.delete(o.send(opts.primary_key))
389
+ save_after_modify.call(self) if save_after_modify
390
+ end
391
+ end
392
+ association_module_private_def(opts._remove_method, opts, &remover)
393
+
394
+ clearer = opts[:clearer] || proc do
395
+ if (array = send(key)) && !array.empty?
396
+ modified!(key)
397
+ array.clear
398
+ save_after_modify.call(self) if save_after_modify
399
+ end
400
+ end
401
+ association_module_private_def(opts._remove_all_method, opts, &clearer)
402
+
403
+ def_add_method(opts)
404
+ def_remove_methods(opts)
405
+ end
406
+ end
407
+ end
408
+
409
+ module DatasetMethods
410
+ private
411
+
412
+ # Support filtering by many_to_pg_array associations using a subquery.
413
+ def many_to_pg_array_association_filter_expression(op, ref, obj)
414
+ pk = ref.qualify(model.table_name, ref.primary_key)
415
+ key = ref[:key]
416
+ expr = case obj
417
+ when Sequel::Model
418
+ if (assoc_pks = obj.send(key)) && !assoc_pks.empty?
419
+ Sequel.expr(pk=>assoc_pks.to_a)
420
+ end
421
+ when Array
422
+ if (assoc_pks = obj.map{|o| o.send(key)}.flatten.compact.uniq) && !assoc_pks.empty?
423
+ Sequel.expr(pk=>assoc_pks)
424
+ end
425
+ when Sequel::Dataset
426
+ Sequel.expr(pk=>obj.select{Sequel.pg_array_op(ref.qualify(obj.model.table_name, ref[:key_column])).unnest})
427
+ end
428
+ expr = Sequel::SQL::Constants::FALSE unless expr
429
+ association_filter_handle_inversion(op, expr, [pk])
430
+ end
431
+
432
+ # Support filtering by pg_array_to_many associations using a subquery.
433
+ def pg_array_to_many_association_filter_expression(op, ref, obj)
434
+ key = ref.qualify(model.table_name, ref[:key_column])
435
+ expr = case obj
436
+ when Sequel::Model
437
+ if pkv = obj.send(ref.primary_key_method)
438
+ Sequel.pg_array_op(key).contains([pkv])
439
+ end
440
+ when Array
441
+ if (pkvs = obj.map{|o| o.send(ref.primary_key_method)}.compact) && !pkvs.empty?
442
+ Sequel.pg_array(key).overlaps(pkvs)
443
+ end
444
+ when Sequel::Dataset
445
+ 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)
446
+ end
447
+ expr = Sequel::SQL::Constants::FALSE unless expr
448
+ association_filter_handle_inversion(op, expr, [key])
449
+ end
450
+ end
451
+ end
452
+ end
453
+ end