sequel 5.8.0 → 5.38.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 (510) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +409 -1795
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -4
  5. data/bin/sequel +4 -0
  6. data/doc/advanced_associations.rdoc +136 -18
  7. data/doc/association_basics.rdoc +10 -5
  8. data/doc/cheat_sheet.rdoc +1 -0
  9. data/doc/code_order.rdoc +12 -2
  10. data/doc/dataset_filtering.rdoc +17 -2
  11. data/doc/mass_assignment.rdoc +3 -3
  12. data/doc/model_dataset_method_design.rdoc +1 -1
  13. data/doc/model_plugins.rdoc +1 -1
  14. data/doc/opening_databases.rdoc +30 -8
  15. data/doc/postgresql.rdoc +107 -2
  16. data/doc/release_notes/5.10.0.txt +84 -0
  17. data/doc/release_notes/5.11.0.txt +83 -0
  18. data/doc/release_notes/5.12.0.txt +141 -0
  19. data/doc/release_notes/5.13.0.txt +27 -0
  20. data/doc/release_notes/5.14.0.txt +63 -0
  21. data/doc/release_notes/5.15.0.txt +39 -0
  22. data/doc/release_notes/5.16.0.txt +110 -0
  23. data/doc/release_notes/5.17.0.txt +31 -0
  24. data/doc/release_notes/5.18.0.txt +69 -0
  25. data/doc/release_notes/5.19.0.txt +28 -0
  26. data/doc/release_notes/5.20.0.txt +89 -0
  27. data/doc/release_notes/5.21.0.txt +87 -0
  28. data/doc/release_notes/5.22.0.txt +48 -0
  29. data/doc/release_notes/5.23.0.txt +56 -0
  30. data/doc/release_notes/5.24.0.txt +56 -0
  31. data/doc/release_notes/5.25.0.txt +32 -0
  32. data/doc/release_notes/5.26.0.txt +35 -0
  33. data/doc/release_notes/5.27.0.txt +21 -0
  34. data/doc/release_notes/5.28.0.txt +16 -0
  35. data/doc/release_notes/5.29.0.txt +22 -0
  36. data/doc/release_notes/5.30.0.txt +20 -0
  37. data/doc/release_notes/5.31.0.txt +148 -0
  38. data/doc/release_notes/5.32.0.txt +46 -0
  39. data/doc/release_notes/5.33.0.txt +24 -0
  40. data/doc/release_notes/5.34.0.txt +40 -0
  41. data/doc/release_notes/5.35.0.txt +56 -0
  42. data/doc/release_notes/5.36.0.txt +60 -0
  43. data/doc/release_notes/5.37.0.txt +30 -0
  44. data/doc/release_notes/5.38.0.txt +28 -0
  45. data/doc/release_notes/5.9.0.txt +99 -0
  46. data/doc/security.rdoc +10 -0
  47. data/doc/sharding.rdoc +42 -28
  48. data/doc/sql.rdoc +12 -0
  49. data/doc/testing.rdoc +24 -17
  50. data/doc/transactions.rdoc +78 -0
  51. data/doc/validations.rdoc +2 -2
  52. data/lib/sequel/adapters/ado.rb +26 -18
  53. data/lib/sequel/adapters/ado/access.rb +2 -2
  54. data/lib/sequel/adapters/ado/mssql.rb +5 -8
  55. data/lib/sequel/adapters/amalgalite.rb +1 -1
  56. data/lib/sequel/adapters/jdbc.rb +71 -27
  57. data/lib/sequel/adapters/jdbc/mysql.rb +6 -6
  58. data/lib/sequel/adapters/jdbc/oracle.rb +7 -6
  59. data/lib/sequel/adapters/jdbc/postgresql.rb +17 -28
  60. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +5 -6
  61. data/lib/sequel/adapters/jdbc/sqlite.rb +33 -2
  62. data/lib/sequel/adapters/jdbc/sqlserver.rb +4 -3
  63. data/lib/sequel/adapters/jdbc/transactions.rb +14 -28
  64. data/lib/sequel/adapters/mysql.rb +14 -15
  65. data/lib/sequel/adapters/mysql2.rb +5 -3
  66. data/lib/sequel/adapters/odbc.rb +4 -6
  67. data/lib/sequel/adapters/oracle.rb +7 -7
  68. data/lib/sequel/adapters/postgres.rb +52 -16
  69. data/lib/sequel/adapters/shared/access.rb +16 -12
  70. data/lib/sequel/adapters/shared/db2.rb +5 -0
  71. data/lib/sequel/adapters/shared/mssql.rb +41 -18
  72. data/lib/sequel/adapters/shared/mysql.rb +66 -19
  73. data/lib/sequel/adapters/shared/oracle.rb +29 -23
  74. data/lib/sequel/adapters/shared/postgres.rb +341 -95
  75. data/lib/sequel/adapters/shared/sqlanywhere.rb +23 -10
  76. data/lib/sequel/adapters/shared/sqlite.rb +174 -21
  77. data/lib/sequel/adapters/sqlanywhere.rb +33 -17
  78. data/lib/sequel/adapters/sqlite.rb +78 -68
  79. data/lib/sequel/adapters/tinytds.rb +14 -6
  80. data/lib/sequel/adapters/utils/emulate_offset_with_reverse_and_count.rb +2 -5
  81. data/lib/sequel/adapters/utils/mysql_mysql2.rb +5 -1
  82. data/lib/sequel/connection_pool.rb +2 -6
  83. data/lib/sequel/connection_pool/sharded_single.rb +7 -4
  84. data/lib/sequel/connection_pool/sharded_threaded.rb +32 -21
  85. data/lib/sequel/connection_pool/single.rb +1 -1
  86. data/lib/sequel/connection_pool/threaded.rb +26 -11
  87. data/lib/sequel/core.rb +327 -319
  88. data/lib/sequel/database/connecting.rb +7 -8
  89. data/lib/sequel/database/logging.rb +7 -1
  90. data/lib/sequel/database/misc.rb +68 -34
  91. data/lib/sequel/database/query.rb +6 -4
  92. data/lib/sequel/database/schema_generator.rb +31 -11
  93. data/lib/sequel/database/schema_methods.rb +32 -22
  94. data/lib/sequel/database/transactions.rb +129 -25
  95. data/lib/sequel/dataset.rb +4 -2
  96. data/lib/sequel/dataset/actions.rb +34 -23
  97. data/lib/sequel/dataset/features.rb +34 -0
  98. data/lib/sequel/dataset/graph.rb +27 -11
  99. data/lib/sequel/dataset/misc.rb +17 -3
  100. data/lib/sequel/dataset/placeholder_literalizer.rb +50 -21
  101. data/lib/sequel/dataset/prepared_statements.rb +96 -26
  102. data/lib/sequel/dataset/query.rb +43 -8
  103. data/lib/sequel/dataset/sql.rb +189 -41
  104. data/lib/sequel/deprecated.rb +3 -1
  105. data/lib/sequel/exceptions.rb +2 -0
  106. data/lib/sequel/extensions/_pretty_table.rb +1 -2
  107. data/lib/sequel/extensions/any_not_empty.rb +45 -0
  108. data/lib/sequel/extensions/caller_logging.rb +79 -0
  109. data/lib/sequel/extensions/columns_introspection.rb +1 -2
  110. data/lib/sequel/extensions/connection_expiration.rb +6 -6
  111. data/lib/sequel/extensions/connection_validator.rb +7 -6
  112. data/lib/sequel/extensions/constant_sql_override.rb +65 -0
  113. data/lib/sequel/extensions/constraint_validations.rb +53 -28
  114. data/lib/sequel/extensions/core_refinements.rb +2 -0
  115. data/lib/sequel/extensions/duplicate_columns_handler.rb +2 -0
  116. data/lib/sequel/extensions/escaped_like.rb +100 -0
  117. data/lib/sequel/extensions/eval_inspect.rb +3 -1
  118. data/lib/sequel/extensions/exclude_or_null.rb +68 -0
  119. data/lib/sequel/extensions/fiber_concurrency.rb +24 -0
  120. data/lib/sequel/extensions/index_caching.rb +9 -7
  121. data/lib/sequel/extensions/integer64.rb +3 -1
  122. data/lib/sequel/extensions/looser_typecasting.rb +3 -3
  123. data/lib/sequel/extensions/migration.rb +13 -6
  124. data/lib/sequel/extensions/named_timezones.rb +84 -23
  125. data/lib/sequel/extensions/pg_array.rb +87 -79
  126. data/lib/sequel/extensions/pg_array_ops.rb +14 -6
  127. data/lib/sequel/extensions/pg_enum.rb +34 -18
  128. data/lib/sequel/extensions/pg_extended_date_support.rb +34 -14
  129. data/lib/sequel/extensions/pg_hstore.rb +6 -0
  130. data/lib/sequel/extensions/pg_hstore_ops.rb +2 -0
  131. data/lib/sequel/extensions/pg_inet.rb +15 -5
  132. data/lib/sequel/extensions/pg_interval.rb +2 -0
  133. data/lib/sequel/extensions/pg_json.rb +387 -123
  134. data/lib/sequel/extensions/pg_json_ops.rb +168 -0
  135. data/lib/sequel/extensions/pg_range.rb +20 -10
  136. data/lib/sequel/extensions/pg_range_ops.rb +2 -0
  137. data/lib/sequel/extensions/pg_row.rb +3 -2
  138. data/lib/sequel/extensions/pg_row_ops.rb +24 -0
  139. data/lib/sequel/extensions/pg_static_cache_updater.rb +2 -2
  140. data/lib/sequel/extensions/pg_timestamptz.rb +2 -0
  141. data/lib/sequel/extensions/query.rb +1 -0
  142. data/lib/sequel/extensions/run_transaction_hooks.rb +72 -0
  143. data/lib/sequel/extensions/s.rb +2 -0
  144. data/lib/sequel/extensions/schema_dumper.rb +13 -7
  145. data/lib/sequel/extensions/sequel_4_dataset_methods.rb +4 -2
  146. data/lib/sequel/extensions/server_block.rb +18 -7
  147. data/lib/sequel/extensions/sql_comments.rb +2 -2
  148. data/lib/sequel/extensions/symbol_aref_refinement.rb +2 -0
  149. data/lib/sequel/extensions/symbol_as_refinement.rb +2 -0
  150. data/lib/sequel/extensions/to_dot.rb +9 -3
  151. data/lib/sequel/model.rb +3 -1
  152. data/lib/sequel/model/associations.rb +403 -69
  153. data/lib/sequel/model/base.rb +170 -90
  154. data/lib/sequel/model/plugins.rb +105 -0
  155. data/lib/sequel/plugins/after_initialize.rb +1 -1
  156. data/lib/sequel/plugins/association_dependencies.rb +3 -3
  157. data/lib/sequel/plugins/association_lazy_eager_option.rb +66 -0
  158. data/lib/sequel/plugins/association_multi_add_remove.rb +85 -0
  159. data/lib/sequel/plugins/association_pks.rb +74 -22
  160. data/lib/sequel/plugins/association_proxies.rb +6 -2
  161. data/lib/sequel/plugins/auto_validations.rb +36 -17
  162. data/lib/sequel/plugins/blacklist_security.rb +1 -2
  163. data/lib/sequel/plugins/boolean_subsets.rb +4 -1
  164. data/lib/sequel/plugins/caching.rb +3 -0
  165. data/lib/sequel/plugins/class_table_inheritance.rb +62 -34
  166. data/lib/sequel/plugins/composition.rb +13 -9
  167. data/lib/sequel/plugins/csv_serializer.rb +28 -9
  168. data/lib/sequel/plugins/defaults_setter.rb +2 -2
  169. data/lib/sequel/plugins/dirty.rb +60 -22
  170. data/lib/sequel/plugins/eager_graph_eager.rb +139 -0
  171. data/lib/sequel/plugins/empty_failure_backtraces.rb +38 -0
  172. data/lib/sequel/plugins/finder.rb +2 -2
  173. data/lib/sequel/plugins/forbid_lazy_load.rb +216 -0
  174. data/lib/sequel/plugins/hook_class_methods.rb +17 -5
  175. data/lib/sequel/plugins/insert_conflict.rb +72 -0
  176. data/lib/sequel/plugins/instance_specific_default.rb +113 -0
  177. data/lib/sequel/plugins/inverted_subsets.rb +2 -2
  178. data/lib/sequel/plugins/json_serializer.rb +21 -14
  179. data/lib/sequel/plugins/lazy_attributes.rb +1 -1
  180. data/lib/sequel/plugins/list.rb +22 -10
  181. data/lib/sequel/plugins/many_through_many.rb +1 -1
  182. data/lib/sequel/plugins/nested_attributes.rb +27 -5
  183. data/lib/sequel/plugins/pg_array_associations.rb +12 -9
  184. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +149 -61
  185. data/lib/sequel/plugins/prepared_statements.rb +6 -12
  186. data/lib/sequel/plugins/prepared_statements_safe.rb +1 -3
  187. data/lib/sequel/plugins/rcte_tree.rb +20 -22
  188. data/lib/sequel/plugins/sharding.rb +13 -7
  189. data/lib/sequel/plugins/single_table_inheritance.rb +20 -15
  190. data/lib/sequel/plugins/skip_saving_columns.rb +108 -0
  191. data/lib/sequel/plugins/static_cache.rb +36 -17
  192. data/lib/sequel/plugins/static_cache_cache.rb +53 -0
  193. data/lib/sequel/plugins/string_stripper.rb +1 -1
  194. data/lib/sequel/plugins/subclasses.rb +2 -0
  195. data/lib/sequel/plugins/subset_conditions.rb +2 -2
  196. data/lib/sequel/plugins/tactical_eager_loading.rb +73 -2
  197. data/lib/sequel/plugins/throw_failures.rb +110 -0
  198. data/lib/sequel/plugins/tree.rb +49 -31
  199. data/lib/sequel/plugins/typecast_on_load.rb +3 -2
  200. data/lib/sequel/plugins/validation_class_methods.rb +11 -5
  201. data/lib/sequel/plugins/validation_helpers.rb +2 -2
  202. data/lib/sequel/sql.rb +120 -30
  203. data/lib/sequel/timezones.rb +55 -14
  204. data/lib/sequel/version.rb +6 -1
  205. metadata +101 -361
  206. data/Rakefile +0 -151
  207. data/doc/release_notes/4.0.0.txt +0 -262
  208. data/doc/release_notes/4.1.0.txt +0 -85
  209. data/doc/release_notes/4.10.0.txt +0 -226
  210. data/doc/release_notes/4.11.0.txt +0 -147
  211. data/doc/release_notes/4.12.0.txt +0 -105
  212. data/doc/release_notes/4.13.0.txt +0 -169
  213. data/doc/release_notes/4.14.0.txt +0 -68
  214. data/doc/release_notes/4.15.0.txt +0 -56
  215. data/doc/release_notes/4.16.0.txt +0 -36
  216. data/doc/release_notes/4.17.0.txt +0 -38
  217. data/doc/release_notes/4.18.0.txt +0 -36
  218. data/doc/release_notes/4.19.0.txt +0 -45
  219. data/doc/release_notes/4.2.0.txt +0 -129
  220. data/doc/release_notes/4.20.0.txt +0 -79
  221. data/doc/release_notes/4.21.0.txt +0 -94
  222. data/doc/release_notes/4.22.0.txt +0 -72
  223. data/doc/release_notes/4.23.0.txt +0 -65
  224. data/doc/release_notes/4.24.0.txt +0 -99
  225. data/doc/release_notes/4.25.0.txt +0 -181
  226. data/doc/release_notes/4.26.0.txt +0 -44
  227. data/doc/release_notes/4.27.0.txt +0 -78
  228. data/doc/release_notes/4.28.0.txt +0 -57
  229. data/doc/release_notes/4.29.0.txt +0 -41
  230. data/doc/release_notes/4.3.0.txt +0 -40
  231. data/doc/release_notes/4.30.0.txt +0 -37
  232. data/doc/release_notes/4.31.0.txt +0 -57
  233. data/doc/release_notes/4.32.0.txt +0 -132
  234. data/doc/release_notes/4.33.0.txt +0 -88
  235. data/doc/release_notes/4.34.0.txt +0 -86
  236. data/doc/release_notes/4.35.0.txt +0 -130
  237. data/doc/release_notes/4.36.0.txt +0 -116
  238. data/doc/release_notes/4.37.0.txt +0 -50
  239. data/doc/release_notes/4.38.0.txt +0 -67
  240. data/doc/release_notes/4.39.0.txt +0 -127
  241. data/doc/release_notes/4.4.0.txt +0 -92
  242. data/doc/release_notes/4.40.0.txt +0 -179
  243. data/doc/release_notes/4.41.0.txt +0 -77
  244. data/doc/release_notes/4.42.0.txt +0 -221
  245. data/doc/release_notes/4.43.0.txt +0 -87
  246. data/doc/release_notes/4.44.0.txt +0 -125
  247. data/doc/release_notes/4.45.0.txt +0 -370
  248. data/doc/release_notes/4.46.0.txt +0 -404
  249. data/doc/release_notes/4.47.0.txt +0 -56
  250. data/doc/release_notes/4.48.0.txt +0 -293
  251. data/doc/release_notes/4.49.0.txt +0 -222
  252. data/doc/release_notes/4.5.0.txt +0 -34
  253. data/doc/release_notes/4.6.0.txt +0 -30
  254. data/doc/release_notes/4.7.0.txt +0 -103
  255. data/doc/release_notes/4.8.0.txt +0 -175
  256. data/doc/release_notes/4.9.0.txt +0 -190
  257. data/spec/adapter_spec.rb +0 -4
  258. data/spec/adapters/db2_spec.rb +0 -170
  259. data/spec/adapters/mssql_spec.rb +0 -804
  260. data/spec/adapters/mysql_spec.rb +0 -1041
  261. data/spec/adapters/oracle_spec.rb +0 -327
  262. data/spec/adapters/postgres_spec.rb +0 -4000
  263. data/spec/adapters/spec_helper.rb +0 -43
  264. data/spec/adapters/sqlanywhere_spec.rb +0 -97
  265. data/spec/adapters/sqlite_spec.rb +0 -600
  266. data/spec/bin_spec.rb +0 -269
  267. data/spec/core/connection_pool_spec.rb +0 -1228
  268. data/spec/core/database_spec.rb +0 -2673
  269. data/spec/core/dataset_spec.rb +0 -5419
  270. data/spec/core/deprecated_spec.rb +0 -70
  271. data/spec/core/expression_filters_spec.rb +0 -1344
  272. data/spec/core/mock_adapter_spec.rb +0 -722
  273. data/spec/core/object_graph_spec.rb +0 -306
  274. data/spec/core/placeholder_literalizer_spec.rb +0 -166
  275. data/spec/core/schema_generator_spec.rb +0 -214
  276. data/spec/core/schema_spec.rb +0 -1820
  277. data/spec/core/spec_helper.rb +0 -23
  278. data/spec/core/version_spec.rb +0 -7
  279. data/spec/core_extensions_spec.rb +0 -762
  280. data/spec/core_model_spec.rb +0 -2
  281. data/spec/core_spec.rb +0 -1
  282. data/spec/deprecation_helper.rb +0 -30
  283. data/spec/extensions/accessed_columns_spec.rb +0 -51
  284. data/spec/extensions/active_model_spec.rb +0 -99
  285. data/spec/extensions/after_initialize_spec.rb +0 -24
  286. data/spec/extensions/arbitrary_servers_spec.rb +0 -109
  287. data/spec/extensions/association_dependencies_spec.rb +0 -125
  288. data/spec/extensions/association_pks_spec.rb +0 -423
  289. data/spec/extensions/association_proxies_spec.rb +0 -100
  290. data/spec/extensions/auto_literal_strings_spec.rb +0 -205
  291. data/spec/extensions/auto_validations_spec.rb +0 -202
  292. data/spec/extensions/blacklist_security_spec.rb +0 -95
  293. data/spec/extensions/blank_spec.rb +0 -69
  294. data/spec/extensions/boolean_readers_spec.rb +0 -93
  295. data/spec/extensions/boolean_subsets_spec.rb +0 -47
  296. data/spec/extensions/caching_spec.rb +0 -273
  297. data/spec/extensions/class_table_inheritance_spec.rb +0 -568
  298. data/spec/extensions/column_conflicts_spec.rb +0 -75
  299. data/spec/extensions/column_select_spec.rb +0 -129
  300. data/spec/extensions/columns_introspection_spec.rb +0 -90
  301. data/spec/extensions/columns_updated_spec.rb +0 -35
  302. data/spec/extensions/composition_spec.rb +0 -248
  303. data/spec/extensions/connection_expiration_spec.rb +0 -133
  304. data/spec/extensions/connection_validator_spec.rb +0 -127
  305. data/spec/extensions/constraint_validations_plugin_spec.rb +0 -300
  306. data/spec/extensions/constraint_validations_spec.rb +0 -395
  307. data/spec/extensions/core_refinements_spec.rb +0 -528
  308. data/spec/extensions/csv_serializer_spec.rb +0 -183
  309. data/spec/extensions/current_datetime_timestamp_spec.rb +0 -27
  310. data/spec/extensions/dataset_associations_spec.rb +0 -365
  311. data/spec/extensions/dataset_source_alias_spec.rb +0 -51
  312. data/spec/extensions/date_arithmetic_spec.rb +0 -181
  313. data/spec/extensions/datetime_parse_to_time_spec.rb +0 -169
  314. data/spec/extensions/def_dataset_method_spec.rb +0 -100
  315. data/spec/extensions/defaults_setter_spec.rb +0 -141
  316. data/spec/extensions/delay_add_association_spec.rb +0 -73
  317. data/spec/extensions/dirty_spec.rb +0 -189
  318. data/spec/extensions/duplicate_columns_handler_spec.rb +0 -104
  319. data/spec/extensions/eager_each_spec.rb +0 -62
  320. data/spec/extensions/empty_array_consider_nulls_spec.rb +0 -24
  321. data/spec/extensions/error_splitter_spec.rb +0 -18
  322. data/spec/extensions/error_sql_spec.rb +0 -20
  323. data/spec/extensions/eval_inspect_spec.rb +0 -74
  324. data/spec/extensions/finder_spec.rb +0 -260
  325. data/spec/extensions/force_encoding_spec.rb +0 -126
  326. data/spec/extensions/freeze_datasets_spec.rb +0 -31
  327. data/spec/extensions/graph_each_spec.rb +0 -113
  328. data/spec/extensions/hook_class_methods_spec.rb +0 -380
  329. data/spec/extensions/identifier_mangling_spec.rb +0 -201
  330. data/spec/extensions/implicit_subquery_spec.rb +0 -58
  331. data/spec/extensions/index_caching_spec.rb +0 -66
  332. data/spec/extensions/inflector_spec.rb +0 -183
  333. data/spec/extensions/input_transformer_spec.rb +0 -69
  334. data/spec/extensions/insert_returning_select_spec.rb +0 -72
  335. data/spec/extensions/instance_filters_spec.rb +0 -79
  336. data/spec/extensions/instance_hooks_spec.rb +0 -246
  337. data/spec/extensions/integer64_spec.rb +0 -22
  338. data/spec/extensions/inverted_subsets_spec.rb +0 -33
  339. data/spec/extensions/json_serializer_spec.rb +0 -336
  340. data/spec/extensions/lazy_attributes_spec.rb +0 -183
  341. data/spec/extensions/list_spec.rb +0 -275
  342. data/spec/extensions/looser_typecasting_spec.rb +0 -43
  343. data/spec/extensions/many_through_many_spec.rb +0 -2177
  344. data/spec/extensions/migration_spec.rb +0 -840
  345. data/spec/extensions/modification_detection_spec.rb +0 -93
  346. data/spec/extensions/mssql_optimistic_locking_spec.rb +0 -92
  347. data/spec/extensions/named_timezones_spec.rb +0 -109
  348. data/spec/extensions/nested_attributes_spec.rb +0 -703
  349. data/spec/extensions/null_dataset_spec.rb +0 -85
  350. data/spec/extensions/optimistic_locking_spec.rb +0 -127
  351. data/spec/extensions/pagination_spec.rb +0 -116
  352. data/spec/extensions/pg_array_associations_spec.rb +0 -802
  353. data/spec/extensions/pg_array_ops_spec.rb +0 -144
  354. data/spec/extensions/pg_array_spec.rb +0 -398
  355. data/spec/extensions/pg_auto_constraint_validations_spec.rb +0 -165
  356. data/spec/extensions/pg_enum_spec.rb +0 -113
  357. data/spec/extensions/pg_extended_date_support_spec.rb +0 -126
  358. data/spec/extensions/pg_hstore_ops_spec.rb +0 -238
  359. data/spec/extensions/pg_hstore_spec.rb +0 -219
  360. data/spec/extensions/pg_inet_ops_spec.rb +0 -102
  361. data/spec/extensions/pg_inet_spec.rb +0 -72
  362. data/spec/extensions/pg_interval_spec.rb +0 -103
  363. data/spec/extensions/pg_json_ops_spec.rb +0 -289
  364. data/spec/extensions/pg_json_spec.rb +0 -262
  365. data/spec/extensions/pg_loose_count_spec.rb +0 -23
  366. data/spec/extensions/pg_range_ops_spec.rb +0 -60
  367. data/spec/extensions/pg_range_spec.rb +0 -487
  368. data/spec/extensions/pg_row_ops_spec.rb +0 -61
  369. data/spec/extensions/pg_row_plugin_spec.rb +0 -60
  370. data/spec/extensions/pg_row_spec.rb +0 -363
  371. data/spec/extensions/pg_static_cache_updater_spec.rb +0 -93
  372. data/spec/extensions/pg_timestamptz_spec.rb +0 -17
  373. data/spec/extensions/prepared_statements_safe_spec.rb +0 -66
  374. data/spec/extensions/prepared_statements_spec.rb +0 -182
  375. data/spec/extensions/pretty_table_spec.rb +0 -123
  376. data/spec/extensions/query_spec.rb +0 -94
  377. data/spec/extensions/rcte_tree_spec.rb +0 -381
  378. data/spec/extensions/round_timestamps_spec.rb +0 -39
  379. data/spec/extensions/s_spec.rb +0 -60
  380. data/spec/extensions/schema_caching_spec.rb +0 -64
  381. data/spec/extensions/schema_dumper_spec.rb +0 -868
  382. data/spec/extensions/select_remove_spec.rb +0 -38
  383. data/spec/extensions/sequel_4_dataset_methods_spec.rb +0 -121
  384. data/spec/extensions/serialization_modification_detection_spec.rb +0 -98
  385. data/spec/extensions/serialization_spec.rb +0 -365
  386. data/spec/extensions/server_block_spec.rb +0 -97
  387. data/spec/extensions/server_logging_spec.rb +0 -45
  388. data/spec/extensions/sharding_spec.rb +0 -189
  389. data/spec/extensions/shared_caching_spec.rb +0 -151
  390. data/spec/extensions/single_table_inheritance_spec.rb +0 -347
  391. data/spec/extensions/singular_table_names_spec.rb +0 -22
  392. data/spec/extensions/skip_create_refresh_spec.rb +0 -18
  393. data/spec/extensions/spec_helper.rb +0 -61
  394. data/spec/extensions/split_array_nil_spec.rb +0 -24
  395. data/spec/extensions/split_values_spec.rb +0 -57
  396. data/spec/extensions/sql_comments_spec.rb +0 -33
  397. data/spec/extensions/sql_expr_spec.rb +0 -59
  398. data/spec/extensions/static_cache_spec.rb +0 -410
  399. data/spec/extensions/string_agg_spec.rb +0 -90
  400. data/spec/extensions/string_date_time_spec.rb +0 -95
  401. data/spec/extensions/string_stripper_spec.rb +0 -68
  402. data/spec/extensions/subclasses_spec.rb +0 -79
  403. data/spec/extensions/subset_conditions_spec.rb +0 -38
  404. data/spec/extensions/symbol_aref_refinement_spec.rb +0 -28
  405. data/spec/extensions/symbol_as_refinement_spec.rb +0 -21
  406. data/spec/extensions/synchronize_sql_spec.rb +0 -124
  407. data/spec/extensions/table_select_spec.rb +0 -83
  408. data/spec/extensions/tactical_eager_loading_spec.rb +0 -141
  409. data/spec/extensions/thread_local_timezones_spec.rb +0 -67
  410. data/spec/extensions/timestamps_spec.rb +0 -209
  411. data/spec/extensions/to_dot_spec.rb +0 -153
  412. data/spec/extensions/touch_spec.rb +0 -226
  413. data/spec/extensions/tree_spec.rb +0 -284
  414. data/spec/extensions/typecast_on_load_spec.rb +0 -86
  415. data/spec/extensions/unlimited_update_spec.rb +0 -21
  416. data/spec/extensions/update_or_create_spec.rb +0 -83
  417. data/spec/extensions/update_primary_key_spec.rb +0 -105
  418. data/spec/extensions/update_refresh_spec.rb +0 -59
  419. data/spec/extensions/uuid_spec.rb +0 -101
  420. data/spec/extensions/validate_associated_spec.rb +0 -52
  421. data/spec/extensions/validation_class_methods_spec.rb +0 -1040
  422. data/spec/extensions/validation_contexts_spec.rb +0 -31
  423. data/spec/extensions/validation_helpers_spec.rb +0 -525
  424. data/spec/extensions/whitelist_security_spec.rb +0 -157
  425. data/spec/extensions/xml_serializer_spec.rb +0 -213
  426. data/spec/files/bad_down_migration/001_create_alt_basic.rb +0 -4
  427. data/spec/files/bad_down_migration/002_create_alt_advanced.rb +0 -4
  428. data/spec/files/bad_timestamped_migrations/1273253849_create_sessions.rb +0 -9
  429. data/spec/files/bad_timestamped_migrations/1273253851_create_nodes.rb +0 -9
  430. data/spec/files/bad_timestamped_migrations/1273253853_3_create_users.rb +0 -3
  431. data/spec/files/bad_up_migration/001_create_alt_basic.rb +0 -4
  432. data/spec/files/bad_up_migration/002_create_alt_advanced.rb +0 -3
  433. data/spec/files/convert_to_timestamp_migrations/001_create_sessions.rb +0 -9
  434. data/spec/files/convert_to_timestamp_migrations/002_create_nodes.rb +0 -9
  435. data/spec/files/convert_to_timestamp_migrations/003_3_create_users.rb +0 -4
  436. data/spec/files/convert_to_timestamp_migrations/1273253850_create_artists.rb +0 -9
  437. data/spec/files/convert_to_timestamp_migrations/1273253852_create_albums.rb +0 -9
  438. data/spec/files/double_migration/001_create_sessions.rb +0 -9
  439. data/spec/files/double_migration/002_create_nodes.rb +0 -19
  440. data/spec/files/double_migration/003_3_create_users.rb +0 -4
  441. data/spec/files/duplicate_integer_migrations/001_create_alt_advanced.rb +0 -4
  442. data/spec/files/duplicate_integer_migrations/001_create_alt_basic.rb +0 -4
  443. data/spec/files/duplicate_timestamped_migrations/1273253849_create_sessions.rb +0 -9
  444. data/spec/files/duplicate_timestamped_migrations/1273253853_create_nodes.rb +0 -9
  445. data/spec/files/duplicate_timestamped_migrations/1273253853_create_users.rb +0 -4
  446. data/spec/files/empty_migration/001_create_sessions.rb +0 -9
  447. data/spec/files/empty_migration/002_create_nodes.rb +0 -0
  448. data/spec/files/empty_migration/003_3_create_users.rb +0 -4
  449. data/spec/files/integer_migrations/001_create_sessions.rb +0 -9
  450. data/spec/files/integer_migrations/002_create_nodes.rb +0 -9
  451. data/spec/files/integer_migrations/003_3_create_users.rb +0 -4
  452. data/spec/files/interleaved_timestamped_migrations/1273253849_create_sessions.rb +0 -9
  453. data/spec/files/interleaved_timestamped_migrations/1273253850_create_artists.rb +0 -9
  454. data/spec/files/interleaved_timestamped_migrations/1273253851_create_nodes.rb +0 -9
  455. data/spec/files/interleaved_timestamped_migrations/1273253852_create_albums.rb +0 -9
  456. data/spec/files/interleaved_timestamped_migrations/1273253853_3_create_users.rb +0 -4
  457. data/spec/files/missing_integer_migrations/001_create_alt_basic.rb +0 -4
  458. data/spec/files/missing_integer_migrations/003_create_alt_advanced.rb +0 -4
  459. data/spec/files/missing_timestamped_migrations/1273253849_create_sessions.rb +0 -9
  460. data/spec/files/missing_timestamped_migrations/1273253853_3_create_users.rb +0 -4
  461. data/spec/files/reversible_migrations/001_reversible.rb +0 -5
  462. data/spec/files/reversible_migrations/002_reversible.rb +0 -5
  463. data/spec/files/reversible_migrations/003_reversible.rb +0 -5
  464. data/spec/files/reversible_migrations/004_reversible.rb +0 -5
  465. data/spec/files/reversible_migrations/005_reversible.rb +0 -10
  466. data/spec/files/reversible_migrations/006_reversible.rb +0 -10
  467. data/spec/files/reversible_migrations/007_reversible.rb +0 -10
  468. data/spec/files/timestamped_migrations/1273253849_create_sessions.rb +0 -9
  469. data/spec/files/timestamped_migrations/1273253851_create_nodes.rb +0 -9
  470. data/spec/files/timestamped_migrations/1273253853_3_create_users.rb +0 -4
  471. data/spec/files/transaction_specified_migrations/001_create_alt_basic.rb +0 -4
  472. data/spec/files/transaction_specified_migrations/002_create_basic.rb +0 -4
  473. data/spec/files/transaction_unspecified_migrations/001_create_alt_basic.rb +0 -3
  474. data/spec/files/transaction_unspecified_migrations/002_create_basic.rb +0 -3
  475. data/spec/files/uppercase_timestamped_migrations/1273253849_CREATE_SESSIONS.RB +0 -9
  476. data/spec/files/uppercase_timestamped_migrations/1273253851_CREATE_NODES.RB +0 -9
  477. data/spec/files/uppercase_timestamped_migrations/1273253853_3_CREATE_USERS.RB +0 -4
  478. data/spec/guards_helper.rb +0 -58
  479. data/spec/integration/associations_test.rb +0 -2513
  480. data/spec/integration/database_test.rb +0 -113
  481. data/spec/integration/dataset_test.rb +0 -1880
  482. data/spec/integration/eager_loader_test.rb +0 -687
  483. data/spec/integration/migrator_test.rb +0 -262
  484. data/spec/integration/model_test.rb +0 -203
  485. data/spec/integration/plugin_test.rb +0 -2302
  486. data/spec/integration/prepared_statement_test.rb +0 -398
  487. data/spec/integration/schema_test.rb +0 -869
  488. data/spec/integration/spec_helper.rb +0 -64
  489. data/spec/integration/timezone_test.rb +0 -86
  490. data/spec/integration/transaction_test.rb +0 -354
  491. data/spec/integration/type_test.rb +0 -127
  492. data/spec/model/association_reflection_spec.rb +0 -803
  493. data/spec/model/associations_spec.rb +0 -4538
  494. data/spec/model/base_spec.rb +0 -817
  495. data/spec/model/class_dataset_methods_spec.rb +0 -146
  496. data/spec/model/dataset_methods_spec.rb +0 -198
  497. data/spec/model/eager_loading_spec.rb +0 -2262
  498. data/spec/model/hooks_spec.rb +0 -370
  499. data/spec/model/inflector_spec.rb +0 -26
  500. data/spec/model/model_spec.rb +0 -953
  501. data/spec/model/plugins_spec.rb +0 -318
  502. data/spec/model/record_spec.rb +0 -2107
  503. data/spec/model/spec_helper.rb +0 -45
  504. data/spec/model/validations_spec.rb +0 -193
  505. data/spec/model_no_assoc_spec.rb +0 -1
  506. data/spec/model_spec.rb +0 -1
  507. data/spec/plugin_spec.rb +0 -1
  508. data/spec/sequel_coverage.rb +0 -15
  509. data/spec/sequel_warning.rb +0 -4
  510. data/spec/spec_config.rb +0 -12
@@ -49,7 +49,9 @@ module Sequel::S
49
49
  Sequel.expr(*a, &block)
50
50
  end
51
51
 
52
+ # :nocov:
52
53
  if RUBY_VERSION >= '2.0.0'
54
+ # :nocov:
53
55
  refine Object do
54
56
  include Sequel::S
55
57
  end
@@ -37,7 +37,7 @@ module Sequel
37
37
  {:type =>schema[:type] == :boolean ? TrueClass : Integer}
38
38
  when /\Abigint(?:\((?:\d+)\))?(?: unsigned)?\z/
39
39
  {:type=>:Bignum}
40
- when /\A(?:real|float|double(?: precision)?|double\(\d+,\d+\)(?: unsigned)?)\z/
40
+ when /\A(?:real|float|double(?: precision)?|double\(\d+,\d+\))(?: unsigned)?\z/
41
41
  {:type=>Float}
42
42
  when 'boolean', 'bit', 'bool'
43
43
  {:type=>TrueClass}
@@ -57,7 +57,7 @@ module Sequel
57
57
  {:type=>String, :size=>($1.to_i if $1)}
58
58
  when /\A(?:small)?money\z/
59
59
  {:type=>BigDecimal, :size=>[19,2]}
60
- when /\A(?:decimal|numeric|number)(?:\((\d+)(?:,\s*(\d+))?\))?\z/
60
+ when /\A(?:decimal|numeric|number)(?:\((\d+)(?:,\s*(\d+))?\))?(?: unsigned)?\z/
61
61
  s = [($1.to_i if $1), ($2.to_i if $2)].compact
62
62
  {:type=>BigDecimal, :size=>(s.empty? ? nil : s)}
63
63
  when /\A(?:bytea|(?:tiny|medium|long)?blob|(?:var)?binary)(?:\((\d+)\))?\z/
@@ -199,12 +199,18 @@ END_MIG
199
199
  end
200
200
  type = col_opts.delete(:type)
201
201
  col_opts.delete(:size) if col_opts[:size].nil?
202
- col_opts[:default] = if schema[:ruby_default].nil?
203
- column_schema_to_ruby_default_fallback(schema[:default], options)
202
+ if schema[:generated]
203
+ if options[:same_db] && database_type == :postgres
204
+ col_opts[:generated_always_as] = column_schema_to_ruby_default_fallback(schema[:default], options)
205
+ end
204
206
  else
205
- schema[:ruby_default]
207
+ col_opts[:default] = if schema[:ruby_default].nil?
208
+ column_schema_to_ruby_default_fallback(schema[:default], options)
209
+ else
210
+ schema[:ruby_default]
211
+ end
212
+ col_opts.delete(:default) if col_opts[:default].nil?
206
213
  end
207
- col_opts.delete(:default) if col_opts[:default].nil?
208
214
  col_opts[:null] = false if schema[:allow_null] == false
209
215
  if table = schema[:table]
210
216
  [:key, :on_delete, :on_update, :deferrable].each{|f| col_opts[f] = schema[f] if schema[f]}
@@ -212,7 +218,7 @@ END_MIG
212
218
  gen.foreign_key(name, table, col_opts)
213
219
  else
214
220
  gen.column(name, type, col_opts)
215
- if [Integer, :Bignum, Float].include?(type) && schema[:db_type] =~ / unsigned\z/io
221
+ if [Integer, :Bignum, Float, BigDecimal].include?(type) && schema[:db_type] =~ / unsigned\z/io
216
222
  gen.check(Sequel::SQL::Identifier.new(name) >= 0)
217
223
  end
218
224
  end
@@ -41,7 +41,8 @@ module Sequel
41
41
  # # => 6
42
42
  # DB[:table].interval{function(column)} # SELECT (max(function(column)) - min(function(column))) FROM table LIMIT 1
43
43
  # # => 7
44
- def interval(column=Sequel.virtual_row(&Proc.new))
44
+ def interval(column=(no_arg = true), &block)
45
+ column = Sequel.virtual_row(&block) if no_arg
45
46
  if loader = cached_placeholder_literalizer(:_interval_loader) do |pl|
46
47
  arg = pl.arg
47
48
  aggregate_dataset.limit(1).select((SQL::Function.new(:max, arg) - SQL::Function.new(:min, arg)).as(:interval))
@@ -60,7 +61,8 @@ module Sequel
60
61
  # # => 1..10
61
62
  # DB[:table].interval{function(column)} # SELECT max(function(column)) AS v1, min(function(column)) AS v2 FROM table LIMIT 1
62
63
  # # => 0..7
63
- def range(column=Sequel.virtual_row(&Proc.new))
64
+ def range(column=(no_arg = true), &block)
65
+ column = Sequel.virtual_row(&block) if no_arg
64
66
  r = if loader = cached_placeholder_literalizer(:_range_loader) do |pl|
65
67
  arg = pl.arg
66
68
  aggregate_dataset.limit(1).select(SQL::Function.new(:min, arg).as(:v1), SQL::Function.new(:max, arg).as(:v2))
@@ -52,6 +52,12 @@
52
52
  # DB[:a].server(:read_only).delete # Uses shard2
53
53
  # end
54
54
  #
55
+ # If you use an invalid server when calling with_server, it will be
56
+ # treated the same way as if you called Dataset#server with an invalid
57
+ # server. By default, the default server will be used in such cases.
58
+ # If you would like a different server to be used, or an exception to
59
+ # be raised, then use the :servers_hash Database option.
60
+ #
55
61
  # Related modules: Sequel::ServerBlock, Sequel::UnthreadedServerBlock,
56
62
  # Sequel::ThreadedServerBlock
57
63
 
@@ -110,9 +116,9 @@ module Sequel
110
116
  else
111
117
  case server
112
118
  when :default, nil
113
- @default_servers[-1][0]
119
+ @servers[@default_servers[-1][0]]
114
120
  when :read_only
115
- @default_servers[-1][1]
121
+ @servers[@default_servers[-1][1]]
116
122
  else
117
123
  super
118
124
  end
@@ -137,13 +143,13 @@ module Sequel
137
143
 
138
144
  # Make the given server the new default server for the current thread.
139
145
  def set_default_server(default_server, read_only_server=default_server)
140
- sync{(@default_servers[Thread.current] ||= [])} << [default_server, read_only_server]
146
+ sync{(@default_servers[Sequel.current] ||= [])} << [default_server, read_only_server]
141
147
  end
142
148
 
143
149
  # Remove the current default server for the current thread, restoring the
144
150
  # previous default server.
145
151
  def clear_default_server
146
- t = Thread.current
152
+ t = Sequel.current
147
153
  a = sync{@default_servers[t]}
148
154
  a.pop
149
155
  sync{@default_servers.delete(t)} if a.empty?
@@ -151,15 +157,20 @@ module Sequel
151
157
 
152
158
  # Use the server given to with_server for the given thread, if appropriate.
153
159
  def pick_server(server)
154
- a = sync{@default_servers[Thread.current]}
160
+ a = sync{@default_servers[Sequel.current]}
155
161
  if !a || a.empty?
156
162
  super
157
163
  else
164
+ # Hash handling required to work when loaded after arbitrary servers plugin.
158
165
  case server
159
166
  when :default, nil
160
- a[-1][0]
167
+ v = a[-1][0]
168
+ v = @servers[v] unless v.is_a?(Hash)
169
+ v
161
170
  when :read_only
162
- a[-1][1]
171
+ v = a[-1][1]
172
+ v = @servers[v] unless v.is_a?(Hash)
173
+ v
163
174
  else
164
175
  super
165
176
  end
@@ -9,8 +9,8 @@
9
9
  # #
10
10
  #
11
11
  # As you can see, this uses single line SQL comments (--) suffixed
12
- # by a newline. This # plugin transforms all consecutive
13
- # whitespace in the comment to # a single string:
12
+ # by a newline. This plugin transforms all consecutive whitespace
13
+ # in the comment to a single string:
14
14
  #
15
15
  # ds = DB[:table].comment("Some\r\nComment Here").all
16
16
  # # SELECT * FROM table -- Some Comment Here
@@ -25,7 +25,9 @@
25
25
  #
26
26
  # Related module: Sequel::SymbolAref
27
27
 
28
+ # :nocov:
28
29
  raise(Sequel::Error, "Refinements require ruby 2.0.0 or greater") unless RUBY_VERSION >= '2.0.0'
30
+ # :nocov:
29
31
 
30
32
  module Sequel::SymbolAref
31
33
  refine Symbol do
@@ -23,7 +23,9 @@
23
23
  #
24
24
  # Related module: Sequel::SymbolAs
25
25
 
26
+ # :nocov:
26
27
  raise(Sequel::Error, "Refinements require ruby 2.0.0 or greater") unless RUBY_VERSION >= '2.0.0'
28
+ # :nocov:
27
29
 
28
30
  module Sequel::SymbolAs
29
31
  refine Symbol do
@@ -53,7 +53,13 @@ module Sequel
53
53
  # is given, it is used directly as the node or transition. Otherwise
54
54
  # a node is created for the current object.
55
55
  def dot(label, j=nil)
56
- @dot << "#{j||@i} [label=#{label.to_s.inspect}];"
56
+ label = case label
57
+ when nil
58
+ "<nil>"
59
+ else
60
+ label.to_s
61
+ end
62
+ @dot << "#{j||@i} [label=#{label.inspect}];"
57
63
  end
58
64
 
59
65
  # Recursive method that parses all of Sequel's internal datastructures,
@@ -61,7 +67,7 @@ module Sequel
61
67
  # structure.
62
68
  def v(e, l)
63
69
  @i += 1
64
- dot(l, "#{@stack.last} -> #{@i}") if l
70
+ dot(l, "#{@stack.last} -> #{@i}")
65
71
  @stack.push(@i)
66
72
  case e
67
73
  when LiteralString
@@ -144,7 +150,7 @@ module Sequel
144
150
  dot "Dataset"
145
151
  TO_DOT_OPTIONS.each do |k|
146
152
  if val = e.opts[k]
147
- v(val, k.to_s)
153
+ v(val, k)
148
154
  end
149
155
  end
150
156
  else
@@ -69,7 +69,9 @@ module Sequel
69
69
  require_relative "model/base"
70
70
  require_relative "model/exceptions"
71
71
  require_relative "model/errors"
72
+ # :nocov:
72
73
  if !defined?(::SEQUEL_NO_ASSOCIATIONS) && !ENV.has_key?('SEQUEL_NO_ASSOCIATIONS')
74
+ # :nocov:
73
75
  require_relative 'model/associations'
74
76
  plugin Model::Associations
75
77
  end
@@ -77,7 +79,7 @@ module Sequel
77
79
  def_Model(::Sequel)
78
80
 
79
81
  # The setter methods (methods ending with =) that are never allowed
80
- # to be called automatically via +set+/+update+/+new+/etc..
82
+ # to be called automatically via +set+/+update+/+new+/etc.
81
83
  RESTRICTED_SETTER_METHODS = instance_methods.map(&:to_s).select{|l| l.end_with?('=')}.freeze
82
84
  end
83
85
  end
@@ -164,11 +164,11 @@ module Sequel
164
164
  # range to return the object(s) at the correct offset/limit.
165
165
  def apply_ruby_eager_limit_strategy(rows, limit_and_offset = limit_and_offset())
166
166
  name = self[:name]
167
+ return unless range = slice_range(limit_and_offset)
167
168
  if returns_array?
168
- range = slice_range(limit_and_offset)
169
169
  rows.each{|o| o.associations[name] = o.associations[name][range] || []}
170
- elsif sr = slice_range(limit_and_offset)
171
- offset = sr.begin
170
+ else
171
+ offset = range.begin
172
172
  rows.each{|o| o.associations[name] = o.associations[name][offset]}
173
173
  end
174
174
  end
@@ -356,7 +356,7 @@ module Sequel
356
356
  def finalize
357
357
  return unless cache = self[:cache]
358
358
 
359
- finalize_settings.each do |meth, key|
359
+ finalizer = proc do |meth, key|
360
360
  next if has_key?(key)
361
361
 
362
362
  # Allow calling private methods to make sure caching is done appropriately
@@ -364,6 +364,13 @@ module Sequel
364
364
  self[key] = cache.delete(key) if cache.has_key?(key)
365
365
  end
366
366
 
367
+ finalize_settings.each(&finalizer)
368
+
369
+ unless self[:instance_specific]
370
+ finalizer.call(:associated_eager_dataset, :associated_eager_dataset)
371
+ finalizer.call(:filter_by_associations_conditions_dataset, :filter_by_associations_conditions_dataset)
372
+ end
373
+
367
374
  nil
368
375
  end
369
376
 
@@ -371,9 +378,7 @@ module Sequel
371
378
  FINALIZE_SETTINGS = {
372
379
  :associated_class=>:class,
373
380
  :associated_dataset=>:_dataset,
374
- :associated_eager_dataset=>:associated_eager_dataset,
375
381
  :eager_limit_strategy=>:_eager_limit_strategy,
376
- :filter_by_associations_conditions_dataset=>:filter_by_associations_conditions_dataset,
377
382
  :placeholder_loader=>:placeholder_loader,
378
383
  :predicate_key=>:predicate_key,
379
384
  :predicate_keys=>:predicate_keys,
@@ -432,7 +437,11 @@ module Sequel
432
437
  if use_placeholder_loader?
433
438
  cached_fetch(:placeholder_loader) do
434
439
  Sequel::Dataset::PlaceholderLiteralizer.loader(associated_dataset) do |pl, ds|
435
- ds.where(Sequel.&(*predicate_keys.map{|k| SQL::BooleanExpression.new(:'=', k, pl.arg)}))
440
+ ds = ds.where(Sequel.&(*predicate_keys.map{|k| SQL::BooleanExpression.new(:'=', k, pl.arg)}))
441
+ if self[:block]
442
+ ds = self[:block].call(ds)
443
+ end
444
+ ds
436
445
  end
437
446
  end
438
447
  end
@@ -633,10 +642,15 @@ module Sequel
633
642
  ds = ds.eager(associations)
634
643
  end
635
644
  if block = eo[:eager_block]
645
+ orig_ds = ds
636
646
  ds = block.call(ds)
637
647
  end
638
648
  if eager_loading_use_associated_key?
639
- ds = ds.select_append(*associated_key_array)
649
+ ds = if ds.opts[:eager_graph] && !orig_ds.opts[:eager_graph]
650
+ block.call(orig_ds.select_append(*associated_key_array))
651
+ else
652
+ ds.select_append(*associated_key_array)
653
+ end
640
654
  end
641
655
  if self[:eager_graph]
642
656
  raise(Error, "cannot eagerly load a #{self[:type]} association that uses :eager_graph") if eager_loading_use_associated_key?
@@ -747,8 +761,8 @@ module Sequel
747
761
 
748
762
  # If +s+ is an array, map +s+ over the block. Otherwise, just call the
749
763
  # block with +s+.
750
- def transform(s)
751
- s.is_a?(Array) ? s.map(&Proc.new) : (yield s)
764
+ def transform(s, &block)
765
+ s.is_a?(Array) ? s.map(&block) : (yield s)
752
766
  end
753
767
 
754
768
  # What eager limit strategy should be used when true is given as the value,
@@ -791,7 +805,7 @@ module Sequel
791
805
 
792
806
  # Whether the placeholder loader can be used to load the association.
793
807
  def use_placeholder_loader?
794
- !self[:instance_specific] && !self[:eager_graph]
808
+ self[:use_placeholder_loader]
795
809
  end
796
810
  end
797
811
 
@@ -1239,7 +1253,9 @@ module Sequel
1239
1253
  else
1240
1254
  assoc_record.values.delete(left_key_alias)
1241
1255
  end
1242
- next unless objects = h[hash_key]
1256
+
1257
+ objects = h[hash_key]
1258
+
1243
1259
  if assign_singular
1244
1260
  objects.each do |object|
1245
1261
  object.associations[name] ||= assoc_record
@@ -1612,9 +1628,10 @@ module Sequel
1612
1628
  # is hash or array of two element arrays. Consider also specifying the :graph_block
1613
1629
  # option if the value for this option is not a hash or array of two element arrays
1614
1630
  # and you plan to use this association in eager_graph or association_join.
1615
- # :dataset :: A proc that is instance_execed to get the base dataset to use (before the other
1631
+ # :dataset :: A proc that is used to define the method to get the base dataset to use (before the other
1616
1632
  # options are applied). If the proc accepts an argument, it is passed the related
1617
- # association reflection.
1633
+ # association reflection. It is a best practice to always have the dataset accept an argument
1634
+ # and use the argument to return the appropriate dataset.
1618
1635
  # :distinct :: Use the DISTINCT clause when selecting associating object, both when
1619
1636
  # lazy loading and eager loading via .eager (but not when using .eager_graph).
1620
1637
  # :eager :: The associations to eagerly load via +eager+ when loading the associated object(s).
@@ -1785,11 +1802,12 @@ module Sequel
1785
1802
  opts.merge!(:type => type, :name => name, :cache=>({} if cache_associations), :model => self)
1786
1803
 
1787
1804
  opts[:block] = block if block
1788
- if !opts.has_key?(:instance_specific) && (block || orig_opts[:block] || orig_opts[:dataset])
1805
+ opts[:instance_specific] = true if orig_opts[:dataset]
1806
+ if !opts.has_key?(:instance_specific) && (block || orig_opts[:block])
1789
1807
  # It's possible the association is instance specific, in that it depends on
1790
1808
  # values other than the foreign key value. This needs to be checked for
1791
1809
  # in certain places to disable optimizations.
1792
- opts[:instance_specific] = true
1810
+ opts[:instance_specific] = _association_instance_specific_default(name)
1793
1811
  end
1794
1812
  opts = assoc_class.new.merge!(opts)
1795
1813
 
@@ -1797,6 +1815,7 @@ module Sequel
1797
1815
  raise(Error, "cannot clone an association to an association of different type (association #{name} with type #{type} cloning #{opts[:clone]} with type #{cloned_assoc[:type]})")
1798
1816
  end
1799
1817
 
1818
+ opts[:use_placeholder_loader] = !opts[:instance_specific] && !opts[:eager_graph]
1800
1819
  opts[:eager_block] = opts[:block] unless opts.include?(:eager_block)
1801
1820
  opts[:graph_join_type] ||= :left_outer
1802
1821
  opts[:order_eager_graph] = true unless opts.include?(:order_eager_graph)
@@ -1893,6 +1912,12 @@ module Sequel
1893
1912
  Plugins.def_dataset_methods(self, [:eager, :eager_graph, :eager_graph_with_options, :association_join, :association_full_join, :association_inner_join, :association_left_join, :association_right_join])
1894
1913
 
1895
1914
  private
1915
+
1916
+ # The default value for the instance_specific option, if the association
1917
+ # could be instance specific and the :instance_specific option is not specified.
1918
+ def _association_instance_specific_default(_)
1919
+ true
1920
+ end
1896
1921
 
1897
1922
  # The module to use for the association's methods. Defaults to
1898
1923
  # the overridable_methods_module.
@@ -1904,7 +1929,7 @@ module Sequel
1904
1929
  # can be easily overridden in the class itself while allowing for
1905
1930
  # super to be called.
1906
1931
  def association_module_def(name, opts=OPTS, &block)
1907
- association_module(opts).module_eval{define_method(name, &block)}
1932
+ association_module(opts).send(:define_method, name, &block)
1908
1933
  end
1909
1934
 
1910
1935
  # Add a private method to the module included in the class.
@@ -1939,6 +1964,11 @@ module Sequel
1939
1964
  end
1940
1965
 
1941
1966
  association_module_def(opts.dataset_method, opts){_dataset(opts)}
1967
+ if opts[:block]
1968
+ opts[:block_method] = Plugins.def_sequel_method(association_module(opts), "#{opts[:name]}_block", 1, &opts[:block])
1969
+ end
1970
+ opts[:dataset_opt_arity] = opts[:dataset].arity == 0 ? 0 : 1
1971
+ opts[:dataset_opt_method] = Plugins.def_sequel_method(association_module(opts), "#{opts[:name]}_dataset_opt", opts[:dataset_opt_arity], &opts[:dataset])
1942
1972
  def_association_method(opts)
1943
1973
 
1944
1974
  return if opts[:read_only]
@@ -2109,9 +2139,7 @@ module Sequel
2109
2139
 
2110
2140
  eager_load_results(opts, eo) do |assoc_record|
2111
2141
  hash_key = uses_cks ? pk_meths.map{|k| assoc_record.get_column_value(k)} : assoc_record.get_column_value(opts.primary_key_method)
2112
- if objects = h[hash_key]
2113
- objects.each{|object| object.associations[name] = assoc_record}
2114
- end
2142
+ h[hash_key].each{|object| object.associations[name] = assoc_record}
2115
2143
  end
2116
2144
  end
2117
2145
 
@@ -2124,7 +2152,7 @@ module Sequel
2124
2152
  graph_cks = opts[:graph_keys]
2125
2153
  opts[:eager_grapher] ||= proc do |eo|
2126
2154
  ds = eo[:self]
2127
- ds.graph(eager_graph_dataset(opts, eo), use_only_conditions ? only_conditions : opts.primary_keys.zip(graph_cks) + conditions, Hash[eo].merge!(:select=>select, :join_type=>eo[:join_type]||join_type, :qualify=>:deep, :from_self_alias=>eo[:from_self_alias]), &graph_block)
2155
+ ds.graph(eager_graph_dataset(opts, eo), use_only_conditions ? only_conditions : opts.primary_keys.zip(graph_cks) + conditions, eo.merge(:select=>select, :join_type=>eo[:join_type]||join_type, :qualify=>:deep), &graph_block)
2128
2156
  end
2129
2157
 
2130
2158
  return if opts[:read_only]
@@ -2158,7 +2186,7 @@ module Sequel
2158
2186
  eager_load_results(opts, eo) do |assoc_record|
2159
2187
  assoc_record.values.delete(delete_rn) if delete_rn
2160
2188
  hash_key = uses_cks ? km.map{|k| assoc_record.get_column_value(k)} : assoc_record.get_column_value(km)
2161
- next unless objects = h[hash_key]
2189
+ objects = h[hash_key]
2162
2190
  if assign_singular
2163
2191
  objects.each do |object|
2164
2192
  unless object.associations[name]
@@ -2184,7 +2212,7 @@ module Sequel
2184
2212
  graph_block = opts[:graph_block]
2185
2213
  opts[:eager_grapher] ||= proc do |eo|
2186
2214
  ds = eo[:self]
2187
- ds = ds.graph(opts.apply_eager_graph_limit_strategy(eo[:limit_strategy], eager_graph_dataset(opts, eo)), use_only_conditions ? only_conditions : cks.zip(pkcs) + conditions, Hash[eo].merge!(:select=>select, :join_type=>eo[:join_type]||join_type, :qualify=>:deep, :from_self_alias=>eo[:from_self_alias]), &graph_block)
2215
+ ds = ds.graph(opts.apply_eager_graph_limit_strategy(eo[:limit_strategy], eager_graph_dataset(opts, eo)), use_only_conditions ? only_conditions : cks.zip(pkcs) + conditions, eo.merge(:select=>select, :join_type=>eo[:join_type]||join_type, :qualify=>:deep), &graph_block)
2188
2216
  # We only load reciprocals for one_to_many associations, as other reciprocals don't make sense
2189
2217
  ds.opts[:eager_graph][:reciprocals][eo[:table_alias]] = opts.reciprocal
2190
2218
  ds
@@ -2199,12 +2227,28 @@ module Sequel
2199
2227
  if one_to_one
2200
2228
  opts[:setter] ||= proc do |o|
2201
2229
  up_ds = _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)})))
2230
+
2231
+ if (froms = up_ds.opts[:from]) && (from = froms[0]) && (from.is_a?(Sequel::Dataset) || (from.is_a?(Sequel::SQL::AliasedExpression) && from.expression.is_a?(Sequel::Dataset)))
2232
+ if old = up_ds.first
2233
+ cks.each{|k| old.set_column_value(:"#{k}=", nil)}
2234
+ end
2235
+ save_old = true
2236
+ end
2237
+
2202
2238
  if o
2203
- up_ds = up_ds.exclude(o.pk_hash) unless o.new?
2239
+ if !o.new? && !save_old
2240
+ up_ds = up_ds.exclude(o.pk_hash)
2241
+ end
2204
2242
  cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
2205
2243
  end
2244
+
2206
2245
  checked_transaction do
2207
- up_ds.skip_limit_check.update(ck_nil_hash)
2246
+ if save_old
2247
+ old.save(save_opts) || raise(Sequel::Error, "invalid previously associated object, cannot save") if old
2248
+ else
2249
+ up_ds.skip_limit_check.update(ck_nil_hash)
2250
+ end
2251
+
2208
2252
  o.save(save_opts) || raise(Sequel::Error, "invalid associated object, cannot save") if o
2209
2253
  end
2210
2254
  end
@@ -2282,7 +2326,8 @@ module Sequel
2282
2326
  end
2283
2327
  ds = ds.clone(:model_object => self)
2284
2328
  ds = ds.eager_graph(opts[:eager_graph]) if opts[:eager_graph] && opts.eager_graph_lazy_dataset?
2285
- ds = instance_exec(ds, &opts[:block]) if opts[:block]
2329
+ # block method is private
2330
+ ds = send(opts[:block_method], ds) if opts[:block_method]
2286
2331
  ds
2287
2332
  end
2288
2333
 
@@ -2305,10 +2350,11 @@ module Sequel
2305
2350
  # Return an association dataset for the given association reflection
2306
2351
  def _dataset(opts)
2307
2352
  raise(Sequel::Error, "model object #{inspect} does not have a primary key") if opts.dataset_need_primary_key? && !pk
2308
- ds = if opts[:dataset].arity == 1
2309
- instance_exec(opts, &opts[:dataset])
2353
+ ds = if opts[:dataset_opt_arity] == 1
2354
+ # dataset_opt_method is private
2355
+ send(opts[:dataset_opt_method], opts)
2310
2356
  else
2311
- instance_exec(&opts[:dataset])
2357
+ send(opts[:dataset_opt_method])
2312
2358
  end
2313
2359
  _apply_association_options(opts, ds)
2314
2360
  end
@@ -2401,7 +2447,32 @@ module Sequel
2401
2447
  # cached associations.
2402
2448
  def change_column_value(column, value)
2403
2449
  if assocs = model.autoreloading_associations[column]
2404
- assocs.each{|a| associations.delete(a)}
2450
+ vals = @values
2451
+ if new?
2452
+ # Do deeper checking for new objects, so that associations are
2453
+ # not deleted when values do not change. This code is run at
2454
+ # a higher level for existing objects.
2455
+ if value == (c = vals[column]) && value.class == c.class
2456
+ # If the value is the same, there is no reason to delete
2457
+ # the related associations, so exit early in that case.
2458
+ return super
2459
+ end
2460
+
2461
+ only_delete_nil = c.nil?
2462
+ elsif vals[column].nil?
2463
+ only_delete_nil = true
2464
+ end
2465
+
2466
+ if only_delete_nil
2467
+ # If the current foreign key value is nil, but the association
2468
+ # is already present in the cache, it was probably added to the
2469
+ # cache for a reason, and we do not want to delete it in that case.
2470
+ # However, we still want to delete associations with nil values
2471
+ # to remove the cached false negative.
2472
+ assocs.each{|a| associations.delete(a) if associations[a].nil?}
2473
+ else
2474
+ assocs.each{|a| associations.delete(a)}
2475
+ end
2405
2476
  end
2406
2477
  super
2407
2478
  end
@@ -2567,13 +2638,25 @@ module Sequel
2567
2638
  # Set the given object as the associated object for the given *_to_one association reflection
2568
2639
  def _set_associated_object(opts, o)
2569
2640
  a = associations[opts[:name]]
2570
- return if a && a == o && !set_associated_object_if_same?
2641
+ reciprocal = opts.reciprocal
2642
+ if set_associated_object_if_same?
2643
+ if reciprocal
2644
+ remove_reciprocal = a && (a != o || a.associations[reciprocal] != self)
2645
+ add_reciprocal = o && o.associations[reciprocal] != self
2646
+ end
2647
+ else
2648
+ return if a && a == o
2649
+ if reciprocal
2650
+ remove_reciprocal = a
2651
+ add_reciprocal = o
2652
+ end
2653
+ end
2571
2654
  run_association_callbacks(opts, :before_set, o)
2572
- remove_reciprocal_object(opts, a) if a
2655
+ remove_reciprocal_object(opts, a) if remove_reciprocal
2573
2656
  # Allow calling private _setter method
2574
2657
  send(opts[:_setter_method], o)
2575
2658
  associations[opts[:name]] = o
2576
- add_reciprocal_object(opts, o) if o
2659
+ add_reciprocal_object(opts, o) if add_reciprocal
2577
2660
  run_association_callbacks(opts, :after_set, o)
2578
2661
  o
2579
2662
  end
@@ -2628,7 +2711,7 @@ module Sequel
2628
2711
  # Album.eager(:artist, :genre).all
2629
2712
  # Album.eager_graph(:artist, :genre).all
2630
2713
  # Album.eager(:artist).eager(:genre).all
2631
- # Album.eager_graph(:artist).eager(:genre).all
2714
+ # Album.eager_graph(:artist).eager_graph(:genre).all
2632
2715
  # Artist.eager(albums: :tracks).all
2633
2716
  # Artist.eager_graph(albums: :tracks).all
2634
2717
  # Artist.eager(albums: {tracks: :genre}).all
@@ -2661,13 +2744,79 @@ module Sequel
2661
2744
  end
2662
2745
 
2663
2746
  # Adds one or more INNER JOINs to the existing dataset using the keys and conditions
2664
- # specified by the given association. The following methods also exist for specifying
2665
- # a different type of JOIN:
2747
+ # specified by the given association(s). Take the same arguments as eager_graph, and
2748
+ # operates similarly, but only adds the joins as opposed to making the other changes
2749
+ # (such as adding selected columns and setting up eager loading).
2750
+ #
2751
+ # The following methods also exist for specifying a different type of JOIN:
2666
2752
  #
2667
2753
  # association_full_join :: FULL JOIN
2668
2754
  # association_inner_join :: INNER JOIN
2669
2755
  # association_left_join :: LEFT JOIN
2670
2756
  # association_right_join :: RIGHT JOIN
2757
+ #
2758
+ # Examples:
2759
+ #
2760
+ # # For each album, association_join load the artist
2761
+ # Album.association_join(:artist).all
2762
+ # # SELECT *
2763
+ # # FROM albums
2764
+ # # INNER JOIN artists AS artist ON (artists.id = albums.artist_id)
2765
+ #
2766
+ # # For each album, association_join load the artist, using a specified alias
2767
+ # Album.association_join(Sequel[:artist].as(:a)).all
2768
+ # # SELECT *
2769
+ # # FROM albums
2770
+ # # INNER JOIN artists AS a ON (a.id = albums.artist_id)
2771
+ #
2772
+ # # For each album, association_join load the artist and genre
2773
+ # Album.association_join(:artist, :genre).all
2774
+ # Album.association_join(:artist).association_join(:genre).all
2775
+ # # SELECT *
2776
+ # # FROM albums
2777
+ # # INNER JOIN artists AS artist ON (artist.id = albums.artist_id)
2778
+ # # INNER JOIN genres AS genre ON (genre.id = albums.genre_id)
2779
+ #
2780
+ # # For each artist, association_join load albums and tracks for each album
2781
+ # Artist.association_join(albums: :tracks).all
2782
+ # # SELECT *
2783
+ # # FROM artists
2784
+ # # INNER JOIN albums ON (albums.artist_id = artists.id)
2785
+ # # INNER JOIN tracks ON (tracks.album_id = albums.id)
2786
+ #
2787
+ # # For each artist, association_join load albums, tracks for each album, and genre for each track
2788
+ # Artist.association_join(albums: {tracks: :genre}).all
2789
+ # # SELECT *
2790
+ # # FROM artists
2791
+ # # INNER JOIN albums ON (albums.artist_id = artists.id)
2792
+ # # INNER JOIN tracks ON (tracks.album_id = albums.id)
2793
+ # # INNER JOIN genres AS genre ON (genre.id = tracks.genre_id)
2794
+ #
2795
+ # # For each artist, association_join load albums with year > 1990
2796
+ # Artist.association_join(albums: proc{|ds| ds.where{year > 1990}}).all
2797
+ # # SELECT *
2798
+ # # FROM artists
2799
+ # # INNER JOIN (
2800
+ # # SELECT * FROM albums WHERE (year > 1990)
2801
+ # # ) AS albums ON (albums.artist_id = artists.id)
2802
+ #
2803
+ # # For each artist, association_join load albums and tracks 1-10 for each album
2804
+ # Artist.association_join(albums: {tracks: proc{|ds| ds.where(number: 1..10)}}).all
2805
+ # # SELECT *
2806
+ # # FROM artists
2807
+ # # INNER JOIN albums ON (albums.artist_id = artists.id)
2808
+ # # INNER JOIN (
2809
+ # # SELECT * FROM tracks WHERE ((number >= 1) AND (number <= 10))
2810
+ # # ) AS tracks ON (tracks.albums_id = albums.id)
2811
+ #
2812
+ # # For each artist, association_join load albums with year > 1990, and tracks for those albums
2813
+ # Artist.association_join(albums: {proc{|ds| ds.where{year > 1990}}=>:tracks}).all
2814
+ # # SELECT *
2815
+ # # FROM artists
2816
+ # # INNER JOIN (
2817
+ # # SELECT * FROM albums WHERE (year > 1990)
2818
+ # # ) AS albums ON (albums.artist_id = artists.id)
2819
+ # # INNER JOIN tracks ON (tracks.album_id = albums.id)
2671
2820
  def association_join(*associations)
2672
2821
  association_inner_join(*associations)
2673
2822
  end
@@ -2681,8 +2830,8 @@ module Sequel
2681
2830
  # creates a subquery to the join table.
2682
2831
  def complex_expression_sql_append(sql, op, args)
2683
2832
  r = args[1]
2684
- if (((op == :'=' || op == :'!=') and r.is_a?(Sequel::Model)) ||
2685
- (multiple = ((op == :IN || op == :'NOT IN') and ((is_ds = r.is_a?(Sequel::Dataset)) or r.all?{|x| x.is_a?(Sequel::Model)}))))
2833
+ if (((op == :'=' || op == :'!=') && r.is_a?(Sequel::Model)) ||
2834
+ (multiple = ((op == :IN || op == :'NOT IN') && ((is_ds = r.is_a?(Sequel::Dataset)) || (r.respond_to?(:all?) && r.all?{|x| x.is_a?(Sequel::Model)})))))
2686
2835
  l = args[0]
2687
2836
  if ar = model.association_reflections[l]
2688
2837
  if multiple
@@ -2747,11 +2896,61 @@ module Sequel
2747
2896
  #
2748
2897
  # Each association's order, if defined, is respected.
2749
2898
  # If the association uses a block or has an :eager_block argument, it is used.
2899
+ #
2900
+ # To modify the associated dataset that will be used for the eager load, you should use a
2901
+ # hash for the association, with the key being the association name symbol, and the value being
2902
+ # a callable object that is called with the associated dataset and should return a modified
2903
+ # dataset. If that association also has dependent associations, instead of a callable object,
2904
+ # use a hash with the callable object being the key, and the dependent association(s) as the value.
2905
+ #
2906
+ # Examples:
2907
+ #
2908
+ # # For each album, eager load the artist
2909
+ # Album.eager(:artist).all
2910
+ # # SELECT * FROM albums
2911
+ # # SELECT * FROM artists WHERE (id IN (...))
2912
+ #
2913
+ # # For each album, eager load the artist and genre
2914
+ # Album.eager(:artist, :genre).all
2915
+ # Album.eager(:artist).eager(:genre).all
2916
+ # # SELECT * FROM albums
2917
+ # # SELECT * FROM artists WHERE (id IN (...))
2918
+ # # SELECT * FROM genres WHERE (id IN (...))
2919
+ #
2920
+ # # For each artist, eager load albums and tracks for each album
2921
+ # Artist.eager(albums: :tracks).all
2922
+ # # SELECT * FROM artists
2923
+ # # SELECT * FROM albums WHERE (artist_id IN (...))
2924
+ # # SELECT * FROM tracks WHERE (album_id IN (...))
2925
+ #
2926
+ # # For each artist, eager load albums, tracks for each album, and genre for each track
2927
+ # Artist.eager(albums: {tracks: :genre}).all
2928
+ # # SELECT * FROM artists
2929
+ # # SELECT * FROM albums WHERE (artist_id IN (...))
2930
+ # # SELECT * FROM tracks WHERE (album_id IN (...))
2931
+ # # SELECT * FROM genre WHERE (id IN (...))
2932
+ #
2933
+ # # For each artist, eager load albums with year > 1990
2934
+ # Artist.eager(albums: proc{|ds| ds.where{year > 1990}}).all
2935
+ # # SELECT * FROM artists
2936
+ # # SELECT * FROM albums WHERE ((year > 1990) AND (artist_id IN (...)))
2937
+ #
2938
+ # # For each artist, eager load albums and tracks 1-10 for each album
2939
+ # Artist.eager(albums: {tracks: proc{|ds| ds.where(number: 1..10)}}).all
2940
+ # # SELECT * FROM artists
2941
+ # # SELECT * FROM albums WHERE (artist_id IN (...))
2942
+ # # SELECT * FROM tracks WHERE ((number >= 1) AND (number <= 10) AND (album_id IN (...)))
2943
+ #
2944
+ # # For each artist, eager load albums with year > 1990, and tracks for those albums
2945
+ # Artist.eager(albums: {proc{|ds| ds.where{year > 1990}}=>:tracks}).all
2946
+ # # SELECT * FROM artists
2947
+ # # SELECT * FROM albums WHERE ((year > 1990) AND (artist_id IN (...)))
2948
+ # # SELECT * FROM albums WHERE (artist_id IN (...))
2750
2949
  def eager(*associations)
2751
2950
  opts = @opts[:eager]
2752
2951
  association_opts = eager_options_for_associations(associations)
2753
- opts = opts ? Hash[opts].merge!(association_opts) : association_opts
2754
- clone(:eager=>opts)
2952
+ opts = opts ? opts.merge(association_opts) : association_opts
2953
+ clone(:eager=>opts.freeze)
2755
2954
  end
2756
2955
 
2757
2956
  # The secondary eager loading method. Loads all associations in a single query. This
@@ -2775,6 +2974,86 @@ module Sequel
2775
2974
  #
2776
2975
  # Like +eager+, you need to call +all+ on the dataset for the eager loading to work. If you just
2777
2976
  # call +each+, it will yield plain hashes, each containing all columns from all the tables.
2977
+ #
2978
+ # To modify the associated dataset that will be joined to the current dataset, you should use a
2979
+ # hash for the association, with the key being the association name symbol, and the value being
2980
+ # a callable object that is called with the associated dataset and should return a modified
2981
+ # dataset. If that association also has dependent associations, instead of a callable object,
2982
+ # use a hash with the callable object being the key, and the dependent association(s) as the value.
2983
+ #
2984
+ # You can specify an custom alias and/or join type on a per-association basis by providing an
2985
+ # Sequel::SQL::AliasedExpression object instead of an a Symbol for the association name.
2986
+ #
2987
+ # Examples:
2988
+ #
2989
+ # # For each album, eager_graph load the artist
2990
+ # Album.eager_graph(:artist).all
2991
+ # # SELECT ...
2992
+ # # FROM albums
2993
+ # # LEFT OUTER JOIN artists AS artist ON (artists.id = albums.artist_id)
2994
+ #
2995
+ # # For each album, eager_graph load the artist, using a specified alias
2996
+ # Album.eager_graph(Sequel[:artist].as(:a)).all
2997
+ # # SELECT ...
2998
+ # # FROM albums
2999
+ # # LEFT OUTER JOIN artists AS a ON (a.id = albums.artist_id)
3000
+ #
3001
+ # # For each album, eager_graph load the artist, using a specified alias
3002
+ # # and custom join type
3003
+ #
3004
+ # Album.eager_graph(Sequel[:artist].as(:a, join_type: :inner)).all
3005
+ # # SELECT ...
3006
+ # # FROM albums
3007
+ # # INNER JOIN artists AS a ON (a.id = albums.artist_id)
3008
+ #
3009
+ # # For each album, eager_graph load the artist and genre
3010
+ # Album.eager_graph(:artist, :genre).all
3011
+ # Album.eager_graph(:artist).eager_graph(:genre).all
3012
+ # # SELECT ...
3013
+ # # FROM albums
3014
+ # # LEFT OUTER JOIN artists AS artist ON (artist.id = albums.artist_id)
3015
+ # # LEFT OUTER JOIN genres AS genre ON (genre.id = albums.genre_id)
3016
+ #
3017
+ # # For each artist, eager_graph load albums and tracks for each album
3018
+ # Artist.eager_graph(albums: :tracks).all
3019
+ # # SELECT ...
3020
+ # # FROM artists
3021
+ # # LEFT OUTER JOIN albums ON (albums.artist_id = artists.id)
3022
+ # # LEFT OUTER JOIN tracks ON (tracks.album_id = albums.id)
3023
+ #
3024
+ # # For each artist, eager_graph load albums, tracks for each album, and genre for each track
3025
+ # Artist.eager_graph(albums: {tracks: :genre}).all
3026
+ # # SELECT ...
3027
+ # # FROM artists
3028
+ # # LEFT OUTER JOIN albums ON (albums.artist_id = artists.id)
3029
+ # # LEFT OUTER JOIN tracks ON (tracks.album_id = albums.id)
3030
+ # # LEFT OUTER JOIN genres AS genre ON (genre.id = tracks.genre_id)
3031
+ #
3032
+ # # For each artist, eager_graph load albums with year > 1990
3033
+ # Artist.eager_graph(albums: proc{|ds| ds.where{year > 1990}}).all
3034
+ # # SELECT ...
3035
+ # # FROM artists
3036
+ # # LEFT OUTER JOIN (
3037
+ # # SELECT * FROM albums WHERE (year > 1990)
3038
+ # # ) AS albums ON (albums.artist_id = artists.id)
3039
+ #
3040
+ # # For each artist, eager_graph load albums and tracks 1-10 for each album
3041
+ # Artist.eager_graph(albums: {tracks: proc{|ds| ds.where(number: 1..10)}}).all
3042
+ # # SELECT ...
3043
+ # # FROM artists
3044
+ # # LEFT OUTER JOIN albums ON (albums.artist_id = artists.id)
3045
+ # # LEFT OUTER JOIN (
3046
+ # # SELECT * FROM tracks WHERE ((number >= 1) AND (number <= 10))
3047
+ # # ) AS tracks ON (tracks.albums_id = albums.id)
3048
+ #
3049
+ # # For each artist, eager_graph load albums with year > 1990, and tracks for those albums
3050
+ # Artist.eager_graph(albums: {proc{|ds| ds.where{year > 1990}}=>:tracks}).all
3051
+ # # SELECT ...
3052
+ # # FROM artists
3053
+ # # LEFT OUTER JOIN (
3054
+ # # SELECT * FROM albums WHERE (year > 1990)
3055
+ # # ) AS albums ON (albums.artist_id = artists.id)
3056
+ # # LEFT OUTER JOIN tracks ON (tracks.album_id = albums.id)
2778
3057
  def eager_graph(*associations)
2779
3058
  eager_graph_with_options(associations)
2780
3059
  end
@@ -2800,8 +3079,11 @@ module Sequel
2800
3079
  # significantly slower in some cases (perhaps even the majority of cases), so you should
2801
3080
  # only use this if you have benchmarked that it is faster for your use cases.
2802
3081
  def eager_graph_with_options(associations, opts=OPTS)
3082
+ return self if associations.empty?
3083
+
3084
+ opts = opts.dup unless opts.frozen?
2803
3085
  associations = [associations] unless associations.is_a?(Array)
2804
- if eg = @opts[:eager_graph]
3086
+ ds = if eg = @opts[:eager_graph]
2805
3087
  eg = eg.dup
2806
3088
  [:requirements, :reflections, :reciprocals, :limits].each{|k| eg[k] = eg[k].dup}
2807
3089
  eg[:local] = opts
@@ -2815,8 +3097,12 @@ module Sequel
2815
3097
  # :limits :: Any limit/offset array slicing that need to be handled in ruby land after loading
2816
3098
  opts = {:requirements=>{}, :master=>alias_symbol(first_source), :reflections=>{}, :reciprocals=>{}, :limits=>{}, :local=>opts, :cartesian_product_number=>0, :row_proc=>row_proc}
2817
3099
  ds = clone(:eager_graph=>opts)
2818
- ds.eager_graph_associations(ds, model, ds.opts[:eager_graph][:master], [], *associations).naked
3100
+ ds = ds.eager_graph_associations(ds, model, ds.opts[:eager_graph][:master], [], *associations).naked
2819
3101
  end
3102
+
3103
+ ds.opts[:eager_graph].freeze
3104
+ ds.opts[:eager_graph].each_value{|v| v.freeze if v.is_a?(Hash)}
3105
+ ds
2820
3106
  end
2821
3107
 
2822
3108
  # If the dataset is being eagerly loaded, default to calling all
@@ -2864,11 +3150,16 @@ module Sequel
2864
3150
  # ta :: table_alias used for the parent association
2865
3151
  # requirements :: an array, used as a stack for requirements
2866
3152
  # r :: association reflection for the current association, or an SQL::AliasedExpression
2867
- # with the reflection as the expression and the alias base as the aliaz.
3153
+ # with the reflection as the expression, the alias base as the alias (or nil to
3154
+ # use the default alias), and an optional hash with a :join_type entry as the columns
3155
+ # to use a custom join type.
2868
3156
  # *associations :: any associations dependent on this one
2869
3157
  def eager_graph_association(ds, model, ta, requirements, r, *associations)
2870
3158
  if r.is_a?(SQL::AliasedExpression)
2871
3159
  alias_base = r.alias
3160
+ if r.columns.is_a?(Hash)
3161
+ join_type = r.columns[:join_type]
3162
+ end
2872
3163
  r = r.expression
2873
3164
  else
2874
3165
  alias_base = r[:graph_alias_base]
@@ -2891,7 +3182,7 @@ module Sequel
2891
3182
  raise Error, "Cannot eager_graph association when :conditions specified and not a hash or an array of pairs. Specify :graph_conditions, :graph_only_conditions, or :graph_block for the association. Model: #{r[:model]}, association: #{r[:name]}"
2892
3183
  end
2893
3184
 
2894
- ds = loader.call(:self=>ds, :table_alias=>assoc_table_alias, :implicit_qualifier=>(ta == ds.opts[:eager_graph][:master]) ? first_source : qualifier_from_alias_symbol(ta, first_source), :callback=>callback, :join_type=>local_opts[:join_type], :join_only=>local_opts[:join_only], :limit_strategy=>limit_strategy, :from_self_alias=>ds.opts[:eager_graph][:master])
3185
+ ds = loader.call(:self=>ds, :table_alias=>assoc_table_alias, :implicit_qualifier=>(ta == ds.opts[:eager_graph][:master]) ? first_source : qualifier_from_alias_symbol(ta, first_source), :callback=>callback, :join_type=>join_type || local_opts[:join_type], :join_only=>local_opts[:join_only], :limit_strategy=>limit_strategy, :from_self_alias=>ds.opts[:eager_graph][:master])
2895
3186
  if r[:order_eager_graph] && (order = r.fetch(:graph_order, r[:order]))
2896
3187
  ds = ds.order_append(*qualified_expression(order, assoc_table_alias))
2897
3188
  end
@@ -2916,7 +3207,6 @@ module Sequel
2916
3207
  # requirements :: an array, used as a stack for requirements
2917
3208
  # *associations :: the associations to add to the graph
2918
3209
  def eager_graph_associations(ds, model, ta, requirements, *associations)
2919
- return ds if associations.empty?
2920
3210
  associations.flatten.each do |association|
2921
3211
  ds = case association
2922
3212
  when Symbol, SQL::AliasedExpression
@@ -2936,7 +3226,7 @@ module Sequel
2936
3226
  # Replace the array of plain hashes with an array of model objects will all eager_graphed
2937
3227
  # associations set in the associations cache for each object.
2938
3228
  def eager_graph_build_associations(hashes)
2939
- hashes.replace(EagerGraphLoader.new(self).load(hashes))
3229
+ hashes.replace(_eager_graph_build_associations(hashes, eager_graph_loader))
2940
3230
  end
2941
3231
 
2942
3232
  private
@@ -2947,6 +3237,12 @@ module Sequel
2947
3237
  clone(:join=>clone(:graph_from_self=>false).eager_graph_with_options(associations, :join_type=>type, :join_only=>true).opts[:join])
2948
3238
  end
2949
3239
 
3240
+ # Process the array of hashes using the eager graph loader to return an array
3241
+ # of model objects with the associations set.
3242
+ def _eager_graph_build_associations(hashes, egl)
3243
+ egl.load(hashes)
3244
+ end
3245
+
2950
3246
  # If the association has conditions itself, then it requires additional filters be
2951
3247
  # added to the current dataset to ensure that the passed in object would also be
2952
3248
  # included by the association's conditions.
@@ -3032,12 +3328,28 @@ module Sequel
3032
3328
  # per-call determining of the alias base.
3033
3329
  def eager_graph_check_association(model, association)
3034
3330
  if association.is_a?(SQL::AliasedExpression)
3035
- SQL::AliasedExpression.new(check_association(model, association.expression), association.alias)
3331
+ expr = association.expression
3332
+ if expr.is_a?(SQL::Identifier)
3333
+ expr = expr.value
3334
+ if expr.is_a?(String)
3335
+ expr = expr.to_sym
3336
+ end
3337
+ end
3338
+
3339
+ SQL::AliasedExpression.new(check_association(model, expr), association.alias || expr, association.columns)
3036
3340
  else
3037
3341
  check_association(model, association)
3038
3342
  end
3039
3343
  end
3040
3344
 
3345
+ # The EagerGraphLoader instance used for converting eager_graph results.
3346
+ def eager_graph_loader
3347
+ unless egl = cache_get(:_model_eager_graph_loader)
3348
+ egl = cache_set(:_model_eager_graph_loader, EagerGraphLoader.new(self))
3349
+ end
3350
+ egl.dup
3351
+ end
3352
+
3041
3353
  # Eagerly load all specified associations
3042
3354
  def eager_load(a, eager_assoc=@opts[:eager])
3043
3355
  return if a.empty?
@@ -3081,7 +3393,7 @@ module Sequel
3081
3393
  associations = eager_assoc[r[:name]]
3082
3394
  if associations.respond_to?(:call)
3083
3395
  eager_block = associations
3084
- associations = {}
3396
+ associations = OPTS
3085
3397
  elsif associations.is_a?(Hash) && associations.length == 1 && (pr_assoc = associations.to_a.first) && pr_assoc.first.respond_to?(:call)
3086
3398
  eager_block, associations = pr_assoc
3087
3399
  end
@@ -3175,7 +3487,6 @@ module Sequel
3175
3487
  # Hash with table alias symbol keys and [limit, offset] values
3176
3488
  attr_reader :limit_map
3177
3489
 
3178
- # Hash with table alias symbol keys and callable values used to create model instances
3179
3490
  # The table alias symbol for the primary model
3180
3491
  attr_reader :master
3181
3492
 
@@ -3225,12 +3536,15 @@ module Sequel
3225
3536
  :offset
3226
3537
  end
3227
3538
  end
3539
+ after_load_map.freeze
3540
+ alias_map.freeze
3541
+ type_map.freeze
3228
3542
 
3229
3543
  # Make dependency map hash out of requirements array for each association.
3230
3544
  # This builds a tree of dependencies that will be used for recursion
3231
3545
  # to ensure that all parts of the object graph are loaded into the
3232
3546
  # appropriate subordinate association.
3233
- @dependency_map = {}
3547
+ dependency_map = @dependency_map = {}
3234
3548
  # Sort the associations by requirements length, so that
3235
3549
  # requirements are added to the dependency hash before their
3236
3550
  # dependencies.
@@ -3246,18 +3560,12 @@ module Sequel
3246
3560
  hash[ta] = {}
3247
3561
  end
3248
3562
  end
3563
+ freezer = lambda do |h|
3564
+ h.freeze
3565
+ h.each_value(&freezer)
3566
+ end
3567
+ freezer.call(dependency_map)
3249
3568
 
3250
- # This mapping is used to make sure that duplicate entries in the
3251
- # result set are mapped to a single record. For example, using a
3252
- # single one_to_many association with 10 associated records,
3253
- # the main object column values appear in the object graph 10 times.
3254
- # We map by primary key, if available, or by the object's entire values,
3255
- # if not. The mapping must be per table, so create sub maps for each table
3256
- # alias.
3257
- records_map = {@master=>{}}
3258
- alias_map.keys.each{|ta| records_map[ta] = {}}
3259
- @records_map = records_map
3260
-
3261
3569
  datasets = opts[:graph][:table_aliases].to_a.reject{|ta,ds| ds.nil?}
3262
3570
  column_aliases = opts[:graph][:column_aliases]
3263
3571
  primary_keys = {}
@@ -3283,9 +3591,9 @@ module Sequel
3283
3591
  h.select{|ca, c| primary_keys[ta] = ca if pk == c}
3284
3592
  end
3285
3593
  end
3286
- @column_maps = column_maps
3287
- @primary_keys = primary_keys
3288
- @row_procs = row_procs
3594
+ @column_maps = column_maps.freeze
3595
+ @primary_keys = primary_keys.freeze
3596
+ @row_procs = row_procs.freeze
3289
3597
 
3290
3598
  # For performance, create two special maps for the master table,
3291
3599
  # so you can skip a hash lookup.
@@ -3297,22 +3605,35 @@ module Sequel
3297
3605
  # used for performance, to get all values in one hash lookup instead of
3298
3606
  # separate hash lookups for each data structure.
3299
3607
  ta_map = {}
3300
- alias_map.keys.each do |ta|
3301
- ta_map[ta] = [records_map[ta], row_procs[ta], alias_map[ta], type_map[ta], reciprocal_map[ta]]
3608
+ alias_map.each_key do |ta|
3609
+ ta_map[ta] = [row_procs[ta], alias_map[ta], type_map[ta], reciprocal_map[ta]].freeze
3302
3610
  end
3303
- @ta_map = ta_map
3611
+ @ta_map = ta_map.freeze
3612
+ freeze
3304
3613
  end
3305
3614
 
3306
3615
  # Return an array of primary model instances with the associations cache prepopulated
3307
3616
  # for all model objects (both primary and associated).
3308
3617
  def load(hashes)
3618
+ # This mapping is used to make sure that duplicate entries in the
3619
+ # result set are mapped to a single record. For example, using a
3620
+ # single one_to_many association with 10 associated records,
3621
+ # the main object column values appear in the object graph 10 times.
3622
+ # We map by primary key, if available, or by the object's entire values,
3623
+ # if not. The mapping must be per table, so create sub maps for each table
3624
+ # alias.
3625
+ @records_map = records_map = {}
3626
+ alias_map.keys.each{|ta| records_map[ta] = {}}
3627
+
3309
3628
  master = master()
3310
3629
 
3311
3630
  # Assign to local variables for speed increase
3312
3631
  rp = row_procs[master]
3313
- rm = records_map[master]
3632
+ rm = records_map[master] = {}
3314
3633
  dm = dependency_map
3315
3634
 
3635
+ records_map.freeze
3636
+
3316
3637
  # This will hold the final record set that we will be replacing the object graph with.
3317
3638
  records = []
3318
3639
 
@@ -3333,6 +3654,9 @@ module Sequel
3333
3654
  # Run after_load procs if there are any
3334
3655
  post_process(records, dm) if @unique || !after_load_map.empty? || !limit_map.empty?
3335
3656
 
3657
+ records_map.each_value(&:freeze)
3658
+ freeze
3659
+
3336
3660
  records
3337
3661
  end
3338
3662
 
@@ -3352,7 +3676,17 @@ module Sequel
3352
3676
  end
3353
3677
  key = hkey(ta_h)
3354
3678
  end
3355
- rm, rp, assoc_name, tm, rcm = @ta_map[ta]
3679
+ rp, assoc_name, tm, rcm = @ta_map[ta]
3680
+ rm = records_map[ta]
3681
+
3682
+ # Check type map for all dependencies, and use a unique
3683
+ # object if any are dependencies for multiple objects,
3684
+ # to prevent duplicate objects from showing up in the case
3685
+ # the normal duplicate removal code is not being used.
3686
+ if !@unique && !deps.empty? && deps.any?{|dep_key,_| @ta_map[dep_key][2]}
3687
+ key = [current.object_id, key]
3688
+ end
3689
+
3356
3690
  unless rec = rm[key]
3357
3691
  rec = rm[key] = rp.call(hfor(ta, h))
3358
3692
  end