sequel 3.21.0 → 3.41.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 (363) hide show
  1. data/CHANGELOG +1131 -3
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +106 -65
  4. data/Rakefile +59 -27
  5. data/bin/sequel +50 -26
  6. data/doc/active_record.rdoc +68 -52
  7. data/doc/advanced_associations.rdoc +201 -75
  8. data/doc/association_basics.rdoc +327 -79
  9. data/doc/bin_sequel.rdoc +144 -0
  10. data/doc/cheat_sheet.rdoc +21 -21
  11. data/doc/core_extensions.rdoc +374 -0
  12. data/doc/dataset_basics.rdoc +10 -10
  13. data/doc/dataset_filtering.rdoc +54 -42
  14. data/doc/mass_assignment.rdoc +56 -0
  15. data/doc/migration.rdoc +89 -557
  16. data/doc/model_hooks.rdoc +80 -29
  17. data/doc/object_model.rdoc +576 -0
  18. data/doc/opening_databases.rdoc +121 -53
  19. data/doc/prepared_statements.rdoc +41 -15
  20. data/doc/querying.rdoc +137 -56
  21. data/doc/reflection.rdoc +36 -10
  22. data/doc/release_notes/3.22.0.txt +39 -0
  23. data/doc/release_notes/3.23.0.txt +172 -0
  24. data/doc/release_notes/3.24.0.txt +420 -0
  25. data/doc/release_notes/3.25.0.txt +88 -0
  26. data/doc/release_notes/3.26.0.txt +88 -0
  27. data/doc/release_notes/3.27.0.txt +82 -0
  28. data/doc/release_notes/3.28.0.txt +304 -0
  29. data/doc/release_notes/3.29.0.txt +459 -0
  30. data/doc/release_notes/3.30.0.txt +135 -0
  31. data/doc/release_notes/3.31.0.txt +146 -0
  32. data/doc/release_notes/3.32.0.txt +202 -0
  33. data/doc/release_notes/3.33.0.txt +157 -0
  34. data/doc/release_notes/3.34.0.txt +671 -0
  35. data/doc/release_notes/3.35.0.txt +144 -0
  36. data/doc/release_notes/3.36.0.txt +245 -0
  37. data/doc/release_notes/3.37.0.txt +338 -0
  38. data/doc/release_notes/3.38.0.txt +234 -0
  39. data/doc/release_notes/3.39.0.txt +237 -0
  40. data/doc/release_notes/3.40.0.txt +73 -0
  41. data/doc/release_notes/3.41.0.txt +155 -0
  42. data/doc/schema_modification.rdoc +585 -0
  43. data/doc/sharding.rdoc +99 -8
  44. data/doc/sql.rdoc +154 -112
  45. data/doc/testing.rdoc +169 -0
  46. data/doc/thread_safety.rdoc +17 -0
  47. data/doc/transactions.rdoc +137 -0
  48. data/doc/validations.rdoc +1 -1
  49. data/doc/virtual_rows.rdoc +78 -43
  50. data/lib/sequel/adapters/ado/access.rb +334 -0
  51. data/lib/sequel/adapters/ado/mssql.rb +18 -12
  52. data/lib/sequel/adapters/ado.rb +69 -21
  53. data/lib/sequel/adapters/amalgalite.rb +17 -14
  54. data/lib/sequel/adapters/cubrid.rb +143 -0
  55. data/lib/sequel/adapters/db2.rb +178 -85
  56. data/lib/sequel/adapters/dbi.rb +19 -21
  57. data/lib/sequel/adapters/do/mysql.rb +17 -11
  58. data/lib/sequel/adapters/do/postgres.rb +2 -61
  59. data/lib/sequel/adapters/do/sqlite.rb +0 -10
  60. data/lib/sequel/adapters/do.rb +20 -39
  61. data/lib/sequel/adapters/firebird.rb +33 -210
  62. data/lib/sequel/adapters/ibmdb.rb +452 -0
  63. data/lib/sequel/adapters/informix.rb +6 -29
  64. data/lib/sequel/adapters/jdbc/as400.rb +16 -34
  65. data/lib/sequel/adapters/jdbc/cubrid.rb +52 -0
  66. data/lib/sequel/adapters/jdbc/db2.rb +78 -0
  67. data/lib/sequel/adapters/jdbc/derby.rb +325 -0
  68. data/lib/sequel/adapters/jdbc/firebird.rb +24 -0
  69. data/lib/sequel/adapters/jdbc/h2.rb +68 -31
  70. data/lib/sequel/adapters/jdbc/hsqldb.rb +189 -0
  71. data/lib/sequel/adapters/jdbc/informix.rb +21 -0
  72. data/lib/sequel/adapters/jdbc/jtds.rb +40 -0
  73. data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
  74. data/lib/sequel/adapters/jdbc/mysql.rb +16 -28
  75. data/lib/sequel/adapters/jdbc/oracle.rb +88 -25
  76. data/lib/sequel/adapters/jdbc/postgresql.rb +77 -52
  77. data/lib/sequel/adapters/jdbc/progress.rb +21 -0
  78. data/lib/sequel/adapters/jdbc/sqlite.rb +20 -10
  79. data/lib/sequel/adapters/jdbc/sqlserver.rb +72 -0
  80. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  81. data/lib/sequel/adapters/jdbc.rb +308 -114
  82. data/lib/sequel/adapters/mock.rb +372 -0
  83. data/lib/sequel/adapters/mysql.rb +130 -230
  84. data/lib/sequel/adapters/mysql2.rb +64 -63
  85. data/lib/sequel/adapters/odbc/mssql.rb +17 -9
  86. data/lib/sequel/adapters/odbc.rb +29 -10
  87. data/lib/sequel/adapters/openbase.rb +7 -11
  88. data/lib/sequel/adapters/oracle.rb +364 -57
  89. data/lib/sequel/adapters/postgres.rb +379 -145
  90. data/lib/sequel/adapters/shared/access.rb +236 -11
  91. data/lib/sequel/adapters/shared/cubrid.rb +216 -0
  92. data/lib/sequel/adapters/shared/db2.rb +353 -0
  93. data/lib/sequel/adapters/shared/firebird.rb +231 -0
  94. data/lib/sequel/adapters/shared/informix.rb +53 -0
  95. data/lib/sequel/adapters/shared/mssql.rb +412 -116
  96. data/lib/sequel/adapters/shared/mysql.rb +396 -89
  97. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +166 -0
  98. data/lib/sequel/adapters/shared/oracle.rb +262 -45
  99. data/lib/sequel/adapters/shared/postgres.rb +688 -279
  100. data/lib/sequel/adapters/shared/progress.rb +5 -8
  101. data/lib/sequel/adapters/shared/sqlite.rb +317 -96
  102. data/lib/sequel/adapters/sqlite.rb +109 -46
  103. data/lib/sequel/adapters/swift/mysql.rb +10 -12
  104. data/lib/sequel/adapters/swift/postgres.rb +13 -79
  105. data/lib/sequel/adapters/swift/sqlite.rb +9 -6
  106. data/lib/sequel/adapters/swift.rb +31 -27
  107. data/lib/sequel/adapters/tinytds.rb +177 -25
  108. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +77 -0
  109. data/lib/sequel/adapters/utils/pg_types.rb +81 -0
  110. data/lib/sequel/adapters/utils/split_alter_table.rb +36 -0
  111. data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
  112. data/lib/sequel/ast_transformer.rb +194 -0
  113. data/lib/sequel/connection_pool/sharded_single.rb +23 -13
  114. data/lib/sequel/connection_pool/sharded_threaded.rb +64 -13
  115. data/lib/sequel/connection_pool/single.rb +11 -3
  116. data/lib/sequel/connection_pool/threaded.rb +69 -20
  117. data/lib/sequel/connection_pool.rb +20 -18
  118. data/lib/sequel/core.rb +143 -11
  119. data/lib/sequel/database/connecting.rb +66 -29
  120. data/lib/sequel/database/dataset.rb +4 -4
  121. data/lib/sequel/database/dataset_defaults.rb +63 -2
  122. data/lib/sequel/database/logging.rb +7 -2
  123. data/lib/sequel/database/misc.rb +192 -51
  124. data/lib/sequel/database/query.rb +299 -114
  125. data/lib/sequel/database/schema_generator.rb +103 -36
  126. data/lib/sequel/database/schema_methods.rb +310 -69
  127. data/lib/sequel/dataset/actions.rb +344 -90
  128. data/lib/sequel/dataset/features.rb +101 -8
  129. data/lib/sequel/dataset/graph.rb +42 -15
  130. data/lib/sequel/dataset/misc.rb +52 -25
  131. data/lib/sequel/dataset/mutation.rb +12 -7
  132. data/lib/sequel/dataset/prepared_statements.rb +68 -30
  133. data/lib/sequel/dataset/query.rb +322 -68
  134. data/lib/sequel/dataset/sql.rb +716 -293
  135. data/lib/sequel/dataset.rb +8 -0
  136. data/lib/sequel/exceptions.rb +4 -0
  137. data/lib/sequel/extensions/_pretty_table.rb +83 -0
  138. data/lib/sequel/extensions/arbitrary_servers.rb +109 -0
  139. data/lib/sequel/extensions/blank.rb +4 -0
  140. data/lib/sequel/extensions/columns_introspection.rb +72 -0
  141. data/lib/sequel/extensions/connection_validator.rb +109 -0
  142. data/lib/sequel/extensions/constraint_validations.rb +451 -0
  143. data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +30 -46
  144. data/lib/sequel/extensions/eval_inspect.rb +173 -0
  145. data/lib/sequel/extensions/inflector.rb +4 -0
  146. data/lib/sequel/extensions/looser_typecasting.rb +5 -4
  147. data/lib/sequel/extensions/migration.rb +100 -11
  148. data/lib/sequel/extensions/named_timezones.rb +9 -0
  149. data/lib/sequel/extensions/null_dataset.rb +94 -0
  150. data/lib/sequel/extensions/pagination.rb +4 -0
  151. data/lib/sequel/extensions/pg_array.rb +539 -0
  152. data/lib/sequel/extensions/pg_array_ops.rb +263 -0
  153. data/lib/sequel/extensions/pg_auto_parameterize.rb +175 -0
  154. data/lib/sequel/extensions/pg_hstore.rb +334 -0
  155. data/lib/sequel/extensions/pg_hstore_ops.rb +293 -0
  156. data/lib/sequel/extensions/pg_inet.rb +113 -0
  157. data/lib/sequel/extensions/pg_interval.rb +191 -0
  158. data/lib/sequel/extensions/pg_json.rb +212 -0
  159. data/lib/sequel/extensions/pg_range.rb +513 -0
  160. data/lib/sequel/extensions/pg_range_ops.rb +150 -0
  161. data/lib/sequel/extensions/pg_row.rb +576 -0
  162. data/lib/sequel/extensions/pg_row_ops.rb +182 -0
  163. data/lib/sequel/extensions/pg_statement_cache.rb +317 -0
  164. data/lib/sequel/extensions/pretty_table.rb +17 -72
  165. data/lib/sequel/extensions/query.rb +8 -4
  166. data/lib/sequel/extensions/query_literals.rb +79 -0
  167. data/lib/sequel/extensions/schema_caching.rb +76 -0
  168. data/lib/sequel/extensions/schema_dumper.rb +282 -76
  169. data/lib/sequel/extensions/select_remove.rb +39 -0
  170. data/lib/sequel/extensions/server_block.rb +140 -0
  171. data/lib/sequel/extensions/split_array_nil.rb +65 -0
  172. data/lib/sequel/extensions/sql_expr.rb +8 -110
  173. data/lib/sequel/extensions/string_date_time.rb +4 -0
  174. data/lib/sequel/extensions/thread_local_timezones.rb +10 -4
  175. data/lib/sequel/extensions/to_dot.rb +99 -83
  176. data/lib/sequel/model/associations.rb +1263 -409
  177. data/lib/sequel/model/base.rb +624 -171
  178. data/lib/sequel/model/errors.rb +1 -1
  179. data/lib/sequel/model/exceptions.rb +24 -2
  180. data/lib/sequel/model/inflections.rb +1 -1
  181. data/lib/sequel/model.rb +30 -11
  182. data/lib/sequel/no_core_ext.rb +2 -0
  183. data/lib/sequel/plugins/active_model.rb +13 -1
  184. data/lib/sequel/plugins/association_pks.rb +137 -16
  185. data/lib/sequel/plugins/caching.rb +25 -18
  186. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  187. data/lib/sequel/plugins/composition.rb +44 -12
  188. data/lib/sequel/plugins/constraint_validations.rb +198 -0
  189. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  190. data/lib/sequel/plugins/defaults_setter.rb +72 -0
  191. data/lib/sequel/plugins/dirty.rb +214 -0
  192. data/lib/sequel/plugins/eager_each.rb +59 -0
  193. data/lib/sequel/plugins/force_encoding.rb +6 -6
  194. data/lib/sequel/plugins/hook_class_methods.rb +1 -1
  195. data/lib/sequel/plugins/identity_map.rb +134 -15
  196. data/lib/sequel/plugins/instance_filters.rb +10 -0
  197. data/lib/sequel/plugins/instance_hooks.rb +1 -1
  198. data/lib/sequel/plugins/json_serializer.rb +77 -6
  199. data/lib/sequel/plugins/list.rb +13 -2
  200. data/lib/sequel/plugins/many_through_many.rb +103 -51
  201. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
  202. data/lib/sequel/plugins/nested_attributes.rb +150 -66
  203. data/lib/sequel/plugins/optimistic_locking.rb +8 -0
  204. data/lib/sequel/plugins/pg_row.rb +121 -0
  205. data/lib/sequel/plugins/pg_typecast_on_load.rb +65 -0
  206. data/lib/sequel/plugins/prepared_statements.rb +168 -0
  207. data/lib/sequel/plugins/prepared_statements_associations.rb +87 -0
  208. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  209. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  210. data/lib/sequel/plugins/rcte_tree.rb +31 -17
  211. data/lib/sequel/plugins/schema.rb +8 -3
  212. data/lib/sequel/plugins/serialization.rb +98 -49
  213. data/lib/sequel/plugins/serialization_modification_detection.rb +63 -0
  214. data/lib/sequel/plugins/sharding.rb +21 -54
  215. data/lib/sequel/plugins/single_table_inheritance.rb +57 -12
  216. data/lib/sequel/plugins/static_cache.rb +99 -0
  217. data/lib/sequel/plugins/subclasses.rb +29 -3
  218. data/lib/sequel/plugins/tactical_eager_loading.rb +7 -7
  219. data/lib/sequel/plugins/timestamps.rb +1 -1
  220. data/lib/sequel/plugins/touch.rb +18 -6
  221. data/lib/sequel/plugins/tree.rb +3 -3
  222. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  223. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  224. data/lib/sequel/plugins/validation_class_methods.rb +2 -1
  225. data/lib/sequel/plugins/validation_helpers.rb +57 -4
  226. data/lib/sequel/plugins/xml_serializer.rb +15 -4
  227. data/lib/sequel/sql.rb +721 -141
  228. data/lib/sequel/timezones.rb +67 -40
  229. data/lib/sequel/version.rb +1 -1
  230. data/spec/adapters/db2_spec.rb +146 -0
  231. data/spec/adapters/firebird_spec.rb +53 -39
  232. data/spec/adapters/mssql_spec.rb +261 -66
  233. data/spec/adapters/mysql_spec.rb +597 -352
  234. data/spec/adapters/oracle_spec.rb +92 -117
  235. data/spec/adapters/postgres_spec.rb +2103 -341
  236. data/spec/adapters/spec_helper.rb +16 -6
  237. data/spec/adapters/sqlite_spec.rb +237 -76
  238. data/spec/core/connection_pool_spec.rb +270 -141
  239. data/spec/core/database_spec.rb +980 -524
  240. data/spec/core/dataset_spec.rb +1658 -1243
  241. data/spec/core/expression_filters_spec.rb +741 -334
  242. data/spec/core/mock_adapter_spec.rb +453 -0
  243. data/spec/core/object_graph_spec.rb +88 -129
  244. data/spec/core/schema_generator_spec.rb +4 -4
  245. data/spec/core/schema_spec.rb +556 -53
  246. data/spec/core/spec_helper.rb +7 -48
  247. data/spec/core_extensions_spec.rb +626 -0
  248. data/spec/extensions/active_model_spec.rb +13 -0
  249. data/spec/extensions/arbitrary_servers_spec.rb +110 -0
  250. data/spec/extensions/association_autoreloading_spec.rb +18 -10
  251. data/spec/extensions/association_dependencies_spec.rb +15 -25
  252. data/spec/extensions/association_pks_spec.rb +227 -33
  253. data/spec/extensions/association_proxies_spec.rb +4 -4
  254. data/spec/extensions/boolean_readers_spec.rb +25 -25
  255. data/spec/extensions/caching_spec.rb +47 -51
  256. data/spec/extensions/class_table_inheritance_spec.rb +31 -83
  257. data/spec/extensions/columns_introspection_spec.rb +91 -0
  258. data/spec/extensions/composition_spec.rb +18 -13
  259. data/spec/extensions/connection_validator_spec.rb +118 -0
  260. data/spec/extensions/constraint_validations_plugin_spec.rb +196 -0
  261. data/spec/extensions/constraint_validations_spec.rb +316 -0
  262. data/spec/extensions/dataset_associations_spec.rb +199 -0
  263. data/spec/extensions/defaults_setter_spec.rb +88 -0
  264. data/spec/extensions/dirty_spec.rb +155 -0
  265. data/spec/extensions/eager_each_spec.rb +34 -0
  266. data/spec/extensions/eval_inspect_spec.rb +67 -0
  267. data/spec/extensions/force_encoding_spec.rb +4 -2
  268. data/spec/extensions/hook_class_methods_spec.rb +97 -128
  269. data/spec/extensions/identity_map_spec.rb +142 -24
  270. data/spec/extensions/inflector_spec.rb +0 -4
  271. data/spec/extensions/instance_filters_spec.rb +11 -21
  272. data/spec/extensions/instance_hooks_spec.rb +72 -0
  273. data/spec/extensions/json_serializer_spec.rb +49 -12
  274. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  275. data/spec/extensions/list_spec.rb +49 -40
  276. data/spec/extensions/looser_typecasting_spec.rb +7 -7
  277. data/spec/extensions/many_through_many_spec.rb +368 -254
  278. data/spec/extensions/many_to_one_pk_lookup_spec.rb +140 -0
  279. data/spec/extensions/migration_spec.rb +140 -35
  280. data/spec/extensions/named_timezones_spec.rb +29 -11
  281. data/spec/extensions/nested_attributes_spec.rb +268 -89
  282. data/spec/extensions/null_dataset_spec.rb +85 -0
  283. data/spec/extensions/optimistic_locking_spec.rb +24 -21
  284. data/spec/extensions/pg_array_ops_spec.rb +112 -0
  285. data/spec/extensions/pg_array_spec.rb +324 -0
  286. data/spec/extensions/pg_auto_parameterize_spec.rb +65 -0
  287. data/spec/extensions/pg_hstore_ops_spec.rb +140 -0
  288. data/spec/extensions/pg_hstore_spec.rb +200 -0
  289. data/spec/extensions/pg_inet_spec.rb +47 -0
  290. data/spec/extensions/pg_interval_spec.rb +72 -0
  291. data/spec/extensions/pg_json_spec.rb +99 -0
  292. data/spec/extensions/pg_range_ops_spec.rb +56 -0
  293. data/spec/extensions/pg_range_spec.rb +395 -0
  294. data/spec/extensions/pg_row_ops_spec.rb +58 -0
  295. data/spec/extensions/pg_row_plugin_spec.rb +49 -0
  296. data/spec/extensions/pg_row_spec.rb +340 -0
  297. data/spec/extensions/pg_statement_cache_spec.rb +208 -0
  298. data/spec/extensions/pg_typecast_on_load_spec.rb +58 -0
  299. data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
  300. data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
  301. data/spec/extensions/prepared_statements_spec.rb +87 -0
  302. data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
  303. data/spec/extensions/pretty_table_spec.rb +7 -6
  304. data/spec/extensions/query_literals_spec.rb +167 -0
  305. data/spec/extensions/query_spec.rb +9 -3
  306. data/spec/extensions/rcte_tree_spec.rb +50 -43
  307. data/spec/extensions/schema_caching_spec.rb +41 -0
  308. data/spec/extensions/schema_dumper_spec.rb +434 -49
  309. data/spec/extensions/schema_spec.rb +30 -49
  310. data/spec/extensions/select_remove_spec.rb +38 -0
  311. data/spec/extensions/serialization_modification_detection_spec.rb +72 -0
  312. data/spec/extensions/serialization_spec.rb +86 -48
  313. data/spec/extensions/server_block_spec.rb +90 -0
  314. data/spec/extensions/sharding_spec.rb +69 -143
  315. data/spec/extensions/single_table_inheritance_spec.rb +65 -29
  316. data/spec/extensions/skip_create_refresh_spec.rb +1 -1
  317. data/spec/extensions/spec_helper.rb +34 -67
  318. data/spec/extensions/split_array_nil_spec.rb +24 -0
  319. data/spec/extensions/sql_expr_spec.rb +29 -60
  320. data/spec/extensions/static_cache_spec.rb +145 -0
  321. data/spec/extensions/subclasses_spec.rb +14 -0
  322. data/spec/extensions/tactical_eager_loading_spec.rb +17 -19
  323. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  324. data/spec/extensions/timestamps_spec.rb +6 -6
  325. data/spec/extensions/to_dot_spec.rb +8 -11
  326. data/spec/extensions/touch_spec.rb +53 -14
  327. data/spec/extensions/tree_spec.rb +11 -26
  328. data/spec/extensions/typecast_on_load_spec.rb +9 -6
  329. data/spec/extensions/update_primary_key_spec.rb +30 -24
  330. data/spec/extensions/validation_class_methods_spec.rb +77 -68
  331. data/spec/extensions/validation_helpers_spec.rb +48 -37
  332. data/spec/extensions/xml_serializer_spec.rb +29 -4
  333. data/spec/files/transaction_specified_migrations/001_create_alt_basic.rb +4 -0
  334. data/spec/files/transaction_specified_migrations/002_create_basic.rb +4 -0
  335. data/spec/files/transaction_unspecified_migrations/001_create_alt_basic.rb +3 -0
  336. data/spec/files/transaction_unspecified_migrations/002_create_basic.rb +3 -0
  337. data/spec/integration/associations_test.rb +659 -21
  338. data/spec/integration/database_test.rb +25 -5
  339. data/spec/integration/dataset_test.rb +729 -141
  340. data/spec/integration/eager_loader_test.rb +38 -54
  341. data/spec/integration/migrator_test.rb +2 -3
  342. data/spec/integration/model_test.rb +81 -2
  343. data/spec/integration/plugin_test.rb +871 -160
  344. data/spec/integration/prepared_statement_test.rb +272 -128
  345. data/spec/integration/schema_test.rb +377 -57
  346. data/spec/integration/spec_helper.rb +49 -32
  347. data/spec/integration/timezone_test.rb +38 -12
  348. data/spec/integration/transaction_test.rb +183 -8
  349. data/spec/integration/type_test.rb +40 -8
  350. data/spec/model/association_reflection_spec.rb +207 -8
  351. data/spec/model/associations_spec.rb +1206 -693
  352. data/spec/model/base_spec.rb +334 -100
  353. data/spec/model/dataset_methods_spec.rb +45 -27
  354. data/spec/model/eager_loading_spec.rb +828 -692
  355. data/spec/model/hooks_spec.rb +355 -67
  356. data/spec/model/model_spec.rb +251 -179
  357. data/spec/model/plugins_spec.rb +24 -13
  358. data/spec/model/record_spec.rb +506 -232
  359. data/spec/model/spec_helper.rb +16 -77
  360. data/spec/model/validations_spec.rb +38 -2
  361. data/spec/rcov.opts +2 -0
  362. metadata +223 -81
  363. data/spec/core/core_sql_spec.rb +0 -451
@@ -22,10 +22,7 @@ module Sequel
22
22
  # Identity maps are thread-local and only persist for the duration of the block,
23
23
  # so they should only be considered as a possible performance enhancer.
24
24
  #
25
- # The identity_map plugin is not compatible with the standard eager loading of
26
- # many_to_many and many_through_many associations. If you want to use the identity_map plugin,
27
- # you should use +eager_graph+ instead of +eager+ for those associations. It is also
28
- # not compatible with the eager loading in the +rcte_tree+ plugin.
25
+ # The identity_map plugin is not compatible with the eager loading in the +rcte_tree+ plugin.
29
26
  #
30
27
  # Usage:
31
28
  #
@@ -37,6 +34,116 @@ module Sequel
37
34
  # # would need to do Album.with_identity_map{} to use the identity map
38
35
  module IdentityMap
39
36
  module ClassMethods
37
+ # Override the default :eager_loader option for many_*_many associations to work
38
+ # with an identity_map. If the :eager_graph association option is used, you'll probably have to use
39
+ # :uniq=>true on the current association amd :cartesian_product_number=>2 on the association
40
+ # mentioned by :eager_graph, otherwise you'll end up with duplicates because the row proc will be
41
+ # getting called multiple times for the same object. If you do have duplicates and you use :eager_graph,
42
+ # they'll probably be lost. Making that work correctly would require changing a lot of the core
43
+ # architecture, such as how graphing and eager graphing work.
44
+ def associate(type, name, opts = {}, &block)
45
+ if opts[:eager_loader]
46
+ super
47
+ elsif type == :many_to_many
48
+ opts = super
49
+ el = opts[:eager_loader]
50
+ model = self
51
+ left_pk = opts[:left_primary_key]
52
+ uses_lcks = opts[:uses_left_composite_keys]
53
+ uses_rcks = opts[:uses_right_composite_keys]
54
+ right = opts[:right_key]
55
+ join_table = opts[:join_table]
56
+ left = opts[:left_key]
57
+ lcks = opts[:left_keys]
58
+ left_key_alias = opts[:left_key_alias] ||= opts.default_associated_key_alias
59
+ opts[:eager_loader] = lambda do |eo|
60
+ return el.call(eo) unless model.identity_map
61
+ h = eo[:id_map]
62
+ eo[:rows].each{|object| object.associations[name] = []}
63
+ r = uses_rcks ? rcks.zip(opts.right_primary_keys) : [[right, opts.right_primary_key]]
64
+ l = uses_lcks ? [[lcks.map{|k| SQL::QualifiedIdentifier.new(join_table, k)}, h.keys]] : [[left, h.keys]]
65
+
66
+ # Replace the row proc to remove the left key alias before calling the previous row proc.
67
+ # Associate the value of the left key alias with the associated object (through its object_id).
68
+ # When loading the associated objects, lookup the left key alias value and associate the
69
+ # associated objects to the main objects if the left key alias value matches the left primary key
70
+ # value of the main object.
71
+ #
72
+ # The deleting of the left key alias from the hash before calling the previous row proc
73
+ # is necessary when an identity map is used, otherwise if the same associated object is returned more than
74
+ # once for the association, it won't know which of current objects to associate it to.
75
+ ds = opts.associated_class.inner_join(join_table, r + l)
76
+ pr = ds.row_proc
77
+ h2 = {}
78
+ ds.row_proc = proc do |hash|
79
+ hash_key = if uses_lcks
80
+ left_key_alias.map{|k| hash.delete(k)}
81
+ else
82
+ hash.delete(left_key_alias)
83
+ end
84
+ obj = pr.call(hash)
85
+ (h2[obj.object_id] ||= []) << hash_key
86
+ obj
87
+ end
88
+ model.eager_loading_dataset(opts, ds, Array(opts.select), eo[:associations], eo) .all do |assoc_record|
89
+ if hash_keys = h2.delete(assoc_record.object_id)
90
+ hash_keys.each do |hash_key|
91
+ if objects = h[hash_key]
92
+ objects.each{|object| object.associations[name].push(assoc_record)}
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ opts
99
+ elsif type == :many_through_many
100
+ opts = super
101
+ el = opts[:eager_loader]
102
+ model = self
103
+ left_pk = opts[:left_primary_key]
104
+ left_key = opts[:left_key]
105
+ uses_lcks = opts[:uses_left_composite_keys]
106
+ left_keys = Array(left_key)
107
+ left_key_alias = opts[:left_key_alias]
108
+ opts[:eager_loader] = lambda do |eo|
109
+ return el.call(eo) unless model.identity_map
110
+ h = eo[:id_map]
111
+ eo[:rows].each{|object| object.associations[name] = []}
112
+ ds = opts.associated_class
113
+ opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
114
+ ft = opts.final_reverse_edge
115
+ conds = uses_lcks ? [[left_keys.map{|k| SQL::QualifiedIdentifier.new(ft[:table], k)}, h.keys]] : [[left_key, h.keys]]
116
+
117
+ # See above comment in many_to_many eager_loader
118
+ ds = ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + conds, :table_alias=>ft[:alias])
119
+ pr = ds.row_proc
120
+ h2 = {}
121
+ ds.row_proc = proc do |hash|
122
+ hash_key = if uses_lcks
123
+ left_key_alias.map{|k| hash.delete(k)}
124
+ else
125
+ hash.delete(left_key_alias)
126
+ end
127
+ obj = pr.call(hash)
128
+ (h2[obj.object_id] ||= []) << hash_key
129
+ obj
130
+ end
131
+ model.eager_loading_dataset(opts, ds, Array(opts.select), eo[:associations], eo).all do |assoc_record|
132
+ if hash_keys = h2.delete(assoc_record.object_id)
133
+ hash_keys.each do |hash_key|
134
+ if objects = h[hash_key]
135
+ objects.each{|object| object.associations[name].push(assoc_record)}
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
141
+ opts
142
+ else
143
+ super
144
+ end
145
+ end
146
+
40
147
  # Returns the current thread-local identity map. Should be a hash if
41
148
  # there is an active identity map, and nil otherwise.
42
149
  def identity_map
@@ -46,7 +153,8 @@ module Sequel
46
153
  # The identity map key for an object of the current class with the given pk.
47
154
  # May not always be correct for a class which uses STI.
48
155
  def identity_map_key(pk)
49
- "#{self}:#{pk ? Array(pk).join(',') : "nil:#{rand}"}"
156
+ pk = Array(pk)
157
+ "#{self}:#{pk.join(',')}" unless pk.compact.empty?
50
158
  end
51
159
 
52
160
  # If the identity map is in use, check it for a current copy of the object.
@@ -56,13 +164,15 @@ module Sequel
56
164
  # certain fields in an initial query, make modifications to some of those
57
165
  # fields and request other, potentially overlapping fields in a new query,
58
166
  # and not have the second query override fields you modified.
59
- def load(row)
60
- return super unless idm = identity_map
61
- if o = idm[identity_map_key(Array(primary_key).map{|x| row[x]})]
167
+ def call(row)
168
+ return super unless (idm = identity_map) && (pk = primary_key)
169
+ if (k = identity_map_key(Array(pk).map{|x| row[x]})) && (o = idm[k])
62
170
  o.merge_db_update(row)
63
171
  else
64
172
  o = super
65
- idm[identity_map_key(o.pk)] = o
173
+ if (k = identity_map_key(o.pk))
174
+ idm[k] = o
175
+ end
66
176
  end
67
177
  o
68
178
  end
@@ -89,7 +199,7 @@ module Sequel
89
199
  # Check the current identity map if it exists for the object with
90
200
  # the matching pk. If one is found, return it, otherwise call super.
91
201
  def primary_key_lookup(pk)
92
- (idm = identity_map and o = idm[identity_map_key(pk)]) ? o : super
202
+ ((idm = identity_map) && (k = identity_map_key(pk)) && (o = idm[k])) ? o : super
93
203
  end
94
204
  end
95
205
 
@@ -97,8 +207,8 @@ module Sequel
97
207
  # Remove instances from the identity map cache if they are deleted.
98
208
  def delete
99
209
  super
100
- if idm = model.identity_map
101
- idm.delete(model.identity_map_key(pk))
210
+ if (idm = model.identity_map) && (k = model.identity_map_key(pk))
211
+ idm.delete(k)
102
212
  end
103
213
  self
104
214
  end
@@ -121,10 +231,19 @@ module Sequel
121
231
  # key option has a value and the association uses the primary key of
122
232
  # the associated class as the :primary_key option, check the identity
123
233
  # map for the associated object and return it if present.
124
- def _load_associated_objects(opts)
234
+ def _load_associated_object(opts, dynamic_opts)
125
235
  klass = opts.associated_class
126
- if klass.respond_to?(:identity_map) && idm = klass.identity_map and opts[:type] == :many_to_one and opts[:primary_key] == klass.primary_key and
127
- opts[:key] and pk = _associated_object_pk(opts[:key]) and o = idm[klass.identity_map_key(pk)]
236
+ cache_lookup = opts.fetch(:idm_cache_lookup) do
237
+ opts[:idm_cache_lookup] = klass.respond_to?(:identity_map) &&
238
+ opts[:type] == :many_to_one &&
239
+ opts[:key] &&
240
+ opts.primary_key == klass.primary_key
241
+ end
242
+ if cache_lookup &&
243
+ !dynamic_opts[:callback] &&
244
+ (idm = klass.identity_map) &&
245
+ (k = klass.identity_map_key(_associated_object_pk(opts[:key]))) &&
246
+ (o = idm[k])
128
247
  o
129
248
  else
130
249
  super
@@ -68,6 +68,16 @@ module Sequel
68
68
 
69
69
  private
70
70
 
71
+ # If there are any instance filters, make sure not to use the
72
+ # instance delete optimization.
73
+ def _delete_without_checking
74
+ if @instance_filters && !@instance_filters.empty?
75
+ _delete_dataset.delete
76
+ else
77
+ super
78
+ end
79
+ end
80
+
71
81
  # Lazily initialize the instance filter array.
72
82
  def instance_filters
73
83
  @instance_filters ||= []
@@ -29,7 +29,7 @@ module Sequel
29
29
  BEFORE_HOOKS = Sequel::Model::BEFORE_HOOKS
30
30
  AFTER_HOOKS = Sequel::Model::AFTER_HOOKS - [:after_initialize]
31
31
  HOOKS = BEFORE_HOOKS + AFTER_HOOKS
32
- HOOKS.each{|h| class_eval("def #{h}_hook(&block); add_instance_hook(:#{h}, &block) end", __FILE__, __LINE__)}
32
+ HOOKS.each{|h| class_eval("def #{h}_hook(&block); add_instance_hook(:#{h}, &block); self end", __FILE__, __LINE__)}
33
33
 
34
34
  BEFORE_HOOKS.each{|h| class_eval("def #{h}; run_before_instance_hooks(:#{h}) == false ? false : super end", __FILE__, __LINE__)}
35
35
  AFTER_HOOKS.each{|h| class_eval(<<-END, __FILE__, __LINE__ + 1)}
@@ -56,6 +56,30 @@ module Sequel
56
56
  # Album.to_json
57
57
  # Album.filter(:artist_id=>1).to_json(:include=>:tags)
58
58
  #
59
+ # If you have an existing array of model instances you want to convert to
60
+ # JSON, you can call the class to_json method with the :array option:
61
+ #
62
+ # Album.to_json(:array=>[Album[1], Album[2]])
63
+ #
64
+ # Note that active_support/json makes incompatible changes to the to_json API,
65
+ # and breaks some aspects of the json_serializer plugin. You can undo the damage
66
+ # done by active_support/json by doing:
67
+ #
68
+ # class Array
69
+ # def to_json(options = {})
70
+ # JSON.generate(self)
71
+ # end
72
+ # end
73
+ #
74
+ # class Hash
75
+ # def to_json(options = {})
76
+ # JSON.generate(self)
77
+ # end
78
+ # end
79
+ #
80
+ # Note that this will probably cause active_support/json to no longer work
81
+ # correctly in some cases.
82
+ #
59
83
  # Usage:
60
84
  #
61
85
  # # Add JSON output capability to all model subclass instances (called before loading subclasses)
@@ -134,8 +158,13 @@ module Sequel
134
158
  module InstanceMethods
135
159
  # Parse the provided JSON, which should return a hash,
136
160
  # and call +set+ with that hash.
137
- def from_json(json)
138
- set(JSON.parse(json))
161
+ def from_json(json, opts={})
162
+ h = JSON.parse(json)
163
+ if fields = opts[:fields]
164
+ set_fields(h, fields, opts)
165
+ else
166
+ set(h)
167
+ end
139
168
  end
140
169
 
141
170
  # Return a string in JSON format. Accepts the following
@@ -149,7 +178,7 @@ module Sequel
149
178
  # to include in the JSON output. Using a nested
150
179
  # hash, you can pass options to associations
151
180
  # to affect the JSON used for associated objects.
152
- # :naked :: Not to add the JSON.create_id key to the JSON
181
+ # :naked :: Not to add the JSON.create_id (json_class) key to the JSON
153
182
  # output hash, so when the JSON is parsed, it
154
183
  # will yield a hash instead of a model object.
155
184
  # :only :: Symbol or Array of Symbols of columns to only
@@ -195,7 +224,21 @@ module Sequel
195
224
  module DatasetMethods
196
225
  # Return a JSON string representing an array of all objects in
197
226
  # this dataset. Takes the same options as the the instance
198
- # method, and passes them to every instance.
227
+ # method, and passes them to every instance. Additionally,
228
+ # respects the following options:
229
+ #
230
+ # :array :: An array of instances. If this is not provided,
231
+ # calls #all on the receiver to get the array.
232
+ # :root :: If set to :collection, only wraps the collection
233
+ # in a root object. If set to :instance, only wraps
234
+ # the instances in a root object. If set to :both,
235
+ # wraps both the collection and instances in a root
236
+ # object. Unfortunately, for backwards compatibility,
237
+ # if this option is true and doesn't match one of those
238
+ # symbols, it defaults to both. That may change in a
239
+ # future version, so for forwards compatibility, you
240
+ # should pick a specific symbol for your desired
241
+ # behavior.
199
242
  def to_json(*a)
200
243
  if opts = a.first.is_a?(Hash)
201
244
  opts = model.json_serializer_opts.merge(a.first)
@@ -203,8 +246,36 @@ module Sequel
203
246
  else
204
247
  opts = model.json_serializer_opts
205
248
  end
206
- res = row_proc ? all.map{|obj| Literal.new(obj.to_json(opts))} : all
207
- opts[:root] ? {model.send(:pluralize, model.send(:underscore, model.to_s)) => res}.to_json(*a) : res.to_json(*a)
249
+
250
+ collection_root = case opts[:root]
251
+ when nil, false, :instance
252
+ false
253
+ when :collection
254
+ opts = opts.dup
255
+ opts.delete(:root)
256
+ opts[:naked] = true unless opts.has_key?(:naked)
257
+ true
258
+ else
259
+ true
260
+ end
261
+
262
+ res = if row_proc
263
+ array = if opts[:array]
264
+ opts = opts.dup
265
+ opts.delete(:array)
266
+ else
267
+ all
268
+ end
269
+ array.map{|obj| Literal.new(obj.to_json(opts))}
270
+ else
271
+ all
272
+ end
273
+
274
+ if collection_root
275
+ {model.send(:pluralize, model.send(:underscore, model.to_s)) => res}.to_json(*a)
276
+ else
277
+ res.to_json(*a)
278
+ end
208
279
  end
209
280
  end
210
281
  end
@@ -39,9 +39,11 @@ module Sequel
39
39
  # Item.plugin :list, :scope=>:user_id
40
40
  #
41
41
  # Note that using this plugin modifies the order of the model's dataset to
42
- # sort by the position and scope fields.
42
+ # sort by the position and scope fields. Also note that this plugin is subject to
43
+ # race conditions, and is not safe when concurrent modifications are made
44
+ # to the same list.
43
45
  #
44
- # Also note that unlike ruby arrays, the list plugin assumes that the
46
+ # Additionally, note that unlike ruby arrays, the list plugin assumes that the
45
47
  # first entry in the list has position 1, not position 0.
46
48
  #
47
49
  # Copyright (c) 2007-2010 Sharon Rosner, Wayne E. Seguin, Aman Gupta, Adrian Madrid, Jeremy Evans
@@ -90,6 +92,15 @@ module Sequel
90
92
  list_dataset.first(position_field => p)
91
93
  end
92
94
 
95
+ # Set the value of the position_field to the maximum value plus 1 unless the
96
+ # position field already has a value.
97
+ def before_create
98
+ unless send(position_field)
99
+ send("#{position_field}=", list_dataset.max(position_field).to_i+1)
100
+ end
101
+ super
102
+ end
103
+
93
104
  # Find the last position in the list containing this instance.
94
105
  def last_position
95
106
  list_dataset.max(position_field).to_i
@@ -47,20 +47,18 @@ module Sequel
47
47
  class ManyThroughManyAssociationReflection < Sequel::Model::Associations::ManyToManyAssociationReflection
48
48
  Sequel::Model::Associations::ASSOCIATION_TYPES[:many_through_many] = self
49
49
 
50
- # The table containing the column to use for the associated key when eagerly loading
51
- def associated_key_table
52
- self[:associated_key_table] = self[:final_reverse_edge][:alias]
53
- end
54
-
55
50
  # The default associated key alias(es) to use when eager loading
56
51
  # associations via eager.
57
52
  def default_associated_key_alias
58
53
  self[:uses_left_composite_keys] ? (0...self[:through].first[:left].length).map{|i| :"x_foreign_key_#{i}_x"} : :x_foreign_key_x
59
54
  end
60
55
 
61
- # The list of joins to use when eager graphing
62
- def edges
63
- self[:edges] || calculate_edges || self[:edges]
56
+ %w'associated_key_table predicate_key edges final_edge final_reverse_edge reverse_edges'.each do |meth|
57
+ class_eval(<<-END, __FILE__, __LINE__+1)
58
+ def #{meth}
59
+ cached_fetch(:#{meth}){calculate_edges[:#{meth}]}
60
+ end
61
+ END
64
62
  end
65
63
 
66
64
  # Many through many associations don't have a reciprocal
@@ -68,11 +66,6 @@ module Sequel
68
66
  nil
69
67
  end
70
68
 
71
- # The list of joins to use when lazy loading or eager loading
72
- def reverse_edges
73
- self[:reverse_edges] || calculate_edges || self[:reverse_edges]
74
- end
75
-
76
69
  private
77
70
 
78
71
  # Make sure to use unique table aliases when lazy loading or eager loading
@@ -94,7 +87,7 @@ module Sequel
94
87
 
95
88
  # Transform the :through option into a list of edges and reverse edges to use to join tables when loading the association.
96
89
  def calculate_edges
97
- es = [{:left_table=>self[:model].table_name, :left_key=>self[:left_primary_key]}]
90
+ es = [{:left_table=>self[:model].table_name, :left_key=>self[:left_primary_key_column]}]
98
91
  self[:through].each do |t|
99
92
  es.last.merge!(:right_key=>t[:left], :right_table=>t[:table], :join_type=>t[:join_type]||self[:graph_join_type], :conditions=>(t[:conditions]||[]).to_a, :block=>t[:block])
100
93
  es.last[:only_conditions] = t[:only_conditions] if t.include?(:only_conditions)
@@ -109,37 +102,38 @@ module Sequel
109
102
  reverse_edges = es.reverse.map{|e| {:table=>e[:left_table], :left=>e[:left_key], :right=>e[:right_key]}}
110
103
  reverse_edges.pop
111
104
  calculate_reverse_edge_aliases(reverse_edges)
112
- self[:final_edge] = edges.pop
113
- self[:final_reverse_edge] = reverse_edges.pop
114
- self[:edges] = edges
115
- self[:reverse_edges] = reverse_edges
116
- nil
105
+ final_reverse_edge = reverse_edges.pop
106
+ final_reverse_alias = final_reverse_edge[:alias]
107
+
108
+ h = {:final_edge=>edges.pop,
109
+ :final_reverse_edge=>final_reverse_edge,
110
+ :edges=>edges,
111
+ :reverse_edges=>reverse_edges,
112
+ :predicate_key=>qualify(final_reverse_alias, edges.first[:right]),
113
+ :associated_key_table=>final_reverse_edge[:alias],
114
+ }
115
+ h.each{|k, v| cached_set(k, v)}
116
+ h
117
117
  end
118
118
  end
119
+
119
120
  module ClassMethods
120
121
  # Create a many_through_many association. Arguments:
121
122
  # * name - Same as associate, the name of the association.
122
123
  # * through - The tables and keys to join between the current table and the associated table.
123
124
  # Must be an array, with elements that are either 3 element arrays, or hashes with keys :table, :left, and :right.
124
125
  # The required entries in the array/hash are:
125
- # * :table (first array element) - The name of the table to join.
126
- # * :left (middle array element) - The key joining the table to the previous table. Can use an
127
- # array of symbols for a composite key association.
128
- # * :right (last array element) - The key joining the table to the next table. Can use an
129
- # array of symbols for a composite key association.
126
+ # :table (first array element) :: The name of the table to join.
127
+ # :left (middle array element) :: The key joining the table to the previous table. Can use an
128
+ # array of symbols for a composite key association.
129
+ # :right (last array element) :: The key joining the table to the next table. Can use an
130
+ # array of symbols for a composite key association.
130
131
  # If a hash is provided, the following keys are respected when using eager_graph:
131
- # * :block - A proc to use as the block argument to join.
132
- # * :conditions - Extra conditions to add to the JOIN ON clause. Must be a hash or array of two pairs.
133
- # * :join_type - The join type to use for the join, defaults to :left_outer.
134
- # * :only_conditions - Conditions to use for the join instead of the ones specified by the keys.
135
- # * opts - The options for the associaion. Takes the same options as associate, and supports these additional options:
136
- # * :left_primary_key - column in current table that the first :left option in
137
- # through points to, as a symbol. Defaults to primary key of current table. Can use an
138
- # array of symbols for a composite key association.
139
- # * :right_primary_key - column in associated table that the final :right option in
140
- # through points to, as a symbol. Defaults to primary key of the associated table. Can use an
141
- # array of symbols for a composite key association.
142
- # * :uniq - Adds a after_load callback that makes the array of objects unique.
132
+ # :block :: A proc to use as the block argument to join.
133
+ # :conditions :: Extra conditions to add to the JOIN ON clause. Must be a hash or array of two pairs.
134
+ # :join_type :: The join type to use for the join, defaults to :left_outer.
135
+ # :only_conditions :: Conditions to use for the join instead of the ones specified by the keys.
136
+ # * opts - The options for the associaion. Takes the same options as many_to_many.
143
137
  def many_through_many(name, through, opts={}, &block)
144
138
  associate(:many_through_many, name, opts.merge(through.is_a?(Hash) ? through : {:through=>through}), &block)
145
139
  end
@@ -170,24 +164,41 @@ module Sequel
170
164
  uses_lcks = opts[:uses_left_composite_keys] = left_key.is_a?(Array)
171
165
  left_keys = Array(left_key)
172
166
  left_pk = (opts[:left_primary_key] ||= self.primary_key)
167
+ opts[:eager_loader_key] = left_pk unless opts.has_key?(:eager_loader_key)
173
168
  left_pks = opts[:left_primary_keys] = Array(left_pk)
169
+ lpkc = opts[:left_primary_key_column] ||= left_pk
170
+ lpkcs = opts[:left_primary_key_columns] ||= Array(lpkc)
174
171
  opts[:dataset] ||= lambda do
175
172
  ds = opts.associated_class
176
- opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
177
- ft = opts[:final_reverse_edge]
178
- ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + left_keys.zip(left_pks.map{|k| send(k)}), :table_alias=>ft[:alias])
173
+ opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias], :qualify=>:deep)}
174
+ ft = opts.final_reverse_edge
175
+ 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)
179
176
  end
180
177
 
181
178
  left_key_alias = opts[:left_key_alias] ||= opts.default_associated_key_alias
182
179
  opts[:eager_loader] ||= lambda do |eo|
183
- h = eo[:key_hash][left_pk]
184
- eo[:rows].each{|object| object.associations[name] = []}
180
+ h = eo[:id_map]
181
+ rows = eo[:rows]
182
+ rows.each{|object| object.associations[name] = []}
185
183
  ds = opts.associated_class
186
- opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
187
- ft = opts[:final_reverse_edge]
188
- conds = uses_lcks ? [[left_keys.map{|k| SQL::QualifiedIdentifier.new(ft[:table], k)}, h.keys]] : [[left_key, h.keys]]
189
- ds = ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + conds, :table_alias=>ft[:alias])
190
- model.eager_loading_dataset(opts, ds, Array(opts.select), eo[:associations], eo).all do |assoc_record|
184
+ opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias], :qualify=>:deep)}
185
+ ft = opts.final_reverse_edge
186
+ ds = ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + [[opts.predicate_key, h.keys]], :table_alias=>ft[:alias], :qualify=>:deep)
187
+ ds = model.eager_loading_dataset(opts, ds, nil, eo[:associations], eo)
188
+ case opts.eager_limit_strategy
189
+ when :window_function
190
+ delete_rn = true
191
+ rn = ds.row_number_column
192
+ ds = apply_window_function_eager_limit_strategy(ds, opts)
193
+ when :correlated_subquery
194
+ ds = apply_correlated_subquery_eager_limit_strategy(ds, opts) do |xds|
195
+ dsa = ds.send(:dataset_alias, 2)
196
+ opts.reverse_edges.each{|t| xds = xds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
197
+ 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)
198
+ end
199
+ end
200
+ ds.all do |assoc_record|
201
+ assoc_record.values.delete(rn) if delete_rn
191
202
  hash_key = if uses_lcks
192
203
  left_key_alias.map{|k| assoc_record.values.delete(k)}
193
204
  else
@@ -196,6 +207,10 @@ module Sequel
196
207
  next unless objects = h[hash_key]
197
208
  objects.each{|object| object.associations[name].push(assoc_record)}
198
209
  end
210
+ if opts.eager_limit_strategy == :ruby
211
+ limit, offset = opts.limit_and_offset
212
+ rows.each{|o| o.associations[name] = o.associations[name].slice(offset||0, limit) || []}
213
+ end
199
214
  end
200
215
 
201
216
  join_type = opts[:graph_join_type]
@@ -204,19 +219,56 @@ module Sequel
204
219
  only_conditions = opts[:graph_only_conditions]
205
220
  use_only_conditions = opts.include?(:graph_only_conditions)
206
221
  conditions = opts[:graph_conditions]
207
- opts[:eager_grapher] ||= proc do |ds, assoc_alias, table_alias|
208
- iq = table_alias
222
+ opts[:eager_grapher] ||= proc do |eo|
223
+ ds = eo[:self]
224
+ iq = eo[:implicit_qualifier]
209
225
  opts.edges.each do |t|
210
- ds = ds.graph(t[:table], t.fetch(:only_conditions, (Array(t[:right]).zip(Array(t[:left])) + t[:conditions])), :select=>false, :table_alias=>ds.unused_table_alias(t[:table]), :join_type=>t[:join_type], :implicit_qualifier=>iq, &t[:block])
226
+ ds = ds.graph(t[:table], t.fetch(:only_conditions, (Array(t[:right]).zip(Array(t[:left])) + t[:conditions])), :select=>false, :table_alias=>ds.unused_table_alias(t[:table]), :join_type=>t[:join_type], :qualify=>:deep, :implicit_qualifier=>iq, &t[:block])
211
227
  iq = nil
212
228
  end
213
- fe = opts[:final_edge]
214
- ds.graph(opts.associated_class, use_only_conditions ? only_conditions : (Array(opts.right_primary_key).zip(Array(fe[:left])) + conditions), :select=>select, :table_alias=>assoc_alias, :join_type=>join_type, &graph_block)
229
+ fe = opts.final_edge
230
+ ds.graph(opts.associated_class, use_only_conditions ? only_conditions : (Array(opts.right_primary_key).zip(Array(fe[:left])) + conditions), :select=>select, :table_alias=>eo[:table_alias], :qualify=>:deep, :join_type=>join_type, &graph_block)
215
231
  end
216
232
 
217
233
  def_association_dataset_methods(opts)
218
234
  end
219
235
  end
236
+
237
+ module DatasetMethods
238
+ private
239
+
240
+ # Use a subquery to filter rows to those related to the given associated object
241
+ def many_through_many_association_filter_expression(op, ref, obj)
242
+ lpks = ref[:left_primary_key_columns]
243
+ lpks = lpks.first if lpks.length == 1
244
+ lpks = ref.qualify(model.table_name, lpks)
245
+ edges = ref.edges
246
+ first, rest = edges.first, edges[1..-1]
247
+ last = edges.last
248
+ ds = model.db[first[:table]].select(*Array(ref.qualify(first[:table], first[:right])))
249
+ rest.each{|e| ds = ds.join(e[:table], e.fetch(:only_conditions, (Array(e[:right]).zip(Array(e[:left])) + e[:conditions])), :table_alias=>ds.unused_table_alias(e[:table]), :qualify=>:deep, &e[:block])}
250
+ last_alias = if rest.empty?
251
+ first[:table]
252
+ else
253
+ last_join = ds.opts[:join].last
254
+ last_join.table_alias || last_join.table
255
+ end
256
+
257
+ meths = if obj.is_a?(Sequel::Dataset)
258
+ ref.qualify(obj.model.table_name, ref.right_primary_keys)
259
+ else
260
+ ref.right_primary_key_methods
261
+ end
262
+
263
+ exp = association_filter_key_expression(ref.qualify(last_alias, Array(ref.final_edge[:left])), meths, obj)
264
+ if exp == SQL::Constants::FALSE
265
+ association_filter_handle_inversion(op, exp, Array(lpks))
266
+ else
267
+ ds = ds.where(exp).exclude(SQL::BooleanExpression.from_value_pairs(ds.opts[:select].zip([]), :OR))
268
+ association_filter_handle_inversion(op, SQL::BooleanExpression.from_value_pairs(lpks=>ds), Array(lpks))
269
+ end
270
+ end
271
+ end
220
272
  end
221
273
  end
222
274
  end