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