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 Database
3
3
  # ---------------------
4
- # :section: Methods that modify the database schema
4
+ # :section: 2 - Methods that modify the database schema
5
5
  # These methods execute code on the database that modifies the database's schema.
6
6
  # ---------------------
7
7
 
@@ -21,7 +21,7 @@ module Sequel
21
21
  UNSIGNED = ' UNSIGNED'.freeze
22
22
 
23
23
  # The order of column modifiers to use when defining a column.
24
- COLUMN_DEFINITION_ORDER = [:default, :null, :unique, :primary_key, :auto_increment, :references]
24
+ COLUMN_DEFINITION_ORDER = [:collate, :default, :null, :unique, :primary_key, :auto_increment, :references]
25
25
 
26
26
  # Adds a column to the specified table. This method expects a column name,
27
27
  # a datatype and optionally a hash with additional constraints and options:
@@ -29,7 +29,7 @@ module Sequel
29
29
  # DB.add_column :items, :name, :text, :unique => true, :null => false
30
30
  # DB.add_column :items, :category, :text, :default => 'ruby'
31
31
  #
32
- # See alter_table.
32
+ # See <tt>alter_table</tt>.
33
33
  def add_column(table, *args)
34
34
  alter_table(table) {add_column(*args)}
35
35
  end
@@ -40,9 +40,9 @@ module Sequel
40
40
  # DB.add_index :posts, [:author, :title], :unique => true
41
41
  #
42
42
  # Options:
43
- # * :ignore_errors - Ignore any DatabaseErrors that are raised
43
+ # :ignore_errors :: Ignore any DatabaseErrors that are raised
44
44
  #
45
- # See alter_table.
45
+ # See <tt>alter_table</tt>.
46
46
  def add_index(table, columns, options={})
47
47
  e = options[:ignore_errors]
48
48
  begin
@@ -65,10 +65,10 @@ module Sequel
65
65
  # end
66
66
  #
67
67
  # Note that +add_column+ accepts all the options available for column
68
- # definitions using create_table, and +add_index+ accepts all the options
68
+ # definitions using <tt>create_table</tt>, and +add_index+ accepts all the options
69
69
  # available for index definition.
70
70
  #
71
- # See Schema::AlterTableGenerator and the {"Migrations and Schema Modification" guide}[link:files/doc/migration_rdoc.html].
71
+ # See <tt>Schema::AlterTableGenerator</tt> and the {"Migrations and Schema Modification" guide}[link:files/doc/migration_rdoc.html].
72
72
  def alter_table(name, generator=nil, &block)
73
73
  generator ||= Schema::AlterTableGenerator.new(self, &block)
74
74
  alter_table_sql_list(name, generator.operations).flatten.each {|sql| execute_ddl(sql)}
@@ -89,7 +89,7 @@ module Sequel
89
89
  # :temp :: Create the table as a temporary table.
90
90
  # :ignore_index_errors :: Ignore any errors when creating indexes.
91
91
  #
92
- # See Schema::Generator and the {"Migrations and Schema Modification" guide}[link:files/doc/migration_rdoc.html].
92
+ # See <tt>Schema::Generator</tt> and the {"Migrations and Schema Modification" guide}[link:files/doc/migration_rdoc.html].
93
93
  def create_table(name, options={}, &block)
94
94
  remove_cached_schema(name)
95
95
  options = {:generator=>options} if options.is_a?(Schema::Generator)
@@ -99,19 +99,28 @@ module Sequel
99
99
  nil
100
100
  end
101
101
 
102
- # Forcibly creates a table, attempting to drop it unconditionally (and catching any errors), then creating it.
102
+ # Forcibly create a table, attempting to drop it if it already exists, then creating it.
103
103
  #
104
104
  # DB.create_table!(:a){Integer :a}
105
- # # DROP TABLE a
105
+ # # SELECT * FROM a LIMIT a -- check existence
106
+ # # DROP TABLE a -- drop table if already exists
106
107
  # # CREATE TABLE a (a integer)
107
108
  def create_table!(name, options={}, &block)
108
- drop_table(name) rescue nil
109
+ drop_table(name) if table_exists?(name)
109
110
  create_table(name, options, &block)
110
111
  end
111
112
 
112
- # Creates the table unless the table already exists
113
+ # Creates the table unless the table already exists.
114
+ #
115
+ # DB.create_table?(:a){Integer :a}
116
+ # # SELECT * FROM a LIMIT a -- check existence
117
+ # # CREATE TABLE a (a integer) -- if it doesn't already exist
113
118
  def create_table?(name, options={}, &block)
114
- create_table(name, options, &block) unless table_exists?(name)
119
+ if supports_create_table_if_not_exists?
120
+ create_table(name, options.merge(:if_not_exists=>true), &block)
121
+ elsif !table_exists?(name)
122
+ create_table(name, options, &block)
123
+ end
115
124
  end
116
125
 
117
126
  # Creates a view, replacing it if it already exists:
@@ -138,7 +147,7 @@ module Sequel
138
147
  #
139
148
  # DB.drop_column :items, :category
140
149
  #
141
- # See alter_table.
150
+ # See <tt>alter_table</tt>.
142
151
  def drop_column(table, *args)
143
152
  alter_table(table) {drop_column(*args)}
144
153
  end
@@ -148,17 +157,20 @@ module Sequel
148
157
  # DB.drop_index :posts, :title
149
158
  # DB.drop_index :posts, [:author, :title]
150
159
  #
151
- # See alter_table.
160
+ # See <tt>alter_table</tt>.
152
161
  def drop_index(table, columns, options={})
153
162
  alter_table(table){drop_index(columns, options)}
154
163
  end
155
164
 
156
165
  # Drops one or more tables corresponding to the given names:
157
166
  #
167
+ # DB.drop_table(:posts)
158
168
  # DB.drop_table(:posts, :comments)
169
+ # DB.drop_table(:posts, :comments, :cascade=>true)
159
170
  def drop_table(*names)
171
+ options = names.last.is_a?(Hash) ? names.pop : {}
160
172
  names.each do |n|
161
- execute_ddl(drop_table_sql(n))
173
+ execute_ddl(drop_table_sql(n, options))
162
174
  remove_cached_schema(n)
163
175
  end
164
176
  nil
@@ -167,9 +179,12 @@ module Sequel
167
179
  # Drops one or more views corresponding to the given names:
168
180
  #
169
181
  # DB.drop_view(:cheap_items)
182
+ # DB.drop_view(:cheap_items, :pricey_items)
183
+ # DB.drop_view(:cheap_items, :pricey_items, :cascade=>true)
170
184
  def drop_view(*names)
185
+ options = names.last.is_a?(Hash) ? names.pop : {}
171
186
  names.each do |n|
172
- execute_ddl("DROP VIEW #{quote_schema_table(n)}")
187
+ execute_ddl(drop_view_sql(n, options))
173
188
  remove_cached_schema(n)
174
189
  end
175
190
  nil
@@ -191,7 +206,7 @@ module Sequel
191
206
  #
192
207
  # DB.rename_column :items, :cntr, :counter
193
208
  #
194
- # See alter_table.
209
+ # See <tt>alter_table</tt>.
195
210
  def rename_column(table, *args)
196
211
  alter_table(table) {rename_column(*args)}
197
212
  end
@@ -200,7 +215,7 @@ module Sequel
200
215
  #
201
216
  # DB.set_column_default :items, :category, 'perl!'
202
217
  #
203
- # See alter_table.
218
+ # See <tt>alter_table</tt>.
204
219
  def set_column_default(table, *args)
205
220
  alter_table(table) {set_column_default(*args)}
206
221
  end
@@ -209,7 +224,7 @@ module Sequel
209
224
  #
210
225
  # DB.set_column_type :items, :price, :float
211
226
  #
212
- # See alter_table.
227
+ # See <tt>alter_table</tt>.
213
228
  def set_column_type(table, *args)
214
229
  alter_table(table) {set_column_type(*args)}
215
230
  end
@@ -224,7 +239,7 @@ module Sequel
224
239
  when :add_column
225
240
  "ADD COLUMN #{column_definition_sql(op)}"
226
241
  when :drop_column
227
- "DROP COLUMN #{quoted_name}"
242
+ "DROP COLUMN #{quoted_name}#{' CASCADE' if op[:cascade]}"
228
243
  when :rename_column
229
244
  "RENAME COLUMN #{quoted_name} TO #{quote_identifier(op[:new_name])}"
230
245
  when :set_column_type
@@ -240,7 +255,7 @@ module Sequel
240
255
  when :add_constraint
241
256
  "ADD #{constraint_definition_sql(op)}"
242
257
  when :drop_constraint
243
- "DROP CONSTRAINT #{quoted_name}"
258
+ "DROP CONSTRAINT #{quoted_name}#{' CASCADE' if op[:cascade]}"
244
259
  else
245
260
  raise Error, "Unsupported ALTER TABLE operation"
246
261
  end
@@ -275,7 +290,12 @@ module Sequel
275
290
  def column_definition_auto_increment_sql(sql, column)
276
291
  sql << " #{auto_increment_sql}" if column[:auto_increment]
277
292
  end
278
-
293
+
294
+ # Add collate SQL fragment to column creation SQL.
295
+ def column_definition_collate_sql(sql, column)
296
+ sql << " COLLATE #{column[:collate]}" if column[:collate]
297
+ end
298
+
279
299
  # Add default SQL fragment to column creation SQL.
280
300
  def column_definition_default_sql(sql, column)
281
301
  sql << " DEFAULT #{literal(column[:default])}" if column.include?(:default)
@@ -354,7 +374,7 @@ module Sequel
354
374
 
355
375
  # Execute the create index statements using the generator.
356
376
  def create_table_indexes_from_generator(name, generator, options)
357
- e = options[:ignore_index_errors]
377
+ e = options[:ignore_index_errors] || options[:if_not_exists]
358
378
  generator.indexes.each do |index|
359
379
  begin
360
380
  index_sql_list(name, [index]).each{|sql| execute_ddl(sql)}
@@ -366,7 +386,7 @@ module Sequel
366
386
 
367
387
  # DDL statement for creating a table with the given name, columns, and options
368
388
  def create_table_sql(name, generator, options)
369
- "CREATE #{temporary_table_sql if options[:temp]}TABLE #{quote_schema_table(name)} (#{column_list_sql(generator)})"
389
+ "CREATE #{temporary_table_sql if options[:temp]}TABLE#{' IF NOT EXISTS' if options[:if_not_exists]} #{options[:temp] ? quote_identifier(name) : quote_schema_table(name)} (#{column_list_sql(generator)})"
370
390
  end
371
391
 
372
392
  # Default index name for the table and columns, may be too long
@@ -382,10 +402,15 @@ module Sequel
382
402
  end
383
403
 
384
404
  # SQL DDL statement to drop the table with the given name.
385
- def drop_table_sql(name)
386
- "DROP TABLE #{quote_schema_table(name)}"
405
+ def drop_table_sql(name, options)
406
+ "DROP TABLE #{quote_schema_table(name)}#{' CASCADE' if options[:cascade]}"
387
407
  end
388
408
 
409
+ # SQL DDL statement to drop a view with the given name.
410
+ def drop_view_sql(name, options)
411
+ "DROP VIEW #{quote_schema_table(name)}#{' CASCADE' if options[:cascade]}"
412
+ end
413
+
389
414
  # Proxy the filter_expr call to the dataset, used for creating constraints.
390
415
  def filter_expr(*args, &block)
391
416
  schema_utility_dataset.literal(schema_utility_dataset.send(:filter_expr, *args, &block))
@@ -1,7 +1,7 @@
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.
@@ -101,10 +101,15 @@ module Sequel
101
101
  #
102
102
  # DB[:table].delete # DELETE * FROM table
103
103
  # # => 3
104
- def delete
105
- execute_dui(delete_sql)
104
+ def delete(&block)
105
+ sql = delete_sql
106
+ if uses_returning?(:delete)
107
+ returning_fetch_rows(sql, &block)
108
+ else
109
+ execute_dui(sql)
110
+ end
106
111
  end
107
-
112
+
108
113
  # Iterates over the records in the dataset as they are yielded from the
109
114
  # database adapter, and returns self.
110
115
  #
@@ -248,14 +253,16 @@ module Sequel
248
253
  # the value of the primary key for the inserted row, but that is adapter dependent.
249
254
  #
250
255
  # +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.
256
+ # no arguments or single empty hash :: Uses DEFAULT VALUES
257
+ # single hash :: Most common format, treats keys as columns an values as values
258
+ # single array :: Treats entries as values, with no columns
259
+ # two arrays :: Treats first array as columns, second array as values
260
+ # single Dataset :: Treats as an insert based on a selection from the dataset given,
261
+ # with no columns
262
+ # array and dataset :: Treats as an insert based on a selection from the dataset
263
+ # given, with the columns given by the array.
264
+ #
265
+ # Examples:
259
266
  #
260
267
  # DB[:items].insert
261
268
  # # INSERT INTO items DEFAULT VALUES
@@ -277,8 +284,13 @@ module Sequel
277
284
  #
278
285
  # DB[:items].insert([:a, :b], DB[:old_items])
279
286
  # # INSERT INTO items (a, b) SELECT * FROM old_items
280
- def insert(*values)
281
- execute_insert(insert_sql(*values))
287
+ def insert(*values, &block)
288
+ sql = insert_sql(*values)
289
+ if uses_returning?(:insert)
290
+ returning_fetch_rows(sql, &block)
291
+ else
292
+ execute_insert(sql)
293
+ end
282
294
  end
283
295
 
284
296
  # Inserts multiple values. If a block is given it is invoked for each
@@ -310,7 +322,7 @@ module Sequel
310
322
  aggregate_dataset.get{max(column) - min(column)}
311
323
  end
312
324
 
313
- # Reverses the order and then runs first. Note that this
325
+ # Reverses the order and then runs #first with the given arguments and block. Note that this
314
326
  # will not necessarily give you the last record in the dataset,
315
327
  # unless you have an unambiguous order. If there is not
316
328
  # currently an order for this dataset, raises an +Error+.
@@ -334,10 +346,19 @@ module Sequel
334
346
  #
335
347
  # DB[:table].map{|r| r[:id] * 2} # SELECT * FROM table
336
348
  # # => [2, 4, 6, ...]
349
+ #
350
+ # You can also provide an array of column names:
351
+ #
352
+ # DB[:table].map([:id, :name]) # SELECT * FROM table
353
+ # # => [[1, 'A'], [2, 'B'], [3, 'C'], ...]
337
354
  def map(column=nil, &block)
338
355
  if column
339
356
  raise(Error, ARG_BLOCK_ERROR_MSG) if block
340
- super(){|r| r[column]}
357
+ if column.is_a?(Array)
358
+ super(){|r| column.map{|c| r[c]}}
359
+ else
360
+ super(){|r| r[column]}
361
+ end
341
362
  else
342
363
  super(&block)
343
364
  end
@@ -393,49 +414,63 @@ module Sequel
393
414
  #
394
415
  # DB[:table].select_hash(:id, :name) # SELECT id, name FROM table
395
416
  # # => {1=>'a', 2=>'b', ...}
417
+ #
418
+ # You can also provide an array of column names for either the key_column,
419
+ # the value column, or both:
420
+ #
421
+ # DB[:table].select_hash([:id, :foo], [:name, :bar]) # SELECT * FROM table
422
+ # # {[1, 3]=>['a', 'c'], [2, 4]=>['b', 'd'], ...}
396
423
  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))
424
+ if key_column.is_a?(Array)
425
+ if value_column.is_a?(Array)
426
+ select(*(key_column + value_column)).to_hash(key_column.map{|c| hash_key_symbol(c)}, value_column.map{|c| hash_key_symbol(c)})
427
+ else
428
+ select(*(key_column + [value_column])).to_hash(key_column.map{|c| hash_key_symbol(c)}, hash_key_symbol(value_column))
429
+ end
430
+ elsif value_column.is_a?(Array)
431
+ select(key_column, *value_column).to_hash(hash_key_symbol(key_column), value_column.map{|c| hash_key_symbol(c)})
432
+ else
433
+ select(key_column, value_column).to_hash(hash_key_symbol(key_column), hash_key_symbol(value_column))
434
+ end
398
435
  end
399
436
 
400
437
  # Selects the column given (either as an argument or as a block), and
401
438
  # returns an array of all values of that column in the dataset. If you
402
439
  # give a block argument that returns an array with multiple entries,
403
- # the contents of the resulting array are undefined.
440
+ # the contents of the resulting array are undefined. Raises an Error
441
+ # if called with both an argument and a block.
404
442
  #
405
443
  # DB[:table].select_map(:id) # SELECT id FROM table
406
444
  # # => [3, 5, 8, 1, ...]
407
445
  #
408
- # DB[:table].select_map{abs(id)} # SELECT abs(id) FROM table
409
- # # => [3, 5, 8, 1, ...]
446
+ # DB[:table].select_map{id * 2} # SELECT (id * 2) FROM table
447
+ # # => [6, 10, 16, 2, ...]
448
+ #
449
+ # You can also provide an array of column names:
450
+ #
451
+ # DB[:table].select_map([:id, :name]) # SELECT id, name FROM table
452
+ # # => [[1, 'A'], [2, 'B'], [3, 'C'], ...]
410
453
  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}
454
+ _select_map(column, false, &block)
419
455
  end
456
+
420
457
 
421
458
  # The same as select_map, but in addition orders the array by the column.
422
459
  #
423
460
  # DB[:table].select_order_map(:id) # SELECT id FROM table ORDER BY id
424
461
  # # => [1, 2, 3, 4, ...]
425
462
  #
426
- # DB[:table].select_order_map{abs(id)} # SELECT abs(id) FROM table ORDER BY abs(id)
427
- # # => [1, 2, 3, 4, ...]
463
+ # DB[:table].select_order_map{id * 2} # SELECT (id * 2) FROM table ORDER BY (id * 2)
464
+ # # => [2, 4, 6, 8, ...]
465
+ #
466
+ # You can also provide an array of column names:
467
+ #
468
+ # DB[:table].select_order_map([:id, :name]) # SELECT id, name FROM table ORDER BY id, name
469
+ # # => [[1, 'A'], [2, 'B'], [3, 'C'], ...]
428
470
  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}
471
+ _select_map(column, true, &block)
437
472
  end
438
-
473
+
439
474
  # Alias for update, but not aliased directly so subclasses
440
475
  # don't have to override both methods.
441
476
  def set(*args)
@@ -499,11 +534,37 @@ module Sequel
499
534
  #
500
535
  # DB[:table].to_hash(:id) # SELECT * FROM table
501
536
  # # {1=>{:id=>1, :name=>'Jim'}, 2=>{:id=>2, :name=>'Bob'}, ...}
537
+ #
538
+ # You can also provide an array of column names for either the key_column,
539
+ # the value column, or both:
540
+ #
541
+ # DB[:table].to_hash([:id, :foo], [:name, :bar]) # SELECT * FROM table
542
+ # # {[1, 3]=>['Jim', 'bo'], [2, 4]=>['Bob', 'be'], ...}
543
+ #
544
+ # DB[:table].to_hash([:id, :name]) # SELECT * FROM table
545
+ # # {[1, 'Jim']=>{:id=>1, :name=>'Jim'}, [2, 'Bob'=>{:id=>2, :name=>'Bob'}, ...}
502
546
  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
547
+ h = {}
548
+ if value_column
549
+ if value_column.is_a?(Array)
550
+ if key_column.is_a?(Array)
551
+ each{|r| h[key_column.map{|c| r[c]}] = value_column.map{|c| r[c]}}
552
+ else
553
+ each{|r| h[r[key_column]] = value_column.map{|c| r[c]}}
554
+ end
555
+ else
556
+ if key_column.is_a?(Array)
557
+ each{|r| h[key_column.map{|c| r[c]}] = r[value_column]}
558
+ else
559
+ each{|r| h[r[key_column]] = r[value_column]}
560
+ end
561
+ end
562
+ elsif key_column.is_a?(Array)
563
+ each{|r| h[key_column.map{|c| r[c]}] = r}
564
+ else
565
+ each{|r| h[r[key_column]] = r}
506
566
  end
567
+ h
507
568
  end
508
569
 
509
570
  # Truncates the dataset. Returns nil.
@@ -522,14 +583,40 @@ module Sequel
522
583
  # DB[:table].update(:x=>nil) # UPDATE table SET x = NULL
523
584
  # # => 10
524
585
  #
525
- # DB[:table].update(:x=>:x+1, :y=>0) # UPDATE table SET x = (x + 1), :y = 0
586
+ # DB[:table].update(:x=>:x+1, :y=>0) # UPDATE table SET x = (x + 1), y = 0
526
587
  # # => 10
527
- def update(values={})
528
- execute_dui(update_sql(values))
588
+ def update(values={}, &block)
589
+ sql = update_sql(values)
590
+ if uses_returning?(:update)
591
+ returning_fetch_rows(sql, &block)
592
+ else
593
+ execute_dui(sql)
594
+ end
529
595
  end
530
596
 
531
597
  private
532
598
 
599
+ # Internals of +select_map+ and +select_order_map+
600
+ def _select_map(column, order, &block)
601
+ ds = naked.ungraphed
602
+ if column
603
+ raise(Error, ARG_BLOCK_ERROR_MSG) if block
604
+ columns = Array(column)
605
+ select_cols = order ? columns.map{|c| c.is_a?(SQL::OrderedExpression) ? c.expression : c} : columns
606
+ ds = ds.select(*select_cols)
607
+ ds = ds.order(*columns.map{|c| unaliased_identifier(c)}) if order
608
+ else
609
+ ds = ds.select(&block)
610
+ ds = ds.order(&block) if order
611
+ end
612
+ if ds.opts[:select].length > 1
613
+ ret_cols = select_cols.map{|c| hash_key_symbol(c)}
614
+ ds.map{|r| ret_cols.map{|c| r[c]}}
615
+ else
616
+ ds.map{|r| r.values.first}
617
+ end
618
+ end
619
+
533
620
  # Set the server to use to :default unless it is already set in the passed opts
534
621
  def default_server_opts(opts)
535
622
  {:server=>@opts[:server] || :default}.merge(opts)
@@ -561,9 +648,19 @@ module Sequel
561
648
  # specifying the symbol that is likely to be used as the hash key
562
649
  # for the column when records are returned.
563
650
  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
651
+ case s
652
+ when Symbol
653
+ _, c, a = split_symbol(s)
654
+ (a || c).to_sym
655
+ when SQL::Identifier
656
+ hash_key_symbol(s.value)
657
+ when SQL::QualifiedIdentifier
658
+ hash_key_symbol(s.column)
659
+ when SQL::AliasedExpression
660
+ hash_key_symbol(s.aliaz)
661
+ else
662
+ raise(Error, "#{s.inspect} is not supported, should be a Symbol, String, SQL::Identifier, SQL::QualifiedIdentifier, or SQL::AliasedExpression")
663
+ end
567
664
  end
568
665
 
569
666
  # Modify the identifier returned from the database based on the
@@ -580,6 +677,20 @@ module Sequel
580
677
  def post_load(all_records)
581
678
  end
582
679
 
680
+ # Called by insert/update/delete when returning is used.
681
+ # Yields each row as a plain hash to the block if one is given, or returns
682
+ # an array of plain hashes for all rows if a block is not given
683
+ def returning_fetch_rows(sql, &block)
684
+ if block
685
+ default_server.fetch_rows(sql, &block)
686
+ nil
687
+ else
688
+ rows = []
689
+ default_server.fetch_rows(sql){|r| rows << r}
690
+ rows
691
+ end
692
+ end
693
+
583
694
  # Return the unaliased part of the identifier. Handles both
584
695
  # implicit aliases in symbols, as well as SQL::AliasedExpression
585
696
  # objects. Other objects are returned as is.
@@ -590,6 +701,14 @@ module Sequel
590
701
  c_table ? SQL::QualifiedIdentifier.new(c_table, column.to_sym) : column.to_sym
591
702
  when SQL::AliasedExpression
592
703
  c.expression
704
+ when SQL::OrderedExpression
705
+ expr = c.expression
706
+ if expr.is_a?(Symbol)
707
+ expr = unaliased_identifier(expr)
708
+ SQL::OrderedExpression.new(unaliased_identifier(c.expression), c.descending, :nulls=>c.nulls)
709
+ else
710
+ c
711
+ end
593
712
  else
594
713
  c
595
714
  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
@@ -28,15 +31,30 @@ module Sequel
28
31
  end
29
32
 
30
33
  # Whether the dataset supports common table expressions (the WITH clause).
31
- def supports_cte?
32
- select_clause_methods.include?(WITH_SUPPORTED)
34
+ # If given, +type+ can be :select, :insert, :update, or :delete, in which case it
35
+ # determines whether WITH is supported for the respective statement type.
36
+ def supports_cte?(type=:select)
37
+ send(:"#{type}_clause_methods").include?(:"#{type}_with_sql")
33
38
  end
34
39
 
35
- # Whether the dataset supports the DISTINCT ON clause, false by default.
40
+ # Whether the dataset supports common table expressions (the WITH clause)
41
+ # in subqueries. If false, applies the WITH clause to the main query, which can cause issues
42
+ # if multiple WITH clauses use the same name.
43
+ def supports_cte_in_subqueries?
44
+ false
45
+ end
46
+
47
+ # Whether the dataset supports or can emulate the DISTINCT ON clause, false by default.
36
48
  def supports_distinct_on?
37
49
  false
38
50
  end
39
51
 
52
+ # Whether this dataset supports the +insert_select+ method for returning all columns values
53
+ # directly from an insert query.
54
+ def supports_insert_select?
55
+ supports_returning?(:insert)
56
+ end
57
+
40
58
  # Whether the dataset supports the INTERSECT and EXCEPT compound operations, true by default.
41
59
  def supports_intersect_except?
42
60
  true
@@ -67,7 +85,24 @@ module Sequel
67
85
  def supports_multiple_column_in?
68
86
  true
69
87
  end
88
+
89
+ # Whether the dataset supports or can fully emulate the DISTINCT ON clause,
90
+ # including respecting the ORDER BY clause, false by default
91
+ def supports_ordered_distinct_on?
92
+ supports_distinct_on?
93
+ end
70
94
 
95
+ # Whether the RETURNING clause is supported for the given type of query.
96
+ # +type+ can be :insert, :update, or :delete.
97
+ def supports_returning?(type)
98
+ send(:"#{type}_clause_methods").include?(:"#{type}_returning_sql")
99
+ end
100
+
101
+ # Whether the database supports SELECT *, column FROM table
102
+ def supports_select_all_and_column?
103
+ true
104
+ end
105
+
71
106
  # Whether the dataset supports timezones in literal timestamps
72
107
  def supports_timestamp_timezones?
73
108
  false
@@ -82,5 +117,19 @@ module Sequel
82
117
  def supports_window_functions?
83
118
  false
84
119
  end
120
+
121
+ # Whether the dataset supports WHERE TRUE (or WHERE 1 for databases that
122
+ # that use 1 for true).
123
+ def supports_where_true?
124
+ true
125
+ end
126
+
127
+ private
128
+
129
+ # Whether the RETURNING clause is used for the given dataset.
130
+ # +type+ can be :insert, :update, or :delete.
131
+ def uses_returning?(type)
132
+ opts[:returning] && !@opts[:sql] && supports_returning?(type)
133
+ end
85
134
  end
86
135
  end
@@ -1,7 +1,7 @@
1
1
  module Sequel
2
2
  class Dataset
3
3
  # ---------------------
4
- # :section: Methods related to dataset graphing
4
+ # :section: 5 - Methods related to dataset graphing
5
5
  # Dataset graphing changes the dataset to yield hashes where keys are table
6
6
  # name symbols and values are hashes representing the columns related to
7
7
  # that table. All of these methods return modified copies of the receiver.