viking-sequel 3.10.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 (237) hide show
  1. data/CHANGELOG +3134 -0
  2. data/COPYING +19 -0
  3. data/README.rdoc +723 -0
  4. data/Rakefile +193 -0
  5. data/bin/sequel +196 -0
  6. data/doc/advanced_associations.rdoc +644 -0
  7. data/doc/cheat_sheet.rdoc +218 -0
  8. data/doc/dataset_basics.rdoc +106 -0
  9. data/doc/dataset_filtering.rdoc +158 -0
  10. data/doc/opening_databases.rdoc +296 -0
  11. data/doc/prepared_statements.rdoc +104 -0
  12. data/doc/reflection.rdoc +84 -0
  13. data/doc/release_notes/1.0.txt +38 -0
  14. data/doc/release_notes/1.1.txt +143 -0
  15. data/doc/release_notes/1.3.txt +101 -0
  16. data/doc/release_notes/1.4.0.txt +53 -0
  17. data/doc/release_notes/1.5.0.txt +155 -0
  18. data/doc/release_notes/2.0.0.txt +298 -0
  19. data/doc/release_notes/2.1.0.txt +271 -0
  20. data/doc/release_notes/2.10.0.txt +328 -0
  21. data/doc/release_notes/2.11.0.txt +215 -0
  22. data/doc/release_notes/2.12.0.txt +534 -0
  23. data/doc/release_notes/2.2.0.txt +253 -0
  24. data/doc/release_notes/2.3.0.txt +88 -0
  25. data/doc/release_notes/2.4.0.txt +106 -0
  26. data/doc/release_notes/2.5.0.txt +137 -0
  27. data/doc/release_notes/2.6.0.txt +157 -0
  28. data/doc/release_notes/2.7.0.txt +166 -0
  29. data/doc/release_notes/2.8.0.txt +171 -0
  30. data/doc/release_notes/2.9.0.txt +97 -0
  31. data/doc/release_notes/3.0.0.txt +221 -0
  32. data/doc/release_notes/3.1.0.txt +406 -0
  33. data/doc/release_notes/3.10.0.txt +286 -0
  34. data/doc/release_notes/3.2.0.txt +268 -0
  35. data/doc/release_notes/3.3.0.txt +192 -0
  36. data/doc/release_notes/3.4.0.txt +325 -0
  37. data/doc/release_notes/3.5.0.txt +510 -0
  38. data/doc/release_notes/3.6.0.txt +366 -0
  39. data/doc/release_notes/3.7.0.txt +179 -0
  40. data/doc/release_notes/3.8.0.txt +151 -0
  41. data/doc/release_notes/3.9.0.txt +233 -0
  42. data/doc/schema.rdoc +36 -0
  43. data/doc/sharding.rdoc +113 -0
  44. data/doc/virtual_rows.rdoc +205 -0
  45. data/lib/sequel.rb +1 -0
  46. data/lib/sequel/adapters/ado.rb +90 -0
  47. data/lib/sequel/adapters/ado/mssql.rb +30 -0
  48. data/lib/sequel/adapters/amalgalite.rb +176 -0
  49. data/lib/sequel/adapters/db2.rb +139 -0
  50. data/lib/sequel/adapters/dbi.rb +113 -0
  51. data/lib/sequel/adapters/do.rb +188 -0
  52. data/lib/sequel/adapters/do/mysql.rb +49 -0
  53. data/lib/sequel/adapters/do/postgres.rb +91 -0
  54. data/lib/sequel/adapters/do/sqlite.rb +40 -0
  55. data/lib/sequel/adapters/firebird.rb +283 -0
  56. data/lib/sequel/adapters/informix.rb +77 -0
  57. data/lib/sequel/adapters/jdbc.rb +587 -0
  58. data/lib/sequel/adapters/jdbc/as400.rb +58 -0
  59. data/lib/sequel/adapters/jdbc/h2.rb +133 -0
  60. data/lib/sequel/adapters/jdbc/mssql.rb +57 -0
  61. data/lib/sequel/adapters/jdbc/mysql.rb +78 -0
  62. data/lib/sequel/adapters/jdbc/oracle.rb +50 -0
  63. data/lib/sequel/adapters/jdbc/postgresql.rb +108 -0
  64. data/lib/sequel/adapters/jdbc/sqlite.rb +55 -0
  65. data/lib/sequel/adapters/mysql.rb +421 -0
  66. data/lib/sequel/adapters/odbc.rb +143 -0
  67. data/lib/sequel/adapters/odbc/mssql.rb +42 -0
  68. data/lib/sequel/adapters/openbase.rb +64 -0
  69. data/lib/sequel/adapters/oracle.rb +131 -0
  70. data/lib/sequel/adapters/postgres.rb +504 -0
  71. data/lib/sequel/adapters/shared/mssql.rb +490 -0
  72. data/lib/sequel/adapters/shared/mysql.rb +498 -0
  73. data/lib/sequel/adapters/shared/oracle.rb +195 -0
  74. data/lib/sequel/adapters/shared/postgres.rb +830 -0
  75. data/lib/sequel/adapters/shared/progress.rb +44 -0
  76. data/lib/sequel/adapters/shared/sqlite.rb +389 -0
  77. data/lib/sequel/adapters/sqlite.rb +224 -0
  78. data/lib/sequel/adapters/utils/stored_procedures.rb +84 -0
  79. data/lib/sequel/connection_pool.rb +99 -0
  80. data/lib/sequel/connection_pool/sharded_single.rb +84 -0
  81. data/lib/sequel/connection_pool/sharded_threaded.rb +211 -0
  82. data/lib/sequel/connection_pool/single.rb +29 -0
  83. data/lib/sequel/connection_pool/threaded.rb +150 -0
  84. data/lib/sequel/core.rb +293 -0
  85. data/lib/sequel/core_sql.rb +241 -0
  86. data/lib/sequel/database.rb +1079 -0
  87. data/lib/sequel/database/schema_generator.rb +327 -0
  88. data/lib/sequel/database/schema_methods.rb +203 -0
  89. data/lib/sequel/database/schema_sql.rb +320 -0
  90. data/lib/sequel/dataset.rb +32 -0
  91. data/lib/sequel/dataset/actions.rb +441 -0
  92. data/lib/sequel/dataset/features.rb +86 -0
  93. data/lib/sequel/dataset/graph.rb +254 -0
  94. data/lib/sequel/dataset/misc.rb +119 -0
  95. data/lib/sequel/dataset/mutation.rb +64 -0
  96. data/lib/sequel/dataset/prepared_statements.rb +227 -0
  97. data/lib/sequel/dataset/query.rb +709 -0
  98. data/lib/sequel/dataset/sql.rb +996 -0
  99. data/lib/sequel/exceptions.rb +51 -0
  100. data/lib/sequel/extensions/blank.rb +43 -0
  101. data/lib/sequel/extensions/inflector.rb +242 -0
  102. data/lib/sequel/extensions/looser_typecasting.rb +21 -0
  103. data/lib/sequel/extensions/migration.rb +239 -0
  104. data/lib/sequel/extensions/named_timezones.rb +61 -0
  105. data/lib/sequel/extensions/pagination.rb +100 -0
  106. data/lib/sequel/extensions/pretty_table.rb +82 -0
  107. data/lib/sequel/extensions/query.rb +52 -0
  108. data/lib/sequel/extensions/schema_dumper.rb +271 -0
  109. data/lib/sequel/extensions/sql_expr.rb +122 -0
  110. data/lib/sequel/extensions/string_date_time.rb +46 -0
  111. data/lib/sequel/extensions/thread_local_timezones.rb +48 -0
  112. data/lib/sequel/metaprogramming.rb +9 -0
  113. data/lib/sequel/model.rb +120 -0
  114. data/lib/sequel/model/associations.rb +1514 -0
  115. data/lib/sequel/model/base.rb +1069 -0
  116. data/lib/sequel/model/default_inflections.rb +45 -0
  117. data/lib/sequel/model/errors.rb +39 -0
  118. data/lib/sequel/model/exceptions.rb +21 -0
  119. data/lib/sequel/model/inflections.rb +162 -0
  120. data/lib/sequel/model/plugins.rb +70 -0
  121. data/lib/sequel/plugins/active_model.rb +59 -0
  122. data/lib/sequel/plugins/association_dependencies.rb +103 -0
  123. data/lib/sequel/plugins/association_proxies.rb +41 -0
  124. data/lib/sequel/plugins/boolean_readers.rb +53 -0
  125. data/lib/sequel/plugins/caching.rb +141 -0
  126. data/lib/sequel/plugins/class_table_inheritance.rb +214 -0
  127. data/lib/sequel/plugins/composition.rb +138 -0
  128. data/lib/sequel/plugins/force_encoding.rb +72 -0
  129. data/lib/sequel/plugins/hook_class_methods.rb +126 -0
  130. data/lib/sequel/plugins/identity_map.rb +116 -0
  131. data/lib/sequel/plugins/instance_filters.rb +98 -0
  132. data/lib/sequel/plugins/instance_hooks.rb +57 -0
  133. data/lib/sequel/plugins/lazy_attributes.rb +77 -0
  134. data/lib/sequel/plugins/many_through_many.rb +208 -0
  135. data/lib/sequel/plugins/nested_attributes.rb +206 -0
  136. data/lib/sequel/plugins/optimistic_locking.rb +81 -0
  137. data/lib/sequel/plugins/rcte_tree.rb +281 -0
  138. data/lib/sequel/plugins/schema.rb +66 -0
  139. data/lib/sequel/plugins/serialization.rb +166 -0
  140. data/lib/sequel/plugins/single_table_inheritance.rb +74 -0
  141. data/lib/sequel/plugins/subclasses.rb +45 -0
  142. data/lib/sequel/plugins/tactical_eager_loading.rb +61 -0
  143. data/lib/sequel/plugins/timestamps.rb +87 -0
  144. data/lib/sequel/plugins/touch.rb +118 -0
  145. data/lib/sequel/plugins/typecast_on_load.rb +72 -0
  146. data/lib/sequel/plugins/validation_class_methods.rb +405 -0
  147. data/lib/sequel/plugins/validation_helpers.rb +223 -0
  148. data/lib/sequel/sql.rb +1020 -0
  149. data/lib/sequel/timezones.rb +161 -0
  150. data/lib/sequel/version.rb +12 -0
  151. data/lib/sequel_core.rb +1 -0
  152. data/lib/sequel_model.rb +1 -0
  153. data/spec/adapters/firebird_spec.rb +407 -0
  154. data/spec/adapters/informix_spec.rb +97 -0
  155. data/spec/adapters/mssql_spec.rb +403 -0
  156. data/spec/adapters/mysql_spec.rb +1019 -0
  157. data/spec/adapters/oracle_spec.rb +286 -0
  158. data/spec/adapters/postgres_spec.rb +969 -0
  159. data/spec/adapters/spec_helper.rb +51 -0
  160. data/spec/adapters/sqlite_spec.rb +432 -0
  161. data/spec/core/connection_pool_spec.rb +808 -0
  162. data/spec/core/core_sql_spec.rb +417 -0
  163. data/spec/core/database_spec.rb +1662 -0
  164. data/spec/core/dataset_spec.rb +3827 -0
  165. data/spec/core/expression_filters_spec.rb +595 -0
  166. data/spec/core/object_graph_spec.rb +296 -0
  167. data/spec/core/schema_generator_spec.rb +159 -0
  168. data/spec/core/schema_spec.rb +830 -0
  169. data/spec/core/spec_helper.rb +56 -0
  170. data/spec/core/version_spec.rb +7 -0
  171. data/spec/extensions/active_model_spec.rb +76 -0
  172. data/spec/extensions/association_dependencies_spec.rb +127 -0
  173. data/spec/extensions/association_proxies_spec.rb +50 -0
  174. data/spec/extensions/blank_spec.rb +67 -0
  175. data/spec/extensions/boolean_readers_spec.rb +92 -0
  176. data/spec/extensions/caching_spec.rb +250 -0
  177. data/spec/extensions/class_table_inheritance_spec.rb +252 -0
  178. data/spec/extensions/composition_spec.rb +194 -0
  179. data/spec/extensions/force_encoding_spec.rb +117 -0
  180. data/spec/extensions/hook_class_methods_spec.rb +470 -0
  181. data/spec/extensions/identity_map_spec.rb +202 -0
  182. data/spec/extensions/inflector_spec.rb +181 -0
  183. data/spec/extensions/instance_filters_spec.rb +55 -0
  184. data/spec/extensions/instance_hooks_spec.rb +133 -0
  185. data/spec/extensions/lazy_attributes_spec.rb +153 -0
  186. data/spec/extensions/looser_typecasting_spec.rb +39 -0
  187. data/spec/extensions/many_through_many_spec.rb +884 -0
  188. data/spec/extensions/migration_spec.rb +332 -0
  189. data/spec/extensions/named_timezones_spec.rb +72 -0
  190. data/spec/extensions/nested_attributes_spec.rb +396 -0
  191. data/spec/extensions/optimistic_locking_spec.rb +100 -0
  192. data/spec/extensions/pagination_spec.rb +99 -0
  193. data/spec/extensions/pretty_table_spec.rb +91 -0
  194. data/spec/extensions/query_spec.rb +85 -0
  195. data/spec/extensions/rcte_tree_spec.rb +205 -0
  196. data/spec/extensions/schema_dumper_spec.rb +357 -0
  197. data/spec/extensions/schema_spec.rb +127 -0
  198. data/spec/extensions/serialization_spec.rb +209 -0
  199. data/spec/extensions/single_table_inheritance_spec.rb +96 -0
  200. data/spec/extensions/spec_helper.rb +91 -0
  201. data/spec/extensions/sql_expr_spec.rb +89 -0
  202. data/spec/extensions/string_date_time_spec.rb +93 -0
  203. data/spec/extensions/subclasses_spec.rb +52 -0
  204. data/spec/extensions/tactical_eager_loading_spec.rb +65 -0
  205. data/spec/extensions/thread_local_timezones_spec.rb +45 -0
  206. data/spec/extensions/timestamps_spec.rb +150 -0
  207. data/spec/extensions/touch_spec.rb +155 -0
  208. data/spec/extensions/typecast_on_load_spec.rb +69 -0
  209. data/spec/extensions/validation_class_methods_spec.rb +984 -0
  210. data/spec/extensions/validation_helpers_spec.rb +438 -0
  211. data/spec/integration/associations_test.rb +281 -0
  212. data/spec/integration/database_test.rb +26 -0
  213. data/spec/integration/dataset_test.rb +963 -0
  214. data/spec/integration/eager_loader_test.rb +734 -0
  215. data/spec/integration/model_test.rb +130 -0
  216. data/spec/integration/plugin_test.rb +814 -0
  217. data/spec/integration/prepared_statement_test.rb +213 -0
  218. data/spec/integration/schema_test.rb +361 -0
  219. data/spec/integration/spec_helper.rb +73 -0
  220. data/spec/integration/timezone_test.rb +55 -0
  221. data/spec/integration/transaction_test.rb +122 -0
  222. data/spec/integration/type_test.rb +96 -0
  223. data/spec/model/association_reflection_spec.rb +175 -0
  224. data/spec/model/associations_spec.rb +2633 -0
  225. data/spec/model/base_spec.rb +418 -0
  226. data/spec/model/dataset_methods_spec.rb +78 -0
  227. data/spec/model/eager_loading_spec.rb +1391 -0
  228. data/spec/model/hooks_spec.rb +240 -0
  229. data/spec/model/inflector_spec.rb +26 -0
  230. data/spec/model/model_spec.rb +593 -0
  231. data/spec/model/plugins_spec.rb +236 -0
  232. data/spec/model/record_spec.rb +1500 -0
  233. data/spec/model/spec_helper.rb +97 -0
  234. data/spec/model/validations_spec.rb +153 -0
  235. data/spec/rcov.opts +6 -0
  236. data/spec/spec_config.rb.example +10 -0
  237. metadata +346 -0
@@ -0,0 +1,490 @@
1
+ module Sequel
2
+ Dataset::NON_SQL_OPTIONS << :disable_insert_output
3
+ module MSSQL
4
+ module DatabaseMethods
5
+ AUTO_INCREMENT = 'IDENTITY(1,1)'.freeze
6
+ SERVER_VERSION_RE = /^(\d+)\.(\d+)\.(\d+)/.freeze
7
+ SERVER_VERSION_SQL = "SELECT CAST(SERVERPROPERTY('ProductVersion') AS varchar)".freeze
8
+ SQL_BEGIN = "BEGIN TRANSACTION".freeze
9
+ SQL_COMMIT = "COMMIT TRANSACTION".freeze
10
+ SQL_ROLLBACK = "IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION".freeze
11
+ SQL_ROLLBACK_TO_SAVEPOINT = 'IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION autopoint_%d'.freeze
12
+ SQL_SAVEPOINT = 'SAVE TRANSACTION autopoint_%d'.freeze
13
+ TEMPORARY = "#".freeze
14
+
15
+ # Microsoft SQL Server uses the :mssql type.
16
+ def database_type
17
+ :mssql
18
+ end
19
+
20
+ # The version of the MSSQL server, as an integer (e.g. 10001600 for
21
+ # SQL Server 2008 Express).
22
+ def server_version(server=nil)
23
+ return @server_version if @server_version
24
+ @server_version = synchronize(server) do |conn|
25
+ (conn.server_version rescue nil) if conn.respond_to?(:server_version)
26
+ end
27
+ unless @server_version
28
+ m = SERVER_VERSION_RE.match(fetch(SERVER_VERSION_SQL).single_value.to_s)
29
+ @server_version = (m[1].to_i * 1000000) + (m[2].to_i * 10000) + m[3].to_i
30
+ end
31
+ @server_version
32
+ end
33
+
34
+ # MSSQL supports savepoints, though it doesn't support committing/releasing them savepoint
35
+ def supports_savepoints?
36
+ true
37
+ end
38
+
39
+ # Microsoft SQL Server supports using the INFORMATION_SCHEMA to get
40
+ # information on tables.
41
+ def tables(opts={})
42
+ m = output_identifier_meth
43
+ metadata_dataset.from(:information_schema__tables___t).
44
+ select(:table_name).
45
+ filter(:table_type=>'BASE TABLE', :table_schema=>(opts[:schema]||default_schema||'dbo').to_s).
46
+ map{|x| m.call(x[:table_name])}
47
+ end
48
+
49
+ private
50
+
51
+ # MSSQL uses the IDENTITY(1,1) column for autoincrementing columns.
52
+ def auto_increment_sql
53
+ AUTO_INCREMENT
54
+ end
55
+
56
+ # MSSQL specific syntax for altering tables.
57
+ def alter_table_sql(table, op)
58
+ case op[:op]
59
+ when :add_column
60
+ "ALTER TABLE #{quote_schema_table(table)} ADD #{column_definition_sql(op)}"
61
+ when :rename_column
62
+ "SP_RENAME #{literal("#{quote_schema_table(table)}.#{quote_identifier(op[:name])}")}, #{literal(op[:new_name].to_s)}, 'COLUMN'"
63
+ when :set_column_type
64
+ "ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(op[:name])} #{type_literal(op)}"
65
+ when :set_column_null
66
+ sch = schema(table).find{|k,v| k.to_s == op[:name].to_s}.last
67
+ type = sch[:db_type]
68
+ if [:string, :decimal].include?(sch[:type]) and size = (sch[:max_chars] || sch[:column_size])
69
+ type += "(#{size}#{", #{sch[:scale]}" if sch[:scale] && sch[:scale].to_i > 0})"
70
+ end
71
+ "ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(op[:name])} #{type_literal(:type=>type)} #{'NOT ' unless op[:null]}NULL"
72
+ when :set_column_default
73
+ "ALTER TABLE #{quote_schema_table(table)} ADD CONSTRAINT #{quote_identifier("sequel_#{table}_#{op[:name]}_def")} DEFAULT #{literal(op[:default])} FOR #{quote_identifier(op[:name])}"
74
+ else
75
+ super(table, op)
76
+ end
77
+ end
78
+
79
+ # SQL to start a new savepoint
80
+ def begin_savepoint_sql(depth)
81
+ SQL_SAVEPOINT % depth
82
+ end
83
+
84
+ # SQL to BEGIN a transaction.
85
+ def begin_transaction_sql
86
+ SQL_BEGIN
87
+ end
88
+
89
+ # Commit the active transaction on the connection, does not commit/release
90
+ # savepoints.
91
+ def commit_transaction(conn)
92
+ log_connection_execute(conn, commit_transaction_sql) unless Thread.current[:sequel_transaction_depth] > 1
93
+ end
94
+
95
+ # SQL to COMMIT a transaction.
96
+ def commit_transaction_sql
97
+ SQL_COMMIT
98
+ end
99
+
100
+ # The SQL to drop an index for the table.
101
+ def drop_index_sql(table, op)
102
+ "DROP INDEX #{quote_identifier(op[:name] || default_index_name(table, op[:columns]))} ON #{quote_schema_table(table)}"
103
+ end
104
+
105
+ # Always quote identifiers in the metadata_dataset, so schema parsing works.
106
+ def metadata_dataset
107
+ ds = super
108
+ ds.quote_identifiers = true
109
+ ds
110
+ end
111
+
112
+ # Use SP_RENAME to rename the table
113
+ def rename_table_sql(name, new_name)
114
+ "SP_RENAME #{quote_schema_table(name)}, #{quote_schema_table(new_name)}"
115
+ end
116
+
117
+ # SQL to rollback to a savepoint
118
+ def rollback_savepoint_sql(depth)
119
+ SQL_ROLLBACK_TO_SAVEPOINT % depth
120
+ end
121
+
122
+ # SQL to ROLLBACK a transaction.
123
+ def rollback_transaction_sql
124
+ SQL_ROLLBACK
125
+ end
126
+
127
+ # MSSQL uses the INFORMATION_SCHEMA to hold column information. This method does
128
+ # not support the parsing of primary key information.
129
+ def schema_parse_table(table_name, opts)
130
+ m = output_identifier_meth
131
+ m2 = input_identifier_meth
132
+ ds = metadata_dataset.from(:information_schema__tables___t).
133
+ join(:information_schema__columns___c, :table_catalog=>:table_catalog,
134
+ :table_schema => :table_schema, :table_name => :table_name).
135
+ 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).
136
+ filter(:c__table_name=>m2.call(table_name.to_s))
137
+ if schema = opts[:schema] || default_schema
138
+ ds.filter!(:c__table_schema=>schema)
139
+ end
140
+ ds.map do |row|
141
+ row[:allow_null] = row[:allow_null] == 'YES' ? true : false
142
+ row[:default] = nil if blank_object?(row[:default])
143
+ row[:type] = schema_column_type(row[:db_type])
144
+ [m.call(row.delete(:column)), row]
145
+ end
146
+ end
147
+
148
+ # SQL fragment for marking a table as temporary
149
+ def temporary_table_sql
150
+ TEMPORARY
151
+ end
152
+
153
+ # MSSQL has both datetime and timestamp classes, most people are going
154
+ # to want datetime
155
+ def type_literal_generic_datetime(column)
156
+ :datetime
157
+ end
158
+
159
+ # MSSQL has both datetime and timestamp classes, most people are going
160
+ # to want datetime
161
+ def type_literal_generic_time(column)
162
+ column[:only_time] ? :time : :datetime
163
+ end
164
+
165
+ # MSSQL doesn't have a true boolean class, so it uses bit
166
+ def type_literal_generic_trueclass(column)
167
+ :bit
168
+ end
169
+
170
+ # MSSQL uses image type for blobs
171
+ def type_literal_generic_file(column)
172
+ :image
173
+ end
174
+
175
+ # support for clustered index type
176
+ def index_definition_sql(table_name, index)
177
+ index_name = index[:name] || default_index_name(table_name, index[:columns])
178
+ clustered = index[:type] == :clustered
179
+ if index[:where]
180
+ raise Error, "Partial indexes are not supported for this database"
181
+ else
182
+ "CREATE #{'UNIQUE ' if index[:unique]}#{'CLUSTERED ' if clustered}INDEX #{quote_identifier(index_name)} ON #{quote_schema_table(table_name)} #{literal(index[:columns])}"
183
+ end
184
+ end
185
+ end
186
+
187
+ module DatasetMethods
188
+ BOOL_TRUE = '1'.freeze
189
+ BOOL_FALSE = '0'.freeze
190
+ COMMA_SEPARATOR = ', '.freeze
191
+ DELETE_CLAUSE_METHODS = Dataset.clause_methods(:delete, %w'with from output from2 where')
192
+ INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'with into columns output values')
193
+ SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with limit distinct columns into from lock join where group having order compounds')
194
+ UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'with table set output from where')
195
+ NOLOCK = ' WITH (NOLOCK)'.freeze
196
+ UPDLOCK = ' WITH (UPDLOCK)'.freeze
197
+ WILDCARD = LiteralString.new('*').freeze
198
+ CONSTANT_MAP = {:CURRENT_DATE=>'CAST(CURRENT_TIMESTAMP AS DATE)'.freeze, :CURRENT_TIME=>'CAST(CURRENT_TIMESTAMP AS TIME)'.freeze}
199
+
200
+ # MSSQL uses + for string concatenation, and LIKE is case insensitive by default.
201
+ def complex_expression_sql(op, args)
202
+ case op
203
+ when :'||'
204
+ super(:+, args)
205
+ when :ILIKE
206
+ super(:LIKE, args)
207
+ when :"NOT ILIKE"
208
+ super(:"NOT LIKE", args)
209
+ else
210
+ super(op, args)
211
+ end
212
+ end
213
+
214
+ # MSSQL doesn't support the SQL standard CURRENT_DATE or CURRENT_TIME
215
+ def constant_sql(constant)
216
+ CONSTANT_MAP[constant] || super
217
+ end
218
+
219
+ # Disable the use of INSERT OUTPUT
220
+ def disable_insert_output
221
+ clone(:disable_insert_output=>true)
222
+ end
223
+
224
+ # Disable the use of INSERT OUTPUT, modifying the receiver
225
+ def disable_insert_output!
226
+ mutation_method(:disable_insert_output)
227
+ end
228
+
229
+ # When returning all rows, if an offset is used, delete the row_number column
230
+ # before yielding the row.
231
+ def fetch_rows(sql, &block)
232
+ @opts[:offset] ? super(sql){|r| r.delete(row_number_column); yield r} : super(sql, &block)
233
+ end
234
+
235
+ # MSSQL uses the CONTAINS keyword for full text search
236
+ def full_text_search(cols, terms, opts = {})
237
+ filter("CONTAINS (#{literal(cols)}, #{literal(terms)})")
238
+ end
239
+
240
+ # Use the OUTPUT clause to get the value of all columns for the newly inserted record.
241
+ def insert_select(*values)
242
+ return unless supports_output_clause?
243
+ naked.clone(default_server_opts(:sql=>output(nil, [:inserted.*]).insert_sql(*values))).single_record unless opts[:disable_insert_output]
244
+ end
245
+
246
+ # Specify a table for a SELECT ... INTO query.
247
+ def into(table)
248
+ clone(:into => table)
249
+ end
250
+
251
+ # MSSQL uses a UNION ALL statement to insert multiple values at once.
252
+ def multi_insert_sql(columns, values)
253
+ [insert_sql(columns, LiteralString.new(values.map {|r| "SELECT #{expression_list(r)}" }.join(" UNION ALL ")))]
254
+ end
255
+
256
+ # Allows you to do a dirty read of uncommitted data using WITH (NOLOCK).
257
+ def nolock
258
+ lock_style(:dirty)
259
+ end
260
+
261
+ # Include an OUTPUT clause in the eventual INSERT, UPDATE, or DELETE query.
262
+ #
263
+ # The first argument is the table to output into, and the second argument
264
+ # is either an Array of column values to select, or a Hash which maps output
265
+ # column names to selected values, in the style of #insert or #update.
266
+ #
267
+ # Output into a returned result set is not currently supported.
268
+ #
269
+ # Examples:
270
+ #
271
+ # dataset.output(:output_table, [:deleted__id, :deleted__name])
272
+ # dataset.output(:output_table, :id => :inserted__id, :name => :inserted__name)
273
+ def output(into, values)
274
+ raise(Error, "SQL Server versions 2000 and earlier do not support the OUTPUT clause") unless supports_output_clause?
275
+ output = {}
276
+ case values
277
+ when Hash
278
+ output[:column_list], output[:select_list] = values.keys, values.values
279
+ when Array
280
+ output[:select_list] = values
281
+ end
282
+ output[:into] = into
283
+ clone({:output => output})
284
+ end
285
+
286
+ # An output method that modifies the receiver.
287
+ def output!(into, values)
288
+ mutation_method(:output, into, values)
289
+ end
290
+
291
+ # MSSQL uses [] to quote identifiers
292
+ def quoted_identifier(name)
293
+ "[#{name}]"
294
+ end
295
+
296
+ # MSSQL Requires the use of the ROW_NUMBER window function to emulate
297
+ # an offset. This implementation requires MSSQL 2005 or greater (offset
298
+ # can't be emulated well in MSSQL 2000).
299
+ #
300
+ # The implementation is ugly, cloning the current dataset and modifying
301
+ # the clone to add a ROW_NUMBER window function (and some other things),
302
+ # then using the modified clone in a subselect which is selected from.
303
+ #
304
+ # If offset is used, an order must be provided, because the use of ROW_NUMBER
305
+ # requires an order.
306
+ def select_sql
307
+ return super unless o = @opts[:offset]
308
+ raise(Error, 'MSSQL requires an order be provided if using an offset') unless order = @opts[:order]
309
+ dsa1 = dataset_alias(1)
310
+ rn = row_number_column
311
+ subselect_sql(unlimited.
312
+ unordered.
313
+ select_append{ROW_NUMBER(:over, :order=>order){}.as(rn)}.
314
+ from_self(:alias=>dsa1).
315
+ limit(@opts[:limit]).
316
+ where(SQL::Identifier.new(rn) > o))
317
+ end
318
+
319
+ # The version of the database server.
320
+ def server_version
321
+ db.server_version(@opts[:server])
322
+ end
323
+
324
+ # Microsoft SQL Server does not support INTERSECT or EXCEPT
325
+ def supports_intersect_except?
326
+ false
327
+ end
328
+
329
+ # MSSQL does not support IS TRUE
330
+ def supports_is_true?
331
+ false
332
+ end
333
+
334
+ # MSSQL doesn't support JOIN USING
335
+ def supports_join_using?
336
+ false
337
+ end
338
+
339
+ # MSSQL 2005+ supports modifying joined datasets
340
+ def supports_modifying_joins?
341
+ true
342
+ end
343
+
344
+ # MSSQL does not support multiple columns for the IN/NOT IN operators
345
+ def supports_multiple_column_in?
346
+ false
347
+ end
348
+
349
+ # Only 2005+ supports the output clause.
350
+ def supports_output_clause?
351
+ server_version >= 9000000
352
+ end
353
+
354
+ # MSSQL 2005+ supports window functions
355
+ def supports_window_functions?
356
+ true
357
+ end
358
+
359
+ private
360
+
361
+ # MSSQL supports the OUTPUT clause for DELETE statements.
362
+ # It also allows prepending a WITH clause.
363
+ def delete_clause_methods
364
+ DELETE_CLAUSE_METHODS
365
+ end
366
+
367
+ # Only include the primary table in the main delete clause
368
+ def delete_from_sql(sql)
369
+ sql << " FROM #{source_list(@opts[:from][0..0])}"
370
+ end
371
+
372
+ # MSSQL supports FROM clauses in DELETE and UPDATE statements.
373
+ def delete_from2_sql(sql)
374
+ if joined_dataset?
375
+ select_from_sql(sql)
376
+ select_join_sql(sql)
377
+ end
378
+ end
379
+ alias update_from_sql delete_from2_sql
380
+
381
+ # Handle the with clause for delete, insert, and update statements
382
+ # to be the same as the insert statement.
383
+ def delete_with_sql(sql)
384
+ select_with_sql(sql)
385
+ end
386
+ alias insert_with_sql delete_with_sql
387
+ alias update_with_sql delete_with_sql
388
+
389
+ # MSSQL raises an error if you try to provide more than 3 decimal places
390
+ # for a fractional timestamp. This probably doesn't work for smalldatetime
391
+ # fields.
392
+ def format_timestamp_usec(usec)
393
+ sprintf(".%03d", usec/1000)
394
+ end
395
+
396
+ # MSSQL supports the OUTPUT clause for INSERT statements.
397
+ # It also allows prepending a WITH clause.
398
+ def insert_clause_methods
399
+ INSERT_CLAUSE_METHODS
400
+ end
401
+
402
+ # MSSQL uses a literal hexidecimal number for blob strings
403
+ def literal_blob(v)
404
+ blob = '0x'
405
+ v.each_byte{|x| blob << sprintf('%02x', x)}
406
+ blob
407
+ end
408
+
409
+ # Use unicode string syntax for all strings. Don't double backslashes.
410
+ def literal_string(v)
411
+ "N'#{v.gsub(/'/, "''")}'"
412
+ end
413
+
414
+ # Use 0 for false on MSSQL
415
+ def literal_false
416
+ BOOL_FALSE
417
+ end
418
+
419
+ # Use 1 for true on MSSQL
420
+ def literal_true
421
+ BOOL_TRUE
422
+ end
423
+
424
+ # The alias to use for the row_number column when emulating OFFSET
425
+ def row_number_column
426
+ :x_sequel_row_number_x
427
+ end
428
+
429
+ # MSSQL adds the limit before the columns
430
+ def select_clause_methods
431
+ SELECT_CLAUSE_METHODS
432
+ end
433
+
434
+ def select_into_sql(sql)
435
+ sql << " INTO #{table_ref(@opts[:into])}" if @opts[:into]
436
+ end
437
+
438
+ # MSSQL uses TOP N for limit. For MSSQL 2005+ TOP (N) is used
439
+ # to allow the limit to be a bound variable.
440
+ def select_limit_sql(sql)
441
+ if l = @opts[:limit]
442
+ l = literal(l)
443
+ l = "(#{l})" if server_version >= 9000000
444
+ sql << " TOP #{l}"
445
+ end
446
+ end
447
+
448
+ # Support different types of locking styles
449
+ def select_lock_sql(sql)
450
+ case @opts[:lock]
451
+ when :update
452
+ sql << UPDLOCK
453
+ when :dirty
454
+ sql << NOLOCK
455
+ else
456
+ super
457
+ end
458
+ end
459
+
460
+ # SQL fragment for MSSQL's OUTPUT clause.
461
+ def output_sql(sql)
462
+ return unless supports_output_clause?
463
+ return unless output = @opts[:output]
464
+ sql << " OUTPUT #{column_list(output[:select_list])}"
465
+ if into = output[:into]
466
+ sql << " INTO #{table_ref(into)}"
467
+ if column_list = output[:column_list]
468
+ cl = []
469
+ column_list.each { |k, v| cl << literal(String === k ? k.to_sym : k) }
470
+ sql << " (#{cl.join(COMMA_SEPARATOR)})"
471
+ end
472
+ end
473
+ end
474
+ alias delete_output_sql output_sql
475
+ alias update_output_sql output_sql
476
+ alias insert_output_sql output_sql
477
+
478
+ # MSSQL supports the OUTPUT clause for UPDATE statements.
479
+ # It also allows prepending a WITH clause.
480
+ def update_clause_methods
481
+ UPDATE_CLAUSE_METHODS
482
+ end
483
+
484
+ # Only include the primary table in the main update clause
485
+ def update_table_sql(sql)
486
+ sql << " #{source_list(@opts[:from][0..0])}"
487
+ end
488
+ end
489
+ end
490
+ end