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,3 +1,5 @@
1
+ Sequel.require %w'emulate_offset_with_row_number split_alter_table', 'adapters/utils/'
2
+
1
3
  module Sequel
2
4
  Dataset::NON_SQL_OPTIONS << :disable_insert_output
3
5
  module MSSQL
@@ -10,16 +12,92 @@ module Sequel
10
12
  SQL_ROLLBACK = "IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION".freeze
11
13
  SQL_ROLLBACK_TO_SAVEPOINT = 'IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION autopoint_%d'.freeze
12
14
  SQL_SAVEPOINT = 'SAVE TRANSACTION autopoint_%d'.freeze
15
+ MSSQL_DEFAULT_RE = /\A(?:\(N?('.*')\)|\(\((-?\d+(?:\.\d+)?)\)\))\z/
16
+ FOREIGN_KEY_ACTION_MAP = {0 => :no_action, 1 => :cascade, 2 => :set_null, 3 => :set_default}.freeze
17
+
18
+ include Sequel::Database::SplitAlterTable
13
19
 
20
+ # Whether to use N'' to quote strings, which allows unicode characters inside the
21
+ # strings. True by default for compatibility, can be set to false for a possible
22
+ # performance increase. This sets the default for all datasets created from this
23
+ # Database object.
24
+ attr_accessor :mssql_unicode_strings
25
+
14
26
  # The types to check for 0 scale to transform :decimal types
15
27
  # to :integer.
16
28
  DECIMAL_TYPE_RE = /number|numeric|decimal/io
17
-
29
+
18
30
  # Microsoft SQL Server uses the :mssql type.
19
31
  def database_type
20
32
  :mssql
21
33
  end
22
34
 
35
+ # Microsoft SQL Server namespaces indexes per table.
36
+ def global_index_namespace?
37
+ false
38
+ end
39
+
40
+ # Return foreign key information using the system views, including
41
+ # :name, :on_delete, and :on_update entries in the hashes.
42
+ def foreign_key_list(table, opts={})
43
+ m = output_identifier_meth
44
+ im = input_identifier_meth
45
+ schema, table = schema_and_table(table)
46
+ current_schema = m.call(get(Sequel.function('schema_name')))
47
+ fk_action_map = FOREIGN_KEY_ACTION_MAP
48
+ ds = metadata_dataset.from(:sys__foreign_keys___fk).
49
+ join(:sys__foreign_key_columns___fkc, :constraint_object_id => :object_id).
50
+ join(:sys__all_columns___pc, :object_id => :fkc__parent_object_id, :column_id => :fkc__parent_column_id).
51
+ join(:sys__all_columns___rc, :object_id => :fkc__referenced_object_id, :column_id => :fkc__referenced_column_id).
52
+ where{{object_schema_name(:fk__parent_object_id) => im.call(schema || current_schema)}}.
53
+ where{{object_name(:fk__parent_object_id) => im.call(table)}}.
54
+ select{[:fk__name,
55
+ :fk__delete_referential_action,
56
+ :fk__update_referential_action,
57
+ :pc__name___column,
58
+ :rc__name___referenced_column,
59
+ object_schema_name(:fk__referenced_object_id).as(:schema),
60
+ object_name(:fk__referenced_object_id).as(:table)]}.
61
+ order(:name, :fkc__constraint_column_id)
62
+ h = {}
63
+ ds.each do |row|
64
+ if r = h[row[:name]]
65
+ r[:columns] << m.call(row[:column])
66
+ r[:key] << m.call(row[:referenced_column])
67
+ else
68
+ referenced_schema = m.call(row[:schema])
69
+ referenced_table = m.call(row[:table])
70
+ h[row[:name]] = { :name => m.call(row[:name]),
71
+ :table => (referenced_schema == current_schema) ? referenced_table : :"#{referenced_schema}__#{referenced_table}",
72
+ :columns => [m.call(row[:column])],
73
+ :key => [m.call(row[:referenced_column])],
74
+ :on_update => fk_action_map[row[:update_referential_action]],
75
+ :on_delete => fk_action_map[row[:delete_referential_action]] }
76
+ end
77
+ end
78
+ h.values
79
+ end
80
+
81
+ # Use the system tables to get index information
82
+ def indexes(table, opts={})
83
+ m = output_identifier_meth
84
+ im = input_identifier_meth
85
+ indexes = {}
86
+ metadata_dataset.from(:sys__tables___t).
87
+ join(:sys__indexes___i, :object_id=>:object_id).
88
+ join(:sys__index_columns___ic, :object_id=>:object_id, :index_id=>:index_id).
89
+ join(:sys__columns___c, :object_id=>:object_id, :column_id=>:column_id).
90
+ select(:i__name, :i__is_unique, :c__name___column).
91
+ where{{t__name=>im.call(table)}}.
92
+ where(:i__is_primary_key=>0, :i__is_disabled=>0).
93
+ order(:i__name, :ic__index_column_id).
94
+ each do |r|
95
+ index = indexes[m.call(r[:name])] ||= {:columns=>[], :unique=>(r[:is_unique] && r[:is_unique]!=0)}
96
+ index[:columns] << m.call(r[:column])
97
+ end
98
+ indexes
99
+ end
100
+
23
101
  # The version of the MSSQL server, as an integer (e.g. 10001600 for
24
102
  # SQL Server 2008 Express).
25
103
  def server_version(server=nil)
@@ -44,18 +122,33 @@ module Sequel
44
122
  true
45
123
  end
46
124
 
125
+ # MSSQL supports transaction DDL statements.
126
+ def supports_transactional_ddl?
127
+ true
128
+ end
129
+
47
130
  # Microsoft SQL Server supports using the INFORMATION_SCHEMA to get
48
131
  # information on tables.
49
132
  def tables(opts={})
50
- m = output_identifier_meth
51
- metadata_dataset.from(:information_schema__tables___t).
52
- select(:table_name).
53
- filter(:table_type=>'BASE TABLE', :table_schema=>(opts[:schema]||default_schema||'dbo').to_s).
54
- map{|x| m.call(x[:table_name])}
133
+ information_schema_tables('BASE TABLE', opts)
55
134
  end
56
135
 
136
+ # Microsoft SQL Server supports using the INFORMATION_SCHEMA to get
137
+ # information on views.
138
+ def views(opts={})
139
+ information_schema_tables('VIEW', opts)
140
+ end
141
+
57
142
  private
58
143
 
144
+ # Add dropping of the default constraint to the list of SQL queries.
145
+ # This is necessary before dropping the column or changing its type.
146
+ def add_drop_default_constraint_sql(sqls, table, column)
147
+ if constraint = default_constraint_name(table, column)
148
+ sqls << "ALTER TABLE #{quote_schema_table(table)} DROP CONSTRAINT #{constraint}"
149
+ end
150
+ end
151
+
59
152
  # MSSQL uses the IDENTITY(1,1) column for autoincrementing columns.
60
153
  def auto_increment_sql
61
154
  AUTO_INCREMENT
@@ -66,10 +159,26 @@ module Sequel
66
159
  case op[:op]
67
160
  when :add_column
68
161
  "ALTER TABLE #{quote_schema_table(table)} ADD #{column_definition_sql(op)}"
162
+ when :drop_column
163
+ sqls = []
164
+ add_drop_default_constraint_sql(sqls, table, op[:name])
165
+ sqls << super
69
166
  when :rename_column
70
167
  "sp_rename #{literal("#{quote_schema_table(table)}.#{quote_identifier(op[:name])}")}, #{literal(op[:new_name].to_s)}, 'COLUMN'"
71
168
  when :set_column_type
72
- "ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(op[:name])} #{type_literal(op)}"
169
+ sqls = []
170
+ if sch = schema(table)
171
+ if cs = sch.each{|k, v| break v if k == op[:name]; nil}
172
+ cs = cs.dup
173
+ add_drop_default_constraint_sql(sqls, table, op[:name])
174
+ cs[:default] = cs[:ruby_default]
175
+ op = cs.merge!(op)
176
+ default = op.delete(:default)
177
+ end
178
+ end
179
+ sqls << "ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{column_definition_sql(op)}"
180
+ sqls << alter_table_sql(table, op.merge(:op=>:set_column_default, :default=>default)) if default
181
+ sqls
73
182
  when :set_column_null
74
183
  sch = schema(table).find{|k,v| k.to_s == op[:name].to_s}.last
75
184
  type = sch[:db_type]
@@ -94,10 +203,18 @@ module Sequel
94
203
  SQL_BEGIN
95
204
  end
96
205
 
206
+ # Handle MSSQL specific default format.
207
+ def column_schema_normalize_default(default, type)
208
+ if m = MSSQL_DEFAULT_RE.match(default)
209
+ default = m[1] || m[2]
210
+ end
211
+ super(default, type)
212
+ end
213
+
97
214
  # Commit the active transaction on the connection, does not commit/release
98
215
  # savepoints.
99
216
  def commit_transaction(conn, opts={})
100
- log_connection_execute(conn, commit_transaction_sql) unless Thread.current[:sequel_transaction_depth] > 1
217
+ log_connection_execute(conn, commit_transaction_sql) unless _trans(conn)[:savepoint_level] > 1
101
218
  end
102
219
 
103
220
  # SQL to COMMIT a transaction.
@@ -108,15 +225,55 @@ module Sequel
108
225
  # MSSQL uses the name of the table to decide the difference between
109
226
  # a regular and temporary table, with temporary table names starting with
110
227
  # a #.
111
- def create_table_sql(name, generator, options)
112
- "CREATE TABLE #{quote_schema_table(options[:temp] ? "##{name}" : name)} (#{column_list_sql(generator)})"
228
+ def create_table_prefix_sql(name, options)
229
+ "CREATE TABLE #{quote_schema_table(options[:temp] ? "##{name}" : name)}"
113
230
  end
114
231
 
232
+ # MSSQL doesn't support CREATE TABLE AS, it only supports SELECT INTO.
233
+ # Emulating CREATE TABLE AS using SELECT INTO is only possible if a dataset
234
+ # is given as the argument, it can't work with a string, so raise an
235
+ # Error if a string is given.
236
+ def create_table_as(name, ds, options)
237
+ raise(Error, "must provide dataset instance as value of create_table :as option on MSSQL") unless ds.is_a?(Sequel::Dataset)
238
+ run(ds.into(name).sql)
239
+ end
240
+
241
+ # The name of the constraint for setting the default value on the table and column.
242
+ # The SQL used to select default constraints utilizes MSSQL catalog views which were introduced in 2005.
243
+ # This method intentionally does not support MSSQL 2000.
244
+ def default_constraint_name(table, column_name)
245
+ if server_version >= 9000000
246
+ table_name = schema_and_table(table).compact.join('.')
247
+ self[:sys__default_constraints].
248
+ where{{:parent_object_id => Sequel::SQL::Function.new(:object_id, table_name), col_name(:parent_object_id, :parent_column_id) => column_name.to_s}}.
249
+ get(:name)
250
+ end
251
+ end
252
+
115
253
  # The SQL to drop an index for the table.
116
254
  def drop_index_sql(table, op)
117
255
  "DROP INDEX #{quote_identifier(op[:name] || default_index_name(table, op[:columns]))} ON #{quote_schema_table(table)}"
118
256
  end
119
257
 
258
+ # support for clustered index type
259
+ def index_definition_sql(table_name, index)
260
+ index_name = index[:name] || default_index_name(table_name, index[:columns])
261
+ if index[:type] == :full_text
262
+ "CREATE FULLTEXT INDEX ON #{quote_schema_table(table_name)} #{literal(index[:columns])} KEY INDEX #{literal(index[:key_index])}"
263
+ else
264
+ "CREATE #{'UNIQUE ' if index[:unique]}#{'CLUSTERED ' if index[:type] == :clustered}INDEX #{quote_identifier(index_name)} ON #{quote_schema_table(table_name)} #{literal(index[:columns])}#{" INCLUDE #{literal(index[:include])}" if index[:include]}#{" WHERE #{filter_expr(index[:where])}" if index[:where]}"
265
+ end
266
+ end
267
+
268
+ # Backbone of the tables and views support.
269
+ def information_schema_tables(type, opts)
270
+ m = output_identifier_meth
271
+ metadata_dataset.from(:information_schema__tables___t).
272
+ select(:table_name).
273
+ filter(:table_type=>type, :table_schema=>(opts[:schema]||default_schema||'dbo').to_s).
274
+ map{|x| m.call(x[:table_name])}
275
+ end
276
+
120
277
  # Always quote identifiers in the metadata_dataset, so schema parsing works.
121
278
  def metadata_dataset
122
279
  ds = super
@@ -139,20 +296,41 @@ module Sequel
139
296
  SQL_ROLLBACK
140
297
  end
141
298
 
142
- # MSSQL uses the INFORMATION_SCHEMA to hold column information. This method does
143
- # not support the parsing of primary key information.
299
+ # The closest MSSQL equivalent of a boolean datatype is the bit type.
300
+ def schema_column_type(db_type)
301
+ case db_type
302
+ when /\A(bit)\z/io
303
+ :boolean
304
+ else
305
+ super
306
+ end
307
+ end
308
+
309
+ # MSSQL uses the INFORMATION_SCHEMA to hold column information, and
310
+ # parses primary key information from the sysindexes, sysindexkeys,
311
+ # and syscolumns system tables.
144
312
  def schema_parse_table(table_name, opts)
145
- m = output_identifier_meth
146
- m2 = input_identifier_meth
313
+ m = output_identifier_meth(opts[:dataset])
314
+ m2 = input_identifier_meth(opts[:dataset])
315
+ tn = m2.call(table_name.to_s)
316
+ table_id = get{object_id(tn)}
317
+ pk_index_id = metadata_dataset.from(:sysindexes).
318
+ where(:id=>table_id, :indid=>1..254){{(status & 2048)=>2048}}.
319
+ get(:indid)
320
+ pk_cols = metadata_dataset.from(:sysindexkeys___sik).
321
+ join(:syscolumns___sc, :id=>:id, :colid=>:colid).
322
+ where(:sik__id=>table_id, :sik__indid=>pk_index_id).
323
+ select_order_map(:sc__name)
147
324
  ds = metadata_dataset.from(:information_schema__tables___t).
148
325
  join(:information_schema__columns___c, :table_catalog=>:table_catalog,
149
326
  :table_schema => :table_schema, :table_name => :table_name).
150
327
  select(:column_name___column, :data_type___db_type, :character_maximum_length___max_chars, :column_default___default, :is_nullable___allow_null, :numeric_precision___column_size, :numeric_scale___scale).
151
- filter(:c__table_name=>m2.call(table_name.to_s))
328
+ filter(:c__table_name=>tn)
152
329
  if schema = opts[:schema] || default_schema
153
330
  ds.filter!(:c__table_schema=>schema)
154
331
  end
155
332
  ds.map do |row|
333
+ row[:primary_key] = pk_cols.include?(row[:column])
156
334
  row[:allow_null] = row[:allow_null] == 'YES' ? true : false
157
335
  row[:default] = nil if blank_object?(row[:default])
158
336
  row[:type] = if row[:db_type] =~ DECIMAL_TYPE_RE && row[:scale] == 0
@@ -163,6 +341,11 @@ module Sequel
163
341
  [m.call(row.delete(:column)), row]
164
342
  end
165
343
  end
344
+
345
+ # Set the mssql_unicode_strings settings from the given options.
346
+ def set_mssql_unicode_strings
347
+ @mssql_unicode_strings = typecast_value_boolean(@opts.fetch(:mssql_unicode_strings, true))
348
+ end
166
349
 
167
350
  # MSSQL has both datetime and timestamp classes, most people are going
168
351
  # to want datetime
@@ -185,53 +368,100 @@ module Sequel
185
368
  def type_literal_generic_file(column)
186
369
  :'varbinary(max)'
187
370
  end
188
-
189
- # support for clustered index type
190
- def index_definition_sql(table_name, index)
191
- index_name = index[:name] || default_index_name(table_name, index[:columns])
192
- clustered = index[:type] == :clustered
193
- if index[:where]
194
- raise Error, "Partial indexes are not supported for this database"
195
- else
196
- "CREATE #{'UNIQUE ' if index[:unique]}#{'CLUSTERED ' if clustered}INDEX #{quote_identifier(index_name)} ON #{quote_schema_table(table_name)} #{literal(index[:columns])}"
197
- end
198
- end
199
371
  end
200
372
 
201
373
  module DatasetMethods
374
+ include EmulateOffsetWithRowNumber
375
+
202
376
  BOOL_TRUE = '1'.freeze
203
377
  BOOL_FALSE = '0'.freeze
204
378
  COMMA_SEPARATOR = ', '.freeze
205
- DELETE_CLAUSE_METHODS = Dataset.clause_methods(:delete, %w'with from output from2 where')
206
- INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'with into columns output values')
207
- SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with distinct limit columns into from lock join where group having order compounds')
208
- UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'with table set output from where')
379
+ DELETE_CLAUSE_METHODS = Dataset.clause_methods(:delete, %w'with delete from output from2 where')
380
+ INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'with insert into columns output values')
381
+ SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with select distinct limit columns into from lock join where group having order compounds')
382
+ UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'with update limit table set output from where')
209
383
  NOLOCK = ' WITH (NOLOCK)'.freeze
210
384
  UPDLOCK = ' WITH (UPDLOCK)'.freeze
211
385
  WILDCARD = LiteralString.new('*').freeze
212
386
  CONSTANT_MAP = {:CURRENT_DATE=>'CAST(CURRENT_TIMESTAMP AS DATE)'.freeze, :CURRENT_TIME=>'CAST(CURRENT_TIMESTAMP AS TIME)'.freeze}
387
+ EXTRACT_MAP = {:year=>"yy", :month=>"m", :day=>"d", :hour=>"hh", :minute=>"n", :second=>"s"}
388
+ BRACKET_CLOSE = Dataset::BRACKET_CLOSE
389
+ BRACKET_OPEN = Dataset::BRACKET_OPEN
390
+ COMMA = Dataset::COMMA
391
+ PAREN_CLOSE = Dataset::PAREN_CLOSE
392
+ PAREN_SPACE_OPEN = Dataset::PAREN_SPACE_OPEN
393
+ SPACE = Dataset::SPACE
394
+ FROM = Dataset::FROM
395
+ APOS = Dataset::APOS
396
+ APOS_RE = Dataset::APOS_RE
397
+ DOUBLE_APOS = Dataset::DOUBLE_APOS
398
+ INTO = Dataset::INTO
399
+ DATEPART_SECOND_OPEN = "CAST((datepart(".freeze
400
+ DATEPART_SECOND_MIDDLE = ') + datepart(ns, '.freeze
401
+ DATEPART_SECOND_CLOSE = ")/1000000000.0) AS double precision)".freeze
402
+ DATEPART_OPEN = "datepart(".freeze
403
+ UNION_ALL = ' UNION ALL '.freeze
404
+ SELECT_SPACE = 'SELECT '.freeze
405
+ TIMESTAMP_USEC_FORMAT = ".%03d".freeze
406
+ OUTPUT_INSERTED = " OUTPUT INSERTED.*".freeze
407
+ HEX_START = '0x'.freeze
408
+ UNICODE_STRING_START = "N'".freeze
409
+ BACKSLASH_CRLF_RE = /\\((?:\r\n)|\n)/.freeze
410
+ BACKSLASH_CRLF_REPLACE = '\\\\\\\\\\1\\1'.freeze
411
+ TOP_PAREN = " TOP (".freeze
412
+ TOP = " TOP ".freeze
413
+ OUTPUT = " OUTPUT ".freeze
414
+ HSTAR = "H*".freeze
415
+ CASE_SENSITIVE_COLLATION = 'Latin1_General_CS_AS'.freeze
416
+ CASE_INSENSITIVE_COLLATION = 'Latin1_General_CI_AS'.freeze
417
+ DEFAULT_TIMESTAMP_FORMAT = "'%Y-%m-%dT%H:%M:%S%N%z'".freeze
418
+ FORMAT_DATE = "'%Y%m%d'".freeze
419
+
420
+ # Allow overriding of the mssql_unicode_strings option at the dataset level.
421
+ attr_accessor :mssql_unicode_strings
422
+
423
+ # Copy the mssql_unicode_strings option from the +db+ object.
424
+ def initialize(db, opts={})
425
+ super
426
+ @mssql_unicode_strings = db.mssql_unicode_strings
427
+ end
213
428
 
214
429
  # MSSQL uses + for string concatenation, and LIKE is case insensitive by default.
215
- def complex_expression_sql(op, args)
430
+ def complex_expression_sql_append(sql, op, args)
216
431
  case op
217
432
  when :'||'
218
- super(:+, args)
219
- when :ILIKE
220
- super(:LIKE, args)
221
- when :"NOT ILIKE"
222
- super(:"NOT LIKE", args)
433
+ super(sql, :+, args)
434
+ when :LIKE, :"NOT LIKE"
435
+ super(sql, op, args.map{|a| LiteralString.new("(#{literal(a)} COLLATE #{CASE_SENSITIVE_COLLATION})")})
436
+ when :ILIKE, :"NOT ILIKE"
437
+ super(sql, (op == :ILIKE ? :LIKE : :"NOT LIKE"), args.map{|a| LiteralString.new("(#{literal(a)} COLLATE #{CASE_INSENSITIVE_COLLATION})")})
223
438
  when :<<
224
- "(#{literal(args[0])} * POWER(2, #{literal(args[1])}))"
439
+ sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} * POWER(2, #{literal(b)}))"}
225
440
  when :>>
226
- "(#{literal(args[0])} / POWER(2, #{literal(args[1])}))"
441
+ sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} / POWER(2, #{literal(b)}))"}
442
+ when :extract
443
+ part = args.at(0)
444
+ raise(Sequel::Error, "unsupported extract argument: #{part.inspect}") unless format = EXTRACT_MAP[part]
445
+ if part == :second
446
+ expr = literal(args.at(1))
447
+ sql << DATEPART_SECOND_OPEN << format.to_s << COMMA << expr << DATEPART_SECOND_MIDDLE << expr << DATEPART_SECOND_CLOSE
448
+ else
449
+ sql << DATEPART_OPEN << format.to_s << COMMA
450
+ literal_append(sql, args.at(1))
451
+ sql << PAREN_CLOSE
452
+ end
227
453
  else
228
- super(op, args)
454
+ super
229
455
  end
230
456
  end
231
457
 
232
458
  # MSSQL doesn't support the SQL standard CURRENT_DATE or CURRENT_TIME
233
- def constant_sql(constant)
234
- CONSTANT_MAP[constant] || super
459
+ def constant_sql_append(sql, constant)
460
+ if c = CONSTANT_MAP[constant]
461
+ sql << c
462
+ else
463
+ super
464
+ end
235
465
  end
236
466
 
237
467
  # Disable the use of INSERT OUTPUT
@@ -244,21 +474,31 @@ module Sequel
244
474
  mutation_method(:disable_insert_output)
245
475
  end
246
476
 
247
- # When returning all rows, if an offset is used, delete the row_number column
248
- # before yielding the row.
249
- def fetch_rows(sql, &block)
250
- @opts[:offset] ? super(sql){|r| r.delete(row_number_column); yield r} : super(sql, &block)
477
+ # There is no function on Microsoft SQL Server that does character length
478
+ # and respects trailing spaces (datalength respects trailing spaces, but
479
+ # counts bytes instead of characters). Use a hack to work around the
480
+ # trailing spaces issue.
481
+ def emulated_function_sql_append(sql, f)
482
+ case f.f
483
+ when :char_length
484
+ literal_append(sql, SQL::Function.new(:len, Sequel.join([f.args.first, 'x'])) - 1)
485
+ when :trim
486
+ literal_append(sql, SQL::Function.new(:ltrim, SQL::Function.new(:rtrim, f.args.first)))
487
+ else
488
+ super
489
+ end
251
490
  end
252
491
 
253
492
  # MSSQL uses the CONTAINS keyword for full text search
254
493
  def full_text_search(cols, terms, opts = {})
255
- filter("CONTAINS (#{literal(cols)}, #{literal(terms)})")
494
+ terms = "\"#{terms.join('" OR "')}\"" if terms.is_a?(Array)
495
+ filter("CONTAINS (?, ?)", cols, terms)
256
496
  end
257
497
 
258
498
  # Use the OUTPUT clause to get the value of all columns for the newly inserted record.
259
499
  def insert_select(*values)
260
- return unless supports_output_clause?
261
- naked.clone(default_server_opts(:sql=>output(nil, [:inserted.*]).insert_sql(*values))).single_record unless opts[:disable_insert_output]
500
+ return unless supports_insert_select?
501
+ naked.clone(default_server_opts(:sql=>output(nil, [SQL::ColumnAll.new(:inserted)]).insert_sql(*values))).single_record
262
502
  end
263
503
 
264
504
  # Specify a table for a SELECT ... INTO query.
@@ -266,17 +506,18 @@ module Sequel
266
506
  clone(:into => table)
267
507
  end
268
508
 
269
- # SQL Server does not support CTEs on subqueries, so move any CTEs
270
- # on joined datasets to the top level. The user is responsible for
271
- # resolving any name clashes this may cause.
272
- def join_table(type, table, expr=nil, table_alias={}, &block)
273
- return super unless Dataset === table && table.opts[:with]
274
- clone(:with => (opts[:with] || []) + table.opts[:with]).join_table(type, table.clone(:with => nil), expr, table_alias, &block)
275
- end
276
-
277
509
  # MSSQL uses a UNION ALL statement to insert multiple values at once.
278
510
  def multi_insert_sql(columns, values)
279
- [insert_sql(columns, LiteralString.new(values.map {|r| "SELECT #{expression_list(r)}" }.join(" UNION ALL ")))]
511
+ c = false
512
+ sql = LiteralString.new('')
513
+ u = UNION_ALL
514
+ values.each do |v|
515
+ sql << u if c
516
+ sql << SELECT_SPACE
517
+ expression_list_append(sql, v)
518
+ c ||= true
519
+ end
520
+ [insert_sql(columns, sql)]
280
521
  end
281
522
 
282
523
  # Allows you to do a dirty read of uncommitted data using WITH (NOLOCK).
@@ -314,39 +555,32 @@ module Sequel
314
555
  mutation_method(:output, into, values)
315
556
  end
316
557
 
317
- # MSSQL uses [] to quote identifiers
318
- def quoted_identifier(name)
319
- "[#{name}]"
558
+ # MSSQL uses [] to quote identifiers. MSSQL does not support
559
+ # escaping of ], so you cannot use that character in an identifier.
560
+ def quoted_identifier_append(sql, name)
561
+ sql << BRACKET_OPEN << name.to_s << BRACKET_CLOSE
320
562
  end
321
563
 
322
- # MSSQL Requires the use of the ROW_NUMBER window function to emulate
323
- # an offset. This implementation requires MSSQL 2005 or greater (offset
324
- # can't be emulated well in MSSQL 2000).
325
- #
326
- # The implementation is ugly, cloning the current dataset and modifying
327
- # the clone to add a ROW_NUMBER window function (and some other things),
328
- # then using the modified clone in a subselect which is selected from.
329
- #
330
- # If offset is used, an order must be provided, because the use of ROW_NUMBER
331
- # requires an order.
332
- def select_sql
333
- return super unless o = @opts[:offset]
334
- raise(Error, 'MSSQL requires an order be provided if using an offset') unless order = @opts[:order]
335
- dsa1 = dataset_alias(1)
336
- rn = row_number_column
337
- subselect_sql(unlimited.
338
- unordered.
339
- select_append{ROW_NUMBER(:over, :order=>order){}.as(rn)}.
340
- from_self(:alias=>dsa1).
341
- limit(@opts[:limit]).
342
- where(SQL::Identifier.new(rn) > o))
343
- end
344
-
345
564
  # The version of the database server.
346
565
  def server_version
347
566
  db.server_version(@opts[:server])
348
567
  end
349
568
 
569
+ # MSSQL 2005+ supports GROUP BY CUBE.
570
+ def supports_group_cube?
571
+ is_2005_or_later?
572
+ end
573
+
574
+ # MSSQL 2005+ supports GROUP BY ROLLUP
575
+ def supports_group_rollup?
576
+ is_2005_or_later?
577
+ end
578
+
579
+ # MSSQL supports insert_select via the OUTPUT clause.
580
+ def supports_insert_select?
581
+ supports_output_clause? && !opts[:disable_insert_output]
582
+ end
583
+
350
584
  # MSSQL 2005+ supports INTERSECT and EXCEPT
351
585
  def supports_intersect_except?
352
586
  is_2005_or_later?
@@ -381,18 +615,55 @@ module Sequel
381
615
  def supports_window_functions?
382
616
  true
383
617
  end
618
+
619
+ # MSSQL cannot use WHERE 1.
620
+ def supports_where_true?
621
+ false
622
+ end
384
623
 
385
624
  protected
625
+
626
+ # If returned primary keys are requested, use OUTPUT unless already set on the
627
+ # dataset. If OUTPUT is already set, use existing returning values. If OUTPUT
628
+ # is only set to return a single columns, return an array of just that column.
629
+ # Otherwise, return an array of hashes.
630
+ def _import(columns, values, opts={})
631
+ if opts[:return] == :primary_key && !@opts[:output]
632
+ output(nil, [SQL::QualifiedIdentifier.new(:inserted, first_primary_key)])._import(columns, values, opts)
633
+ elsif @opts[:output]
634
+ statements = multi_insert_sql(columns, values)
635
+ @db.transaction(opts.merge(:server=>@opts[:server])) do
636
+ statements.map{|st| with_sql(st)}
637
+ end.first.map{|v| v.length == 1 ? v.values.first : v}
638
+ else
639
+ super
640
+ end
641
+ end
642
+
386
643
  # MSSQL does not allow ordering in sub-clauses unless 'top' (limit) is specified
387
644
  def aggregate_dataset
388
645
  (options_overlap(Sequel::Dataset::COUNT_FROM_SELF_OPTS) && !options_overlap([:limit])) ? unordered.from_self : super
389
646
  end
390
647
 
391
648
  private
649
+
650
+ # Whether we are using SQL Server 2005 or later.
392
651
  def is_2005_or_later?
393
652
  server_version >= 9000000
394
653
  end
395
654
 
655
+ # Whether we are using SQL Server 2008 or later.
656
+ def is_2008_or_later?
657
+ server_version >= 10000000
658
+ end
659
+
660
+ # Use strict ISO-8601 format with T between date and time,
661
+ # since that is the format that is multilanguage and not
662
+ # DATEFORMAT dependent.
663
+ def default_timestamp_format
664
+ DEFAULT_TIMESTAMP_FORMAT
665
+ end
666
+
396
667
  # MSSQL supports the OUTPUT clause for DELETE statements.
397
668
  # It also allows prepending a WITH clause.
398
669
  def delete_clause_methods
@@ -401,7 +672,8 @@ module Sequel
401
672
 
402
673
  # Only include the primary table in the main delete clause
403
674
  def delete_from_sql(sql)
404
- sql << " FROM #{source_list(@opts[:from][0..0])}"
675
+ sql << FROM
676
+ source_list_append(sql, @opts[:from][0..0])
405
677
  end
406
678
 
407
679
  # MSSQL supports FROM clauses in DELETE and UPDATE statements.
@@ -413,19 +685,17 @@ module Sequel
413
685
  end
414
686
  alias update_from_sql delete_from2_sql
415
687
 
416
- # Handle the with clause for delete, insert, and update statements
417
- # to be the same as the insert statement.
418
- def delete_with_sql(sql)
419
- select_with_sql(sql)
688
+ # Return the first primary key for the current table. If this table has
689
+ # multiple primary keys, this will only return one of them. Used by #_import.
690
+ def first_primary_key
691
+ @db.schema(self).map{|k, v| k if v[:primary_key] == true}.compact.first
420
692
  end
421
- alias insert_with_sql delete_with_sql
422
- alias update_with_sql delete_with_sql
423
-
693
+
424
694
  # MSSQL raises an error if you try to provide more than 3 decimal places
425
695
  # for a fractional timestamp. This probably doesn't work for smalldatetime
426
696
  # fields.
427
697
  def format_timestamp_usec(usec)
428
- sprintf(".%03d", usec/1000)
698
+ sprintf(TIMESTAMP_USEC_FORMAT, usec/1000)
429
699
  end
430
700
 
431
701
  # MSSQL supports the OUTPUT clause for INSERT statements.
@@ -434,51 +704,71 @@ module Sequel
434
704
  INSERT_CLAUSE_METHODS
435
705
  end
436
706
 
707
+ # Use OUTPUT INSERTED.* to return all columns of the inserted row,
708
+ # for use with the prepared statement code.
709
+ def insert_output_sql(sql)
710
+ if @opts.has_key?(:returning)
711
+ sql << OUTPUT_INSERTED
712
+ else
713
+ output_sql(sql)
714
+ end
715
+ end
716
+
437
717
  # MSSQL uses a literal hexidecimal number for blob strings
438
- def literal_blob(v)
439
- blob = '0x'
440
- v.each_byte{|x| blob << sprintf('%02x', x)}
441
- blob
718
+ def literal_blob_append(sql, v)
719
+ sql << HEX_START << v.unpack(HSTAR).first
442
720
  end
443
721
 
444
- # Use unicode string syntax for all strings. Don't double backslashes.
445
- def literal_string(v)
446
- "N'#{v.gsub(/'/, "''")}'"
722
+ # Use YYYYmmdd format, since that's the only want that is
723
+ # multilanguage and not DATEFORMAT dependent.
724
+ def literal_date(v)
725
+ v.strftime(FORMAT_DATE)
447
726
  end
448
-
727
+
449
728
  # Use 0 for false on MSSQL
450
729
  def literal_false
451
730
  BOOL_FALSE
452
731
  end
453
732
 
733
+ # Optionally use unicode string syntax for all strings. Don't double
734
+ # backslashes.
735
+ def literal_string_append(sql, v)
736
+ sql << (mssql_unicode_strings ? UNICODE_STRING_START : APOS)
737
+ sql << v.gsub(APOS_RE, DOUBLE_APOS).gsub(BACKSLASH_CRLF_RE, BACKSLASH_CRLF_REPLACE) << APOS
738
+ end
739
+
454
740
  # Use 1 for true on MSSQL
455
741
  def literal_true
456
742
  BOOL_TRUE
457
743
  end
458
744
 
459
- # The alias to use for the row_number column when emulating OFFSET
460
- def row_number_column
461
- :x_sequel_row_number_x
462
- end
463
-
464
745
  # MSSQL adds the limit before the columns
465
746
  def select_clause_methods
466
747
  SELECT_CLAUSE_METHODS
467
748
  end
468
749
 
469
750
  def select_into_sql(sql)
470
- sql << " INTO #{table_ref(@opts[:into])}" if @opts[:into]
751
+ if i = @opts[:into]
752
+ sql << INTO
753
+ identifier_append(sql, i)
754
+ end
471
755
  end
472
756
 
473
757
  # MSSQL uses TOP N for limit. For MSSQL 2005+ TOP (N) is used
474
758
  # to allow the limit to be a bound variable.
475
759
  def select_limit_sql(sql)
476
760
  if l = @opts[:limit]
477
- l = literal(l)
478
- l = "(#{l})" if server_version >= 9000000
479
- sql << " TOP #{l}"
761
+ if is_2005_or_later?
762
+ sql << TOP_PAREN
763
+ literal_append(sql, l)
764
+ sql << PAREN_CLOSE
765
+ else
766
+ sql << TOP
767
+ literal_append(sql, l)
768
+ end
480
769
  end
481
770
  end
771
+ alias update_limit_sql select_limit_sql
482
772
 
483
773
  # Support different types of locking styles
484
774
  def select_lock_sql(sql)
@@ -496,19 +786,20 @@ module Sequel
496
786
  def output_sql(sql)
497
787
  return unless supports_output_clause?
498
788
  return unless output = @opts[:output]
499
- sql << " OUTPUT #{column_list(output[:select_list])}"
789
+ sql << OUTPUT
790
+ column_list_append(sql, output[:select_list])
500
791
  if into = output[:into]
501
- sql << " INTO #{table_ref(into)}"
792
+ sql << INTO
793
+ identifier_append(sql, into)
502
794
  if column_list = output[:column_list]
503
- cl = []
504
- column_list.each { |k, v| cl << literal(String === k ? k.to_sym : k) }
505
- sql << " (#{cl.join(COMMA_SEPARATOR)})"
795
+ sql << PAREN_SPACE_OPEN
796
+ source_list_append(sql, column_list)
797
+ sql << PAREN_CLOSE
506
798
  end
507
799
  end
508
800
  end
509
801
  alias delete_output_sql output_sql
510
802
  alias update_output_sql output_sql
511
- alias insert_output_sql output_sql
512
803
 
513
804
  # MSSQL supports the OUTPUT clause for UPDATE statements.
514
805
  # It also allows prepending a WITH clause.
@@ -518,7 +809,12 @@ module Sequel
518
809
 
519
810
  # Only include the primary table in the main update clause
520
811
  def update_table_sql(sql)
521
- sql << " #{source_list(@opts[:from][0..0])}"
812
+ sql << SPACE
813
+ source_list_append(sql, @opts[:from][0..0])
814
+ end
815
+
816
+ def uses_with_rollup?
817
+ !is_2008_or_later?
522
818
  end
523
819
  end
524
820
  end