sequel 3.21.0 → 3.36.0

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