sequel 4.9.0 → 4.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 (110) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +79 -1
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/Rakefile +2 -12
  6. data/bin/sequel +1 -0
  7. data/doc/advanced_associations.rdoc +82 -25
  8. data/doc/association_basics.rdoc +21 -22
  9. data/doc/core_extensions.rdoc +1 -1
  10. data/doc/opening_databases.rdoc +7 -0
  11. data/doc/release_notes/4.10.0.txt +226 -0
  12. data/doc/security.rdoc +1 -0
  13. data/doc/testing.rdoc +7 -7
  14. data/doc/transactions.rdoc +8 -0
  15. data/lib/sequel/adapters/jdbc.rb +160 -168
  16. data/lib/sequel/adapters/jdbc/db2.rb +17 -18
  17. data/lib/sequel/adapters/jdbc/derby.rb +5 -28
  18. data/lib/sequel/adapters/jdbc/h2.rb +11 -22
  19. data/lib/sequel/adapters/jdbc/hsqldb.rb +31 -18
  20. data/lib/sequel/adapters/jdbc/jtds.rb +0 -15
  21. data/lib/sequel/adapters/jdbc/oracle.rb +36 -35
  22. data/lib/sequel/adapters/jdbc/postgresql.rb +72 -90
  23. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +18 -16
  24. data/lib/sequel/adapters/jdbc/sqlite.rb +7 -0
  25. data/lib/sequel/adapters/jdbc/sqlserver.rb +10 -30
  26. data/lib/sequel/adapters/jdbc/transactions.rb +5 -6
  27. data/lib/sequel/adapters/openbase.rb +1 -7
  28. data/lib/sequel/adapters/postgres.rb +1 -1
  29. data/lib/sequel/adapters/shared/access.rb +3 -6
  30. data/lib/sequel/adapters/shared/cubrid.rb +24 -9
  31. data/lib/sequel/adapters/shared/db2.rb +13 -5
  32. data/lib/sequel/adapters/shared/firebird.rb +16 -16
  33. data/lib/sequel/adapters/shared/informix.rb +2 -5
  34. data/lib/sequel/adapters/shared/mssql.rb +72 -63
  35. data/lib/sequel/adapters/shared/mysql.rb +72 -40
  36. data/lib/sequel/adapters/shared/oracle.rb +27 -15
  37. data/lib/sequel/adapters/shared/postgres.rb +24 -44
  38. data/lib/sequel/adapters/shared/progress.rb +1 -5
  39. data/lib/sequel/adapters/shared/sqlanywhere.rb +26 -18
  40. data/lib/sequel/adapters/shared/sqlite.rb +21 -6
  41. data/lib/sequel/adapters/utils/emulate_offset_with_reverse_and_count.rb +8 -1
  42. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +8 -2
  43. data/lib/sequel/adapters/utils/split_alter_table.rb +8 -0
  44. data/lib/sequel/core.rb +14 -9
  45. data/lib/sequel/database/dataset_defaults.rb +1 -0
  46. data/lib/sequel/database/misc.rb +12 -0
  47. data/lib/sequel/database/query.rb +4 -1
  48. data/lib/sequel/database/schema_methods.rb +3 -2
  49. data/lib/sequel/database/transactions.rb +47 -17
  50. data/lib/sequel/dataset/features.rb +12 -2
  51. data/lib/sequel/dataset/mutation.rb +2 -0
  52. data/lib/sequel/dataset/placeholder_literalizer.rb +12 -4
  53. data/lib/sequel/dataset/prepared_statements.rb +6 -0
  54. data/lib/sequel/dataset/query.rb +1 -1
  55. data/lib/sequel/dataset/sql.rb +132 -70
  56. data/lib/sequel/extensions/columns_introspection.rb +1 -1
  57. data/lib/sequel/extensions/null_dataset.rb +8 -4
  58. data/lib/sequel/extensions/pg_array.rb +4 -4
  59. data/lib/sequel/extensions/pg_row.rb +1 -0
  60. data/lib/sequel/model/associations.rb +468 -188
  61. data/lib/sequel/model/base.rb +88 -13
  62. data/lib/sequel/plugins/association_pks.rb +23 -64
  63. data/lib/sequel/plugins/auto_validations.rb +3 -2
  64. data/lib/sequel/plugins/dataset_associations.rb +1 -3
  65. data/lib/sequel/plugins/many_through_many.rb +18 -65
  66. data/lib/sequel/plugins/pg_array_associations.rb +97 -86
  67. data/lib/sequel/plugins/prepared_statements.rb +2 -1
  68. data/lib/sequel/plugins/prepared_statements_associations.rb +36 -27
  69. data/lib/sequel/plugins/rcte_tree.rb +12 -16
  70. data/lib/sequel/plugins/sharding.rb +21 -3
  71. data/lib/sequel/plugins/single_table_inheritance.rb +2 -1
  72. data/lib/sequel/plugins/subclasses.rb +1 -9
  73. data/lib/sequel/plugins/tactical_eager_loading.rb +9 -0
  74. data/lib/sequel/plugins/tree.rb +2 -2
  75. data/lib/sequel/plugins/validation_class_methods.rb +1 -1
  76. data/lib/sequel/version.rb +1 -1
  77. data/spec/adapters/mssql_spec.rb +57 -15
  78. data/spec/adapters/mysql_spec.rb +11 -0
  79. data/spec/bin_spec.rb +2 -2
  80. data/spec/core/database_spec.rb +38 -4
  81. data/spec/core/dataset_spec.rb +45 -7
  82. data/spec/core/placeholder_literalizer_spec.rb +17 -0
  83. data/spec/core/schema_spec.rb +6 -1
  84. data/spec/extensions/active_model_spec.rb +18 -9
  85. data/spec/extensions/association_pks_spec.rb +20 -18
  86. data/spec/extensions/association_proxies_spec.rb +9 -9
  87. data/spec/extensions/auto_validations_spec.rb +6 -0
  88. data/spec/extensions/columns_introspection_spec.rb +1 -0
  89. data/spec/extensions/constraint_validations_spec.rb +3 -1
  90. data/spec/extensions/many_through_many_spec.rb +191 -111
  91. data/spec/extensions/pg_array_associations_spec.rb +133 -103
  92. data/spec/extensions/prepared_statements_associations_spec.rb +23 -4
  93. data/spec/extensions/rcte_tree_spec.rb +35 -27
  94. data/spec/extensions/sequel_3_dataset_methods_spec.rb +0 -1
  95. data/spec/extensions/sharding_spec.rb +2 -2
  96. data/spec/extensions/tactical_eager_loading_spec.rb +4 -0
  97. data/spec/extensions/to_dot_spec.rb +1 -0
  98. data/spec/extensions/touch_spec.rb +2 -2
  99. data/spec/integration/associations_test.rb +130 -37
  100. data/spec/integration/dataset_test.rb +17 -0
  101. data/spec/integration/model_test.rb +17 -0
  102. data/spec/integration/schema_test.rb +14 -0
  103. data/spec/integration/transaction_test.rb +25 -1
  104. data/spec/model/association_reflection_spec.rb +63 -24
  105. data/spec/model/associations_spec.rb +104 -57
  106. data/spec/model/base_spec.rb +14 -1
  107. data/spec/model/class_dataset_methods_spec.rb +1 -0
  108. data/spec/model/eager_loading_spec.rb +221 -74
  109. data/spec/model/model_spec.rb +119 -1
  110. metadata +4 -2
@@ -3,6 +3,14 @@ Sequel.require 'adapters/jdbc/transactions'
3
3
 
4
4
  module Sequel
5
5
  module JDBC
6
+ class TypeConvertor
7
+ def SqlAnywhereBoolean(r, i)
8
+ if v = Short(r, i)
9
+ v != 0
10
+ end
11
+ end
12
+ end
13
+
6
14
  module SqlAnywhere
7
15
  # Database instance methods for Sybase databases accessed via JDBC.
8
16
  module DatabaseMethods
@@ -23,6 +31,11 @@ module Sequel
23
31
  rs.getInt(1)
24
32
  end
25
33
  end
34
+
35
+ def setup_type_convertor_map
36
+ super
37
+ @type_convertor_map[:SqlAnywhereBoolean] = TypeConvertor::INSTANCE.method(:SqlAnywhereBoolean)
38
+ end
26
39
  end
27
40
 
28
41
  #Dataset class for Sybase datasets accessed via JDBC.
@@ -31,26 +44,15 @@ module Sequel
31
44
 
32
45
  private
33
46
 
34
- class ::Sequel::JDBC::Dataset::TYPE_TRANSLATOR
35
- def sqla_boolean(i) i != 0 end
36
- end
37
-
38
- BOOLEAN_METHOD = TYPE_TRANSLATOR_INSTANCE.method(:sqla_boolean)
47
+ SMALLINT_TYPE = Java::JavaSQL::Types::SMALLINT
39
48
 
40
- def convert_type_proc(v, ctn=nil)
41
- case
42
- when ctn && ctn =~ SqlAnywhere::DatabaseMethods::SMALLINT_RE
43
- BOOLEAN_METHOD
49
+ def type_convertor(map, meta, type, i)
50
+ if convert_smallint_to_bool && type == SMALLINT_TYPE
51
+ map[:SqlAnywhereBoolean]
44
52
  else
45
- super(v, ctn)
53
+ super
46
54
  end
47
55
  end
48
-
49
- # SQLAnywhere needs the column info if it is converting smallint to bool,
50
- # since the JDBC adapter always returns smallint as integer.
51
- def convert_type_proc_uses_column_info?
52
- convert_smallint_to_bool
53
- end
54
56
  end
55
57
  end
56
58
  end
@@ -59,6 +59,13 @@ module Sequel
59
59
  end
60
60
  conn
61
61
  end
62
+
63
+ # Use getLong instead of getInt for converting integers on SQLite, since SQLite does not enforce a limit of 2**32.
64
+ def setup_type_convertor_map
65
+ super
66
+ @type_convertor_map[Java::JavaSQL::Types::INTEGER] = @type_convertor_map[Java::JavaSQL::Types::BIGINT]
67
+ @basic_type_convertor_map[Java::JavaSQL::Types::INTEGER] = @basic_type_convertor_map[Java::JavaSQL::Types::BIGINT]
68
+ end
62
69
  end
63
70
  end
64
71
  end
@@ -19,39 +19,19 @@ module Sequel
19
19
  # than getObject() for this column avoids the problem.
20
20
  # Reference: http://social.msdn.microsoft.com/Forums/en/sqldataaccess/thread/20df12f3-d1bf-4526-9daa-239a83a8e435
21
21
  module MetadataDatasetMethods
22
- def process_result_set_convert(cols, result)
23
- while result.next
24
- row = {}
25
- cols.each do |n, i, ctn, p|
26
- v = (n == :is_autoincrement ? result.getString(i) : result.getObject(i))
27
- row[n] = if v
28
- if p
29
- p.call(v)
30
- elsif p.nil?
31
- cols[i-1][3] = p = convert_type_proc(v, ctn)
32
- if p
33
- p.call(v)
34
- else
35
- v
36
- end
37
- else
38
- v
39
- end
40
- else
41
- v
42
- end
43
- end
44
- yield row
22
+ def type_convertor(map, meta, type, i)
23
+ if output_identifier(meta.getColumnLabel(i)) == :is_autoincrement
24
+ map[Java::JavaSQL::Types::VARCHAR]
25
+ else
26
+ super
45
27
  end
46
28
  end
47
29
 
48
- def process_result_set_no_convert(cols, result)
49
- while result.next
50
- row = {}
51
- cols.each do |n, i|
52
- row[n] = (n == :is_autoincrement ? result.getString(i) : result.getObject(i))
53
- end
54
- yield row
30
+ def basic_type_convertor(map, meta, type, i)
31
+ if output_identifier(meta.getColumnLabel(i)) == :is_autoincrement
32
+ map[Java::JavaSQL::Types::VARCHAR]
33
+ else
34
+ super
55
35
  end
56
36
  end
57
37
  end
@@ -47,14 +47,13 @@ module Sequel
47
47
  def begin_transaction(conn, opts=OPTS)
48
48
  if supports_savepoints?
49
49
  th = _trans(conn)
50
- if sps = th[:savepoints]
50
+ if sps = th[:savepoint_objs]
51
51
  sps << log_yield(TRANSACTION_SAVEPOINT){conn.set_savepoint}
52
52
  else
53
53
  log_yield(TRANSACTION_BEGIN){conn.setAutoCommit(false)}
54
- th[:savepoints] = []
54
+ th[:savepoint_objs] = []
55
55
  set_transaction_isolation(conn, opts)
56
56
  end
57
- th[:savepoint_level] += 1
58
57
  else
59
58
  log_yield(TRANSACTION_BEGIN){conn.setAutoCommit(false)}
60
59
  set_transaction_isolation(conn, opts)
@@ -64,7 +63,7 @@ module Sequel
64
63
  # Use JDBC connection's commit method to commit transactions
65
64
  def commit_transaction(conn, opts=OPTS)
66
65
  if supports_savepoints?
67
- sps = _trans(conn)[:savepoints]
66
+ sps = _trans(conn)[:savepoint_objs]
68
67
  if sps.empty?
69
68
  log_yield(TRANSACTION_COMMIT){conn.commit}
70
69
  elsif supports_releasing_savepoints?
@@ -81,7 +80,7 @@ module Sequel
81
80
  conn.setTransactionIsolation(jdbc_level)
82
81
  end
83
82
  if supports_savepoints?
84
- sps = _trans(conn)[:savepoints]
83
+ sps = _trans(conn)[:savepoint_objs]
85
84
  conn.setAutoCommit(true) if sps.empty?
86
85
  sps.pop
87
86
  else
@@ -94,7 +93,7 @@ module Sequel
94
93
  # Use JDBC connection's rollback method to rollback transactions
95
94
  def rollback_transaction(conn, opts=OPTS)
96
95
  if supports_savepoints?
97
- sps = _trans(conn)[:savepoints]
96
+ sps = _trans(conn)[:savepoint_objs]
98
97
  if sps.empty?
99
98
  log_yield(TRANSACTION_ROLLBACK){conn.rollback}
100
99
  else
@@ -29,7 +29,7 @@ module Sequel
29
29
  end
30
30
 
31
31
  class Dataset < Sequel::Dataset
32
- SELECT_CLAUSE_METHODS = clause_methods(:select, %w'select distinct columns from join where group having compounds order limit')
32
+ def_sql_method(self, :select, %w'select distinct columns from join where group having compounds order limit')
33
33
 
34
34
  Database::DatasetClass = self
35
35
 
@@ -48,12 +48,6 @@ module Sequel
48
48
  end
49
49
  self
50
50
  end
51
-
52
- private
53
-
54
- def select_clause_methods
55
- SELECT_CLAUSE_METHODS
56
- end
57
51
  end
58
52
  end
59
53
  end
@@ -108,7 +108,7 @@ module Sequel
108
108
  # PGconn subclass for connection specific methods used with the
109
109
  # pg, postgres, or postgres-pr driver.
110
110
  class Adapter < ::PGconn
111
- DISCONNECT_ERROR_RE = /\Acould not receive data from server/
111
+ DISCONNECT_ERROR_RE = /\A(?:could not receive data from server|no connection to the server|connection not open)/
112
112
 
113
113
  self.translate_results = false if respond_to?(:translate_results=)
114
114
 
@@ -88,9 +88,11 @@ module Sequel
88
88
  end
89
89
 
90
90
  module DatasetMethods
91
+ include(Module.new do
92
+ Dataset.def_sql_method(self, :select, %w'select distinct limit columns into from join where group order having compounds')
93
+ end)
91
94
  include EmulateOffsetWithReverseAndCount
92
95
 
93
- SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'select distinct limit columns into from join where group order having compounds')
94
96
  DATE_FORMAT = '#%Y-%m-%d#'.freeze
95
97
  TIMESTAMP_FORMAT = '#%Y-%m-%d %H:%M:%S#'.freeze
96
98
  TOP = " TOP ".freeze
@@ -295,11 +297,6 @@ module Sequel
295
297
  def quoted_identifier_append(sql, v)
296
298
  sql << BRACKET_OPEN << v.to_s << BRACKET_CLOSE
297
299
  end
298
-
299
- # Access requires the limit clause come before other clauses
300
- def select_clause_methods
301
- SELECT_CLAUSE_METHODS
302
- end
303
300
  end
304
301
  end
305
302
  end
@@ -165,12 +165,14 @@ module Sequel
165
165
  end
166
166
 
167
167
  module DatasetMethods
168
- SELECT_CLAUSE_METHODS = Sequel::Dataset.clause_methods(:select, %w'select distinct columns from join where group having compounds order limit')
169
- LIMIT = Sequel::Dataset::LIMIT
170
168
  COMMA = Sequel::Dataset::COMMA
169
+ LIMIT = Sequel::Dataset::LIMIT
171
170
  BOOL_FALSE = '0'.freeze
172
171
  BOOL_TRUE = '1'.freeze
173
172
 
173
+ # Hope you don't have more than 2**32 + offset rows in your dataset
174
+ ONLY_OFFSET = ",4294967295".freeze
175
+
174
176
  def supports_join_using?
175
177
  false
176
178
  end
@@ -200,23 +202,36 @@ module Sequel
200
202
  BOOL_TRUE
201
203
  end
202
204
 
203
- # CUBRID doesn't support CTEs or FOR UPDATE.
204
- def select_clause_methods
205
- SELECT_CLAUSE_METHODS
205
+ # CUBRID supports multiple rows in INSERT.
206
+ def multi_insert_sql_strategy
207
+ :values
206
208
  end
207
209
 
208
210
  # CUBRID requires a limit to use an offset,
209
211
  # and requires a FROM table if a limit is used.
210
212
  def select_limit_sql(sql)
211
- if @opts[:from] && (l = @opts[:limit])
213
+ return unless @opts[:from]
214
+ l = @opts[:limit]
215
+ o = @opts[:offset]
216
+ if l || o
212
217
  sql << LIMIT
213
- if o = @opts[:offset]
218
+ if o
214
219
  literal_append(sql, o)
215
- sql << COMMA
220
+ if l
221
+ sql << COMMA
222
+ literal_append(sql, l)
223
+ else
224
+ sql << ONLY_OFFSET
225
+ end
226
+ else
227
+ literal_append(sql, l)
216
228
  end
217
- literal_append(sql, l)
218
229
  end
219
230
  end
231
+
232
+ # CUBRID doesn't support FOR UPDATE.
233
+ def select_lock_sql(sql)
234
+ end
220
235
  end
221
236
  end
222
237
  end
@@ -270,6 +270,10 @@ module Sequel
270
270
  end
271
271
  end
272
272
 
273
+ def supports_cte?(type=:select)
274
+ type == :select
275
+ end
276
+
273
277
  # DB2 supports GROUP BY CUBE
274
278
  def supports_group_cube?
275
279
  true
@@ -317,6 +321,10 @@ module Sequel
317
321
 
318
322
  private
319
323
 
324
+ def empty_from_sql
325
+ EMPTY_FROM_TABLE
326
+ end
327
+
320
328
  # DB2 needs the standard workaround to insert all default values into
321
329
  # a table with more than one column.
322
330
  def insert_supports_empty_values?
@@ -342,16 +350,16 @@ module Sequel
342
350
  end
343
351
  end
344
352
 
353
+ # DB2 can insert multiple rows using a UNION
354
+ def multi_insert_sql_strategy
355
+ :union
356
+ end
357
+
345
358
  # DB2 does not require that ROW_NUMBER be ordered.
346
359
  def require_offset_order?
347
360
  false
348
361
  end
349
362
 
350
- # Add a fallback table for empty from situation
351
- def select_from_sql(sql)
352
- @opts[:from] ? super : (sql << EMPTY_FROM_TABLE)
353
- end
354
-
355
363
  # Modify the sql to limit the number of rows returned
356
364
  # Note:
357
365
  #
@@ -152,12 +152,13 @@ module Sequel
152
152
  BOOL_TRUE = '1'.freeze
153
153
  BOOL_FALSE = '0'.freeze
154
154
  NULL = LiteralString.new('NULL').freeze
155
- SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with select distinct limit columns from join where group having compounds order')
156
- INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'insert into columns values returning')
157
155
  FIRST = " FIRST ".freeze
158
156
  SKIP = " SKIP ".freeze
159
157
  DEFAULT_FROM = " FROM RDB$DATABASE"
160
158
 
159
+ Dataset.def_sql_method(self, :select, %w'with select distinct limit columns from join where group having compounds order')
160
+ Dataset.def_sql_method(self, :insert, %w'insert into columns values returning')
161
+
161
162
  # Insert given values into the database.
162
163
  def insert(*values)
163
164
  if @opts[:sql] || @opts[:returning]
@@ -176,6 +177,10 @@ module Sequel
176
177
  true
177
178
  end
178
179
 
180
+ def supports_cte?(type=:select)
181
+ type == :select
182
+ end
183
+
179
184
  def supports_insert_select?
180
185
  true
181
186
  end
@@ -185,10 +190,14 @@ module Sequel
185
190
  false
186
191
  end
187
192
 
193
+ def supports_returning?(type)
194
+ type == :insert
195
+ end
196
+
188
197
  private
189
198
 
190
- def insert_clause_methods
191
- INSERT_CLAUSE_METHODS
199
+ def empty_from_sql
200
+ DEFAULT_FROM
192
201
  end
193
202
 
194
203
  def insert_pk(*values)
@@ -204,19 +213,10 @@ module Sequel
204
213
  BOOL_TRUE
205
214
  end
206
215
 
207
- # The order of clauses in the SELECT SQL statement
208
- def select_clause_methods
209
- SELECT_CLAUSE_METHODS
216
+ # Firebird can insert multiple rows using a UNION
217
+ def multi_insert_sql_strategy
218
+ :union
210
219
  end
211
-
212
- # Use a default FROM table if the dataset does not contain a FROM table.
213
- def select_from_sql(sql)
214
- if @opts[:from]
215
- super
216
- else
217
- sql << DEFAULT_FROM
218
- end
219
- end
220
220
 
221
221
  def select_limit_sql(sql)
222
222
  if l = @opts[:limit]
@@ -25,20 +25,17 @@ module Sequel
25
25
  end
26
26
 
27
27
  module DatasetMethods
28
- SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'select limit distinct columns from join where having group compounds order')
29
28
  FIRST = " FIRST ".freeze
30
29
  SKIP = " SKIP ".freeze
31
30
 
32
- private
31
+ Dataset.def_sql_method(self, :select, %w'select limit distinct columns from join where having group compounds order')
33
32
 
34
33
  # Informix does not support INTERSECT or EXCEPT
35
34
  def supports_intersect_except?
36
35
  false
37
36
  end
38
37
 
39
- def select_clause_methods
40
- SELECT_CLAUSE_METHODS
41
- end
38
+ private
42
39
 
43
40
  def select_limit_sql(sql)
44
41
  if o = @opts[:offset]
@@ -37,39 +37,73 @@ module Sequel
37
37
  # Execute the given stored procedure with the given name.
38
38
  #
39
39
  # Options:
40
- # :args :: Array of arguments to stored procedure. Output parameters to
41
- # the function are specified using :output. You can also name
42
- # output parameters and provide a type by using an array containing
43
- # :output, the type name, and the parameter name.
40
+ # :args :: Arguments to stored procedure. For named argumetns, this should be a
41
+ # hash keyed by argument named. For unnamed arguments, this should be an
42
+ # array. Output parameters to the function are specified using :output.
43
+ # You can also name output parameters and provide a type by using an
44
+ # array containing :output, the type name, and the parameter name.
44
45
  # :server :: The server/shard on which to execute the procedure.
45
46
  #
47
+ # This method returns a single hash with the following keys:
48
+ #
49
+ # :result :: The result code of the stored procedure
50
+ # :numrows :: The number of rows affected by the stored procedure
51
+ # output params :: Values for any output paramters, using the name given for the output parameter
52
+ #
46
53
  # Examples:
47
54
  #
48
55
  # DB.call_mssql_sproc(:SequelTest, {:args => ['input arg', :output]})
49
56
  # DB.call_mssql_sproc(:SequelTest, {:args => ['input arg', [:output, 'int', 'varname']]})
57
+ #
58
+ # named params:
59
+ # DB.call_mssql_sproc(:SequelTest, :args => {
60
+ # 'input_arg1_name' => 'input arg1 value',
61
+ # 'input_arg2_name' => 'input arg2 value',
62
+ # 'output_arg_name' => [:output, 'int', 'varname']
63
+ # })
50
64
  def call_mssql_sproc(name, opts=OPTS)
51
65
  args = opts[:args] || []
52
66
  names = ['@RC AS RESULT', '@@ROWCOUNT AS NUMROWS']
53
67
  declarations = ['@RC int']
54
68
  values = []
55
69
 
56
- args.each_with_index do |v, i|
57
- if v.is_a?(Array)
58
- v, type, select = v
70
+ if args.is_a?(Hash)
71
+ named_args = true
72
+ args = args.to_a
73
+ method = :each
74
+ else
75
+ method = :each_with_index
76
+ end
77
+
78
+ args.send(method) do |v, i|
79
+ if named_args
80
+ k = v
81
+ v, type, select = i
82
+ raise Error, "must provide output parameter name when using output parameters with named arguments" if v == :output && !select
59
83
  else
60
- type = select = nil
84
+ v, type, select = v
61
85
  end
62
86
 
63
87
  if v == :output
64
- type = "nvarchar(max)" unless type
65
- varname = "var#{i}" unless varname
66
- select ||= varname
88
+ type ||= "nvarchar(max)"
89
+ if named_args
90
+ varname = select
91
+ else
92
+ varname = "var#{i}"
93
+ select ||= varname
94
+ end
67
95
  names << "@#{varname} AS #{quote_identifier(select)}"
68
96
  declarations << "@#{varname} #{type}"
69
- values << "@#{varname} OUTPUT"
97
+ value = "@#{varname} OUTPUT"
70
98
  else
71
- values << literal(v)
99
+ value = literal(v)
100
+ end
101
+
102
+ if named_args
103
+ value = "@#{k}=#{value}"
72
104
  end
105
+
106
+ values << value
73
107
  end
74
108
 
75
109
  sql = "DECLARE #{declarations.join(', ')}; EXECUTE @RC = #{name} #{values.join(', ')}; SELECT #{names.join(', ')}"
@@ -159,6 +193,9 @@ module Sequel
159
193
  # SQL Server 2008 Express).
160
194
  def server_version(server=nil)
161
195
  return @server_version if @server_version
196
+ if @opts[:server_version]
197
+ return @server_version = Integer(@opts[:server_version])
198
+ end
162
199
  @server_version = synchronize(server) do |conn|
163
200
  (conn.server_version rescue nil) if conn.respond_to?(:server_version)
164
201
  end
@@ -276,7 +313,7 @@ module Sequel
276
313
  # Commit the active transaction on the connection, does not commit/release
277
314
  # savepoints.
278
315
  def commit_transaction(conn, opts=OPTS)
279
- log_connection_execute(conn, commit_transaction_sql) unless _trans(conn)[:savepoint_level] > 1
316
+ log_connection_execute(conn, commit_transaction_sql) unless savepoint_level(conn) > 1
280
317
  end
281
318
 
282
319
  # SQL to COMMIT a transaction.
@@ -453,16 +490,14 @@ module Sequel
453
490
  end
454
491
 
455
492
  module DatasetMethods
493
+ include(Module.new do
494
+ Dataset.def_sql_method(self, :select, %w'with select distinct limit columns into from lock join where group having order compounds')
495
+ end)
456
496
  include EmulateOffsetWithRowNumber
457
497
 
458
498
  BOOL_TRUE = '1'.freeze
459
499
  BOOL_FALSE = '0'.freeze
460
500
  COMMA_SEPARATOR = ', '.freeze
461
- DELETE_CLAUSE_METHODS = Dataset.clause_methods(:delete, %w'with delete from output from2 where')
462
- INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'with insert into columns output values')
463
- SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with select distinct limit columns into from lock join where group having order compounds')
464
- UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'with update limit table set output from where')
465
- UPDATE_CLAUSE_METHODS_2000 = Dataset.clause_methods(:update, %w'update table set output from where')
466
501
  NOLOCK = ' WITH (NOLOCK)'.freeze
467
502
  UPDLOCK = ' WITH (UPDLOCK)'.freeze
468
503
  WILDCARD = LiteralString.new('*').freeze
@@ -484,8 +519,6 @@ module Sequel
484
519
  DATEPART_SECOND_MIDDLE = ') + datepart(ns, '.freeze
485
520
  DATEPART_SECOND_CLOSE = ")/1000000000.0) AS double precision)".freeze
486
521
  DATEPART_OPEN = "datepart(".freeze
487
- UNION_ALL = ' UNION ALL '.freeze
488
- SELECT_SPACE = 'SELECT '.freeze
489
522
  TIMESTAMP_USEC_FORMAT = ".%03d".freeze
490
523
  OUTPUT_INSERTED = " OUTPUT INSERTED.*".freeze
491
524
  HEX_START = '0x'.freeze
@@ -507,7 +540,11 @@ module Sequel
507
540
  ROWS_ONLY = " ROWS ONLY".freeze
508
541
  FETCH_NEXT = " FETCH NEXT ".freeze
509
542
 
510
- Sequel::Dataset.def_mutation_method(:disable_insert_output, :output, :module=>self)
543
+ Dataset.def_mutation_method(:disable_insert_output, :output, :module=>self)
544
+ Dataset.def_sql_method(self, :delete, %w'with delete from output from2 where')
545
+ Dataset.def_sql_method(self, :insert, %w'with insert into columns output values')
546
+ Dataset.def_sql_method(self, :update, [['if is_2005_or_later?', %w'with update limit table set output from where'], ['else', %w'update table set output from where']])
547
+
511
548
 
512
549
  # Allow overriding of the mssql_unicode_strings option at the dataset level.
513
550
  attr_writer :mssql_unicode_strings
@@ -604,20 +641,6 @@ module Sequel
604
641
  clone(:into => table)
605
642
  end
606
643
 
607
- # MSSQL uses a UNION ALL statement to insert multiple values at once.
608
- def multi_insert_sql(columns, values)
609
- c = false
610
- sql = LiteralString.new('')
611
- u = UNION_ALL
612
- values.each do |v|
613
- sql << u if c
614
- sql << SELECT_SPACE
615
- expression_list_append(sql, v)
616
- c ||= true
617
- end
618
- [insert_sql(columns, sql)]
619
- end
620
-
621
644
  # Allows you to do a dirty read of uncommitted data using WITH (NOLOCK).
622
645
  def nolock
623
646
  lock_style(:dirty)
@@ -675,6 +698,10 @@ module Sequel
675
698
  db.server_version(@opts[:server])
676
699
  end
677
700
 
701
+ def supports_cte?(type=:select)
702
+ is_2005_or_later?
703
+ end
704
+
678
705
  # MSSQL 2005+ supports GROUP BY CUBE.
679
706
  def supports_group_cube?
680
707
  is_2005_or_later?
@@ -715,6 +742,11 @@ module Sequel
715
742
  false
716
743
  end
717
744
 
745
+ # MSSQL 2012+ supports offsets in correlated subqueries.
746
+ def supports_offsets_in_correlated_subqueries?
747
+ is_2012_or_later?
748
+ end
749
+
718
750
  # MSSQL 2005+ supports the output clause.
719
751
  def supports_output_clause?
720
752
  is_2005_or_later?
@@ -778,12 +810,6 @@ module Sequel
778
810
  DEFAULT_TIMESTAMP_FORMAT
779
811
  end
780
812
 
781
- # MSSQL supports the OUTPUT clause for DELETE statements.
782
- # It also allows prepending a WITH clause.
783
- def delete_clause_methods
784
- DELETE_CLAUSE_METHODS
785
- end
786
-
787
813
  # Only include the primary table in the main delete clause
788
814
  def delete_from_sql(sql)
789
815
  sql << FROM
@@ -817,12 +843,6 @@ module Sequel
817
843
  sprintf(TIMESTAMP_USEC_FORMAT, usec/1000)
818
844
  end
819
845
 
820
- # MSSQL supports the OUTPUT clause for INSERT statements.
821
- # It also allows prepending a WITH clause.
822
- def insert_clause_methods
823
- INSERT_CLAUSE_METHODS
824
- end
825
-
826
846
  # Use OUTPUT INSERTED.* to return all columns of the inserted row,
827
847
  # for use with the prepared statement code.
828
848
  def insert_output_sql(sql)
@@ -873,10 +893,10 @@ module Sequel
873
893
  BOOL_TRUE
874
894
  end
875
895
 
876
- # MSSQL adds the limit before the columns, except on 2012+ when using
877
- # offsets on ordered queries.
878
- def select_clause_methods
879
- SELECT_CLAUSE_METHODS
896
+ # MSSQL 2008+ supports multiple rows in the VALUES clause, older versions
897
+ # can use UNION.
898
+ def multi_insert_sql_strategy
899
+ is_2008_or_later? ? :values : :union
880
900
  end
881
901
 
882
902
  def select_into_sql(sql)
@@ -954,17 +974,6 @@ module Sequel
954
974
  alias delete_output_sql output_sql
955
975
  alias update_output_sql output_sql
956
976
 
957
- # MSSQL supports the OUTPUT and TOP clause for UPDATE statements.
958
- # It also allows prepending a WITH clause. For MSSQL 2000
959
- # and below, exclude WITH and TOP.
960
- def update_clause_methods
961
- if is_2005_or_later?
962
- UPDATE_CLAUSE_METHODS
963
- else
964
- UPDATE_CLAUSE_METHODS_2000
965
- end
966
- end
967
-
968
977
  # Only include the primary table in the main update clause
969
978
  def update_table_sql(sql)
970
979
  sql << SPACE