sequel 3.21.0 → 3.29.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 (200) hide show
  1. data/CHANGELOG +413 -3
  2. data/README.rdoc +20 -6
  3. data/Rakefile +23 -16
  4. data/bin/sequel +1 -5
  5. data/doc/association_basics.rdoc +210 -43
  6. data/doc/dataset_basics.rdoc +4 -4
  7. data/doc/mass_assignment.rdoc +54 -0
  8. data/doc/migration.rdoc +15 -538
  9. data/doc/model_hooks.rdoc +72 -27
  10. data/doc/opening_databases.rdoc +86 -50
  11. data/doc/prepared_statements.rdoc +40 -13
  12. data/doc/reflection.rdoc +8 -2
  13. data/doc/release_notes/3.22.0.txt +39 -0
  14. data/doc/release_notes/3.23.0.txt +172 -0
  15. data/doc/release_notes/3.24.0.txt +420 -0
  16. data/doc/release_notes/3.25.0.txt +88 -0
  17. data/doc/release_notes/3.26.0.txt +88 -0
  18. data/doc/release_notes/3.27.0.txt +82 -0
  19. data/doc/release_notes/3.28.0.txt +304 -0
  20. data/doc/release_notes/3.29.0.txt +459 -0
  21. data/doc/schema_modification.rdoc +547 -0
  22. data/doc/sharding.rdoc +7 -1
  23. data/doc/testing.rdoc +115 -0
  24. data/doc/transactions.rdoc +137 -0
  25. data/doc/virtual_rows.rdoc +2 -2
  26. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  27. data/lib/sequel/adapters/ado.rb +40 -18
  28. data/lib/sequel/adapters/amalgalite.rb +15 -7
  29. data/lib/sequel/adapters/db2.rb +175 -86
  30. data/lib/sequel/adapters/dbi.rb +15 -15
  31. data/lib/sequel/adapters/do/mysql.rb +0 -5
  32. data/lib/sequel/adapters/do/postgres.rb +0 -5
  33. data/lib/sequel/adapters/do/sqlite.rb +0 -5
  34. data/lib/sequel/adapters/do.rb +17 -36
  35. data/lib/sequel/adapters/firebird.rb +27 -208
  36. data/lib/sequel/adapters/ibmdb.rb +448 -0
  37. data/lib/sequel/adapters/informix.rb +6 -23
  38. data/lib/sequel/adapters/jdbc/as400.rb +5 -31
  39. data/lib/sequel/adapters/jdbc/db2.rb +44 -0
  40. data/lib/sequel/adapters/jdbc/derby.rb +217 -0
  41. data/lib/sequel/adapters/jdbc/firebird.rb +29 -0
  42. data/lib/sequel/adapters/jdbc/h2.rb +26 -17
  43. data/lib/sequel/adapters/jdbc/hsqldb.rb +166 -0
  44. data/lib/sequel/adapters/jdbc/informix.rb +26 -0
  45. data/lib/sequel/adapters/jdbc/jtds.rb +29 -0
  46. data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
  47. data/lib/sequel/adapters/jdbc/mysql.rb +9 -10
  48. data/lib/sequel/adapters/jdbc/oracle.rb +67 -25
  49. data/lib/sequel/adapters/jdbc/postgresql.rb +6 -11
  50. data/lib/sequel/adapters/jdbc/sqlite.rb +0 -5
  51. data/lib/sequel/adapters/jdbc/sqlserver.rb +41 -0
  52. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  53. data/lib/sequel/adapters/jdbc.rb +107 -38
  54. data/lib/sequel/adapters/mock.rb +315 -0
  55. data/lib/sequel/adapters/mysql.rb +78 -182
  56. data/lib/sequel/adapters/mysql2.rb +24 -23
  57. data/lib/sequel/adapters/odbc/db2.rb +17 -0
  58. data/lib/sequel/adapters/odbc/mssql.rb +0 -5
  59. data/lib/sequel/adapters/odbc.rb +28 -9
  60. data/lib/sequel/adapters/openbase.rb +2 -4
  61. data/lib/sequel/adapters/oracle.rb +349 -51
  62. data/lib/sequel/adapters/postgres.rb +169 -21
  63. data/lib/sequel/adapters/shared/access.rb +21 -6
  64. data/lib/sequel/adapters/shared/db2.rb +288 -0
  65. data/lib/sequel/adapters/shared/firebird.rb +214 -0
  66. data/lib/sequel/adapters/shared/informix.rb +45 -0
  67. data/lib/sequel/adapters/shared/mssql.rb +108 -65
  68. data/lib/sequel/adapters/shared/mysql.rb +56 -13
  69. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +149 -0
  70. data/lib/sequel/adapters/shared/oracle.rb +185 -34
  71. data/lib/sequel/adapters/shared/postgres.rb +107 -58
  72. data/lib/sequel/adapters/shared/progress.rb +0 -6
  73. data/lib/sequel/adapters/shared/sqlite.rb +168 -41
  74. data/lib/sequel/adapters/sqlite.rb +27 -18
  75. data/lib/sequel/adapters/swift/mysql.rb +9 -5
  76. data/lib/sequel/adapters/swift/postgres.rb +0 -5
  77. data/lib/sequel/adapters/swift/sqlite.rb +6 -4
  78. data/lib/sequel/adapters/swift.rb +5 -5
  79. data/lib/sequel/adapters/tinytds.rb +152 -17
  80. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +63 -0
  81. data/lib/sequel/ast_transformer.rb +190 -0
  82. data/lib/sequel/connection_pool/threaded.rb +3 -2
  83. data/lib/sequel/connection_pool.rb +1 -1
  84. data/lib/sequel/core.rb +46 -5
  85. data/lib/sequel/database/connecting.rb +5 -6
  86. data/lib/sequel/database/dataset.rb +4 -4
  87. data/lib/sequel/database/dataset_defaults.rb +59 -1
  88. data/lib/sequel/database/logging.rb +1 -1
  89. data/lib/sequel/database/misc.rb +100 -19
  90. data/lib/sequel/database/query.rb +153 -58
  91. data/lib/sequel/database/schema_generator.rb +8 -5
  92. data/lib/sequel/database/schema_methods.rb +59 -29
  93. data/lib/sequel/dataset/actions.rb +194 -57
  94. data/lib/sequel/dataset/features.rb +81 -8
  95. data/lib/sequel/dataset/graph.rb +8 -7
  96. data/lib/sequel/dataset/misc.rb +50 -23
  97. data/lib/sequel/dataset/mutation.rb +5 -6
  98. data/lib/sequel/dataset/prepared_statements.rb +32 -15
  99. data/lib/sequel/dataset/query.rb +223 -42
  100. data/lib/sequel/dataset/sql.rb +58 -61
  101. data/lib/sequel/dataset.rb +8 -0
  102. data/lib/sequel/exceptions.rb +4 -0
  103. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  104. data/lib/sequel/extensions/migration.rb +6 -4
  105. data/lib/sequel/extensions/named_timezones.rb +5 -0
  106. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  107. data/lib/sequel/extensions/to_dot.rb +95 -83
  108. data/lib/sequel/model/associations.rb +997 -331
  109. data/lib/sequel/model/base.rb +386 -129
  110. data/lib/sequel/model/errors.rb +1 -1
  111. data/lib/sequel/model/exceptions.rb +5 -1
  112. data/lib/sequel/model.rb +15 -8
  113. data/lib/sequel/plugins/association_pks.rb +22 -4
  114. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  115. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  116. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  117. data/lib/sequel/plugins/force_encoding.rb +6 -6
  118. data/lib/sequel/plugins/identity_map.rb +114 -7
  119. data/lib/sequel/plugins/many_through_many.rb +65 -7
  120. data/lib/sequel/plugins/prepared_statements.rb +151 -0
  121. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  122. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  123. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  124. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  125. data/lib/sequel/plugins/serialization.rb +6 -1
  126. data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
  127. data/lib/sequel/plugins/sharding.rb +12 -25
  128. data/lib/sequel/plugins/single_table_inheritance.rb +3 -1
  129. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  130. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  131. data/lib/sequel/plugins/xml_serializer.rb +3 -3
  132. data/lib/sequel/sql.rb +107 -51
  133. data/lib/sequel/timezones.rb +44 -35
  134. data/lib/sequel/version.rb +1 -1
  135. data/spec/adapters/db2_spec.rb +146 -0
  136. data/spec/adapters/mssql_spec.rb +65 -29
  137. data/spec/adapters/mysql_spec.rb +122 -123
  138. data/spec/adapters/oracle_spec.rb +48 -76
  139. data/spec/adapters/postgres_spec.rb +213 -61
  140. data/spec/adapters/spec_helper.rb +6 -5
  141. data/spec/adapters/sqlite_spec.rb +35 -21
  142. data/spec/core/connection_pool_spec.rb +71 -92
  143. data/spec/core/core_sql_spec.rb +20 -31
  144. data/spec/core/database_spec.rb +729 -508
  145. data/spec/core/dataset_spec.rb +980 -1044
  146. data/spec/core/expression_filters_spec.rb +159 -42
  147. data/spec/core/mock_adapter_spec.rb +378 -0
  148. data/spec/core/object_graph_spec.rb +48 -114
  149. data/spec/core/schema_generator_spec.rb +3 -3
  150. data/spec/core/schema_spec.rb +298 -38
  151. data/spec/core/spec_helper.rb +6 -48
  152. data/spec/extensions/association_pks_spec.rb +38 -0
  153. data/spec/extensions/class_table_inheritance_spec.rb +1 -1
  154. data/spec/extensions/columns_introspection_spec.rb +91 -0
  155. data/spec/extensions/dataset_associations_spec.rb +199 -0
  156. data/spec/extensions/defaults_setter_spec.rb +64 -0
  157. data/spec/extensions/identity_map_spec.rb +162 -0
  158. data/spec/extensions/instance_hooks_spec.rb +71 -0
  159. data/spec/extensions/many_through_many_spec.rb +195 -20
  160. data/spec/extensions/migration_spec.rb +17 -17
  161. data/spec/extensions/named_timezones_spec.rb +22 -2
  162. data/spec/extensions/nested_attributes_spec.rb +4 -0
  163. data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
  164. data/spec/extensions/prepared_statements_safe_spec.rb +79 -0
  165. data/spec/extensions/prepared_statements_spec.rb +72 -0
  166. data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
  167. data/spec/extensions/schema_dumper_spec.rb +2 -2
  168. data/spec/extensions/schema_spec.rb +13 -21
  169. data/spec/extensions/serialization_modification_detection_spec.rb +37 -0
  170. data/spec/extensions/serialization_spec.rb +5 -8
  171. data/spec/extensions/single_table_inheritance_spec.rb +11 -0
  172. data/spec/extensions/spec_helper.rb +7 -1
  173. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  174. data/spec/extensions/to_dot_spec.rb +3 -5
  175. data/spec/extensions/typecast_on_load_spec.rb +1 -6
  176. data/spec/extensions/xml_serializer_spec.rb +16 -4
  177. data/spec/integration/associations_test.rb +522 -21
  178. data/spec/integration/database_test.rb +4 -2
  179. data/spec/integration/dataset_test.rb +375 -62
  180. data/spec/integration/eager_loader_test.rb +19 -21
  181. data/spec/integration/model_test.rb +80 -1
  182. data/spec/integration/plugin_test.rb +304 -116
  183. data/spec/integration/prepared_statement_test.rb +200 -120
  184. data/spec/integration/schema_test.rb +161 -30
  185. data/spec/integration/spec_helper.rb +39 -30
  186. data/spec/integration/timezone_test.rb +38 -12
  187. data/spec/integration/transaction_test.rb +172 -5
  188. data/spec/integration/type_test.rb +17 -3
  189. data/spec/model/association_reflection_spec.rb +174 -7
  190. data/spec/model/associations_spec.rb +849 -661
  191. data/spec/model/base_spec.rb +255 -95
  192. data/spec/model/dataset_methods_spec.rb +7 -27
  193. data/spec/model/eager_loading_spec.rb +640 -676
  194. data/spec/model/hooks_spec.rb +309 -67
  195. data/spec/model/model_spec.rb +207 -167
  196. data/spec/model/plugins_spec.rb +24 -13
  197. data/spec/model/record_spec.rb +321 -218
  198. data/spec/model/spec_helper.rb +13 -71
  199. data/spec/model/validations_spec.rb +11 -0
  200. metadata +95 -38
@@ -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.
@@ -184,11 +203,17 @@ 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)
@@ -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,12 @@ 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)
304
338
  end
305
339
 
306
340
  # Returns a copy of the dataset with the HAVING conditions changed. See #filter for argument types.
@@ -356,7 +390,11 @@ module Sequel
356
390
  inner_join(*args, &block)
357
391
  end
358
392
 
359
- # Returns a joined dataset. Uses the following arguments:
393
+ # Returns a joined dataset. Not usually called directly, users should use the
394
+ # appropriate join method (e.g. join, left_join, natural_join, cross_join) which fills
395
+ # in the +type+ argument.
396
+ #
397
+ # Takes the following arguments:
360
398
  #
361
399
  # * type - The type of join to do (e.g. :inner)
362
400
  # * table - Depends on type:
@@ -386,7 +424,29 @@ module Sequel
386
424
  # in which case it yields the table alias/name for the table currently being joined,
387
425
  # the table alias/name for the last joined (or first table), and an array of previous
388
426
  # SQL::JoinClause. Unlike +filter+, this block is not treated as a virtual row block.
427
+ #
428
+ # Examples:
429
+ #
430
+ # DB[:a].join_table(:cross, :b)
431
+ # # SELECT * FROM a CROSS JOIN b
432
+ #
433
+ # DB[:a].join_table(:inner, DB[:b], :c=>d)
434
+ # # SELECT * FROM a INNER JOIN (SELECT * FROM b) AS t1 ON (t1.c = a.d)
435
+ #
436
+ # DB[:a].join_table(:left, :b___c, [:d])
437
+ # # SELECT * FROM a LEFT JOIN b AS c USING (d)
438
+ #
439
+ # DB[:a].natural_join(:b).join_table(:inner, :c) do |ta, jta, js|
440
+ # (:d.qualify(ta) > :e.qualify(jta)) & {:f.qualify(ta)=>DB.from(js.first.table).select(:g)}
441
+ # end
442
+ # # SELECT * FROM a NATURAL JOIN b INNER JOIN c
443
+ # # ON ((c.d > b.e) AND (c.f IN (SELECT g FROM b)))
389
444
  def join_table(type, table, expr=nil, options={}, &block)
445
+ if hoist_cte?(table)
446
+ s, ds = hoist_cte(table)
447
+ return s.join_table(type, ds, expr, options, &block)
448
+ end
449
+
390
450
  using_join = expr.is_a?(Array) && !expr.empty? && expr.all?{|x| x.is_a?(Symbol)}
391
451
  if using_join && !supports_join_using?
392
452
  h = {}
@@ -431,6 +491,7 @@ module Sequel
431
491
  v = qualified_column_name(v, last_alias) if v.is_a?(Symbol)
432
492
  [k,v]
433
493
  end
494
+ expr = SQL::BooleanExpression.from_value_pairs(expr)
434
495
  end
435
496
  if block
436
497
  expr2 = yield(table_name, last_alias, @opts[:join] || [])
@@ -531,7 +592,7 @@ module Sequel
531
592
  # DB[:items].order{sum(name).desc} # SELECT * FROM items ORDER BY sum(name) DESC
532
593
  # DB[:items].order(nil) # SELECT * FROM items
533
594
  def order(*columns, &block)
534
- columns += Array(Sequel.virtual_row(&block)) if block
595
+ virtual_row_columns(columns, block)
535
596
  clone(:order => (columns.compact.empty?) ? nil : columns)
536
597
  end
537
598
 
@@ -565,7 +626,7 @@ module Sequel
565
626
  @opts[:order] ? ds.order_more(*@opts[:order]) : ds
566
627
  end
567
628
 
568
- # Qualify to the given table, or first source if not table is given.
629
+ # Qualify to the given table, or first source if no table is given.
569
630
  #
570
631
  # DB[:items].filter(:id=>1).qualify
571
632
  # # SELECT items.* FROM items WHERE (items.id = 1)
@@ -606,6 +667,18 @@ module Sequel
606
667
  qualify_to(first_source)
607
668
  end
608
669
 
670
+ # Modify the RETURNING clause, only supported on a few databases. If returning
671
+ # is used, instead of insert returning the autogenerated primary key or
672
+ # update/delete returning the number of modified rows, results are
673
+ # returned using +fetch_rows+.
674
+ #
675
+ # DB[:items].returning # RETURNING *
676
+ # DB[:items].returning(nil) # RETURNING NULL
677
+ # DB[:items].returning(:id, :name) # RETURNING id, name
678
+ def returning(*values)
679
+ clone(:returning=>values)
680
+ end
681
+
609
682
  # Returns a copy of the dataset with the order reversed. If no order is
610
683
  # given, the existing order is inverted.
611
684
  #
@@ -629,7 +702,7 @@ module Sequel
629
702
  # DB[:items].select(:a, :b) # SELECT a, b FROM items
630
703
  # DB[:items].select{[a, sum(b)]} # SELECT a, sum(b) FROM items
631
704
  def select(*columns, &block)
632
- columns += Array(Sequel.virtual_row(&block)) if block
705
+ virtual_row_columns(columns, block)
633
706
  m = []
634
707
  columns.each do |i|
635
708
  i.is_a?(Hash) ? m.concat(i.map{|k, v| SQL::AliasedExpression.new(k,v)}) : m << i
@@ -637,11 +710,19 @@ module Sequel
637
710
  clone(:select => m)
638
711
  end
639
712
 
640
- # Returns a copy of the dataset selecting the wildcard.
713
+ # Returns a copy of the dataset selecting the wildcard if no arguments
714
+ # are given. If arguments are given, treat them as tables and select
715
+ # all columns (using the wildcard) from each table.
641
716
  #
642
717
  # DB[:items].select(:a).select_all # SELECT * FROM items
643
- def select_all
644
- clone(:select => nil)
718
+ # DB[:items].select_all(:items) # SELECT items.* FROM items
719
+ # DB[:items].select_all(:items, :foo) # SELECT items.*, foo.* FROM items
720
+ def select_all(*tables)
721
+ if tables.empty?
722
+ clone(:select => nil)
723
+ else
724
+ select(*tables.map{|t| i, a = split_alias(t); a || i}.map{|t| SQL::ColumnAll.new(t)})
725
+ end
645
726
  end
646
727
 
647
728
  # Returns a copy of the dataset with the given columns added
@@ -653,10 +734,29 @@ module Sequel
653
734
  # DB[:items].select_append(:b) # SELECT *, b FROM items
654
735
  def select_append(*columns, &block)
655
736
  cur_sel = @opts[:select]
656
- cur_sel = [WILDCARD] if !cur_sel || cur_sel.empty?
737
+ if !cur_sel || cur_sel.empty?
738
+ unless supports_select_all_and_column?
739
+ return select_all(*(Array(@opts[:from]) + Array(@opts[:join]))).select_more(*columns, &block)
740
+ end
741
+ cur_sel = [WILDCARD]
742
+ end
657
743
  select(*(cur_sel + columns), &block)
658
744
  end
659
745
 
746
+ # Set both the select and group clauses with the given +columns+.
747
+ # Column aliases may be supplied, and will be included in the select clause.
748
+ # This also takes a virtual row block similar to +filter+.
749
+ #
750
+ # DB[:items].select_group(:a, :b)
751
+ # # SELECT a, b FROM items GROUP BY a, b
752
+ #
753
+ # DB[:items].select_group(:c___a){f(c2)}
754
+ # # SELECT c AS a, f(c2) FROM items GROUP BY c, f(c2)
755
+ def select_group(*columns, &block)
756
+ virtual_row_columns(columns, block)
757
+ select(*columns).group(*columns.map{|c| unaliased_identifier(c)})
758
+ end
759
+
660
760
  # Returns a copy of the dataset with the given columns added
661
761
  # to the existing selected columns. If no columns are currently selected
662
762
  # it will just select the columns given.
@@ -670,7 +770,7 @@ module Sequel
670
770
  end
671
771
 
672
772
  # 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
773
+ # shard to run a query against, or to override the default (where SELECT uses
674
774
  # :read_only database and all other queries use the :default database). This
675
775
  # method is always available but is only useful when database sharding is being
676
776
  # used.
@@ -702,6 +802,22 @@ module Sequel
702
802
  clone(:overrides=>hash.merge(@opts[:overrides]||{}))
703
803
  end
704
804
 
805
+ # Unbind bound variables from this dataset's filter and return an array of two
806
+ # objects. The first object is a modified dataset where the filter has been
807
+ # replaced with one that uses bound variable placeholders. The second object
808
+ # is the hash of unbound variables. You can then prepare and execute (or just
809
+ # call) the dataset with the bound variables to get results.
810
+ #
811
+ # ds, bv = DB[:items].filter(:a=>1).unbind
812
+ # ds # SELECT * FROM items WHERE (a = $a)
813
+ # bv # {:a => 1}
814
+ # ds.call(:select, bv)
815
+ def unbind
816
+ u = Unbinder.new
817
+ ds = clone(:where=>u.transform(opts[:where]), :join=>u.transform(opts[:join]))
818
+ [ds, u.binds]
819
+ end
820
+
705
821
  # Returns a copy of the dataset with no filters (HAVING or WHERE clause) applied.
706
822
  #
707
823
  # DB[:items].group(:a).having(:a=>1).where(:b).unfiltered
@@ -726,8 +842,8 @@ module Sequel
726
842
  # :all :: Set to true to use UNION ALL instead of UNION, so duplicate rows can occur
727
843
  # :from_self :: Set to false to not wrap the returned dataset in a from_self, use with care.
728
844
  #
729
- # DB[:items].union(DB[:other_items]).sql
730
- # #=> "SELECT * FROM items UNION SELECT * FROM other_items"
845
+ # DB[:items].union(DB[:other_items])
846
+ # # SELECT * FROM (SELECT * FROM items UNION SELECT * FROM other_items) AS t1
731
847
  #
732
848
  # DB[:items].union(DB[:other_items], :all=>true, :from_self=>false)
733
849
  # # SELECT * FROM items UNION ALL SELECT * FROM other_items
@@ -774,7 +890,12 @@ module Sequel
774
890
  # # WITH items AS (SELECT * FROM syx WHERE (name LIKE 'A%')) SELECT * FROM items
775
891
  def with(name, dataset, opts={})
776
892
  raise(Error, 'This datatset does not support common table expressions') unless supports_cte?
777
- clone(:with=>(@opts[:with]||[]) + [opts.merge(:name=>name, :dataset=>dataset)])
893
+ if hoist_cte?(dataset)
894
+ s, ds = hoist_cte(dataset)
895
+ s.with(name, ds, opts)
896
+ else
897
+ clone(:with=>(@opts[:with]||[]) + [opts.merge(:name=>name, :dataset=>dataset)])
898
+ end
778
899
  end
779
900
 
780
901
  # Add a recursive common table expression (CTE) with the given name, a dataset that
@@ -796,20 +917,50 @@ module Sequel
796
917
  # # SELECT i AS id, pi AS parent_id FROM t
797
918
  def with_recursive(name, nonrecursive, recursive, opts={})
798
919
  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}))])
920
+ if hoist_cte?(nonrecursive)
921
+ s, ds = hoist_cte(nonrecursive)
922
+ s.with_recursive(name, ds, recursive, opts)
923
+ elsif hoist_cte?(recursive)
924
+ s, ds = hoist_cte(recursive)
925
+ s.with_recursive(name, nonrecursive, ds, opts)
926
+ else
927
+ clone(:with=>(@opts[:with]||[]) + [opts.merge(:recursive=>true, :name=>name, :dataset=>nonrecursive.union(recursive, {:all=>opts[:union_all] != false, :from_self=>false}))])
928
+ end
800
929
  end
801
930
 
802
931
  # Returns a copy of the dataset with the static SQL used. This is useful if you want
803
932
  # to keep the same row_proc/graph, but change the SQL used to custom SQL.
804
933
  #
805
934
  # DB[:items].with_sql('SELECT * FROM foo') # SELECT * FROM foo
935
+ #
936
+ # You can use placeholders in your SQL and provide arguments for those placeholders:
937
+ #
938
+ # DB[:items].with_sql('SELECT ? FROM foo', 1) # SELECT 1 FROM foo
939
+ #
940
+ # You can also provide a method name and arguments to call to get the SQL:
941
+ #
942
+ # DB[:items].with_sql(:insert_sql, :b=>1) # INSERT INTO items (b) VALUES (1)
806
943
  def with_sql(sql, *args)
807
- sql = SQL::PlaceholderLiteralString.new(sql, args) unless args.empty?
944
+ if sql.is_a?(Symbol)
945
+ sql = send(sql, *args)
946
+ else
947
+ sql = SQL::PlaceholderLiteralString.new(sql, args) unless args.empty?
948
+ end
808
949
  clone(:sql=>sql)
809
950
  end
810
951
 
811
952
  protected
812
953
 
954
+ # Add the dataset to the list of compounds
955
+ def compound_clone(type, dataset, opts)
956
+ if hoist_cte?(dataset)
957
+ s, ds = hoist_cte(dataset)
958
+ return s.compound_clone(type, ds, opts)
959
+ end
960
+ ds = compound_from_self.clone(:compounds=>Array(@opts[:compounds]).map{|x| x.dup} + [[type, dataset.compound_from_self, opts[:all]]])
961
+ opts[:from_self] == false ? ds : ds.from_self(opts)
962
+ end
963
+
813
964
  # Return true if the dataset has a non-nil value for any key in opts.
814
965
  def options_overlap(opts)
815
966
  !(@opts.collect{|k,v| k unless v.nil?}.compact & opts).empty?
@@ -823,22 +974,22 @@ module Sequel
823
974
 
824
975
  private
825
976
 
826
- # Internal filter method so it works on either the having or where clauses.
827
- def _filter(clause, *cond, &block)
977
+ # Internal filter/exclude method so it works on either the having or where clauses.
978
+ def _filter_or_exclude(invert, clause, *cond, &block)
828
979
  cond = cond.first if cond.size == 1
829
980
  if cond.respond_to?(:empty?) && cond.empty? && !block
830
981
  clone
831
982
  else
832
983
  cond = filter_expr(cond, &block)
984
+ cond = SQL::BooleanExpression.invert(cond) if invert
833
985
  cond = SQL::BooleanExpression.new(:AND, @opts[clause], cond) if @opts[clause]
834
986
  clone(clause => cond)
835
987
  end
836
988
  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)
989
+
990
+ # Internal filter method so it works on either the having or where clauses.
991
+ def _filter(clause, *cond, &block)
992
+ _filter_or_exclude(false, clause, *cond, &block)
842
993
  end
843
994
 
844
995
  # SQL expression object based on the expr type. See +filter+.
@@ -867,7 +1018,13 @@ module Sequel
867
1018
  when Symbol, SQL::Expression
868
1019
  expr
869
1020
  when TrueClass, FalseClass
870
- SQL::BooleanExpression.new(:NOOP, expr)
1021
+ if supports_where_true?
1022
+ SQL::BooleanExpression.new(:NOOP, expr)
1023
+ elsif expr
1024
+ SQL::Constants::SQLTRUE
1025
+ else
1026
+ SQL::Constants::SQLFALSE
1027
+ end
871
1028
  when String
872
1029
  LiteralString.new("(#{expr})")
873
1030
  else
@@ -875,6 +1032,18 @@ module Sequel
875
1032
  end
876
1033
  end
877
1034
 
1035
+ # Return two datasets, the first a clone of the receiver with the WITH
1036
+ # clause from the given dataset added to it, and the second a clone of
1037
+ # the given dataset with the WITH clause removed.
1038
+ def hoist_cte(ds)
1039
+ [clone(:with => (opts[:with] || []) + ds.opts[:with]), ds.clone(:with => nil)]
1040
+ end
1041
+
1042
+ # Whether CTEs need to be hoisted from the given ds into the current ds.
1043
+ def hoist_cte?(ds)
1044
+ ds.is_a?(Dataset) && ds.opts[:with] && !supports_cte_in_subqueries?
1045
+ end
1046
+
878
1047
  # Inverts the given order by breaking it into a list of column references
879
1048
  # and inverting them.
880
1049
  #
@@ -892,5 +1061,17 @@ module Sequel
892
1061
  end
893
1062
  end
894
1063
  end
1064
+
1065
+ # Return self if the dataset already has a server, or a cloned dataset with the
1066
+ # default server otherwise.
1067
+ def default_server
1068
+ @opts[:server] ? self : clone(:server=>:default)
1069
+ end
1070
+
1071
+ # Treat the +block+ as a virtual_row block if not +nil+ and
1072
+ # add the resulting columns to the +columns+ array (modifies +columns+).
1073
+ def virtual_row_columns(columns, block)
1074
+ columns.concat(Array(Sequel.virtual_row(&block))) if block
1075
+ end
895
1076
  end
896
1077
  end