sequel 3.38.0 → 3.39.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 (79) hide show
  1. data/CHANGELOG +62 -0
  2. data/README.rdoc +2 -2
  3. data/bin/sequel +12 -2
  4. data/doc/advanced_associations.rdoc +1 -1
  5. data/doc/association_basics.rdoc +13 -0
  6. data/doc/release_notes/3.39.0.txt +237 -0
  7. data/doc/schema_modification.rdoc +4 -4
  8. data/lib/sequel/adapters/jdbc/derby.rb +1 -0
  9. data/lib/sequel/adapters/mock.rb +5 -0
  10. data/lib/sequel/adapters/mysql.rb +8 -1
  11. data/lib/sequel/adapters/mysql2.rb +10 -3
  12. data/lib/sequel/adapters/postgres.rb +72 -8
  13. data/lib/sequel/adapters/shared/db2.rb +1 -0
  14. data/lib/sequel/adapters/shared/mssql.rb +57 -0
  15. data/lib/sequel/adapters/shared/mysql.rb +95 -19
  16. data/lib/sequel/adapters/shared/oracle.rb +14 -0
  17. data/lib/sequel/adapters/shared/postgres.rb +63 -24
  18. data/lib/sequel/adapters/shared/sqlite.rb +6 -9
  19. data/lib/sequel/connection_pool/sharded_threaded.rb +8 -3
  20. data/lib/sequel/connection_pool/threaded.rb +9 -4
  21. data/lib/sequel/database/query.rb +60 -48
  22. data/lib/sequel/database/schema_generator.rb +13 -6
  23. data/lib/sequel/database/schema_methods.rb +65 -12
  24. data/lib/sequel/dataset/actions.rb +22 -4
  25. data/lib/sequel/dataset/features.rb +5 -0
  26. data/lib/sequel/dataset/graph.rb +2 -3
  27. data/lib/sequel/dataset/misc.rb +2 -2
  28. data/lib/sequel/dataset/query.rb +0 -2
  29. data/lib/sequel/dataset/sql.rb +33 -12
  30. data/lib/sequel/extensions/constraint_validations.rb +451 -0
  31. data/lib/sequel/extensions/eval_inspect.rb +17 -2
  32. data/lib/sequel/extensions/pg_array_ops.rb +15 -5
  33. data/lib/sequel/extensions/pg_interval.rb +2 -2
  34. data/lib/sequel/extensions/pg_row_ops.rb +18 -0
  35. data/lib/sequel/extensions/schema_dumper.rb +3 -11
  36. data/lib/sequel/model/associations.rb +3 -2
  37. data/lib/sequel/model/base.rb +57 -13
  38. data/lib/sequel/model/exceptions.rb +20 -2
  39. data/lib/sequel/plugins/constraint_validations.rb +198 -0
  40. data/lib/sequel/plugins/defaults_setter.rb +15 -1
  41. data/lib/sequel/plugins/dirty.rb +2 -2
  42. data/lib/sequel/plugins/identity_map.rb +12 -8
  43. data/lib/sequel/plugins/subclasses.rb +19 -1
  44. data/lib/sequel/plugins/tree.rb +3 -3
  45. data/lib/sequel/plugins/validation_helpers.rb +24 -4
  46. data/lib/sequel/sql.rb +64 -24
  47. data/lib/sequel/timezones.rb +10 -2
  48. data/lib/sequel/version.rb +1 -1
  49. data/spec/adapters/mssql_spec.rb +26 -25
  50. data/spec/adapters/mysql_spec.rb +57 -23
  51. data/spec/adapters/oracle_spec.rb +34 -49
  52. data/spec/adapters/postgres_spec.rb +226 -128
  53. data/spec/adapters/sqlite_spec.rb +50 -49
  54. data/spec/core/connection_pool_spec.rb +22 -0
  55. data/spec/core/database_spec.rb +53 -47
  56. data/spec/core/dataset_spec.rb +36 -32
  57. data/spec/core/expression_filters_spec.rb +14 -2
  58. data/spec/core/mock_adapter_spec.rb +4 -0
  59. data/spec/core/object_graph_spec.rb +0 -13
  60. data/spec/core/schema_spec.rb +64 -5
  61. data/spec/core_extensions_spec.rb +1 -0
  62. data/spec/extensions/constraint_validations_plugin_spec.rb +196 -0
  63. data/spec/extensions/constraint_validations_spec.rb +316 -0
  64. data/spec/extensions/defaults_setter_spec.rb +24 -0
  65. data/spec/extensions/eval_inspect_spec.rb +9 -0
  66. data/spec/extensions/identity_map_spec.rb +11 -2
  67. data/spec/extensions/pg_array_ops_spec.rb +9 -0
  68. data/spec/extensions/pg_row_ops_spec.rb +11 -1
  69. data/spec/extensions/pg_row_plugin_spec.rb +4 -0
  70. data/spec/extensions/schema_dumper_spec.rb +8 -5
  71. data/spec/extensions/subclasses_spec.rb +14 -0
  72. data/spec/extensions/validation_helpers_spec.rb +15 -2
  73. data/spec/integration/dataset_test.rb +75 -1
  74. data/spec/integration/plugin_test.rb +146 -0
  75. data/spec/integration/schema_test.rb +34 -0
  76. data/spec/model/dataset_methods_spec.rb +38 -0
  77. data/spec/model/hooks_spec.rb +6 -0
  78. data/spec/model/validations_spec.rb +27 -2
  79. metadata +8 -2
@@ -300,7 +300,7 @@ module Sequel
300
300
  # Pretend tinyint is another integer type if its length is not 1, to
301
301
  # avoid casting to boolean if Sequel::MySQL.convert_tinyint_to_bool
302
302
  # is set.
303
- type_proc = f.type == 1 && f.length != 1 ? cps[2] : cps[f.type]
303
+ type_proc = f.type == 1 && cast_tinyint_integer?(f) ? cps[2] : cps[f.type]
304
304
  [output_identifier(f.name), type_proc, i+=1]
305
305
  end
306
306
  @columns = cols.map{|c| c.first}
@@ -338,6 +338,13 @@ module Sequel
338
338
  end
339
339
 
340
340
  private
341
+
342
+ # Whether a tinyint field should be casted as an integer. By default,
343
+ # casts to integer if the field length is not 1. Can be overwritten
344
+ # to make tinyint casting dataset dependent.
345
+ def cast_tinyint_integer?(field)
346
+ field.length != 1
347
+ end
341
348
 
342
349
  # Set the :type option to :select if it hasn't been set.
343
350
  def execute(sql, opts={}, &block)
@@ -87,7 +87,7 @@ module Sequel
87
87
  # yield the connection if a block is given.
88
88
  def _execute(conn, sql, opts)
89
89
  begin
90
- r = log_yield((log_sql = opts[:log_sql]) ? sql + log_sql : sql){conn.query(sql, :database_timezone => timezone, :application_timezone => Sequel.application_timezone, :cast_booleans => convert_tinyint_to_bool)}
90
+ r = log_yield((log_sql = opts[:log_sql]) ? sql + log_sql : sql){conn.query(sql, :database_timezone => timezone, :application_timezone => Sequel.application_timezone)}
91
91
  if opts[:type] == :select
92
92
  yield r if r
93
93
  elsif block_given?
@@ -150,7 +150,7 @@ module Sequel
150
150
  cols = r.fields
151
151
  @columns = cols2 = cols.map{|c| output_identifier(c.to_s)}
152
152
  cs = cols.zip(cols2)
153
- r.each do |row|
153
+ r.each(:cast_booleans=>convert_tinyint_to_bool?) do |row|
154
154
  h = {}
155
155
  cs.each do |a, b|
156
156
  h[b] = row[a]
@@ -159,7 +159,7 @@ module Sequel
159
159
  end
160
160
  else
161
161
  @columns = r.fields
162
- r.each{|h| yield h}
162
+ r.each(:cast_booleans=>convert_tinyint_to_bool?){|h| yield h}
163
163
  end
164
164
  end
165
165
  self
@@ -167,6 +167,13 @@ module Sequel
167
167
 
168
168
  private
169
169
 
170
+ # Whether to cast tinyint(1) columns to integer instead of boolean.
171
+ # By default, uses the opposite of the database's convert_tinyint_to_bool
172
+ # setting. Exists for compatibility with the mysql adapter.
173
+ def convert_tinyint_to_bool?
174
+ @db.convert_tinyint_to_bool
175
+ end
176
+
170
177
  # Set the :type option to :select if it hasn't been set.
171
178
  def execute(sql, opts={}, &block)
172
179
  super(sql, {:type=>:select}.merge(opts), &block)
@@ -133,7 +133,7 @@ module Sequel
133
133
  begin
134
134
  block_given? ? yield(q) : q.cmd_tuples
135
135
  ensure
136
- q.clear if q
136
+ q.clear if q && q.respond_to?(:clear)
137
137
  end
138
138
  end
139
139
 
@@ -235,13 +235,11 @@ module Sequel
235
235
  end
236
236
 
237
237
  if SEQUEL_POSTGRES_USES_PG
238
- # +copy_table+ uses PostgreSQL's +COPY+ SQL statement to return formatted
238
+ # +copy_table+ uses PostgreSQL's +COPY TO STDOUT+ SQL statement to return formatted
239
239
  # results directly to the caller. This method is only supported if pg is the
240
240
  # underlying ruby driver. This method should only be called if you want
241
- # results returned to the client. If you are using +COPY FROM+ or +COPY TO+
242
- # with a filename, you should just use +run+ instead of this method. This
243
- # method does not currently support +COPY FROM STDIN+, but that may be supported
244
- # in the future.
241
+ # results returned to the client. If you are using +COPY TO+
242
+ # with a filename, you should just use +run+ instead of this method.
245
243
  #
246
244
  # The table argument supports the following types:
247
245
  #
@@ -297,6 +295,67 @@ module Sequel
297
295
  end
298
296
  end
299
297
 
298
+ # +copy_into+ uses PostgreSQL's +COPY FROM STDIN+ SQL statement to do very fast inserts
299
+ # into a table using input preformatting in either CSV or PostgreSQL text format.
300
+ # This method is only supported if pg is the underlying ruby driver. This method should only be called if you want
301
+ # results returned to the client. If you are using +COPY FROM+
302
+ # with a filename, you should just use +run+ instead of this method.
303
+ #
304
+ # The following options are respected:
305
+ #
306
+ # :columns :: The columns to insert into, with the same order as the columns in the
307
+ # input data. If this isn't given, uses all columns in the table.
308
+ # :data :: The data to copy to PostgreSQL, which should already be in CSV or PostgreSQL
309
+ # text format. This can be either a string, or any object that responds to
310
+ # each and yields string.
311
+ # :format :: The format to use. text is the default, so this should be :csv or :binary.
312
+ # :options :: An options SQL string to use, which should contain comma separated options.
313
+ # :server :: The server on which to run the query.
314
+ #
315
+ # If a block is provided and :data option is not, this will yield to the block repeatedly.
316
+ # The block should return a string, or nil to signal that it is finished.
317
+ def copy_into(table, opts={})
318
+ sql = "COPY #{literal(table)}"
319
+ if cols = opts[:columns]
320
+ sql << literal(Array(cols))
321
+ end
322
+ sql << " FROM STDIN"
323
+ if opts[:options] || opts[:format]
324
+ sql << " ("
325
+ sql << "FORMAT #{opts[:format]}" if opts[:format]
326
+ sql << "#{', ' if opts[:format]}#{opts[:options]}" if opts[:options]
327
+ sql << ')'
328
+ end
329
+
330
+ data = opts[:data]
331
+ data = Array(data) if data.is_a?(String)
332
+
333
+ if block_given? && data
334
+ raise Error, "Cannot provide both a :data option and a block to copy_into"
335
+ elsif !block_given? && !data
336
+ raise Error, "Must provide either a :data option or a block to copy_into"
337
+ end
338
+
339
+ synchronize(opts[:server]) do |conn|
340
+ conn.execute(sql)
341
+ begin
342
+ if block_given?
343
+ while buf = yield
344
+ conn.put_copy_data(buf)
345
+ end
346
+ else
347
+ data.each{|buf| conn.put_copy_data(buf)}
348
+ end
349
+ rescue Exception => e
350
+ conn.put_copy_end("ruby exception occurred while copying data into PostgreSQL")
351
+ raise
352
+ ensure
353
+ conn.put_copy_end unless e
354
+ conn.get_result
355
+ end
356
+ end
357
+ end
358
+
300
359
  # Listens on the given channel (or multiple channels if channel is an array), waiting for notifications.
301
360
  # After a notification is received, or the timeout has passed, stops listening to the channel. Options:
302
361
  #
@@ -373,6 +432,11 @@ module Sequel
373
432
  end
374
433
  end
375
434
 
435
+ # Execute the prepared statement name with the given arguments on the connection.
436
+ def _execute_prepared_statement(conn, ps_name, args, opts)
437
+ conn.exec_prepared(ps_name, args)
438
+ end
439
+
376
440
  # Convert exceptions raised from the block into DatabaseErrors.
377
441
  def check_database_errors
378
442
  begin
@@ -426,11 +490,11 @@ module Sequel
426
490
  log_sql << ")"
427
491
  end
428
492
 
429
- q = conn.check_disconnect_errors{log_yield(log_sql, args){conn.exec_prepared(ps_name, args)}}
493
+ q = conn.check_disconnect_errors{log_yield(log_sql, args){_execute_prepared_statement(conn, ps_name, args, opts)}}
430
494
  begin
431
495
  block_given? ? yield(q) : q.cmd_tuples
432
496
  ensure
433
- q.clear
497
+ q.clear if q && q.respond_to?(:clear)
434
498
  end
435
499
  end
436
500
 
@@ -197,6 +197,7 @@ module Sequel
197
197
  PAREN_CLOSE = Dataset::PAREN_CLOSE
198
198
  PAREN_OPEN = Dataset::PAREN_OPEN
199
199
  BITWISE_METHOD_MAP = {:& =>:BITAND, :| => :BITOR, :^ => :BITXOR, :'B~'=>:BITNOT}
200
+ EMULATED_FUNCTION_MAP = {:char_length=>'length'.freeze}
200
201
  BOOL_TRUE = '1'.freeze
201
202
  BOOL_FALSE = '0'.freeze
202
203
  CAST_STRING_OPEN = "RTRIM(CHAR(".freeze
@@ -12,6 +12,7 @@ module Sequel
12
12
  SQL_ROLLBACK = "IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION".freeze
13
13
  SQL_ROLLBACK_TO_SAVEPOINT = 'IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION autopoint_%d'.freeze
14
14
  SQL_SAVEPOINT = 'SAVE TRANSACTION autopoint_%d'.freeze
15
+ MSSQL_DEFAULT_RE = /\A(?:\(N?('.*')\)|\(\((-?\d+(?:\.\d+)?)\)\))\z/
15
16
 
16
17
  # Whether to use N'' to quote strings, which allows unicode characters inside the
17
18
  # strings. True by default for compatibility, can be set to false for a possible
@@ -109,6 +110,39 @@ module Sequel
109
110
  AUTO_INCREMENT
110
111
  end
111
112
 
113
+ # Preprocess the array of operations. If it looks like some operations depend
114
+ # on results of earlier operations and may require reloading the schema to
115
+ # work correctly, split those operations into separate lists, and between each
116
+ # list, remove the cached schema so that the later operations deal with the
117
+ # then current table schema.
118
+ def apply_alter_table(name, ops)
119
+ modified_columns = []
120
+ op_groups = [[]]
121
+ ops.each do |op|
122
+ case op[:op]
123
+ when :add_column, :set_column_type, :set_column_null
124
+ if modified_columns.include?(op[:name])
125
+ op_groups << []
126
+ else
127
+ modified_columns << op[:name]
128
+ end
129
+ when :rename_column
130
+ if modified_columns.include?(op[:name]) || modified_columns.include?(op[:new_name])
131
+ op_groups << []
132
+ end
133
+ modified_columns << op[:name] unless modified_columns.include?(op[:name])
134
+ modified_columns << op[:new_name] unless modified_columns.include?(op[:new_name])
135
+ end
136
+ op_groups.last << op
137
+ end
138
+
139
+ op_groups.each do |ops|
140
+ next if ops.empty?
141
+ alter_table_sql_list(name, ops).each{|sql| execute_ddl(sql)}
142
+ remove_cached_schema(name)
143
+ end
144
+ end
145
+
112
146
  # MSSQL specific syntax for altering tables.
113
147
  def alter_table_sql(table, op)
114
148
  case op[:op]
@@ -158,6 +192,14 @@ module Sequel
158
192
  SQL_BEGIN
159
193
  end
160
194
 
195
+ # Handle MSSQL specific default format.
196
+ def column_schema_normalize_default(default, type)
197
+ if m = MSSQL_DEFAULT_RE.match(default)
198
+ default = m[1] || m[2]
199
+ end
200
+ super(default, type)
201
+ end
202
+
161
203
  # Commit the active transaction on the connection, does not commit/release
162
204
  # savepoints.
163
205
  def commit_transaction(conn, opts={})
@@ -421,6 +463,21 @@ module Sequel
421
463
  mutation_method(:disable_insert_output)
422
464
  end
423
465
 
466
+ # There is no function on Microsoft SQL Server that does character length
467
+ # and respects trailing spaces (datalength respects trailing spaces, but
468
+ # counts bytes instead of characters). Use a hack to work around the
469
+ # trailing spaces issue.
470
+ def emulated_function_sql_append(sql, f)
471
+ case f.f
472
+ when :char_length
473
+ literal_append(sql, SQL::Function.new(:len, Sequel.join([f.args.first, 'x'])) - 1)
474
+ when :trim
475
+ literal_append(sql, SQL::Function.new(:ltrim, SQL::Function.new(:rtrim, f.args.first)))
476
+ else
477
+ super
478
+ end
479
+ end
480
+
424
481
  # MSSQL uses the CONTAINS keyword for full text search
425
482
  def full_text_search(cols, terms, opts = {})
426
483
  terms = "\"#{terms.join('" OR "')}\"" if terms.is_a?(Array)
@@ -33,6 +33,7 @@ module Sequel
33
33
  CAST_TYPES = {String=>:CHAR, Integer=>:SIGNED, Time=>:DATETIME, DateTime=>:DATETIME, Numeric=>:DECIMAL, BigDecimal=>:DECIMAL, File=>:BINARY}
34
34
  COLUMN_DEFINITION_ORDER = [:collate, :null, :default, :unique, :primary_key, :auto_increment, :references]
35
35
  PRIMARY = 'PRIMARY'.freeze
36
+ MYSQL_TIMESTAMP_RE = /\ACURRENT_(?:DATE|TIMESTAMP)?\z/
36
37
 
37
38
  # MySQL's cast rules are restrictive in that you can't just cast to any possible
38
39
  # database type.
@@ -110,8 +111,10 @@ module Sequel
110
111
 
111
112
  # Get version of MySQL server, used for determined capabilities.
112
113
  def server_version
113
- m = /(\d+)\.(\d+)\.(\d+)/.match(get(SQL::Function.new(:version)))
114
- @server_version ||= (m[1].to_i * 10000) + (m[2].to_i * 100) + m[3].to_i
114
+ @server_version ||= begin
115
+ m = /(\d+)\.(\d+)\.(\d+)/.match(get(SQL::Function.new(:version)))
116
+ (m[1].to_i * 10000) + (m[2].to_i * 100) + m[3].to_i
117
+ end
115
118
  end
116
119
 
117
120
  # MySQL supports CREATE TABLE IF NOT EXISTS syntax.
@@ -167,18 +170,50 @@ module Sequel
167
170
 
168
171
  private
169
172
 
170
- # Use MySQL specific syntax for rename column, set column type, and
171
- # drop index cases.
172
- def alter_table_sql(table, op)
173
+ # Preprocess the array of operations. If it looks like some operations depend
174
+ # on results of earlier operations and may require reloading the schema to
175
+ # work correctly, split those operations into separate lists, and between each
176
+ # list, remove the cached schema so that the later operations deal with the
177
+ # then current table schema.
178
+ def apply_alter_table(name, ops)
179
+ modified_columns = []
180
+ op_groups = [[]]
181
+ ops.each do |op|
182
+ case op[:op]
183
+ when :add_column, :set_column_type, :set_column_null, :set_column_default
184
+ if modified_columns.include?(op[:name])
185
+ op_groups << []
186
+ else
187
+ modified_columns << op[:name]
188
+ end
189
+ when :rename_column
190
+ if modified_columns.include?(op[:name]) || modified_columns.include?(op[:new_name])
191
+ op_groups << []
192
+ end
193
+ modified_columns << op[:name] unless modified_columns.include?(op[:name])
194
+ modified_columns << op[:new_name] unless modified_columns.include?(op[:new_name])
195
+ end
196
+ op_groups.last << op
197
+ end
198
+
199
+ op_groups.each do |ops|
200
+ next if ops.empty?
201
+ alter_table_sql_list(name, ops).each{|sql| execute_ddl(sql)}
202
+ remove_cached_schema(name)
203
+ end
204
+ end
205
+
206
+ # Use MySQL specific syntax for some alter table operations.
207
+ def alter_table_op_sql(table, op)
173
208
  case op[:op]
174
209
  when :add_column
175
210
  if related = op.delete(:table)
176
- sql = super(table, op)
211
+ sql = super
177
212
  op[:table] = related
178
213
  op[:key] ||= primary_key_from_schema(related)
179
- [sql, "ALTER TABLE #{quote_schema_table(table)} ADD FOREIGN KEY (#{quote_identifier(op[:name])})#{column_references_sql(op)}"]
214
+ sql << ", ADD FOREIGN KEY (#{quote_identifier(op[:name])})#{column_references_sql(op)}"
180
215
  else
181
- super(table, op)
216
+ super
182
217
  end
183
218
  when :rename_column, :set_column_type, :set_column_null, :set_column_default
184
219
  o = op[:op]
@@ -189,31 +224,52 @@ module Sequel
189
224
  opts[:null] = o == :set_column_null ? op[:null] : opts[:allow_null]
190
225
  opts[:default] = o == :set_column_default ? op[:default] : opts[:ruby_default]
191
226
  opts.delete(:default) if opts[:default] == nil
192
- "ALTER TABLE #{quote_schema_table(table)} CHANGE COLUMN #{quote_identifier(op[:name])} #{column_definition_sql(op.merge(opts))}"
193
- when :drop_index
194
- "#{drop_index_sql(table, op)} ON #{quote_schema_table(table)}"
227
+ "CHANGE COLUMN #{quote_identifier(op[:name])} #{column_definition_sql(op.merge(opts))}"
195
228
  when :drop_constraint
196
229
  type = case op[:type]
197
230
  when :primary_key
198
- return "ALTER TABLE #{quote_schema_table(table)} DROP PRIMARY KEY"
231
+ "DROP PRIMARY KEY"
199
232
  when :foreign_key
200
- 'FOREIGN KEY'
233
+ "DROP FOREIGN KEY #{quote_identifier(op[:name])}"
201
234
  when :unique
202
- 'INDEX'
203
- else
204
- raise(Error, "must specify constraint type via :type=>(:foreign_key|:primary_key|:unique) when dropping constraints on MySQL")
235
+ "DROP INDEX #{quote_identifier(op[:name])}"
205
236
  end
206
- "ALTER TABLE #{quote_schema_table(table)} DROP #{type} #{quote_identifier(op[:name])}"
207
237
  when :add_constraint
208
238
  if op[:type] == :foreign_key
209
239
  op[:key] ||= primary_key_from_schema(op[:table])
210
240
  end
211
- super(table, op)
241
+ super
212
242
  else
213
- super(table, op)
243
+ super
214
244
  end
215
245
  end
216
246
 
247
+ # MySQL server requires table names when dropping indexes.
248
+ def alter_table_sql(table, op)
249
+ case op[:op]
250
+ when :drop_index
251
+ "#{drop_index_sql(table, op)} ON #{quote_schema_table(table)}"
252
+ else
253
+ super
254
+ end
255
+ end
256
+
257
+ # Handle MySQL specific default format.
258
+ def column_schema_normalize_default(default, type)
259
+ if column_schema_default_string_type?(type)
260
+ return if [:date, :datetime, :time].include?(type) && MYSQL_TIMESTAMP_RE.match(default)
261
+ default = "'#{default.gsub("'", "''").gsub('\\', '\\\\')}'"
262
+ end
263
+ super(default, type)
264
+ end
265
+
266
+ # Don't allow combining adding foreign key operations with other
267
+ # operations, since in some cases adding a foreign key constraint in
268
+ # the same query as other operations results in MySQL error 150.
269
+ def combinable_alter_table_op?(op)
270
+ super && !(op[:op] == :add_constraint && op[:type] == :foreign_key)
271
+ end
272
+
217
273
  # The SQL queries to execute on initial connection
218
274
  def mysql_connection_setting_sqls
219
275
  sqls = []
@@ -369,6 +425,16 @@ module Sequel
369
425
  end
370
426
  end
371
427
 
428
+ # Recognize MySQL set type.
429
+ def schema_column_type(db_type)
430
+ case db_type
431
+ when /\Aset/io
432
+ :set
433
+ else
434
+ super
435
+ end
436
+ end
437
+
372
438
  # Use the MySQL specific DESCRIBE syntax to get a table description.
373
439
  def schema_parse_table(table_name, opts)
374
440
  m = output_identifier_meth(opts[:dataset])
@@ -387,6 +453,11 @@ module Sequel
387
453
  end
388
454
  end
389
455
 
456
+ # MySQL can combine multiple alter table ops into a single query.
457
+ def supports_combining_alter_table_ops?
458
+ true
459
+ end
460
+
390
461
  # Respect the :size option if given to produce
391
462
  # tinyblob, mediumblob, and longblob if :tiny,
392
463
  # :medium, or :long is given.
@@ -667,6 +738,11 @@ module Sequel
667
738
  false
668
739
  end
669
740
 
741
+ # MySQL supports pattern matching via regular expressions
742
+ def supports_regexp?
743
+ true
744
+ end
745
+
670
746
  # MySQL does support fractional timestamps in literal timestamps, but it
671
747
  # ignores them. Also, using them seems to cause problems on 1.9. Since
672
748
  # they are ignored anyway, not using them is probably best.