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.
- 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.
|