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
@@ -1,10 +1,15 @@
1
1
  module Sequel
2
2
  class Dataset
3
3
  # ---------------------
4
- # :section: Methods that return modified datasets
4
+ # :section: 1 - Methods that return modified datasets
5
5
  # These methods all return modified copies of the receiver.
6
6
  # ---------------------
7
7
 
8
+ # Hash of extension name symbols to callable objects to load the extension
9
+ # into the Dataset object (usually by extending it with a module defined
10
+ # in the extension).
11
+ EXTENSIONS = {}
12
+
8
13
  # The dataset options that require the removal of cached columns
9
14
  # if changed.
10
15
  COLUMN_CHANGE_OPTS = [:select, :sql, :from, :join].freeze
@@ -28,12 +33,35 @@ module Sequel
28
33
  JOIN_METHODS = (CONDITIONED_JOIN_TYPES + UNCONDITIONED_JOIN_TYPES).map{|x| "#{x}_join".to_sym} + [:join, :join_table]
29
34
 
30
35
  # Methods that return modified datasets
31
- QUERY_METHODS = %w'add_graph_aliases and distinct except exclude
32
- filter for_update from from_self graph grep group group_and_count group_by having intersect invert
33
- limit lock_style naked or order order_append order_by order_more order_prepend paginate qualify query
34
- reverse reverse_order select select_all select_append select_more server
35
- set_defaults set_graph_aliases set_overrides unfiltered ungraphed ungrouped union
36
- unlimited unordered where with with_recursive with_sql'.collect{|x| x.to_sym} + JOIN_METHODS
36
+ QUERY_METHODS = (<<-METHS).split.map{|x| x.to_sym} + JOIN_METHODS
37
+ add_graph_aliases and distinct except exclude exclude_having exclude_where
38
+ filter for_update from from_self graph grep group group_and_count group_by having intersect invert
39
+ limit lock_style naked or order order_append order_by order_more order_prepend paginate qualify query
40
+ reverse reverse_order select select_all select_append select_group select_more server
41
+ set_defaults set_graph_aliases set_overrides unfiltered ungraphed ungrouped union
42
+ unlimited unordered where with with_recursive with_sql
43
+ METHS
44
+
45
+ # Register an extension callback for Dataset objects. ext should be the
46
+ # extension name symbol, and mod should either be a Module that the
47
+ # dataset is extended with, or a callable object called with the database
48
+ # object. If mod is not provided, a block can be provided and is treated
49
+ # as the mod object.
50
+ #
51
+ # If mod is a module, this also registers a Database extension that will
52
+ # extend all of the database's datasets.
53
+ def self.register_extension(ext, mod=nil, &block)
54
+ if mod
55
+ raise(Error, "cannot provide both mod and block to Dataset.register_extension") if block
56
+ if mod.is_a?(Module)
57
+ block = proc{|ds| ds.extend(mod)}
58
+ Sequel::Database.register_extension(ext){|db| db.extend_datasets(mod)}
59
+ else
60
+ block = mod
61
+ end
62
+ end
63
+ Sequel.synchronize{EXTENSIONS[ext] = block}
64
+ end
37
65
 
38
66
  # Adds an further filter to an existing filter using AND. If no filter
39
67
  # exists an error is raised. This method is identical to #filter except
@@ -80,7 +108,7 @@ module Sequel
80
108
  # :from_self :: Set to false to not wrap the returned dataset in a from_self, use with care.
81
109
  #
82
110
  # DB[:items].except(DB[:other_items])
83
- # # SELECT * FROM items EXCEPT SELECT * FROM other_items
111
+ # # SELECT * FROM (SELECT * FROM items EXCEPT SELECT * FROM other_items) AS t1
84
112
  #
85
113
  # DB[:items].except(DB[:other_items], :all=>true, :from_self=>false)
86
114
  # # SELECT * FROM items EXCEPT ALL SELECT * FROM other_items
@@ -103,12 +131,51 @@ module Sequel
103
131
  # DB[:items].exclude(:category => 'software', :id=>3)
104
132
  # # SELECT * FROM items WHERE ((category != 'software') OR (id != 3))
105
133
  def exclude(*cond, &block)
106
- clause = (@opts[:having] ? :having : :where)
107
- cond = cond.first if cond.size == 1
108
- cond = filter_expr(cond, &block)
109
- cond = SQL::BooleanExpression.invert(cond)
110
- cond = SQL::BooleanExpression.new(:AND, @opts[clause], cond) if @opts[clause]
111
- clone(clause => cond)
134
+ _filter_or_exclude(true, @opts[:having] ? :having : :where, *cond, &block)
135
+ end
136
+
137
+ # Inverts the given conditions and adds them to the HAVING clause.
138
+ #
139
+ # DB[:items].select_group(:name).exclude_having{count(name) < 2}
140
+ # # SELECT name FROM items GROUP BY name HAVING (count(name) >= 2)
141
+ def exclude_having(*cond, &block)
142
+ _filter_or_exclude(true, :having, *cond, &block)
143
+ end
144
+
145
+ # Inverts the given conditions and adds them to the WHERE clause.
146
+ #
147
+ # DB[:items].select_group(:name).exclude_where(:category => 'software')
148
+ # # SELECT * FROM items WHERE (category != 'software')
149
+ #
150
+ # DB[:items].select_group(:name).
151
+ # exclude_having{count(name) < 2}.
152
+ # exclude_where(:category => 'software')
153
+ # # SELECT name FROM items WHERE (category != 'software')
154
+ # # GROUP BY name HAVING (count(name) >= 2)
155
+ def exclude_where(*cond, &block)
156
+ _filter_or_exclude(true, :where, *cond, &block)
157
+ end
158
+
159
+ # Return a clone of the dataset loaded with the extensions, see #extension!.
160
+ def extension(*exts)
161
+ clone.extension!(*exts)
162
+ end
163
+
164
+ # Load an extension into the receiver. In addition to requiring the extension file, this
165
+ # also modifies the dataset to work with the extension (usually extending it with a
166
+ # module defined in the extension file). If no related extension file exists or the
167
+ # extension does not have specific support for Database objects, an Error will be raised.
168
+ # Returns self.
169
+ def extension!(*exts)
170
+ Sequel.extension(*exts)
171
+ exts.each do |ext|
172
+ if pr = Sequel.synchronize{EXTENSIONS[ext]}
173
+ pr.call(self)
174
+ else
175
+ raise(Error, "Extension #{ext} does not have specific support handling individual datasets")
176
+ end
177
+ end
178
+ self
112
179
  end
113
180
 
114
181
  # Returns a copy of the dataset with the given conditions imposed upon it.
@@ -145,7 +212,7 @@ module Sequel
145
212
  # DB[:items].filter('price < ?', 100)
146
213
  # # SELECT * FROM items WHERE price < 100
147
214
  #
148
- # DB[:items].filter([[:id, (1,2,3)], [:id, 0..10]])
215
+ # DB[:items].filter([[:id, [1,2,3]], [:id, 0..10]])
149
216
  # # SELECT * FROM items WHERE ((id IN (1, 2, 3)) AND ((id >= 0) AND (id <= 10)))
150
217
  #
151
218
  # DB[:items].filter('price < 100')
@@ -184,16 +251,22 @@ module Sequel
184
251
  def from(*source)
185
252
  table_alias_num = 0
186
253
  sources = []
254
+ ctes = nil
187
255
  source.each do |s|
188
256
  case s
189
257
  when Hash
190
258
  s.each{|k,v| sources << SQL::AliasedExpression.new(k,v)}
191
259
  when Dataset
260
+ if hoist_cte?(s)
261
+ ctes ||= []
262
+ ctes += s.opts[:with]
263
+ s = s.clone(:with=>nil)
264
+ end
192
265
  sources << SQL::AliasedExpression.new(s, dataset_alias(table_alias_num+=1))
193
266
  when Symbol
194
267
  sch, table, aliaz = split_symbol(s)
195
268
  if aliaz
196
- s = sch ? SQL::QualifiedIdentifier.new(sch.to_sym, table.to_sym) : SQL::Identifier.new(table.to_sym)
269
+ s = sch ? SQL::QualifiedIdentifier.new(sch, table) : SQL::Identifier.new(table)
197
270
  sources << SQL::AliasedExpression.new(s, aliaz.to_sym)
198
271
  else
199
272
  sources << s
@@ -203,6 +276,7 @@ module Sequel
203
276
  end
204
277
  end
205
278
  o = {:from=>sources.empty? ? nil : sources}
279
+ o[:with] = (opts[:with] || []) + ctes if ctes
206
280
  o[:num_dataset_sources] = table_alias_num if table_alias_num > 0
207
281
  clone(o)
208
282
  end
@@ -270,21 +344,25 @@ module Sequel
270
344
  end
271
345
 
272
346
  # Returns a copy of the dataset with the results grouped by the value of
273
- # the given columns.
347
+ # the given columns. If a block is given, it is treated
348
+ # as a virtual row block, similar to +filter+.
274
349
  #
275
350
  # DB[:items].group(:id) # SELECT * FROM items GROUP BY id
276
351
  # DB[:items].group(:id, :name) # SELECT * FROM items GROUP BY id, name
277
- def group(*columns)
352
+ # DB[:items].group{[a, sum(b)]} # SELECT * FROM items GROUP BY a, sum(b)
353
+ def group(*columns, &block)
354
+ virtual_row_columns(columns, block)
278
355
  clone(:group => (columns.compact.empty? ? nil : columns))
279
356
  end
280
357
 
281
358
  # Alias of group
282
- def group_by(*columns)
283
- group(*columns)
359
+ def group_by(*columns, &block)
360
+ group(*columns, &block)
284
361
  end
285
362
 
286
363
  # Returns a dataset grouped by the given column with count by group.
287
364
  # Column aliases may be supplied, and will be included in the select clause.
365
+ # If a block is given, it is treated as a virtual row block, similar to +filter+.
288
366
  #
289
367
  # Examples:
290
368
  #
@@ -299,8 +377,24 @@ module Sequel
299
377
  # DB[:items].group_and_count(:first_name___name).all
300
378
  # # SELECT first_name AS name, count(*) AS count FROM items GROUP BY first_name
301
379
  # # => [{:name=>'a', :count=>1}, ...]
302
- def group_and_count(*columns)
303
- group(*columns.map{|c| unaliased_identifier(c)}).select(*(columns + [COUNT_OF_ALL_AS_COUNT]))
380
+ #
381
+ # DB[:items].group_and_count{substr(first_name, 1, 1).as(initial)}.all
382
+ # # SELECT substr(first_name, 1, 1) AS initial, count(*) AS count FROM items GROUP BY substr(first_name, 1, 1)
383
+ # # => [{:initial=>'a', :count=>1}, ...]
384
+ def group_and_count(*columns, &block)
385
+ select_group(*columns, &block).select_more(COUNT_OF_ALL_AS_COUNT)
386
+ end
387
+
388
+ # Adds the appropriate CUBE syntax to GROUP BY.
389
+ def group_cube
390
+ raise Error, "GROUP BY CUBE not supported on #{db.database_type}" unless supports_group_cube?
391
+ clone(:group_options=>:cube)
392
+ end
393
+
394
+ # Adds the appropriate ROLLUP syntax to GROUP BY.
395
+ def group_rollup
396
+ raise Error, "GROUP BY ROLLUP not supported on #{db.database_type}" unless supports_group_rollup?
397
+ clone(:group_options=>:rollup)
304
398
  end
305
399
 
306
400
  # Returns a copy of the dataset with the HAVING conditions changed. See #filter for argument types.
@@ -356,12 +450,15 @@ module Sequel
356
450
  inner_join(*args, &block)
357
451
  end
358
452
 
359
- # Returns a joined dataset. Uses the following arguments:
453
+ # Returns a joined dataset. Not usually called directly, users should use the
454
+ # appropriate join method (e.g. join, left_join, natural_join, cross_join) which fills
455
+ # in the +type+ argument.
456
+ #
457
+ # Takes the following arguments:
360
458
  #
361
459
  # * type - The type of join to do (e.g. :inner)
362
460
  # * table - Depends on type:
363
461
  # * Dataset - a subselect is performed with an alias of tN for some value of N
364
- # * Model (or anything responding to :table_name) - table.table_name
365
462
  # * String, Symbol: table
366
463
  # * expr - specifies conditions, depends on type:
367
464
  # * Hash, Array of two element arrays - Assumes key (1st arg) is column of joined table (unless already
@@ -382,11 +479,36 @@ module Sequel
382
479
  # to the same table more than once. No alias is used by default.
383
480
  # * :implicit_qualifier - The name to use for qualifying implicit conditions. By default,
384
481
  # the last joined or primary table is used.
482
+ # * :qualify - Can be set to false to not do any implicit qualification. Can be set
483
+ # to :deep to use the Qualifier AST Transformer, which will attempt to qualify
484
+ # subexpressions of the expression tree.
385
485
  # * block - The block argument should only be given if a JOIN with an ON clause is used,
386
486
  # in which case it yields the table alias/name for the table currently being joined,
387
487
  # the table alias/name for the last joined (or first table), and an array of previous
388
488
  # SQL::JoinClause. Unlike +filter+, this block is not treated as a virtual row block.
489
+ #
490
+ # Examples:
491
+ #
492
+ # DB[:a].join_table(:cross, :b)
493
+ # # SELECT * FROM a CROSS JOIN b
494
+ #
495
+ # DB[:a].join_table(:inner, DB[:b], :c=>d)
496
+ # # SELECT * FROM a INNER JOIN (SELECT * FROM b) AS t1 ON (t1.c = a.d)
497
+ #
498
+ # DB[:a].join_table(:left, :b___c, [:d])
499
+ # # SELECT * FROM a LEFT JOIN b AS c USING (d)
500
+ #
501
+ # DB[:a].natural_join(:b).join_table(:inner, :c) do |ta, jta, js|
502
+ # (Sequel.qualify(ta, :d) > Sequel.qualify(jta, :e)) & {Sequel.qualify(ta, :f)=>DB.from(js.first.table).select(:g)}
503
+ # end
504
+ # # SELECT * FROM a NATURAL JOIN b INNER JOIN c
505
+ # # ON ((c.d > b.e) AND (c.f IN (SELECT g FROM b)))
389
506
  def join_table(type, table, expr=nil, options={}, &block)
507
+ if hoist_cte?(table)
508
+ s, ds = hoist_cte(table)
509
+ return s.join_table(type, ds, expr, options, &block)
510
+ end
511
+
390
512
  using_join = expr.is_a?(Array) && !expr.empty? && expr.all?{|x| x.is_a?(Symbol)}
391
513
  if using_join && !supports_join_using?
392
514
  h = {}
@@ -398,6 +520,7 @@ module Sequel
398
520
  when Hash
399
521
  table_alias = options[:table_alias]
400
522
  last_alias = options[:implicit_qualifier]
523
+ qualify_type = options[:qualify]
401
524
  when Symbol, String, SQL::Identifier
402
525
  table_alias = options
403
526
  last_alias = nil
@@ -412,7 +535,6 @@ module Sequel
412
535
  end
413
536
  table_name = table_alias
414
537
  else
415
- table = table.table_name if table.respond_to?(:table_name)
416
538
  table, implicit_table_alias = split_alias(table)
417
539
  table_alias ||= implicit_table_alias
418
540
  table_name = table_alias || table
@@ -427,10 +549,19 @@ module Sequel
427
549
  last_alias ||= @opts[:last_joined_table] || first_source_alias
428
550
  if Sequel.condition_specifier?(expr)
429
551
  expr = expr.collect do |k, v|
430
- k = qualified_column_name(k, table_name) if k.is_a?(Symbol)
431
- v = qualified_column_name(v, last_alias) if v.is_a?(Symbol)
552
+ case qualify_type
553
+ when false
554
+ nil # Do no qualification
555
+ when :deep
556
+ k = Sequel::Qualifier.new(self, table_name).transform(k)
557
+ v = Sequel::Qualifier.new(self, last_alias).transform(v)
558
+ else
559
+ k = qualified_column_name(k, table_name) if k.is_a?(Symbol)
560
+ v = qualified_column_name(v, last_alias) if v.is_a?(Symbol)
561
+ end
432
562
  [k,v]
433
563
  end
564
+ expr = SQL::BooleanExpression.from_value_pairs(expr)
434
565
  end
435
566
  if block
436
567
  expr2 = yield(table_name, last_alias, @opts[:join] || [])
@@ -461,7 +592,7 @@ module Sequel
461
592
  # DB[:items].limit(10...20) # SELECT * FROM items LIMIT 10 OFFSET 10
462
593
  # DB[:items].limit(10..20) # SELECT * FROM items LIMIT 11 OFFSET 10
463
594
  # DB[:items].limit(nil, 20) # SELECT * FROM items OFFSET 20
464
- def limit(l, o = nil)
595
+ def limit(l, o = (no_offset = true; nil))
465
596
  return from_self.limit(l, o) if @opts[:sql]
466
597
 
467
598
  if Range === l
@@ -479,6 +610,8 @@ module Sequel
479
610
  raise(Error, 'Offsets must be greater than or equal to 0') unless o >= 0
480
611
  end
481
612
  opts[:offset] = o
613
+ elsif !no_offset
614
+ opts[:offset] = nil
482
615
  end
483
616
  clone(opts)
484
617
  end
@@ -524,14 +657,14 @@ module Sequel
524
657
  #
525
658
  # DB[:items].order(:name) # SELECT * FROM items ORDER BY name
526
659
  # DB[:items].order(:a, :b) # SELECT * FROM items ORDER BY a, b
527
- # DB[:items].order('a + b'.lit) # SELECT * FROM items ORDER BY a + b
660
+ # DB[:items].order(Sequel.lit('a + b')) # SELECT * FROM items ORDER BY a + b
528
661
  # DB[:items].order(:a + :b) # SELECT * FROM items ORDER BY (a + b)
529
- # DB[:items].order(:name.desc) # SELECT * FROM items ORDER BY name DESC
530
- # DB[:items].order(:name.asc(:nulls=>:last)) # SELECT * FROM items ORDER BY name ASC NULLS LAST
662
+ # DB[:items].order(Sequel.desc(:name)) # SELECT * FROM items ORDER BY name DESC
663
+ # DB[:items].order(Sequel.asc(:name, :nulls=>:last)) # SELECT * FROM items ORDER BY name ASC NULLS LAST
531
664
  # DB[:items].order{sum(name).desc} # SELECT * FROM items ORDER BY sum(name) DESC
532
665
  # DB[:items].order(nil) # SELECT * FROM items
533
666
  def order(*columns, &block)
534
- columns += Array(Sequel.virtual_row(&block)) if block
667
+ virtual_row_columns(columns, block)
535
668
  clone(:order => (columns.compact.empty?) ? nil : columns)
536
669
  end
537
670
 
@@ -565,7 +698,7 @@ module Sequel
565
698
  @opts[:order] ? ds.order_more(*@opts[:order]) : ds
566
699
  end
567
700
 
568
- # Qualify to the given table, or first source if not table is given.
701
+ # Qualify to the given table, or first source if no table is given.
569
702
  #
570
703
  # DB[:items].filter(:id=>1).qualify
571
704
  # # SELECT items.* FROM items WHERE (items.id = 1)
@@ -606,19 +739,33 @@ module Sequel
606
739
  qualify_to(first_source)
607
740
  end
608
741
 
742
+ # Modify the RETURNING clause, only supported on a few databases. If returning
743
+ # is used, instead of insert returning the autogenerated primary key or
744
+ # update/delete returning the number of modified rows, results are
745
+ # returned using +fetch_rows+.
746
+ #
747
+ # DB[:items].returning # RETURNING *
748
+ # DB[:items].returning(nil) # RETURNING NULL
749
+ # DB[:items].returning(:id, :name) # RETURNING id, name
750
+ def returning(*values)
751
+ clone(:returning=>values)
752
+ end
753
+
609
754
  # Returns a copy of the dataset with the order reversed. If no order is
610
755
  # given, the existing order is inverted.
611
756
  #
612
757
  # DB[:items].reverse(:id) # SELECT * FROM items ORDER BY id DESC
758
+ # DB[:items].reverse{foo(bar)} # SELECT * FROM items ORDER BY foo(bar) DESC
613
759
  # DB[:items].order(:id).reverse # SELECT * FROM items ORDER BY id DESC
614
- # DB[:items].order(:id).reverse(:name.asc) # SELECT * FROM items ORDER BY name ASC
615
- def reverse(*order)
760
+ # DB[:items].order(:id).reverse(Sequel.desc(:name)) # SELECT * FROM items ORDER BY name ASC
761
+ def reverse(*order, &block)
762
+ virtual_row_columns(order, block)
616
763
  order(*invert_order(order.empty? ? @opts[:order] : order))
617
764
  end
618
765
 
619
766
  # Alias of +reverse+
620
- def reverse_order(*order)
621
- reverse(*order)
767
+ def reverse_order(*order, &block)
768
+ reverse(*order, &block)
622
769
  end
623
770
 
624
771
  # Returns a copy of the dataset with the columns selected changed
@@ -629,7 +776,7 @@ module Sequel
629
776
  # DB[:items].select(:a, :b) # SELECT a, b FROM items
630
777
  # DB[:items].select{[a, sum(b)]} # SELECT a, sum(b) FROM items
631
778
  def select(*columns, &block)
632
- columns += Array(Sequel.virtual_row(&block)) if block
779
+ virtual_row_columns(columns, block)
633
780
  m = []
634
781
  columns.each do |i|
635
782
  i.is_a?(Hash) ? m.concat(i.map{|k, v| SQL::AliasedExpression.new(k,v)}) : m << i
@@ -637,11 +784,19 @@ module Sequel
637
784
  clone(:select => m)
638
785
  end
639
786
 
640
- # Returns a copy of the dataset selecting the wildcard.
787
+ # Returns a copy of the dataset selecting the wildcard if no arguments
788
+ # are given. If arguments are given, treat them as tables and select
789
+ # all columns (using the wildcard) from each table.
641
790
  #
642
791
  # DB[:items].select(:a).select_all # SELECT * FROM items
643
- def select_all
644
- clone(:select => nil)
792
+ # DB[:items].select_all(:items) # SELECT items.* FROM items
793
+ # DB[:items].select_all(:items, :foo) # SELECT items.*, foo.* FROM items
794
+ def select_all(*tables)
795
+ if tables.empty?
796
+ clone(:select => nil)
797
+ else
798
+ select(*tables.map{|t| i, a = split_alias(t); a || i}.map{|t| SQL::ColumnAll.new(t)})
799
+ end
645
800
  end
646
801
 
647
802
  # Returns a copy of the dataset with the given columns added
@@ -653,10 +808,29 @@ module Sequel
653
808
  # DB[:items].select_append(:b) # SELECT *, b FROM items
654
809
  def select_append(*columns, &block)
655
810
  cur_sel = @opts[:select]
656
- cur_sel = [WILDCARD] if !cur_sel || cur_sel.empty?
811
+ if !cur_sel || cur_sel.empty?
812
+ unless supports_select_all_and_column?
813
+ return select_all(*(Array(@opts[:from]) + Array(@opts[:join]))).select_more(*columns, &block)
814
+ end
815
+ cur_sel = [WILDCARD]
816
+ end
657
817
  select(*(cur_sel + columns), &block)
658
818
  end
659
819
 
820
+ # Set both the select and group clauses with the given +columns+.
821
+ # Column aliases may be supplied, and will be included in the select clause.
822
+ # This also takes a virtual row block similar to +filter+.
823
+ #
824
+ # DB[:items].select_group(:a, :b)
825
+ # # SELECT a, b FROM items GROUP BY a, b
826
+ #
827
+ # DB[:items].select_group(:c___a){f(c2)}
828
+ # # SELECT c AS a, f(c2) FROM items GROUP BY c, f(c2)
829
+ def select_group(*columns, &block)
830
+ virtual_row_columns(columns, block)
831
+ select(*columns).group(*columns.map{|c| unaliased_identifier(c)})
832
+ end
833
+
660
834
  # Returns a copy of the dataset with the given columns added
661
835
  # to the existing selected columns. If no columns are currently selected
662
836
  # it will just select the columns given.
@@ -670,7 +844,7 @@ module Sequel
670
844
  end
671
845
 
672
846
  # Set the server for this dataset to use. Used to pick a specific database
673
- # shard to run a query against, or to override the default (which is SELECT uses
847
+ # shard to run a query against, or to override the default (where SELECT uses
674
848
  # :read_only database and all other queries use the :default database). This
675
849
  # method is always available but is only useful when database sharding is being
676
850
  # used.
@@ -702,6 +876,22 @@ module Sequel
702
876
  clone(:overrides=>hash.merge(@opts[:overrides]||{}))
703
877
  end
704
878
 
879
+ # Unbind bound variables from this dataset's filter and return an array of two
880
+ # objects. The first object is a modified dataset where the filter has been
881
+ # replaced with one that uses bound variable placeholders. The second object
882
+ # is the hash of unbound variables. You can then prepare and execute (or just
883
+ # call) the dataset with the bound variables to get results.
884
+ #
885
+ # ds, bv = DB[:items].filter(:a=>1).unbind
886
+ # ds # SELECT * FROM items WHERE (a = $a)
887
+ # bv # {:a => 1}
888
+ # ds.call(:select, bv)
889
+ def unbind
890
+ u = Unbinder.new
891
+ ds = clone(:where=>u.transform(opts[:where]), :join=>u.transform(opts[:join]))
892
+ [ds, u.binds]
893
+ end
894
+
705
895
  # Returns a copy of the dataset with no filters (HAVING or WHERE clause) applied.
706
896
  #
707
897
  # DB[:items].group(:a).having(:a=>1).where(:b).unfiltered
@@ -726,8 +916,8 @@ module Sequel
726
916
  # :all :: Set to true to use UNION ALL instead of UNION, so duplicate rows can occur
727
917
  # :from_self :: Set to false to not wrap the returned dataset in a from_self, use with care.
728
918
  #
729
- # DB[:items].union(DB[:other_items]).sql
730
- # #=> "SELECT * FROM items UNION SELECT * FROM other_items"
919
+ # DB[:items].union(DB[:other_items])
920
+ # # SELECT * FROM (SELECT * FROM items UNION SELECT * FROM other_items) AS t1
731
921
  #
732
922
  # DB[:items].union(DB[:other_items], :all=>true, :from_self=>false)
733
923
  # # SELECT * FROM items UNION ALL SELECT * FROM other_items
@@ -774,7 +964,12 @@ module Sequel
774
964
  # # WITH items AS (SELECT * FROM syx WHERE (name LIKE 'A%')) SELECT * FROM items
775
965
  def with(name, dataset, opts={})
776
966
  raise(Error, 'This datatset does not support common table expressions') unless supports_cte?
777
- clone(:with=>(@opts[:with]||[]) + [opts.merge(:name=>name, :dataset=>dataset)])
967
+ if hoist_cte?(dataset)
968
+ s, ds = hoist_cte(dataset)
969
+ s.with(name, ds, opts)
970
+ else
971
+ clone(:with=>(@opts[:with]||[]) + [opts.merge(:name=>name, :dataset=>dataset)])
972
+ end
778
973
  end
779
974
 
780
975
  # Add a recursive common table expression (CTE) with the given name, a dataset that
@@ -783,33 +978,62 @@ module Sequel
783
978
  # :args :: Specify the arguments/columns for the CTE, should be an array of symbols.
784
979
  # :union_all :: Set to false to use UNION instead of UNION ALL combining the nonrecursive and recursive parts.
785
980
  #
786
- # DB[:t].select(:i___id, :pi___parent_id).
787
- # with_recursive(:t,
788
- # DB[:i1].filter(:parent_id=>nil),
789
- # DB[:t].join(:t, :i=>:parent_id).select(:i1__id, :i1__parent_id),
790
- # :args=>[:i, :pi])
791
- # # WITH RECURSIVE t(i, pi) AS (
792
- # # SELECT * FROM i1 WHERE (parent_id IS NULL)
981
+ # DB[:t].with_recursive(:t,
982
+ # DB[:i1].select(:id, :parent_id).filter(:parent_id=>nil),
983
+ # DB[:i1].join(:t, :id=>:parent_id).select(:i1__id, :i1__parent_id),
984
+ # :args=>[:id, :parent_id])
985
+ #
986
+ # # WITH RECURSIVE "t"("id", "parent_id") AS (
987
+ # # SELECT "id", "parent_id" FROM "i1" WHERE ("parent_id" IS NULL)
793
988
  # # UNION ALL
794
- # # SELECT i1.id, i1.parent_id FROM t INNER JOIN t ON (t.i = t.parent_id)
795
- # # )
796
- # # SELECT i AS id, pi AS parent_id FROM t
989
+ # # SELECT "i1"."id", "i1"."parent_id" FROM "i1" INNER JOIN "t" ON ("t"."id" = "i1"."parent_id")
990
+ # # ) SELECT * FROM "t"
797
991
  def with_recursive(name, nonrecursive, recursive, opts={})
798
992
  raise(Error, 'This datatset does not support common table expressions') unless supports_cte?
799
- clone(:with=>(@opts[:with]||[]) + [opts.merge(:recursive=>true, :name=>name, :dataset=>nonrecursive.union(recursive, {:all=>opts[:union_all] != false, :from_self=>false}))])
993
+ if hoist_cte?(nonrecursive)
994
+ s, ds = hoist_cte(nonrecursive)
995
+ s.with_recursive(name, ds, recursive, opts)
996
+ elsif hoist_cte?(recursive)
997
+ s, ds = hoist_cte(recursive)
998
+ s.with_recursive(name, nonrecursive, ds, opts)
999
+ else
1000
+ clone(:with=>(@opts[:with]||[]) + [opts.merge(:recursive=>true, :name=>name, :dataset=>nonrecursive.union(recursive, {:all=>opts[:union_all] != false, :from_self=>false}))])
1001
+ end
800
1002
  end
801
1003
 
802
1004
  # Returns a copy of the dataset with the static SQL used. This is useful if you want
803
1005
  # to keep the same row_proc/graph, but change the SQL used to custom SQL.
804
1006
  #
805
1007
  # DB[:items].with_sql('SELECT * FROM foo') # SELECT * FROM foo
1008
+ #
1009
+ # You can use placeholders in your SQL and provide arguments for those placeholders:
1010
+ #
1011
+ # DB[:items].with_sql('SELECT ? FROM foo', 1) # SELECT 1 FROM foo
1012
+ #
1013
+ # You can also provide a method name and arguments to call to get the SQL:
1014
+ #
1015
+ # DB[:items].with_sql(:insert_sql, :b=>1) # INSERT INTO items (b) VALUES (1)
806
1016
  def with_sql(sql, *args)
807
- sql = SQL::PlaceholderLiteralString.new(sql, args) unless args.empty?
1017
+ if sql.is_a?(Symbol)
1018
+ sql = send(sql, *args)
1019
+ else
1020
+ sql = SQL::PlaceholderLiteralString.new(sql, args) unless args.empty?
1021
+ end
808
1022
  clone(:sql=>sql)
809
1023
  end
810
1024
 
811
1025
  protected
812
1026
 
1027
+ # Add the dataset to the list of compounds
1028
+ def compound_clone(type, dataset, opts)
1029
+ if hoist_cte?(dataset)
1030
+ s, ds = hoist_cte(dataset)
1031
+ return s.compound_clone(type, ds, opts)
1032
+ end
1033
+ ds = compound_from_self.clone(:compounds=>Array(@opts[:compounds]).map{|x| x.dup} + [[type, dataset.compound_from_self, opts[:all]]])
1034
+ opts[:from_self] == false ? ds : ds.from_self(opts)
1035
+ end
1036
+
813
1037
  # Return true if the dataset has a non-nil value for any key in opts.
814
1038
  def options_overlap(opts)
815
1039
  !(@opts.collect{|k,v| k unless v.nil?}.compact & opts).empty?
@@ -823,22 +1047,22 @@ module Sequel
823
1047
 
824
1048
  private
825
1049
 
826
- # Internal filter method so it works on either the having or where clauses.
827
- def _filter(clause, *cond, &block)
1050
+ # Internal filter/exclude method so it works on either the having or where clauses.
1051
+ def _filter_or_exclude(invert, clause, *cond, &block)
828
1052
  cond = cond.first if cond.size == 1
829
1053
  if cond.respond_to?(:empty?) && cond.empty? && !block
830
1054
  clone
831
1055
  else
832
1056
  cond = filter_expr(cond, &block)
1057
+ cond = SQL::BooleanExpression.invert(cond) if invert
833
1058
  cond = SQL::BooleanExpression.new(:AND, @opts[clause], cond) if @opts[clause]
834
1059
  clone(clause => cond)
835
1060
  end
836
1061
  end
837
-
838
- # Add the dataset to the list of compounds
839
- def compound_clone(type, dataset, opts)
840
- ds = compound_from_self.clone(:compounds=>Array(@opts[:compounds]).map{|x| x.dup} + [[type, dataset.compound_from_self, opts[:all]]])
841
- opts[:from_self] == false ? ds : ds.from_self(opts)
1062
+
1063
+ # Internal filter method so it works on either the having or where clauses.
1064
+ def _filter(clause, *cond, &block)
1065
+ _filter_or_exclude(false, clause, *cond, &block)
842
1066
  end
843
1067
 
844
1068
  # SQL expression object based on the expr type. See +filter+.
@@ -867,7 +1091,13 @@ module Sequel
867
1091
  when Symbol, SQL::Expression
868
1092
  expr
869
1093
  when TrueClass, FalseClass
870
- SQL::BooleanExpression.new(:NOOP, expr)
1094
+ if supports_where_true?
1095
+ SQL::BooleanExpression.new(:NOOP, expr)
1096
+ elsif expr
1097
+ SQL::Constants::SQLTRUE
1098
+ else
1099
+ SQL::Constants::SQLFALSE
1100
+ end
871
1101
  when String
872
1102
  LiteralString.new("(#{expr})")
873
1103
  else
@@ -875,11 +1105,23 @@ module Sequel
875
1105
  end
876
1106
  end
877
1107
 
1108
+ # Return two datasets, the first a clone of the receiver with the WITH
1109
+ # clause from the given dataset added to it, and the second a clone of
1110
+ # the given dataset with the WITH clause removed.
1111
+ def hoist_cte(ds)
1112
+ [clone(:with => (opts[:with] || []) + ds.opts[:with]), ds.clone(:with => nil)]
1113
+ end
1114
+
1115
+ # Whether CTEs need to be hoisted from the given ds into the current ds.
1116
+ def hoist_cte?(ds)
1117
+ ds.is_a?(Dataset) && ds.opts[:with] && !supports_cte_in_subqueries?
1118
+ end
1119
+
878
1120
  # Inverts the given order by breaking it into a list of column references
879
1121
  # and inverting them.
880
1122
  #
881
- # DB[:items].invert_order([:id.desc]]) #=> [:id]
882
- # DB[:items].invert_order(:category, :price.desc]) #=> [:category.desc, :price]
1123
+ # DB[:items].invert_order([Sequel.desc(:id)]]) #=> [Sequel.asc(:id)]
1124
+ # DB[:items].invert_order([:category, Sequel.desc(:price)]) #=> [Sequel.desc(:category), Sequel.asc(:price)]
883
1125
  def invert_order(order)
884
1126
  return nil unless order
885
1127
  new_order = []
@@ -892,5 +1134,17 @@ module Sequel
892
1134
  end
893
1135
  end
894
1136
  end
1137
+
1138
+ # Return self if the dataset already has a server, or a cloned dataset with the
1139
+ # default server otherwise.
1140
+ def default_server
1141
+ @opts[:server] ? self : clone(:server=>:default)
1142
+ end
1143
+
1144
+ # Treat the +block+ as a virtual_row block if not +nil+ and
1145
+ # add the resulting columns to the +columns+ array (modifies +columns+).
1146
+ def virtual_row_columns(columns, block)
1147
+ columns.concat(Array(Sequel.virtual_row(&block))) if block
1148
+ end
895
1149
  end
896
1150
  end