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
@@ -17,20 +17,14 @@ module Sequel
17
17
  def self.extended(db)
18
18
  db.extend_datasets(DatasetQuery)
19
19
  end
20
- end
21
20
 
22
- class Database
23
21
  # Return a dataset modified by the query block
24
22
  def query(&block)
25
- Sequel::Deprecation.deprecate('Loading the query extension globally', "Please use Database#extension to load the extension into this database") unless is_a?(DatabaseQuery)
26
23
  dataset.query(&block)
27
24
  end
28
25
  end
29
26
 
30
27
  module DatasetQuery
31
- end
32
-
33
- class Dataset
34
28
  # Translates a query block into a dataset. Query blocks are an
35
29
  # alternative to Sequel's usual method chaining, by using
36
30
  # instance_eval with a proxy object:
@@ -45,12 +39,13 @@ module Sequel
45
39
  #
46
40
  # dataset = DB[:items].select(:x, :y, :z).filter{(x > 1) & (y > 2)}.reverse(:z)
47
41
  def query(&block)
48
- Sequel::Deprecation.deprecate('Loading the query extension globally', "Please use Database/Dataset#extension to load the extension into this dataset") unless is_a?(DatasetQuery)
49
- query = Query.new(self)
42
+ query = Dataset::Query.new(self)
50
43
  query.instance_eval(&block)
51
44
  query.dataset
52
45
  end
46
+ end
53
47
 
48
+ class Dataset
54
49
  # Proxy object used by Dataset#query.
55
50
  class Query < Sequel::BasicObject
56
51
  # The current dataset in the query. This changes on each method call.
@@ -44,12 +44,8 @@
44
44
 
45
45
  module Sequel
46
46
  module SchemaCaching
47
- end
48
-
49
- class Database
50
47
  # Dump the cached schema to the filename given in Marshal format.
51
48
  def dump_schema_cache(file)
52
- Sequel::Deprecation.deprecate('Loading the schema_caching extension globally', "Please use Database#extension to load the extension into this database") unless is_a?(SchemaCaching)
53
49
  File.open(file, 'wb'){|f| f.write(Marshal.dump(@schemas))}
54
50
  nil
55
51
  end
@@ -57,14 +53,12 @@ module Sequel
57
53
  # Dump the cached schema to the filename given unless the file
58
54
  # already exists.
59
55
  def dump_schema_cache?(file)
60
- Sequel::Deprecation.deprecate('Loading the schema_caching extension globally', "Please use Database#extension to load the extension into this database") unless is_a?(SchemaCaching)
61
56
  dump_schema_cache(file) unless File.exist?(file)
62
57
  end
63
58
 
64
59
  # Replace the schema cache with the data from the given file, which
65
60
  # should be in Marshal format.
66
61
  def load_schema_cache(file)
67
- Sequel::Deprecation.deprecate('Loading the schema_caching extension globally', "Please use Database#extension to load the extension into this database") unless is_a?(SchemaCaching)
68
62
  @schemas = Marshal.load(File.read(file))
69
63
  nil
70
64
  end
@@ -72,7 +66,6 @@ module Sequel
72
66
  # Replace the schema cache with the data from the given file if the
73
67
  # file exists.
74
68
  def load_schema_cache?(file)
75
- Sequel::Deprecation.deprecate('Loading the schema_caching extension globally', "Please use Database#extension to load the extension into this database") unless is_a?(SchemaCaching)
76
69
  load_schema_cache(file) if File.exist?(file)
77
70
  end
78
71
  end
@@ -12,9 +12,6 @@ Sequel.extension :eval_inspect
12
12
 
13
13
  module Sequel
14
14
  module SchemaDumper
15
- end
16
-
17
- class Database
18
15
  # Dump foreign key constraints for all tables as a migration. This complements
19
16
  # the :foreign_keys=>false option to dump_schema_migration. This only dumps
20
17
  # the constraints (not the columns) using alter_table/add_foreign_key with an
@@ -22,8 +19,7 @@ module Sequel
22
19
  #
23
20
  # Note that the migration this produces does not have a down
24
21
  # block, so you cannot reverse it.
25
- def dump_foreign_key_migration(options={})
26
- Sequel::Deprecation.deprecate('Loading the schema_dumper extension globally', "Please use Database#extension to load the extension into this database") unless is_a?(SchemaDumper)
22
+ def dump_foreign_key_migration(options=OPTS)
27
23
  ts = tables(options)
28
24
  <<END_MIG
29
25
  Sequel.migration do
@@ -41,8 +37,7 @@ END_MIG
41
37
  # :index_names :: If set to false, don't record names of indexes. If
42
38
  # set to :namespace, prepend the table name to the index name if the
43
39
  # database does not use a global index namespace.
44
- def dump_indexes_migration(options={})
45
- Sequel::Deprecation.deprecate('Loading the schema_dumper extension globally', "Please use Database#extension to load the extension into this database") unless is_a?(SchemaDumper)
40
+ def dump_indexes_migration(options=OPTS)
46
41
  ts = tables(options)
47
42
  <<END_MIG
48
43
  Sequel.migration do
@@ -66,8 +61,7 @@ END_MIG
66
61
  # later via #dump_index_migration).
67
62
  # :index_names :: If set to false, don't record names of indexes. If
68
63
  # set to :namespace, prepend the table name to the index name.
69
- def dump_schema_migration(options={})
70
- Sequel::Deprecation.deprecate('Loading the schema_dumper extension globally', "Please use Database#extension to load the extension into this database") unless is_a?(SchemaDumper)
64
+ def dump_schema_migration(options=OPTS)
71
65
  options = options.dup
72
66
  if options[:indexes] == false && !options.has_key?(:foreign_keys)
73
67
  # Unless foreign_keys option is specifically set, disable if indexes
@@ -96,8 +90,7 @@ END_MIG
96
90
 
97
91
  # Return a string with a create table block that will recreate the given
98
92
  # table's schema. Takes the same options as dump_schema_migration.
99
- def dump_table_schema(table, options={})
100
- Sequel::Deprecation.deprecate('Loading the schema_dumper extension globally', "Please use Database#extension to load the extension into this database") unless is_a?(SchemaDumper)
93
+ def dump_table_schema(table, options=OPTS)
101
94
  table = table.value.to_s if table.is_a?(SQL::Identifier)
102
95
  gen = dump_table_generator(table, options)
103
96
  commands = [gen.dump_columns, gen.dump_constraints, gen.dump_indexes].reject{|x| x == ''}.join("\n\n")
@@ -215,7 +208,7 @@ END_MIG
215
208
 
216
209
  # For the table given, get the list of foreign keys and return an alter_table
217
210
  # string that would add the foreign keys if run in a migration.
218
- def dump_table_foreign_keys(table, options={})
211
+ def dump_table_foreign_keys(table, options=OPTS)
219
212
  if supports_foreign_key_parsing?
220
213
  fks = foreign_key_list(table, options).sort_by{|fk| fk[:columns].map{|c| c.to_s}}
221
214
  end
@@ -229,7 +222,7 @@ END_MIG
229
222
 
230
223
  # Return a Schema::Generator object that will recreate the
231
224
  # table's schema. Takes the same options as dump_schema_migration.
232
- def dump_table_generator(table, options={})
225
+ def dump_table_generator(table, options=OPTS)
233
226
  table = table.value.to_s if table.is_a?(SQL::Identifier)
234
227
  raise(Error, "must provide table as a Symbol, String, or Sequel::SQL::Identifier") unless [String, Symbol].any?{|c| table.is_a?(c)}
235
228
  s = schema(table).dup
@@ -277,7 +270,7 @@ END_MIG
277
270
 
278
271
  # Return a string that containing add_index/drop_index method calls for
279
272
  # creating the index migration.
280
- def dump_table_indexes(table, meth, options={})
273
+ def dump_table_indexes(table, meth, options=OPTS)
281
274
  if supports_index_parsing?
282
275
  indexes = indexes(table).sort_by{|k,v| k.to_s}
283
276
  else
@@ -292,7 +285,7 @@ END_MIG
292
285
  end
293
286
 
294
287
  # Convert the parsed index information into options to the Generators index method.
295
- def index_to_generator_opts(table, name, index_opts, options={})
288
+ def index_to_generator_opts(table, name, index_opts, options=OPTS)
296
289
  h = {}
297
290
  if options[:index_names] != false && default_index_name(table, index_opts[:columns]) != name.to_s
298
291
  if options[:index_names] == :namespace && !global_index_namespace?
@@ -308,7 +301,7 @@ END_MIG
308
301
 
309
302
  # Sort the tables so that referenced tables are created before tables that
310
303
  # reference them, and then by name. If foreign keys are disabled, just sort by name.
311
- def sort_dumped_tables(tables, options={})
304
+ def sort_dumped_tables(tables, options=OPTS)
312
305
  if options[:foreign_keys] != false && supports_foreign_key_parsing?
313
306
  table_fks = {}
314
307
  tables.each{|t| table_fks[t] = foreign_key_list(t)}
@@ -440,7 +433,7 @@ END_MIG
440
433
  # The value of this option should be the table name to use.
441
434
  # * :drop_index - Same as add_index, but create drop_index statements.
442
435
  # * :ignore_errors - Add the ignore_errors option to the outputted indexes
443
- def dump_indexes(options={})
436
+ def dump_indexes(options=OPTS)
444
437
  is = indexes.map do |c|
445
438
  c = c.dup
446
439
  cols = c.delete(:columns)
@@ -14,9 +14,6 @@
14
14
 
15
15
  module Sequel
16
16
  module SelectRemove
17
- end
18
-
19
- class Dataset
20
17
  # Remove columns from the list of selected columns. If any of the currently selected
21
18
  # columns use expressions/aliases, this will remove selected columns with the given
22
19
  # aliases. It will also remove entries from the selection that match exactly:
@@ -38,7 +35,6 @@ module Sequel
38
35
  #
39
36
  # There may be other cases where this method does not work correctly, use it with caution.
40
37
  def select_remove(*cols)
41
- Sequel::Deprecation.deprecate('Loading the select_remove extension globally', "Please use Database/Dataset#extension to load the extension into this dataset") unless is_a?(SelectRemove)
42
38
  if (sel = @opts[:select]) && !sel.empty?
43
39
  select(*(columns.zip(sel).reject{|c, s| cols.include?(c)}.map{|c, s| s} - cols))
44
40
  else
@@ -16,8 +16,18 @@
16
16
 
17
17
  module Sequel
18
18
  module SetOverrides
19
+ Dataset::NON_SQL_OPTIONS.concat([:defaults, :overrides])
19
20
  Dataset.def_mutation_method(:set_defaults, :set_overrides, :module=>self)
20
21
 
22
+ # Set overrides/defaults for insert hashes
23
+ def insert_sql(*values)
24
+ if values.size == 1 && (vals = values.first).is_a?(Hash)
25
+ super(merge_defaults_overrides(vals))
26
+ else
27
+ super
28
+ end
29
+ end
30
+
21
31
  # Set the default values for insert and update statements. The values hash passed
22
32
  # to insert or update are merged into this hash, so any values in the hash passed
23
33
  # to insert or update will override values passed to this method.
@@ -37,6 +47,24 @@ module Sequel
37
47
  def set_overrides(hash)
38
48
  clone(:overrides=>hash.merge(@opts[:overrides]||{}))
39
49
  end
50
+
51
+ # Set overrides/defaults for update hashes
52
+ def update_sql(values = {})
53
+ if values.is_a?(Hash)
54
+ super(merge_defaults_overrides(values))
55
+ else
56
+ super
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ # Return new hashe with merged defaults and overrides.
63
+ def merge_defaults_overrides(vals)
64
+ vals = @opts[:defaults].merge(vals) if @opts[:defaults]
65
+ vals = vals.merge(@opts[:overrides]) if @opts[:overrides]
66
+ vals
67
+ end
40
68
  end
41
69
 
42
70
  Dataset.register_extension(:set_overrides, SetOverrides)
@@ -10,6 +10,12 @@
10
10
  module Sequel
11
11
  class ToDot
12
12
  module DatasetMethods
13
+ # Return a string that can be processed by the +dot+ program (included
14
+ # with graphviz) in order to see a visualization of the dataset's
15
+ # abstract syntax tree.
16
+ def to_dot
17
+ ToDot.output(self)
18
+ end
13
19
  end
14
20
 
15
21
  # The option keys that should be included in the dot output.
@@ -145,15 +151,5 @@ module Sequel
145
151
  end
146
152
  end
147
153
 
148
- class Dataset
149
- # Return a string that can be processed by the +dot+ program (included
150
- # with graphviz) in order to see a visualization of the dataset's
151
- # abstract syntax tree.
152
- def to_dot
153
- Sequel::Deprecation.deprecate('Loading the to_dot extension globally', "Please use Database/Dataset#extension to load the extension into this dataset") unless is_a?(ToDot::DatasetMethods)
154
- ToDot.output(self)
155
- end
156
- end
157
-
158
154
  Dataset.register_extension(:to_dot, ToDot::DatasetMethods)
159
155
  end
@@ -72,6 +72,8 @@ module Sequel
72
72
  # You can set the +SEQUEL_NO_ASSOCIATIONS+ constant or environment variable to
73
73
  # make Sequel not load the associations plugin by default.
74
74
  class Model
75
+ OPTS = Sequel::OPTS
76
+
75
77
  # Map that stores model classes created with <tt>Sequel::Model()</tt>, to allow the reopening
76
78
  # of classes when dealing with code reloading.
77
79
  ANONYMOUS_MODEL_CLASSES = {}
@@ -80,9 +82,6 @@ module Sequel
80
82
  DATASET_METHODS = (Dataset::ACTION_METHODS + Dataset::QUERY_METHODS +
81
83
  [:each_server]) - [:and, :or, :[], :[]=, :columns, :columns!, :delete, :update, :add_graph_aliases]
82
84
 
83
- # Class instance variables to set to nil when a subclass is created, for -w compliance
84
- EMPTY_INSTANCE_VARIABLES = [:@overridable_methods_module, :@db]
85
-
86
85
  # Boolean settings that can be modified at the global, class, or instance level.
87
86
  BOOLEAN_SETTINGS = [:typecast_empty_string_to_nil, :typecast_on_assignment, :strict_param_setting, \
88
87
  :raise_on_save_failure, :raise_on_typecast_failure, :require_modification, :use_after_commit_rollback, :use_transactions]
@@ -94,7 +93,7 @@ module Sequel
94
93
 
95
94
  # Hooks that are called after an action. When overriding these, it is recommended to call
96
95
  # +super+ on the first line of your method, so later hooks are called after earlier hooks.
97
- AFTER_HOOKS = [:after_initialize, :after_create, :after_update, :after_save, :after_destroy,
96
+ AFTER_HOOKS = [:after_create, :after_update, :after_save, :after_destroy,
98
97
  :after_validation, :after_commit, :after_rollback, :after_destroy_commit, :after_destroy_rollback]
99
98
 
100
99
  # Hooks that are called around an action. If overridden, these methods must call super
@@ -120,7 +119,7 @@ module Sequel
120
119
  :@typecast_empty_string_to_nil=>nil, :@typecast_on_assignment=>nil,
121
120
  :@raise_on_typecast_failure=>nil, :@plugins=>:dup, :@setter_methods=>nil,
122
121
  :@use_after_commit_rollback=>nil, :@fast_pk_lookup_sql=>nil,
123
- :@fast_instance_delete_sql=>nil, :@default_eager_limit_strategy=>nil,
122
+ :@fast_instance_delete_sql=>nil,
124
123
  :@db=>nil, :@default_set_fields_options=>:dup}
125
124
 
126
125
  # Regular expression that determines if a method name is normal in the sense that
@@ -137,7 +136,7 @@ module Sequel
137
136
  @db_schema = nil
138
137
  @dataset = nil
139
138
  @dataset_method_modules = []
140
- @default_eager_limit_strategy = nil
139
+ @default_eager_limit_strategy = true
141
140
  @default_set_fields_options = {}
142
141
  @overridable_methods_module = nil
143
142
  @fast_pk_lookup_sql = nil
@@ -145,7 +144,7 @@ module Sequel
145
144
  @plugins = []
146
145
  @primary_key = :id
147
146
  @raise_on_save_failure = true
148
- @raise_on_typecast_failure = true
147
+ @raise_on_typecast_failure = false
149
148
  @require_modification = nil
150
149
  @restrict_primary_key = true
151
150
  @restricted_columns = nil
@@ -9,6 +9,7 @@ module Sequel
9
9
  # Set an empty association reflection hash in the model
10
10
  def self.apply(model)
11
11
  model.instance_variable_set(:@association_reflections, {})
12
+ model.instance_variable_set(:@autoreloading_associations, {})
12
13
  end
13
14
 
14
15
  # AssociationReflection is a Hash subclass that keeps information on Sequel::Model associations. It
@@ -189,25 +190,14 @@ module Sequel
189
190
  def reciprocal
190
191
  cached_fetch(:reciprocal) do
191
192
  possible_recips = []
192
- fallback_recips = []
193
193
 
194
194
  associated_class.all_association_reflections.each do |assoc_reflect|
195
195
  if reciprocal_association?(assoc_reflect)
196
- if deprecated_reciprocal_association?(assoc_reflect)
197
- fallback_recips << assoc_reflect
198
- else
199
- possible_recips << assoc_reflect
200
- end
196
+ possible_recips << assoc_reflect
201
197
  end
202
198
  end
203
199
 
204
- Sequel::Deprecation.deprecate("Multiple reciprocal association candidates found for #{self[:name]} association (#{possible_recips.map{|r| r[:name]}.join(', ')}). Choosing the first candidate is", "Please explicitly specify the reciprocal option for the #{self[:name]} association") if possible_recips.size >= 2
205
- if possible_recips.empty? && !fallback_recips.empty?
206
- possible_recips = fallback_recips
207
- Sequel::Deprecation.deprecate("All reciprocal association candidates found for #{self[:name]} association have conditions, blocks, or differing primary keys (#{possible_recips.map{|r| r[:name]}.join(', ')}). Automatic choosing of an reciprocal association with conditions or blocks is", "Please explicitly specify the reciprocal option for the #{self[:name]} association")
208
- end
209
-
210
- unless possible_recips.empty?
200
+ if possible_recips.length == 1
211
201
  cached_set(:reciprocal_type, possible_recips.first[:type]) if reciprocal_type.is_a?(Array)
212
202
  possible_recips.first[:name]
213
203
  end
@@ -263,6 +253,14 @@ module Sequel
263
253
  :"#{self[:name]}="
264
254
  end
265
255
 
256
+ # The range used for slicing when using the :ruby eager limit strategy.
257
+ def slice_range
258
+ limit, offset = limit_and_offset
259
+ if limit || offset
260
+ (offset||0)..(limit ? (offset||0)+limit-1 : -1)
261
+ end
262
+ end
263
+
266
264
  private
267
265
 
268
266
  if defined?(RUBY_ENGINE) && RUBY_ENGINE != 'ruby'
@@ -300,14 +298,11 @@ module Sequel
300
298
  end
301
299
  end
302
300
 
303
- # REMOVE40: merge into reciprocal_association?
304
- def deprecated_reciprocal_association?(assoc_reflect)
305
- assoc_reflect[:conditions] || assoc_reflect[:block]
306
- end
307
-
308
301
  def reciprocal_association?(assoc_reflect)
309
302
  Array(reciprocal_type).include?(assoc_reflect[:type]) &&
310
- assoc_reflect.associated_class == self[:model]
303
+ assoc_reflect.associated_class == self[:model] &&
304
+ assoc_reflect[:conditions].nil? &&
305
+ assoc_reflect[:block].nil?
311
306
  end
312
307
 
313
308
  # If +s+ is an array, map +s+ over the block. Otherwise, just call the
@@ -400,13 +395,8 @@ module Sequel
400
395
 
401
396
  private
402
397
 
403
- # REMOVE40: merge into reciprocal_association?
404
- def deprecated_reciprocal_association?(assoc_reflect)
405
- super || primary_key != assoc_reflect.primary_key
406
- end
407
-
408
398
  def reciprocal_association?(assoc_reflect)
409
- super && self[:keys] == assoc_reflect[:keys]
399
+ super && self[:keys] == assoc_reflect[:keys] && primary_key == assoc_reflect.primary_key
410
400
  end
411
401
 
412
402
  # The reciprocal type of a many_to_one association is either
@@ -444,7 +434,7 @@ module Sequel
444
434
 
445
435
  # The column in the current table that the key in the associated table references.
446
436
  def primary_key
447
- self[:primary_key]
437
+ self[:primary_key]
448
438
  end
449
439
 
450
440
  # #primary_key qualified by the current table
@@ -475,13 +465,8 @@ module Sequel
475
465
 
476
466
  private
477
467
 
478
- # REMOVE40: merge into reciprocal_association?
479
- def deprecated_reciprocal_association?(assoc_reflect)
480
- super || primary_key != assoc_reflect.primary_key
481
- end
482
-
483
468
  def reciprocal_association?(assoc_reflect)
484
- super && self[:keys] == assoc_reflect[:keys]
469
+ super && self[:keys] == assoc_reflect[:keys] && primary_key == assoc_reflect.primary_key
485
470
  end
486
471
 
487
472
  # The reciprocal type of a one_to_many association is a many_to_one association.
@@ -497,15 +482,18 @@ module Sequel
497
482
  # support both DISTINCT ON and window functions as strategies.
498
483
  def eager_limit_strategy
499
484
  cached_fetch(:_eager_limit_strategy) do
500
- case s = self[:eager_limit_strategy]
485
+ offset = limit_and_offset.last
486
+ case s = self.fetch(:eager_limit_strategy){(self[:model].default_eager_limit_strategy || :ruby) if offset}
501
487
  when Symbol
502
488
  s
503
489
  when true
504
490
  ds = associated_class.dataset
505
- if ds.supports_ordered_distinct_on?
491
+ if ds.supports_ordered_distinct_on? && offset.nil?
506
492
  :distinct_on
507
493
  elsif ds.supports_window_functions?
508
494
  :window_function
495
+ else
496
+ :ruby
509
497
  end
510
498
  else
511
499
  nil
@@ -515,7 +503,11 @@ module Sequel
515
503
 
516
504
  # The limit and offset for this association (returned as a two element array).
517
505
  def limit_and_offset
518
- [1, nil]
506
+ if (v = self[:limit]).is_a?(Array)
507
+ v
508
+ else
509
+ [v, nil]
510
+ end
519
511
  end
520
512
 
521
513
  # one_to_one associations return a single object, not an array
@@ -644,15 +636,12 @@ module Sequel
644
636
 
645
637
  private
646
638
 
647
- # REMOVE40: merge into reciprocal_association?
648
- def deprecated_reciprocal_association?(assoc_reflect)
649
- super || right_primary_keys != assoc_reflect[:left_primary_key_columns] || self[:left_primary_key_columns] != assoc_reflect.right_primary_keys
650
- end
651
-
652
639
  def reciprocal_association?(assoc_reflect)
653
640
  super && assoc_reflect[:left_keys] == self[:right_keys] &&
654
641
  assoc_reflect[:right_keys] == self[:left_keys] &&
655
- assoc_reflect[:join_table] == self[:join_table]
642
+ assoc_reflect[:join_table] == self[:join_table] &&
643
+ right_primary_keys == assoc_reflect[:left_primary_key_columns] &&
644
+ self[:left_primary_key_columns] == assoc_reflect.right_primary_keys
656
645
  end
657
646
 
658
647
  def reciprocal_type
@@ -727,7 +716,13 @@ module Sequel
727
716
  # All association reflections defined for this model (default: {}).
728
717
  attr_reader :association_reflections
729
718
 
730
- # The default :eager_limit_strategy option to use for *_many associations (default: nil)
719
+ # Hash with column symbol keys and arrays of many_to_one
720
+ # association symbols that should be cleared when the column
721
+ # value changes.
722
+ attr_reader :autoreloading_associations
723
+
724
+ # The default :eager_limit_strategy option to use for limited or offset associations (default: true, causing Sequel
725
+ # to use what it considers the most appropriate strategy).
731
726
  attr_accessor :default_eager_limit_strategy
732
727
 
733
728
  # Array of all association reflections for this model class
@@ -823,14 +818,8 @@ module Sequel
823
818
  # (the alias that was used for the current table), and possibly :eager_block (a callback
824
819
  # proc accepting the associated dataset, for per-call customization).
825
820
  # Should return a copy of the dataset with the association graphed into it.
826
- # :eager_limit_strategy :: Determines the strategy used for enforcing limits when eager loading associations via
827
- # the +eager+ method. For one_to_one associations, no strategy is used by default, and
828
- # for *_many associations, the :ruby strategy is used by default, which still retrieves
829
- # all records but slices the resulting array after the association is retrieved. You
830
- # can pass a +true+ value for this option to have Sequel pick what it thinks is the best
831
- # choice for the database, or specify a specific symbol to manually select a strategy.
832
- # one_to_one associations support :distinct_on and :window_function.
833
- # *_many associations support :ruby, and :window_function.
821
+ # :eager_limit_strategy :: Determines the strategy used for enforcing limits and offsets when eager loading
822
+ # associations via the +eager+ method.
834
823
  # :eager_loader :: A proc to use to implement eager loading, overriding the default. Takes a single hash argument,
835
824
  # with at least the keys: :rows, which is an array of current model instances, :associations,
836
825
  # which is a hash of dependent associations, :self, which is the dataset doing the eager loading,
@@ -955,18 +944,9 @@ module Sequel
955
944
  # object to get the foreign key values for the join table.
956
945
  # Defaults to :right_primary_key option.
957
946
  # :uniq :: Adds a after_load callback that makes the array of objects unique.
958
- def associate(type, name, opts = {}, &block)
959
- if opts[:one_to_one] && type == :one_to_many
960
- Sequel::Deprecation.deprecate('Raising an Error when the one_to_many type uses the :one_to_one option', "Use the one_to_one associationtype")
961
- raise(Error, 'one_to_many association type with :one_to_one option removed, used one_to_one association type')
962
- end
947
+ def associate(type, name, opts = OPTS, &block)
963
948
  raise(Error, 'invalid association type') unless assoc_class = ASSOCIATION_TYPES[type]
964
949
  raise(Error, 'Model.associate name argument must be a symbol') unless name.is_a?(Symbol)
965
- raise(Error, ':eager_loader option must have an arity of 1 or 3') if opts[:eager_loader] && ![1, 3].include?(opts[:eager_loader].arity)
966
- raise(Error, ':eager_grapher option must have an arity of 1 or 3') if opts[:eager_grapher] && ![1, 3].include?(opts[:eager_grapher].arity)
967
-
968
- Sequel::Deprecation.deprecate('The :eager_loader association option accepting 3 arguments', "Please switch to accepting a single options hash") if opts[:eager_loader] && opts[:eager_loader].arity == 3
969
- Sequel::Deprecation.deprecate('The :eager_grapher association option accepting 3 arguments', "Please switch to accepting a single options hash") if opts[:eager_grapher] && opts[:eager_grapher].arity == 3
970
950
 
971
951
  # dup early so we don't modify opts
972
952
  orig_opts = opts.dup
@@ -1017,7 +997,7 @@ module Sequel
1017
997
  end
1018
998
 
1019
999
  # Modify and return eager loading dataset based on association options.
1020
- def eager_loading_dataset(opts, ds, select, associations, eager_options={})
1000
+ def eager_loading_dataset(opts, ds, select, associations, eager_options=OPTS)
1021
1001
  ds = apply_association_dataset_opts(opts, ds)
1022
1002
  ds = ds.select(*select) if select
1023
1003
  if opts[:eager_graph]
@@ -1037,93 +1017,44 @@ module Sequel
1037
1017
  end
1038
1018
 
1039
1019
  # Shortcut for adding a many_to_many association, see #associate
1040
- def many_to_many(name, opts={}, &block)
1020
+ def many_to_many(name, opts=OPTS, &block)
1041
1021
  associate(:many_to_many, name, opts, &block)
1042
1022
  end
1043
1023
 
1044
1024
  # Shortcut for adding a many_to_one association, see #associate
1045
- def many_to_one(name, opts={}, &block)
1025
+ def many_to_one(name, opts=OPTS, &block)
1046
1026
  associate(:many_to_one, name, opts, &block)
1047
1027
  end
1048
1028
 
1049
1029
  # Shortcut for adding a one_to_many association, see #associate
1050
- def one_to_many(name, opts={}, &block)
1030
+ def one_to_many(name, opts=OPTS, &block)
1051
1031
  associate(:one_to_many, name, opts, &block)
1052
1032
  end
1053
1033
 
1054
1034
  # Shortcut for adding a one_to_one association, see #associate.
1055
- def one_to_one(name, opts={}, &block)
1035
+ def one_to_one(name, opts=OPTS, &block)
1056
1036
  associate(:one_to_one, name, opts, &block)
1057
1037
  end
1058
1038
 
1059
- Plugins.inherited_instance_variables(self, :@association_reflections=>:dup, :@default_eager_limit_strategy=>nil)
1039
+ Plugins.inherited_instance_variables(self, :@association_reflections=>:dup, :@autoreloading_associations=>:hash_dup, :@default_eager_limit_strategy=>nil)
1060
1040
  Plugins.def_dataset_methods(self, [:eager, :eager_graph])
1061
1041
 
1062
1042
  private
1063
1043
 
1064
- # Use a correlated subquery to limit the results of the eager loading dataset.
1065
- def apply_correlated_subquery_eager_limit_strategy(ds, opts)
1066
- Sequel::Deprecation.deprecate('The correlated_subquery eager limit strategy', 'Switch to another eager limit strategy.')
1067
- klass = opts.associated_class
1068
- kds = klass.dataset
1069
- dsa = ds.send(:dataset_alias, 1)
1070
- raise Error, "can't use a correlated subquery if the associated class (#{opts.associated_class.inspect}) does not have a primary key" unless pk = klass.primary_key
1071
- pka = Array(pk)
1072
- raise Error, "can't use a correlated subquery if the associated class (#{opts.associated_class.inspect}) has a composite primary key and the database does not support multiple column IN" if pka.length > 1 && !ds.supports_multiple_column_in?
1073
- table = kds.opts[:from]
1074
- raise Error, "can't use a correlated subquery unless the associated class (#{opts.associated_class.inspect}) uses a single FROM table" unless table && table.length == 1
1075
- table = table.first
1076
- if order = ds.opts[:order]
1077
- oproc = lambda do |x|
1078
- case x
1079
- when Symbol
1080
- t, c, _ = ds.send(:split_symbol, x)
1081
- if t && t.to_sym == table
1082
- SQL::QualifiedIdentifier.new(dsa, c)
1083
- else
1084
- x
1085
- end
1086
- when SQL::QualifiedIdentifier
1087
- if x.table == table
1088
- SQL::QualifiedIdentifier.new(dsa, x.column)
1089
- else
1090
- x
1091
- end
1092
- when SQL::OrderedExpression
1093
- SQL::OrderedExpression.new(oproc.call(x.expression), x.descending, :nulls=>x.nulls)
1094
- else
1095
- x
1096
- end
1097
- end
1098
- order = order.map(&oproc)
1099
- end
1100
- limit, offset = opts.limit_and_offset
1101
-
1102
- subquery = yield kds.
1103
- unlimited.
1104
- from(SQL::AliasedExpression.new(table, dsa)).
1105
- select(*pka.map{|k| SQL::QualifiedIdentifier.new(dsa, k)}).
1106
- order(*order).
1107
- limit(limit, offset)
1108
-
1109
- pk = if pk.is_a?(Array)
1110
- pk.map{|k| SQL::QualifiedIdentifier.new(table, k)}
1111
- else
1112
- SQL::QualifiedIdentifier.new(table, pk)
1113
- end
1114
- ds.where(pk=>subquery)
1115
- end
1116
-
1117
1044
  # Use a window function to limit the results of the eager loading dataset.
1118
1045
  def apply_window_function_eager_limit_strategy(ds, opts)
1119
1046
  rn = ds.row_number_column
1120
1047
  limit, offset = opts.limit_and_offset
1121
1048
  ds = ds.unordered.select_append{row_number(:over, :partition=>opts.predicate_key, :order=>ds.opts[:order]){}.as(rn)}.from_self
1122
1049
  ds = if opts[:type] == :one_to_one
1123
- ds.where(rn => 1)
1050
+ ds.where(rn => offset ? offset+1 : 1)
1124
1051
  elsif offset
1125
1052
  offset += 1
1126
- ds.where(rn => (offset...(offset+limit)))
1053
+ if limit
1054
+ ds.where(rn => (offset...(offset+limit)))
1055
+ else
1056
+ ds.where{SQL::Identifier.new(rn) >= offset}
1057
+ end
1127
1058
  else
1128
1059
  ds.where{SQL::Identifier.new(rn) <= limit}
1129
1060
  end
@@ -1131,19 +1062,19 @@ module Sequel
1131
1062
 
1132
1063
  # The module to use for the association's methods. Defaults to
1133
1064
  # the overridable_methods_module.
1134
- def association_module(opts={})
1065
+ def association_module(opts=OPTS)
1135
1066
  opts.fetch(:methods_module, overridable_methods_module)
1136
1067
  end
1137
1068
 
1138
1069
  # Add a method to the module included in the class, so the method
1139
1070
  # can be easily overridden in the class itself while allowing for
1140
1071
  # super to be called.
1141
- def association_module_def(name, opts={}, &block)
1072
+ def association_module_def(name, opts=OPTS, &block)
1142
1073
  association_module(opts).module_eval{define_method(name, &block)}
1143
1074
  end
1144
1075
 
1145
1076
  # Add a private method to the module included in the class.
1146
- def association_module_private_def(name, opts={}, &block)
1077
+ def association_module_private_def(name, opts=OPTS, &block)
1147
1078
  association_module_def(name, opts, &block)
1148
1079
  association_module(opts).send(:private, name)
1149
1080
  end
@@ -1190,6 +1121,7 @@ module Sequel
1190
1121
  graph_jt_conds = opts[:graph_join_table_conditions] = opts.fetch(:graph_join_table_conditions, []).to_a
1191
1122
  opts[:graph_join_table_join_type] ||= opts[:graph_join_type]
1192
1123
  opts[:after_load].unshift(:array_uniq!) if opts[:uniq]
1124
+ slice_range = opts.slice_range
1193
1125
  opts[:dataset] ||= proc{opts.associated_dataset.inner_join(join_table, rcks.zip(opts.right_primary_keys) + opts.predicate_keys.zip(lcpks.map{|k| send(k)}), :qualify=>:deep)}
1194
1126
 
1195
1127
  opts[:eager_loader] ||= proc do |eo|
@@ -1199,16 +1131,10 @@ module Sequel
1199
1131
  r = rcks.zip(opts.right_primary_keys)
1200
1132
  l = [[opts.predicate_key, h.keys]]
1201
1133
  ds = model.eager_loading_dataset(opts, opts.associated_class.inner_join(join_table, r + l, :qualify=>:deep), nil, eo[:associations], eo)
1202
- case opts.eager_limit_strategy
1203
- when :window_function
1134
+ if opts.eager_limit_strategy == :window_function
1204
1135
  delete_rn = true
1205
1136
  rn = ds.row_number_column
1206
1137
  ds = apply_window_function_eager_limit_strategy(ds, opts)
1207
- when :correlated_subquery
1208
- ds = apply_correlated_subquery_eager_limit_strategy(ds, opts) do |xds|
1209
- dsa = ds.send(:dataset_alias, 2)
1210
- xds.inner_join(join_table, r + lcks.map{|k| [k, SQL::QualifiedIdentifier.new(opts.join_table_alias, k)]}, :table_alias=>dsa, :qualify=>:deep)
1211
- end
1212
1138
  end
1213
1139
  ds.all do |assoc_record|
1214
1140
  assoc_record.values.delete(rn) if delete_rn
@@ -1221,8 +1147,7 @@ module Sequel
1221
1147
  objects.each{|object| object.associations[name].push(assoc_record)}
1222
1148
  end
1223
1149
  if opts.eager_limit_strategy == :ruby
1224
- limit, offset = opts.limit_and_offset
1225
- rows.each{|o| o.associations[name] = o.associations[name].slice(offset||0, limit) || []}
1150
+ rows.each{|o| o.associations[name] = o.associations[name][slice_range] || []}
1226
1151
  end
1227
1152
  end
1228
1153
 
@@ -1285,6 +1210,17 @@ module Sequel
1285
1210
  end
1286
1211
  uses_cks = opts[:uses_composite_keys] = cks.length > 1
1287
1212
  opts[:cartesian_product_number] ||= 0
1213
+
1214
+ if !opts.has_key?(:many_to_one_pk_lookup) &&
1215
+ (opts[:dataset] || opts[:conditions] || opts[:block] || opts[:select] ||
1216
+ (opts.has_key?(:key) && opts[:key] == nil))
1217
+ opts[:many_to_one_pk_lookup] = false
1218
+ end
1219
+ auto_assocs = @autoreloading_associations
1220
+ cks.each do |k|
1221
+ (auto_assocs[k] ||= []) << name
1222
+ end
1223
+
1288
1224
  opts[:dataset] ||= proc do
1289
1225
  opts.associated_dataset.where(opts.predicate_keys.zip(cks.map{|k| send(k)}))
1290
1226
  end
@@ -1342,21 +1278,18 @@ module Sequel
1342
1278
  pkcs = opts[:primary_key_columns] ||= Array(pkc)
1343
1279
  raise(Error, "mismatched number of keys: #{cks.inspect} vs #{cpks.inspect}") unless cks.length == cpks.length
1344
1280
  uses_cks = opts[:uses_composite_keys] = cks.length > 1
1281
+ slice_range = opts.slice_range
1345
1282
  opts[:dataset] ||= proc do
1346
1283
  opts.associated_dataset.where(opts.predicate_keys.zip(cpks.map{|k| send(k)}))
1347
1284
  end
1348
1285
  opts[:eager_loader] ||= proc do |eo|
1349
1286
  h = eo[:id_map]
1350
1287
  rows = eo[:rows]
1351
- if one_to_one
1352
- rows.each{|object| object.associations[name] = nil}
1353
- else
1354
- rows.each{|object| object.associations[name] = []}
1355
- end
1356
1288
  reciprocal = opts.reciprocal
1357
1289
  klass = opts.associated_class
1358
1290
  filter_keys = opts.predicate_key
1359
1291
  ds = model.eager_loading_dataset(opts, klass.where(filter_keys=>h.keys), nil, eo[:associations], eo)
1292
+ assign_singular = true if one_to_one
1360
1293
  case opts.eager_limit_strategy
1361
1294
  when :distinct_on
1362
1295
  ds = ds.distinct(*filter_keys).order_prepend(*filter_keys)
@@ -1364,16 +1297,19 @@ module Sequel
1364
1297
  delete_rn = true
1365
1298
  rn = ds.row_number_column
1366
1299
  ds = apply_window_function_eager_limit_strategy(ds, opts)
1367
- when :correlated_subquery
1368
- ds = apply_correlated_subquery_eager_limit_strategy(ds, opts) do |xds|
1369
- xds.where(opts.associated_object_keys.map{|k| [SQL::QualifiedIdentifier.new(xds.first_source_alias, k), SQL::QualifiedIdentifier.new(xds.first_source_table, k)]})
1370
- end
1300
+ when :ruby
1301
+ assign_singular = false if one_to_one && slice_range
1302
+ end
1303
+ if assign_singular
1304
+ rows.each{|object| object.associations[name] = nil}
1305
+ else
1306
+ rows.each{|object| object.associations[name] = []}
1371
1307
  end
1372
1308
  ds.all do |assoc_record|
1373
1309
  assoc_record.values.delete(rn) if delete_rn
1374
1310
  hash_key = uses_cks ? km.map{|k| assoc_record.send(k)} : assoc_record.send(km)
1375
1311
  next unless objects = h[hash_key]
1376
- if one_to_one
1312
+ if assign_singular
1377
1313
  objects.each do |object|
1378
1314
  unless object.associations[name]
1379
1315
  object.associations[name] = assoc_record
@@ -1388,8 +1324,13 @@ module Sequel
1388
1324
  end
1389
1325
  end
1390
1326
  if opts.eager_limit_strategy == :ruby
1391
- limit, offset = opts.limit_and_offset
1392
- rows.each{|o| o.associations[name] = o.associations[name].slice(offset||0, limit) || []}
1327
+ if one_to_one
1328
+ if slice_range
1329
+ rows.each{|o| o.associations[name] = o.associations[name][slice_range.begin]}
1330
+ end
1331
+ else
1332
+ rows.each{|o| o.associations[name] = o.associations[name][slice_range] || []}
1333
+ end
1393
1334
  end
1394
1335
  end
1395
1336
 
@@ -1492,20 +1433,6 @@ module Sequel
1492
1433
  super
1493
1434
  end
1494
1435
 
1495
- # Clear the associations cache when refreshing
1496
- def set_values(hash)
1497
- @associations.clear if @associations
1498
- super
1499
- end
1500
-
1501
- # Formally used internally by the associations code, like pk but doesn't raise
1502
- # an Error if the model has no primary key. Not used any longer, deprecated.
1503
- def pk_or_nil
1504
- Sequel::Deprecation.deprecate('Model#pk_or_nil', 'There is no replacement')
1505
- key = primary_key
1506
- key.is_a?(Array) ? key.map{|k| @values[k]} : @values[key]
1507
- end
1508
-
1509
1436
  private
1510
1437
 
1511
1438
  # Apply the association options such as :order and :limit to the given dataset, returning a modified dataset.
@@ -1559,10 +1486,12 @@ module Sequel
1559
1486
 
1560
1487
  # Return the associated objects from the dataset, without association callbacks, reciprocals, and caching.
1561
1488
  # Still apply the dynamic callback if present.
1562
- def _load_associated_objects(opts, dynamic_opts={})
1489
+ def _load_associated_objects(opts, dynamic_opts=OPTS)
1563
1490
  if opts.can_have_associated_objects?(self)
1564
1491
  if opts.returns_array?
1565
1492
  _load_associated_object_array(opts, dynamic_opts)
1493
+ elsif load_with_primary_key_lookup?(opts, dynamic_opts)
1494
+ opts.associated_class.send(:primary_key_lookup, ((fk = opts[:key]).is_a?(Array) ? fk.map{|c| send(c)} : send(fk)))
1566
1495
  else
1567
1496
  _load_associated_object(opts, dynamic_opts)
1568
1497
  end
@@ -1570,7 +1499,13 @@ module Sequel
1570
1499
  []
1571
1500
  end
1572
1501
  end
1573
-
1502
+
1503
+ # Clear the associations cache when refreshing
1504
+ def _refresh_set_values(hash)
1505
+ @associations.clear if @associations
1506
+ super
1507
+ end
1508
+
1574
1509
  # Add the given associated object to the given association
1575
1510
  def add_associated_object(opts, o, *args)
1576
1511
  klass = opts.associated_class
@@ -1612,6 +1547,15 @@ module Sequel
1612
1547
  a.uniq!
1613
1548
  end
1614
1549
 
1550
+ # If a foreign key column value changes, clear the related
1551
+ # cached associations.
1552
+ def change_column_value(column, value)
1553
+ if assocs = model.autoreloading_associations[column]
1554
+ assocs.each{|a| associations.delete(a)}
1555
+ end
1556
+ super
1557
+ end
1558
+
1615
1559
  # Save the associated object if the associated object needs a primary key
1616
1560
  # and the associated object is new and does not have one. Raise an error if
1617
1561
  # the object still does not have a primary key
@@ -1655,6 +1599,13 @@ module Sequel
1655
1599
  end
1656
1600
  end
1657
1601
 
1602
+ # Whether to use a simple primary key lookup on the associated class when loading.
1603
+ def load_with_primary_key_lookup?(opts, dynamic_opts)
1604
+ opts[:type] == :many_to_one &&
1605
+ !dynamic_opts[:callback] &&
1606
+ opts.send(:cached_fetch, :many_to_one_pk_lookup){opts.primary_key == opts.associated_class.primary_key}
1607
+ end
1608
+
1658
1609
  # Remove all associated objects from the given association
1659
1610
  def remove_all_associated_objects(opts, *args)
1660
1611
  raise(Sequel::Error, "model object #{inspect} does not have a primary key") unless pk
@@ -1922,18 +1873,20 @@ module Sequel
1922
1873
  # Like +eager+, you need to call +all+ on the dataset for the eager loading to work. If you just
1923
1874
  # call +each+, it will yield plain hashes, each containing all columns from all the tables.
1924
1875
  def eager_graph(*associations)
1925
- ds = if eg = @opts[:eager_graph]
1876
+ if eg = @opts[:eager_graph]
1926
1877
  eg = eg.dup
1927
1878
  [:requirements, :reflections, :reciprocals].each{|k| eg[k] = eg[k].dup}
1928
- clone(:eager_graph=>eg)
1879
+ ds = clone(:eager_graph=>eg)
1880
+ ds.eager_graph_associations(ds, model, ds.opts[:eager_graph][:master], [], *associations)
1929
1881
  else
1930
1882
  # Each of the following have a symbol key for the table alias, with the following values:
1931
1883
  # :reciprocals - the reciprocal instance variable to use for this association
1932
1884
  # :reflections - AssociationReflection instance related to this association
1933
1885
  # :requirements - array of requirements for this association
1934
- clone(:eager_graph=>{:requirements=>{}, :master=>alias_symbol(first_source), :reflections=>{}, :reciprocals=>{}, :cartesian_product_number=>0})
1886
+ ds = clone(:eager_graph=>{:requirements=>{}, :master=>alias_symbol(first_source), :reflections=>{}, :reciprocals=>{}, :cartesian_product_number=>0, :row_proc=>row_proc})
1887
+ ds.eager_graph_associations(ds, model, ds.opts[:eager_graph][:master], [], *associations).
1888
+ naked
1935
1889
  end
1936
- ds.eager_graph_associations(ds, model, ds.opts[:eager_graph][:master], [], *associations)
1937
1890
  end
1938
1891
 
1939
1892
  # Do not attempt to split the result set into associations,
@@ -1941,7 +1894,11 @@ module Sequel
1941
1894
  # want to use eager_graph as a shortcut to have all of the joins
1942
1895
  # and aliasing set up, but want to do something else with the dataset.
1943
1896
  def ungraphed
1944
- super.clone(:eager_graph=>nil)
1897
+ ds = super.clone(:eager_graph=>nil)
1898
+ if (eg = @opts[:eager_graph]) && (rp = eg[:row_proc])
1899
+ ds.row_proc = rp
1900
+ end
1901
+ ds
1945
1902
  end
1946
1903
 
1947
1904
  protected
@@ -1977,11 +1934,7 @@ module Sequel
1977
1934
  associations = assoc.is_a?(Array) ? assoc : [assoc]
1978
1935
  end
1979
1936
  end
1980
- ds = if loader.arity == 1
1981
- loader.call(:self=>ds, :table_alias=>assoc_table_alias, :implicit_qualifier=>ta, :callback=>callback)
1982
- else
1983
- loader.call(ds, assoc_table_alias, ta)
1984
- end
1937
+ ds = loader.call(:self=>ds, :table_alias=>assoc_table_alias, :implicit_qualifier=>ta, :callback=>callback)
1985
1938
  ds = ds.order_more(*qualified_expression(r[:order], assoc_table_alias)) if r[:order] and r[:order_eager_graph]
1986
1939
  eager_graph = ds.opts[:eager_graph]
1987
1940
  eager_graph[:requirements][assoc_table_alias] = requirements.dup
@@ -2134,20 +2087,11 @@ module Sequel
2134
2087
  elsif associations.is_a?(Hash) && associations.length == 1 && (pr_assoc = associations.to_a.first) && pr_assoc.first.respond_to?(:call)
2135
2088
  eager_block, associations = pr_assoc
2136
2089
  end
2137
- if loader.arity == 1
2138
- loader.call(:key_hash=>key_hash, :rows=>a, :associations=>associations, :self=>self, :eager_block=>eager_block, :id_map=>id_map)
2139
- else
2140
- loader.call(key_hash, a, associations)
2141
- end
2090
+ loader.call(:key_hash=>key_hash, :rows=>a, :associations=>associations, :self=>self, :eager_block=>eager_block, :id_map=>id_map)
2142
2091
  a.each{|object| object.send(:run_association_callbacks, r, :after_load, object.associations[r[:name]])} unless r[:after_load].empty?
2143
2092
  end
2144
2093
  end
2145
2094
 
2146
- # Return plain hashes instead of calling the row_proc if eager_graph is being used.
2147
- def graph_each(&block)
2148
- @opts[:eager_graph] ? fetch_rows(select_sql, &block) : super
2149
- end
2150
-
2151
2095
  # Return a subquery expression for filering by a many_to_many association
2152
2096
  def many_to_many_association_filter_expression(op, ref, obj)
2153
2097
  lpks, lks, rks = ref.values_at(:left_primary_key_columns, :left_keys, :right_keys)
@@ -2474,7 +2418,8 @@ module Sequel
2474
2418
  list.uniq!
2475
2419
  if lo = limit_map[ta]
2476
2420
  limit, offset = lo
2477
- list.replace(list[offset||0, limit])
2421
+ offset ||= 0
2422
+ list.replace(list[(offset)..(limit ? (offset)+limit-1 : -1)])
2478
2423
  end
2479
2424
  list
2480
2425
  elsif list