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