sequel 3.21.0 → 3.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. data/CHANGELOG +413 -3
  2. data/README.rdoc +20 -6
  3. data/Rakefile +23 -16
  4. data/bin/sequel +1 -5
  5. data/doc/association_basics.rdoc +210 -43
  6. data/doc/dataset_basics.rdoc +4 -4
  7. data/doc/mass_assignment.rdoc +54 -0
  8. data/doc/migration.rdoc +15 -538
  9. data/doc/model_hooks.rdoc +72 -27
  10. data/doc/opening_databases.rdoc +86 -50
  11. data/doc/prepared_statements.rdoc +40 -13
  12. data/doc/reflection.rdoc +8 -2
  13. data/doc/release_notes/3.22.0.txt +39 -0
  14. data/doc/release_notes/3.23.0.txt +172 -0
  15. data/doc/release_notes/3.24.0.txt +420 -0
  16. data/doc/release_notes/3.25.0.txt +88 -0
  17. data/doc/release_notes/3.26.0.txt +88 -0
  18. data/doc/release_notes/3.27.0.txt +82 -0
  19. data/doc/release_notes/3.28.0.txt +304 -0
  20. data/doc/release_notes/3.29.0.txt +459 -0
  21. data/doc/schema_modification.rdoc +547 -0
  22. data/doc/sharding.rdoc +7 -1
  23. data/doc/testing.rdoc +115 -0
  24. data/doc/transactions.rdoc +137 -0
  25. data/doc/virtual_rows.rdoc +2 -2
  26. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  27. data/lib/sequel/adapters/ado.rb +40 -18
  28. data/lib/sequel/adapters/amalgalite.rb +15 -7
  29. data/lib/sequel/adapters/db2.rb +175 -86
  30. data/lib/sequel/adapters/dbi.rb +15 -15
  31. data/lib/sequel/adapters/do/mysql.rb +0 -5
  32. data/lib/sequel/adapters/do/postgres.rb +0 -5
  33. data/lib/sequel/adapters/do/sqlite.rb +0 -5
  34. data/lib/sequel/adapters/do.rb +17 -36
  35. data/lib/sequel/adapters/firebird.rb +27 -208
  36. data/lib/sequel/adapters/ibmdb.rb +448 -0
  37. data/lib/sequel/adapters/informix.rb +6 -23
  38. data/lib/sequel/adapters/jdbc/as400.rb +5 -31
  39. data/lib/sequel/adapters/jdbc/db2.rb +44 -0
  40. data/lib/sequel/adapters/jdbc/derby.rb +217 -0
  41. data/lib/sequel/adapters/jdbc/firebird.rb +29 -0
  42. data/lib/sequel/adapters/jdbc/h2.rb +26 -17
  43. data/lib/sequel/adapters/jdbc/hsqldb.rb +166 -0
  44. data/lib/sequel/adapters/jdbc/informix.rb +26 -0
  45. data/lib/sequel/adapters/jdbc/jtds.rb +29 -0
  46. data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
  47. data/lib/sequel/adapters/jdbc/mysql.rb +9 -10
  48. data/lib/sequel/adapters/jdbc/oracle.rb +67 -25
  49. data/lib/sequel/adapters/jdbc/postgresql.rb +6 -11
  50. data/lib/sequel/adapters/jdbc/sqlite.rb +0 -5
  51. data/lib/sequel/adapters/jdbc/sqlserver.rb +41 -0
  52. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  53. data/lib/sequel/adapters/jdbc.rb +107 -38
  54. data/lib/sequel/adapters/mock.rb +315 -0
  55. data/lib/sequel/adapters/mysql.rb +78 -182
  56. data/lib/sequel/adapters/mysql2.rb +24 -23
  57. data/lib/sequel/adapters/odbc/db2.rb +17 -0
  58. data/lib/sequel/adapters/odbc/mssql.rb +0 -5
  59. data/lib/sequel/adapters/odbc.rb +28 -9
  60. data/lib/sequel/adapters/openbase.rb +2 -4
  61. data/lib/sequel/adapters/oracle.rb +349 -51
  62. data/lib/sequel/adapters/postgres.rb +169 -21
  63. data/lib/sequel/adapters/shared/access.rb +21 -6
  64. data/lib/sequel/adapters/shared/db2.rb +288 -0
  65. data/lib/sequel/adapters/shared/firebird.rb +214 -0
  66. data/lib/sequel/adapters/shared/informix.rb +45 -0
  67. data/lib/sequel/adapters/shared/mssql.rb +108 -65
  68. data/lib/sequel/adapters/shared/mysql.rb +56 -13
  69. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +149 -0
  70. data/lib/sequel/adapters/shared/oracle.rb +185 -34
  71. data/lib/sequel/adapters/shared/postgres.rb +107 -58
  72. data/lib/sequel/adapters/shared/progress.rb +0 -6
  73. data/lib/sequel/adapters/shared/sqlite.rb +168 -41
  74. data/lib/sequel/adapters/sqlite.rb +27 -18
  75. data/lib/sequel/adapters/swift/mysql.rb +9 -5
  76. data/lib/sequel/adapters/swift/postgres.rb +0 -5
  77. data/lib/sequel/adapters/swift/sqlite.rb +6 -4
  78. data/lib/sequel/adapters/swift.rb +5 -5
  79. data/lib/sequel/adapters/tinytds.rb +152 -17
  80. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +63 -0
  81. data/lib/sequel/ast_transformer.rb +190 -0
  82. data/lib/sequel/connection_pool/threaded.rb +3 -2
  83. data/lib/sequel/connection_pool.rb +1 -1
  84. data/lib/sequel/core.rb +46 -5
  85. data/lib/sequel/database/connecting.rb +5 -6
  86. data/lib/sequel/database/dataset.rb +4 -4
  87. data/lib/sequel/database/dataset_defaults.rb +59 -1
  88. data/lib/sequel/database/logging.rb +1 -1
  89. data/lib/sequel/database/misc.rb +100 -19
  90. data/lib/sequel/database/query.rb +153 -58
  91. data/lib/sequel/database/schema_generator.rb +8 -5
  92. data/lib/sequel/database/schema_methods.rb +59 -29
  93. data/lib/sequel/dataset/actions.rb +194 -57
  94. data/lib/sequel/dataset/features.rb +81 -8
  95. data/lib/sequel/dataset/graph.rb +8 -7
  96. data/lib/sequel/dataset/misc.rb +50 -23
  97. data/lib/sequel/dataset/mutation.rb +5 -6
  98. data/lib/sequel/dataset/prepared_statements.rb +32 -15
  99. data/lib/sequel/dataset/query.rb +223 -42
  100. data/lib/sequel/dataset/sql.rb +58 -61
  101. data/lib/sequel/dataset.rb +8 -0
  102. data/lib/sequel/exceptions.rb +4 -0
  103. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  104. data/lib/sequel/extensions/migration.rb +6 -4
  105. data/lib/sequel/extensions/named_timezones.rb +5 -0
  106. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  107. data/lib/sequel/extensions/to_dot.rb +95 -83
  108. data/lib/sequel/model/associations.rb +997 -331
  109. data/lib/sequel/model/base.rb +386 -129
  110. data/lib/sequel/model/errors.rb +1 -1
  111. data/lib/sequel/model/exceptions.rb +5 -1
  112. data/lib/sequel/model.rb +15 -8
  113. data/lib/sequel/plugins/association_pks.rb +22 -4
  114. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  115. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  116. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  117. data/lib/sequel/plugins/force_encoding.rb +6 -6
  118. data/lib/sequel/plugins/identity_map.rb +114 -7
  119. data/lib/sequel/plugins/many_through_many.rb +65 -7
  120. data/lib/sequel/plugins/prepared_statements.rb +151 -0
  121. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  122. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  123. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  124. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  125. data/lib/sequel/plugins/serialization.rb +6 -1
  126. data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
  127. data/lib/sequel/plugins/sharding.rb +12 -25
  128. data/lib/sequel/plugins/single_table_inheritance.rb +3 -1
  129. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  130. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  131. data/lib/sequel/plugins/xml_serializer.rb +3 -3
  132. data/lib/sequel/sql.rb +107 -51
  133. data/lib/sequel/timezones.rb +44 -35
  134. data/lib/sequel/version.rb +1 -1
  135. data/spec/adapters/db2_spec.rb +146 -0
  136. data/spec/adapters/mssql_spec.rb +65 -29
  137. data/spec/adapters/mysql_spec.rb +122 -123
  138. data/spec/adapters/oracle_spec.rb +48 -76
  139. data/spec/adapters/postgres_spec.rb +213 -61
  140. data/spec/adapters/spec_helper.rb +6 -5
  141. data/spec/adapters/sqlite_spec.rb +35 -21
  142. data/spec/core/connection_pool_spec.rb +71 -92
  143. data/spec/core/core_sql_spec.rb +20 -31
  144. data/spec/core/database_spec.rb +729 -508
  145. data/spec/core/dataset_spec.rb +980 -1044
  146. data/spec/core/expression_filters_spec.rb +159 -42
  147. data/spec/core/mock_adapter_spec.rb +378 -0
  148. data/spec/core/object_graph_spec.rb +48 -114
  149. data/spec/core/schema_generator_spec.rb +3 -3
  150. data/spec/core/schema_spec.rb +298 -38
  151. data/spec/core/spec_helper.rb +6 -48
  152. data/spec/extensions/association_pks_spec.rb +38 -0
  153. data/spec/extensions/class_table_inheritance_spec.rb +1 -1
  154. data/spec/extensions/columns_introspection_spec.rb +91 -0
  155. data/spec/extensions/dataset_associations_spec.rb +199 -0
  156. data/spec/extensions/defaults_setter_spec.rb +64 -0
  157. data/spec/extensions/identity_map_spec.rb +162 -0
  158. data/spec/extensions/instance_hooks_spec.rb +71 -0
  159. data/spec/extensions/many_through_many_spec.rb +195 -20
  160. data/spec/extensions/migration_spec.rb +17 -17
  161. data/spec/extensions/named_timezones_spec.rb +22 -2
  162. data/spec/extensions/nested_attributes_spec.rb +4 -0
  163. data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
  164. data/spec/extensions/prepared_statements_safe_spec.rb +79 -0
  165. data/spec/extensions/prepared_statements_spec.rb +72 -0
  166. data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
  167. data/spec/extensions/schema_dumper_spec.rb +2 -2
  168. data/spec/extensions/schema_spec.rb +13 -21
  169. data/spec/extensions/serialization_modification_detection_spec.rb +37 -0
  170. data/spec/extensions/serialization_spec.rb +5 -8
  171. data/spec/extensions/single_table_inheritance_spec.rb +11 -0
  172. data/spec/extensions/spec_helper.rb +7 -1
  173. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  174. data/spec/extensions/to_dot_spec.rb +3 -5
  175. data/spec/extensions/typecast_on_load_spec.rb +1 -6
  176. data/spec/extensions/xml_serializer_spec.rb +16 -4
  177. data/spec/integration/associations_test.rb +522 -21
  178. data/spec/integration/database_test.rb +4 -2
  179. data/spec/integration/dataset_test.rb +375 -62
  180. data/spec/integration/eager_loader_test.rb +19 -21
  181. data/spec/integration/model_test.rb +80 -1
  182. data/spec/integration/plugin_test.rb +304 -116
  183. data/spec/integration/prepared_statement_test.rb +200 -120
  184. data/spec/integration/schema_test.rb +161 -30
  185. data/spec/integration/spec_helper.rb +39 -30
  186. data/spec/integration/timezone_test.rb +38 -12
  187. data/spec/integration/transaction_test.rb +172 -5
  188. data/spec/integration/type_test.rb +17 -3
  189. data/spec/model/association_reflection_spec.rb +174 -7
  190. data/spec/model/associations_spec.rb +849 -661
  191. data/spec/model/base_spec.rb +255 -95
  192. data/spec/model/dataset_methods_spec.rb +7 -27
  193. data/spec/model/eager_loading_spec.rb +640 -676
  194. data/spec/model/hooks_spec.rb +309 -67
  195. data/spec/model/model_spec.rb +207 -167
  196. data/spec/model/plugins_spec.rb +24 -13
  197. data/spec/model/record_spec.rb +321 -218
  198. data/spec/model/spec_helper.rb +13 -71
  199. data/spec/model/validations_spec.rb +11 -0
  200. metadata +95 -38
@@ -1,7 +1,7 @@
1
1
  module Sequel
2
2
  class Database
3
3
  # ---------------------
4
- # :section: Methods that execute queries and/or return results
4
+ # :section: 1 - Methods that execute queries and/or return results
5
5
  # This methods generally execute SQL code on the database server.
6
6
  # ---------------------
7
7
 
@@ -37,9 +37,12 @@ module Sequel
37
37
  attr_accessor :transaction_isolation_level
38
38
 
39
39
  # Runs the supplied SQL statement string on the database server.
40
- # Alias for run.
40
+ # Returns self so it can be safely chained:
41
+ #
42
+ # DB << "UPADTE albums SET artist_id = NULL" << "DROP TABLE artists"
41
43
  def <<(sql)
42
44
  run(sql)
45
+ self
43
46
  end
44
47
 
45
48
  # Call the prepared statement with the given name with the given hash
@@ -82,12 +85,12 @@ module Sequel
82
85
  #
83
86
  # DB.get(1) # SELECT 1
84
87
  # # => 1
85
- # DB.get{version{}} # SELECT server_version()
88
+ # DB.get{server_version{}} # SELECT server_version()
86
89
  def get(*args, &block)
87
90
  dataset.get(*args, &block)
88
91
  end
89
92
 
90
- # Return a hash containing index information. Hash keys are index name symbols.
93
+ # Return a hash containing index information for the table. Hash keys are index name symbols.
91
94
  # Values are subhashes with two keys, :columns and :unique. The value of :columns
92
95
  # is an array of symbols of column names. The value of :unique is true or false
93
96
  # depending on if the index is unique.
@@ -110,17 +113,17 @@ module Sequel
110
113
  nil
111
114
  end
112
115
 
113
- # Parse the schema from the database.
114
116
  # Returns the schema for the given table as an array with all members being arrays of length 2,
115
117
  # the first member being the column name, and the second member being a hash of column information.
118
+ # The table argument can also be a dataset, as long as it only has one table.
116
119
  # Available options are:
117
120
  #
118
121
  # :reload :: Ignore any cached results, and get fresh information from the database.
119
122
  # :schema :: An explicit schema to use. It may also be implicitly provided
120
123
  # via the table name.
121
124
  #
122
- # If schema parsing is supported by the database, the column information should at least contain the
123
- # following columns:
125
+ # If schema parsing is supported by the database, the column information should hash at least contain the
126
+ # following entries:
124
127
  #
125
128
  # :allow_null :: Whether NULL is an allowed value for the column.
126
129
  # :db_type :: The database type for the column, as a database specific string.
@@ -129,7 +132,8 @@ module Sequel
129
132
  # it means that primary key information is unavailable, not that the column
130
133
  # is not a primary key.
131
134
  # :ruby_default :: The database default for the column, as a ruby object. In many cases, complex
132
- # database defaults cannot be parsed into ruby objects.
135
+ # database defaults cannot be parsed into ruby objects, in which case nil will be
136
+ # used as the value.
133
137
  # :type :: A symbol specifying the type, such as :integer or :string.
134
138
  #
135
139
  # Example:
@@ -152,9 +156,20 @@ module Sequel
152
156
  def schema(table, opts={})
153
157
  raise(Error, 'schema parsing is not implemented on this database') unless respond_to?(:schema_parse_table, true)
154
158
 
155
- sch, table_name = schema_and_table(table)
156
- quoted_name = quote_schema_table(table)
157
- opts = opts.merge(:schema=>sch) if sch && !opts.include?(:schema)
159
+ opts = opts.dup
160
+ if table.is_a?(Dataset)
161
+ o = table.opts
162
+ from = o[:from]
163
+ raise(Error, "can only parse the schema for a dataset with a single from table") unless from && from.length == 1 && !o.include?(:join) && !o.include?(:sql)
164
+ tab = table.first_source_table
165
+ sch, table_name = schema_and_table(tab)
166
+ quoted_name = table.literal(tab)
167
+ opts[:dataset] = table
168
+ else
169
+ sch, table_name = schema_and_table(table)
170
+ quoted_name = quote_schema_table(table)
171
+ end
172
+ opts[:schema] = sch if sch && !opts.include?(:schema)
158
173
 
159
174
  @schemas.delete(quoted_name) if opts[:reload]
160
175
  return @schemas[quoted_name] if @schemas[quoted_name]
@@ -169,13 +184,14 @@ module Sequel
169
184
  # to the database.
170
185
  #
171
186
  # DB.table_exists?(:foo) # => false
187
+ # # SELECT * FROM foo LIMIT 1
172
188
  def table_exists?(name)
173
- begin
174
- from(name).first
175
- true
176
- rescue
177
- false
178
- end
189
+ sch, table_name = schema_and_table(name)
190
+ name = SQL::QualifiedIdentifier.new(sch, table_name) if sch
191
+ from(name).first
192
+ true
193
+ rescue
194
+ false
179
195
  end
180
196
 
181
197
  # Return all tables in the database as an array of symbols.
@@ -198,6 +214,9 @@ module Sequel
198
214
  # :prepare :: A string to use as the transaction identifier for a
199
215
  # prepared transaction (two-phase commit), if the database/adapter
200
216
  # supports prepared transactions.
217
+ # :rollback :: Can the set to :reraise to reraise any Sequel::Rollback exceptions
218
+ # raised, or :always to always rollback even if no exceptions occur
219
+ # (useful for testing).
201
220
  # :server :: The server to use for the transaction.
202
221
  # :savepoint :: Whether to create a new savepoint for this transaction,
203
222
  # only respected if the database/adapter supports savepoints. By
@@ -210,6 +229,13 @@ module Sequel
210
229
  end
211
230
  end
212
231
 
232
+ # Return all views in the database as an array of symbols.
233
+ #
234
+ # DB.views # => [:gold_albums, :artists_with_many_albums]
235
+ def views(opts={})
236
+ raise NotImplemented, "#views should be overridden by adapters"
237
+ end
238
+
213
239
  private
214
240
 
215
241
  # Internal generic transaction method. Any exception raised by the given
@@ -217,40 +243,65 @@ module Sequel
217
243
  # not a Sequel::Rollback, the error will be reraised. If no exception occurs
218
244
  # inside the block, the transaction is commited.
219
245
  def _transaction(conn, opts={})
246
+ rollback = opts[:rollback]
220
247
  begin
221
- add_transaction
222
- t = begin_transaction(conn, opts)
223
- yield(conn)
248
+ add_transaction(conn, opts)
249
+ begin_transaction(conn, opts)
250
+ if rollback == :always
251
+ begin
252
+ yield(conn)
253
+ rescue Exception => e1
254
+ raise e1
255
+ ensure
256
+ raise ::Sequel::Rollback unless e1
257
+ end
258
+ else
259
+ yield(conn)
260
+ end
224
261
  rescue Exception => e
225
- rollback_transaction(t, opts) if t
226
- transaction_error(e)
262
+ rollback_transaction(conn, opts)
263
+ transaction_error(e, :conn=>conn, :rollback=>rollback)
227
264
  ensure
228
265
  begin
229
- commit_transaction(t, opts) unless e
230
- rescue Exception => e
231
- raise_error(e, :classes=>database_error_classes)
266
+ committed = commit_or_rollback_transaction(e, conn, opts)
267
+ rescue Exception => e2
268
+ raise_error(e2, :classes=>database_error_classes, :conn=>conn)
232
269
  ensure
233
- remove_transaction(t)
270
+ remove_transaction(conn, committed)
234
271
  end
235
272
  end
236
273
  end
237
-
274
+
238
275
  # Add the current thread to the list of active transactions
239
- def add_transaction
240
- th = Thread.current
276
+ def add_transaction(conn, opts)
241
277
  if supports_savepoints?
242
- unless @transactions.include?(th)
243
- th[:sequel_transaction_depth] = 0
244
- @transactions << th
278
+ unless @transactions[conn]
279
+ @transactions[conn] = {:savepoint_level=>0}
280
+ @transactions[conn][:prepare] = opts[:prepare] if supports_prepared_transactions?
245
281
  end
246
282
  else
247
- @transactions << th
283
+ @transactions[conn] = {}
284
+ @transactions[conn][:prepare] = opts[:prepare] if supports_prepared_transactions?
248
285
  end
249
286
  end
250
287
 
288
+ # Call all stored after_commit blocks for the given transaction
289
+ def after_transaction_commit(conn)
290
+ if ary = @transactions[conn][:after_commit]
291
+ ary.each{|b| b.call}
292
+ end
293
+ end
294
+
295
+ # Call all stored after_rollback blocks for the given transaction
296
+ def after_transaction_rollback(conn)
297
+ if ary = @transactions[conn][:after_rollback]
298
+ ary.each{|b| b.call}
299
+ end
300
+ end
301
+
251
302
  # Whether the current thread/connection is already inside a transaction
252
303
  def already_in_transaction?(conn, opts)
253
- @transactions.include?(Thread.current) && (!supports_savepoints? || !opts[:savepoint])
304
+ @transactions.has_key?(conn) && (!supports_savepoints? || !opts[:savepoint])
254
305
  end
255
306
 
256
307
  # SQL to start a new savepoint
@@ -267,17 +318,16 @@ module Sequel
267
318
  # Start a new database transaction or a new savepoint on the given connection.
268
319
  def begin_transaction(conn, opts={})
269
320
  if supports_savepoints?
270
- th = Thread.current
271
- if (depth = th[:sequel_transaction_depth]) > 0
321
+ th = @transactions[conn]
322
+ if (depth = th[:savepoint_level]) > 0
272
323
  log_connection_execute(conn, begin_savepoint_sql(depth))
273
324
  else
274
325
  begin_new_transaction(conn, opts)
275
326
  end
276
- th[:sequel_transaction_depth] += 1
327
+ th[:savepoint_level] += 1
277
328
  else
278
329
  begin_new_transaction(conn, opts)
279
330
  end
280
- conn
281
331
  end
282
332
 
283
333
  # SQL to BEGIN a transaction.
@@ -335,6 +385,38 @@ module Sequel
335
385
  end
336
386
  end
337
387
 
388
+ if (! defined?(RUBY_ENGINE) or RUBY_ENGINE == 'ruby' or RUBY_ENGINE == 'rbx') and RUBY_VERSION < '1.9'
389
+ # Whether to commit the current transaction. On ruby 1.8 and rubinius,
390
+ # Thread.current.status is checked because Thread#kill skips rescue
391
+ # blocks (so exception would be nil), but the transaction should
392
+ # still be rolled back.
393
+ def commit_or_rollback_transaction(exception, conn, opts)
394
+ if exception
395
+ false
396
+ else
397
+ if Thread.current.status == 'aborting'
398
+ rollback_transaction(conn, opts)
399
+ false
400
+ else
401
+ commit_transaction(conn, opts)
402
+ true
403
+ end
404
+ end
405
+ end
406
+ else
407
+ # Whether to commit the current transaction. On ruby 1.9 and JRuby,
408
+ # transactions will be committed if Thread#kill is used on an thread
409
+ # that has a transaction open, and there isn't a work around.
410
+ def commit_or_rollback_transaction(exception, conn, opts)
411
+ if exception
412
+ false
413
+ else
414
+ commit_transaction(conn, opts)
415
+ true
416
+ end
417
+ end
418
+ end
419
+
338
420
  # SQL to commit a savepoint
339
421
  def commit_savepoint_sql(depth)
340
422
  SQL_RELEASE_SAVEPOINT % depth
@@ -343,7 +425,7 @@ module Sequel
343
425
  # Commit the active transaction on the connection
344
426
  def commit_transaction(conn, opts={})
345
427
  if supports_savepoints?
346
- depth = Thread.current[:sequel_transaction_depth]
428
+ depth = @transactions[conn][:savepoint_level]
347
429
  log_connection_execute(conn, depth > 1 ? commit_savepoint_sql(depth-1) : commit_transaction_sql)
348
430
  else
349
431
  log_connection_execute(conn, commit_transaction_sql)
@@ -364,8 +446,8 @@ module Sequel
364
446
  # Return a Method object for the dataset's output_identifier_method.
365
447
  # Used in metadata parsing to make sure the returned information is in the
366
448
  # correct format.
367
- def input_identifier_meth
368
- dataset.method(:input_identifier)
449
+ def input_identifier_meth(ds=nil)
450
+ (ds || dataset).method(:input_identifier)
369
451
  end
370
452
 
371
453
  # Return a dataset that uses the default identifier input and output methods
@@ -382,24 +464,28 @@ module Sequel
382
464
  # Return a Method object for the dataset's output_identifier_method.
383
465
  # Used in metadata parsing to make sure the returned information is in the
384
466
  # correct format.
385
- def output_identifier_meth
386
- dataset.method(:output_identifier)
467
+ def output_identifier_meth(ds=nil)
468
+ (ds || dataset).method(:output_identifier)
387
469
  end
388
470
 
389
- # SQL to ROLLBACK a transaction.
390
- def rollback_transaction_sql
391
- SQL_ROLLBACK
392
- end
393
-
394
471
  # Remove the cached schema for the given schema name
395
472
  def remove_cached_schema(table)
396
473
  @schemas.delete(quote_schema_table(table)) if @schemas
397
474
  end
398
475
 
399
476
  # Remove the current thread from the list of active transactions
400
- def remove_transaction(conn)
401
- th = Thread.current
402
- @transactions.delete(th) if !supports_savepoints? || ((th[:sequel_transaction_depth] -= 1) <= 0)
477
+ def remove_transaction(conn, committed)
478
+ if !supports_savepoints? || ((@transactions[conn][:savepoint_level] -= 1) <= 0)
479
+ begin
480
+ if committed
481
+ after_transaction_commit(conn)
482
+ else
483
+ after_transaction_rollback(conn)
484
+ end
485
+ ensure
486
+ @transactions.delete(conn)
487
+ end
488
+ end
403
489
  end
404
490
 
405
491
  # SQL to rollback to a savepoint
@@ -410,13 +496,18 @@ module Sequel
410
496
  # Rollback the active transaction on the connection
411
497
  def rollback_transaction(conn, opts={})
412
498
  if supports_savepoints?
413
- depth = Thread.current[:sequel_transaction_depth]
499
+ depth = @transactions[conn][:savepoint_level]
414
500
  log_connection_execute(conn, depth > 1 ? rollback_savepoint_sql(depth-1) : rollback_transaction_sql)
415
501
  else
416
502
  log_connection_execute(conn, rollback_transaction_sql)
417
503
  end
418
504
  end
419
505
 
506
+ # SQL to ROLLBACK a transaction.
507
+ def rollback_transaction_sql
508
+ SQL_ROLLBACK
509
+ end
510
+
420
511
  # Match the database's column type to a ruby type via a
421
512
  # regular expression, and return the ruby type as a symbol
422
513
  # such as :integer or :string.
@@ -434,13 +525,13 @@ module Sequel
434
525
  :datetime
435
526
  when /\Atime( with(out)? time zone)?\z/io
436
527
  :time
437
- when /\A(boolean|bit)\z/io
528
+ when /\A(bool(ean)?)\z/io
438
529
  :boolean
439
530
  when /\A(real|float|double( precision)?)\z/io
440
531
  :float
441
- when /\A(?:(?:(?:num(?:ber|eric)?|decimal)(?:\(\d+,\s*(\d+)\))?)|(?:small)?money)\z/io
442
- $1 && $1 == '0' ? :integer : :decimal
443
- when /bytea|blob|image|(var)?binary/io
532
+ when /\A(?:(?:(?:num(?:ber|eric)?|decimal)(?:\(\d+,\s*(\d+|false|true)\))?)|(?:small)?money)\z/io
533
+ $1 && ['0', 'false'].include?($1) ? :integer : :decimal
534
+ when /bytea|[bc]lob|image|(var)?binary/io
444
535
  :blob
445
536
  when /\Aenum/io
446
537
  :enum
@@ -460,8 +551,12 @@ module Sequel
460
551
  end
461
552
 
462
553
  # Raise a database error unless the exception is an Rollback.
463
- def transaction_error(e)
464
- raise_error(e, :classes=>database_error_classes) unless e.is_a?(Rollback)
554
+ def transaction_error(e, opts={})
555
+ if e.is_a?(Rollback)
556
+ raise e if opts[:rollback] == :reraise
557
+ else
558
+ raise_error(e, opts.merge(:classes=>database_error_classes))
559
+ end
465
560
  end
466
561
  end
467
562
  end
@@ -86,7 +86,8 @@ module Sequel
86
86
  # :index :: Create an index on this column.
87
87
  # :key :: For foreign key columns, the column in the associated table
88
88
  # that this column references. Unnecessary if this column
89
- # references the primary key of the associated table.
89
+ # references the primary key of the associated table, except if you are
90
+ # using MySQL.
90
91
  # :null :: Mark the column as allowing NULL values (if true),
91
92
  # or not allowing NULL values (if false). If unspecified, will default
92
93
  # to whatever the database default is.
@@ -329,15 +330,17 @@ module Sequel
329
330
  # Remove a column from the DDL for the table.
330
331
  #
331
332
  # drop_column(:artist_id) # DROP COLUMN artist_id
332
- def drop_column(name)
333
- @operations << {:op => :drop_column, :name => name}
333
+ # drop_column(:artist_id, :cascade=>true) # DROP COLUMN artist_id CASCADE
334
+ def drop_column(name, opts={})
335
+ @operations << {:op => :drop_column, :name => name}.merge(opts)
334
336
  end
335
337
 
336
338
  # Remove a constraint from the DDL for the table.
337
339
  #
338
340
  # drop_constraint(:unique_name) # DROP CONSTRAINT unique_name
339
- def drop_constraint(name)
340
- @operations << {:op => :drop_constraint, :name => name}
341
+ # drop_constraint(:unique_name, :cascade=>true) # DROP CONSTRAINT unique_name CASCADE
342
+ def drop_constraint(name, opts={})
343
+ @operations << {:op => :drop_constraint, :name => name}.merge(opts)
341
344
  end
342
345
 
343
346
  # Remove an index from the DDL for the table.
@@ -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,17 +65,17 @@ 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
- alter_table_sql_list(name, generator.operations).flatten.each {|sql| execute_ddl(sql)}
75
74
  remove_cached_schema(name)
75
+ apply_alter_table(name, generator.operations)
76
76
  nil
77
77
  end
78
-
78
+
79
79
  # Creates a table with the columns given in the provided block:
80
80
  #
81
81
  # DB.create_table :posts do
@@ -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,13 +224,18 @@ 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
216
231
 
217
232
  private
218
233
 
234
+ # Apply the changes in the given alter table ops to the table given by name.
235
+ def apply_alter_table(name, ops)
236
+ alter_table_sql_list(name, ops).flatten.each{|sql| execute_ddl(sql)}
237
+ end
238
+
219
239
  # The SQL to execute to modify the DDL for the given table name. op
220
240
  # should be one of the operations returned by the AlterTableGenerator.
221
241
  def alter_table_sql(table, op)
@@ -224,7 +244,7 @@ module Sequel
224
244
  when :add_column
225
245
  "ADD COLUMN #{column_definition_sql(op)}"
226
246
  when :drop_column
227
- "DROP COLUMN #{quoted_name}"
247
+ "DROP COLUMN #{quoted_name}#{' CASCADE' if op[:cascade]}"
228
248
  when :rename_column
229
249
  "RENAME COLUMN #{quoted_name} TO #{quote_identifier(op[:new_name])}"
230
250
  when :set_column_type
@@ -240,7 +260,7 @@ module Sequel
240
260
  when :add_constraint
241
261
  "ADD #{constraint_definition_sql(op)}"
242
262
  when :drop_constraint
243
- "DROP CONSTRAINT #{quoted_name}"
263
+ "DROP CONSTRAINT #{quoted_name}#{' CASCADE' if op[:cascade]}"
244
264
  else
245
265
  raise Error, "Unsupported ALTER TABLE operation"
246
266
  end
@@ -275,7 +295,12 @@ module Sequel
275
295
  def column_definition_auto_increment_sql(sql, column)
276
296
  sql << " #{auto_increment_sql}" if column[:auto_increment]
277
297
  end
278
-
298
+
299
+ # Add collate SQL fragment to column creation SQL.
300
+ def column_definition_collate_sql(sql, column)
301
+ sql << " COLLATE #{column[:collate]}" if column[:collate]
302
+ end
303
+
279
304
  # Add default SQL fragment to column creation SQL.
280
305
  def column_definition_default_sql(sql, column)
281
306
  sql << " DEFAULT #{literal(column[:default])}" if column.include?(:default)
@@ -354,7 +379,7 @@ module Sequel
354
379
 
355
380
  # Execute the create index statements using the generator.
356
381
  def create_table_indexes_from_generator(name, generator, options)
357
- e = options[:ignore_index_errors]
382
+ e = options[:ignore_index_errors] || options[:if_not_exists]
358
383
  generator.indexes.each do |index|
359
384
  begin
360
385
  index_sql_list(name, [index]).each{|sql| execute_ddl(sql)}
@@ -366,7 +391,7 @@ module Sequel
366
391
 
367
392
  # DDL statement for creating a table with the given name, columns, and options
368
393
  def create_table_sql(name, generator, options)
369
- "CREATE #{temporary_table_sql if options[:temp]}TABLE #{quote_schema_table(name)} (#{column_list_sql(generator)})"
394
+ "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
395
  end
371
396
 
372
397
  # Default index name for the table and columns, may be too long
@@ -382,10 +407,15 @@ module Sequel
382
407
  end
383
408
 
384
409
  # SQL DDL statement to drop the table with the given name.
385
- def drop_table_sql(name)
386
- "DROP TABLE #{quote_schema_table(name)}"
410
+ def drop_table_sql(name, options)
411
+ "DROP TABLE #{quote_schema_table(name)}#{' CASCADE' if options[:cascade]}"
387
412
  end
388
413
 
414
+ # SQL DDL statement to drop a view with the given name.
415
+ def drop_view_sql(name, options)
416
+ "DROP VIEW #{quote_schema_table(name)}#{' CASCADE' if options[:cascade]}"
417
+ end
418
+
389
419
  # Proxy the filter_expr call to the dataset, used for creating constraints.
390
420
  def filter_expr(*args, &block)
391
421
  schema_utility_dataset.literal(schema_utility_dataset.send(:filter_expr, *args, &block))