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