sequel 3.21.0 → 3.28.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 (158) hide show
  1. data/CHANGELOG +294 -0
  2. data/README.rdoc +20 -6
  3. data/Rakefile +20 -15
  4. data/doc/association_basics.rdoc +210 -43
  5. data/doc/dataset_basics.rdoc +4 -4
  6. data/doc/mass_assignment.rdoc +54 -0
  7. data/doc/migration.rdoc +15 -538
  8. data/doc/model_hooks.rdoc +64 -27
  9. data/doc/opening_databases.rdoc +37 -10
  10. data/doc/prepared_statements.rdoc +16 -10
  11. data/doc/reflection.rdoc +8 -2
  12. data/doc/release_notes/3.22.0.txt +39 -0
  13. data/doc/release_notes/3.23.0.txt +172 -0
  14. data/doc/release_notes/3.24.0.txt +420 -0
  15. data/doc/release_notes/3.25.0.txt +88 -0
  16. data/doc/release_notes/3.26.0.txt +88 -0
  17. data/doc/release_notes/3.27.0.txt +82 -0
  18. data/doc/release_notes/3.28.0.txt +304 -0
  19. data/doc/schema_modification.rdoc +547 -0
  20. data/doc/testing.rdoc +106 -0
  21. data/doc/transactions.rdoc +97 -0
  22. data/doc/virtual_rows.rdoc +2 -2
  23. data/lib/sequel/adapters/ado.rb +12 -1
  24. data/lib/sequel/adapters/amalgalite.rb +4 -0
  25. data/lib/sequel/adapters/db2.rb +95 -58
  26. data/lib/sequel/adapters/do.rb +12 -0
  27. data/lib/sequel/adapters/firebird.rb +25 -203
  28. data/lib/sequel/adapters/ibmdb.rb +440 -0
  29. data/lib/sequel/adapters/informix.rb +4 -19
  30. data/lib/sequel/adapters/jdbc/as400.rb +0 -7
  31. data/lib/sequel/adapters/jdbc/db2.rb +49 -0
  32. data/lib/sequel/adapters/jdbc/firebird.rb +34 -0
  33. data/lib/sequel/adapters/jdbc/h2.rb +16 -5
  34. data/lib/sequel/adapters/jdbc/informix.rb +31 -0
  35. data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
  36. data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
  37. data/lib/sequel/adapters/jdbc/mysql.rb +9 -0
  38. data/lib/sequel/adapters/jdbc/oracle.rb +2 -27
  39. data/lib/sequel/adapters/jdbc/postgresql.rb +6 -0
  40. data/lib/sequel/adapters/jdbc/sqlserver.rb +46 -0
  41. data/lib/sequel/adapters/jdbc/transactions.rb +34 -0
  42. data/lib/sequel/adapters/jdbc.rb +62 -29
  43. data/lib/sequel/adapters/mysql.rb +22 -139
  44. data/lib/sequel/adapters/mysql2.rb +9 -14
  45. data/lib/sequel/adapters/odbc/db2.rb +21 -0
  46. data/lib/sequel/adapters/odbc.rb +15 -3
  47. data/lib/sequel/adapters/oracle.rb +17 -1
  48. data/lib/sequel/adapters/postgres.rb +111 -16
  49. data/lib/sequel/adapters/shared/access.rb +21 -0
  50. data/lib/sequel/adapters/shared/db2.rb +290 -0
  51. data/lib/sequel/adapters/shared/firebird.rb +214 -0
  52. data/lib/sequel/adapters/shared/informix.rb +45 -0
  53. data/lib/sequel/adapters/shared/mssql.rb +85 -47
  54. data/lib/sequel/adapters/shared/mysql.rb +50 -7
  55. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +143 -0
  56. data/lib/sequel/adapters/shared/oracle.rb +0 -4
  57. data/lib/sequel/adapters/shared/postgres.rb +75 -43
  58. data/lib/sequel/adapters/shared/sqlite.rb +56 -8
  59. data/lib/sequel/adapters/sqlite.rb +12 -11
  60. data/lib/sequel/adapters/swift/mysql.rb +9 -0
  61. data/lib/sequel/adapters/tinytds.rb +139 -7
  62. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +55 -0
  63. data/lib/sequel/ast_transformer.rb +190 -0
  64. data/lib/sequel/connection_pool/threaded.rb +3 -2
  65. data/lib/sequel/connection_pool.rb +1 -1
  66. data/lib/sequel/core.rb +6 -5
  67. data/lib/sequel/database/connecting.rb +5 -5
  68. data/lib/sequel/database/dataset.rb +1 -1
  69. data/lib/sequel/database/dataset_defaults.rb +1 -1
  70. data/lib/sequel/database/logging.rb +1 -1
  71. data/lib/sequel/database/misc.rb +38 -17
  72. data/lib/sequel/database/query.rb +50 -19
  73. data/lib/sequel/database/schema_generator.rb +8 -5
  74. data/lib/sequel/database/schema_methods.rb +52 -27
  75. data/lib/sequel/dataset/actions.rb +167 -48
  76. data/lib/sequel/dataset/features.rb +57 -8
  77. data/lib/sequel/dataset/graph.rb +1 -1
  78. data/lib/sequel/dataset/misc.rb +39 -20
  79. data/lib/sequel/dataset/mutation.rb +3 -3
  80. data/lib/sequel/dataset/prepared_statements.rb +29 -14
  81. data/lib/sequel/dataset/query.rb +182 -32
  82. data/lib/sequel/dataset/sql.rb +31 -58
  83. data/lib/sequel/dataset.rb +8 -0
  84. data/lib/sequel/exceptions.rb +4 -0
  85. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  86. data/lib/sequel/extensions/migration.rb +6 -4
  87. data/lib/sequel/extensions/to_dot.rb +95 -83
  88. data/lib/sequel/model/associations.rb +893 -309
  89. data/lib/sequel/model/base.rb +302 -105
  90. data/lib/sequel/model/errors.rb +1 -1
  91. data/lib/sequel/model/exceptions.rb +5 -1
  92. data/lib/sequel/model.rb +13 -7
  93. data/lib/sequel/plugins/association_pks.rb +22 -4
  94. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  95. data/lib/sequel/plugins/identity_map.rb +113 -6
  96. data/lib/sequel/plugins/many_through_many.rb +67 -5
  97. data/lib/sequel/plugins/prepared_statements.rb +140 -0
  98. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  99. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  100. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  101. data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
  102. data/lib/sequel/plugins/sharding.rb +12 -20
  103. data/lib/sequel/plugins/single_table_inheritance.rb +2 -0
  104. data/lib/sequel/plugins/update_primary_key.rb +1 -1
  105. data/lib/sequel/plugins/xml_serializer.rb +3 -3
  106. data/lib/sequel/sql.rb +107 -51
  107. data/lib/sequel/timezones.rb +12 -3
  108. data/lib/sequel/version.rb +1 -1
  109. data/spec/adapters/db2_spec.rb +146 -0
  110. data/spec/adapters/mssql_spec.rb +36 -0
  111. data/spec/adapters/mysql_spec.rb +36 -19
  112. data/spec/adapters/postgres_spec.rb +115 -28
  113. data/spec/adapters/spec_helper.rb +6 -0
  114. data/spec/adapters/sqlite_spec.rb +11 -0
  115. data/spec/core/connection_pool_spec.rb +62 -77
  116. data/spec/core/database_spec.rb +244 -287
  117. data/spec/core/dataset_spec.rb +383 -34
  118. data/spec/core/expression_filters_spec.rb +159 -41
  119. data/spec/core/schema_spec.rb +326 -3
  120. data/spec/core/spec_helper.rb +45 -0
  121. data/spec/extensions/association_pks_spec.rb +38 -0
  122. data/spec/extensions/columns_introspection_spec.rb +91 -0
  123. data/spec/extensions/defaults_setter_spec.rb +64 -0
  124. data/spec/extensions/identity_map_spec.rb +162 -0
  125. data/spec/extensions/many_through_many_spec.rb +195 -20
  126. data/spec/extensions/migration_spec.rb +17 -17
  127. data/spec/extensions/nested_attributes_spec.rb +1 -0
  128. data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
  129. data/spec/extensions/prepared_statements_safe_spec.rb +79 -0
  130. data/spec/extensions/prepared_statements_spec.rb +72 -0
  131. data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
  132. data/spec/extensions/schema_dumper_spec.rb +2 -2
  133. data/spec/extensions/schema_spec.rb +12 -20
  134. data/spec/extensions/serialization_modification_detection_spec.rb +36 -0
  135. data/spec/extensions/single_table_inheritance_spec.rb +11 -0
  136. data/spec/extensions/spec_helper.rb +3 -1
  137. data/spec/extensions/to_dot_spec.rb +3 -5
  138. data/spec/extensions/xml_serializer_spec.rb +16 -4
  139. data/spec/integration/associations_test.rb +405 -15
  140. data/spec/integration/database_test.rb +4 -2
  141. data/spec/integration/dataset_test.rb +240 -20
  142. data/spec/integration/plugin_test.rb +142 -5
  143. data/spec/integration/prepared_statement_test.rb +174 -95
  144. data/spec/integration/schema_test.rb +128 -16
  145. data/spec/integration/spec_helper.rb +15 -0
  146. data/spec/integration/transaction_test.rb +40 -0
  147. data/spec/integration/type_test.rb +16 -2
  148. data/spec/model/association_reflection_spec.rb +91 -0
  149. data/spec/model/associations_spec.rb +476 -5
  150. data/spec/model/base_spec.rb +91 -1
  151. data/spec/model/eager_loading_spec.rb +519 -31
  152. data/spec/model/hooks_spec.rb +161 -0
  153. data/spec/model/model_spec.rb +89 -2
  154. data/spec/model/plugins_spec.rb +17 -0
  155. data/spec/model/record_spec.rb +184 -12
  156. data/spec/model/spec_helper.rb +5 -0
  157. data/spec/model/validations_spec.rb +11 -0
  158. metadata +85 -34
@@ -1,7 +1,7 @@
1
1
  module Sequel
2
2
  class Dataset
3
3
  # ---------------------
4
- # :section: Miscellaneous methods
4
+ # :section: 6 - Miscellaneous methods
5
5
  # These methods don't fit cleanly into another section.
6
6
  # ---------------------
7
7
 
@@ -23,17 +23,13 @@ module Sequel
23
23
  # DB[:posts]
24
24
  #
25
25
  # Sequel::Dataset is an abstract class that is not useful by itself. Each
26
- # database adaptor provides a subclass of Sequel::Dataset, and has
26
+ # database adapter provides a subclass of Sequel::Dataset, and has
27
27
  # the Database#dataset method return an instance of that subclass.
28
28
  def initialize(db, opts = nil)
29
29
  @db = db
30
- @quote_identifiers = db.quote_identifiers? if db.respond_to?(:quote_identifiers?)
31
- @identifier_input_method = db.identifier_input_method if db.respond_to?(:identifier_input_method)
32
- @identifier_output_method = db.identifier_output_method if db.respond_to?(:identifier_output_method)
33
30
  @opts = opts || {}
34
- @row_proc = nil
35
31
  end
36
-
32
+
37
33
  # Define a hash value such that datasets with the same DB, opts, and SQL
38
34
  # will be consider equal.
39
35
  def ==(o)
@@ -45,15 +41,6 @@ module Sequel
45
41
  self == o
46
42
  end
47
43
 
48
- # Return the dataset as an aliased expression with the given alias. You can
49
- # use this as a FROM or JOIN dataset, or as a column if this dataset
50
- # returns a single row and column.
51
- #
52
- # DB.from(DB[:table].as(:b)) # SELECT * FROM (SELECT * FROM table) AS b
53
- def as(aliaz)
54
- ::Sequel::SQL::AliasedExpression.new(self, aliaz)
55
- end
56
-
57
44
  # Yield a dataset for each server in the connection pool that is tied to that server.
58
45
  # Intended for use in sharded environments where all servers need to be modified
59
46
  # with the same data:
@@ -96,10 +83,10 @@ module Sequel
96
83
  # have a table, raises an error. If the table is aliased, returns the original
97
84
  # table, not the alias
98
85
  #
99
- # DB[:table].first_source_alias
86
+ # DB[:table].first_source_table
100
87
  # # => :table
101
88
  #
102
- # DB[:table___t].first_source_alias
89
+ # DB[:table___t].first_source_table
103
90
  # # => :table
104
91
  def first_source_table
105
92
  source = @opts[:from]
@@ -120,7 +107,31 @@ module Sequel
120
107
  # Define a hash value such that datasets with the same DB, opts, and SQL
121
108
  # will have the same hash value
122
109
  def hash
123
- [db, opts.sort_by{|k| k.to_s}, sql].hash
110
+ [db, opts.sort_by{|k, v| k.to_s}, sql].hash
111
+ end
112
+
113
+ # The String instance method to call on identifiers before sending them to
114
+ # the database.
115
+ def identifier_input_method
116
+ if defined?(@identifier_input_method)
117
+ @identifier_input_method
118
+ elsif db.respond_to?(:identifier_input_method)
119
+ @identifier_input_method = db.identifier_input_method
120
+ else
121
+ @identifier_input_method = nil
122
+ end
123
+ end
124
+
125
+ # The String instance method to call on identifiers before sending them to
126
+ # the database.
127
+ def identifier_output_method
128
+ if defined?(@identifier_output_method)
129
+ @identifier_output_method
130
+ elsif db.respond_to?(:identifier_output_method)
131
+ @identifier_output_method = db.identifier_output_method
132
+ else
133
+ @identifier_output_method = nil
134
+ end
124
135
  end
125
136
 
126
137
  # Returns a string representation of the dataset including the class name
@@ -129,7 +140,13 @@ module Sequel
129
140
  "#<#{self.class}: #{sql.inspect}>"
130
141
  end
131
142
 
132
- # Splits a possible implicit alias in C, handling both SQL::AliasedExpressions
143
+ # The alias to use for the row_number column, used when emulating OFFSET
144
+ # support and for eager limit strategies
145
+ def row_number_column
146
+ :x_sequel_row_number_x
147
+ end
148
+
149
+ # Splits a possible implicit alias in +c+, handling both SQL::AliasedExpressions
133
150
  # and Symbols. Returns an array of two elements, with the first being the
134
151
  # main expression, and the second being the alias.
135
152
  def split_alias(c)
@@ -139,6 +156,8 @@ module Sequel
139
156
  [c_table ? SQL::QualifiedIdentifier.new(c_table, column.to_sym) : column.to_sym, aliaz]
140
157
  when SQL::AliasedExpression
141
158
  [c.expression, c.aliaz]
159
+ when SQL::JoinClause
160
+ [c.table, c.table_alias]
142
161
  else
143
162
  [c, nil]
144
163
  end
@@ -1,7 +1,7 @@
1
1
  module Sequel
2
2
  class Dataset
3
3
  # ---------------------
4
- # :section: Mutation methods
4
+ # :section: 7 - Mutation methods
5
5
  # These methods modify the receiving dataset and should be used with care.
6
6
  # ---------------------
7
7
 
@@ -21,10 +21,10 @@ module Sequel
21
21
  def_mutation_method(*MUTATION_METHODS)
22
22
 
23
23
  # Set the method to call on identifiers going into the database for this dataset
24
- attr_accessor :identifier_input_method
24
+ attr_writer :identifier_input_method
25
25
 
26
26
  # Set the method to call on identifiers coming the database for this dataset
27
- attr_accessor :identifier_output_method
27
+ attr_writer :identifier_output_method
28
28
 
29
29
  # Whether to quote identifiers for this dataset
30
30
  attr_writer :quote_identifiers
@@ -1,7 +1,7 @@
1
1
  module Sequel
2
2
  class Dataset
3
3
  # ---------------------
4
- # :section: Methods related to prepared statements or bound variables
4
+ # :section: 8 - Methods related to prepared statements or bound variables
5
5
  # On some adapters, these use native prepared statements and bound variables, on others
6
6
  # support is emulated. For details, see the {"Prepared Statements/Bound Variables" guide}[link:files/doc/prepared_statements_rdoc.html].
7
7
  # ---------------------
@@ -80,7 +80,9 @@ module Sequel
80
80
  when :select, :all
81
81
  select_sql
82
82
  when :first
83
- clone(:limit=>1).select_sql
83
+ limit(1).select_sql
84
+ when :insert_select
85
+ returning.insert_sql(*@prepared_modify_values)
84
86
  when :insert
85
87
  insert_sql(*@prepared_modify_values)
86
88
  when :update
@@ -95,8 +97,8 @@ module Sequel
95
97
  # and they are substituted using prepared_arg.
96
98
  def literal_symbol(v)
97
99
  if @opts[:bind_vars] and match = PLACEHOLDER_RE.match(v.to_s)
98
- v2 = prepared_arg(match[1].to_sym)
99
- v2 ? literal(v2) : v
100
+ s = match[1].to_sym
101
+ prepared_arg?(s) ? literal(prepared_arg(s)) : v
100
102
  else
101
103
  super
102
104
  end
@@ -118,6 +120,9 @@ module Sequel
118
120
  case @prepared_type
119
121
  when :select, :all
120
122
  all(&block)
123
+ when :insert_select
124
+ meta_def(:select_sql){prepared_sql}
125
+ first
121
126
  when :first
122
127
  first
123
128
  when :insert
@@ -136,6 +141,11 @@ module Sequel
136
141
  @opts[:bind_vars][k]
137
142
  end
138
143
 
144
+ # Whether there is a bound value for the given key.
145
+ def prepared_arg?(k)
146
+ @opts[:bind_vars].has_key?(k)
147
+ end
148
+
139
149
  # Use a clone of the dataset extended with prepared statement
140
150
  # support and using the same argument hash so that you can use
141
151
  # bind variables/prepared arguments in subselects.
@@ -171,6 +181,11 @@ module Sequel
171
181
  prepared_args << k
172
182
  prepared_arg_placeholder
173
183
  end
184
+
185
+ # Always assume there is a prepared arg in the argument mapper.
186
+ def prepared_arg?(k)
187
+ true
188
+ end
174
189
  end
175
190
 
176
191
  # Set the bind variables to use for the call. If bind variables have
@@ -184,11 +199,9 @@ module Sequel
184
199
  clone(:bind_vars=>@opts[:bind_vars] ? @opts[:bind_vars].merge(bind_vars) : bind_vars)
185
200
  end
186
201
 
187
- # For the given type (:select, :insert, :update, or :delete),
188
- # run the sql with the bind variables
189
- # specified in the hash. values is a hash of passed to
190
- # insert or update (if one of those types is used),
191
- # which may contain placeholders.
202
+ # For the given type (:select, :first, :insert, :insert_select, :update, or :delete),
203
+ # run the sql with the bind variables specified in the hash. +values+ is a hash passed to
204
+ # insert or update (if one of those types is used), which may contain placeholders.
192
205
  #
193
206
  # DB[:table].filter(:id=>:$id).call(:first, :id=>1)
194
207
  # # SELECT * FROM table WHERE id = ? LIMIT 1 -- (1)
@@ -197,11 +210,13 @@ module Sequel
197
210
  prepare(type, nil, *values).call(bind_variables, &block)
198
211
  end
199
212
 
200
- # Prepare an SQL statement for later execution. This returns
201
- # a clone of the dataset extended with PreparedStatementMethods,
202
- # on which you can call call with the hash of bind variables to
203
- # do substitution. The prepared statement is also stored in
204
- # the associated database. The following usage is identical:
213
+ # Prepare an SQL statement for later execution. Takes a type similar to #call,
214
+ # and the name symbol of the prepared statement.
215
+ # This returns a clone of the dataset extended with PreparedStatementMethods,
216
+ # which you can +call+ with the hash of bind variables to use.
217
+ # The prepared statement is also stored in
218
+ # the associated database, where it can be called by name.
219
+ # The following usage is identical:
205
220
  #
206
221
  # ps = DB[:table].filter(:name=>:$name).prepare(:first, :select_by_name)
207
222
  #
@@ -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,10 +28,10 @@ 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
31
+ QUERY_METHODS = %w'add_graph_aliases and distinct except exclude exclude_having exclude_where
32
32
  filter for_update from from_self graph grep group group_and_count group_by having intersect invert
33
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
34
+ reverse reverse_order select select_all select_append select_group select_more server
35
35
  set_defaults set_graph_aliases set_overrides unfiltered ungraphed ungrouped union
36
36
  unlimited unordered where with with_recursive with_sql'.collect{|x| x.to_sym} + JOIN_METHODS
37
37
 
@@ -80,7 +80,7 @@ module Sequel
80
80
  # :from_self :: Set to false to not wrap the returned dataset in a from_self, use with care.
81
81
  #
82
82
  # DB[:items].except(DB[:other_items])
83
- # # SELECT * FROM items EXCEPT SELECT * FROM other_items
83
+ # # SELECT * FROM (SELECT * FROM items EXCEPT SELECT * FROM other_items) AS t1
84
84
  #
85
85
  # DB[:items].except(DB[:other_items], :all=>true, :from_self=>false)
86
86
  # # SELECT * FROM items EXCEPT ALL SELECT * FROM other_items
@@ -103,12 +103,29 @@ module Sequel
103
103
  # DB[:items].exclude(:category => 'software', :id=>3)
104
104
  # # SELECT * FROM items WHERE ((category != 'software') OR (id != 3))
105
105
  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)
106
+ _filter_or_exclude(true, @opts[:having] ? :having : :where, *cond, &block)
107
+ end
108
+
109
+ # Inverts the given conditions and adds them to the HAVING clause.
110
+ #
111
+ # DB[:items].select_group(:name).exclude_having{count(name) < 2}
112
+ # # SELECT name FROM items GROUP BY name HAVING (count(name) >= 2)
113
+ def exclude_having(*cond, &block)
114
+ _filter_or_exclude(true, :having, *cond, &block)
115
+ end
116
+
117
+ # Inverts the given conditions and adds them to the WHERE clause.
118
+ #
119
+ # DB[:items].select_group(:name).exclude_where(:category => 'software')
120
+ # # SELECT * FROM items WHERE (category != 'software')
121
+ #
122
+ # DB[:items].select_group(:name).
123
+ # exclude_having{count(name) < 2}.
124
+ # exclude_where(:category => 'software')
125
+ # # SELECT name FROM items WHERE (category != 'software')
126
+ # # GROUP BY name HAVING (count(name) >= 2)
127
+ def exclude_where(*cond, &block)
128
+ _filter_or_exclude(true, :where, *cond, &block)
112
129
  end
113
130
 
114
131
  # Returns a copy of the dataset with the given conditions imposed upon it.
@@ -270,21 +287,25 @@ module Sequel
270
287
  end
271
288
 
272
289
  # Returns a copy of the dataset with the results grouped by the value of
273
- # the given columns.
290
+ # the given columns. If a block is given, it is treated
291
+ # as a virtual row block, similar to +filter+.
274
292
  #
275
293
  # DB[:items].group(:id) # SELECT * FROM items GROUP BY id
276
294
  # DB[:items].group(:id, :name) # SELECT * FROM items GROUP BY id, name
277
- def group(*columns)
295
+ # DB[:items].group{[a, sum(b)]} # SELECT * FROM items GROUP BY a, sum(b)
296
+ def group(*columns, &block)
297
+ virtual_row_columns(columns, block)
278
298
  clone(:group => (columns.compact.empty? ? nil : columns))
279
299
  end
280
300
 
281
301
  # Alias of group
282
- def group_by(*columns)
283
- group(*columns)
302
+ def group_by(*columns, &block)
303
+ group(*columns, &block)
284
304
  end
285
305
 
286
306
  # Returns a dataset grouped by the given column with count by group.
287
307
  # Column aliases may be supplied, and will be included in the select clause.
308
+ # If a block is given, it is treated as a virtual row block, similar to +filter+.
288
309
  #
289
310
  # Examples:
290
311
  #
@@ -299,8 +320,12 @@ module Sequel
299
320
  # DB[:items].group_and_count(:first_name___name).all
300
321
  # # SELECT first_name AS name, count(*) AS count FROM items GROUP BY first_name
301
322
  # # => [{: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]))
323
+ #
324
+ # DB[:items].group_and_count{substr(first_name, 1, 1).as(initial)}.all
325
+ # # SELECT substr(first_name, 1, 1) AS initial, count(*) AS count FROM items GROUP BY substr(first_name, 1, 1)
326
+ # # => [{:initial=>'a', :count=>1}, ...]
327
+ def group_and_count(*columns, &block)
328
+ select_group(*columns, &block).select_more(COUNT_OF_ALL_AS_COUNT)
304
329
  end
305
330
 
306
331
  # Returns a copy of the dataset with the HAVING conditions changed. See #filter for argument types.
@@ -356,7 +381,11 @@ module Sequel
356
381
  inner_join(*args, &block)
357
382
  end
358
383
 
359
- # Returns a joined dataset. Uses the following arguments:
384
+ # Returns a joined dataset. Not usually called directly, users should use the
385
+ # appropriate join method (e.g. join, left_join, natural_join, cross_join) which fills
386
+ # in the +type+ argument.
387
+ #
388
+ # Takes the following arguments:
360
389
  #
361
390
  # * type - The type of join to do (e.g. :inner)
362
391
  # * table - Depends on type:
@@ -386,7 +415,29 @@ module Sequel
386
415
  # in which case it yields the table alias/name for the table currently being joined,
387
416
  # the table alias/name for the last joined (or first table), and an array of previous
388
417
  # SQL::JoinClause. Unlike +filter+, this block is not treated as a virtual row block.
418
+ #
419
+ # Examples:
420
+ #
421
+ # DB[:a].join_table(:cross, :b)
422
+ # # SELECT * FROM a CROSS JOIN b
423
+ #
424
+ # DB[:a].join_table(:inner, DB[:b], :c=>d)
425
+ # # SELECT * FROM a INNER JOIN (SELECT * FROM b) AS t1 ON (t1.c = a.d)
426
+ #
427
+ # DB[:a].join_table(:left, :b___c, [:d])
428
+ # # SELECT * FROM a LEFT JOIN b AS c USING (d)
429
+ #
430
+ # DB[:a].natural_join(:b).join_table(:inner, :c) do |ta, jta, js|
431
+ # (:d.qualify(ta) > :e.qualify(jta)) & {:f.qualify(ta)=>DB.from(js.first.table).select(:g)}
432
+ # end
433
+ # # SELECT * FROM a NATURAL JOIN b INNER JOIN c
434
+ # # ON ((c.d > b.e) AND (c.f IN (SELECT g FROM b)))
389
435
  def join_table(type, table, expr=nil, options={}, &block)
436
+ if table.is_a?(Dataset) && table.opts[:with] && !supports_cte_in_subqueries?
437
+ s, ds = hoist_cte(table)
438
+ return s.join_table(type, ds, expr, options, &block)
439
+ end
440
+
390
441
  using_join = expr.is_a?(Array) && !expr.empty? && expr.all?{|x| x.is_a?(Symbol)}
391
442
  if using_join && !supports_join_using?
392
443
  h = {}
@@ -431,6 +482,7 @@ module Sequel
431
482
  v = qualified_column_name(v, last_alias) if v.is_a?(Symbol)
432
483
  [k,v]
433
484
  end
485
+ expr = SQL::BooleanExpression.from_value_pairs(expr)
434
486
  end
435
487
  if block
436
488
  expr2 = yield(table_name, last_alias, @opts[:join] || [])
@@ -531,7 +583,7 @@ module Sequel
531
583
  # DB[:items].order{sum(name).desc} # SELECT * FROM items ORDER BY sum(name) DESC
532
584
  # DB[:items].order(nil) # SELECT * FROM items
533
585
  def order(*columns, &block)
534
- columns += Array(Sequel.virtual_row(&block)) if block
586
+ virtual_row_columns(columns, block)
535
587
  clone(:order => (columns.compact.empty?) ? nil : columns)
536
588
  end
537
589
 
@@ -565,7 +617,7 @@ module Sequel
565
617
  @opts[:order] ? ds.order_more(*@opts[:order]) : ds
566
618
  end
567
619
 
568
- # Qualify to the given table, or first source if not table is given.
620
+ # Qualify to the given table, or first source if no table is given.
569
621
  #
570
622
  # DB[:items].filter(:id=>1).qualify
571
623
  # # SELECT items.* FROM items WHERE (items.id = 1)
@@ -606,6 +658,18 @@ module Sequel
606
658
  qualify_to(first_source)
607
659
  end
608
660
 
661
+ # Modify the RETURNING clause, only supported on a few databases. If returning
662
+ # is used, instead of insert returning the autogenerated primary key or
663
+ # update/delete returning the number of modified rows, results are
664
+ # returned using +fetch_rows+.
665
+ #
666
+ # DB[:items].returning # RETURNING *
667
+ # DB[:items].returning(nil) # RETURNING NULL
668
+ # DB[:items].returning(:id, :name) # RETURNING id, name
669
+ def returning(*values)
670
+ clone(:returning=>values)
671
+ end
672
+
609
673
  # Returns a copy of the dataset with the order reversed. If no order is
610
674
  # given, the existing order is inverted.
611
675
  #
@@ -629,7 +693,7 @@ module Sequel
629
693
  # DB[:items].select(:a, :b) # SELECT a, b FROM items
630
694
  # DB[:items].select{[a, sum(b)]} # SELECT a, sum(b) FROM items
631
695
  def select(*columns, &block)
632
- columns += Array(Sequel.virtual_row(&block)) if block
696
+ virtual_row_columns(columns, block)
633
697
  m = []
634
698
  columns.each do |i|
635
699
  i.is_a?(Hash) ? m.concat(i.map{|k, v| SQL::AliasedExpression.new(k,v)}) : m << i
@@ -637,11 +701,19 @@ module Sequel
637
701
  clone(:select => m)
638
702
  end
639
703
 
640
- # Returns a copy of the dataset selecting the wildcard.
704
+ # Returns a copy of the dataset selecting the wildcard if no arguments
705
+ # are given. If arguments are given, treat them as tables and select
706
+ # all columns (using the wildcard) from each table.
641
707
  #
642
708
  # DB[:items].select(:a).select_all # SELECT * FROM items
643
- def select_all
644
- clone(:select => nil)
709
+ # DB[:items].select_all(:items) # SELECT items.* FROM items
710
+ # DB[:items].select_all(:items, :foo) # SELECT items.*, foo.* FROM items
711
+ def select_all(*tables)
712
+ if tables.empty?
713
+ clone(:select => nil)
714
+ else
715
+ select(*tables.map{|t| i, a = split_alias(t); a || i}.map{|t| SQL::ColumnAll.new(t)})
716
+ end
645
717
  end
646
718
 
647
719
  # Returns a copy of the dataset with the given columns added
@@ -653,10 +725,29 @@ module Sequel
653
725
  # DB[:items].select_append(:b) # SELECT *, b FROM items
654
726
  def select_append(*columns, &block)
655
727
  cur_sel = @opts[:select]
656
- cur_sel = [WILDCARD] if !cur_sel || cur_sel.empty?
728
+ if !cur_sel || cur_sel.empty?
729
+ unless supports_select_all_and_column?
730
+ return select_all(*(Array(@opts[:from]) + Array(@opts[:join]))).select_more(*columns, &block)
731
+ end
732
+ cur_sel = [WILDCARD]
733
+ end
657
734
  select(*(cur_sel + columns), &block)
658
735
  end
659
736
 
737
+ # Set both the select and group clauses with the given +columns+.
738
+ # Column aliases may be supplied, and will be included in the select clause.
739
+ # This also takes a virtual row block similar to +filter+.
740
+ #
741
+ # DB[:items].select_group(:a, :b)
742
+ # # SELECT a, b FROM items GROUP BY a, b
743
+ #
744
+ # DB[:items].select_group(:c___a){f(c2)}
745
+ # # SELECT c AS a, f(c2) FROM items GROUP BY c, f(c2)
746
+ def select_group(*columns, &block)
747
+ virtual_row_columns(columns, block)
748
+ select(*columns).group(*columns.map{|c| unaliased_identifier(c)})
749
+ end
750
+
660
751
  # Returns a copy of the dataset with the given columns added
661
752
  # to the existing selected columns. If no columns are currently selected
662
753
  # it will just select the columns given.
@@ -670,7 +761,7 @@ module Sequel
670
761
  end
671
762
 
672
763
  # 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
764
+ # shard to run a query against, or to override the default (where SELECT uses
674
765
  # :read_only database and all other queries use the :default database). This
675
766
  # method is always available but is only useful when database sharding is being
676
767
  # used.
@@ -702,6 +793,22 @@ module Sequel
702
793
  clone(:overrides=>hash.merge(@opts[:overrides]||{}))
703
794
  end
704
795
 
796
+ # Unbind bound variables from this dataset's filter and return an array of two
797
+ # objects. The first object is a modified dataset where the filter has been
798
+ # replaced with one that uses bound variable placeholders. The second object
799
+ # is the hash of unbound variables. You can then prepare and execute (or just
800
+ # call) the dataset with the bound variables to get results.
801
+ #
802
+ # ds, bv = DB[:items].filter(:a=>1).unbind
803
+ # ds # SELECT * FROM items WHERE (a = $a)
804
+ # bv # {:a => 1}
805
+ # ds.call(:select, bv)
806
+ def unbind
807
+ u = Unbinder.new
808
+ ds = clone(:where=>u.transform(opts[:where]), :join=>u.transform(opts[:join]))
809
+ [ds, u.binds]
810
+ end
811
+
705
812
  # Returns a copy of the dataset with no filters (HAVING or WHERE clause) applied.
706
813
  #
707
814
  # DB[:items].group(:a).having(:a=>1).where(:b).unfiltered
@@ -726,8 +833,8 @@ module Sequel
726
833
  # :all :: Set to true to use UNION ALL instead of UNION, so duplicate rows can occur
727
834
  # :from_self :: Set to false to not wrap the returned dataset in a from_self, use with care.
728
835
  #
729
- # DB[:items].union(DB[:other_items]).sql
730
- # #=> "SELECT * FROM items UNION SELECT * FROM other_items"
836
+ # DB[:items].union(DB[:other_items])
837
+ # # SELECT * FROM (SELECT * FROM items UNION SELECT * FROM other_items) AS t1
731
838
  #
732
839
  # DB[:items].union(DB[:other_items], :all=>true, :from_self=>false)
733
840
  # # SELECT * FROM items UNION ALL SELECT * FROM other_items
@@ -803,8 +910,20 @@ module Sequel
803
910
  # to keep the same row_proc/graph, but change the SQL used to custom SQL.
804
911
  #
805
912
  # DB[:items].with_sql('SELECT * FROM foo') # SELECT * FROM foo
913
+ #
914
+ # You can use placeholders in your SQL and provide arguments for those placeholders:
915
+ #
916
+ # DB[:items].with_sql('SELECT ? FROM foo', 1) # SELECT 1 FROM foo
917
+ #
918
+ # You can also provide a method name and arguments to call to get the SQL:
919
+ #
920
+ # DB[:items].with_sql(:insert_sql, :b=>1) # INSERT INTO items (b) VALUES (1)
806
921
  def with_sql(sql, *args)
807
- sql = SQL::PlaceholderLiteralString.new(sql, args) unless args.empty?
922
+ if sql.is_a?(Symbol)
923
+ sql = send(sql, *args)
924
+ else
925
+ sql = SQL::PlaceholderLiteralString.new(sql, args) unless args.empty?
926
+ end
808
927
  clone(:sql=>sql)
809
928
  end
810
929
 
@@ -823,18 +942,24 @@ module Sequel
823
942
 
824
943
  private
825
944
 
826
- # Internal filter method so it works on either the having or where clauses.
827
- def _filter(clause, *cond, &block)
945
+ # Internal filter/exclude method so it works on either the having or where clauses.
946
+ def _filter_or_exclude(invert, clause, *cond, &block)
828
947
  cond = cond.first if cond.size == 1
829
948
  if cond.respond_to?(:empty?) && cond.empty? && !block
830
949
  clone
831
950
  else
832
951
  cond = filter_expr(cond, &block)
952
+ cond = SQL::BooleanExpression.invert(cond) if invert
833
953
  cond = SQL::BooleanExpression.new(:AND, @opts[clause], cond) if @opts[clause]
834
954
  clone(clause => cond)
835
955
  end
836
956
  end
837
-
957
+
958
+ # Internal filter method so it works on either the having or where clauses.
959
+ def _filter(clause, *cond, &block)
960
+ _filter_or_exclude(false, clause, *cond, &block)
961
+ end
962
+
838
963
  # Add the dataset to the list of compounds
839
964
  def compound_clone(type, dataset, opts)
840
965
  ds = compound_from_self.clone(:compounds=>Array(@opts[:compounds]).map{|x| x.dup} + [[type, dataset.compound_from_self, opts[:all]]])
@@ -867,7 +992,13 @@ module Sequel
867
992
  when Symbol, SQL::Expression
868
993
  expr
869
994
  when TrueClass, FalseClass
870
- SQL::BooleanExpression.new(:NOOP, expr)
995
+ if supports_where_true?
996
+ SQL::BooleanExpression.new(:NOOP, expr)
997
+ elsif expr
998
+ SQL::Constants::SQLTRUE
999
+ else
1000
+ SQL::Constants::SQLFALSE
1001
+ end
871
1002
  when String
872
1003
  LiteralString.new("(#{expr})")
873
1004
  else
@@ -875,6 +1006,13 @@ module Sequel
875
1006
  end
876
1007
  end
877
1008
 
1009
+ # Return two datasets, the first a clone of the receiver with the WITH
1010
+ # clause from the given dataset added to it, and the second a clone of
1011
+ # the given dataset with the WITH clause removed.
1012
+ def hoist_cte(ds)
1013
+ [clone(:with => (opts[:with] || []) + ds.opts[:with]), ds.clone(:with => nil)]
1014
+ end
1015
+
878
1016
  # Inverts the given order by breaking it into a list of column references
879
1017
  # and inverting them.
880
1018
  #
@@ -892,5 +1030,17 @@ module Sequel
892
1030
  end
893
1031
  end
894
1032
  end
1033
+
1034
+ # Return self if the dataset already has a server, or a cloned dataset with the
1035
+ # default server otherwise.
1036
+ def default_server
1037
+ @opts[:server] ? self : clone(:server=>:default)
1038
+ end
1039
+
1040
+ # Treat the +block+ as a virtual_row block if not +nil+ and
1041
+ # add the resulting columns to the +columns+ array (modifies +columns+).
1042
+ def virtual_row_columns(columns, block)
1043
+ columns.concat(Array(Sequel.virtual_row(&block))) if block
1044
+ end
895
1045
  end
896
1046
  end