sequel 3.21.0 → 3.31.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 +485 -3
- data/README.rdoc +20 -6
- data/Rakefile +37 -19
- data/bin/sequel +1 -5
- data/doc/association_basics.rdoc +249 -43
- data/doc/dataset_basics.rdoc +4 -4
- data/doc/dataset_filtering.rdoc +8 -0
- data/doc/mass_assignment.rdoc +54 -0
- data/doc/migration.rdoc +15 -538
- data/doc/model_hooks.rdoc +72 -27
- data/doc/opening_databases.rdoc +89 -50
- data/doc/prepared_statements.rdoc +40 -13
- data/doc/reflection.rdoc +8 -2
- data/doc/release_notes/3.22.0.txt +39 -0
- data/doc/release_notes/3.23.0.txt +172 -0
- data/doc/release_notes/3.24.0.txt +420 -0
- data/doc/release_notes/3.25.0.txt +88 -0
- data/doc/release_notes/3.26.0.txt +88 -0
- data/doc/release_notes/3.27.0.txt +82 -0
- data/doc/release_notes/3.28.0.txt +304 -0
- data/doc/release_notes/3.29.0.txt +459 -0
- data/doc/release_notes/3.30.0.txt +135 -0
- data/doc/release_notes/3.31.0.txt +146 -0
- data/doc/schema_modification.rdoc +547 -0
- data/doc/sharding.rdoc +7 -1
- data/doc/testing.rdoc +129 -0
- data/doc/transactions.rdoc +137 -0
- data/doc/virtual_rows.rdoc +2 -2
- data/lib/sequel/adapters/ado/mssql.rb +18 -6
- data/lib/sequel/adapters/ado.rb +40 -18
- data/lib/sequel/adapters/amalgalite.rb +17 -9
- data/lib/sequel/adapters/db2.rb +175 -86
- data/lib/sequel/adapters/dbi.rb +15 -15
- data/lib/sequel/adapters/do/mysql.rb +5 -7
- data/lib/sequel/adapters/do/postgres.rb +0 -5
- data/lib/sequel/adapters/do/sqlite.rb +0 -5
- data/lib/sequel/adapters/do.rb +17 -36
- data/lib/sequel/adapters/firebird.rb +27 -208
- data/lib/sequel/adapters/ibmdb.rb +448 -0
- data/lib/sequel/adapters/informix.rb +6 -23
- data/lib/sequel/adapters/jdbc/as400.rb +16 -34
- data/lib/sequel/adapters/jdbc/db2.rb +45 -0
- data/lib/sequel/adapters/jdbc/derby.rb +247 -0
- data/lib/sequel/adapters/jdbc/firebird.rb +29 -0
- data/lib/sequel/adapters/jdbc/h2.rb +56 -29
- data/lib/sequel/adapters/jdbc/hsqldb.rb +170 -0
- data/lib/sequel/adapters/jdbc/informix.rb +26 -0
- data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
- data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
- data/lib/sequel/adapters/jdbc/mysql.rb +11 -11
- data/lib/sequel/adapters/jdbc/oracle.rb +81 -25
- data/lib/sequel/adapters/jdbc/postgresql.rb +9 -13
- data/lib/sequel/adapters/jdbc/sqlite.rb +2 -6
- data/lib/sequel/adapters/jdbc/sqlserver.rb +71 -0
- data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
- data/lib/sequel/adapters/jdbc.rb +231 -78
- data/lib/sequel/adapters/mock.rb +335 -0
- data/lib/sequel/adapters/mysql.rb +82 -184
- data/lib/sequel/adapters/mysql2.rb +26 -25
- data/lib/sequel/adapters/odbc/db2.rb +17 -0
- data/lib/sequel/adapters/odbc/mssql.rb +1 -6
- data/lib/sequel/adapters/odbc.rb +28 -9
- data/lib/sequel/adapters/openbase.rb +3 -5
- data/lib/sequel/adapters/oracle.rb +351 -53
- data/lib/sequel/adapters/postgres.rb +194 -33
- data/lib/sequel/adapters/shared/access.rb +32 -9
- data/lib/sequel/adapters/shared/db2.rb +321 -0
- data/lib/sequel/adapters/shared/firebird.rb +221 -0
- data/lib/sequel/adapters/shared/informix.rb +53 -0
- data/lib/sequel/adapters/shared/mssql.rb +231 -106
- data/lib/sequel/adapters/shared/mysql.rb +181 -63
- data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +149 -0
- data/lib/sequel/adapters/shared/oracle.rb +231 -41
- data/lib/sequel/adapters/shared/postgres.rb +214 -82
- data/lib/sequel/adapters/shared/progress.rb +4 -7
- data/lib/sequel/adapters/shared/sqlite.rb +201 -57
- data/lib/sequel/adapters/sqlite.rb +29 -21
- data/lib/sequel/adapters/swift/mysql.rb +12 -7
- data/lib/sequel/adapters/swift/postgres.rb +0 -5
- data/lib/sequel/adapters/swift/sqlite.rb +8 -6
- data/lib/sequel/adapters/swift.rb +5 -5
- data/lib/sequel/adapters/tinytds.rb +160 -19
- data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +66 -0
- data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
- data/lib/sequel/ast_transformer.rb +190 -0
- data/lib/sequel/connection_pool/threaded.rb +3 -2
- data/lib/sequel/connection_pool.rb +1 -1
- data/lib/sequel/core.rb +46 -5
- data/lib/sequel/database/connecting.rb +5 -6
- data/lib/sequel/database/dataset.rb +4 -4
- data/lib/sequel/database/dataset_defaults.rb +59 -1
- data/lib/sequel/database/logging.rb +1 -1
- data/lib/sequel/database/misc.rb +104 -19
- data/lib/sequel/database/query.rb +153 -58
- data/lib/sequel/database/schema_generator.rb +17 -7
- data/lib/sequel/database/schema_methods.rb +59 -29
- data/lib/sequel/dataset/actions.rb +231 -76
- data/lib/sequel/dataset/features.rb +91 -8
- data/lib/sequel/dataset/graph.rb +38 -11
- data/lib/sequel/dataset/misc.rb +50 -23
- data/lib/sequel/dataset/mutation.rb +5 -6
- data/lib/sequel/dataset/prepared_statements.rb +48 -27
- data/lib/sequel/dataset/query.rb +236 -43
- data/lib/sequel/dataset/sql.rb +665 -253
- data/lib/sequel/dataset.rb +8 -0
- data/lib/sequel/exceptions.rb +4 -0
- data/lib/sequel/extensions/columns_introspection.rb +61 -0
- data/lib/sequel/extensions/migration.rb +6 -4
- data/lib/sequel/extensions/named_timezones.rb +5 -0
- data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
- data/lib/sequel/extensions/to_dot.rb +95 -83
- data/lib/sequel/model/associations.rb +1020 -337
- data/lib/sequel/model/base.rb +414 -132
- data/lib/sequel/model/errors.rb +1 -1
- data/lib/sequel/model/exceptions.rb +5 -1
- data/lib/sequel/model.rb +15 -8
- data/lib/sequel/plugins/association_pks.rb +22 -4
- data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
- data/lib/sequel/plugins/dataset_associations.rb +100 -0
- data/lib/sequel/plugins/defaults_setter.rb +58 -0
- data/lib/sequel/plugins/force_encoding.rb +6 -6
- data/lib/sequel/plugins/identity_map.rb +114 -7
- data/lib/sequel/plugins/many_through_many.rb +65 -7
- data/lib/sequel/plugins/prepared_statements.rb +151 -0
- data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
- data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
- data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
- data/lib/sequel/plugins/rcte_tree.rb +29 -15
- data/lib/sequel/plugins/serialization.rb +88 -44
- data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
- data/lib/sequel/plugins/sharding.rb +21 -54
- data/lib/sequel/plugins/single_table_inheritance.rb +3 -1
- data/lib/sequel/plugins/typecast_on_load.rb +9 -12
- data/lib/sequel/plugins/update_primary_key.rb +2 -2
- data/lib/sequel/plugins/xml_serializer.rb +3 -3
- data/lib/sequel/sql.rb +109 -52
- data/lib/sequel/timezones.rb +57 -38
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/db2_spec.rb +146 -0
- data/spec/adapters/mssql_spec.rb +111 -29
- data/spec/adapters/mysql_spec.rb +135 -123
- data/spec/adapters/oracle_spec.rb +49 -77
- data/spec/adapters/postgres_spec.rb +267 -78
- data/spec/adapters/spec_helper.rb +6 -5
- data/spec/adapters/sqlite_spec.rb +35 -21
- data/spec/core/connection_pool_spec.rb +71 -92
- data/spec/core/core_sql_spec.rb +21 -30
- data/spec/core/database_spec.rb +788 -512
- data/spec/core/dataset_spec.rb +1073 -1053
- data/spec/core/expression_filters_spec.rb +159 -42
- data/spec/core/mock_adapter_spec.rb +419 -0
- data/spec/core/object_graph_spec.rb +86 -114
- data/spec/core/schema_generator_spec.rb +3 -3
- data/spec/core/schema_spec.rb +298 -38
- data/spec/core/spec_helper.rb +6 -48
- data/spec/extensions/association_autoreloading_spec.rb +1 -10
- data/spec/extensions/association_dependencies_spec.rb +2 -12
- data/spec/extensions/association_pks_spec.rb +66 -32
- data/spec/extensions/caching_spec.rb +23 -50
- data/spec/extensions/class_table_inheritance_spec.rb +31 -83
- data/spec/extensions/columns_introspection_spec.rb +91 -0
- data/spec/extensions/composition_spec.rb +18 -13
- data/spec/extensions/dataset_associations_spec.rb +199 -0
- data/spec/extensions/defaults_setter_spec.rb +64 -0
- data/spec/extensions/hook_class_methods_spec.rb +65 -91
- data/spec/extensions/identity_map_spec.rb +114 -22
- data/spec/extensions/instance_filters_spec.rb +11 -21
- data/spec/extensions/instance_hooks_spec.rb +53 -0
- data/spec/extensions/json_serializer_spec.rb +4 -5
- data/spec/extensions/lazy_attributes_spec.rb +16 -20
- data/spec/extensions/list_spec.rb +17 -39
- data/spec/extensions/many_through_many_spec.rb +283 -250
- data/spec/extensions/migration_spec.rb +35 -32
- data/spec/extensions/named_timezones_spec.rb +23 -3
- data/spec/extensions/nested_attributes_spec.rb +97 -88
- data/spec/extensions/optimistic_locking_spec.rb +9 -20
- data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
- data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
- data/spec/extensions/prepared_statements_spec.rb +53 -0
- data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
- data/spec/extensions/pretty_table_spec.rb +1 -6
- data/spec/extensions/rcte_tree_spec.rb +41 -43
- data/spec/extensions/schema_dumper_spec.rb +5 -8
- data/spec/extensions/schema_spec.rb +13 -21
- data/spec/extensions/serialization_modification_detection_spec.rb +37 -0
- data/spec/extensions/serialization_spec.rb +72 -43
- data/spec/extensions/sharding_spec.rb +66 -140
- data/spec/extensions/single_table_inheritance_spec.rb +18 -29
- data/spec/extensions/spec_helper.rb +13 -61
- data/spec/extensions/sql_expr_spec.rb +20 -60
- data/spec/extensions/tactical_eager_loading_spec.rb +9 -19
- data/spec/extensions/thread_local_timezones_spec.rb +22 -2
- data/spec/extensions/timestamps_spec.rb +6 -6
- data/spec/extensions/to_dot_spec.rb +4 -7
- data/spec/extensions/touch_spec.rb +13 -14
- data/spec/extensions/tree_spec.rb +11 -26
- data/spec/extensions/typecast_on_load_spec.rb +1 -6
- data/spec/extensions/update_primary_key_spec.rb +30 -24
- data/spec/extensions/validation_class_methods_spec.rb +30 -51
- data/spec/extensions/validation_helpers_spec.rb +16 -35
- data/spec/extensions/xml_serializer_spec.rb +16 -4
- data/spec/integration/associations_test.rb +537 -21
- data/spec/integration/database_test.rb +4 -2
- data/spec/integration/dataset_test.rb +461 -65
- data/spec/integration/eager_loader_test.rb +19 -21
- data/spec/integration/model_test.rb +80 -1
- data/spec/integration/plugin_test.rb +304 -116
- data/spec/integration/prepared_statement_test.rb +212 -122
- data/spec/integration/schema_test.rb +161 -30
- data/spec/integration/spec_helper.rb +39 -30
- data/spec/integration/timezone_test.rb +38 -12
- data/spec/integration/transaction_test.rb +172 -5
- data/spec/integration/type_test.rb +17 -3
- data/spec/model/association_reflection_spec.rb +201 -7
- data/spec/model/associations_spec.rb +865 -662
- data/spec/model/base_spec.rb +275 -95
- data/spec/model/dataset_methods_spec.rb +7 -27
- data/spec/model/eager_loading_spec.rb +677 -676
- data/spec/model/hooks_spec.rb +309 -67
- data/spec/model/model_spec.rb +222 -168
- data/spec/model/plugins_spec.rb +24 -13
- data/spec/model/record_spec.rb +381 -218
- data/spec/model/spec_helper.rb +13 -71
- data/spec/model/validations_spec.rb +11 -0
- metadata +115 -73
|
@@ -83,13 +83,9 @@ module Sequel
|
|
|
83
83
|
@server_version ||= (m[1].to_i * 10000) + (m[2].to_i * 100) + m[3].to_i
|
|
84
84
|
end
|
|
85
85
|
|
|
86
|
-
#
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
# * :server - Set the server to use
|
|
90
|
-
def tables(opts={})
|
|
91
|
-
m = output_identifier_meth
|
|
92
|
-
metadata_dataset.with_sql('SHOW TABLES').server(opts[:server]).map{|r| m.call(r.values.first)}
|
|
86
|
+
# MySQL supports CREATE TABLE IF NOT EXISTS syntax.
|
|
87
|
+
def supports_create_table_if_not_exists?
|
|
88
|
+
true
|
|
93
89
|
end
|
|
94
90
|
|
|
95
91
|
# MySQL supports prepared transactions (two-phase commit) using XA
|
|
@@ -107,6 +103,14 @@ module Sequel
|
|
|
107
103
|
true
|
|
108
104
|
end
|
|
109
105
|
|
|
106
|
+
# Return an array of symbols specifying table names in the current database.
|
|
107
|
+
#
|
|
108
|
+
# Options:
|
|
109
|
+
# * :server - Set the server to use
|
|
110
|
+
def tables(opts={})
|
|
111
|
+
full_tables('BASE TABLE', opts)
|
|
112
|
+
end
|
|
113
|
+
|
|
110
114
|
# Changes the database in use by issuing a USE statement. I would be
|
|
111
115
|
# very careful if I used this.
|
|
112
116
|
def use(db_name)
|
|
@@ -116,6 +120,14 @@ module Sequel
|
|
|
116
120
|
self
|
|
117
121
|
end
|
|
118
122
|
|
|
123
|
+
# Return an array of symbols specifying view names in the current database.
|
|
124
|
+
#
|
|
125
|
+
# Options:
|
|
126
|
+
# * :server - Set the server to use
|
|
127
|
+
def views(opts={})
|
|
128
|
+
full_tables('VIEW', opts)
|
|
129
|
+
end
|
|
130
|
+
|
|
119
131
|
private
|
|
120
132
|
|
|
121
133
|
# Use MySQL specific syntax for rename column, set column type, and
|
|
@@ -142,6 +154,18 @@ module Sequel
|
|
|
142
154
|
"ALTER TABLE #{quote_schema_table(table)} CHANGE COLUMN #{quote_identifier(op[:name])} #{column_definition_sql(op.merge(opts))}"
|
|
143
155
|
when :drop_index
|
|
144
156
|
"#{drop_index_sql(table, op)} ON #{quote_schema_table(table)}"
|
|
157
|
+
when :drop_constraint
|
|
158
|
+
type = case op[:type]
|
|
159
|
+
when :primary_key
|
|
160
|
+
return "ALTER TABLE #{quote_schema_table(table)} DROP PRIMARY KEY"
|
|
161
|
+
when :foreign_key
|
|
162
|
+
'FOREIGN KEY'
|
|
163
|
+
when :unique
|
|
164
|
+
'INDEX'
|
|
165
|
+
else
|
|
166
|
+
raise(Error, "must specify constraint type via :type=>(:foreign_key|:primary_key|:unique) when dropping constraints on MySQL")
|
|
167
|
+
end
|
|
168
|
+
"ALTER TABLE #{quote_schema_table(table)} DROP #{type} #{quote_identifier(op[:name])}"
|
|
145
169
|
else
|
|
146
170
|
super(table, op)
|
|
147
171
|
end
|
|
@@ -161,9 +185,9 @@ module Sequel
|
|
|
161
185
|
# Use XA START to start a new prepared transaction if the :prepare
|
|
162
186
|
# option is given.
|
|
163
187
|
def begin_transaction(conn, opts={})
|
|
164
|
-
if s = opts[:prepare]
|
|
188
|
+
if (s = opts[:prepare]) && (th = @transactions[conn])[:savepoint_level] == 0
|
|
165
189
|
log_connection_execute(conn, "XA START #{literal(s)}")
|
|
166
|
-
|
|
190
|
+
th[:savepoint_level] += 1
|
|
167
191
|
else
|
|
168
192
|
super
|
|
169
193
|
end
|
|
@@ -184,7 +208,7 @@ module Sequel
|
|
|
184
208
|
# Prepare the XA transaction for a two-phase commit if the
|
|
185
209
|
# :prepare option is given.
|
|
186
210
|
def commit_transaction(conn, opts={})
|
|
187
|
-
if s = opts[:prepare]
|
|
211
|
+
if (s = opts[:prepare]) && @transactions[conn][:savepoint_level] <= 1
|
|
188
212
|
log_connection_execute(conn, "XA END #{literal(s)}")
|
|
189
213
|
log_connection_execute(conn, "XA PREPARE #{literal(s)}")
|
|
190
214
|
else
|
|
@@ -205,6 +229,12 @@ module Sequel
|
|
|
205
229
|
"#{super}#{" ENGINE=#{engine}" if engine}#{" DEFAULT CHARSET=#{charset}" if charset}#{" DEFAULT COLLATE=#{collate}" if collate}"
|
|
206
230
|
end
|
|
207
231
|
|
|
232
|
+
# Backbone of the tables and views support using SHOW FULL TABLES.
|
|
233
|
+
def full_tables(type, opts)
|
|
234
|
+
m = output_identifier_meth
|
|
235
|
+
metadata_dataset.with_sql('SHOW FULL TABLES').server(opts[:server]).map{|r| m.call(r.values.first) if r.delete(:Table_type) == type}.compact
|
|
236
|
+
end
|
|
237
|
+
|
|
208
238
|
# MySQL folds unquoted identifiers to lowercase, so it shouldn't need to upcase identifiers on input.
|
|
209
239
|
def identifier_input_method_default
|
|
210
240
|
nil
|
|
@@ -232,7 +262,7 @@ module Sequel
|
|
|
232
262
|
|
|
233
263
|
# Rollback the currently open XA transaction
|
|
234
264
|
def rollback_transaction(conn, opts={})
|
|
235
|
-
if s = opts[:prepare]
|
|
265
|
+
if (s = opts[:prepare]) && @transactions[conn][:savepoint_level] <= 1
|
|
236
266
|
log_connection_execute(conn, "XA END #{literal(s)}")
|
|
237
267
|
log_connection_execute(conn, "XA PREPARE #{literal(s)}")
|
|
238
268
|
log_connection_execute(conn, "XA ROLLBACK #{literal(s)}")
|
|
@@ -248,8 +278,8 @@ module Sequel
|
|
|
248
278
|
|
|
249
279
|
# Use the MySQL specific DESCRIBE syntax to get a table description.
|
|
250
280
|
def schema_parse_table(table_name, opts)
|
|
251
|
-
m = output_identifier_meth
|
|
252
|
-
im = input_identifier_meth
|
|
281
|
+
m = output_identifier_meth(opts[:dataset])
|
|
282
|
+
im = input_identifier_meth(opts[:dataset])
|
|
253
283
|
metadata_dataset.with_sql("DESCRIBE ?", SQL::Identifier.new(im.call(table_name))).map do |row|
|
|
254
284
|
row[:auto_increment] = true if row.delete(:Extra).to_s =~ /auto_increment/io
|
|
255
285
|
row[:allow_null] = row.delete(:Null) == 'YES'
|
|
@@ -303,27 +333,71 @@ module Sequel
|
|
|
303
333
|
COMMA_SEPARATOR = ', '.freeze
|
|
304
334
|
FOR_SHARE = ' LOCK IN SHARE MODE'.freeze
|
|
305
335
|
SQL_CALC_FOUND_ROWS = ' SQL_CALC_FOUND_ROWS'.freeze
|
|
306
|
-
DELETE_CLAUSE_METHODS = Dataset.clause_methods(:delete, %w'from where order limit')
|
|
307
|
-
INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'ignore into columns values on_duplicate_key_update')
|
|
308
|
-
SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'distinct calc_found_rows columns from join where group having compounds order limit lock')
|
|
309
|
-
UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'table set where order limit')
|
|
336
|
+
DELETE_CLAUSE_METHODS = Dataset.clause_methods(:delete, %w'delete from where order limit')
|
|
337
|
+
INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'insert ignore into columns values on_duplicate_key_update')
|
|
338
|
+
SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'select distinct calc_found_rows columns from join where group having compounds order limit lock')
|
|
339
|
+
UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'update table set where order limit')
|
|
340
|
+
SPACE = Dataset::SPACE
|
|
341
|
+
PAREN_OPEN = Dataset::PAREN_OPEN
|
|
342
|
+
PAREN_CLOSE = Dataset::PAREN_CLOSE
|
|
343
|
+
NOT_SPACE = Dataset::NOT_SPACE
|
|
344
|
+
FROM = Dataset::FROM
|
|
345
|
+
INSERT = Dataset::INSERT
|
|
346
|
+
COMMA = Dataset::COMMA
|
|
347
|
+
LIMIT = Dataset::LIMIT
|
|
348
|
+
GROUP_BY = Dataset::GROUP_BY
|
|
349
|
+
REGEXP = 'REGEXP'.freeze
|
|
350
|
+
LIKE = 'LIKE'.freeze
|
|
351
|
+
BINARY = 'BINARY '.freeze
|
|
352
|
+
CONCAT = "CONCAT".freeze
|
|
353
|
+
CAST_BITCOMP_OPEN = "CAST(~".freeze
|
|
354
|
+
CAST_BITCOMP_CLOSE = " AS SIGNED INTEGER)".freeze
|
|
355
|
+
STRAIGHT_JOIN = 'STRAIGHT_JOIN'.freeze
|
|
356
|
+
NATURAL_LEFT_JOIN = 'NATURAL LEFT JOIN'.freeze
|
|
357
|
+
BACKTICK = '`'.freeze
|
|
358
|
+
EMPTY_COLUMNS = " ()".freeze
|
|
359
|
+
EMPTY_VALUES = " VALUES ()".freeze
|
|
360
|
+
IGNORE = " IGNORE".freeze
|
|
361
|
+
REPLACE = 'REPLACE'.freeze
|
|
362
|
+
ON_DUPLICATE_KEY_UPDATE = " ON DUPLICATE KEY UPDATE ".freeze
|
|
363
|
+
EQ_VALUES = '=VALUES('.freeze
|
|
364
|
+
EQ = '='.freeze
|
|
365
|
+
WITH_ROLLUP = ' WITH ROLLUP'.freeze
|
|
310
366
|
|
|
311
367
|
# MySQL specific syntax for LIKE/REGEXP searches, as well as
|
|
312
368
|
# string concatenation.
|
|
313
|
-
def
|
|
369
|
+
def complex_expression_sql_append(sql, op, args)
|
|
314
370
|
case op
|
|
371
|
+
when :IN, :"NOT IN"
|
|
372
|
+
ds = args.at(1)
|
|
373
|
+
if ds.is_a?(Sequel::Dataset) && ds.opts[:limit]
|
|
374
|
+
super(sql, op, [args.at(0), ds.from_self])
|
|
375
|
+
else
|
|
376
|
+
super
|
|
377
|
+
end
|
|
315
378
|
when :~, :'!~', :'~*', :'!~*', :LIKE, :'NOT LIKE', :ILIKE, :'NOT ILIKE'
|
|
316
|
-
|
|
379
|
+
sql << PAREN_OPEN
|
|
380
|
+
literal_append(sql, args.at(0))
|
|
381
|
+
sql << SPACE
|
|
382
|
+
sql << 'NOT ' if [:'NOT LIKE', :'NOT ILIKE', :'!~', :'!~*'].include?(op)
|
|
383
|
+
sql << ([:~, :'!~', :'~*', :'!~*'].include?(op) ? REGEXP : LIKE)
|
|
384
|
+
sql << SPACE
|
|
385
|
+
sql << BINARY if [:~, :'!~', :LIKE, :'NOT LIKE'].include?(op)
|
|
386
|
+
literal_append(sql, args.at(1))
|
|
387
|
+
sql << PAREN_CLOSE
|
|
317
388
|
when :'||'
|
|
318
389
|
if args.length > 1
|
|
319
|
-
|
|
390
|
+
sql << CONCAT
|
|
391
|
+
array_sql_append(sql, args)
|
|
320
392
|
else
|
|
321
|
-
|
|
393
|
+
literal_append(sql, args.at(0))
|
|
322
394
|
end
|
|
323
395
|
when :'B~'
|
|
324
|
-
|
|
396
|
+
sql << CAST_BITCOMP_OPEN
|
|
397
|
+
literal_append(sql, args.at(0))
|
|
398
|
+
sql << CAST_BITCOMP_CLOSE
|
|
325
399
|
else
|
|
326
|
-
super
|
|
400
|
+
super
|
|
327
401
|
end
|
|
328
402
|
end
|
|
329
403
|
|
|
@@ -351,8 +425,9 @@ module Sequel
|
|
|
351
425
|
end
|
|
352
426
|
|
|
353
427
|
# MySQL specific full text search syntax.
|
|
354
|
-
def full_text_sql(cols,
|
|
355
|
-
|
|
428
|
+
def full_text_sql(cols, terms, opts = {})
|
|
429
|
+
terms = terms.join(' ') if terms.is_a?(Array)
|
|
430
|
+
SQL::PlaceholderLiteralString.new("MATCH ? AGAINST (?#{" IN BOOLEAN MODE" if opts[:boolean]})", [Array(cols), terms], true)
|
|
356
431
|
end
|
|
357
432
|
|
|
358
433
|
# MySQL allows HAVING clause on ungrouped datasets.
|
|
@@ -372,9 +447,12 @@ module Sequel
|
|
|
372
447
|
# STRAIGHT_JOIN.
|
|
373
448
|
def join_type_sql(join_type)
|
|
374
449
|
case join_type
|
|
375
|
-
when :straight
|
|
376
|
-
|
|
377
|
-
|
|
450
|
+
when :straight
|
|
451
|
+
STRAIGHT_JOIN
|
|
452
|
+
when :natural_inner
|
|
453
|
+
NATURAL_LEFT_JOIN
|
|
454
|
+
else
|
|
455
|
+
super
|
|
378
456
|
end
|
|
379
457
|
end
|
|
380
458
|
|
|
@@ -415,7 +493,9 @@ module Sequel
|
|
|
415
493
|
|
|
416
494
|
# MySQL specific syntax for inserting multiple values at once.
|
|
417
495
|
def multi_insert_sql(columns, values)
|
|
418
|
-
|
|
496
|
+
sql = LiteralString.new('VALUES ')
|
|
497
|
+
expression_list_append(sql, values.map{|r| Array(r)})
|
|
498
|
+
[insert_sql(columns, sql)]
|
|
419
499
|
end
|
|
420
500
|
|
|
421
501
|
# MySQL uses the number of rows actually modified in the update,
|
|
@@ -425,8 +505,8 @@ module Sequel
|
|
|
425
505
|
end
|
|
426
506
|
|
|
427
507
|
# MySQL uses the nonstandard ` (backtick) for quoting identifiers.
|
|
428
|
-
def
|
|
429
|
-
|
|
508
|
+
def quoted_identifier_append(sql, c)
|
|
509
|
+
sql << BACKTICK << c.to_s << BACKTICK
|
|
430
510
|
end
|
|
431
511
|
|
|
432
512
|
# MySQL specific syntax for REPLACE (aka UPSERT, or update if exists,
|
|
@@ -441,6 +521,11 @@ module Sequel
|
|
|
441
521
|
true
|
|
442
522
|
end
|
|
443
523
|
|
|
524
|
+
# MySQL supports GROUP BY WITH ROLLUP (but not CUBE)
|
|
525
|
+
def supports_group_rollup?
|
|
526
|
+
true
|
|
527
|
+
end
|
|
528
|
+
|
|
444
529
|
# MySQL does not support INTERSECT or EXCEPT
|
|
445
530
|
def supports_intersect_except?
|
|
446
531
|
false
|
|
@@ -451,6 +536,12 @@ module Sequel
|
|
|
451
536
|
true
|
|
452
537
|
end
|
|
453
538
|
|
|
539
|
+
# MySQL's DISTINCT ON emulation using GROUP BY does not respect the
|
|
540
|
+
# queries ORDER BY clause.
|
|
541
|
+
def supports_ordered_distinct_on?
|
|
542
|
+
false
|
|
543
|
+
end
|
|
544
|
+
|
|
454
545
|
# MySQL does support fractional timestamps in literal timestamps, but it
|
|
455
546
|
# ignores them. Also, using them seems to cause problems on 1.9. Since
|
|
456
547
|
# they are ignored anyway, not using them is probably best.
|
|
@@ -458,13 +549,6 @@ module Sequel
|
|
|
458
549
|
false
|
|
459
550
|
end
|
|
460
551
|
|
|
461
|
-
protected
|
|
462
|
-
|
|
463
|
-
# If this is an replace instead of an insert, use replace instead
|
|
464
|
-
def _insert_sql
|
|
465
|
-
@opts[:replace] ? clause_sql(:replace) : super
|
|
466
|
-
end
|
|
467
|
-
|
|
468
552
|
private
|
|
469
553
|
|
|
470
554
|
# MySQL supports the ORDER BY and LIMIT clauses for DELETE statements
|
|
@@ -476,7 +560,10 @@ module Sequel
|
|
|
476
560
|
# from, but include the others for the purposes of selecting rows.
|
|
477
561
|
def delete_from_sql(sql)
|
|
478
562
|
if joined_dataset?
|
|
479
|
-
sql <<
|
|
563
|
+
sql << SPACE
|
|
564
|
+
source_list_append(sql, @opts[:from][0..0])
|
|
565
|
+
sql << FROM
|
|
566
|
+
source_list_append(sql, @opts[:from])
|
|
480
567
|
select_join_sql(sql)
|
|
481
568
|
else
|
|
482
569
|
super
|
|
@@ -493,7 +580,7 @@ module Sequel
|
|
|
493
580
|
def insert_columns_sql(sql)
|
|
494
581
|
values = opts[:values]
|
|
495
582
|
if values.is_a?(Array) && values.empty?
|
|
496
|
-
sql <<
|
|
583
|
+
sql << EMPTY_COLUMNS
|
|
497
584
|
else
|
|
498
585
|
super
|
|
499
586
|
end
|
|
@@ -501,19 +588,57 @@ module Sequel
|
|
|
501
588
|
|
|
502
589
|
# MySQL supports INSERT IGNORE INTO
|
|
503
590
|
def insert_ignore_sql(sql)
|
|
504
|
-
sql <<
|
|
591
|
+
sql << IGNORE if opts[:insert_ignore]
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
# If this is an replace instead of an insert, use replace instead
|
|
595
|
+
def insert_insert_sql(sql)
|
|
596
|
+
sql << (@opts[:replace] ? REPLACE : INSERT)
|
|
505
597
|
end
|
|
506
598
|
|
|
507
599
|
# MySQL supports INSERT ... ON DUPLICATE KEY UPDATE
|
|
508
600
|
def insert_on_duplicate_key_update_sql(sql)
|
|
509
|
-
|
|
601
|
+
if update_cols = opts[:on_duplicate_key_update]
|
|
602
|
+
update_vals = nil
|
|
603
|
+
|
|
604
|
+
if update_cols.empty?
|
|
605
|
+
update_cols = columns
|
|
606
|
+
elsif update_cols.last.is_a?(Hash)
|
|
607
|
+
update_vals = update_cols.last
|
|
608
|
+
update_cols = update_cols[0..-2]
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
sql << ON_DUPLICATE_KEY_UPDATE
|
|
612
|
+
c = false
|
|
613
|
+
co = COMMA
|
|
614
|
+
values = EQ_VALUES
|
|
615
|
+
endp = PAREN_CLOSE
|
|
616
|
+
update_cols.each do |col|
|
|
617
|
+
sql << co if c
|
|
618
|
+
quote_identifier_append(sql, col)
|
|
619
|
+
sql << values
|
|
620
|
+
quote_identifier_append(sql, col)
|
|
621
|
+
sql << endp
|
|
622
|
+
c ||= true
|
|
623
|
+
end
|
|
624
|
+
if update_vals
|
|
625
|
+
eq = EQ
|
|
626
|
+
update_vals.map do |col,v|
|
|
627
|
+
sql << co if c
|
|
628
|
+
quote_identifier_append(sql, col)
|
|
629
|
+
sql << eq
|
|
630
|
+
literal_append(sql, v)
|
|
631
|
+
c ||= true
|
|
632
|
+
end
|
|
633
|
+
end
|
|
634
|
+
end
|
|
510
635
|
end
|
|
511
636
|
|
|
512
637
|
# MySQL doesn't use the standard DEFAULT VALUES for empty values.
|
|
513
638
|
def insert_values_sql(sql)
|
|
514
639
|
values = opts[:values]
|
|
515
640
|
if values.is_a?(Array) && values.empty?
|
|
516
|
-
sql <<
|
|
641
|
+
sql << EMPTY_VALUES
|
|
517
642
|
else
|
|
518
643
|
super
|
|
519
644
|
end
|
|
@@ -521,7 +646,10 @@ module Sequel
|
|
|
521
646
|
|
|
522
647
|
# MySQL allows a LIMIT in DELETE and UPDATE statements.
|
|
523
648
|
def limit_sql(sql)
|
|
524
|
-
|
|
649
|
+
if l = @opts[:limit]
|
|
650
|
+
sql << LIMIT
|
|
651
|
+
literal_append(sql, @opts[:limit])
|
|
652
|
+
end
|
|
525
653
|
end
|
|
526
654
|
alias delete_limit_sql limit_sql
|
|
527
655
|
alias update_limit_sql limit_sql
|
|
@@ -536,30 +664,20 @@ module Sequel
|
|
|
536
664
|
BOOL_TRUE
|
|
537
665
|
end
|
|
538
666
|
|
|
539
|
-
# MySQL specific syntax for ON DUPLICATE KEY UPDATE
|
|
540
|
-
def on_duplicate_key_update_sql
|
|
541
|
-
if update_cols = opts[:on_duplicate_key_update]
|
|
542
|
-
update_vals = nil
|
|
543
|
-
|
|
544
|
-
if update_cols.empty?
|
|
545
|
-
update_cols = columns
|
|
546
|
-
elsif update_cols.last.is_a?(Hash)
|
|
547
|
-
update_vals = update_cols.last
|
|
548
|
-
update_cols = update_cols[0..-2]
|
|
549
|
-
end
|
|
550
|
-
|
|
551
|
-
updating = update_cols.map{|c| "#{quote_identifier(c)}=VALUES(#{quote_identifier(c)})" }
|
|
552
|
-
updating += update_vals.map{|c,v| "#{quote_identifier(c)}=#{literal(v)}" } if update_vals
|
|
553
|
-
|
|
554
|
-
" ON DUPLICATE KEY UPDATE #{updating.join(COMMA_SEPARATOR)}"
|
|
555
|
-
end
|
|
556
|
-
end
|
|
557
|
-
|
|
558
667
|
# MySQL does not support the SQL WITH clause for SELECT statements
|
|
559
668
|
def select_clause_methods
|
|
560
669
|
SELECT_CLAUSE_METHODS
|
|
561
670
|
end
|
|
562
671
|
|
|
672
|
+
# MySQL supports ROLLUP via nonstandard SQL syntax
|
|
673
|
+
def select_group_sql(sql)
|
|
674
|
+
if group = @opts[:group]
|
|
675
|
+
sql << GROUP_BY
|
|
676
|
+
expression_list_append(sql, group)
|
|
677
|
+
sql << WITH_ROLLUP if @opts[:group_options] == :rollup
|
|
678
|
+
end
|
|
679
|
+
end
|
|
680
|
+
|
|
563
681
|
# Support FOR SHARE locking when using the :share lock style.
|
|
564
682
|
def select_lock_sql(sql)
|
|
565
683
|
@opts[:lock] == :share ? (sql << FOR_SHARE) : super
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
Sequel.require %w'shared/mysql utils/stored_procedures', 'adapters'
|
|
2
|
+
|
|
3
|
+
module Sequel
|
|
4
|
+
module MySQL
|
|
5
|
+
# This module is used by the mysql and mysql2 adapters to support
|
|
6
|
+
# prepared statements and stored procedures.
|
|
7
|
+
module PreparedStatements
|
|
8
|
+
module DatabaseMethods
|
|
9
|
+
# Support stored procedures on MySQL
|
|
10
|
+
def call_sproc(name, opts={}, &block)
|
|
11
|
+
args = opts[:args] || []
|
|
12
|
+
execute("CALL #{name}#{args.empty? ? '()' : literal(args)}", opts.merge(:sproc=>false), &block)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Executes the given SQL using an available connection, yielding the
|
|
16
|
+
# connection if the block is given.
|
|
17
|
+
def execute(sql, opts={}, &block)
|
|
18
|
+
if opts[:sproc]
|
|
19
|
+
call_sproc(sql, opts, &block)
|
|
20
|
+
elsif sql.is_a?(Symbol)
|
|
21
|
+
execute_prepared_statement(sql, opts, &block)
|
|
22
|
+
else
|
|
23
|
+
synchronize(opts[:server]){|conn| _execute(conn, sql, opts, &block)}
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def add_prepared_statements_cache(conn)
|
|
30
|
+
class << conn
|
|
31
|
+
attr_accessor :prepared_statements
|
|
32
|
+
end
|
|
33
|
+
conn.prepared_statements = {}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Executes a prepared statement on an available connection. If the
|
|
37
|
+
# prepared statement already exists for the connection and has the same
|
|
38
|
+
# SQL, reuse it, otherwise, prepare the new statement. Because of the
|
|
39
|
+
# usual MySQL stupidity, we are forced to name arguments via separate
|
|
40
|
+
# SET queries. Use @sequel_arg_N (for N starting at 1) for these
|
|
41
|
+
# arguments.
|
|
42
|
+
def execute_prepared_statement(ps_name, opts, &block)
|
|
43
|
+
args = opts[:arguments]
|
|
44
|
+
ps = prepared_statements[ps_name]
|
|
45
|
+
sql = ps.prepared_sql
|
|
46
|
+
synchronize(opts[:server]) do |conn|
|
|
47
|
+
unless conn.prepared_statements[ps_name] == sql
|
|
48
|
+
conn.prepared_statements[ps_name] = sql
|
|
49
|
+
_execute(conn, "PREPARE #{ps_name} FROM #{literal(sql)}", opts)
|
|
50
|
+
end
|
|
51
|
+
i = 0
|
|
52
|
+
_execute(conn, "SET " + args.map {|arg| "@sequel_arg_#{i+=1} = #{literal(arg)}"}.join(", "), opts) unless args.empty?
|
|
53
|
+
_execute(conn, "EXECUTE #{ps_name}#{" USING #{(1..i).map{|j| "@sequel_arg_#{j}"}.join(', ')}" unless i == 0}", opts, &block)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
end
|
|
58
|
+
module DatasetMethods
|
|
59
|
+
include Sequel::Dataset::StoredProcedures
|
|
60
|
+
|
|
61
|
+
# Methods to add to MySQL prepared statement calls without using a
|
|
62
|
+
# real database prepared statement and bound variables.
|
|
63
|
+
module CallableStatementMethods
|
|
64
|
+
# Extend given dataset with this module so subselects inside subselects in
|
|
65
|
+
# prepared statements work.
|
|
66
|
+
def subselect_sql_append(sql, ds)
|
|
67
|
+
ps = ds.to_prepared_statement(:select).clone(:append_sql => sql)
|
|
68
|
+
ps.extend(CallableStatementMethods)
|
|
69
|
+
ps = ps.bind(@opts[:bind_vars]) if @opts[:bind_vars]
|
|
70
|
+
ps.prepared_args = prepared_args
|
|
71
|
+
ps.prepared_sql
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Methods for MySQL prepared statements using the native driver.
|
|
76
|
+
module PreparedStatementMethods
|
|
77
|
+
include Sequel::Dataset::UnnumberedArgumentMapper
|
|
78
|
+
|
|
79
|
+
# Raise a more obvious error if you attempt to call a unnamed prepared statement.
|
|
80
|
+
def call(*)
|
|
81
|
+
raise Error, "Cannot call prepared statement without a name" if prepared_statement_name.nil?
|
|
82
|
+
super
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
# Execute the prepared statement with the bind arguments instead of
|
|
88
|
+
# the given SQL.
|
|
89
|
+
def execute(sql, opts={}, &block)
|
|
90
|
+
super(prepared_statement_name, {:arguments=>bind_arguments}.merge(opts), &block)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Same as execute, explicit due to intricacies of alias and super.
|
|
94
|
+
def execute_dui(sql, opts={}, &block)
|
|
95
|
+
super(prepared_statement_name, {:arguments=>bind_arguments}.merge(opts), &block)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Methods for MySQL stored procedures using the native driver.
|
|
100
|
+
module StoredProcedureMethods
|
|
101
|
+
include Sequel::Dataset::StoredProcedureMethods
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
|
|
105
|
+
# Execute the database stored procedure with the stored arguments.
|
|
106
|
+
def execute(sql, opts={}, &block)
|
|
107
|
+
super(@sproc_name, {:args=>@sproc_args, :sproc=>true}.merge(opts), &block)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Same as execute, explicit due to intricacies of alias and super.
|
|
111
|
+
def execute_dui(sql, opts={}, &block)
|
|
112
|
+
super(@sproc_name, {:args=>@sproc_args, :sproc=>true}.merge(opts), &block)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# MySQL is different in that it supports prepared statements but not bound
|
|
117
|
+
# variables outside of prepared statements. The default implementation
|
|
118
|
+
# breaks the use of subselects in prepared statements, so extend the
|
|
119
|
+
# temporary prepared statement that this creates with a module that
|
|
120
|
+
# fixes it.
|
|
121
|
+
def call(type, bind_arguments={}, *values, &block)
|
|
122
|
+
ps = to_prepared_statement(type, values)
|
|
123
|
+
ps.extend(CallableStatementMethods)
|
|
124
|
+
ps.call(bind_arguments, &block)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Store the given type of prepared statement in the associated database
|
|
128
|
+
# with the given name.
|
|
129
|
+
def prepare(type, name=nil, *values)
|
|
130
|
+
ps = to_prepared_statement(type, values)
|
|
131
|
+
ps.extend(PreparedStatementMethods)
|
|
132
|
+
if name
|
|
133
|
+
ps.prepared_statement_name = name
|
|
134
|
+
db.prepared_statements[name] = ps
|
|
135
|
+
end
|
|
136
|
+
ps
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
private
|
|
140
|
+
|
|
141
|
+
# Extend the dataset with the MySQL stored procedure methods.
|
|
142
|
+
def prepare_extend_sproc(ds)
|
|
143
|
+
ds.extend(StoredProcedureMethods)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|