sequel 3.21.0 → 3.36.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 (311) hide show
  1. data/CHANGELOG +855 -3
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +23 -7
  4. data/Rakefile +42 -24
  5. data/bin/sequel +26 -8
  6. data/doc/active_record.rdoc +1 -0
  7. data/doc/advanced_associations.rdoc +171 -11
  8. data/doc/association_basics.rdoc +293 -43
  9. data/doc/dataset_basics.rdoc +5 -5
  10. data/doc/dataset_filtering.rdoc +8 -0
  11. data/doc/mass_assignment.rdoc +56 -0
  12. data/doc/migration.rdoc +85 -552
  13. data/doc/model_hooks.rdoc +78 -27
  14. data/doc/object_model.rdoc +541 -0
  15. data/doc/opening_databases.rdoc +97 -50
  16. data/doc/prepared_statements.rdoc +40 -14
  17. data/doc/querying.rdoc +28 -4
  18. data/doc/reflection.rdoc +30 -4
  19. data/doc/release_notes/3.22.0.txt +39 -0
  20. data/doc/release_notes/3.23.0.txt +172 -0
  21. data/doc/release_notes/3.24.0.txt +420 -0
  22. data/doc/release_notes/3.25.0.txt +88 -0
  23. data/doc/release_notes/3.26.0.txt +88 -0
  24. data/doc/release_notes/3.27.0.txt +82 -0
  25. data/doc/release_notes/3.28.0.txt +304 -0
  26. data/doc/release_notes/3.29.0.txt +459 -0
  27. data/doc/release_notes/3.30.0.txt +135 -0
  28. data/doc/release_notes/3.31.0.txt +146 -0
  29. data/doc/release_notes/3.32.0.txt +202 -0
  30. data/doc/release_notes/3.33.0.txt +157 -0
  31. data/doc/release_notes/3.34.0.txt +671 -0
  32. data/doc/release_notes/3.35.0.txt +144 -0
  33. data/doc/release_notes/3.36.0.txt +245 -0
  34. data/doc/schema_modification.rdoc +572 -0
  35. data/doc/sharding.rdoc +95 -3
  36. data/doc/testing.rdoc +129 -0
  37. data/doc/thread_safety.rdoc +17 -0
  38. data/doc/transactions.rdoc +137 -0
  39. data/doc/virtual_rows.rdoc +51 -2
  40. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  41. data/lib/sequel/adapters/ado.rb +40 -18
  42. data/lib/sequel/adapters/amalgalite.rb +17 -9
  43. data/lib/sequel/adapters/db2.rb +175 -86
  44. data/lib/sequel/adapters/dbi.rb +15 -15
  45. data/lib/sequel/adapters/do/mysql.rb +17 -11
  46. data/lib/sequel/adapters/do/postgres.rb +1 -57
  47. data/lib/sequel/adapters/do/sqlite.rb +0 -10
  48. data/lib/sequel/adapters/do.rb +18 -36
  49. data/lib/sequel/adapters/firebird.rb +27 -208
  50. data/lib/sequel/adapters/ibmdb.rb +453 -0
  51. data/lib/sequel/adapters/informix.rb +6 -23
  52. data/lib/sequel/adapters/jdbc/as400.rb +16 -34
  53. data/lib/sequel/adapters/jdbc/db2.rb +47 -0
  54. data/lib/sequel/adapters/jdbc/derby.rb +287 -0
  55. data/lib/sequel/adapters/jdbc/firebird.rb +24 -0
  56. data/lib/sequel/adapters/jdbc/h2.rb +73 -31
  57. data/lib/sequel/adapters/jdbc/hsqldb.rb +184 -0
  58. data/lib/sequel/adapters/jdbc/informix.rb +21 -0
  59. data/lib/sequel/adapters/jdbc/jtds.rb +29 -0
  60. data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
  61. data/lib/sequel/adapters/jdbc/mysql.rb +7 -24
  62. data/lib/sequel/adapters/jdbc/oracle.rb +88 -25
  63. data/lib/sequel/adapters/jdbc/postgresql.rb +58 -48
  64. data/lib/sequel/adapters/jdbc/progress.rb +21 -0
  65. data/lib/sequel/adapters/jdbc/sqlite.rb +20 -10
  66. data/lib/sequel/adapters/jdbc/sqlserver.rb +66 -0
  67. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  68. data/lib/sequel/adapters/jdbc.rb +265 -94
  69. data/lib/sequel/adapters/mock.rb +361 -0
  70. data/lib/sequel/adapters/mysql.rb +118 -222
  71. data/lib/sequel/adapters/mysql2.rb +58 -57
  72. data/lib/sequel/adapters/odbc/mssql.rb +15 -7
  73. data/lib/sequel/adapters/odbc.rb +28 -9
  74. data/lib/sequel/adapters/openbase.rb +3 -5
  75. data/lib/sequel/adapters/oracle.rb +349 -53
  76. data/lib/sequel/adapters/postgres.rb +356 -107
  77. data/lib/sequel/adapters/shared/access.rb +32 -9
  78. data/lib/sequel/adapters/shared/db2.rb +342 -0
  79. data/lib/sequel/adapters/shared/firebird.rb +221 -0
  80. data/lib/sequel/adapters/shared/informix.rb +53 -0
  81. data/lib/sequel/adapters/shared/mssql.rb +335 -118
  82. data/lib/sequel/adapters/shared/mysql.rb +340 -76
  83. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +155 -0
  84. data/lib/sequel/adapters/shared/oracle.rb +243 -45
  85. data/lib/sequel/adapters/shared/postgres.rb +524 -279
  86. data/lib/sequel/adapters/shared/progress.rb +5 -8
  87. data/lib/sequel/adapters/shared/sqlite.rb +265 -89
  88. data/lib/sequel/adapters/sqlite.rb +101 -39
  89. data/lib/sequel/adapters/swift/mysql.rb +9 -12
  90. data/lib/sequel/adapters/swift/postgres.rb +9 -74
  91. data/lib/sequel/adapters/swift/sqlite.rb +8 -6
  92. data/lib/sequel/adapters/swift.rb +8 -7
  93. data/lib/sequel/adapters/tinytds.rb +161 -21
  94. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +66 -0
  95. data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
  96. data/lib/sequel/ast_transformer.rb +194 -0
  97. data/lib/sequel/connection_pool/sharded_single.rb +11 -1
  98. data/lib/sequel/connection_pool/sharded_threaded.rb +28 -2
  99. data/lib/sequel/connection_pool/single.rb +5 -0
  100. data/lib/sequel/connection_pool/threaded.rb +38 -14
  101. data/lib/sequel/connection_pool.rb +10 -2
  102. data/lib/sequel/core.rb +118 -11
  103. data/lib/sequel/database/connecting.rb +38 -28
  104. data/lib/sequel/database/dataset.rb +4 -4
  105. data/lib/sequel/database/dataset_defaults.rb +63 -2
  106. data/lib/sequel/database/logging.rb +7 -2
  107. data/lib/sequel/database/misc.rb +150 -46
  108. data/lib/sequel/database/query.rb +215 -65
  109. data/lib/sequel/database/schema_generator.rb +64 -20
  110. data/lib/sequel/database/schema_methods.rb +212 -54
  111. data/lib/sequel/dataset/actions.rb +322 -85
  112. data/lib/sequel/dataset/features.rb +96 -8
  113. data/lib/sequel/dataset/graph.rb +38 -11
  114. data/lib/sequel/dataset/misc.rb +50 -23
  115. data/lib/sequel/dataset/mutation.rb +12 -7
  116. data/lib/sequel/dataset/prepared_statements.rb +68 -30
  117. data/lib/sequel/dataset/query.rb +237 -44
  118. data/lib/sequel/dataset/sql.rb +698 -278
  119. data/lib/sequel/dataset.rb +8 -0
  120. data/lib/sequel/exceptions.rb +4 -0
  121. data/lib/sequel/extensions/_pretty_table.rb +79 -0
  122. data/lib/sequel/extensions/arbitrary_servers.rb +108 -0
  123. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  124. data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +16 -34
  125. data/lib/sequel/extensions/migration.rb +97 -11
  126. data/lib/sequel/extensions/named_timezones.rb +5 -0
  127. data/lib/sequel/extensions/null_dataset.rb +90 -0
  128. data/lib/sequel/extensions/pg_array.rb +460 -0
  129. data/lib/sequel/extensions/pg_array_ops.rb +220 -0
  130. data/lib/sequel/extensions/pg_auto_parameterize.rb +169 -0
  131. data/lib/sequel/extensions/pg_hstore.rb +296 -0
  132. data/lib/sequel/extensions/pg_hstore_ops.rb +259 -0
  133. data/lib/sequel/extensions/pg_inet.rb +89 -0
  134. data/lib/sequel/extensions/pg_json.rb +178 -0
  135. data/lib/sequel/extensions/pg_statement_cache.rb +316 -0
  136. data/lib/sequel/extensions/pretty_table.rb +5 -71
  137. data/lib/sequel/extensions/query.rb +1 -1
  138. data/lib/sequel/extensions/query_literals.rb +79 -0
  139. data/lib/sequel/extensions/schema_caching.rb +76 -0
  140. data/lib/sequel/extensions/schema_dumper.rb +251 -37
  141. data/lib/sequel/extensions/select_remove.rb +35 -0
  142. data/lib/sequel/extensions/server_block.rb +139 -0
  143. data/lib/sequel/extensions/sql_expr.rb +4 -110
  144. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  145. data/lib/sequel/extensions/to_dot.rb +95 -83
  146. data/lib/sequel/model/associations.rb +1187 -393
  147. data/lib/sequel/model/base.rb +578 -170
  148. data/lib/sequel/model/errors.rb +1 -1
  149. data/lib/sequel/model/exceptions.rb +5 -1
  150. data/lib/sequel/model/inflections.rb +1 -1
  151. data/lib/sequel/model.rb +30 -11
  152. data/lib/sequel/no_core_ext.rb +2 -0
  153. data/lib/sequel/plugins/active_model.rb +13 -1
  154. data/lib/sequel/plugins/association_pks.rb +22 -4
  155. data/lib/sequel/plugins/caching.rb +25 -18
  156. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  157. data/lib/sequel/plugins/composition.rb +44 -12
  158. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  159. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  160. data/lib/sequel/plugins/dirty.rb +214 -0
  161. data/lib/sequel/plugins/eager_each.rb +59 -0
  162. data/lib/sequel/plugins/force_encoding.rb +6 -6
  163. data/lib/sequel/plugins/hook_class_methods.rb +1 -1
  164. data/lib/sequel/plugins/identity_map.rb +123 -8
  165. data/lib/sequel/plugins/instance_filters.rb +10 -0
  166. data/lib/sequel/plugins/instance_hooks.rb +1 -1
  167. data/lib/sequel/plugins/json_serializer.rb +58 -6
  168. data/lib/sequel/plugins/list.rb +12 -2
  169. data/lib/sequel/plugins/many_through_many.rb +79 -28
  170. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
  171. data/lib/sequel/plugins/nested_attributes.rb +139 -63
  172. data/lib/sequel/plugins/optimistic_locking.rb +8 -0
  173. data/lib/sequel/plugins/prepared_statements.rb +167 -0
  174. data/lib/sequel/plugins/prepared_statements_associations.rb +87 -0
  175. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  176. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  177. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  178. data/lib/sequel/plugins/schema.rb +7 -2
  179. data/lib/sequel/plugins/serialization.rb +98 -49
  180. data/lib/sequel/plugins/serialization_modification_detection.rb +63 -0
  181. data/lib/sequel/plugins/sharding.rb +21 -54
  182. data/lib/sequel/plugins/single_table_inheritance.rb +5 -3
  183. data/lib/sequel/plugins/static_cache.rb +99 -0
  184. data/lib/sequel/plugins/subclasses.rb +10 -2
  185. data/lib/sequel/plugins/tactical_eager_loading.rb +7 -7
  186. data/lib/sequel/plugins/timestamps.rb +1 -1
  187. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  188. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  189. data/lib/sequel/plugins/validation_class_methods.rb +1 -1
  190. data/lib/sequel/plugins/xml_serializer.rb +15 -4
  191. data/lib/sequel/sql.rb +526 -59
  192. data/lib/sequel/timezones.rb +57 -38
  193. data/lib/sequel/version.rb +1 -1
  194. data/spec/adapters/db2_spec.rb +146 -0
  195. data/spec/adapters/firebird_spec.rb +1 -1
  196. data/spec/adapters/mssql_spec.rb +142 -40
  197. data/spec/adapters/mysql_spec.rb +380 -234
  198. data/spec/adapters/oracle_spec.rb +67 -77
  199. data/spec/adapters/postgres_spec.rb +1050 -212
  200. data/spec/adapters/spec_helper.rb +8 -5
  201. data/spec/adapters/sqlite_spec.rb +139 -27
  202. data/spec/core/connection_pool_spec.rb +196 -93
  203. data/spec/core/database_spec.rb +879 -510
  204. data/spec/core/dataset_spec.rb +1415 -1086
  205. data/spec/core/expression_filters_spec.rb +554 -109
  206. data/spec/core/mock_adapter_spec.rb +449 -0
  207. data/spec/core/object_graph_spec.rb +86 -114
  208. data/spec/core/schema_generator_spec.rb +4 -4
  209. data/spec/core/schema_spec.rb +482 -38
  210. data/spec/core/spec_helper.rb +6 -48
  211. data/spec/extensions/active_model_spec.rb +13 -0
  212. data/spec/extensions/arbitrary_servers_spec.rb +110 -0
  213. data/spec/extensions/association_autoreloading_spec.rb +18 -10
  214. data/spec/extensions/association_dependencies_spec.rb +15 -25
  215. data/spec/extensions/association_pks_spec.rb +66 -32
  216. data/spec/extensions/association_proxies_spec.rb +4 -4
  217. data/spec/extensions/caching_spec.rb +47 -51
  218. data/spec/extensions/class_table_inheritance_spec.rb +31 -83
  219. data/spec/extensions/columns_introspection_spec.rb +91 -0
  220. data/spec/extensions/composition_spec.rb +18 -13
  221. data/spec/{core/core_sql_spec.rb → extensions/core_extensions_spec.rb} +119 -139
  222. data/spec/extensions/dataset_associations_spec.rb +199 -0
  223. data/spec/extensions/defaults_setter_spec.rb +64 -0
  224. data/spec/extensions/dirty_spec.rb +155 -0
  225. data/spec/extensions/eager_each_spec.rb +34 -0
  226. data/spec/extensions/force_encoding_spec.rb +4 -2
  227. data/spec/extensions/hook_class_methods_spec.rb +69 -92
  228. data/spec/extensions/identity_map_spec.rb +131 -22
  229. data/spec/extensions/inflector_spec.rb +0 -4
  230. data/spec/extensions/instance_filters_spec.rb +11 -21
  231. data/spec/extensions/instance_hooks_spec.rb +72 -0
  232. data/spec/extensions/json_serializer_spec.rb +49 -6
  233. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  234. data/spec/extensions/list_spec.rb +48 -39
  235. data/spec/extensions/many_through_many_spec.rb +283 -250
  236. data/spec/extensions/many_to_one_pk_lookup_spec.rb +140 -0
  237. data/spec/extensions/migration_spec.rb +140 -35
  238. data/spec/extensions/named_timezones_spec.rb +29 -11
  239. data/spec/extensions/nested_attributes_spec.rb +251 -89
  240. data/spec/extensions/null_dataset_spec.rb +85 -0
  241. data/spec/extensions/optimistic_locking_spec.rb +24 -21
  242. data/spec/extensions/pg_array_ops_spec.rb +105 -0
  243. data/spec/extensions/pg_array_spec.rb +196 -0
  244. data/spec/extensions/pg_auto_parameterize_spec.rb +65 -0
  245. data/spec/extensions/pg_hstore_ops_spec.rb +136 -0
  246. data/spec/extensions/pg_hstore_spec.rb +195 -0
  247. data/spec/extensions/pg_inet_spec.rb +44 -0
  248. data/spec/extensions/pg_json_spec.rb +101 -0
  249. data/spec/extensions/pg_statement_cache_spec.rb +209 -0
  250. data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
  251. data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
  252. data/spec/extensions/prepared_statements_spec.rb +87 -0
  253. data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
  254. data/spec/extensions/pretty_table_spec.rb +7 -6
  255. data/spec/extensions/query_literals_spec.rb +168 -0
  256. data/spec/extensions/query_spec.rb +6 -0
  257. data/spec/extensions/rcte_tree_spec.rb +50 -43
  258. data/spec/extensions/schema_caching_spec.rb +41 -0
  259. data/spec/extensions/schema_dumper_spec.rb +429 -22
  260. data/spec/extensions/schema_spec.rb +25 -21
  261. data/spec/extensions/select_remove_spec.rb +38 -0
  262. data/spec/extensions/serialization_modification_detection_spec.rb +72 -0
  263. data/spec/extensions/serialization_spec.rb +85 -43
  264. data/spec/extensions/server_block_spec.rb +90 -0
  265. data/spec/extensions/sharding_spec.rb +69 -143
  266. data/spec/extensions/single_table_inheritance_spec.rb +23 -29
  267. data/spec/extensions/skip_create_refresh_spec.rb +1 -1
  268. data/spec/extensions/spec_helper.rb +22 -61
  269. data/spec/extensions/sql_expr_spec.rb +29 -60
  270. data/spec/extensions/static_cache_spec.rb +145 -0
  271. data/spec/extensions/tactical_eager_loading_spec.rb +17 -19
  272. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  273. data/spec/extensions/timestamps_spec.rb +6 -6
  274. data/spec/extensions/to_dot_spec.rb +4 -7
  275. data/spec/extensions/touch_spec.rb +13 -14
  276. data/spec/extensions/tree_spec.rb +11 -26
  277. data/spec/extensions/typecast_on_load_spec.rb +9 -6
  278. data/spec/extensions/update_primary_key_spec.rb +30 -24
  279. data/spec/extensions/validation_class_methods_spec.rb +30 -51
  280. data/spec/extensions/validation_helpers_spec.rb +16 -35
  281. data/spec/extensions/xml_serializer_spec.rb +29 -4
  282. data/spec/files/transaction_specified_migrations/001_create_alt_basic.rb +4 -0
  283. data/spec/files/transaction_specified_migrations/002_create_basic.rb +4 -0
  284. data/spec/files/transaction_unspecified_migrations/001_create_alt_basic.rb +3 -0
  285. data/spec/files/transaction_unspecified_migrations/002_create_basic.rb +3 -0
  286. data/spec/integration/associations_test.rb +659 -21
  287. data/spec/integration/database_test.rb +9 -3
  288. data/spec/integration/dataset_test.rb +605 -91
  289. data/spec/integration/eager_loader_test.rb +19 -21
  290. data/spec/integration/migrator_test.rb +2 -3
  291. data/spec/integration/model_test.rb +81 -2
  292. data/spec/integration/plugin_test.rb +454 -143
  293. data/spec/integration/prepared_statement_test.rb +263 -128
  294. data/spec/integration/schema_test.rb +309 -43
  295. data/spec/integration/spec_helper.rb +40 -30
  296. data/spec/integration/timezone_test.rb +38 -12
  297. data/spec/integration/transaction_test.rb +183 -8
  298. data/spec/integration/type_test.rb +18 -4
  299. data/spec/model/association_reflection_spec.rb +201 -7
  300. data/spec/model/associations_spec.rb +1067 -661
  301. data/spec/model/base_spec.rb +329 -95
  302. data/spec/model/dataset_methods_spec.rb +7 -27
  303. data/spec/model/eager_loading_spec.rb +677 -676
  304. data/spec/model/hooks_spec.rb +349 -67
  305. data/spec/model/model_spec.rb +240 -168
  306. data/spec/model/plugins_spec.rb +24 -13
  307. data/spec/model/record_spec.rb +490 -232
  308. data/spec/model/spec_helper.rb +14 -71
  309. data/spec/model/validations_spec.rb +11 -0
  310. data/spec/rcov.opts +2 -0
  311. metadata +178 -80
@@ -1,5 +1,6 @@
1
1
  module Sequel
2
2
  Dataset::NON_SQL_OPTIONS << :insert_ignore
3
+ Dataset::NON_SQL_OPTIONS << :update_ignore
3
4
  Dataset::NON_SQL_OPTIONS << :on_duplicate_key_update
4
5
 
5
6
  module MySQL
@@ -30,7 +31,7 @@ module Sequel
30
31
  module DatabaseMethods
31
32
  AUTO_INCREMENT = 'AUTO_INCREMENT'.freeze
32
33
  CAST_TYPES = {String=>:CHAR, Integer=>:SIGNED, Time=>:DATETIME, DateTime=>:DATETIME, Numeric=>:DECIMAL, BigDecimal=>:DECIMAL, File=>:BINARY}
33
- COLUMN_DEFINITION_ORDER = [:null, :default, :unique, :primary_key, :auto_increment, :references]
34
+ COLUMN_DEFINITION_ORDER = [:collate, :null, :default, :unique, :primary_key, :auto_increment, :references]
34
35
  PRIMARY = 'PRIMARY'.freeze
35
36
 
36
37
  # MySQL's cast rules are restrictive in that you can't just cast to any possible
@@ -50,6 +51,36 @@ module Sequel
50
51
  :mysql
51
52
  end
52
53
 
54
+ # Use the Information Schema's KEY_COLUMN_USAGE table to get
55
+ # basic information on foreign key columns, but include the
56
+ # constraint name.
57
+ def foreign_key_list(table, opts={})
58
+ m = output_identifier_meth
59
+ im = input_identifier_meth
60
+ ds = metadata_dataset.
61
+ from(:INFORMATION_SCHEMA__KEY_COLUMN_USAGE).
62
+ where(:TABLE_NAME=>im.call(table)).
63
+ exclude(:CONSTRAINT_NAME=>'PRIMARY').
64
+ exclude(:REFERENCED_TABLE_NAME=>nil).
65
+ select(:CONSTRAINT_NAME___name, :COLUMN_NAME___column, :REFERENCED_TABLE_NAME___table, :REFERENCED_COLUMN_NAME___key)
66
+
67
+ h = {}
68
+ ds.each do |row|
69
+ if r = h[row[:name]]
70
+ r[:columns] << m.call(row[:column])
71
+ r[:key] << m.call(row[:key])
72
+ else
73
+ h[row[:name]] = {:name=>m.call(row[:name]), :columns=>[m.call(row[:column])], :table=>m.call(row[:table]), :key=>[m.call(row[:key])]}
74
+ end
75
+ end
76
+ h.values
77
+ end
78
+
79
+ # MySQL namespaces indexes per table.
80
+ def global_index_namespace?
81
+ false
82
+ end
83
+
53
84
  # Use SHOW INDEX FROM to get the index information for the
54
85
  # table.
55
86
  #
@@ -83,23 +114,25 @@ module Sequel
83
114
  @server_version ||= (m[1].to_i * 10000) + (m[2].to_i * 100) + m[3].to_i
84
115
  end
85
116
 
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)}
117
+ # MySQL supports CREATE TABLE IF NOT EXISTS syntax.
118
+ def supports_create_table_if_not_exists?
119
+ true
93
120
  end
94
121
 
95
122
  # MySQL supports prepared transactions (two-phase commit) using XA
96
123
  def supports_prepared_transactions?
97
- true
124
+ server_version >= 50000
98
125
  end
99
126
 
100
127
  # MySQL supports savepoints
101
128
  def supports_savepoints?
102
- true
129
+ server_version >= 50000
130
+ end
131
+
132
+ # MySQL doesn't support savepoints inside prepared transactions in from
133
+ # 5.5.12 to 5.5.23, see http://bugs.mysql.com/bug.php?id=64374
134
+ def supports_savepoints_in_prepared_transactions?
135
+ super && (server_version <= 50512 || server_version >= 50523)
103
136
  end
104
137
 
105
138
  # MySQL supports transaction isolation levels
@@ -107,6 +140,14 @@ module Sequel
107
140
  true
108
141
  end
109
142
 
143
+ # Return an array of symbols specifying table names in the current database.
144
+ #
145
+ # Options:
146
+ # * :server - Set the server to use
147
+ def tables(opts={})
148
+ full_tables('BASE TABLE', opts)
149
+ end
150
+
110
151
  # Changes the database in use by issuing a USE statement. I would be
111
152
  # very careful if I used this.
112
153
  def use(db_name)
@@ -116,6 +157,14 @@ module Sequel
116
157
  self
117
158
  end
118
159
 
160
+ # Return an array of symbols specifying view names in the current database.
161
+ #
162
+ # Options:
163
+ # * :server - Set the server to use
164
+ def views(opts={})
165
+ full_tables('VIEW', opts)
166
+ end
167
+
119
168
  private
120
169
 
121
170
  # Use MySQL specific syntax for rename column, set column type, and
@@ -126,6 +175,7 @@ module Sequel
126
175
  if related = op.delete(:table)
127
176
  sql = super(table, op)
128
177
  op[:table] = related
178
+ op[:key] ||= primary_key_from_schema(related)
129
179
  [sql, "ALTER TABLE #{quote_schema_table(table)} ADD FOREIGN KEY (#{quote_identifier(op[:name])})#{column_references_sql(op)}"]
130
180
  else
131
181
  super(table, op)
@@ -142,10 +192,47 @@ module Sequel
142
192
  "ALTER TABLE #{quote_schema_table(table)} CHANGE COLUMN #{quote_identifier(op[:name])} #{column_definition_sql(op.merge(opts))}"
143
193
  when :drop_index
144
194
  "#{drop_index_sql(table, op)} ON #{quote_schema_table(table)}"
195
+ when :drop_constraint
196
+ type = case op[:type]
197
+ when :primary_key
198
+ return "ALTER TABLE #{quote_schema_table(table)} DROP PRIMARY KEY"
199
+ when :foreign_key
200
+ 'FOREIGN KEY'
201
+ when :unique
202
+ 'INDEX'
203
+ else
204
+ raise(Error, "must specify constraint type via :type=>(:foreign_key|:primary_key|:unique) when dropping constraints on MySQL")
205
+ end
206
+ "ALTER TABLE #{quote_schema_table(table)} DROP #{type} #{quote_identifier(op[:name])}"
207
+ when :add_constraint
208
+ if op[:type] == :foreign_key
209
+ op[:key] ||= primary_key_from_schema(op[:table])
210
+ end
211
+ super(table, op)
145
212
  else
146
213
  super(table, op)
147
214
  end
148
215
  end
216
+
217
+ # The SQL queries to execute on initial connection
218
+ def mysql_connection_setting_sqls
219
+ sqls = []
220
+
221
+ # Increase timeout so mysql server doesn't disconnect us
222
+ # Value used by default is maximum allowed value on Windows.
223
+ sqls << "SET @@wait_timeout = #{opts[:timeout] || 2147483}"
224
+
225
+ # By default, MySQL 'where id is null' selects the last inserted id
226
+ sqls << "SET SQL_AUTO_IS_NULL=0" unless opts[:auto_is_null]
227
+
228
+ # If the user has specified one or more sql modes, enable them
229
+ if sql_mode = opts[:sql_mode]
230
+ sql_mode = Array(sql_mode).join(',').upcase
231
+ sqls << "SET sql_mode = '#{sql_mode}'"
232
+ end
233
+
234
+ sqls
235
+ end
149
236
 
150
237
  # Use MySQL specific AUTO_INCREMENT text.
151
238
  def auto_increment_sql
@@ -161,9 +248,9 @@ module Sequel
161
248
  # Use XA START to start a new prepared transaction if the :prepare
162
249
  # option is given.
163
250
  def begin_transaction(conn, opts={})
164
- if s = opts[:prepare]
251
+ if (s = opts[:prepare]) && (th = _trans(conn))[:savepoint_level] == 0
165
252
  log_connection_execute(conn, "XA START #{literal(s)}")
166
- conn
253
+ th[:savepoint_level] += 1
167
254
  else
168
255
  super
169
256
  end
@@ -184,7 +271,7 @@ module Sequel
184
271
  # Prepare the XA transaction for a two-phase commit if the
185
272
  # :prepare option is given.
186
273
  def commit_transaction(conn, opts={})
187
- if s = opts[:prepare]
274
+ if (s = opts[:prepare]) && _trans(conn)[:savepoint_level] <= 1
188
275
  log_connection_execute(conn, "XA END #{literal(s)}")
189
276
  log_connection_execute(conn, "XA PREPARE #{literal(s)}")
190
277
  else
@@ -197,14 +284,50 @@ module Sequel
197
284
  engine = options.fetch(:engine, Sequel::MySQL.default_engine)
198
285
  charset = options.fetch(:charset, Sequel::MySQL.default_charset)
199
286
  collate = options.fetch(:collate, Sequel::MySQL.default_collate)
287
+ generator.constraints.sort_by{|c| (c[:type] == :primary_key) ? -1 : 1}
288
+
289
+ key_proc = lambda do |t|
290
+ if t == name
291
+ if pk = generator.primary_key_name
292
+ [pk]
293
+ elsif !(pkc = generator.constraints.select{|con| con[:type] == :primary_key}).empty?
294
+ pkc.first[:columns]
295
+ end
296
+ else
297
+ primary_key_from_schema(t)
298
+ end
299
+ end
300
+
301
+ generator.constraints.each do |c|
302
+ if c[:type] == :foreign_key
303
+ c[:key] ||= key_proc.call(c[:table])
304
+ end
305
+ end
306
+
200
307
  generator.columns.each do |c|
201
308
  if t = c.delete(:table)
202
- generator.foreign_key([c[:name]], t, c.merge(:name=>nil, :type=>:foreign_key))
309
+ same_table = t == name
310
+ k = c[:key]
311
+
312
+ key ||= key_proc.call(t)
313
+
314
+ if same_table && !k.nil?
315
+ generator.constraints.unshift(:type=>:unique, :columns=>Array(k))
316
+ end
317
+
318
+ generator.foreign_key([c[:name]], t, c.merge(:name=>nil, :type=>:foreign_key, :key=>key))
203
319
  end
204
320
  end
321
+
205
322
  "#{super}#{" ENGINE=#{engine}" if engine}#{" DEFAULT CHARSET=#{charset}" if charset}#{" DEFAULT COLLATE=#{collate}" if collate}"
206
323
  end
207
324
 
325
+ # Backbone of the tables and views support using SHOW FULL TABLES.
326
+ def full_tables(type, opts)
327
+ m = output_identifier_meth
328
+ metadata_dataset.with_sql('SHOW FULL TABLES').server(opts[:server]).map{|r| m.call(r.values.first) if r.delete(:Table_type) == type}.compact
329
+ end
330
+
208
331
  # MySQL folds unquoted identifiers to lowercase, so it shouldn't need to upcase identifiers on input.
209
332
  def identifier_input_method_default
210
333
  nil
@@ -230,9 +353,14 @@ module Sequel
230
353
  "CREATE #{index_type}INDEX #{index_name}#{using} ON #{quote_schema_table(table_name)} #{literal(index[:columns])}"
231
354
  end
232
355
 
356
+ # Parse the schema for the given table to get an array of primary key columns
357
+ def primary_key_from_schema(table)
358
+ schema(table).select{|a| a[1][:primary_key]}.map{|a| a[0]}
359
+ end
360
+
233
361
  # Rollback the currently open XA transaction
234
362
  def rollback_transaction(conn, opts={})
235
- if s = opts[:prepare]
363
+ if (s = opts[:prepare]) && _trans(conn)[:savepoint_level] <= 1
236
364
  log_connection_execute(conn, "XA END #{literal(s)}")
237
365
  log_connection_execute(conn, "XA PREPARE #{literal(s)}")
238
366
  log_connection_execute(conn, "XA ROLLBACK #{literal(s)}")
@@ -248,9 +376,11 @@ module Sequel
248
376
 
249
377
  # Use the MySQL specific DESCRIBE syntax to get a table description.
250
378
  def schema_parse_table(table_name, opts)
251
- m = output_identifier_meth
252
- im = input_identifier_meth
253
- metadata_dataset.with_sql("DESCRIBE ?", SQL::Identifier.new(im.call(table_name))).map do |row|
379
+ m = output_identifier_meth(opts[:dataset])
380
+ im = input_identifier_meth(opts[:dataset])
381
+ table = SQL::Identifier.new(im.call(table_name))
382
+ table = SQL::QualifiedIdentifier.new(im.call(opts[:schema]), table) if opts[:schema]
383
+ metadata_dataset.with_sql("DESCRIBE ?", table).map do |row|
254
384
  row[:auto_increment] = true if row.delete(:Extra).to_s =~ /auto_increment/io
255
385
  row[:allow_null] = row.delete(:Null) == 'YES'
256
386
  row[:default] = row.delete(:Default)
@@ -281,13 +411,17 @@ module Sequel
281
411
  # MySQL has both datetime and timestamp classes, most people are going
282
412
  # to want datetime
283
413
  def type_literal_generic_datetime(column)
284
- :datetime
414
+ if column[:default] == Sequel::CURRENT_TIMESTAMP
415
+ :timestamp
416
+ else
417
+ :datetime
418
+ end
285
419
  end
286
420
 
287
421
  # MySQL has both datetime and timestamp classes, most people are going
288
422
  # to want datetime
289
423
  def type_literal_generic_time(column)
290
- column[:only_time] ? :time : :datetime
424
+ column[:only_time] ? :time : type_literal_generic_datetime(column)
291
425
  end
292
426
 
293
427
  # MySQL doesn't have a true boolean class, so it uses tinyint(1)
@@ -303,27 +437,82 @@ module Sequel
303
437
  COMMA_SEPARATOR = ', '.freeze
304
438
  FOR_SHARE = ' LOCK IN SHARE MODE'.freeze
305
439
  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')
440
+ DELETE_CLAUSE_METHODS = Dataset.clause_methods(:delete, %w'delete from where order limit')
441
+ INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'insert ignore into columns values on_duplicate_key_update')
442
+ SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'select distinct calc_found_rows columns from join where group having compounds order limit lock')
443
+ UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'update ignore table set where order limit')
444
+ APOS = Dataset::APOS
445
+ APOS_RE = Dataset::APOS_RE
446
+ DOUBLE_APOS = Dataset::DOUBLE_APOS
447
+ SPACE = Dataset::SPACE
448
+ PAREN_OPEN = Dataset::PAREN_OPEN
449
+ PAREN_CLOSE = Dataset::PAREN_CLOSE
450
+ NOT_SPACE = Dataset::NOT_SPACE
451
+ FROM = Dataset::FROM
452
+ INSERT = Dataset::INSERT
453
+ COMMA = Dataset::COMMA
454
+ LIMIT = Dataset::LIMIT
455
+ GROUP_BY = Dataset::GROUP_BY
456
+ REGEXP = 'REGEXP'.freeze
457
+ LIKE = 'LIKE'.freeze
458
+ BINARY = 'BINARY '.freeze
459
+ CONCAT = "CONCAT".freeze
460
+ CAST_BITCOMP_OPEN = "CAST(~".freeze
461
+ CAST_BITCOMP_CLOSE = " AS SIGNED INTEGER)".freeze
462
+ STRAIGHT_JOIN = 'STRAIGHT_JOIN'.freeze
463
+ NATURAL_LEFT_JOIN = 'NATURAL LEFT JOIN'.freeze
464
+ BACKTICK = '`'.freeze
465
+ BACKTICK_RE = /`/.freeze
466
+ DOUBLE_BACKTICK = '``'.freeze
467
+ EMPTY_COLUMNS = " ()".freeze
468
+ EMPTY_VALUES = " VALUES ()".freeze
469
+ IGNORE = " IGNORE".freeze
470
+ REPLACE = 'REPLACE'.freeze
471
+ ON_DUPLICATE_KEY_UPDATE = " ON DUPLICATE KEY UPDATE ".freeze
472
+ EQ_VALUES = '=VALUES('.freeze
473
+ EQ = '='.freeze
474
+ WITH_ROLLUP = ' WITH ROLLUP'.freeze
475
+ MATCH_AGAINST = ["(MATCH ".freeze, " AGAINST (".freeze, "))".freeze].freeze
476
+ MATCH_AGAINST_BOOLEAN = ["(MATCH ".freeze, " AGAINST (".freeze, " IN BOOLEAN MODE))".freeze].freeze
477
+ EXPLAIN = 'EXPLAIN '.freeze
478
+ EXPLAIN_EXTENDED = 'EXPLAIN EXTENDED '.freeze
479
+ BACKSLASH_RE = /\\/.freeze
480
+ QUAD_BACKSLASH = "\\\\\\\\".freeze
310
481
 
311
482
  # MySQL specific syntax for LIKE/REGEXP searches, as well as
312
483
  # string concatenation.
313
- def complex_expression_sql(op, args)
484
+ def complex_expression_sql_append(sql, op, args)
314
485
  case op
486
+ when :IN, :"NOT IN"
487
+ ds = args.at(1)
488
+ if ds.is_a?(Sequel::Dataset) && ds.opts[:limit]
489
+ super(sql, op, [args.at(0), ds.from_self])
490
+ else
491
+ super
492
+ end
315
493
  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))})"
494
+ sql << PAREN_OPEN
495
+ literal_append(sql, args.at(0))
496
+ sql << SPACE
497
+ sql << 'NOT ' if [:'NOT LIKE', :'NOT ILIKE', :'!~', :'!~*'].include?(op)
498
+ sql << ([:~, :'!~', :'~*', :'!~*'].include?(op) ? REGEXP : LIKE)
499
+ sql << SPACE
500
+ sql << BINARY if [:~, :'!~', :LIKE, :'NOT LIKE'].include?(op)
501
+ literal_append(sql, args.at(1))
502
+ sql << PAREN_CLOSE
317
503
  when :'||'
318
504
  if args.length > 1
319
- "CONCAT(#{args.collect{|a| literal(a)}.join(', ')})"
505
+ sql << CONCAT
506
+ array_sql_append(sql, args)
320
507
  else
321
- literal(args.at(0))
508
+ literal_append(sql, args.at(0))
322
509
  end
323
510
  when :'B~'
324
- "CAST(~#{literal(args.at(0))} AS SIGNED INTEGER)"
511
+ sql << CAST_BITCOMP_OPEN
512
+ literal_append(sql, args.at(0))
513
+ sql << CAST_BITCOMP_CLOSE
325
514
  else
326
- super(op, args)
515
+ super
327
516
  end
328
517
  end
329
518
 
@@ -340,6 +529,17 @@ module Sequel
340
529
  clone(:calc_found_rows => true)
341
530
  end
342
531
 
532
+ # Return the results of an EXPLAIN query as a string. Options:
533
+ # :extended :: Use EXPLAIN EXPTENDED instead of EXPLAIN if true.
534
+ def explain(opts={})
535
+ # Load the PrettyTable class, needed for explain output
536
+ Sequel.extension(:_pretty_table) unless defined?(Sequel::PrettyTable)
537
+
538
+ ds = db.send(:metadata_dataset).with_sql((opts[:extended] ? EXPLAIN_EXTENDED : EXPLAIN) + select_sql).naked
539
+ rows = ds.all
540
+ Sequel::PrettyTable.string(rows, ds.columns)
541
+ end
542
+
343
543
  # Return a cloned dataset which will use LOCK IN SHARE MODE to lock returned rows.
344
544
  def for_share
345
545
  lock_style(:share)
@@ -351,8 +551,9 @@ module Sequel
351
551
  end
352
552
 
353
553
  # 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]})"
554
+ def full_text_sql(cols, terms, opts = {})
555
+ terms = terms.join(' ') if terms.is_a?(Array)
556
+ SQL::PlaceholderLiteralString.new((opts[:boolean] ? MATCH_AGAINST_BOOLEAN : MATCH_AGAINST), [Array(cols), terms])
356
557
  end
357
558
 
358
559
  # MySQL allows HAVING clause on ungrouped datasets.
@@ -372,9 +573,12 @@ module Sequel
372
573
  # STRAIGHT_JOIN.
373
574
  def join_type_sql(join_type)
374
575
  case join_type
375
- when :straight then 'STRAIGHT_JOIN'
376
- when :natural_inner then 'NATURAL LEFT JOIN'
377
- else super
576
+ when :straight
577
+ STRAIGHT_JOIN
578
+ when :natural_inner
579
+ NATURAL_LEFT_JOIN
580
+ else
581
+ super
378
582
  end
379
583
  end
380
584
 
@@ -389,7 +593,7 @@ module Sequel
389
593
  def insert_ignore
390
594
  clone(:insert_ignore=>true)
391
595
  end
392
-
596
+
393
597
  # Sets up the insert methods to use ON DUPLICATE KEY UPDATE
394
598
  # If you pass no arguments, ALL fields will be
395
599
  # updated with the new values. If you pass the fields you
@@ -415,25 +619,31 @@ module Sequel
415
619
 
416
620
  # MySQL specific syntax for inserting multiple values at once.
417
621
  def multi_insert_sql(columns, values)
418
- [insert_sql(columns, LiteralString.new('VALUES ' + values.map {|r| literal(Array(r))}.join(COMMA_SEPARATOR)))]
419
- end
420
-
421
- # MySQL uses the number of rows actually modified in the update,
422
- # instead of the number of matched by the filter.
423
- def provides_accurate_rows_matched?
424
- false
622
+ sql = LiteralString.new('VALUES ')
623
+ expression_list_append(sql, values.map{|r| Array(r)})
624
+ [insert_sql(columns, sql)]
425
625
  end
426
626
 
427
627
  # MySQL uses the nonstandard ` (backtick) for quoting identifiers.
428
- def quoted_identifier(c)
429
- "`#{c}`"
628
+ def quoted_identifier_append(sql, c)
629
+ sql << BACKTICK << c.to_s.gsub(BACKTICK_RE, DOUBLE_BACKTICK) << BACKTICK
430
630
  end
431
631
 
632
+ # Execute a REPLACE statement on the database.
633
+ def replace(*values)
634
+ execute_insert(replace_sql(*values))
635
+ end
636
+
432
637
  # MySQL specific syntax for REPLACE (aka UPSERT, or update if exists,
433
638
  # insert if it doesn't).
434
639
  def replace_sql(*values)
435
640
  clone(:replace=>true).insert_sql(*values)
436
641
  end
642
+
643
+ # Replace multiple rows in a single query.
644
+ def multi_replace(*values)
645
+ clone(:replace=>true).multi_insert(*values)
646
+ end
437
647
 
438
648
  # MySQL can emulate DISTINCT ON with its non-standard GROUP BY implementation,
439
649
  # though the rows returned cannot be made deterministic through ordering.
@@ -441,6 +651,11 @@ module Sequel
441
651
  true
442
652
  end
443
653
 
654
+ # MySQL supports GROUP BY WITH ROLLUP (but not CUBE)
655
+ def supports_group_rollup?
656
+ true
657
+ end
658
+
444
659
  # MySQL does not support INTERSECT or EXCEPT
445
660
  def supports_intersect_except?
446
661
  false
@@ -451,6 +666,12 @@ module Sequel
451
666
  true
452
667
  end
453
668
 
669
+ # MySQL's DISTINCT ON emulation using GROUP BY does not respect the
670
+ # queries ORDER BY clause.
671
+ def supports_ordered_distinct_on?
672
+ false
673
+ end
674
+
454
675
  # MySQL does support fractional timestamps in literal timestamps, but it
455
676
  # ignores them. Also, using them seems to cause problems on 1.9. Since
456
677
  # they are ignored anyway, not using them is probably best.
@@ -458,13 +679,16 @@ module Sequel
458
679
  false
459
680
  end
460
681
 
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
682
+ # Sets up the update methods to use UPDATE IGNORE.
683
+ # Useful if you have a unique key and want to just skip
684
+ # updating rows that violate the unique key restriction.
685
+ #
686
+ # dataset.update_ignore.update({:name => 'a', :value => 1})
687
+ # # UPDATE IGNORE tablename SET name = 'a', value = 1
688
+ def update_ignore
689
+ clone(:update_ignore=>true)
466
690
  end
467
-
691
+
468
692
  private
469
693
 
470
694
  # MySQL supports the ORDER BY and LIMIT clauses for DELETE statements
@@ -476,7 +700,10 @@ module Sequel
476
700
  # from, but include the others for the purposes of selecting rows.
477
701
  def delete_from_sql(sql)
478
702
  if joined_dataset?
479
- sql << " #{source_list(@opts[:from][0..0])} FROM #{source_list(@opts[:from])}"
703
+ sql << SPACE
704
+ source_list_append(sql, @opts[:from][0..0])
705
+ sql << FROM
706
+ source_list_append(sql, @opts[:from])
480
707
  select_join_sql(sql)
481
708
  else
482
709
  super
@@ -493,7 +720,7 @@ module Sequel
493
720
  def insert_columns_sql(sql)
494
721
  values = opts[:values]
495
722
  if values.is_a?(Array) && values.empty?
496
- sql << " ()"
723
+ sql << EMPTY_COLUMNS
497
724
  else
498
725
  super
499
726
  end
@@ -501,19 +728,62 @@ module Sequel
501
728
 
502
729
  # MySQL supports INSERT IGNORE INTO
503
730
  def insert_ignore_sql(sql)
504
- sql << " IGNORE" if opts[:insert_ignore]
731
+ sql << IGNORE if opts[:insert_ignore]
732
+ end
733
+
734
+ # MySQL supports UPDATE IGNORE
735
+ def update_ignore_sql(sql)
736
+ sql << IGNORE if opts[:update_ignore]
737
+ end
738
+
739
+ # If this is an replace instead of an insert, use replace instead
740
+ def insert_insert_sql(sql)
741
+ sql << (@opts[:replace] ? REPLACE : INSERT)
505
742
  end
506
743
 
507
744
  # MySQL supports INSERT ... ON DUPLICATE KEY UPDATE
508
745
  def insert_on_duplicate_key_update_sql(sql)
509
- sql << on_duplicate_key_update_sql if opts[:on_duplicate_key_update]
746
+ if update_cols = opts[:on_duplicate_key_update]
747
+ update_vals = nil
748
+
749
+ if update_cols.empty?
750
+ update_cols = columns
751
+ elsif update_cols.last.is_a?(Hash)
752
+ update_vals = update_cols.last
753
+ update_cols = update_cols[0..-2]
754
+ end
755
+
756
+ sql << ON_DUPLICATE_KEY_UPDATE
757
+ c = false
758
+ co = COMMA
759
+ values = EQ_VALUES
760
+ endp = PAREN_CLOSE
761
+ update_cols.each do |col|
762
+ sql << co if c
763
+ quote_identifier_append(sql, col)
764
+ sql << values
765
+ quote_identifier_append(sql, col)
766
+ sql << endp
767
+ c ||= true
768
+ end
769
+ if update_vals
770
+ eq = EQ
771
+ update_vals.map do |col,v|
772
+ sql << co if c
773
+ quote_identifier_append(sql, col)
774
+ sql << eq
775
+ literal_append(sql, v)
776
+ c ||= true
777
+ end
778
+ end
779
+ end
510
780
  end
511
781
 
512
782
  # MySQL doesn't use the standard DEFAULT VALUES for empty values.
513
783
  def insert_values_sql(sql)
514
784
  values = opts[:values]
515
785
  if values.is_a?(Array) && values.empty?
516
- sql << " VALUES ()"
786
+ sql << EMPTY_VALUES
517
787
  else
518
788
  super
519
789
  end
@@ -521,7 +791,10 @@ module Sequel
521
791
 
522
792
  # MySQL allows a LIMIT in DELETE and UPDATE statements.
523
793
  def limit_sql(sql)
524
- sql << " LIMIT #{@opts[:limit]}" if @opts[:limit]
794
+ if l = @opts[:limit]
795
+ sql << LIMIT
796
+ literal_append(sql, @opts[:limit])
797
+ end
525
798
  end
526
799
  alias delete_limit_sql limit_sql
527
800
  alias update_limit_sql limit_sql
@@ -531,30 +804,16 @@ module Sequel
531
804
  BOOL_FALSE
532
805
  end
533
806
 
807
+ # SQL fragment for String. Doubles \ and ' by default.
808
+ def literal_string_append(sql, v)
809
+ sql << APOS << v.gsub(BACKSLASH_RE, QUAD_BACKSLASH).gsub(APOS_RE, DOUBLE_APOS) << APOS
810
+ end
811
+
534
812
  # Use 1 for true on MySQL
535
813
  def literal_true
536
814
  BOOL_TRUE
537
815
  end
538
816
 
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
817
  # MySQL does not support the SQL WITH clause for SELECT statements
559
818
  def select_clause_methods
560
819
  SELECT_CLAUSE_METHODS
@@ -574,6 +833,11 @@ module Sequel
574
833
  def update_clause_methods
575
834
  UPDATE_CLAUSE_METHODS
576
835
  end
836
+
837
+ # MySQL uses WITH ROLLUP syntax.
838
+ def uses_with_rollup?
839
+ true
840
+ end
577
841
  end
578
842
  end
579
843
  end