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.
Files changed (158) hide show
  1. data/CHANGELOG +294 -0
  2. data/README.rdoc +20 -6
  3. data/Rakefile +20 -15
  4. data/doc/association_basics.rdoc +210 -43
  5. data/doc/dataset_basics.rdoc +4 -4
  6. data/doc/mass_assignment.rdoc +54 -0
  7. data/doc/migration.rdoc +15 -538
  8. data/doc/model_hooks.rdoc +64 -27
  9. data/doc/opening_databases.rdoc +37 -10
  10. data/doc/prepared_statements.rdoc +16 -10
  11. data/doc/reflection.rdoc +8 -2
  12. data/doc/release_notes/3.22.0.txt +39 -0
  13. data/doc/release_notes/3.23.0.txt +172 -0
  14. data/doc/release_notes/3.24.0.txt +420 -0
  15. data/doc/release_notes/3.25.0.txt +88 -0
  16. data/doc/release_notes/3.26.0.txt +88 -0
  17. data/doc/release_notes/3.27.0.txt +82 -0
  18. data/doc/release_notes/3.28.0.txt +304 -0
  19. data/doc/schema_modification.rdoc +547 -0
  20. data/doc/testing.rdoc +106 -0
  21. data/doc/transactions.rdoc +97 -0
  22. data/doc/virtual_rows.rdoc +2 -2
  23. data/lib/sequel/adapters/ado.rb +12 -1
  24. data/lib/sequel/adapters/amalgalite.rb +4 -0
  25. data/lib/sequel/adapters/db2.rb +95 -58
  26. data/lib/sequel/adapters/do.rb +12 -0
  27. data/lib/sequel/adapters/firebird.rb +25 -203
  28. data/lib/sequel/adapters/ibmdb.rb +440 -0
  29. data/lib/sequel/adapters/informix.rb +4 -19
  30. data/lib/sequel/adapters/jdbc/as400.rb +0 -7
  31. data/lib/sequel/adapters/jdbc/db2.rb +49 -0
  32. data/lib/sequel/adapters/jdbc/firebird.rb +34 -0
  33. data/lib/sequel/adapters/jdbc/h2.rb +16 -5
  34. data/lib/sequel/adapters/jdbc/informix.rb +31 -0
  35. data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
  36. data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
  37. data/lib/sequel/adapters/jdbc/mysql.rb +9 -0
  38. data/lib/sequel/adapters/jdbc/oracle.rb +2 -27
  39. data/lib/sequel/adapters/jdbc/postgresql.rb +6 -0
  40. data/lib/sequel/adapters/jdbc/sqlserver.rb +46 -0
  41. data/lib/sequel/adapters/jdbc/transactions.rb +34 -0
  42. data/lib/sequel/adapters/jdbc.rb +62 -29
  43. data/lib/sequel/adapters/mysql.rb +22 -139
  44. data/lib/sequel/adapters/mysql2.rb +9 -14
  45. data/lib/sequel/adapters/odbc/db2.rb +21 -0
  46. data/lib/sequel/adapters/odbc.rb +15 -3
  47. data/lib/sequel/adapters/oracle.rb +17 -1
  48. data/lib/sequel/adapters/postgres.rb +111 -16
  49. data/lib/sequel/adapters/shared/access.rb +21 -0
  50. data/lib/sequel/adapters/shared/db2.rb +290 -0
  51. data/lib/sequel/adapters/shared/firebird.rb +214 -0
  52. data/lib/sequel/adapters/shared/informix.rb +45 -0
  53. data/lib/sequel/adapters/shared/mssql.rb +85 -47
  54. data/lib/sequel/adapters/shared/mysql.rb +50 -7
  55. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +143 -0
  56. data/lib/sequel/adapters/shared/oracle.rb +0 -4
  57. data/lib/sequel/adapters/shared/postgres.rb +75 -43
  58. data/lib/sequel/adapters/shared/sqlite.rb +56 -8
  59. data/lib/sequel/adapters/sqlite.rb +12 -11
  60. data/lib/sequel/adapters/swift/mysql.rb +9 -0
  61. data/lib/sequel/adapters/tinytds.rb +139 -7
  62. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +55 -0
  63. data/lib/sequel/ast_transformer.rb +190 -0
  64. data/lib/sequel/connection_pool/threaded.rb +3 -2
  65. data/lib/sequel/connection_pool.rb +1 -1
  66. data/lib/sequel/core.rb +6 -5
  67. data/lib/sequel/database/connecting.rb +5 -5
  68. data/lib/sequel/database/dataset.rb +1 -1
  69. data/lib/sequel/database/dataset_defaults.rb +1 -1
  70. data/lib/sequel/database/logging.rb +1 -1
  71. data/lib/sequel/database/misc.rb +38 -17
  72. data/lib/sequel/database/query.rb +50 -19
  73. data/lib/sequel/database/schema_generator.rb +8 -5
  74. data/lib/sequel/database/schema_methods.rb +52 -27
  75. data/lib/sequel/dataset/actions.rb +167 -48
  76. data/lib/sequel/dataset/features.rb +57 -8
  77. data/lib/sequel/dataset/graph.rb +1 -1
  78. data/lib/sequel/dataset/misc.rb +39 -20
  79. data/lib/sequel/dataset/mutation.rb +3 -3
  80. data/lib/sequel/dataset/prepared_statements.rb +29 -14
  81. data/lib/sequel/dataset/query.rb +182 -32
  82. data/lib/sequel/dataset/sql.rb +31 -58
  83. data/lib/sequel/dataset.rb +8 -0
  84. data/lib/sequel/exceptions.rb +4 -0
  85. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  86. data/lib/sequel/extensions/migration.rb +6 -4
  87. data/lib/sequel/extensions/to_dot.rb +95 -83
  88. data/lib/sequel/model/associations.rb +893 -309
  89. data/lib/sequel/model/base.rb +302 -105
  90. data/lib/sequel/model/errors.rb +1 -1
  91. data/lib/sequel/model/exceptions.rb +5 -1
  92. data/lib/sequel/model.rb +13 -7
  93. data/lib/sequel/plugins/association_pks.rb +22 -4
  94. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  95. data/lib/sequel/plugins/identity_map.rb +113 -6
  96. data/lib/sequel/plugins/many_through_many.rb +67 -5
  97. data/lib/sequel/plugins/prepared_statements.rb +140 -0
  98. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  99. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  100. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  101. data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
  102. data/lib/sequel/plugins/sharding.rb +12 -20
  103. data/lib/sequel/plugins/single_table_inheritance.rb +2 -0
  104. data/lib/sequel/plugins/update_primary_key.rb +1 -1
  105. data/lib/sequel/plugins/xml_serializer.rb +3 -3
  106. data/lib/sequel/sql.rb +107 -51
  107. data/lib/sequel/timezones.rb +12 -3
  108. data/lib/sequel/version.rb +1 -1
  109. data/spec/adapters/db2_spec.rb +146 -0
  110. data/spec/adapters/mssql_spec.rb +36 -0
  111. data/spec/adapters/mysql_spec.rb +36 -19
  112. data/spec/adapters/postgres_spec.rb +115 -28
  113. data/spec/adapters/spec_helper.rb +6 -0
  114. data/spec/adapters/sqlite_spec.rb +11 -0
  115. data/spec/core/connection_pool_spec.rb +62 -77
  116. data/spec/core/database_spec.rb +244 -287
  117. data/spec/core/dataset_spec.rb +383 -34
  118. data/spec/core/expression_filters_spec.rb +159 -41
  119. data/spec/core/schema_spec.rb +326 -3
  120. data/spec/core/spec_helper.rb +45 -0
  121. data/spec/extensions/association_pks_spec.rb +38 -0
  122. data/spec/extensions/columns_introspection_spec.rb +91 -0
  123. data/spec/extensions/defaults_setter_spec.rb +64 -0
  124. data/spec/extensions/identity_map_spec.rb +162 -0
  125. data/spec/extensions/many_through_many_spec.rb +195 -20
  126. data/spec/extensions/migration_spec.rb +17 -17
  127. data/spec/extensions/nested_attributes_spec.rb +1 -0
  128. data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
  129. data/spec/extensions/prepared_statements_safe_spec.rb +79 -0
  130. data/spec/extensions/prepared_statements_spec.rb +72 -0
  131. data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
  132. data/spec/extensions/schema_dumper_spec.rb +2 -2
  133. data/spec/extensions/schema_spec.rb +12 -20
  134. data/spec/extensions/serialization_modification_detection_spec.rb +36 -0
  135. data/spec/extensions/single_table_inheritance_spec.rb +11 -0
  136. data/spec/extensions/spec_helper.rb +3 -1
  137. data/spec/extensions/to_dot_spec.rb +3 -5
  138. data/spec/extensions/xml_serializer_spec.rb +16 -4
  139. data/spec/integration/associations_test.rb +405 -15
  140. data/spec/integration/database_test.rb +4 -2
  141. data/spec/integration/dataset_test.rb +240 -20
  142. data/spec/integration/plugin_test.rb +142 -5
  143. data/spec/integration/prepared_statement_test.rb +174 -95
  144. data/spec/integration/schema_test.rb +128 -16
  145. data/spec/integration/spec_helper.rb +15 -0
  146. data/spec/integration/transaction_test.rb +40 -0
  147. data/spec/integration/type_test.rb +16 -2
  148. data/spec/model/association_reflection_spec.rb +91 -0
  149. data/spec/model/associations_spec.rb +476 -5
  150. data/spec/model/base_spec.rb +91 -1
  151. data/spec/model/eager_loading_spec.rb +519 -31
  152. data/spec/model/hooks_spec.rb +161 -0
  153. data/spec/model/model_spec.rb +89 -2
  154. data/spec/model/plugins_spec.rb +17 -0
  155. data/spec/model/record_spec.rb +184 -12
  156. data/spec/model/spec_helper.rb +5 -0
  157. data/spec/model/validations_spec.rb +11 -0
  158. 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
- m = output_identifier_meth
51
- metadata_dataset.from(:information_schema__tables___t).
52
- select(:table_name).
53
- filter(:table_type=>'BASE TABLE', :table_schema=>(opts[:schema]||default_schema||'dbo').to_s).
54
- map{|x| m.call(x[:table_name])}
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 supports_output_clause?
261
- naked.clone(default_server_opts(:sql=>output(nil, [:inserted.*]).insert_sql(*values))).single_record unless opts[:disable_insert_output]
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
- # Use unicode string syntax for all strings. Don't double backslashes.
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
- # Return an array of symbols specifying table names in the current database.
87
- #
88
- # Options:
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])}