sequel 3.21.0 → 3.28.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 +294 -0
- data/README.rdoc +20 -6
- data/Rakefile +20 -15
- data/doc/association_basics.rdoc +210 -43
- data/doc/dataset_basics.rdoc +4 -4
- data/doc/mass_assignment.rdoc +54 -0
- data/doc/migration.rdoc +15 -538
- data/doc/model_hooks.rdoc +64 -27
- data/doc/opening_databases.rdoc +37 -10
- data/doc/prepared_statements.rdoc +16 -10
- 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/schema_modification.rdoc +547 -0
- data/doc/testing.rdoc +106 -0
- data/doc/transactions.rdoc +97 -0
- data/doc/virtual_rows.rdoc +2 -2
- data/lib/sequel/adapters/ado.rb +12 -1
- data/lib/sequel/adapters/amalgalite.rb +4 -0
- data/lib/sequel/adapters/db2.rb +95 -58
- data/lib/sequel/adapters/do.rb +12 -0
- data/lib/sequel/adapters/firebird.rb +25 -203
- data/lib/sequel/adapters/ibmdb.rb +440 -0
- data/lib/sequel/adapters/informix.rb +4 -19
- data/lib/sequel/adapters/jdbc/as400.rb +0 -7
- data/lib/sequel/adapters/jdbc/db2.rb +49 -0
- data/lib/sequel/adapters/jdbc/firebird.rb +34 -0
- data/lib/sequel/adapters/jdbc/h2.rb +16 -5
- data/lib/sequel/adapters/jdbc/informix.rb +31 -0
- data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
- data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
- data/lib/sequel/adapters/jdbc/mysql.rb +9 -0
- data/lib/sequel/adapters/jdbc/oracle.rb +2 -27
- data/lib/sequel/adapters/jdbc/postgresql.rb +6 -0
- data/lib/sequel/adapters/jdbc/sqlserver.rb +46 -0
- data/lib/sequel/adapters/jdbc/transactions.rb +34 -0
- data/lib/sequel/adapters/jdbc.rb +62 -29
- data/lib/sequel/adapters/mysql.rb +22 -139
- data/lib/sequel/adapters/mysql2.rb +9 -14
- data/lib/sequel/adapters/odbc/db2.rb +21 -0
- data/lib/sequel/adapters/odbc.rb +15 -3
- data/lib/sequel/adapters/oracle.rb +17 -1
- data/lib/sequel/adapters/postgres.rb +111 -16
- data/lib/sequel/adapters/shared/access.rb +21 -0
- data/lib/sequel/adapters/shared/db2.rb +290 -0
- data/lib/sequel/adapters/shared/firebird.rb +214 -0
- data/lib/sequel/adapters/shared/informix.rb +45 -0
- data/lib/sequel/adapters/shared/mssql.rb +85 -47
- data/lib/sequel/adapters/shared/mysql.rb +50 -7
- data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +143 -0
- data/lib/sequel/adapters/shared/oracle.rb +0 -4
- data/lib/sequel/adapters/shared/postgres.rb +75 -43
- data/lib/sequel/adapters/shared/sqlite.rb +56 -8
- data/lib/sequel/adapters/sqlite.rb +12 -11
- data/lib/sequel/adapters/swift/mysql.rb +9 -0
- data/lib/sequel/adapters/tinytds.rb +139 -7
- data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +55 -0
- 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 +6 -5
- data/lib/sequel/database/connecting.rb +5 -5
- data/lib/sequel/database/dataset.rb +1 -1
- data/lib/sequel/database/dataset_defaults.rb +1 -1
- data/lib/sequel/database/logging.rb +1 -1
- data/lib/sequel/database/misc.rb +38 -17
- data/lib/sequel/database/query.rb +50 -19
- data/lib/sequel/database/schema_generator.rb +8 -5
- data/lib/sequel/database/schema_methods.rb +52 -27
- data/lib/sequel/dataset/actions.rb +167 -48
- data/lib/sequel/dataset/features.rb +57 -8
- data/lib/sequel/dataset/graph.rb +1 -1
- data/lib/sequel/dataset/misc.rb +39 -20
- data/lib/sequel/dataset/mutation.rb +3 -3
- data/lib/sequel/dataset/prepared_statements.rb +29 -14
- data/lib/sequel/dataset/query.rb +182 -32
- data/lib/sequel/dataset/sql.rb +31 -58
- 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/to_dot.rb +95 -83
- data/lib/sequel/model/associations.rb +893 -309
- data/lib/sequel/model/base.rb +302 -105
- data/lib/sequel/model/errors.rb +1 -1
- data/lib/sequel/model/exceptions.rb +5 -1
- data/lib/sequel/model.rb +13 -7
- data/lib/sequel/plugins/association_pks.rb +22 -4
- data/lib/sequel/plugins/defaults_setter.rb +58 -0
- data/lib/sequel/plugins/identity_map.rb +113 -6
- data/lib/sequel/plugins/many_through_many.rb +67 -5
- data/lib/sequel/plugins/prepared_statements.rb +140 -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/serialization_modification_detection.rb +51 -0
- data/lib/sequel/plugins/sharding.rb +12 -20
- data/lib/sequel/plugins/single_table_inheritance.rb +2 -0
- data/lib/sequel/plugins/update_primary_key.rb +1 -1
- data/lib/sequel/plugins/xml_serializer.rb +3 -3
- data/lib/sequel/sql.rb +107 -51
- data/lib/sequel/timezones.rb +12 -3
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/db2_spec.rb +146 -0
- data/spec/adapters/mssql_spec.rb +36 -0
- data/spec/adapters/mysql_spec.rb +36 -19
- data/spec/adapters/postgres_spec.rb +115 -28
- data/spec/adapters/spec_helper.rb +6 -0
- data/spec/adapters/sqlite_spec.rb +11 -0
- data/spec/core/connection_pool_spec.rb +62 -77
- data/spec/core/database_spec.rb +244 -287
- data/spec/core/dataset_spec.rb +383 -34
- data/spec/core/expression_filters_spec.rb +159 -41
- data/spec/core/schema_spec.rb +326 -3
- data/spec/core/spec_helper.rb +45 -0
- data/spec/extensions/association_pks_spec.rb +38 -0
- data/spec/extensions/columns_introspection_spec.rb +91 -0
- data/spec/extensions/defaults_setter_spec.rb +64 -0
- data/spec/extensions/identity_map_spec.rb +162 -0
- data/spec/extensions/many_through_many_spec.rb +195 -20
- data/spec/extensions/migration_spec.rb +17 -17
- data/spec/extensions/nested_attributes_spec.rb +1 -0
- data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
- data/spec/extensions/prepared_statements_safe_spec.rb +79 -0
- data/spec/extensions/prepared_statements_spec.rb +72 -0
- data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
- data/spec/extensions/schema_dumper_spec.rb +2 -2
- data/spec/extensions/schema_spec.rb +12 -20
- data/spec/extensions/serialization_modification_detection_spec.rb +36 -0
- data/spec/extensions/single_table_inheritance_spec.rb +11 -0
- data/spec/extensions/spec_helper.rb +3 -1
- data/spec/extensions/to_dot_spec.rb +3 -5
- data/spec/extensions/xml_serializer_spec.rb +16 -4
- data/spec/integration/associations_test.rb +405 -15
- data/spec/integration/database_test.rb +4 -2
- data/spec/integration/dataset_test.rb +240 -20
- data/spec/integration/plugin_test.rb +142 -5
- data/spec/integration/prepared_statement_test.rb +174 -95
- data/spec/integration/schema_test.rb +128 -16
- data/spec/integration/spec_helper.rb +15 -0
- data/spec/integration/transaction_test.rb +40 -0
- data/spec/integration/type_test.rb +16 -2
- data/spec/model/association_reflection_spec.rb +91 -0
- data/spec/model/associations_spec.rb +476 -5
- data/spec/model/base_spec.rb +91 -1
- data/spec/model/eager_loading_spec.rb +519 -31
- data/spec/model/hooks_spec.rb +161 -0
- data/spec/model/model_spec.rb +89 -2
- data/spec/model/plugins_spec.rb +17 -0
- data/spec/model/record_spec.rb +184 -12
- data/spec/model/spec_helper.rb +5 -0
- data/spec/model/validations_spec.rb +11 -0
- metadata +85 -34
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
Sequel.require 'adapters/utils/emulate_offset_with_row_number'
|
|
2
|
+
|
|
1
3
|
module Sequel
|
|
2
4
|
Dataset::NON_SQL_OPTIONS << :disable_insert_output
|
|
3
5
|
module MSSQL
|
|
@@ -11,10 +13,16 @@ module Sequel
|
|
|
11
13
|
SQL_ROLLBACK_TO_SAVEPOINT = 'IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION autopoint_%d'.freeze
|
|
12
14
|
SQL_SAVEPOINT = 'SAVE TRANSACTION autopoint_%d'.freeze
|
|
13
15
|
|
|
16
|
+
# Whether to use N'' to quote strings, which allows unicode characters inside the
|
|
17
|
+
# strings. True by default for compatibility, can be set to false for a possible
|
|
18
|
+
# performance increase. This sets the default for all datasets created from this
|
|
19
|
+
# Database object.
|
|
20
|
+
attr_accessor :mssql_unicode_strings
|
|
21
|
+
|
|
14
22
|
# The types to check for 0 scale to transform :decimal types
|
|
15
23
|
# to :integer.
|
|
16
24
|
DECIMAL_TYPE_RE = /number|numeric|decimal/io
|
|
17
|
-
|
|
25
|
+
|
|
18
26
|
# Microsoft SQL Server uses the :mssql type.
|
|
19
27
|
def database_type
|
|
20
28
|
:mssql
|
|
@@ -47,11 +55,13 @@ module Sequel
|
|
|
47
55
|
# Microsoft SQL Server supports using the INFORMATION_SCHEMA to get
|
|
48
56
|
# information on tables.
|
|
49
57
|
def tables(opts={})
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
information_schema_tables('BASE TABLE', opts)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Microsoft SQL Server supports using the INFORMATION_SCHEMA to get
|
|
62
|
+
# information on views.
|
|
63
|
+
def views(opts={})
|
|
64
|
+
information_schema_tables('VIEW', opts)
|
|
55
65
|
end
|
|
56
66
|
|
|
57
67
|
private
|
|
@@ -117,6 +127,15 @@ module Sequel
|
|
|
117
127
|
"DROP INDEX #{quote_identifier(op[:name] || default_index_name(table, op[:columns]))} ON #{quote_schema_table(table)}"
|
|
118
128
|
end
|
|
119
129
|
|
|
130
|
+
# Backbone of the tables and views support.
|
|
131
|
+
def information_schema_tables(type, opts)
|
|
132
|
+
m = output_identifier_meth
|
|
133
|
+
metadata_dataset.from(:information_schema__tables___t).
|
|
134
|
+
select(:table_name).
|
|
135
|
+
filter(:table_type=>type, :table_schema=>(opts[:schema]||default_schema||'dbo').to_s).
|
|
136
|
+
map{|x| m.call(x[:table_name])}
|
|
137
|
+
end
|
|
138
|
+
|
|
120
139
|
# Always quote identifiers in the metadata_dataset, so schema parsing works.
|
|
121
140
|
def metadata_dataset
|
|
122
141
|
ds = super
|
|
@@ -139,6 +158,16 @@ module Sequel
|
|
|
139
158
|
SQL_ROLLBACK
|
|
140
159
|
end
|
|
141
160
|
|
|
161
|
+
# The closest MSSQL equivalent of a boolean datatype is the bit type.
|
|
162
|
+
def schema_column_type(db_type)
|
|
163
|
+
case db_type
|
|
164
|
+
when /\A(bit)\z/io
|
|
165
|
+
:boolean
|
|
166
|
+
else
|
|
167
|
+
super
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
142
171
|
# MSSQL uses the INFORMATION_SCHEMA to hold column information. This method does
|
|
143
172
|
# not support the parsing of primary key information.
|
|
144
173
|
def schema_parse_table(table_name, opts)
|
|
@@ -163,6 +192,11 @@ module Sequel
|
|
|
163
192
|
[m.call(row.delete(:column)), row]
|
|
164
193
|
end
|
|
165
194
|
end
|
|
195
|
+
|
|
196
|
+
# Set the mssql_unicode_strings settings from the given options.
|
|
197
|
+
def set_mssql_unicode_strings
|
|
198
|
+
@mssql_unicode_strings = typecast_value_boolean(@opts.fetch(:mssql_unicode_strings, true))
|
|
199
|
+
end
|
|
166
200
|
|
|
167
201
|
# MSSQL has both datetime and timestamp classes, most people are going
|
|
168
202
|
# to want datetime
|
|
@@ -199,6 +233,8 @@ module Sequel
|
|
|
199
233
|
end
|
|
200
234
|
|
|
201
235
|
module DatasetMethods
|
|
236
|
+
include EmulateOffsetWithRowNumber
|
|
237
|
+
|
|
202
238
|
BOOL_TRUE = '1'.freeze
|
|
203
239
|
BOOL_FALSE = '0'.freeze
|
|
204
240
|
COMMA_SEPARATOR = ', '.freeze
|
|
@@ -210,6 +246,16 @@ module Sequel
|
|
|
210
246
|
UPDLOCK = ' WITH (UPDLOCK)'.freeze
|
|
211
247
|
WILDCARD = LiteralString.new('*').freeze
|
|
212
248
|
CONSTANT_MAP = {:CURRENT_DATE=>'CAST(CURRENT_TIMESTAMP AS DATE)'.freeze, :CURRENT_TIME=>'CAST(CURRENT_TIMESTAMP AS TIME)'.freeze}
|
|
249
|
+
EXTRACT_MAP = {:year=>"yy", :month=>"m", :day=>"d", :hour=>"hh", :minute=>"n", :second=>"s"}
|
|
250
|
+
|
|
251
|
+
# Allow overriding of the mssql_unicode_strings option at the dataset level.
|
|
252
|
+
attr_accessor :mssql_unicode_strings
|
|
253
|
+
|
|
254
|
+
# Copy the mssql_unicode_strings option from the +db+ object.
|
|
255
|
+
def initialize(db, opts={})
|
|
256
|
+
super
|
|
257
|
+
@mssql_unicode_strings = db.mssql_unicode_strings
|
|
258
|
+
end
|
|
213
259
|
|
|
214
260
|
# MSSQL uses + for string concatenation, and LIKE is case insensitive by default.
|
|
215
261
|
def complex_expression_sql(op, args)
|
|
@@ -224,6 +270,13 @@ module Sequel
|
|
|
224
270
|
"(#{literal(args[0])} * POWER(2, #{literal(args[1])}))"
|
|
225
271
|
when :>>
|
|
226
272
|
"(#{literal(args[0])} / POWER(2, #{literal(args[1])}))"
|
|
273
|
+
when :extract
|
|
274
|
+
part = args.at(0)
|
|
275
|
+
raise(Sequel::Error, "unsupported extract argument: #{part.inspect}") unless format = EXTRACT_MAP[part]
|
|
276
|
+
expr = literal(args.at(1))
|
|
277
|
+
s = "datepart(#{format}, #{expr})"
|
|
278
|
+
s = "CAST((#{s} + datepart(ns, #{expr})/1000000000.0) AS double precision)" if part == :second
|
|
279
|
+
s
|
|
227
280
|
else
|
|
228
281
|
super(op, args)
|
|
229
282
|
end
|
|
@@ -257,8 +310,8 @@ module Sequel
|
|
|
257
310
|
|
|
258
311
|
# Use the OUTPUT clause to get the value of all columns for the newly inserted record.
|
|
259
312
|
def insert_select(*values)
|
|
260
|
-
return unless
|
|
261
|
-
naked.clone(default_server_opts(:sql=>output(nil, [:inserted
|
|
313
|
+
return unless supports_insert_select?
|
|
314
|
+
naked.clone(default_server_opts(:sql=>output(nil, [SQL::ColumnAll.new(:inserted)]).insert_sql(*values))).single_record
|
|
262
315
|
end
|
|
263
316
|
|
|
264
317
|
# Specify a table for a SELECT ... INTO query.
|
|
@@ -266,14 +319,6 @@ module Sequel
|
|
|
266
319
|
clone(:into => table)
|
|
267
320
|
end
|
|
268
321
|
|
|
269
|
-
# SQL Server does not support CTEs on subqueries, so move any CTEs
|
|
270
|
-
# on joined datasets to the top level. The user is responsible for
|
|
271
|
-
# resolving any name clashes this may cause.
|
|
272
|
-
def join_table(type, table, expr=nil, table_alias={}, &block)
|
|
273
|
-
return super unless Dataset === table && table.opts[:with]
|
|
274
|
-
clone(:with => (opts[:with] || []) + table.opts[:with]).join_table(type, table.clone(:with => nil), expr, table_alias, &block)
|
|
275
|
-
end
|
|
276
|
-
|
|
277
322
|
# MSSQL uses a UNION ALL statement to insert multiple values at once.
|
|
278
323
|
def multi_insert_sql(columns, values)
|
|
279
324
|
[insert_sql(columns, LiteralString.new(values.map {|r| "SELECT #{expression_list(r)}" }.join(" UNION ALL ")))]
|
|
@@ -319,34 +364,16 @@ module Sequel
|
|
|
319
364
|
"[#{name}]"
|
|
320
365
|
end
|
|
321
366
|
|
|
322
|
-
# MSSQL Requires the use of the ROW_NUMBER window function to emulate
|
|
323
|
-
# an offset. This implementation requires MSSQL 2005 or greater (offset
|
|
324
|
-
# can't be emulated well in MSSQL 2000).
|
|
325
|
-
#
|
|
326
|
-
# The implementation is ugly, cloning the current dataset and modifying
|
|
327
|
-
# the clone to add a ROW_NUMBER window function (and some other things),
|
|
328
|
-
# then using the modified clone in a subselect which is selected from.
|
|
329
|
-
#
|
|
330
|
-
# If offset is used, an order must be provided, because the use of ROW_NUMBER
|
|
331
|
-
# requires an order.
|
|
332
|
-
def select_sql
|
|
333
|
-
return super unless o = @opts[:offset]
|
|
334
|
-
raise(Error, 'MSSQL requires an order be provided if using an offset') unless order = @opts[:order]
|
|
335
|
-
dsa1 = dataset_alias(1)
|
|
336
|
-
rn = row_number_column
|
|
337
|
-
subselect_sql(unlimited.
|
|
338
|
-
unordered.
|
|
339
|
-
select_append{ROW_NUMBER(:over, :order=>order){}.as(rn)}.
|
|
340
|
-
from_self(:alias=>dsa1).
|
|
341
|
-
limit(@opts[:limit]).
|
|
342
|
-
where(SQL::Identifier.new(rn) > o))
|
|
343
|
-
end
|
|
344
|
-
|
|
345
367
|
# The version of the database server.
|
|
346
368
|
def server_version
|
|
347
369
|
db.server_version(@opts[:server])
|
|
348
370
|
end
|
|
349
371
|
|
|
372
|
+
# MSSQL supports insert_select via the OUTPUT clause.
|
|
373
|
+
def supports_insert_select?
|
|
374
|
+
supports_output_clause? && !opts[:disable_insert_output]
|
|
375
|
+
end
|
|
376
|
+
|
|
350
377
|
# MSSQL 2005+ supports INTERSECT and EXCEPT
|
|
351
378
|
def supports_intersect_except?
|
|
352
379
|
is_2005_or_later?
|
|
@@ -381,6 +408,11 @@ module Sequel
|
|
|
381
408
|
def supports_window_functions?
|
|
382
409
|
true
|
|
383
410
|
end
|
|
411
|
+
|
|
412
|
+
# MSSQL cannot use WHERE 1.
|
|
413
|
+
def supports_where_true?
|
|
414
|
+
false
|
|
415
|
+
end
|
|
384
416
|
|
|
385
417
|
protected
|
|
386
418
|
# MSSQL does not allow ordering in sub-clauses unless 'top' (limit) is specified
|
|
@@ -389,6 +421,7 @@ module Sequel
|
|
|
389
421
|
end
|
|
390
422
|
|
|
391
423
|
private
|
|
424
|
+
|
|
392
425
|
def is_2005_or_later?
|
|
393
426
|
server_version >= 9000000
|
|
394
427
|
end
|
|
@@ -434,6 +467,16 @@ module Sequel
|
|
|
434
467
|
INSERT_CLAUSE_METHODS
|
|
435
468
|
end
|
|
436
469
|
|
|
470
|
+
# Use OUTPUT INSERTED.* to return all columns of the inserted row,
|
|
471
|
+
# for use with the prepared statement code.
|
|
472
|
+
def insert_output_sql(sql)
|
|
473
|
+
if @opts.has_key?(:returning)
|
|
474
|
+
sql << " OUTPUT INSERTED.*"
|
|
475
|
+
else
|
|
476
|
+
output_sql(sql)
|
|
477
|
+
end
|
|
478
|
+
end
|
|
479
|
+
|
|
437
480
|
# MSSQL uses a literal hexidecimal number for blob strings
|
|
438
481
|
def literal_blob(v)
|
|
439
482
|
blob = '0x'
|
|
@@ -441,9 +484,10 @@ module Sequel
|
|
|
441
484
|
blob
|
|
442
485
|
end
|
|
443
486
|
|
|
444
|
-
#
|
|
487
|
+
# Optionally use unicode string syntax for all strings. Don't double
|
|
488
|
+
# backslashes.
|
|
445
489
|
def literal_string(v)
|
|
446
|
-
"N'#{v.gsub(/'/, "''")}'"
|
|
490
|
+
"#{'N' if mssql_unicode_strings}'#{v.gsub(/'/, "''")}'"
|
|
447
491
|
end
|
|
448
492
|
|
|
449
493
|
# Use 0 for false on MSSQL
|
|
@@ -456,11 +500,6 @@ module Sequel
|
|
|
456
500
|
BOOL_TRUE
|
|
457
501
|
end
|
|
458
502
|
|
|
459
|
-
# The alias to use for the row_number column when emulating OFFSET
|
|
460
|
-
def row_number_column
|
|
461
|
-
:x_sequel_row_number_x
|
|
462
|
-
end
|
|
463
|
-
|
|
464
503
|
# MSSQL adds the limit before the columns
|
|
465
504
|
def select_clause_methods
|
|
466
505
|
SELECT_CLAUSE_METHODS
|
|
@@ -508,7 +547,6 @@ module Sequel
|
|
|
508
547
|
end
|
|
509
548
|
alias delete_output_sql output_sql
|
|
510
549
|
alias update_output_sql output_sql
|
|
511
|
-
alias insert_output_sql output_sql
|
|
512
550
|
|
|
513
551
|
# MSSQL supports the OUTPUT clause for UPDATE statements.
|
|
514
552
|
# It also allows prepending a WITH clause.
|
|
@@ -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
|
|
@@ -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
|
|
@@ -312,6 +342,13 @@ module Sequel
|
|
|
312
342
|
# string concatenation.
|
|
313
343
|
def complex_expression_sql(op, args)
|
|
314
344
|
case op
|
|
345
|
+
when :IN, :"NOT IN"
|
|
346
|
+
ds = args.at(1)
|
|
347
|
+
if ds.is_a?(Sequel::Dataset) && ds.opts[:limit]
|
|
348
|
+
super(op, [args.at(0), ds.from_self])
|
|
349
|
+
else
|
|
350
|
+
super
|
|
351
|
+
end
|
|
315
352
|
when :~, :'!~', :'~*', :'!~*', :LIKE, :'NOT LIKE', :ILIKE, :'NOT ILIKE'
|
|
316
353
|
"(#{literal(args.at(0))} #{'NOT ' if [:'NOT LIKE', :'NOT ILIKE', :'!~', :'!~*'].include?(op)}#{[:~, :'!~', :'~*', :'!~*'].include?(op) ? 'REGEXP' : 'LIKE'} #{'BINARY ' if [:~, :'!~', :LIKE, :'NOT LIKE'].include?(op)}#{literal(args.at(1))})"
|
|
317
354
|
when :'||'
|
|
@@ -451,6 +488,12 @@ module Sequel
|
|
|
451
488
|
true
|
|
452
489
|
end
|
|
453
490
|
|
|
491
|
+
# MySQL's DISTINCT ON emulation using GROUP BY does not respect the
|
|
492
|
+
# queries ORDER BY clause.
|
|
493
|
+
def supports_ordered_distinct_on?
|
|
494
|
+
false
|
|
495
|
+
end
|
|
496
|
+
|
|
454
497
|
# MySQL does support fractional timestamps in literal timestamps, but it
|
|
455
498
|
# ignores them. Also, using them seems to cause problems on 1.9. Since
|
|
456
499
|
# they are ignored anyway, not using them is probably best.
|
|
@@ -0,0 +1,143 @@
|
|
|
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(ds)
|
|
67
|
+
ps = ds.to_prepared_statement(:select)
|
|
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
|
+
private
|
|
80
|
+
|
|
81
|
+
# Execute the prepared statement with the bind arguments instead of
|
|
82
|
+
# the given SQL.
|
|
83
|
+
def execute(sql, opts={}, &block)
|
|
84
|
+
super(prepared_statement_name, {:arguments=>bind_arguments}.merge(opts), &block)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Same as execute, explicit due to intricacies of alias and super.
|
|
88
|
+
def execute_dui(sql, opts={}, &block)
|
|
89
|
+
super(prepared_statement_name, {:arguments=>bind_arguments}.merge(opts), &block)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Methods for MySQL stored procedures using the native driver.
|
|
94
|
+
module StoredProcedureMethods
|
|
95
|
+
include Sequel::Dataset::StoredProcedureMethods
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
# Execute the database stored procedure with the stored arguments.
|
|
100
|
+
def execute(sql, opts={}, &block)
|
|
101
|
+
super(@sproc_name, {:args=>@sproc_args, :sproc=>true}.merge(opts), &block)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Same as execute, explicit due to intricacies of alias and super.
|
|
105
|
+
def execute_dui(sql, opts={}, &block)
|
|
106
|
+
super(@sproc_name, {:args=>@sproc_args, :sproc=>true}.merge(opts), &block)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# MySQL is different in that it supports prepared statements but not bound
|
|
111
|
+
# variables outside of prepared statements. The default implementation
|
|
112
|
+
# breaks the use of subselects in prepared statements, so extend the
|
|
113
|
+
# temporary prepared statement that this creates with a module that
|
|
114
|
+
# fixes it.
|
|
115
|
+
def call(type, bind_arguments={}, *values, &block)
|
|
116
|
+
ps = to_prepared_statement(type, values)
|
|
117
|
+
ps.extend(CallableStatementMethods)
|
|
118
|
+
ps.call(bind_arguments, &block)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Store the given type of prepared statement in the associated database
|
|
122
|
+
# with the given name.
|
|
123
|
+
def prepare(type, name=nil, *values)
|
|
124
|
+
ps = to_prepared_statement(type, values)
|
|
125
|
+
ps.extend(PreparedStatementMethods)
|
|
126
|
+
if name
|
|
127
|
+
ps.prepared_statement_name = name
|
|
128
|
+
db.prepared_statements[name] = ps
|
|
129
|
+
end
|
|
130
|
+
ps
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
private
|
|
134
|
+
|
|
135
|
+
# Extend the dataset with the MySQL stored procedure methods.
|
|
136
|
+
def prepare_extend_sproc(ds)
|
|
137
|
+
ds.extend(StoredProcedureMethods)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -26,10 +26,6 @@ module Sequel
|
|
|
26
26
|
ds.map{|r| ds.send(:output_identifier, r[:tname])}
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
def table_exists?(name)
|
|
30
|
-
from(:tab).filter(:tname =>dataset.send(:input_identifier, name), :tabtype => 'TABLE').count > 0
|
|
31
|
-
end
|
|
32
|
-
|
|
33
29
|
def views(opts={})
|
|
34
30
|
ds = from(:tab).server(opts[:server]).select(:tname).filter(:tabtype => 'VIEW')
|
|
35
31
|
ds.map{|r| ds.send(:output_identifier, r[:tname])}
|