sequel 3.38.0 → 3.39.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +62 -0
- data/README.rdoc +2 -2
- data/bin/sequel +12 -2
- data/doc/advanced_associations.rdoc +1 -1
- data/doc/association_basics.rdoc +13 -0
- data/doc/release_notes/3.39.0.txt +237 -0
- data/doc/schema_modification.rdoc +4 -4
- data/lib/sequel/adapters/jdbc/derby.rb +1 -0
- data/lib/sequel/adapters/mock.rb +5 -0
- data/lib/sequel/adapters/mysql.rb +8 -1
- data/lib/sequel/adapters/mysql2.rb +10 -3
- data/lib/sequel/adapters/postgres.rb +72 -8
- data/lib/sequel/adapters/shared/db2.rb +1 -0
- data/lib/sequel/adapters/shared/mssql.rb +57 -0
- data/lib/sequel/adapters/shared/mysql.rb +95 -19
- data/lib/sequel/adapters/shared/oracle.rb +14 -0
- data/lib/sequel/adapters/shared/postgres.rb +63 -24
- data/lib/sequel/adapters/shared/sqlite.rb +6 -9
- data/lib/sequel/connection_pool/sharded_threaded.rb +8 -3
- data/lib/sequel/connection_pool/threaded.rb +9 -4
- data/lib/sequel/database/query.rb +60 -48
- data/lib/sequel/database/schema_generator.rb +13 -6
- data/lib/sequel/database/schema_methods.rb +65 -12
- data/lib/sequel/dataset/actions.rb +22 -4
- data/lib/sequel/dataset/features.rb +5 -0
- data/lib/sequel/dataset/graph.rb +2 -3
- data/lib/sequel/dataset/misc.rb +2 -2
- data/lib/sequel/dataset/query.rb +0 -2
- data/lib/sequel/dataset/sql.rb +33 -12
- data/lib/sequel/extensions/constraint_validations.rb +451 -0
- data/lib/sequel/extensions/eval_inspect.rb +17 -2
- data/lib/sequel/extensions/pg_array_ops.rb +15 -5
- data/lib/sequel/extensions/pg_interval.rb +2 -2
- data/lib/sequel/extensions/pg_row_ops.rb +18 -0
- data/lib/sequel/extensions/schema_dumper.rb +3 -11
- data/lib/sequel/model/associations.rb +3 -2
- data/lib/sequel/model/base.rb +57 -13
- data/lib/sequel/model/exceptions.rb +20 -2
- data/lib/sequel/plugins/constraint_validations.rb +198 -0
- data/lib/sequel/plugins/defaults_setter.rb +15 -1
- data/lib/sequel/plugins/dirty.rb +2 -2
- data/lib/sequel/plugins/identity_map.rb +12 -8
- data/lib/sequel/plugins/subclasses.rb +19 -1
- data/lib/sequel/plugins/tree.rb +3 -3
- data/lib/sequel/plugins/validation_helpers.rb +24 -4
- data/lib/sequel/sql.rb +64 -24
- data/lib/sequel/timezones.rb +10 -2
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +26 -25
- data/spec/adapters/mysql_spec.rb +57 -23
- data/spec/adapters/oracle_spec.rb +34 -49
- data/spec/adapters/postgres_spec.rb +226 -128
- data/spec/adapters/sqlite_spec.rb +50 -49
- data/spec/core/connection_pool_spec.rb +22 -0
- data/spec/core/database_spec.rb +53 -47
- data/spec/core/dataset_spec.rb +36 -32
- data/spec/core/expression_filters_spec.rb +14 -2
- data/spec/core/mock_adapter_spec.rb +4 -0
- data/spec/core/object_graph_spec.rb +0 -13
- data/spec/core/schema_spec.rb +64 -5
- data/spec/core_extensions_spec.rb +1 -0
- data/spec/extensions/constraint_validations_plugin_spec.rb +196 -0
- data/spec/extensions/constraint_validations_spec.rb +316 -0
- data/spec/extensions/defaults_setter_spec.rb +24 -0
- data/spec/extensions/eval_inspect_spec.rb +9 -0
- data/spec/extensions/identity_map_spec.rb +11 -2
- data/spec/extensions/pg_array_ops_spec.rb +9 -0
- data/spec/extensions/pg_row_ops_spec.rb +11 -1
- data/spec/extensions/pg_row_plugin_spec.rb +4 -0
- data/spec/extensions/schema_dumper_spec.rb +8 -5
- data/spec/extensions/subclasses_spec.rb +14 -0
- data/spec/extensions/validation_helpers_spec.rb +15 -2
- data/spec/integration/dataset_test.rb +75 -1
- data/spec/integration/plugin_test.rb +146 -0
- data/spec/integration/schema_test.rb +34 -0
- data/spec/model/dataset_methods_spec.rb +38 -0
- data/spec/model/hooks_spec.rb +6 -0
- data/spec/model/validations_spec.rb +27 -2
- 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
|
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
|
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
|
242
|
-
# with a filename, you should just use +run+ instead of this method.
|
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
|
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
|
-
|
114
|
-
|
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
|
-
#
|
171
|
-
#
|
172
|
-
|
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
|
211
|
+
sql = super
|
177
212
|
op[:table] = related
|
178
213
|
op[:key] ||= primary_key_from_schema(related)
|
179
|
-
|
214
|
+
sql << ", ADD FOREIGN KEY (#{quote_identifier(op[:name])})#{column_references_sql(op)}"
|
180
215
|
else
|
181
|
-
super
|
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
|
-
"
|
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
|
-
|
231
|
+
"DROP PRIMARY KEY"
|
199
232
|
when :foreign_key
|
200
|
-
|
233
|
+
"DROP FOREIGN KEY #{quote_identifier(op[:name])}"
|
201
234
|
when :unique
|
202
|
-
|
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
|
241
|
+
super
|
212
242
|
else
|
213
|
-
super
|
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.
|