sequel 3.21.0 → 3.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (352) hide show
  1. data/CHANGELOG +1061 -3
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +103 -63
  4. data/Rakefile +59 -27
  5. data/bin/sequel +50 -26
  6. data/doc/active_record.rdoc +67 -51
  7. data/doc/advanced_associations.rdoc +189 -75
  8. data/doc/association_basics.rdoc +327 -79
  9. data/doc/cheat_sheet.rdoc +21 -21
  10. data/doc/core_extensions.rdoc +374 -0
  11. data/doc/dataset_basics.rdoc +10 -10
  12. data/doc/dataset_filtering.rdoc +54 -42
  13. data/doc/mass_assignment.rdoc +56 -0
  14. data/doc/migration.rdoc +89 -557
  15. data/doc/model_hooks.rdoc +80 -29
  16. data/doc/object_model.rdoc +547 -0
  17. data/doc/opening_databases.rdoc +114 -53
  18. data/doc/prepared_statements.rdoc +41 -15
  19. data/doc/querying.rdoc +137 -56
  20. data/doc/reflection.rdoc +36 -10
  21. data/doc/release_notes/3.22.0.txt +39 -0
  22. data/doc/release_notes/3.23.0.txt +172 -0
  23. data/doc/release_notes/3.24.0.txt +420 -0
  24. data/doc/release_notes/3.25.0.txt +88 -0
  25. data/doc/release_notes/3.26.0.txt +88 -0
  26. data/doc/release_notes/3.27.0.txt +82 -0
  27. data/doc/release_notes/3.28.0.txt +304 -0
  28. data/doc/release_notes/3.29.0.txt +459 -0
  29. data/doc/release_notes/3.30.0.txt +135 -0
  30. data/doc/release_notes/3.31.0.txt +146 -0
  31. data/doc/release_notes/3.32.0.txt +202 -0
  32. data/doc/release_notes/3.33.0.txt +157 -0
  33. data/doc/release_notes/3.34.0.txt +671 -0
  34. data/doc/release_notes/3.35.0.txt +144 -0
  35. data/doc/release_notes/3.36.0.txt +245 -0
  36. data/doc/release_notes/3.37.0.txt +338 -0
  37. data/doc/release_notes/3.38.0.txt +234 -0
  38. data/doc/release_notes/3.39.0.txt +237 -0
  39. data/doc/schema_modification.rdoc +585 -0
  40. data/doc/sharding.rdoc +99 -8
  41. data/doc/sql.rdoc +154 -112
  42. data/doc/testing.rdoc +169 -0
  43. data/doc/thread_safety.rdoc +17 -0
  44. data/doc/transactions.rdoc +137 -0
  45. data/doc/validations.rdoc +1 -1
  46. data/doc/virtual_rows.rdoc +78 -43
  47. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  48. data/lib/sequel/adapters/ado.rb +40 -18
  49. data/lib/sequel/adapters/amalgalite.rb +17 -9
  50. data/lib/sequel/adapters/db2.rb +175 -86
  51. data/lib/sequel/adapters/dbi.rb +15 -15
  52. data/lib/sequel/adapters/do/mysql.rb +17 -11
  53. data/lib/sequel/adapters/do/postgres.rb +2 -61
  54. data/lib/sequel/adapters/do/sqlite.rb +0 -10
  55. data/lib/sequel/adapters/do.rb +18 -36
  56. data/lib/sequel/adapters/firebird.rb +27 -208
  57. data/lib/sequel/adapters/ibmdb.rb +453 -0
  58. data/lib/sequel/adapters/informix.rb +6 -23
  59. data/lib/sequel/adapters/jdbc/as400.rb +16 -34
  60. data/lib/sequel/adapters/jdbc/db2.rb +56 -0
  61. data/lib/sequel/adapters/jdbc/derby.rb +325 -0
  62. data/lib/sequel/adapters/jdbc/firebird.rb +24 -0
  63. data/lib/sequel/adapters/jdbc/h2.rb +73 -31
  64. data/lib/sequel/adapters/jdbc/hsqldb.rb +184 -0
  65. data/lib/sequel/adapters/jdbc/informix.rb +21 -0
  66. data/lib/sequel/adapters/jdbc/jtds.rb +40 -0
  67. data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
  68. data/lib/sequel/adapters/jdbc/mysql.rb +7 -24
  69. data/lib/sequel/adapters/jdbc/oracle.rb +88 -25
  70. data/lib/sequel/adapters/jdbc/postgresql.rb +77 -52
  71. data/lib/sequel/adapters/jdbc/progress.rb +21 -0
  72. data/lib/sequel/adapters/jdbc/sqlite.rb +20 -10
  73. data/lib/sequel/adapters/jdbc/sqlserver.rb +66 -0
  74. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  75. data/lib/sequel/adapters/jdbc.rb +279 -97
  76. data/lib/sequel/adapters/mock.rb +372 -0
  77. data/lib/sequel/adapters/mysql.rb +125 -222
  78. data/lib/sequel/adapters/mysql2.rb +65 -56
  79. data/lib/sequel/adapters/odbc/mssql.rb +17 -9
  80. data/lib/sequel/adapters/odbc.rb +28 -9
  81. data/lib/sequel/adapters/openbase.rb +3 -5
  82. data/lib/sequel/adapters/oracle.rb +349 -53
  83. data/lib/sequel/adapters/postgres.rb +368 -138
  84. data/lib/sequel/adapters/shared/access.rb +32 -9
  85. data/lib/sequel/adapters/shared/db2.rb +343 -0
  86. data/lib/sequel/adapters/shared/firebird.rb +221 -0
  87. data/lib/sequel/adapters/shared/informix.rb +53 -0
  88. data/lib/sequel/adapters/shared/mssql.rb +401 -116
  89. data/lib/sequel/adapters/shared/mysql.rb +424 -89
  90. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +155 -0
  91. data/lib/sequel/adapters/shared/oracle.rb +257 -45
  92. data/lib/sequel/adapters/shared/postgres.rb +675 -279
  93. data/lib/sequel/adapters/shared/progress.rb +5 -8
  94. data/lib/sequel/adapters/shared/sqlite.rb +285 -96
  95. data/lib/sequel/adapters/sqlite.rb +101 -39
  96. data/lib/sequel/adapters/swift/mysql.rb +10 -12
  97. data/lib/sequel/adapters/swift/postgres.rb +13 -79
  98. data/lib/sequel/adapters/swift/sqlite.rb +9 -6
  99. data/lib/sequel/adapters/swift.rb +28 -19
  100. data/lib/sequel/adapters/tinytds.rb +174 -22
  101. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +77 -0
  102. data/lib/sequel/adapters/utils/pg_types.rb +81 -0
  103. data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
  104. data/lib/sequel/ast_transformer.rb +194 -0
  105. data/lib/sequel/connection_pool/sharded_single.rb +11 -1
  106. data/lib/sequel/connection_pool/sharded_threaded.rb +33 -2
  107. data/lib/sequel/connection_pool/single.rb +5 -0
  108. data/lib/sequel/connection_pool/threaded.rb +43 -14
  109. data/lib/sequel/connection_pool.rb +10 -2
  110. data/lib/sequel/core.rb +143 -11
  111. data/lib/sequel/database/connecting.rb +38 -28
  112. data/lib/sequel/database/dataset.rb +4 -4
  113. data/lib/sequel/database/dataset_defaults.rb +63 -2
  114. data/lib/sequel/database/logging.rb +7 -2
  115. data/lib/sequel/database/misc.rb +190 -46
  116. data/lib/sequel/database/query.rb +272 -110
  117. data/lib/sequel/database/schema_generator.rb +103 -36
  118. data/lib/sequel/database/schema_methods.rb +300 -67
  119. data/lib/sequel/dataset/actions.rb +344 -90
  120. data/lib/sequel/dataset/features.rb +101 -8
  121. data/lib/sequel/dataset/graph.rb +42 -15
  122. data/lib/sequel/dataset/misc.rb +52 -25
  123. data/lib/sequel/dataset/mutation.rb +12 -7
  124. data/lib/sequel/dataset/prepared_statements.rb +68 -30
  125. data/lib/sequel/dataset/query.rb +305 -54
  126. data/lib/sequel/dataset/sql.rb +720 -292
  127. data/lib/sequel/dataset.rb +8 -0
  128. data/lib/sequel/exceptions.rb +4 -0
  129. data/lib/sequel/extensions/_pretty_table.rb +83 -0
  130. data/lib/sequel/extensions/arbitrary_servers.rb +109 -0
  131. data/lib/sequel/extensions/blank.rb +4 -0
  132. data/lib/sequel/extensions/columns_introspection.rb +72 -0
  133. data/lib/sequel/extensions/constraint_validations.rb +451 -0
  134. data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +30 -46
  135. data/lib/sequel/extensions/eval_inspect.rb +173 -0
  136. data/lib/sequel/extensions/inflector.rb +4 -0
  137. data/lib/sequel/extensions/looser_typecasting.rb +5 -4
  138. data/lib/sequel/extensions/migration.rb +100 -11
  139. data/lib/sequel/extensions/named_timezones.rb +9 -0
  140. data/lib/sequel/extensions/null_dataset.rb +94 -0
  141. data/lib/sequel/extensions/pagination.rb +4 -0
  142. data/lib/sequel/extensions/pg_array.rb +537 -0
  143. data/lib/sequel/extensions/pg_array_ops.rb +263 -0
  144. data/lib/sequel/extensions/pg_auto_parameterize.rb +175 -0
  145. data/lib/sequel/extensions/pg_hstore.rb +313 -0
  146. data/lib/sequel/extensions/pg_hstore_ops.rb +293 -0
  147. data/lib/sequel/extensions/pg_inet.rb +113 -0
  148. data/lib/sequel/extensions/pg_interval.rb +191 -0
  149. data/lib/sequel/extensions/pg_json.rb +208 -0
  150. data/lib/sequel/extensions/pg_range.rb +512 -0
  151. data/lib/sequel/extensions/pg_range_ops.rb +150 -0
  152. data/lib/sequel/extensions/pg_row.rb +572 -0
  153. data/lib/sequel/extensions/pg_row_ops.rb +182 -0
  154. data/lib/sequel/extensions/pg_statement_cache.rb +317 -0
  155. data/lib/sequel/extensions/pretty_table.rb +17 -72
  156. data/lib/sequel/extensions/query.rb +8 -4
  157. data/lib/sequel/extensions/query_literals.rb +79 -0
  158. data/lib/sequel/extensions/schema_caching.rb +76 -0
  159. data/lib/sequel/extensions/schema_dumper.rb +282 -76
  160. data/lib/sequel/extensions/select_remove.rb +39 -0
  161. data/lib/sequel/extensions/server_block.rb +140 -0
  162. data/lib/sequel/extensions/split_array_nil.rb +65 -0
  163. data/lib/sequel/extensions/sql_expr.rb +8 -110
  164. data/lib/sequel/extensions/string_date_time.rb +4 -0
  165. data/lib/sequel/extensions/thread_local_timezones.rb +10 -4
  166. data/lib/sequel/extensions/to_dot.rb +99 -83
  167. data/lib/sequel/model/associations.rb +1263 -409
  168. data/lib/sequel/model/base.rb +624 -171
  169. data/lib/sequel/model/errors.rb +1 -1
  170. data/lib/sequel/model/exceptions.rb +24 -2
  171. data/lib/sequel/model/inflections.rb +1 -1
  172. data/lib/sequel/model.rb +30 -11
  173. data/lib/sequel/no_core_ext.rb +2 -0
  174. data/lib/sequel/plugins/active_model.rb +13 -1
  175. data/lib/sequel/plugins/association_pks.rb +22 -4
  176. data/lib/sequel/plugins/caching.rb +25 -18
  177. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  178. data/lib/sequel/plugins/composition.rb +44 -12
  179. data/lib/sequel/plugins/constraint_validations.rb +198 -0
  180. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  181. data/lib/sequel/plugins/defaults_setter.rb +72 -0
  182. data/lib/sequel/plugins/dirty.rb +214 -0
  183. data/lib/sequel/plugins/eager_each.rb +59 -0
  184. data/lib/sequel/plugins/force_encoding.rb +6 -6
  185. data/lib/sequel/plugins/hook_class_methods.rb +1 -1
  186. data/lib/sequel/plugins/identity_map.rb +134 -15
  187. data/lib/sequel/plugins/instance_filters.rb +10 -0
  188. data/lib/sequel/plugins/instance_hooks.rb +1 -1
  189. data/lib/sequel/plugins/json_serializer.rb +58 -6
  190. data/lib/sequel/plugins/list.rb +13 -2
  191. data/lib/sequel/plugins/many_through_many.rb +103 -51
  192. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
  193. data/lib/sequel/plugins/nested_attributes.rb +150 -66
  194. data/lib/sequel/plugins/optimistic_locking.rb +8 -0
  195. data/lib/sequel/plugins/pg_row.rb +121 -0
  196. data/lib/sequel/plugins/pg_typecast_on_load.rb +65 -0
  197. data/lib/sequel/plugins/prepared_statements.rb +167 -0
  198. data/lib/sequel/plugins/prepared_statements_associations.rb +87 -0
  199. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  200. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  201. data/lib/sequel/plugins/rcte_tree.rb +31 -17
  202. data/lib/sequel/plugins/schema.rb +8 -3
  203. data/lib/sequel/plugins/serialization.rb +98 -49
  204. data/lib/sequel/plugins/serialization_modification_detection.rb +63 -0
  205. data/lib/sequel/plugins/sharding.rb +21 -54
  206. data/lib/sequel/plugins/single_table_inheritance.rb +5 -3
  207. data/lib/sequel/plugins/static_cache.rb +99 -0
  208. data/lib/sequel/plugins/subclasses.rb +29 -3
  209. data/lib/sequel/plugins/tactical_eager_loading.rb +7 -7
  210. data/lib/sequel/plugins/timestamps.rb +1 -1
  211. data/lib/sequel/plugins/tree.rb +3 -3
  212. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  213. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  214. data/lib/sequel/plugins/validation_class_methods.rb +1 -1
  215. data/lib/sequel/plugins/validation_helpers.rb +55 -4
  216. data/lib/sequel/plugins/xml_serializer.rb +15 -4
  217. data/lib/sequel/sql.rb +649 -122
  218. data/lib/sequel/timezones.rb +67 -40
  219. data/lib/sequel/version.rb +1 -1
  220. data/spec/adapters/db2_spec.rb +146 -0
  221. data/spec/adapters/firebird_spec.rb +1 -1
  222. data/spec/adapters/mssql_spec.rb +194 -66
  223. data/spec/adapters/mysql_spec.rb +475 -306
  224. data/spec/adapters/oracle_spec.rb +92 -117
  225. data/spec/adapters/postgres_spec.rb +1982 -325
  226. data/spec/adapters/spec_helper.rb +9 -6
  227. data/spec/adapters/sqlite_spec.rb +203 -75
  228. data/spec/core/connection_pool_spec.rb +218 -93
  229. data/spec/core/database_spec.rb +956 -522
  230. data/spec/core/dataset_spec.rb +1631 -1242
  231. data/spec/core/expression_filters_spec.rb +690 -334
  232. data/spec/core/mock_adapter_spec.rb +453 -0
  233. data/spec/core/object_graph_spec.rb +88 -129
  234. data/spec/core/schema_generator_spec.rb +4 -4
  235. data/spec/core/schema_spec.rb +556 -53
  236. data/spec/core/spec_helper.rb +7 -48
  237. data/spec/core_extensions_spec.rb +626 -0
  238. data/spec/extensions/active_model_spec.rb +13 -0
  239. data/spec/extensions/arbitrary_servers_spec.rb +110 -0
  240. data/spec/extensions/association_autoreloading_spec.rb +18 -10
  241. data/spec/extensions/association_dependencies_spec.rb +15 -25
  242. data/spec/extensions/association_pks_spec.rb +66 -32
  243. data/spec/extensions/association_proxies_spec.rb +4 -4
  244. data/spec/extensions/boolean_readers_spec.rb +25 -25
  245. data/spec/extensions/caching_spec.rb +47 -51
  246. data/spec/extensions/class_table_inheritance_spec.rb +31 -83
  247. data/spec/extensions/columns_introspection_spec.rb +91 -0
  248. data/spec/extensions/composition_spec.rb +18 -13
  249. data/spec/extensions/constraint_validations_plugin_spec.rb +196 -0
  250. data/spec/extensions/constraint_validations_spec.rb +316 -0
  251. data/spec/extensions/dataset_associations_spec.rb +199 -0
  252. data/spec/extensions/defaults_setter_spec.rb +88 -0
  253. data/spec/extensions/dirty_spec.rb +155 -0
  254. data/spec/extensions/eager_each_spec.rb +34 -0
  255. data/spec/extensions/eval_inspect_spec.rb +67 -0
  256. data/spec/extensions/force_encoding_spec.rb +4 -2
  257. data/spec/extensions/hook_class_methods_spec.rb +97 -128
  258. data/spec/extensions/identity_map_spec.rb +142 -24
  259. data/spec/extensions/inflector_spec.rb +0 -4
  260. data/spec/extensions/instance_filters_spec.rb +11 -21
  261. data/spec/extensions/instance_hooks_spec.rb +72 -0
  262. data/spec/extensions/json_serializer_spec.rb +49 -12
  263. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  264. data/spec/extensions/list_spec.rb +49 -40
  265. data/spec/extensions/looser_typecasting_spec.rb +7 -7
  266. data/spec/extensions/many_through_many_spec.rb +368 -254
  267. data/spec/extensions/many_to_one_pk_lookup_spec.rb +140 -0
  268. data/spec/extensions/migration_spec.rb +140 -35
  269. data/spec/extensions/named_timezones_spec.rb +29 -11
  270. data/spec/extensions/nested_attributes_spec.rb +268 -89
  271. data/spec/extensions/null_dataset_spec.rb +85 -0
  272. data/spec/extensions/optimistic_locking_spec.rb +24 -21
  273. data/spec/extensions/pg_array_ops_spec.rb +112 -0
  274. data/spec/extensions/pg_array_spec.rb +320 -0
  275. data/spec/extensions/pg_auto_parameterize_spec.rb +65 -0
  276. data/spec/extensions/pg_hstore_ops_spec.rb +140 -0
  277. data/spec/extensions/pg_hstore_spec.rb +194 -0
  278. data/spec/extensions/pg_inet_spec.rb +47 -0
  279. data/spec/extensions/pg_interval_spec.rb +72 -0
  280. data/spec/extensions/pg_json_spec.rb +99 -0
  281. data/spec/extensions/pg_range_ops_spec.rb +56 -0
  282. data/spec/extensions/pg_range_spec.rb +395 -0
  283. data/spec/extensions/pg_row_ops_spec.rb +58 -0
  284. data/spec/extensions/pg_row_plugin_spec.rb +49 -0
  285. data/spec/extensions/pg_row_spec.rb +323 -0
  286. data/spec/extensions/pg_statement_cache_spec.rb +208 -0
  287. data/spec/extensions/pg_typecast_on_load_spec.rb +58 -0
  288. data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
  289. data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
  290. data/spec/extensions/prepared_statements_spec.rb +87 -0
  291. data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
  292. data/spec/extensions/pretty_table_spec.rb +7 -6
  293. data/spec/extensions/query_literals_spec.rb +167 -0
  294. data/spec/extensions/query_spec.rb +9 -3
  295. data/spec/extensions/rcte_tree_spec.rb +50 -43
  296. data/spec/extensions/schema_caching_spec.rb +41 -0
  297. data/spec/extensions/schema_dumper_spec.rb +434 -49
  298. data/spec/extensions/schema_spec.rb +30 -49
  299. data/spec/extensions/select_remove_spec.rb +38 -0
  300. data/spec/extensions/serialization_modification_detection_spec.rb +72 -0
  301. data/spec/extensions/serialization_spec.rb +86 -48
  302. data/spec/extensions/server_block_spec.rb +90 -0
  303. data/spec/extensions/sharding_spec.rb +69 -143
  304. data/spec/extensions/single_table_inheritance_spec.rb +23 -29
  305. data/spec/extensions/skip_create_refresh_spec.rb +1 -1
  306. data/spec/extensions/spec_helper.rb +34 -67
  307. data/spec/extensions/split_array_nil_spec.rb +24 -0
  308. data/spec/extensions/sql_expr_spec.rb +29 -60
  309. data/spec/extensions/static_cache_spec.rb +145 -0
  310. data/spec/extensions/subclasses_spec.rb +14 -0
  311. data/spec/extensions/tactical_eager_loading_spec.rb +17 -19
  312. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  313. data/spec/extensions/timestamps_spec.rb +6 -6
  314. data/spec/extensions/to_dot_spec.rb +8 -11
  315. data/spec/extensions/touch_spec.rb +13 -14
  316. data/spec/extensions/tree_spec.rb +11 -26
  317. data/spec/extensions/typecast_on_load_spec.rb +9 -6
  318. data/spec/extensions/update_primary_key_spec.rb +30 -24
  319. data/spec/extensions/validation_class_methods_spec.rb +58 -67
  320. data/spec/extensions/validation_helpers_spec.rb +31 -37
  321. data/spec/extensions/xml_serializer_spec.rb +29 -4
  322. data/spec/files/transaction_specified_migrations/001_create_alt_basic.rb +4 -0
  323. data/spec/files/transaction_specified_migrations/002_create_basic.rb +4 -0
  324. data/spec/files/transaction_unspecified_migrations/001_create_alt_basic.rb +3 -0
  325. data/spec/files/transaction_unspecified_migrations/002_create_basic.rb +3 -0
  326. data/spec/integration/associations_test.rb +659 -21
  327. data/spec/integration/database_test.rb +11 -5
  328. data/spec/integration/dataset_test.rb +717 -129
  329. data/spec/integration/eager_loader_test.rb +38 -54
  330. data/spec/integration/migrator_test.rb +2 -3
  331. data/spec/integration/model_test.rb +81 -2
  332. data/spec/integration/plugin_test.rb +604 -147
  333. data/spec/integration/prepared_statement_test.rb +272 -128
  334. data/spec/integration/schema_test.rb +367 -57
  335. data/spec/integration/spec_helper.rb +42 -32
  336. data/spec/integration/timezone_test.rb +38 -12
  337. data/spec/integration/transaction_test.rb +183 -8
  338. data/spec/integration/type_test.rb +31 -8
  339. data/spec/model/association_reflection_spec.rb +207 -8
  340. data/spec/model/associations_spec.rb +1206 -693
  341. data/spec/model/base_spec.rb +334 -100
  342. data/spec/model/dataset_methods_spec.rb +45 -27
  343. data/spec/model/eager_loading_spec.rb +828 -692
  344. data/spec/model/hooks_spec.rb +355 -67
  345. data/spec/model/model_spec.rb +251 -179
  346. data/spec/model/plugins_spec.rb +24 -13
  347. data/spec/model/record_spec.rb +506 -232
  348. data/spec/model/spec_helper.rb +16 -77
  349. data/spec/model/validations_spec.rb +38 -2
  350. data/spec/rcov.opts +2 -0
  351. metadata +209 -80
  352. data/spec/core/core_sql_spec.rb +0 -451
@@ -1,3 +1,5 @@
1
+ Sequel.require 'adapters/utils/emulate_offset_with_row_number'
2
+
1
3
  module Sequel
2
4
  Dataset::NON_SQL_OPTIONS << :disable_insert_output
3
5
  module MSSQL
@@ -10,16 +12,48 @@ 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/
13
16
 
17
+ # Whether to use N'' to quote strings, which allows unicode characters inside the
18
+ # strings. True by default for compatibility, can be set to false for a possible
19
+ # performance increase. This sets the default for all datasets created from this
20
+ # Database object.
21
+ attr_accessor :mssql_unicode_strings
22
+
14
23
  # The types to check for 0 scale to transform :decimal types
15
24
  # to :integer.
16
25
  DECIMAL_TYPE_RE = /number|numeric|decimal/io
17
-
26
+
18
27
  # Microsoft SQL Server uses the :mssql type.
19
28
  def database_type
20
29
  :mssql
21
30
  end
22
31
 
32
+ # Microsoft SQL Server namespaces indexes per table.
33
+ def global_index_namespace?
34
+ false
35
+ end
36
+
37
+ # Use the system tables to get index information
38
+ def indexes(table, opts={})
39
+ m = output_identifier_meth
40
+ im = input_identifier_meth
41
+ indexes = {}
42
+ metadata_dataset.from(:sys__tables___t).
43
+ join(:sys__indexes___i, :object_id=>:object_id).
44
+ join(:sys__index_columns___ic, :object_id=>:object_id, :index_id=>:index_id).
45
+ join(:sys__columns___c, :object_id=>:object_id, :column_id=>:column_id).
46
+ select(:i__name, :i__is_unique, :c__name___column).
47
+ where{{t__name=>im.call(table)}}.
48
+ where(:i__is_primary_key=>0, :i__is_disabled=>0).
49
+ order(:i__name, :ic__index_column_id).
50
+ each do |r|
51
+ index = indexes[m.call(r[:name])] ||= {:columns=>[], :unique=>(r[:is_unique] && r[:is_unique]!=0)}
52
+ index[:columns] << m.call(r[:column])
53
+ end
54
+ indexes
55
+ end
56
+
23
57
  # The version of the MSSQL server, as an integer (e.g. 10001600 for
24
58
  # SQL Server 2008 Express).
25
59
  def server_version(server=nil)
@@ -44,32 +78,96 @@ module Sequel
44
78
  true
45
79
  end
46
80
 
81
+ # MSSQL supports transaction DDL statements.
82
+ def supports_transactional_ddl?
83
+ true
84
+ end
85
+
47
86
  # Microsoft SQL Server supports using the INFORMATION_SCHEMA to get
48
87
  # information on tables.
49
88
  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])}
89
+ information_schema_tables('BASE TABLE', opts)
55
90
  end
56
91
 
92
+ # Microsoft SQL Server supports using the INFORMATION_SCHEMA to get
93
+ # information on views.
94
+ def views(opts={})
95
+ information_schema_tables('VIEW', opts)
96
+ end
97
+
57
98
  private
58
99
 
100
+ # Add dropping of the default constraint to the list of SQL queries.
101
+ # This is necessary before dropping the column or changing its type.
102
+ def add_drop_default_constraint_sql(sqls, table, column)
103
+ if constraint = default_constraint_name(table, column)
104
+ sqls << "ALTER TABLE #{quote_schema_table(table)} DROP CONSTRAINT #{constraint}"
105
+ end
106
+ end
107
+
59
108
  # MSSQL uses the IDENTITY(1,1) column for autoincrementing columns.
60
109
  def auto_increment_sql
61
110
  AUTO_INCREMENT
62
111
  end
63
112
 
113
+ # Preprocess the array of operations. If it looks like some operations depend
114
+ # on results of earlier operations and may require reloading the schema to
115
+ # work correctly, split those operations into separate lists, and between each
116
+ # list, remove the cached schema so that the later operations deal with the
117
+ # then current table schema.
118
+ def apply_alter_table(name, ops)
119
+ modified_columns = []
120
+ op_groups = [[]]
121
+ ops.each do |op|
122
+ case op[:op]
123
+ when :add_column, :set_column_type, :set_column_null
124
+ if modified_columns.include?(op[:name])
125
+ op_groups << []
126
+ else
127
+ modified_columns << op[:name]
128
+ end
129
+ when :rename_column
130
+ if modified_columns.include?(op[:name]) || modified_columns.include?(op[:new_name])
131
+ op_groups << []
132
+ end
133
+ modified_columns << op[:name] unless modified_columns.include?(op[:name])
134
+ modified_columns << op[:new_name] unless modified_columns.include?(op[:new_name])
135
+ end
136
+ op_groups.last << op
137
+ end
138
+
139
+ op_groups.each do |ops|
140
+ next if ops.empty?
141
+ alter_table_sql_list(name, ops).each{|sql| execute_ddl(sql)}
142
+ remove_cached_schema(name)
143
+ end
144
+ end
145
+
64
146
  # MSSQL specific syntax for altering tables.
65
147
  def alter_table_sql(table, op)
66
148
  case op[:op]
67
149
  when :add_column
68
150
  "ALTER TABLE #{quote_schema_table(table)} ADD #{column_definition_sql(op)}"
151
+ when :drop_column
152
+ sqls = []
153
+ add_drop_default_constraint_sql(sqls, table, op[:name])
154
+ sqls << super
69
155
  when :rename_column
70
156
  "sp_rename #{literal("#{quote_schema_table(table)}.#{quote_identifier(op[:name])}")}, #{literal(op[:new_name].to_s)}, 'COLUMN'"
71
157
  when :set_column_type
72
- "ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(op[:name])} #{type_literal(op)}"
158
+ sqls = []
159
+ if sch = schema(table)
160
+ if cs = sch.each{|k, v| break v if k == op[:name]; nil}
161
+ cs = cs.dup
162
+ add_drop_default_constraint_sql(sqls, table, op[:name])
163
+ cs[:default] = cs[:ruby_default]
164
+ op = cs.merge!(op)
165
+ default = op.delete(:default)
166
+ end
167
+ end
168
+ sqls << "ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{column_definition_sql(op)}"
169
+ sqls << alter_table_sql(table, op.merge(:op=>:set_column_default, :default=>default)) if default
170
+ sqls
73
171
  when :set_column_null
74
172
  sch = schema(table).find{|k,v| k.to_s == op[:name].to_s}.last
75
173
  type = sch[:db_type]
@@ -94,10 +192,18 @@ module Sequel
94
192
  SQL_BEGIN
95
193
  end
96
194
 
195
+ # Handle MSSQL specific default format.
196
+ def column_schema_normalize_default(default, type)
197
+ if m = MSSQL_DEFAULT_RE.match(default)
198
+ default = m[1] || m[2]
199
+ end
200
+ super(default, type)
201
+ end
202
+
97
203
  # Commit the active transaction on the connection, does not commit/release
98
204
  # savepoints.
99
205
  def commit_transaction(conn, opts={})
100
- log_connection_execute(conn, commit_transaction_sql) unless Thread.current[:sequel_transaction_depth] > 1
206
+ log_connection_execute(conn, commit_transaction_sql) unless _trans(conn)[:savepoint_level] > 1
101
207
  end
102
208
 
103
209
  # SQL to COMMIT a transaction.
@@ -108,15 +214,55 @@ module Sequel
108
214
  # MSSQL uses the name of the table to decide the difference between
109
215
  # a regular and temporary table, with temporary table names starting with
110
216
  # a #.
111
- def create_table_sql(name, generator, options)
112
- "CREATE TABLE #{quote_schema_table(options[:temp] ? "##{name}" : name)} (#{column_list_sql(generator)})"
217
+ def create_table_prefix_sql(name, options)
218
+ "CREATE TABLE #{quote_schema_table(options[:temp] ? "##{name}" : name)}"
113
219
  end
114
220
 
221
+ # MSSQL doesn't support CREATE TABLE AS, it only supports SELECT INTO.
222
+ # Emulating CREATE TABLE AS using SELECT INTO is only possible if a dataset
223
+ # is given as the argument, it can't work with a string, so raise an
224
+ # Error if a string is given.
225
+ def create_table_as(name, ds, options)
226
+ raise(Error, "must provide dataset instance as value of create_table :as option on MSSQL") unless ds.is_a?(Sequel::Dataset)
227
+ run(ds.into(name).sql)
228
+ end
229
+
230
+ # The name of the constraint for setting the default value on the table and column.
231
+ # The SQL used to select default constraints utilizes MSSQL catalog views which were introduced in 2005.
232
+ # This method intentionally does not support MSSQL 2000.
233
+ def default_constraint_name(table, column_name)
234
+ if server_version >= 9000000
235
+ table_name = schema_and_table(table).compact.join('.')
236
+ self[:sys__default_constraints].
237
+ where{{:parent_object_id => object_id(table_name), col_name(:parent_object_id, :parent_column_id) => column_name.to_s}}.
238
+ get(:name)
239
+ end
240
+ end
241
+
115
242
  # The SQL to drop an index for the table.
116
243
  def drop_index_sql(table, op)
117
244
  "DROP INDEX #{quote_identifier(op[:name] || default_index_name(table, op[:columns]))} ON #{quote_schema_table(table)}"
118
245
  end
119
246
 
247
+ # support for clustered index type
248
+ def index_definition_sql(table_name, index)
249
+ index_name = index[:name] || default_index_name(table_name, index[:columns])
250
+ if index[:type] == :full_text
251
+ "CREATE FULLTEXT INDEX ON #{quote_schema_table(table_name)} #{literal(index[:columns])} KEY INDEX #{literal(index[:key_index])}"
252
+ else
253
+ "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]}"
254
+ end
255
+ end
256
+
257
+ # Backbone of the tables and views support.
258
+ def information_schema_tables(type, opts)
259
+ m = output_identifier_meth
260
+ metadata_dataset.from(:information_schema__tables___t).
261
+ select(:table_name).
262
+ filter(:table_type=>type, :table_schema=>(opts[:schema]||default_schema||'dbo').to_s).
263
+ map{|x| m.call(x[:table_name])}
264
+ end
265
+
120
266
  # Always quote identifiers in the metadata_dataset, so schema parsing works.
121
267
  def metadata_dataset
122
268
  ds = super
@@ -139,20 +285,41 @@ module Sequel
139
285
  SQL_ROLLBACK
140
286
  end
141
287
 
142
- # MSSQL uses the INFORMATION_SCHEMA to hold column information. This method does
143
- # not support the parsing of primary key information.
288
+ # The closest MSSQL equivalent of a boolean datatype is the bit type.
289
+ def schema_column_type(db_type)
290
+ case db_type
291
+ when /\A(bit)\z/io
292
+ :boolean
293
+ else
294
+ super
295
+ end
296
+ end
297
+
298
+ # MSSQL uses the INFORMATION_SCHEMA to hold column information, and
299
+ # parses primary key information from the sysindexes, sysindexkeys,
300
+ # and syscolumns system tables.
144
301
  def schema_parse_table(table_name, opts)
145
- m = output_identifier_meth
146
- m2 = input_identifier_meth
302
+ m = output_identifier_meth(opts[:dataset])
303
+ m2 = input_identifier_meth(opts[:dataset])
304
+ tn = m2.call(table_name.to_s)
305
+ table_id = get{object_id(tn)}
306
+ pk_index_id = metadata_dataset.from(:sysindexes).
307
+ where(:id=>table_id, :indid=>1..254){{(status & 2048)=>2048}}.
308
+ get(:indid)
309
+ pk_cols = metadata_dataset.from(:sysindexkeys___sik).
310
+ join(:syscolumns___sc, :id=>:id, :colid=>:colid).
311
+ where(:sik__id=>table_id, :sik__indid=>pk_index_id).
312
+ select_order_map(:sc__name)
147
313
  ds = metadata_dataset.from(:information_schema__tables___t).
148
314
  join(:information_schema__columns___c, :table_catalog=>:table_catalog,
149
315
  :table_schema => :table_schema, :table_name => :table_name).
150
316
  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))
317
+ filter(:c__table_name=>tn)
152
318
  if schema = opts[:schema] || default_schema
153
319
  ds.filter!(:c__table_schema=>schema)
154
320
  end
155
321
  ds.map do |row|
322
+ row[:primary_key] = pk_cols.include?(row[:column])
156
323
  row[:allow_null] = row[:allow_null] == 'YES' ? true : false
157
324
  row[:default] = nil if blank_object?(row[:default])
158
325
  row[:type] = if row[:db_type] =~ DECIMAL_TYPE_RE && row[:scale] == 0
@@ -163,6 +330,11 @@ module Sequel
163
330
  [m.call(row.delete(:column)), row]
164
331
  end
165
332
  end
333
+
334
+ # Set the mssql_unicode_strings settings from the given options.
335
+ def set_mssql_unicode_strings
336
+ @mssql_unicode_strings = typecast_value_boolean(@opts.fetch(:mssql_unicode_strings, true))
337
+ end
166
338
 
167
339
  # MSSQL has both datetime and timestamp classes, most people are going
168
340
  # to want datetime
@@ -185,53 +357,100 @@ module Sequel
185
357
  def type_literal_generic_file(column)
186
358
  :'varbinary(max)'
187
359
  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
360
  end
200
361
 
201
362
  module DatasetMethods
363
+ include EmulateOffsetWithRowNumber
364
+
202
365
  BOOL_TRUE = '1'.freeze
203
366
  BOOL_FALSE = '0'.freeze
204
367
  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')
368
+ DELETE_CLAUSE_METHODS = Dataset.clause_methods(:delete, %w'with delete from output from2 where')
369
+ INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'with insert into columns output values')
370
+ SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with select distinct limit columns into from lock join where group having order compounds')
371
+ UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'with update limit table set output from where')
209
372
  NOLOCK = ' WITH (NOLOCK)'.freeze
210
373
  UPDLOCK = ' WITH (UPDLOCK)'.freeze
211
374
  WILDCARD = LiteralString.new('*').freeze
212
375
  CONSTANT_MAP = {:CURRENT_DATE=>'CAST(CURRENT_TIMESTAMP AS DATE)'.freeze, :CURRENT_TIME=>'CAST(CURRENT_TIMESTAMP AS TIME)'.freeze}
376
+ EXTRACT_MAP = {:year=>"yy", :month=>"m", :day=>"d", :hour=>"hh", :minute=>"n", :second=>"s"}
377
+ BRACKET_CLOSE = Dataset::BRACKET_CLOSE
378
+ BRACKET_OPEN = Dataset::BRACKET_OPEN
379
+ COMMA = Dataset::COMMA
380
+ PAREN_CLOSE = Dataset::PAREN_CLOSE
381
+ PAREN_SPACE_OPEN = Dataset::PAREN_SPACE_OPEN
382
+ SPACE = Dataset::SPACE
383
+ FROM = Dataset::FROM
384
+ APOS = Dataset::APOS
385
+ APOS_RE = Dataset::APOS_RE
386
+ DOUBLE_APOS = Dataset::DOUBLE_APOS
387
+ INTO = Dataset::INTO
388
+ DATEPART_SECOND_OPEN = "CAST((datepart(".freeze
389
+ DATEPART_SECOND_MIDDLE = ') + datepart(ns, '.freeze
390
+ DATEPART_SECOND_CLOSE = ")/1000000000.0) AS double precision)".freeze
391
+ DATEPART_OPEN = "datepart(".freeze
392
+ UNION_ALL = ' UNION ALL '.freeze
393
+ SELECT_SPACE = 'SELECT '.freeze
394
+ TIMESTAMP_USEC_FORMAT = ".%03d".freeze
395
+ OUTPUT_INSERTED = " OUTPUT INSERTED.*".freeze
396
+ HEX_START = '0x'.freeze
397
+ UNICODE_STRING_START = "N'".freeze
398
+ BACKSLASH_CRLF_RE = /\\((?:\r\n)|\n)/.freeze
399
+ BACKSLASH_CRLF_REPLACE = '\\\\\\\\\\1\\1'.freeze
400
+ TOP_PAREN = " TOP (".freeze
401
+ TOP = " TOP ".freeze
402
+ OUTPUT = " OUTPUT ".freeze
403
+ HSTAR = "H*".freeze
404
+ CASE_SENSITIVE_COLLATION = 'Latin1_General_CS_AS'.freeze
405
+ CASE_INSENSITIVE_COLLATION = 'Latin1_General_CI_AS'.freeze
406
+ DEFAULT_TIMESTAMP_FORMAT = "'%Y-%m-%dT%H:%M:%S%N%z'".freeze
407
+ FORMAT_DATE = "'%Y%m%d'".freeze
408
+
409
+ # Allow overriding of the mssql_unicode_strings option at the dataset level.
410
+ attr_accessor :mssql_unicode_strings
411
+
412
+ # Copy the mssql_unicode_strings option from the +db+ object.
413
+ def initialize(db, opts={})
414
+ super
415
+ @mssql_unicode_strings = db.mssql_unicode_strings
416
+ end
213
417
 
214
418
  # MSSQL uses + for string concatenation, and LIKE is case insensitive by default.
215
- def complex_expression_sql(op, args)
419
+ def complex_expression_sql_append(sql, op, args)
216
420
  case op
217
421
  when :'||'
218
- super(:+, args)
219
- when :ILIKE
220
- super(:LIKE, args)
221
- when :"NOT ILIKE"
222
- super(:"NOT LIKE", args)
422
+ super(sql, :+, args)
423
+ when :LIKE, :"NOT LIKE"
424
+ super(sql, op, args.map{|a| LiteralString.new("(#{literal(a)} COLLATE #{CASE_SENSITIVE_COLLATION})")})
425
+ when :ILIKE, :"NOT ILIKE"
426
+ super(sql, (op == :ILIKE ? :LIKE : :"NOT LIKE"), args.map{|a| LiteralString.new("(#{literal(a)} COLLATE #{CASE_INSENSITIVE_COLLATION})")})
223
427
  when :<<
224
- "(#{literal(args[0])} * POWER(2, #{literal(args[1])}))"
428
+ sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} * POWER(2, #{literal(b)}))"}
225
429
  when :>>
226
- "(#{literal(args[0])} / POWER(2, #{literal(args[1])}))"
430
+ sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} / POWER(2, #{literal(b)}))"}
431
+ when :extract
432
+ part = args.at(0)
433
+ raise(Sequel::Error, "unsupported extract argument: #{part.inspect}") unless format = EXTRACT_MAP[part]
434
+ if part == :second
435
+ expr = literal(args.at(1))
436
+ sql << DATEPART_SECOND_OPEN << format.to_s << COMMA << expr << DATEPART_SECOND_MIDDLE << expr << DATEPART_SECOND_CLOSE
437
+ else
438
+ sql << DATEPART_OPEN << format.to_s << COMMA
439
+ literal_append(sql, args.at(1))
440
+ sql << PAREN_CLOSE
441
+ end
227
442
  else
228
- super(op, args)
443
+ super
229
444
  end
230
445
  end
231
446
 
232
447
  # MSSQL doesn't support the SQL standard CURRENT_DATE or CURRENT_TIME
233
- def constant_sql(constant)
234
- CONSTANT_MAP[constant] || super
448
+ def constant_sql_append(sql, constant)
449
+ if c = CONSTANT_MAP[constant]
450
+ sql << c
451
+ else
452
+ super
453
+ end
235
454
  end
236
455
 
237
456
  # Disable the use of INSERT OUTPUT
@@ -244,21 +463,31 @@ module Sequel
244
463
  mutation_method(:disable_insert_output)
245
464
  end
246
465
 
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)
466
+ # There is no function on Microsoft SQL Server that does character length
467
+ # and respects trailing spaces (datalength respects trailing spaces, but
468
+ # counts bytes instead of characters). Use a hack to work around the
469
+ # trailing spaces issue.
470
+ def emulated_function_sql_append(sql, f)
471
+ case f.f
472
+ when :char_length
473
+ literal_append(sql, SQL::Function.new(:len, Sequel.join([f.args.first, 'x'])) - 1)
474
+ when :trim
475
+ literal_append(sql, SQL::Function.new(:ltrim, SQL::Function.new(:rtrim, f.args.first)))
476
+ else
477
+ super
478
+ end
251
479
  end
252
480
 
253
481
  # MSSQL uses the CONTAINS keyword for full text search
254
482
  def full_text_search(cols, terms, opts = {})
255
- filter("CONTAINS (#{literal(cols)}, #{literal(terms)})")
483
+ terms = "\"#{terms.join('" OR "')}\"" if terms.is_a?(Array)
484
+ filter("CONTAINS (?, ?)", cols, terms)
256
485
  end
257
486
 
258
487
  # Use the OUTPUT clause to get the value of all columns for the newly inserted record.
259
488
  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]
489
+ return unless supports_insert_select?
490
+ naked.clone(default_server_opts(:sql=>output(nil, [SQL::ColumnAll.new(:inserted)]).insert_sql(*values))).single_record
262
491
  end
263
492
 
264
493
  # Specify a table for a SELECT ... INTO query.
@@ -266,17 +495,18 @@ module Sequel
266
495
  clone(:into => table)
267
496
  end
268
497
 
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
498
  # MSSQL uses a UNION ALL statement to insert multiple values at once.
278
499
  def multi_insert_sql(columns, values)
279
- [insert_sql(columns, LiteralString.new(values.map {|r| "SELECT #{expression_list(r)}" }.join(" UNION ALL ")))]
500
+ c = false
501
+ sql = LiteralString.new('')
502
+ u = UNION_ALL
503
+ values.each do |v|
504
+ sql << u if c
505
+ sql << SELECT_SPACE
506
+ expression_list_append(sql, v)
507
+ c ||= true
508
+ end
509
+ [insert_sql(columns, sql)]
280
510
  end
281
511
 
282
512
  # Allows you to do a dirty read of uncommitted data using WITH (NOLOCK).
@@ -314,39 +544,32 @@ module Sequel
314
544
  mutation_method(:output, into, values)
315
545
  end
316
546
 
317
- # MSSQL uses [] to quote identifiers
318
- def quoted_identifier(name)
319
- "[#{name}]"
547
+ # MSSQL uses [] to quote identifiers. MSSQL does not support
548
+ # escaping of ], so you cannot use that character in an identifier.
549
+ def quoted_identifier_append(sql, name)
550
+ sql << BRACKET_OPEN << name.to_s << BRACKET_CLOSE
320
551
  end
321
552
 
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
553
  # The version of the database server.
346
554
  def server_version
347
555
  db.server_version(@opts[:server])
348
556
  end
349
557
 
558
+ # MSSQL 2005+ supports GROUP BY CUBE.
559
+ def supports_group_cube?
560
+ is_2005_or_later?
561
+ end
562
+
563
+ # MSSQL 2005+ supports GROUP BY ROLLUP
564
+ def supports_group_rollup?
565
+ is_2005_or_later?
566
+ end
567
+
568
+ # MSSQL supports insert_select via the OUTPUT clause.
569
+ def supports_insert_select?
570
+ supports_output_clause? && !opts[:disable_insert_output]
571
+ end
572
+
350
573
  # MSSQL 2005+ supports INTERSECT and EXCEPT
351
574
  def supports_intersect_except?
352
575
  is_2005_or_later?
@@ -381,18 +604,55 @@ module Sequel
381
604
  def supports_window_functions?
382
605
  true
383
606
  end
607
+
608
+ # MSSQL cannot use WHERE 1.
609
+ def supports_where_true?
610
+ false
611
+ end
384
612
 
385
613
  protected
614
+
615
+ # If returned primary keys are requested, use OUTPUT unless already set on the
616
+ # dataset. If OUTPUT is already set, use existing returning values. If OUTPUT
617
+ # is only set to return a single columns, return an array of just that column.
618
+ # Otherwise, return an array of hashes.
619
+ def _import(columns, values, opts={})
620
+ if opts[:return] == :primary_key && !@opts[:output]
621
+ output(nil, [SQL::QualifiedIdentifier.new(:inserted, first_primary_key)])._import(columns, values, opts)
622
+ elsif @opts[:output]
623
+ statements = multi_insert_sql(columns, values)
624
+ @db.transaction(opts.merge(:server=>@opts[:server])) do
625
+ statements.map{|st| with_sql(st)}
626
+ end.first.map{|v| v.length == 1 ? v.values.first : v}
627
+ else
628
+ super
629
+ end
630
+ end
631
+
386
632
  # MSSQL does not allow ordering in sub-clauses unless 'top' (limit) is specified
387
633
  def aggregate_dataset
388
634
  (options_overlap(Sequel::Dataset::COUNT_FROM_SELF_OPTS) && !options_overlap([:limit])) ? unordered.from_self : super
389
635
  end
390
636
 
391
637
  private
638
+
639
+ # Whether we are using SQL Server 2005 or later.
392
640
  def is_2005_or_later?
393
641
  server_version >= 9000000
394
642
  end
395
643
 
644
+ # Whether we are using SQL Server 2008 or later.
645
+ def is_2008_or_later?
646
+ server_version >= 10000000
647
+ end
648
+
649
+ # Use strict ISO-8601 format with T between date and time,
650
+ # since that is the format that is multilanguage and not
651
+ # DATEFORMAT dependent.
652
+ def default_timestamp_format
653
+ DEFAULT_TIMESTAMP_FORMAT
654
+ end
655
+
396
656
  # MSSQL supports the OUTPUT clause for DELETE statements.
397
657
  # It also allows prepending a WITH clause.
398
658
  def delete_clause_methods
@@ -401,7 +661,8 @@ module Sequel
401
661
 
402
662
  # Only include the primary table in the main delete clause
403
663
  def delete_from_sql(sql)
404
- sql << " FROM #{source_list(@opts[:from][0..0])}"
664
+ sql << FROM
665
+ source_list_append(sql, @opts[:from][0..0])
405
666
  end
406
667
 
407
668
  # MSSQL supports FROM clauses in DELETE and UPDATE statements.
@@ -413,19 +674,17 @@ module Sequel
413
674
  end
414
675
  alias update_from_sql delete_from2_sql
415
676
 
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)
677
+ # Return the first primary key for the current table. If this table has
678
+ # multiple primary keys, this will only return one of them. Used by #_import.
679
+ def first_primary_key
680
+ @db.schema(self).map{|k, v| k if v[:primary_key] == true}.compact.first
420
681
  end
421
- alias insert_with_sql delete_with_sql
422
- alias update_with_sql delete_with_sql
423
-
682
+
424
683
  # MSSQL raises an error if you try to provide more than 3 decimal places
425
684
  # for a fractional timestamp. This probably doesn't work for smalldatetime
426
685
  # fields.
427
686
  def format_timestamp_usec(usec)
428
- sprintf(".%03d", usec/1000)
687
+ sprintf(TIMESTAMP_USEC_FORMAT, usec/1000)
429
688
  end
430
689
 
431
690
  # MSSQL supports the OUTPUT clause for INSERT statements.
@@ -434,51 +693,71 @@ module Sequel
434
693
  INSERT_CLAUSE_METHODS
435
694
  end
436
695
 
696
+ # Use OUTPUT INSERTED.* to return all columns of the inserted row,
697
+ # for use with the prepared statement code.
698
+ def insert_output_sql(sql)
699
+ if @opts.has_key?(:returning)
700
+ sql << OUTPUT_INSERTED
701
+ else
702
+ output_sql(sql)
703
+ end
704
+ end
705
+
437
706
  # 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
707
+ def literal_blob_append(sql, v)
708
+ sql << HEX_START << v.unpack(HSTAR).first
442
709
  end
443
710
 
444
- # Use unicode string syntax for all strings. Don't double backslashes.
445
- def literal_string(v)
446
- "N'#{v.gsub(/'/, "''")}'"
711
+ # Use YYYYmmdd format, since that's the only want that is
712
+ # multilanguage and not DATEFORMAT dependent.
713
+ def literal_date(v)
714
+ v.strftime(FORMAT_DATE)
447
715
  end
448
-
716
+
449
717
  # Use 0 for false on MSSQL
450
718
  def literal_false
451
719
  BOOL_FALSE
452
720
  end
453
721
 
722
+ # Optionally use unicode string syntax for all strings. Don't double
723
+ # backslashes.
724
+ def literal_string_append(sql, v)
725
+ sql << (mssql_unicode_strings ? UNICODE_STRING_START : APOS)
726
+ sql << v.gsub(APOS_RE, DOUBLE_APOS).gsub(BACKSLASH_CRLF_RE, BACKSLASH_CRLF_REPLACE) << APOS
727
+ end
728
+
454
729
  # Use 1 for true on MSSQL
455
730
  def literal_true
456
731
  BOOL_TRUE
457
732
  end
458
733
 
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
734
  # MSSQL adds the limit before the columns
465
735
  def select_clause_methods
466
736
  SELECT_CLAUSE_METHODS
467
737
  end
468
738
 
469
739
  def select_into_sql(sql)
470
- sql << " INTO #{table_ref(@opts[:into])}" if @opts[:into]
740
+ if i = @opts[:into]
741
+ sql << INTO
742
+ identifier_append(sql, i)
743
+ end
471
744
  end
472
745
 
473
746
  # MSSQL uses TOP N for limit. For MSSQL 2005+ TOP (N) is used
474
747
  # to allow the limit to be a bound variable.
475
748
  def select_limit_sql(sql)
476
749
  if l = @opts[:limit]
477
- l = literal(l)
478
- l = "(#{l})" if server_version >= 9000000
479
- sql << " TOP #{l}"
750
+ if is_2005_or_later?
751
+ sql << TOP_PAREN
752
+ literal_append(sql, l)
753
+ sql << PAREN_CLOSE
754
+ else
755
+ sql << TOP
756
+ literal_append(sql, l)
757
+ end
480
758
  end
481
759
  end
760
+ alias update_limit_sql select_limit_sql
482
761
 
483
762
  # Support different types of locking styles
484
763
  def select_lock_sql(sql)
@@ -496,19 +775,20 @@ module Sequel
496
775
  def output_sql(sql)
497
776
  return unless supports_output_clause?
498
777
  return unless output = @opts[:output]
499
- sql << " OUTPUT #{column_list(output[:select_list])}"
778
+ sql << OUTPUT
779
+ column_list_append(sql, output[:select_list])
500
780
  if into = output[:into]
501
- sql << " INTO #{table_ref(into)}"
781
+ sql << INTO
782
+ identifier_append(sql, into)
502
783
  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)})"
784
+ sql << PAREN_SPACE_OPEN
785
+ source_list_append(sql, column_list)
786
+ sql << PAREN_CLOSE
506
787
  end
507
788
  end
508
789
  end
509
790
  alias delete_output_sql output_sql
510
791
  alias update_output_sql output_sql
511
- alias insert_output_sql output_sql
512
792
 
513
793
  # MSSQL supports the OUTPUT clause for UPDATE statements.
514
794
  # It also allows prepending a WITH clause.
@@ -518,7 +798,12 @@ module Sequel
518
798
 
519
799
  # Only include the primary table in the main update clause
520
800
  def update_table_sql(sql)
521
- sql << " #{source_list(@opts[:from][0..0])}"
801
+ sql << SPACE
802
+ source_list_append(sql, @opts[:from][0..0])
803
+ end
804
+
805
+ def uses_with_rollup?
806
+ !is_2008_or_later?
522
807
  end
523
808
  end
524
809
  end