sequel 4.9.0 → 4.10.0

Sign up to get free protection for your applications and to get access to all the features.
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