sequel 3.21.0 → 3.36.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 (311) hide show
  1. data/CHANGELOG +855 -3
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +23 -7
  4. data/Rakefile +42 -24
  5. data/bin/sequel +26 -8
  6. data/doc/active_record.rdoc +1 -0
  7. data/doc/advanced_associations.rdoc +171 -11
  8. data/doc/association_basics.rdoc +293 -43
  9. data/doc/dataset_basics.rdoc +5 -5
  10. data/doc/dataset_filtering.rdoc +8 -0
  11. data/doc/mass_assignment.rdoc +56 -0
  12. data/doc/migration.rdoc +85 -552
  13. data/doc/model_hooks.rdoc +78 -27
  14. data/doc/object_model.rdoc +541 -0
  15. data/doc/opening_databases.rdoc +97 -50
  16. data/doc/prepared_statements.rdoc +40 -14
  17. data/doc/querying.rdoc +28 -4
  18. data/doc/reflection.rdoc +30 -4
  19. data/doc/release_notes/3.22.0.txt +39 -0
  20. data/doc/release_notes/3.23.0.txt +172 -0
  21. data/doc/release_notes/3.24.0.txt +420 -0
  22. data/doc/release_notes/3.25.0.txt +88 -0
  23. data/doc/release_notes/3.26.0.txt +88 -0
  24. data/doc/release_notes/3.27.0.txt +82 -0
  25. data/doc/release_notes/3.28.0.txt +304 -0
  26. data/doc/release_notes/3.29.0.txt +459 -0
  27. data/doc/release_notes/3.30.0.txt +135 -0
  28. data/doc/release_notes/3.31.0.txt +146 -0
  29. data/doc/release_notes/3.32.0.txt +202 -0
  30. data/doc/release_notes/3.33.0.txt +157 -0
  31. data/doc/release_notes/3.34.0.txt +671 -0
  32. data/doc/release_notes/3.35.0.txt +144 -0
  33. data/doc/release_notes/3.36.0.txt +245 -0
  34. data/doc/schema_modification.rdoc +572 -0
  35. data/doc/sharding.rdoc +95 -3
  36. data/doc/testing.rdoc +129 -0
  37. data/doc/thread_safety.rdoc +17 -0
  38. data/doc/transactions.rdoc +137 -0
  39. data/doc/virtual_rows.rdoc +51 -2
  40. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  41. data/lib/sequel/adapters/ado.rb +40 -18
  42. data/lib/sequel/adapters/amalgalite.rb +17 -9
  43. data/lib/sequel/adapters/db2.rb +175 -86
  44. data/lib/sequel/adapters/dbi.rb +15 -15
  45. data/lib/sequel/adapters/do/mysql.rb +17 -11
  46. data/lib/sequel/adapters/do/postgres.rb +1 -57
  47. data/lib/sequel/adapters/do/sqlite.rb +0 -10
  48. data/lib/sequel/adapters/do.rb +18 -36
  49. data/lib/sequel/adapters/firebird.rb +27 -208
  50. data/lib/sequel/adapters/ibmdb.rb +453 -0
  51. data/lib/sequel/adapters/informix.rb +6 -23
  52. data/lib/sequel/adapters/jdbc/as400.rb +16 -34
  53. data/lib/sequel/adapters/jdbc/db2.rb +47 -0
  54. data/lib/sequel/adapters/jdbc/derby.rb +287 -0
  55. data/lib/sequel/adapters/jdbc/firebird.rb +24 -0
  56. data/lib/sequel/adapters/jdbc/h2.rb +73 -31
  57. data/lib/sequel/adapters/jdbc/hsqldb.rb +184 -0
  58. data/lib/sequel/adapters/jdbc/informix.rb +21 -0
  59. data/lib/sequel/adapters/jdbc/jtds.rb +29 -0
  60. data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
  61. data/lib/sequel/adapters/jdbc/mysql.rb +7 -24
  62. data/lib/sequel/adapters/jdbc/oracle.rb +88 -25
  63. data/lib/sequel/adapters/jdbc/postgresql.rb +58 -48
  64. data/lib/sequel/adapters/jdbc/progress.rb +21 -0
  65. data/lib/sequel/adapters/jdbc/sqlite.rb +20 -10
  66. data/lib/sequel/adapters/jdbc/sqlserver.rb +66 -0
  67. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  68. data/lib/sequel/adapters/jdbc.rb +265 -94
  69. data/lib/sequel/adapters/mock.rb +361 -0
  70. data/lib/sequel/adapters/mysql.rb +118 -222
  71. data/lib/sequel/adapters/mysql2.rb +58 -57
  72. data/lib/sequel/adapters/odbc/mssql.rb +15 -7
  73. data/lib/sequel/adapters/odbc.rb +28 -9
  74. data/lib/sequel/adapters/openbase.rb +3 -5
  75. data/lib/sequel/adapters/oracle.rb +349 -53
  76. data/lib/sequel/adapters/postgres.rb +356 -107
  77. data/lib/sequel/adapters/shared/access.rb +32 -9
  78. data/lib/sequel/adapters/shared/db2.rb +342 -0
  79. data/lib/sequel/adapters/shared/firebird.rb +221 -0
  80. data/lib/sequel/adapters/shared/informix.rb +53 -0
  81. data/lib/sequel/adapters/shared/mssql.rb +335 -118
  82. data/lib/sequel/adapters/shared/mysql.rb +340 -76
  83. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +155 -0
  84. data/lib/sequel/adapters/shared/oracle.rb +243 -45
  85. data/lib/sequel/adapters/shared/postgres.rb +524 -279
  86. data/lib/sequel/adapters/shared/progress.rb +5 -8
  87. data/lib/sequel/adapters/shared/sqlite.rb +265 -89
  88. data/lib/sequel/adapters/sqlite.rb +101 -39
  89. data/lib/sequel/adapters/swift/mysql.rb +9 -12
  90. data/lib/sequel/adapters/swift/postgres.rb +9 -74
  91. data/lib/sequel/adapters/swift/sqlite.rb +8 -6
  92. data/lib/sequel/adapters/swift.rb +8 -7
  93. data/lib/sequel/adapters/tinytds.rb +161 -21
  94. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +66 -0
  95. data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
  96. data/lib/sequel/ast_transformer.rb +194 -0
  97. data/lib/sequel/connection_pool/sharded_single.rb +11 -1
  98. data/lib/sequel/connection_pool/sharded_threaded.rb +28 -2
  99. data/lib/sequel/connection_pool/single.rb +5 -0
  100. data/lib/sequel/connection_pool/threaded.rb +38 -14
  101. data/lib/sequel/connection_pool.rb +10 -2
  102. data/lib/sequel/core.rb +118 -11
  103. data/lib/sequel/database/connecting.rb +38 -28
  104. data/lib/sequel/database/dataset.rb +4 -4
  105. data/lib/sequel/database/dataset_defaults.rb +63 -2
  106. data/lib/sequel/database/logging.rb +7 -2
  107. data/lib/sequel/database/misc.rb +150 -46
  108. data/lib/sequel/database/query.rb +215 -65
  109. data/lib/sequel/database/schema_generator.rb +64 -20
  110. data/lib/sequel/database/schema_methods.rb +212 -54
  111. data/lib/sequel/dataset/actions.rb +322 -85
  112. data/lib/sequel/dataset/features.rb +96 -8
  113. data/lib/sequel/dataset/graph.rb +38 -11
  114. data/lib/sequel/dataset/misc.rb +50 -23
  115. data/lib/sequel/dataset/mutation.rb +12 -7
  116. data/lib/sequel/dataset/prepared_statements.rb +68 -30
  117. data/lib/sequel/dataset/query.rb +237 -44
  118. data/lib/sequel/dataset/sql.rb +698 -278
  119. data/lib/sequel/dataset.rb +8 -0
  120. data/lib/sequel/exceptions.rb +4 -0
  121. data/lib/sequel/extensions/_pretty_table.rb +79 -0
  122. data/lib/sequel/extensions/arbitrary_servers.rb +108 -0
  123. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  124. data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +16 -34
  125. data/lib/sequel/extensions/migration.rb +97 -11
  126. data/lib/sequel/extensions/named_timezones.rb +5 -0
  127. data/lib/sequel/extensions/null_dataset.rb +90 -0
  128. data/lib/sequel/extensions/pg_array.rb +460 -0
  129. data/lib/sequel/extensions/pg_array_ops.rb +220 -0
  130. data/lib/sequel/extensions/pg_auto_parameterize.rb +169 -0
  131. data/lib/sequel/extensions/pg_hstore.rb +296 -0
  132. data/lib/sequel/extensions/pg_hstore_ops.rb +259 -0
  133. data/lib/sequel/extensions/pg_inet.rb +89 -0
  134. data/lib/sequel/extensions/pg_json.rb +178 -0
  135. data/lib/sequel/extensions/pg_statement_cache.rb +316 -0
  136. data/lib/sequel/extensions/pretty_table.rb +5 -71
  137. data/lib/sequel/extensions/query.rb +1 -1
  138. data/lib/sequel/extensions/query_literals.rb +79 -0
  139. data/lib/sequel/extensions/schema_caching.rb +76 -0
  140. data/lib/sequel/extensions/schema_dumper.rb +251 -37
  141. data/lib/sequel/extensions/select_remove.rb +35 -0
  142. data/lib/sequel/extensions/server_block.rb +139 -0
  143. data/lib/sequel/extensions/sql_expr.rb +4 -110
  144. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  145. data/lib/sequel/extensions/to_dot.rb +95 -83
  146. data/lib/sequel/model/associations.rb +1187 -393
  147. data/lib/sequel/model/base.rb +578 -170
  148. data/lib/sequel/model/errors.rb +1 -1
  149. data/lib/sequel/model/exceptions.rb +5 -1
  150. data/lib/sequel/model/inflections.rb +1 -1
  151. data/lib/sequel/model.rb +30 -11
  152. data/lib/sequel/no_core_ext.rb +2 -0
  153. data/lib/sequel/plugins/active_model.rb +13 -1
  154. data/lib/sequel/plugins/association_pks.rb +22 -4
  155. data/lib/sequel/plugins/caching.rb +25 -18
  156. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  157. data/lib/sequel/plugins/composition.rb +44 -12
  158. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  159. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  160. data/lib/sequel/plugins/dirty.rb +214 -0
  161. data/lib/sequel/plugins/eager_each.rb +59 -0
  162. data/lib/sequel/plugins/force_encoding.rb +6 -6
  163. data/lib/sequel/plugins/hook_class_methods.rb +1 -1
  164. data/lib/sequel/plugins/identity_map.rb +123 -8
  165. data/lib/sequel/plugins/instance_filters.rb +10 -0
  166. data/lib/sequel/plugins/instance_hooks.rb +1 -1
  167. data/lib/sequel/plugins/json_serializer.rb +58 -6
  168. data/lib/sequel/plugins/list.rb +12 -2
  169. data/lib/sequel/plugins/many_through_many.rb +79 -28
  170. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
  171. data/lib/sequel/plugins/nested_attributes.rb +139 -63
  172. data/lib/sequel/plugins/optimistic_locking.rb +8 -0
  173. data/lib/sequel/plugins/prepared_statements.rb +167 -0
  174. data/lib/sequel/plugins/prepared_statements_associations.rb +87 -0
  175. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  176. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  177. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  178. data/lib/sequel/plugins/schema.rb +7 -2
  179. data/lib/sequel/plugins/serialization.rb +98 -49
  180. data/lib/sequel/plugins/serialization_modification_detection.rb +63 -0
  181. data/lib/sequel/plugins/sharding.rb +21 -54
  182. data/lib/sequel/plugins/single_table_inheritance.rb +5 -3
  183. data/lib/sequel/plugins/static_cache.rb +99 -0
  184. data/lib/sequel/plugins/subclasses.rb +10 -2
  185. data/lib/sequel/plugins/tactical_eager_loading.rb +7 -7
  186. data/lib/sequel/plugins/timestamps.rb +1 -1
  187. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  188. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  189. data/lib/sequel/plugins/validation_class_methods.rb +1 -1
  190. data/lib/sequel/plugins/xml_serializer.rb +15 -4
  191. data/lib/sequel/sql.rb +526 -59
  192. data/lib/sequel/timezones.rb +57 -38
  193. data/lib/sequel/version.rb +1 -1
  194. data/spec/adapters/db2_spec.rb +146 -0
  195. data/spec/adapters/firebird_spec.rb +1 -1
  196. data/spec/adapters/mssql_spec.rb +142 -40
  197. data/spec/adapters/mysql_spec.rb +380 -234
  198. data/spec/adapters/oracle_spec.rb +67 -77
  199. data/spec/adapters/postgres_spec.rb +1050 -212
  200. data/spec/adapters/spec_helper.rb +8 -5
  201. data/spec/adapters/sqlite_spec.rb +139 -27
  202. data/spec/core/connection_pool_spec.rb +196 -93
  203. data/spec/core/database_spec.rb +879 -510
  204. data/spec/core/dataset_spec.rb +1415 -1086
  205. data/spec/core/expression_filters_spec.rb +554 -109
  206. data/spec/core/mock_adapter_spec.rb +449 -0
  207. data/spec/core/object_graph_spec.rb +86 -114
  208. data/spec/core/schema_generator_spec.rb +4 -4
  209. data/spec/core/schema_spec.rb +482 -38
  210. data/spec/core/spec_helper.rb +6 -48
  211. data/spec/extensions/active_model_spec.rb +13 -0
  212. data/spec/extensions/arbitrary_servers_spec.rb +110 -0
  213. data/spec/extensions/association_autoreloading_spec.rb +18 -10
  214. data/spec/extensions/association_dependencies_spec.rb +15 -25
  215. data/spec/extensions/association_pks_spec.rb +66 -32
  216. data/spec/extensions/association_proxies_spec.rb +4 -4
  217. data/spec/extensions/caching_spec.rb +47 -51
  218. data/spec/extensions/class_table_inheritance_spec.rb +31 -83
  219. data/spec/extensions/columns_introspection_spec.rb +91 -0
  220. data/spec/extensions/composition_spec.rb +18 -13
  221. data/spec/{core/core_sql_spec.rb → extensions/core_extensions_spec.rb} +119 -139
  222. data/spec/extensions/dataset_associations_spec.rb +199 -0
  223. data/spec/extensions/defaults_setter_spec.rb +64 -0
  224. data/spec/extensions/dirty_spec.rb +155 -0
  225. data/spec/extensions/eager_each_spec.rb +34 -0
  226. data/spec/extensions/force_encoding_spec.rb +4 -2
  227. data/spec/extensions/hook_class_methods_spec.rb +69 -92
  228. data/spec/extensions/identity_map_spec.rb +131 -22
  229. data/spec/extensions/inflector_spec.rb +0 -4
  230. data/spec/extensions/instance_filters_spec.rb +11 -21
  231. data/spec/extensions/instance_hooks_spec.rb +72 -0
  232. data/spec/extensions/json_serializer_spec.rb +49 -6
  233. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  234. data/spec/extensions/list_spec.rb +48 -39
  235. data/spec/extensions/many_through_many_spec.rb +283 -250
  236. data/spec/extensions/many_to_one_pk_lookup_spec.rb +140 -0
  237. data/spec/extensions/migration_spec.rb +140 -35
  238. data/spec/extensions/named_timezones_spec.rb +29 -11
  239. data/spec/extensions/nested_attributes_spec.rb +251 -89
  240. data/spec/extensions/null_dataset_spec.rb +85 -0
  241. data/spec/extensions/optimistic_locking_spec.rb +24 -21
  242. data/spec/extensions/pg_array_ops_spec.rb +105 -0
  243. data/spec/extensions/pg_array_spec.rb +196 -0
  244. data/spec/extensions/pg_auto_parameterize_spec.rb +65 -0
  245. data/spec/extensions/pg_hstore_ops_spec.rb +136 -0
  246. data/spec/extensions/pg_hstore_spec.rb +195 -0
  247. data/spec/extensions/pg_inet_spec.rb +44 -0
  248. data/spec/extensions/pg_json_spec.rb +101 -0
  249. data/spec/extensions/pg_statement_cache_spec.rb +209 -0
  250. data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
  251. data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
  252. data/spec/extensions/prepared_statements_spec.rb +87 -0
  253. data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
  254. data/spec/extensions/pretty_table_spec.rb +7 -6
  255. data/spec/extensions/query_literals_spec.rb +168 -0
  256. data/spec/extensions/query_spec.rb +6 -0
  257. data/spec/extensions/rcte_tree_spec.rb +50 -43
  258. data/spec/extensions/schema_caching_spec.rb +41 -0
  259. data/spec/extensions/schema_dumper_spec.rb +429 -22
  260. data/spec/extensions/schema_spec.rb +25 -21
  261. data/spec/extensions/select_remove_spec.rb +38 -0
  262. data/spec/extensions/serialization_modification_detection_spec.rb +72 -0
  263. data/spec/extensions/serialization_spec.rb +85 -43
  264. data/spec/extensions/server_block_spec.rb +90 -0
  265. data/spec/extensions/sharding_spec.rb +69 -143
  266. data/spec/extensions/single_table_inheritance_spec.rb +23 -29
  267. data/spec/extensions/skip_create_refresh_spec.rb +1 -1
  268. data/spec/extensions/spec_helper.rb +22 -61
  269. data/spec/extensions/sql_expr_spec.rb +29 -60
  270. data/spec/extensions/static_cache_spec.rb +145 -0
  271. data/spec/extensions/tactical_eager_loading_spec.rb +17 -19
  272. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  273. data/spec/extensions/timestamps_spec.rb +6 -6
  274. data/spec/extensions/to_dot_spec.rb +4 -7
  275. data/spec/extensions/touch_spec.rb +13 -14
  276. data/spec/extensions/tree_spec.rb +11 -26
  277. data/spec/extensions/typecast_on_load_spec.rb +9 -6
  278. data/spec/extensions/update_primary_key_spec.rb +30 -24
  279. data/spec/extensions/validation_class_methods_spec.rb +30 -51
  280. data/spec/extensions/validation_helpers_spec.rb +16 -35
  281. data/spec/extensions/xml_serializer_spec.rb +29 -4
  282. data/spec/files/transaction_specified_migrations/001_create_alt_basic.rb +4 -0
  283. data/spec/files/transaction_specified_migrations/002_create_basic.rb +4 -0
  284. data/spec/files/transaction_unspecified_migrations/001_create_alt_basic.rb +3 -0
  285. data/spec/files/transaction_unspecified_migrations/002_create_basic.rb +3 -0
  286. data/spec/integration/associations_test.rb +659 -21
  287. data/spec/integration/database_test.rb +9 -3
  288. data/spec/integration/dataset_test.rb +605 -91
  289. data/spec/integration/eager_loader_test.rb +19 -21
  290. data/spec/integration/migrator_test.rb +2 -3
  291. data/spec/integration/model_test.rb +81 -2
  292. data/spec/integration/plugin_test.rb +454 -143
  293. data/spec/integration/prepared_statement_test.rb +263 -128
  294. data/spec/integration/schema_test.rb +309 -43
  295. data/spec/integration/spec_helper.rb +40 -30
  296. data/spec/integration/timezone_test.rb +38 -12
  297. data/spec/integration/transaction_test.rb +183 -8
  298. data/spec/integration/type_test.rb +18 -4
  299. data/spec/model/association_reflection_spec.rb +201 -7
  300. data/spec/model/associations_spec.rb +1067 -661
  301. data/spec/model/base_spec.rb +329 -95
  302. data/spec/model/dataset_methods_spec.rb +7 -27
  303. data/spec/model/eager_loading_spec.rb +677 -676
  304. data/spec/model/hooks_spec.rb +349 -67
  305. data/spec/model/model_spec.rb +240 -168
  306. data/spec/model/plugins_spec.rb +24 -13
  307. data/spec/model/record_spec.rb +490 -232
  308. data/spec/model/spec_helper.rb +14 -71
  309. data/spec/model/validations_spec.rb +11 -0
  310. data/spec/rcov.opts +2 -0
  311. metadata +178 -80
@@ -1,7 +1,7 @@
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
 
@@ -28,12 +28,14 @@ module Sequel
28
28
  JOIN_METHODS = (CONDITIONED_JOIN_TYPES + UNCONDITIONED_JOIN_TYPES).map{|x| "#{x}_join".to_sym} + [:join, :join_table]
29
29
 
30
30
  # 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
31
+ QUERY_METHODS = (<<-METHS).split.map{|x| x.to_sym} + JOIN_METHODS
32
+ add_graph_aliases and distinct except exclude exclude_having exclude_where
33
+ filter for_update from from_self graph grep group group_and_count group_by having intersect invert
34
+ limit lock_style naked or order order_append order_by order_more order_prepend paginate qualify query
35
+ reverse reverse_order select select_all select_append select_group select_more server
36
+ set_defaults set_graph_aliases set_overrides unfiltered ungraphed ungrouped union
37
+ unlimited unordered where with with_recursive with_sql
38
+ METHS
37
39
 
38
40
  # Adds an further filter to an existing filter using AND. If no filter
39
41
  # exists an error is raised. This method is identical to #filter except
@@ -80,7 +82,7 @@ module Sequel
80
82
  # :from_self :: Set to false to not wrap the returned dataset in a from_self, use with care.
81
83
  #
82
84
  # DB[:items].except(DB[:other_items])
83
- # # SELECT * FROM items EXCEPT SELECT * FROM other_items
85
+ # # SELECT * FROM (SELECT * FROM items EXCEPT SELECT * FROM other_items) AS t1
84
86
  #
85
87
  # DB[:items].except(DB[:other_items], :all=>true, :from_self=>false)
86
88
  # # SELECT * FROM items EXCEPT ALL SELECT * FROM other_items
@@ -103,12 +105,29 @@ module Sequel
103
105
  # DB[:items].exclude(:category => 'software', :id=>3)
104
106
  # # SELECT * FROM items WHERE ((category != 'software') OR (id != 3))
105
107
  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)
108
+ _filter_or_exclude(true, @opts[:having] ? :having : :where, *cond, &block)
109
+ end
110
+
111
+ # Inverts the given conditions and adds them to the HAVING clause.
112
+ #
113
+ # DB[:items].select_group(:name).exclude_having{count(name) < 2}
114
+ # # SELECT name FROM items GROUP BY name HAVING (count(name) >= 2)
115
+ def exclude_having(*cond, &block)
116
+ _filter_or_exclude(true, :having, *cond, &block)
117
+ end
118
+
119
+ # Inverts the given conditions and adds them to the WHERE clause.
120
+ #
121
+ # DB[:items].select_group(:name).exclude_where(:category => 'software')
122
+ # # SELECT * FROM items WHERE (category != 'software')
123
+ #
124
+ # DB[:items].select_group(:name).
125
+ # exclude_having{count(name) < 2}.
126
+ # exclude_where(:category => 'software')
127
+ # # SELECT name FROM items WHERE (category != 'software')
128
+ # # GROUP BY name HAVING (count(name) >= 2)
129
+ def exclude_where(*cond, &block)
130
+ _filter_or_exclude(true, :where, *cond, &block)
112
131
  end
113
132
 
114
133
  # Returns a copy of the dataset with the given conditions imposed upon it.
@@ -145,7 +164,7 @@ module Sequel
145
164
  # DB[:items].filter('price < ?', 100)
146
165
  # # SELECT * FROM items WHERE price < 100
147
166
  #
148
- # DB[:items].filter([[:id, (1,2,3)], [:id, 0..10]])
167
+ # DB[:items].filter([[:id, [1,2,3]], [:id, 0..10]])
149
168
  # # SELECT * FROM items WHERE ((id IN (1, 2, 3)) AND ((id >= 0) AND (id <= 10)))
150
169
  #
151
170
  # DB[:items].filter('price < 100')
@@ -184,16 +203,22 @@ module Sequel
184
203
  def from(*source)
185
204
  table_alias_num = 0
186
205
  sources = []
206
+ ctes = nil
187
207
  source.each do |s|
188
208
  case s
189
209
  when Hash
190
210
  s.each{|k,v| sources << SQL::AliasedExpression.new(k,v)}
191
211
  when Dataset
212
+ if hoist_cte?(s)
213
+ ctes ||= []
214
+ ctes += s.opts[:with]
215
+ s = s.clone(:with=>nil)
216
+ end
192
217
  sources << SQL::AliasedExpression.new(s, dataset_alias(table_alias_num+=1))
193
218
  when Symbol
194
219
  sch, table, aliaz = split_symbol(s)
195
220
  if aliaz
196
- s = sch ? SQL::QualifiedIdentifier.new(sch.to_sym, table.to_sym) : SQL::Identifier.new(table.to_sym)
221
+ s = sch ? SQL::QualifiedIdentifier.new(sch, table) : SQL::Identifier.new(table)
197
222
  sources << SQL::AliasedExpression.new(s, aliaz.to_sym)
198
223
  else
199
224
  sources << s
@@ -203,6 +228,7 @@ module Sequel
203
228
  end
204
229
  end
205
230
  o = {:from=>sources.empty? ? nil : sources}
231
+ o[:with] = (opts[:with] || []) + ctes if ctes
206
232
  o[:num_dataset_sources] = table_alias_num if table_alias_num > 0
207
233
  clone(o)
208
234
  end
@@ -270,21 +296,25 @@ module Sequel
270
296
  end
271
297
 
272
298
  # Returns a copy of the dataset with the results grouped by the value of
273
- # the given columns.
299
+ # the given columns. If a block is given, it is treated
300
+ # as a virtual row block, similar to +filter+.
274
301
  #
275
302
  # DB[:items].group(:id) # SELECT * FROM items GROUP BY id
276
303
  # DB[:items].group(:id, :name) # SELECT * FROM items GROUP BY id, name
277
- def group(*columns)
304
+ # DB[:items].group{[a, sum(b)]} # SELECT * FROM items GROUP BY a, sum(b)
305
+ def group(*columns, &block)
306
+ virtual_row_columns(columns, block)
278
307
  clone(:group => (columns.compact.empty? ? nil : columns))
279
308
  end
280
309
 
281
310
  # Alias of group
282
- def group_by(*columns)
283
- group(*columns)
311
+ def group_by(*columns, &block)
312
+ group(*columns, &block)
284
313
  end
285
314
 
286
315
  # Returns a dataset grouped by the given column with count by group.
287
316
  # Column aliases may be supplied, and will be included in the select clause.
317
+ # If a block is given, it is treated as a virtual row block, similar to +filter+.
288
318
  #
289
319
  # Examples:
290
320
  #
@@ -299,8 +329,24 @@ module Sequel
299
329
  # DB[:items].group_and_count(:first_name___name).all
300
330
  # # SELECT first_name AS name, count(*) AS count FROM items GROUP BY first_name
301
331
  # # => [{: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]))
332
+ #
333
+ # DB[:items].group_and_count{substr(first_name, 1, 1).as(initial)}.all
334
+ # # SELECT substr(first_name, 1, 1) AS initial, count(*) AS count FROM items GROUP BY substr(first_name, 1, 1)
335
+ # # => [{:initial=>'a', :count=>1}, ...]
336
+ def group_and_count(*columns, &block)
337
+ select_group(*columns, &block).select_more(COUNT_OF_ALL_AS_COUNT)
338
+ end
339
+
340
+ # Adds the appropriate CUBE syntax to GROUP BY.
341
+ def group_cube
342
+ raise Error, "GROUP BY CUBE not supported on #{db.database_type}" unless supports_group_cube?
343
+ clone(:group_options=>:cube)
344
+ end
345
+
346
+ # Adds the appropriate ROLLUP syntax to GROUP BY.
347
+ def group_rollup
348
+ raise Error, "GROUP BY ROLLUP not supported on #{db.database_type}" unless supports_group_rollup?
349
+ clone(:group_options=>:rollup)
304
350
  end
305
351
 
306
352
  # Returns a copy of the dataset with the HAVING conditions changed. See #filter for argument types.
@@ -356,7 +402,11 @@ module Sequel
356
402
  inner_join(*args, &block)
357
403
  end
358
404
 
359
- # Returns a joined dataset. Uses the following arguments:
405
+ # Returns a joined dataset. Not usually called directly, users should use the
406
+ # appropriate join method (e.g. join, left_join, natural_join, cross_join) which fills
407
+ # in the +type+ argument.
408
+ #
409
+ # Takes the following arguments:
360
410
  #
361
411
  # * type - The type of join to do (e.g. :inner)
362
412
  # * table - Depends on type:
@@ -386,7 +436,29 @@ module Sequel
386
436
  # in which case it yields the table alias/name for the table currently being joined,
387
437
  # the table alias/name for the last joined (or first table), and an array of previous
388
438
  # SQL::JoinClause. Unlike +filter+, this block is not treated as a virtual row block.
439
+ #
440
+ # Examples:
441
+ #
442
+ # DB[:a].join_table(:cross, :b)
443
+ # # SELECT * FROM a CROSS JOIN b
444
+ #
445
+ # DB[:a].join_table(:inner, DB[:b], :c=>d)
446
+ # # SELECT * FROM a INNER JOIN (SELECT * FROM b) AS t1 ON (t1.c = a.d)
447
+ #
448
+ # DB[:a].join_table(:left, :b___c, [:d])
449
+ # # SELECT * FROM a LEFT JOIN b AS c USING (d)
450
+ #
451
+ # DB[:a].natural_join(:b).join_table(:inner, :c) do |ta, jta, js|
452
+ # (:d.qualify(ta) > :e.qualify(jta)) & {:f.qualify(ta)=>DB.from(js.first.table).select(:g)}
453
+ # end
454
+ # # SELECT * FROM a NATURAL JOIN b INNER JOIN c
455
+ # # ON ((c.d > b.e) AND (c.f IN (SELECT g FROM b)))
389
456
  def join_table(type, table, expr=nil, options={}, &block)
457
+ if hoist_cte?(table)
458
+ s, ds = hoist_cte(table)
459
+ return s.join_table(type, ds, expr, options, &block)
460
+ end
461
+
390
462
  using_join = expr.is_a?(Array) && !expr.empty? && expr.all?{|x| x.is_a?(Symbol)}
391
463
  if using_join && !supports_join_using?
392
464
  h = {}
@@ -431,6 +503,7 @@ module Sequel
431
503
  v = qualified_column_name(v, last_alias) if v.is_a?(Symbol)
432
504
  [k,v]
433
505
  end
506
+ expr = SQL::BooleanExpression.from_value_pairs(expr)
434
507
  end
435
508
  if block
436
509
  expr2 = yield(table_name, last_alias, @opts[:join] || [])
@@ -531,7 +604,7 @@ module Sequel
531
604
  # DB[:items].order{sum(name).desc} # SELECT * FROM items ORDER BY sum(name) DESC
532
605
  # DB[:items].order(nil) # SELECT * FROM items
533
606
  def order(*columns, &block)
534
- columns += Array(Sequel.virtual_row(&block)) if block
607
+ virtual_row_columns(columns, block)
535
608
  clone(:order => (columns.compact.empty?) ? nil : columns)
536
609
  end
537
610
 
@@ -565,7 +638,7 @@ module Sequel
565
638
  @opts[:order] ? ds.order_more(*@opts[:order]) : ds
566
639
  end
567
640
 
568
- # Qualify to the given table, or first source if not table is given.
641
+ # Qualify to the given table, or first source if no table is given.
569
642
  #
570
643
  # DB[:items].filter(:id=>1).qualify
571
644
  # # SELECT items.* FROM items WHERE (items.id = 1)
@@ -606,6 +679,18 @@ module Sequel
606
679
  qualify_to(first_source)
607
680
  end
608
681
 
682
+ # Modify the RETURNING clause, only supported on a few databases. If returning
683
+ # is used, instead of insert returning the autogenerated primary key or
684
+ # update/delete returning the number of modified rows, results are
685
+ # returned using +fetch_rows+.
686
+ #
687
+ # DB[:items].returning # RETURNING *
688
+ # DB[:items].returning(nil) # RETURNING NULL
689
+ # DB[:items].returning(:id, :name) # RETURNING id, name
690
+ def returning(*values)
691
+ clone(:returning=>values)
692
+ end
693
+
609
694
  # Returns a copy of the dataset with the order reversed. If no order is
610
695
  # given, the existing order is inverted.
611
696
  #
@@ -629,7 +714,7 @@ module Sequel
629
714
  # DB[:items].select(:a, :b) # SELECT a, b FROM items
630
715
  # DB[:items].select{[a, sum(b)]} # SELECT a, sum(b) FROM items
631
716
  def select(*columns, &block)
632
- columns += Array(Sequel.virtual_row(&block)) if block
717
+ virtual_row_columns(columns, block)
633
718
  m = []
634
719
  columns.each do |i|
635
720
  i.is_a?(Hash) ? m.concat(i.map{|k, v| SQL::AliasedExpression.new(k,v)}) : m << i
@@ -637,11 +722,19 @@ module Sequel
637
722
  clone(:select => m)
638
723
  end
639
724
 
640
- # Returns a copy of the dataset selecting the wildcard.
725
+ # Returns a copy of the dataset selecting the wildcard if no arguments
726
+ # are given. If arguments are given, treat them as tables and select
727
+ # all columns (using the wildcard) from each table.
641
728
  #
642
729
  # DB[:items].select(:a).select_all # SELECT * FROM items
643
- def select_all
644
- clone(:select => nil)
730
+ # DB[:items].select_all(:items) # SELECT items.* FROM items
731
+ # DB[:items].select_all(:items, :foo) # SELECT items.*, foo.* FROM items
732
+ def select_all(*tables)
733
+ if tables.empty?
734
+ clone(:select => nil)
735
+ else
736
+ select(*tables.map{|t| i, a = split_alias(t); a || i}.map{|t| SQL::ColumnAll.new(t)})
737
+ end
645
738
  end
646
739
 
647
740
  # Returns a copy of the dataset with the given columns added
@@ -653,10 +746,29 @@ module Sequel
653
746
  # DB[:items].select_append(:b) # SELECT *, b FROM items
654
747
  def select_append(*columns, &block)
655
748
  cur_sel = @opts[:select]
656
- cur_sel = [WILDCARD] if !cur_sel || cur_sel.empty?
749
+ if !cur_sel || cur_sel.empty?
750
+ unless supports_select_all_and_column?
751
+ return select_all(*(Array(@opts[:from]) + Array(@opts[:join]))).select_more(*columns, &block)
752
+ end
753
+ cur_sel = [WILDCARD]
754
+ end
657
755
  select(*(cur_sel + columns), &block)
658
756
  end
659
757
 
758
+ # Set both the select and group clauses with the given +columns+.
759
+ # Column aliases may be supplied, and will be included in the select clause.
760
+ # This also takes a virtual row block similar to +filter+.
761
+ #
762
+ # DB[:items].select_group(:a, :b)
763
+ # # SELECT a, b FROM items GROUP BY a, b
764
+ #
765
+ # DB[:items].select_group(:c___a){f(c2)}
766
+ # # SELECT c AS a, f(c2) FROM items GROUP BY c, f(c2)
767
+ def select_group(*columns, &block)
768
+ virtual_row_columns(columns, block)
769
+ select(*columns).group(*columns.map{|c| unaliased_identifier(c)})
770
+ end
771
+
660
772
  # Returns a copy of the dataset with the given columns added
661
773
  # to the existing selected columns. If no columns are currently selected
662
774
  # it will just select the columns given.
@@ -670,7 +782,7 @@ module Sequel
670
782
  end
671
783
 
672
784
  # 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
785
+ # shard to run a query against, or to override the default (where SELECT uses
674
786
  # :read_only database and all other queries use the :default database). This
675
787
  # method is always available but is only useful when database sharding is being
676
788
  # used.
@@ -702,6 +814,22 @@ module Sequel
702
814
  clone(:overrides=>hash.merge(@opts[:overrides]||{}))
703
815
  end
704
816
 
817
+ # Unbind bound variables from this dataset's filter and return an array of two
818
+ # objects. The first object is a modified dataset where the filter has been
819
+ # replaced with one that uses bound variable placeholders. The second object
820
+ # is the hash of unbound variables. You can then prepare and execute (or just
821
+ # call) the dataset with the bound variables to get results.
822
+ #
823
+ # ds, bv = DB[:items].filter(:a=>1).unbind
824
+ # ds # SELECT * FROM items WHERE (a = $a)
825
+ # bv # {:a => 1}
826
+ # ds.call(:select, bv)
827
+ def unbind
828
+ u = Unbinder.new
829
+ ds = clone(:where=>u.transform(opts[:where]), :join=>u.transform(opts[:join]))
830
+ [ds, u.binds]
831
+ end
832
+
705
833
  # Returns a copy of the dataset with no filters (HAVING or WHERE clause) applied.
706
834
  #
707
835
  # DB[:items].group(:a).having(:a=>1).where(:b).unfiltered
@@ -726,8 +854,8 @@ module Sequel
726
854
  # :all :: Set to true to use UNION ALL instead of UNION, so duplicate rows can occur
727
855
  # :from_self :: Set to false to not wrap the returned dataset in a from_self, use with care.
728
856
  #
729
- # DB[:items].union(DB[:other_items]).sql
730
- # #=> "SELECT * FROM items UNION SELECT * FROM other_items"
857
+ # DB[:items].union(DB[:other_items])
858
+ # # SELECT * FROM (SELECT * FROM items UNION SELECT * FROM other_items) AS t1
731
859
  #
732
860
  # DB[:items].union(DB[:other_items], :all=>true, :from_self=>false)
733
861
  # # SELECT * FROM items UNION ALL SELECT * FROM other_items
@@ -774,7 +902,12 @@ module Sequel
774
902
  # # WITH items AS (SELECT * FROM syx WHERE (name LIKE 'A%')) SELECT * FROM items
775
903
  def with(name, dataset, opts={})
776
904
  raise(Error, 'This datatset does not support common table expressions') unless supports_cte?
777
- clone(:with=>(@opts[:with]||[]) + [opts.merge(:name=>name, :dataset=>dataset)])
905
+ if hoist_cte?(dataset)
906
+ s, ds = hoist_cte(dataset)
907
+ s.with(name, ds, opts)
908
+ else
909
+ clone(:with=>(@opts[:with]||[]) + [opts.merge(:name=>name, :dataset=>dataset)])
910
+ end
778
911
  end
779
912
 
780
913
  # Add a recursive common table expression (CTE) with the given name, a dataset that
@@ -796,20 +929,50 @@ module Sequel
796
929
  # # SELECT i AS id, pi AS parent_id FROM t
797
930
  def with_recursive(name, nonrecursive, recursive, opts={})
798
931
  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}))])
932
+ if hoist_cte?(nonrecursive)
933
+ s, ds = hoist_cte(nonrecursive)
934
+ s.with_recursive(name, ds, recursive, opts)
935
+ elsif hoist_cte?(recursive)
936
+ s, ds = hoist_cte(recursive)
937
+ s.with_recursive(name, nonrecursive, ds, opts)
938
+ else
939
+ clone(:with=>(@opts[:with]||[]) + [opts.merge(:recursive=>true, :name=>name, :dataset=>nonrecursive.union(recursive, {:all=>opts[:union_all] != false, :from_self=>false}))])
940
+ end
800
941
  end
801
942
 
802
943
  # Returns a copy of the dataset with the static SQL used. This is useful if you want
803
944
  # to keep the same row_proc/graph, but change the SQL used to custom SQL.
804
945
  #
805
946
  # DB[:items].with_sql('SELECT * FROM foo') # SELECT * FROM foo
947
+ #
948
+ # You can use placeholders in your SQL and provide arguments for those placeholders:
949
+ #
950
+ # DB[:items].with_sql('SELECT ? FROM foo', 1) # SELECT 1 FROM foo
951
+ #
952
+ # You can also provide a method name and arguments to call to get the SQL:
953
+ #
954
+ # DB[:items].with_sql(:insert_sql, :b=>1) # INSERT INTO items (b) VALUES (1)
806
955
  def with_sql(sql, *args)
807
- sql = SQL::PlaceholderLiteralString.new(sql, args) unless args.empty?
956
+ if sql.is_a?(Symbol)
957
+ sql = send(sql, *args)
958
+ else
959
+ sql = SQL::PlaceholderLiteralString.new(sql, args) unless args.empty?
960
+ end
808
961
  clone(:sql=>sql)
809
962
  end
810
963
 
811
964
  protected
812
965
 
966
+ # Add the dataset to the list of compounds
967
+ def compound_clone(type, dataset, opts)
968
+ if hoist_cte?(dataset)
969
+ s, ds = hoist_cte(dataset)
970
+ return s.compound_clone(type, ds, opts)
971
+ end
972
+ ds = compound_from_self.clone(:compounds=>Array(@opts[:compounds]).map{|x| x.dup} + [[type, dataset.compound_from_self, opts[:all]]])
973
+ opts[:from_self] == false ? ds : ds.from_self(opts)
974
+ end
975
+
813
976
  # Return true if the dataset has a non-nil value for any key in opts.
814
977
  def options_overlap(opts)
815
978
  !(@opts.collect{|k,v| k unless v.nil?}.compact & opts).empty?
@@ -823,22 +986,22 @@ module Sequel
823
986
 
824
987
  private
825
988
 
826
- # Internal filter method so it works on either the having or where clauses.
827
- def _filter(clause, *cond, &block)
989
+ # Internal filter/exclude method so it works on either the having or where clauses.
990
+ def _filter_or_exclude(invert, clause, *cond, &block)
828
991
  cond = cond.first if cond.size == 1
829
992
  if cond.respond_to?(:empty?) && cond.empty? && !block
830
993
  clone
831
994
  else
832
995
  cond = filter_expr(cond, &block)
996
+ cond = SQL::BooleanExpression.invert(cond) if invert
833
997
  cond = SQL::BooleanExpression.new(:AND, @opts[clause], cond) if @opts[clause]
834
998
  clone(clause => cond)
835
999
  end
836
1000
  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)
1001
+
1002
+ # Internal filter method so it works on either the having or where clauses.
1003
+ def _filter(clause, *cond, &block)
1004
+ _filter_or_exclude(false, clause, *cond, &block)
842
1005
  end
843
1006
 
844
1007
  # SQL expression object based on the expr type. See +filter+.
@@ -867,7 +1030,13 @@ module Sequel
867
1030
  when Symbol, SQL::Expression
868
1031
  expr
869
1032
  when TrueClass, FalseClass
870
- SQL::BooleanExpression.new(:NOOP, expr)
1033
+ if supports_where_true?
1034
+ SQL::BooleanExpression.new(:NOOP, expr)
1035
+ elsif expr
1036
+ SQL::Constants::SQLTRUE
1037
+ else
1038
+ SQL::Constants::SQLFALSE
1039
+ end
871
1040
  when String
872
1041
  LiteralString.new("(#{expr})")
873
1042
  else
@@ -875,6 +1044,18 @@ module Sequel
875
1044
  end
876
1045
  end
877
1046
 
1047
+ # Return two datasets, the first a clone of the receiver with the WITH
1048
+ # clause from the given dataset added to it, and the second a clone of
1049
+ # the given dataset with the WITH clause removed.
1050
+ def hoist_cte(ds)
1051
+ [clone(:with => (opts[:with] || []) + ds.opts[:with]), ds.clone(:with => nil)]
1052
+ end
1053
+
1054
+ # Whether CTEs need to be hoisted from the given ds into the current ds.
1055
+ def hoist_cte?(ds)
1056
+ ds.is_a?(Dataset) && ds.opts[:with] && !supports_cte_in_subqueries?
1057
+ end
1058
+
878
1059
  # Inverts the given order by breaking it into a list of column references
879
1060
  # and inverting them.
880
1061
  #
@@ -892,5 +1073,17 @@ module Sequel
892
1073
  end
893
1074
  end
894
1075
  end
1076
+
1077
+ # Return self if the dataset already has a server, or a cloned dataset with the
1078
+ # default server otherwise.
1079
+ def default_server
1080
+ @opts[:server] ? self : clone(:server=>:default)
1081
+ end
1082
+
1083
+ # Treat the +block+ as a virtual_row block if not +nil+ and
1084
+ # add the resulting columns to the +columns+ array (modifies +columns+).
1085
+ def virtual_row_columns(columns, block)
1086
+ columns.concat(Array(Sequel.virtual_row(&block))) if block
1087
+ end
895
1088
  end
896
1089
  end