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.
Files changed (225) hide show
  1. data/CHANGELOG +485 -3
  2. data/README.rdoc +20 -6
  3. data/Rakefile +37 -19
  4. data/bin/sequel +1 -5
  5. data/doc/association_basics.rdoc +249 -43
  6. data/doc/dataset_basics.rdoc +4 -4
  7. data/doc/dataset_filtering.rdoc +8 -0
  8. data/doc/mass_assignment.rdoc +54 -0
  9. data/doc/migration.rdoc +15 -538
  10. data/doc/model_hooks.rdoc +72 -27
  11. data/doc/opening_databases.rdoc +89 -50
  12. data/doc/prepared_statements.rdoc +40 -13
  13. data/doc/reflection.rdoc +8 -2
  14. data/doc/release_notes/3.22.0.txt +39 -0
  15. data/doc/release_notes/3.23.0.txt +172 -0
  16. data/doc/release_notes/3.24.0.txt +420 -0
  17. data/doc/release_notes/3.25.0.txt +88 -0
  18. data/doc/release_notes/3.26.0.txt +88 -0
  19. data/doc/release_notes/3.27.0.txt +82 -0
  20. data/doc/release_notes/3.28.0.txt +304 -0
  21. data/doc/release_notes/3.29.0.txt +459 -0
  22. data/doc/release_notes/3.30.0.txt +135 -0
  23. data/doc/release_notes/3.31.0.txt +146 -0
  24. data/doc/schema_modification.rdoc +547 -0
  25. data/doc/sharding.rdoc +7 -1
  26. data/doc/testing.rdoc +129 -0
  27. data/doc/transactions.rdoc +137 -0
  28. data/doc/virtual_rows.rdoc +2 -2
  29. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  30. data/lib/sequel/adapters/ado.rb +40 -18
  31. data/lib/sequel/adapters/amalgalite.rb +17 -9
  32. data/lib/sequel/adapters/db2.rb +175 -86
  33. data/lib/sequel/adapters/dbi.rb +15 -15
  34. data/lib/sequel/adapters/do/mysql.rb +5 -7
  35. data/lib/sequel/adapters/do/postgres.rb +0 -5
  36. data/lib/sequel/adapters/do/sqlite.rb +0 -5
  37. data/lib/sequel/adapters/do.rb +17 -36
  38. data/lib/sequel/adapters/firebird.rb +27 -208
  39. data/lib/sequel/adapters/ibmdb.rb +448 -0
  40. data/lib/sequel/adapters/informix.rb +6 -23
  41. data/lib/sequel/adapters/jdbc/as400.rb +16 -34
  42. data/lib/sequel/adapters/jdbc/db2.rb +45 -0
  43. data/lib/sequel/adapters/jdbc/derby.rb +247 -0
  44. data/lib/sequel/adapters/jdbc/firebird.rb +29 -0
  45. data/lib/sequel/adapters/jdbc/h2.rb +56 -29
  46. data/lib/sequel/adapters/jdbc/hsqldb.rb +170 -0
  47. data/lib/sequel/adapters/jdbc/informix.rb +26 -0
  48. data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
  49. data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
  50. data/lib/sequel/adapters/jdbc/mysql.rb +11 -11
  51. data/lib/sequel/adapters/jdbc/oracle.rb +81 -25
  52. data/lib/sequel/adapters/jdbc/postgresql.rb +9 -13
  53. data/lib/sequel/adapters/jdbc/sqlite.rb +2 -6
  54. data/lib/sequel/adapters/jdbc/sqlserver.rb +71 -0
  55. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  56. data/lib/sequel/adapters/jdbc.rb +231 -78
  57. data/lib/sequel/adapters/mock.rb +335 -0
  58. data/lib/sequel/adapters/mysql.rb +82 -184
  59. data/lib/sequel/adapters/mysql2.rb +26 -25
  60. data/lib/sequel/adapters/odbc/db2.rb +17 -0
  61. data/lib/sequel/adapters/odbc/mssql.rb +1 -6
  62. data/lib/sequel/adapters/odbc.rb +28 -9
  63. data/lib/sequel/adapters/openbase.rb +3 -5
  64. data/lib/sequel/adapters/oracle.rb +351 -53
  65. data/lib/sequel/adapters/postgres.rb +194 -33
  66. data/lib/sequel/adapters/shared/access.rb +32 -9
  67. data/lib/sequel/adapters/shared/db2.rb +321 -0
  68. data/lib/sequel/adapters/shared/firebird.rb +221 -0
  69. data/lib/sequel/adapters/shared/informix.rb +53 -0
  70. data/lib/sequel/adapters/shared/mssql.rb +231 -106
  71. data/lib/sequel/adapters/shared/mysql.rb +181 -63
  72. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +149 -0
  73. data/lib/sequel/adapters/shared/oracle.rb +231 -41
  74. data/lib/sequel/adapters/shared/postgres.rb +214 -82
  75. data/lib/sequel/adapters/shared/progress.rb +4 -7
  76. data/lib/sequel/adapters/shared/sqlite.rb +201 -57
  77. data/lib/sequel/adapters/sqlite.rb +29 -21
  78. data/lib/sequel/adapters/swift/mysql.rb +12 -7
  79. data/lib/sequel/adapters/swift/postgres.rb +0 -5
  80. data/lib/sequel/adapters/swift/sqlite.rb +8 -6
  81. data/lib/sequel/adapters/swift.rb +5 -5
  82. data/lib/sequel/adapters/tinytds.rb +160 -19
  83. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +66 -0
  84. data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
  85. data/lib/sequel/ast_transformer.rb +190 -0
  86. data/lib/sequel/connection_pool/threaded.rb +3 -2
  87. data/lib/sequel/connection_pool.rb +1 -1
  88. data/lib/sequel/core.rb +46 -5
  89. data/lib/sequel/database/connecting.rb +5 -6
  90. data/lib/sequel/database/dataset.rb +4 -4
  91. data/lib/sequel/database/dataset_defaults.rb +59 -1
  92. data/lib/sequel/database/logging.rb +1 -1
  93. data/lib/sequel/database/misc.rb +104 -19
  94. data/lib/sequel/database/query.rb +153 -58
  95. data/lib/sequel/database/schema_generator.rb +17 -7
  96. data/lib/sequel/database/schema_methods.rb +59 -29
  97. data/lib/sequel/dataset/actions.rb +231 -76
  98. data/lib/sequel/dataset/features.rb +91 -8
  99. data/lib/sequel/dataset/graph.rb +38 -11
  100. data/lib/sequel/dataset/misc.rb +50 -23
  101. data/lib/sequel/dataset/mutation.rb +5 -6
  102. data/lib/sequel/dataset/prepared_statements.rb +48 -27
  103. data/lib/sequel/dataset/query.rb +236 -43
  104. data/lib/sequel/dataset/sql.rb +665 -253
  105. data/lib/sequel/dataset.rb +8 -0
  106. data/lib/sequel/exceptions.rb +4 -0
  107. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  108. data/lib/sequel/extensions/migration.rb +6 -4
  109. data/lib/sequel/extensions/named_timezones.rb +5 -0
  110. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  111. data/lib/sequel/extensions/to_dot.rb +95 -83
  112. data/lib/sequel/model/associations.rb +1020 -337
  113. data/lib/sequel/model/base.rb +414 -132
  114. data/lib/sequel/model/errors.rb +1 -1
  115. data/lib/sequel/model/exceptions.rb +5 -1
  116. data/lib/sequel/model.rb +15 -8
  117. data/lib/sequel/plugins/association_pks.rb +22 -4
  118. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  119. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  120. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  121. data/lib/sequel/plugins/force_encoding.rb +6 -6
  122. data/lib/sequel/plugins/identity_map.rb +114 -7
  123. data/lib/sequel/plugins/many_through_many.rb +65 -7
  124. data/lib/sequel/plugins/prepared_statements.rb +151 -0
  125. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  126. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  127. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  128. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  129. data/lib/sequel/plugins/serialization.rb +88 -44
  130. data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
  131. data/lib/sequel/plugins/sharding.rb +21 -54
  132. data/lib/sequel/plugins/single_table_inheritance.rb +3 -1
  133. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  134. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  135. data/lib/sequel/plugins/xml_serializer.rb +3 -3
  136. data/lib/sequel/sql.rb +109 -52
  137. data/lib/sequel/timezones.rb +57 -38
  138. data/lib/sequel/version.rb +1 -1
  139. data/spec/adapters/db2_spec.rb +146 -0
  140. data/spec/adapters/mssql_spec.rb +111 -29
  141. data/spec/adapters/mysql_spec.rb +135 -123
  142. data/spec/adapters/oracle_spec.rb +49 -77
  143. data/spec/adapters/postgres_spec.rb +267 -78
  144. data/spec/adapters/spec_helper.rb +6 -5
  145. data/spec/adapters/sqlite_spec.rb +35 -21
  146. data/spec/core/connection_pool_spec.rb +71 -92
  147. data/spec/core/core_sql_spec.rb +21 -30
  148. data/spec/core/database_spec.rb +788 -512
  149. data/spec/core/dataset_spec.rb +1073 -1053
  150. data/spec/core/expression_filters_spec.rb +159 -42
  151. data/spec/core/mock_adapter_spec.rb +419 -0
  152. data/spec/core/object_graph_spec.rb +86 -114
  153. data/spec/core/schema_generator_spec.rb +3 -3
  154. data/spec/core/schema_spec.rb +298 -38
  155. data/spec/core/spec_helper.rb +6 -48
  156. data/spec/extensions/association_autoreloading_spec.rb +1 -10
  157. data/spec/extensions/association_dependencies_spec.rb +2 -12
  158. data/spec/extensions/association_pks_spec.rb +66 -32
  159. data/spec/extensions/caching_spec.rb +23 -50
  160. data/spec/extensions/class_table_inheritance_spec.rb +31 -83
  161. data/spec/extensions/columns_introspection_spec.rb +91 -0
  162. data/spec/extensions/composition_spec.rb +18 -13
  163. data/spec/extensions/dataset_associations_spec.rb +199 -0
  164. data/spec/extensions/defaults_setter_spec.rb +64 -0
  165. data/spec/extensions/hook_class_methods_spec.rb +65 -91
  166. data/spec/extensions/identity_map_spec.rb +114 -22
  167. data/spec/extensions/instance_filters_spec.rb +11 -21
  168. data/spec/extensions/instance_hooks_spec.rb +53 -0
  169. data/spec/extensions/json_serializer_spec.rb +4 -5
  170. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  171. data/spec/extensions/list_spec.rb +17 -39
  172. data/spec/extensions/many_through_many_spec.rb +283 -250
  173. data/spec/extensions/migration_spec.rb +35 -32
  174. data/spec/extensions/named_timezones_spec.rb +23 -3
  175. data/spec/extensions/nested_attributes_spec.rb +97 -88
  176. data/spec/extensions/optimistic_locking_spec.rb +9 -20
  177. data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
  178. data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
  179. data/spec/extensions/prepared_statements_spec.rb +53 -0
  180. data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
  181. data/spec/extensions/pretty_table_spec.rb +1 -6
  182. data/spec/extensions/rcte_tree_spec.rb +41 -43
  183. data/spec/extensions/schema_dumper_spec.rb +5 -8
  184. data/spec/extensions/schema_spec.rb +13 -21
  185. data/spec/extensions/serialization_modification_detection_spec.rb +37 -0
  186. data/spec/extensions/serialization_spec.rb +72 -43
  187. data/spec/extensions/sharding_spec.rb +66 -140
  188. data/spec/extensions/single_table_inheritance_spec.rb +18 -29
  189. data/spec/extensions/spec_helper.rb +13 -61
  190. data/spec/extensions/sql_expr_spec.rb +20 -60
  191. data/spec/extensions/tactical_eager_loading_spec.rb +9 -19
  192. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  193. data/spec/extensions/timestamps_spec.rb +6 -6
  194. data/spec/extensions/to_dot_spec.rb +4 -7
  195. data/spec/extensions/touch_spec.rb +13 -14
  196. data/spec/extensions/tree_spec.rb +11 -26
  197. data/spec/extensions/typecast_on_load_spec.rb +1 -6
  198. data/spec/extensions/update_primary_key_spec.rb +30 -24
  199. data/spec/extensions/validation_class_methods_spec.rb +30 -51
  200. data/spec/extensions/validation_helpers_spec.rb +16 -35
  201. data/spec/extensions/xml_serializer_spec.rb +16 -4
  202. data/spec/integration/associations_test.rb +537 -21
  203. data/spec/integration/database_test.rb +4 -2
  204. data/spec/integration/dataset_test.rb +461 -65
  205. data/spec/integration/eager_loader_test.rb +19 -21
  206. data/spec/integration/model_test.rb +80 -1
  207. data/spec/integration/plugin_test.rb +304 -116
  208. data/spec/integration/prepared_statement_test.rb +212 -122
  209. data/spec/integration/schema_test.rb +161 -30
  210. data/spec/integration/spec_helper.rb +39 -30
  211. data/spec/integration/timezone_test.rb +38 -12
  212. data/spec/integration/transaction_test.rb +172 -5
  213. data/spec/integration/type_test.rb +17 -3
  214. data/spec/model/association_reflection_spec.rb +201 -7
  215. data/spec/model/associations_spec.rb +865 -662
  216. data/spec/model/base_spec.rb +275 -95
  217. data/spec/model/dataset_methods_spec.rb +7 -27
  218. data/spec/model/eager_loading_spec.rb +677 -676
  219. data/spec/model/hooks_spec.rb +309 -67
  220. data/spec/model/model_spec.rb +222 -168
  221. data/spec/model/plugins_spec.rb +24 -13
  222. data/spec/model/record_spec.rb +381 -218
  223. data/spec/model/spec_helper.rb +13 -71
  224. data/spec/model/validations_spec.rb +11 -0
  225. 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
- # 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
@@ -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
- conn
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 complex_expression_sql(op, args)
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
- "(#{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))})"
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
- "CONCAT(#{args.collect{|a| literal(a)}.join(', ')})"
390
+ sql << CONCAT
391
+ array_sql_append(sql, args)
320
392
  else
321
- literal(args.at(0))
393
+ literal_append(sql, args.at(0))
322
394
  end
323
395
  when :'B~'
324
- "CAST(~#{literal(args.at(0))} AS SIGNED INTEGER)"
396
+ sql << CAST_BITCOMP_OPEN
397
+ literal_append(sql, args.at(0))
398
+ sql << CAST_BITCOMP_CLOSE
325
399
  else
326
- super(op, args)
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, term, opts = {})
355
- "MATCH #{literal(Array(cols))} AGAINST (#{literal(Array(term).join(' '))}#{" IN BOOLEAN MODE" if opts[:boolean]})"
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 then 'STRAIGHT_JOIN'
376
- when :natural_inner then 'NATURAL LEFT JOIN'
377
- else super
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
- [insert_sql(columns, LiteralString.new('VALUES ' + values.map {|r| literal(Array(r))}.join(COMMA_SEPARATOR)))]
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 quoted_identifier(c)
429
- "`#{c}`"
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 << " #{source_list(@opts[:from][0..0])} FROM #{source_list(@opts[:from])}"
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 << " IGNORE" if opts[:insert_ignore]
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
- sql << on_duplicate_key_update_sql if opts[:on_duplicate_key_update]
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 << " VALUES ()"
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
- sql << " LIMIT #{@opts[:limit]}" if @opts[:limit]
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