sequel 3.48.0 → 4.0.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 (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