sequel 3.21.0 → 3.31.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 (225) hide show
  1. data/CHANGELOG +485 -3
  2. data/README.rdoc +20 -6
  3. data/Rakefile +37 -19
  4. data/bin/sequel +1 -5
  5. data/doc/association_basics.rdoc +249 -43
  6. data/doc/dataset_basics.rdoc +4 -4
  7. data/doc/dataset_filtering.rdoc +8 -0
  8. data/doc/mass_assignment.rdoc +54 -0
  9. data/doc/migration.rdoc +15 -538
  10. data/doc/model_hooks.rdoc +72 -27
  11. data/doc/opening_databases.rdoc +89 -50
  12. data/doc/prepared_statements.rdoc +40 -13
  13. data/doc/reflection.rdoc +8 -2
  14. data/doc/release_notes/3.22.0.txt +39 -0
  15. data/doc/release_notes/3.23.0.txt +172 -0
  16. data/doc/release_notes/3.24.0.txt +420 -0
  17. data/doc/release_notes/3.25.0.txt +88 -0
  18. data/doc/release_notes/3.26.0.txt +88 -0
  19. data/doc/release_notes/3.27.0.txt +82 -0
  20. data/doc/release_notes/3.28.0.txt +304 -0
  21. data/doc/release_notes/3.29.0.txt +459 -0
  22. data/doc/release_notes/3.30.0.txt +135 -0
  23. data/doc/release_notes/3.31.0.txt +146 -0
  24. data/doc/schema_modification.rdoc +547 -0
  25. data/doc/sharding.rdoc +7 -1
  26. data/doc/testing.rdoc +129 -0
  27. data/doc/transactions.rdoc +137 -0
  28. data/doc/virtual_rows.rdoc +2 -2
  29. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  30. data/lib/sequel/adapters/ado.rb +40 -18
  31. data/lib/sequel/adapters/amalgalite.rb +17 -9
  32. data/lib/sequel/adapters/db2.rb +175 -86
  33. data/lib/sequel/adapters/dbi.rb +15 -15
  34. data/lib/sequel/adapters/do/mysql.rb +5 -7
  35. data/lib/sequel/adapters/do/postgres.rb +0 -5
  36. data/lib/sequel/adapters/do/sqlite.rb +0 -5
  37. data/lib/sequel/adapters/do.rb +17 -36
  38. data/lib/sequel/adapters/firebird.rb +27 -208
  39. data/lib/sequel/adapters/ibmdb.rb +448 -0
  40. data/lib/sequel/adapters/informix.rb +6 -23
  41. data/lib/sequel/adapters/jdbc/as400.rb +16 -34
  42. data/lib/sequel/adapters/jdbc/db2.rb +45 -0
  43. data/lib/sequel/adapters/jdbc/derby.rb +247 -0
  44. data/lib/sequel/adapters/jdbc/firebird.rb +29 -0
  45. data/lib/sequel/adapters/jdbc/h2.rb +56 -29
  46. data/lib/sequel/adapters/jdbc/hsqldb.rb +170 -0
  47. data/lib/sequel/adapters/jdbc/informix.rb +26 -0
  48. data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
  49. data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
  50. data/lib/sequel/adapters/jdbc/mysql.rb +11 -11
  51. data/lib/sequel/adapters/jdbc/oracle.rb +81 -25
  52. data/lib/sequel/adapters/jdbc/postgresql.rb +9 -13
  53. data/lib/sequel/adapters/jdbc/sqlite.rb +2 -6
  54. data/lib/sequel/adapters/jdbc/sqlserver.rb +71 -0
  55. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  56. data/lib/sequel/adapters/jdbc.rb +231 -78
  57. data/lib/sequel/adapters/mock.rb +335 -0
  58. data/lib/sequel/adapters/mysql.rb +82 -184
  59. data/lib/sequel/adapters/mysql2.rb +26 -25
  60. data/lib/sequel/adapters/odbc/db2.rb +17 -0
  61. data/lib/sequel/adapters/odbc/mssql.rb +1 -6
  62. data/lib/sequel/adapters/odbc.rb +28 -9
  63. data/lib/sequel/adapters/openbase.rb +3 -5
  64. data/lib/sequel/adapters/oracle.rb +351 -53
  65. data/lib/sequel/adapters/postgres.rb +194 -33
  66. data/lib/sequel/adapters/shared/access.rb +32 -9
  67. data/lib/sequel/adapters/shared/db2.rb +321 -0
  68. data/lib/sequel/adapters/shared/firebird.rb +221 -0
  69. data/lib/sequel/adapters/shared/informix.rb +53 -0
  70. data/lib/sequel/adapters/shared/mssql.rb +231 -106
  71. data/lib/sequel/adapters/shared/mysql.rb +181 -63
  72. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +149 -0
  73. data/lib/sequel/adapters/shared/oracle.rb +231 -41
  74. data/lib/sequel/adapters/shared/postgres.rb +214 -82
  75. data/lib/sequel/adapters/shared/progress.rb +4 -7
  76. data/lib/sequel/adapters/shared/sqlite.rb +201 -57
  77. data/lib/sequel/adapters/sqlite.rb +29 -21
  78. data/lib/sequel/adapters/swift/mysql.rb +12 -7
  79. data/lib/sequel/adapters/swift/postgres.rb +0 -5
  80. data/lib/sequel/adapters/swift/sqlite.rb +8 -6
  81. data/lib/sequel/adapters/swift.rb +5 -5
  82. data/lib/sequel/adapters/tinytds.rb +160 -19
  83. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +66 -0
  84. data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
  85. data/lib/sequel/ast_transformer.rb +190 -0
  86. data/lib/sequel/connection_pool/threaded.rb +3 -2
  87. data/lib/sequel/connection_pool.rb +1 -1
  88. data/lib/sequel/core.rb +46 -5
  89. data/lib/sequel/database/connecting.rb +5 -6
  90. data/lib/sequel/database/dataset.rb +4 -4
  91. data/lib/sequel/database/dataset_defaults.rb +59 -1
  92. data/lib/sequel/database/logging.rb +1 -1
  93. data/lib/sequel/database/misc.rb +104 -19
  94. data/lib/sequel/database/query.rb +153 -58
  95. data/lib/sequel/database/schema_generator.rb +17 -7
  96. data/lib/sequel/database/schema_methods.rb +59 -29
  97. data/lib/sequel/dataset/actions.rb +231 -76
  98. data/lib/sequel/dataset/features.rb +91 -8
  99. data/lib/sequel/dataset/graph.rb +38 -11
  100. data/lib/sequel/dataset/misc.rb +50 -23
  101. data/lib/sequel/dataset/mutation.rb +5 -6
  102. data/lib/sequel/dataset/prepared_statements.rb +48 -27
  103. data/lib/sequel/dataset/query.rb +236 -43
  104. data/lib/sequel/dataset/sql.rb +665 -253
  105. data/lib/sequel/dataset.rb +8 -0
  106. data/lib/sequel/exceptions.rb +4 -0
  107. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  108. data/lib/sequel/extensions/migration.rb +6 -4
  109. data/lib/sequel/extensions/named_timezones.rb +5 -0
  110. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  111. data/lib/sequel/extensions/to_dot.rb +95 -83
  112. data/lib/sequel/model/associations.rb +1020 -337
  113. data/lib/sequel/model/base.rb +414 -132
  114. data/lib/sequel/model/errors.rb +1 -1
  115. data/lib/sequel/model/exceptions.rb +5 -1
  116. data/lib/sequel/model.rb +15 -8
  117. data/lib/sequel/plugins/association_pks.rb +22 -4
  118. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  119. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  120. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  121. data/lib/sequel/plugins/force_encoding.rb +6 -6
  122. data/lib/sequel/plugins/identity_map.rb +114 -7
  123. data/lib/sequel/plugins/many_through_many.rb +65 -7
  124. data/lib/sequel/plugins/prepared_statements.rb +151 -0
  125. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  126. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  127. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  128. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  129. data/lib/sequel/plugins/serialization.rb +88 -44
  130. data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
  131. data/lib/sequel/plugins/sharding.rb +21 -54
  132. data/lib/sequel/plugins/single_table_inheritance.rb +3 -1
  133. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  134. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  135. data/lib/sequel/plugins/xml_serializer.rb +3 -3
  136. data/lib/sequel/sql.rb +109 -52
  137. data/lib/sequel/timezones.rb +57 -38
  138. data/lib/sequel/version.rb +1 -1
  139. data/spec/adapters/db2_spec.rb +146 -0
  140. data/spec/adapters/mssql_spec.rb +111 -29
  141. data/spec/adapters/mysql_spec.rb +135 -123
  142. data/spec/adapters/oracle_spec.rb +49 -77
  143. data/spec/adapters/postgres_spec.rb +267 -78
  144. data/spec/adapters/spec_helper.rb +6 -5
  145. data/spec/adapters/sqlite_spec.rb +35 -21
  146. data/spec/core/connection_pool_spec.rb +71 -92
  147. data/spec/core/core_sql_spec.rb +21 -30
  148. data/spec/core/database_spec.rb +788 -512
  149. data/spec/core/dataset_spec.rb +1073 -1053
  150. data/spec/core/expression_filters_spec.rb +159 -42
  151. data/spec/core/mock_adapter_spec.rb +419 -0
  152. data/spec/core/object_graph_spec.rb +86 -114
  153. data/spec/core/schema_generator_spec.rb +3 -3
  154. data/spec/core/schema_spec.rb +298 -38
  155. data/spec/core/spec_helper.rb +6 -48
  156. data/spec/extensions/association_autoreloading_spec.rb +1 -10
  157. data/spec/extensions/association_dependencies_spec.rb +2 -12
  158. data/spec/extensions/association_pks_spec.rb +66 -32
  159. data/spec/extensions/caching_spec.rb +23 -50
  160. data/spec/extensions/class_table_inheritance_spec.rb +31 -83
  161. data/spec/extensions/columns_introspection_spec.rb +91 -0
  162. data/spec/extensions/composition_spec.rb +18 -13
  163. data/spec/extensions/dataset_associations_spec.rb +199 -0
  164. data/spec/extensions/defaults_setter_spec.rb +64 -0
  165. data/spec/extensions/hook_class_methods_spec.rb +65 -91
  166. data/spec/extensions/identity_map_spec.rb +114 -22
  167. data/spec/extensions/instance_filters_spec.rb +11 -21
  168. data/spec/extensions/instance_hooks_spec.rb +53 -0
  169. data/spec/extensions/json_serializer_spec.rb +4 -5
  170. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  171. data/spec/extensions/list_spec.rb +17 -39
  172. data/spec/extensions/many_through_many_spec.rb +283 -250
  173. data/spec/extensions/migration_spec.rb +35 -32
  174. data/spec/extensions/named_timezones_spec.rb +23 -3
  175. data/spec/extensions/nested_attributes_spec.rb +97 -88
  176. data/spec/extensions/optimistic_locking_spec.rb +9 -20
  177. data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
  178. data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
  179. data/spec/extensions/prepared_statements_spec.rb +53 -0
  180. data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
  181. data/spec/extensions/pretty_table_spec.rb +1 -6
  182. data/spec/extensions/rcte_tree_spec.rb +41 -43
  183. data/spec/extensions/schema_dumper_spec.rb +5 -8
  184. data/spec/extensions/schema_spec.rb +13 -21
  185. data/spec/extensions/serialization_modification_detection_spec.rb +37 -0
  186. data/spec/extensions/serialization_spec.rb +72 -43
  187. data/spec/extensions/sharding_spec.rb +66 -140
  188. data/spec/extensions/single_table_inheritance_spec.rb +18 -29
  189. data/spec/extensions/spec_helper.rb +13 -61
  190. data/spec/extensions/sql_expr_spec.rb +20 -60
  191. data/spec/extensions/tactical_eager_loading_spec.rb +9 -19
  192. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  193. data/spec/extensions/timestamps_spec.rb +6 -6
  194. data/spec/extensions/to_dot_spec.rb +4 -7
  195. data/spec/extensions/touch_spec.rb +13 -14
  196. data/spec/extensions/tree_spec.rb +11 -26
  197. data/spec/extensions/typecast_on_load_spec.rb +1 -6
  198. data/spec/extensions/update_primary_key_spec.rb +30 -24
  199. data/spec/extensions/validation_class_methods_spec.rb +30 -51
  200. data/spec/extensions/validation_helpers_spec.rb +16 -35
  201. data/spec/extensions/xml_serializer_spec.rb +16 -4
  202. data/spec/integration/associations_test.rb +537 -21
  203. data/spec/integration/database_test.rb +4 -2
  204. data/spec/integration/dataset_test.rb +461 -65
  205. data/spec/integration/eager_loader_test.rb +19 -21
  206. data/spec/integration/model_test.rb +80 -1
  207. data/spec/integration/plugin_test.rb +304 -116
  208. data/spec/integration/prepared_statement_test.rb +212 -122
  209. data/spec/integration/schema_test.rb +161 -30
  210. data/spec/integration/spec_helper.rb +39 -30
  211. data/spec/integration/timezone_test.rb +38 -12
  212. data/spec/integration/transaction_test.rb +172 -5
  213. data/spec/integration/type_test.rb +17 -3
  214. data/spec/model/association_reflection_spec.rb +201 -7
  215. data/spec/model/associations_spec.rb +865 -662
  216. data/spec/model/base_spec.rb +275 -95
  217. data/spec/model/dataset_methods_spec.rb +7 -27
  218. data/spec/model/eager_loading_spec.rb +677 -676
  219. data/spec/model/hooks_spec.rb +309 -67
  220. data/spec/model/model_spec.rb +222 -168
  221. data/spec/model/plugins_spec.rb +24 -13
  222. data/spec/model/record_spec.rb +381 -218
  223. data/spec/model/spec_helper.rb +13 -71
  224. data/spec/model/validations_spec.rb +11 -0
  225. metadata +115 -73
@@ -1,22 +1,27 @@
1
1
  module Sequel
2
2
  class Dataset
3
3
  # ---------------------
4
- # :section: Methods that execute code on the database
4
+ # :section: 2 - Methods that execute code on the database
5
5
  # These methods all execute the dataset's SQL on the database.
6
6
  # They don't return modified datasets, so if used in a method chain
7
7
  # they should be the last method called.
8
8
  # ---------------------
9
9
 
10
10
  # Action methods defined by Sequel that execute code on the database.
11
- ACTION_METHODS = %w'<< [] []= all avg count columns columns! delete each
12
- empty? fetch_rows first get import insert insert_multiple interval last
13
- map max min multi_insert range select_hash select_map select_order_map
14
- set single_record single_value sum to_csv to_hash truncate update'.map{|x| x.to_sym}
11
+ ACTION_METHODS = (<<-METHS).split.map{|x| x.to_sym}
12
+ << [] []= all avg count columns columns! delete each
13
+ empty? fetch_rows first get import insert insert_multiple interval last
14
+ map max min multi_insert range select_hash select_map select_order_map
15
+ set single_record single_value sum to_csv to_hash truncate update
16
+ METHS
15
17
 
16
- # Alias for insert, but not aliased directly so subclasses
17
- # don't have to override both methods.
18
- def <<(*args)
19
- insert(*args)
18
+ # Inserts the given argument into the database. Returns self so it
19
+ # can be used safely when chaining:
20
+ #
21
+ # DB[:items] << {:id=>0, :name=>'Zero'} << DB[:old_items].select(:id, name)
22
+ def <<(arg)
23
+ insert(arg)
24
+ self
20
25
  end
21
26
 
22
27
  # Returns the first record matching the conditions. Examples:
@@ -72,7 +77,7 @@ module Sequel
72
77
  # # => [:id, :name]
73
78
  def columns
74
79
  return @columns if @columns
75
- ds = unfiltered.unordered.clone(:distinct => nil, :limit => 1)
80
+ ds = unfiltered.unordered.clone(:distinct => nil, :limit => 1, :offset=>nil)
76
81
  ds.each{break}
77
82
  @columns = ds.instance_variable_get(:@columns)
78
83
  @columns || []
@@ -101,10 +106,15 @@ module Sequel
101
106
  #
102
107
  # DB[:table].delete # DELETE * FROM table
103
108
  # # => 3
104
- def delete
105
- execute_dui(delete_sql)
109
+ def delete(&block)
110
+ sql = delete_sql
111
+ if uses_returning?(:delete)
112
+ returning_fetch_rows(sql, &block)
113
+ else
114
+ execute_dui(sql)
115
+ end
106
116
  end
107
-
117
+
108
118
  # Iterates over the records in the dataset as they are yielded from the
109
119
  # database adapter, and returns self.
110
120
  #
@@ -127,10 +137,10 @@ module Sequel
127
137
 
128
138
  # Returns true if no records exist in the dataset, false otherwise
129
139
  #
130
- # DB[:table].empty? # SELECT 1 FROM table LIMIT 1
140
+ # DB[:table].empty? # SELECT 1 AS one FROM table LIMIT 1
131
141
  # # => false
132
142
  def empty?
133
- get(1).nil?
143
+ get(Sequel::SQL::AliasedExpression.new(1, :one)).nil?
134
144
  end
135
145
 
136
146
  # Executes a select query and fetches records, yielding each record to the
@@ -219,43 +229,47 @@ module Sequel
219
229
  # DB[:table].import([:x, :y], DB[:table2].select(:a, :b))
220
230
  # # INSERT INTO table (x, y) SELECT a, b FROM table2
221
231
  #
222
- # The method also accepts a :slice or :commit_every option that specifies
223
- # the number of records to insert per transaction. This is useful especially
224
- # when inserting a large number of records, e.g.:
225
- #
226
- # # this will commit every 50 records
227
- # dataset.import([:x, :y], [[1, 2], [3, 4], ...], :slice => 50)
232
+ # Options:
233
+ # :commit_every :: Open a new transaction for every given number of records.
234
+ # For example, if you provide a value of 50, will commit
235
+ # after every 50 records.
236
+ # :server :: Set the server/shard to use for the transaction and insert
237
+ # queries.
238
+ # :slice :: Same as :commit_every, :commit_every takes precedence.
228
239
  def import(columns, values, opts={})
229
240
  return @db.transaction{insert(columns, values)} if values.is_a?(Dataset)
230
241
 
231
242
  return if values.empty?
232
243
  raise(Error, IMPORT_ERROR_MSG) if columns.empty?
244
+ ds = opts[:server] ? server(opts[:server]) : self
233
245
 
234
246
  if slice_size = opts[:commit_every] || opts[:slice]
235
247
  offset = 0
236
- loop do
237
- @db.transaction(opts){multi_insert_sql(columns, values[offset, slice_size]).each{|st| execute_dui(st)}}
248
+ rows = []
249
+ while offset < values.length
250
+ rows << ds._import(columns, values[offset, slice_size], opts)
238
251
  offset += slice_size
239
- break if offset >= values.length
240
252
  end
253
+ rows.flatten
241
254
  else
242
- statements = multi_insert_sql(columns, values)
243
- @db.transaction{statements.each{|st| execute_dui(st)}}
255
+ ds._import(columns, values, opts)
244
256
  end
245
257
  end
246
-
258
+
247
259
  # Inserts values into the associated table. The returned value is generally
248
260
  # the value of the primary key for the inserted row, but that is adapter dependent.
249
261
  #
250
262
  # +insert+ handles a number of different argument formats:
251
- # * No arguments, single empty hash - Uses DEFAULT VALUES
252
- # * Single hash - Most common format, treats keys as columns an values as values
253
- # * Single array - Treats entries as values, with no columns
254
- # * Two arrays - Treats first array as columns, second array as values
255
- # * Single Dataset - Treats as an insert based on a selection from the dataset given,
256
- # with no columns
257
- # * Array and dataset - Treats as an insert based on a selection from the dataset
258
- # given, with the columns given by the array.
263
+ # no arguments or single empty hash :: Uses DEFAULT VALUES
264
+ # single hash :: Most common format, treats keys as columns an values as values
265
+ # single array :: Treats entries as values, with no columns
266
+ # two arrays :: Treats first array as columns, second array as values
267
+ # single Dataset :: Treats as an insert based on a selection from the dataset given,
268
+ # with no columns
269
+ # array and dataset :: Treats as an insert based on a selection from the dataset
270
+ # given, with the columns given by the array.
271
+ #
272
+ # Examples:
259
273
  #
260
274
  # DB[:items].insert
261
275
  # # INSERT INTO items DEFAULT VALUES
@@ -277,27 +291,35 @@ module Sequel
277
291
  #
278
292
  # DB[:items].insert([:a, :b], DB[:old_items])
279
293
  # # INSERT INTO items (a, b) SELECT * FROM old_items
280
- def insert(*values)
281
- execute_insert(insert_sql(*values))
294
+ def insert(*values, &block)
295
+ sql = insert_sql(*values)
296
+ if uses_returning?(:insert)
297
+ returning_fetch_rows(sql, &block)
298
+ else
299
+ execute_insert(sql)
300
+ end
282
301
  end
283
302
 
284
303
  # Inserts multiple values. If a block is given it is invoked for each
285
304
  # item in the given array before inserting it. See +multi_insert+ as
286
- # a possible faster version that inserts multiple records in one
287
- # SQL statement.
305
+ # a possibly faster version that may be able to insert multiple
306
+ # records in one SQL statement (if supported by the database).
307
+ # Returns an array of primary keys of inserted rows.
288
308
  #
289
309
  # DB[:table].insert_multiple([{:x=>1}, {:x=>2}])
310
+ # # => [4, 5]
290
311
  # # INSERT INTO table (x) VALUES (1)
291
312
  # # INSERT INTO table (x) VALUES (2)
292
313
  #
293
314
  # DB[:table].insert_multiple([{:x=>1}, {:x=>2}]){|row| row[:y] = row[:x] * 2}
315
+ # # => [6, 7]
294
316
  # # INSERT INTO table (x, y) VALUES (1, 2)
295
317
  # # INSERT INTO table (x, y) VALUES (2, 4)
296
318
  def insert_multiple(array, &block)
297
319
  if block
298
- array.each {|i| insert(block[i])}
320
+ array.map{|i| insert(block.call(i))}
299
321
  else
300
- array.each {|i| insert(i)}
322
+ array.map{|i| insert(i)}
301
323
  end
302
324
  end
303
325
 
@@ -310,7 +332,7 @@ module Sequel
310
332
  aggregate_dataset.get{max(column) - min(column)}
311
333
  end
312
334
 
313
- # Reverses the order and then runs first. Note that this
335
+ # Reverses the order and then runs #first with the given arguments and block. Note that this
314
336
  # will not necessarily give you the last record in the dataset,
315
337
  # unless you have an unambiguous order. If there is not
316
338
  # currently an order for this dataset, raises an +Error+.
@@ -334,10 +356,20 @@ module Sequel
334
356
  #
335
357
  # DB[:table].map{|r| r[:id] * 2} # SELECT * FROM table
336
358
  # # => [2, 4, 6, ...]
359
+ #
360
+ # You can also provide an array of column names:
361
+ #
362
+ # DB[:table].map([:id, :name]) # SELECT * FROM table
363
+ # # => [[1, 'A'], [2, 'B'], [3, 'C'], ...]
337
364
  def map(column=nil, &block)
338
365
  if column
339
366
  raise(Error, ARG_BLOCK_ERROR_MSG) if block
340
- super(){|r| r[column]}
367
+ return naked.map(column) if row_proc
368
+ if column.is_a?(Array)
369
+ super(){|r| r.values_at(*column)}
370
+ else
371
+ super(){|r| r[column]}
372
+ end
341
373
  else
342
374
  super(&block)
343
375
  end
@@ -370,7 +402,7 @@ module Sequel
370
402
  # otherwise some columns could be missed or set to null instead of to default
371
403
  # values.
372
404
  #
373
- # You can also use the :slice or :commit_every option that import accepts.
405
+ # This respects the same options as #import.
374
406
  def multi_insert(hashes, opts={})
375
407
  return if hashes.empty?
376
408
  columns = hashes.first.keys
@@ -393,49 +425,63 @@ module Sequel
393
425
  #
394
426
  # DB[:table].select_hash(:id, :name) # SELECT id, name FROM table
395
427
  # # => {1=>'a', 2=>'b', ...}
428
+ #
429
+ # You can also provide an array of column names for either the key_column,
430
+ # the value column, or both:
431
+ #
432
+ # DB[:table].select_hash([:id, :foo], [:name, :bar]) # SELECT * FROM table
433
+ # # {[1, 3]=>['a', 'c'], [2, 4]=>['b', 'd'], ...}
396
434
  def select_hash(key_column, value_column)
397
- select(key_column, value_column).to_hash(hash_key_symbol(key_column), hash_key_symbol(value_column))
435
+ if key_column.is_a?(Array)
436
+ if value_column.is_a?(Array)
437
+ select(*(key_column + value_column)).to_hash(key_column.map{|c| hash_key_symbol(c)}, value_column.map{|c| hash_key_symbol(c)})
438
+ else
439
+ select(*(key_column + [value_column])).to_hash(key_column.map{|c| hash_key_symbol(c)}, hash_key_symbol(value_column))
440
+ end
441
+ elsif value_column.is_a?(Array)
442
+ select(key_column, *value_column).to_hash(hash_key_symbol(key_column), value_column.map{|c| hash_key_symbol(c)})
443
+ else
444
+ select(key_column, value_column).to_hash(hash_key_symbol(key_column), hash_key_symbol(value_column))
445
+ end
398
446
  end
399
447
 
400
448
  # Selects the column given (either as an argument or as a block), and
401
449
  # returns an array of all values of that column in the dataset. If you
402
450
  # give a block argument that returns an array with multiple entries,
403
- # the contents of the resulting array are undefined.
451
+ # the contents of the resulting array are undefined. Raises an Error
452
+ # if called with both an argument and a block.
404
453
  #
405
454
  # DB[:table].select_map(:id) # SELECT id FROM table
406
455
  # # => [3, 5, 8, 1, ...]
407
456
  #
408
- # DB[:table].select_map{abs(id)} # SELECT abs(id) FROM table
409
- # # => [3, 5, 8, 1, ...]
457
+ # DB[:table].select_map{id * 2} # SELECT (id * 2) FROM table
458
+ # # => [6, 10, 16, 2, ...]
459
+ #
460
+ # You can also provide an array of column names:
461
+ #
462
+ # DB[:table].select_map([:id, :name]) # SELECT id, name FROM table
463
+ # # => [[1, 'A'], [2, 'B'], [3, 'C'], ...]
410
464
  def select_map(column=nil, &block)
411
- ds = naked.ungraphed
412
- ds = if column
413
- raise(Error, ARG_BLOCK_ERROR_MSG) if block
414
- ds.select(column)
415
- else
416
- ds.select(&block)
417
- end
418
- ds.map{|r| r.values.first}
465
+ _select_map(column, false, &block)
419
466
  end
467
+
420
468
 
421
469
  # The same as select_map, but in addition orders the array by the column.
422
470
  #
423
471
  # DB[:table].select_order_map(:id) # SELECT id FROM table ORDER BY id
424
472
  # # => [1, 2, 3, 4, ...]
425
473
  #
426
- # DB[:table].select_order_map{abs(id)} # SELECT abs(id) FROM table ORDER BY abs(id)
427
- # # => [1, 2, 3, 4, ...]
474
+ # DB[:table].select_order_map{id * 2} # SELECT (id * 2) FROM table ORDER BY (id * 2)
475
+ # # => [2, 4, 6, 8, ...]
476
+ #
477
+ # You can also provide an array of column names:
478
+ #
479
+ # DB[:table].select_order_map([:id, :name]) # SELECT id, name FROM table ORDER BY id, name
480
+ # # => [[1, 'A'], [2, 'B'], [3, 'C'], ...]
428
481
  def select_order_map(column=nil, &block)
429
- ds = naked.ungraphed
430
- ds = if column
431
- raise(Error, ARG_BLOCK_ERROR_MSG) if block
432
- ds.select(column).order(unaliased_identifier(column))
433
- else
434
- ds.select(&block).order(&block)
435
- end
436
- ds.map{|r| r.values.first}
482
+ _select_map(column, true, &block)
437
483
  end
438
-
484
+
439
485
  # Alias for update, but not aliased directly so subclasses
440
486
  # don't have to override both methods.
441
487
  def set(*args)
@@ -499,11 +545,38 @@ module Sequel
499
545
  #
500
546
  # DB[:table].to_hash(:id) # SELECT * FROM table
501
547
  # # {1=>{:id=>1, :name=>'Jim'}, 2=>{:id=>2, :name=>'Bob'}, ...}
548
+ #
549
+ # You can also provide an array of column names for either the key_column,
550
+ # the value column, or both:
551
+ #
552
+ # DB[:table].to_hash([:id, :foo], [:name, :bar]) # SELECT * FROM table
553
+ # # {[1, 3]=>['Jim', 'bo'], [2, 4]=>['Bob', 'be'], ...}
554
+ #
555
+ # DB[:table].to_hash([:id, :name]) # SELECT * FROM table
556
+ # # {[1, 'Jim']=>{:id=>1, :name=>'Jim'}, [2, 'Bob'=>{:id=>2, :name=>'Bob'}, ...}
502
557
  def to_hash(key_column, value_column = nil)
503
- inject({}) do |m, r|
504
- m[r[key_column]] = value_column ? r[value_column] : r
505
- m
558
+ h = {}
559
+ if value_column
560
+ return naked.to_hash(key_column, value_column) if row_proc
561
+ if value_column.is_a?(Array)
562
+ if key_column.is_a?(Array)
563
+ each{|r| h[r.values_at(*key_column)] = r.values_at(*value_column)}
564
+ else
565
+ each{|r| h[r[key_column]] = r.values_at(*value_column)}
566
+ end
567
+ else
568
+ if key_column.is_a?(Array)
569
+ each{|r| h[r.values_at(*key_column)] = r[value_column]}
570
+ else
571
+ each{|r| h[r[key_column]] = r[value_column]}
572
+ end
573
+ end
574
+ elsif key_column.is_a?(Array)
575
+ each{|r| h[r.values_at(*key_column)] = r}
576
+ else
577
+ each{|r| h[r[key_column]] = r}
506
578
  end
579
+ h
507
580
  end
508
581
 
509
582
  # Truncates the dataset. Returns nil.
@@ -522,14 +595,64 @@ module Sequel
522
595
  # DB[:table].update(:x=>nil) # UPDATE table SET x = NULL
523
596
  # # => 10
524
597
  #
525
- # DB[:table].update(:x=>:x+1, :y=>0) # UPDATE table SET x = (x + 1), :y = 0
598
+ # DB[:table].update(:x=>:x+1, :y=>0) # UPDATE table SET x = (x + 1), y = 0
526
599
  # # => 10
527
- def update(values={})
528
- execute_dui(update_sql(values))
600
+ def update(values={}, &block)
601
+ sql = update_sql(values)
602
+ if uses_returning?(:update)
603
+ returning_fetch_rows(sql, &block)
604
+ else
605
+ execute_dui(sql)
606
+ end
529
607
  end
530
608
 
609
+ protected
610
+
611
+ # Internals of #import. If primary key values are requested, use
612
+ # separate insert commands for each row. Otherwise, call #multi_insert_sql
613
+ # and execute each statement it gives separately.
614
+ def _import(columns, values, opts)
615
+ trans_opts = opts.merge(:server=>@opts[:server])
616
+ if opts[:return] == :primary_key
617
+ @db.transaction(trans_opts){values.map{|v| insert(columns, v)}}
618
+ else
619
+ stmts = multi_insert_sql(columns, values)
620
+ @db.transaction(trans_opts){stmts.each{|st| execute_dui(st)}}
621
+ end
622
+ end
623
+
624
+ # Return an array of arrays of values given by the symbols in ret_cols.
625
+ def _select_map_multiple(ret_cols)
626
+ map{|r| r.values_at(*ret_cols)}
627
+ end
628
+
629
+ # Returns an array of the first value in each row.
630
+ def _select_map_single
631
+ map{|r| r.values.first}
632
+ end
633
+
531
634
  private
532
635
 
636
+ # Internals of +select_map+ and +select_order_map+
637
+ def _select_map(column, order, &block)
638
+ ds = naked.ungraphed
639
+ if column
640
+ raise(Error, ARG_BLOCK_ERROR_MSG) if block
641
+ columns = Array(column)
642
+ select_cols = order ? columns.map{|c| c.is_a?(SQL::OrderedExpression) ? c.expression : c} : columns
643
+ ds = ds.select(*select_cols)
644
+ ds = ds.order(*columns.map{|c| unaliased_identifier(c)}) if order
645
+ else
646
+ ds = ds.select(&block)
647
+ ds = ds.order(&block) if order
648
+ end
649
+ if column.is_a?(Array)
650
+ ds._select_map_multiple(select_cols.map{|c| hash_key_symbol(c)})
651
+ else
652
+ ds._select_map_single
653
+ end
654
+ end
655
+
533
656
  # Set the server to use to :default unless it is already set in the passed opts
534
657
  def default_server_opts(opts)
535
658
  {:server=>@opts[:server] || :default}.merge(opts)
@@ -561,9 +684,19 @@ module Sequel
561
684
  # specifying the symbol that is likely to be used as the hash key
562
685
  # for the column when records are returned.
563
686
  def hash_key_symbol(s)
564
- raise(Error, "#{s.inspect} is not a symbol") unless s.is_a?(Symbol)
565
- _, c, a = split_symbol(s)
566
- (a || c).to_sym
687
+ case s
688
+ when Symbol
689
+ _, c, a = split_symbol(s)
690
+ (a || c).to_sym
691
+ when SQL::Identifier
692
+ hash_key_symbol(s.value)
693
+ when SQL::QualifiedIdentifier
694
+ hash_key_symbol(s.column)
695
+ when SQL::AliasedExpression
696
+ hash_key_symbol(s.aliaz)
697
+ else
698
+ raise(Error, "#{s.inspect} is not supported, should be a Symbol, String, SQL::Identifier, SQL::QualifiedIdentifier, or SQL::AliasedExpression")
699
+ end
567
700
  end
568
701
 
569
702
  # Modify the identifier returned from the database based on the
@@ -580,6 +713,20 @@ module Sequel
580
713
  def post_load(all_records)
581
714
  end
582
715
 
716
+ # Called by insert/update/delete when returning is used.
717
+ # Yields each row as a plain hash to the block if one is given, or returns
718
+ # an array of plain hashes for all rows if a block is not given
719
+ def returning_fetch_rows(sql, &block)
720
+ if block
721
+ default_server.fetch_rows(sql, &block)
722
+ nil
723
+ else
724
+ rows = []
725
+ default_server.fetch_rows(sql){|r| rows << r}
726
+ rows
727
+ end
728
+ end
729
+
583
730
  # Return the unaliased part of the identifier. Handles both
584
731
  # implicit aliases in symbols, as well as SQL::AliasedExpression
585
732
  # objects. Other objects are returned as is.
@@ -590,6 +737,14 @@ module Sequel
590
737
  c_table ? SQL::QualifiedIdentifier.new(c_table, column.to_sym) : column.to_sym
591
738
  when SQL::AliasedExpression
592
739
  c.expression
740
+ when SQL::OrderedExpression
741
+ expr = c.expression
742
+ if expr.is_a?(Symbol)
743
+ expr = unaliased_identifier(expr)
744
+ SQL::OrderedExpression.new(unaliased_identifier(c.expression), c.descending, :nulls=>c.nulls)
745
+ else
746
+ c
747
+ end
593
748
  else
594
749
  c
595
750
  end
@@ -1,17 +1,20 @@
1
1
  module Sequel
2
2
  class Dataset
3
3
  # ---------------------
4
- # :section: Methods that describe what the dataset supports
4
+ # :section: 4 - Methods that describe what the dataset supports
5
5
  # These methods all return booleans, with most describing whether or not the
6
6
  # dataset supports a feature.
7
7
  # ---------------------
8
8
 
9
- # Method used to check if WITH is supported
10
- WITH_SUPPORTED=:select_with_sql
11
-
12
9
  # Whether this dataset quotes identifiers.
13
10
  def quote_identifiers?
14
- @quote_identifiers
11
+ if defined?(@quote_identifiers)
12
+ @quote_identifiers
13
+ elsif db.respond_to?(:quote_identifiers?)
14
+ @quote_identifiers = db.quote_identifiers?
15
+ else
16
+ @quote_identifiers = false
17
+ end
15
18
  end
16
19
 
17
20
  # Whether this dataset will provide accurate number of rows matched for
@@ -21,22 +24,59 @@ module Sequel
21
24
  true
22
25
  end
23
26
 
27
+ # Whether you must use a column alias list for recursive CTEs (false by
28
+ # default).
29
+ def recursive_cte_requires_column_aliases?
30
+ false
31
+ end
32
+
24
33
  # Whether the dataset requires SQL standard datetimes (false by default,
25
34
  # as most allow strings with ISO 8601 format).
26
35
  def requires_sql_standard_datetimes?
27
36
  false
28
37
  end
29
38
 
39
+ # Whether type specifiers are required for prepared statement/bound
40
+ # variable argument placeholders (i.e. :bv__integer)
41
+ def requires_placeholder_type_specifiers?
42
+ false
43
+ end
44
+
30
45
  # Whether the dataset supports common table expressions (the WITH clause).
31
- def supports_cte?
32
- select_clause_methods.include?(WITH_SUPPORTED)
46
+ # If given, +type+ can be :select, :insert, :update, or :delete, in which case it
47
+ # determines whether WITH is supported for the respective statement type.
48
+ def supports_cte?(type=:select)
49
+ send(:"#{type}_clause_methods").include?(:"#{type}_with_sql")
50
+ end
51
+
52
+ # Whether the dataset supports common table expressions (the WITH clause)
53
+ # in subqueries. If false, applies the WITH clause to the main query, which can cause issues
54
+ # if multiple WITH clauses use the same name.
55
+ def supports_cte_in_subqueries?
56
+ false
33
57
  end
34
58
 
35
- # Whether the dataset supports the DISTINCT ON clause, false by default.
59
+ # Whether the dataset supports or can emulate the DISTINCT ON clause, false by default.
36
60
  def supports_distinct_on?
37
61
  false
38
62
  end
39
63
 
64
+ # Whether the dataset supports CUBE with GROUP BY.
65
+ def supports_group_cube?
66
+ false
67
+ end
68
+
69
+ # Whether the dataset supports ROLLUP with GROUP BY.
70
+ def supports_group_rollup?
71
+ false
72
+ end
73
+
74
+ # Whether this dataset supports the +insert_select+ method for returning all columns values
75
+ # directly from an insert query.
76
+ def supports_insert_select?
77
+ supports_returning?(:insert)
78
+ end
79
+
40
80
  # Whether the dataset supports the INTERSECT and EXCEPT compound operations, true by default.
41
81
  def supports_intersect_except?
42
82
  true
@@ -67,7 +107,24 @@ module Sequel
67
107
  def supports_multiple_column_in?
68
108
  true
69
109
  end
110
+
111
+ # Whether the dataset supports or can fully emulate the DISTINCT ON clause,
112
+ # including respecting the ORDER BY clause, false by default
113
+ def supports_ordered_distinct_on?
114
+ supports_distinct_on?
115
+ end
70
116
 
117
+ # Whether the RETURNING clause is supported for the given type of query.
118
+ # +type+ can be :insert, :update, or :delete.
119
+ def supports_returning?(type)
120
+ send(:"#{type}_clause_methods").include?(:"#{type}_returning_sql")
121
+ end
122
+
123
+ # Whether the database supports SELECT *, column FROM table
124
+ def supports_select_all_and_column?
125
+ true
126
+ end
127
+
71
128
  # Whether the dataset supports timezones in literal timestamps
72
129
  def supports_timestamp_timezones?
73
130
  false
@@ -82,5 +139,31 @@ module Sequel
82
139
  def supports_window_functions?
83
140
  false
84
141
  end
142
+
143
+ # Whether the dataset supports WHERE TRUE (or WHERE 1 for databases that
144
+ # that use 1 for true).
145
+ def supports_where_true?
146
+ true
147
+ end
148
+
149
+ private
150
+
151
+ # Whether insert(nil) or insert({}) must be emulated by
152
+ # using at least one value, false by default.
153
+ def insert_supports_empty_values?
154
+ true
155
+ end
156
+
157
+ # Whether using an offset returns an extra row number column that should be
158
+ # eliminated, false by default.
159
+ def offset_returns_row_number_column?
160
+ false
161
+ end
162
+
163
+ # Whether the RETURNING clause is used for the given dataset.
164
+ # +type+ can be :insert, :update, or :delete.
165
+ def uses_returning?(type)
166
+ opts[:returning] && !@opts[:sql] && supports_returning?(type)
167
+ end
85
168
  end
86
169
  end