sequel 3.29.0 → 3.30.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 (106) hide show
  1. data/CHANGELOG +35 -3
  2. data/Rakefile +2 -1
  3. data/doc/association_basics.rdoc +11 -0
  4. data/doc/opening_databases.rdoc +2 -0
  5. data/doc/release_notes/3.30.0.txt +135 -0
  6. data/doc/testing.rdoc +17 -3
  7. data/lib/sequel/adapters/amalgalite.rb +2 -2
  8. data/lib/sequel/adapters/do/mysql.rb +5 -2
  9. data/lib/sequel/adapters/ibmdb.rb +2 -2
  10. data/lib/sequel/adapters/jdbc.rb +126 -43
  11. data/lib/sequel/adapters/jdbc/as400.rb +11 -3
  12. data/lib/sequel/adapters/jdbc/db2.rb +2 -1
  13. data/lib/sequel/adapters/jdbc/derby.rb +44 -19
  14. data/lib/sequel/adapters/jdbc/h2.rb +32 -19
  15. data/lib/sequel/adapters/jdbc/hsqldb.rb +21 -17
  16. data/lib/sequel/adapters/jdbc/jtds.rb +9 -4
  17. data/lib/sequel/adapters/jdbc/mssql.rb +3 -1
  18. data/lib/sequel/adapters/jdbc/mysql.rb +2 -1
  19. data/lib/sequel/adapters/jdbc/oracle.rb +21 -7
  20. data/lib/sequel/adapters/jdbc/postgresql.rb +3 -2
  21. data/lib/sequel/adapters/jdbc/sqlite.rb +2 -1
  22. data/lib/sequel/adapters/jdbc/sqlserver.rb +48 -18
  23. data/lib/sequel/adapters/mock.rb +2 -1
  24. data/lib/sequel/adapters/mysql.rb +4 -2
  25. data/lib/sequel/adapters/mysql2.rb +2 -2
  26. data/lib/sequel/adapters/odbc/mssql.rb +1 -1
  27. data/lib/sequel/adapters/openbase.rb +1 -1
  28. data/lib/sequel/adapters/oracle.rb +6 -6
  29. data/lib/sequel/adapters/postgres.rb +25 -12
  30. data/lib/sequel/adapters/shared/access.rb +14 -6
  31. data/lib/sequel/adapters/shared/db2.rb +36 -13
  32. data/lib/sequel/adapters/shared/firebird.rb +12 -5
  33. data/lib/sequel/adapters/shared/informix.rb +11 -3
  34. data/lib/sequel/adapters/shared/mssql.rb +94 -47
  35. data/lib/sequel/adapters/shared/mysql.rb +107 -49
  36. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +2 -2
  37. data/lib/sequel/adapters/shared/oracle.rb +54 -27
  38. data/lib/sequel/adapters/shared/postgres.rb +65 -26
  39. data/lib/sequel/adapters/shared/progress.rb +4 -1
  40. data/lib/sequel/adapters/shared/sqlite.rb +36 -20
  41. data/lib/sequel/adapters/sqlite.rb +2 -3
  42. data/lib/sequel/adapters/swift/mysql.rb +3 -2
  43. data/lib/sequel/adapters/swift/sqlite.rb +2 -2
  44. data/lib/sequel/adapters/tinytds.rb +14 -8
  45. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +7 -4
  46. data/lib/sequel/database/misc.rb +6 -2
  47. data/lib/sequel/dataset/graph.rb +33 -7
  48. data/lib/sequel/dataset/prepared_statements.rb +19 -5
  49. data/lib/sequel/dataset/sql.rb +611 -201
  50. data/lib/sequel/model/associations.rb +12 -5
  51. data/lib/sequel/model/base.rb +20 -5
  52. data/lib/sequel/plugins/sharding.rb +9 -29
  53. data/lib/sequel/sql.rb +2 -1
  54. data/lib/sequel/timezones.rb +14 -4
  55. data/lib/sequel/version.rb +1 -1
  56. data/spec/adapters/mysql_spec.rb +10 -0
  57. data/spec/adapters/oracle_spec.rb +1 -1
  58. data/spec/core/core_sql_spec.rb +3 -1
  59. data/spec/core/database_spec.rb +42 -0
  60. data/spec/core/dataset_spec.rb +10 -3
  61. data/spec/core/mock_adapter_spec.rb +4 -0
  62. data/spec/core/object_graph_spec.rb +38 -0
  63. data/spec/extensions/association_autoreloading_spec.rb +1 -10
  64. data/spec/extensions/association_dependencies_spec.rb +2 -12
  65. data/spec/extensions/association_pks_spec.rb +35 -39
  66. data/spec/extensions/caching_spec.rb +23 -50
  67. data/spec/extensions/class_table_inheritance_spec.rb +30 -82
  68. data/spec/extensions/composition_spec.rb +18 -13
  69. data/spec/extensions/hook_class_methods_spec.rb +65 -91
  70. data/spec/extensions/identity_map_spec.rb +33 -103
  71. data/spec/extensions/instance_filters_spec.rb +10 -21
  72. data/spec/extensions/instance_hooks_spec.rb +6 -24
  73. data/spec/extensions/json_serializer_spec.rb +4 -5
  74. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  75. data/spec/extensions/list_spec.rb +17 -39
  76. data/spec/extensions/many_through_many_spec.rb +135 -277
  77. data/spec/extensions/migration_spec.rb +18 -15
  78. data/spec/extensions/named_timezones_spec.rb +1 -1
  79. data/spec/extensions/nested_attributes_spec.rb +97 -92
  80. data/spec/extensions/optimistic_locking_spec.rb +9 -20
  81. data/spec/extensions/prepared_statements_associations_spec.rb +22 -37
  82. data/spec/extensions/prepared_statements_safe_spec.rb +9 -27
  83. data/spec/extensions/prepared_statements_spec.rb +11 -30
  84. data/spec/extensions/prepared_statements_with_pk_spec.rb +6 -13
  85. data/spec/extensions/pretty_table_spec.rb +1 -6
  86. data/spec/extensions/rcte_tree_spec.rb +41 -43
  87. data/spec/extensions/schema_dumper_spec.rb +3 -6
  88. data/spec/extensions/serialization_spec.rb +20 -32
  89. data/spec/extensions/sharding_spec.rb +66 -140
  90. data/spec/extensions/single_table_inheritance_spec.rb +14 -36
  91. data/spec/extensions/spec_helper.rb +10 -64
  92. data/spec/extensions/sql_expr_spec.rb +20 -60
  93. data/spec/extensions/tactical_eager_loading_spec.rb +9 -19
  94. data/spec/extensions/timestamps_spec.rb +6 -6
  95. data/spec/extensions/to_dot_spec.rb +1 -2
  96. data/spec/extensions/touch_spec.rb +13 -14
  97. data/spec/extensions/tree_spec.rb +11 -26
  98. data/spec/extensions/update_primary_key_spec.rb +30 -24
  99. data/spec/extensions/validation_class_methods_spec.rb +30 -51
  100. data/spec/extensions/validation_helpers_spec.rb +16 -35
  101. data/spec/integration/dataset_test.rb +16 -4
  102. data/spec/integration/prepared_statement_test.rb +4 -2
  103. data/spec/model/eager_loading_spec.rb +16 -0
  104. data/spec/model/model_spec.rb +15 -1
  105. data/spec/model/record_spec.rb +60 -0
  106. metadata +23 -40
@@ -8,27 +8,57 @@ module Sequel
8
8
  # Database instance methods for SQLServer databases accessed via JDBC.
9
9
  module DatabaseMethods
10
10
  include Sequel::JDBC::MSSQL::DatabaseMethods
11
-
12
- def metadata_dataset
13
- ds = super
14
- # Work around a bug in SQL Server JDBC Driver 3.0, where the metadata
15
- # for the getColumns result set specifies an incorrect type for the
16
- # IS_AUTOINCREMENT column. The column is a string, but the type is
17
- # specified as a short. This causes getObject() to throw a
18
- # com.microsoft.sqlserver.jdbc.SQLServerException: "The conversion
19
- # from char to SMALLINT is unsupported." Using getString() rather
20
- # than getObject() for this column avoids the problem.
21
- # Reference: http://social.msdn.microsoft.com/Forums/en/sqldataaccess/thread/20df12f3-d1bf-4526-9daa-239a83a8e435
22
- def ds.result_set_object_getter
23
- lambda do |result, n, i|
24
- if n == :is_autoincrement
25
- @convert_types ? convert_type(result.getString(i)) : result.getString(i)
26
- else
27
- @convert_types ? convert_type(result.getObject(i)) : result.getObject(i)
11
+
12
+ # Work around a bug in SQL Server JDBC Driver 3.0, where the metadata
13
+ # for the getColumns result set specifies an incorrect type for the
14
+ # IS_AUTOINCREMENT column. The column is a string, but the type is
15
+ # specified as a short. This causes getObject() to throw a
16
+ # com.microsoft.sqlserver.jdbc.SQLServerException: "The conversion
17
+ # from char to SMALLINT is unsupported." Using getString() rather
18
+ # than getObject() for this column avoids the problem.
19
+ # Reference: http://social.msdn.microsoft.com/Forums/en/sqldataaccess/thread/20df12f3-d1bf-4526-9daa-239a83a8e435
20
+ module MetadataDatasetMethods
21
+ def process_result_set_convert(cols, result, rn)
22
+ while result.next
23
+ row = {}
24
+ cols.each do |n, i, p|
25
+ v = (n == :is_autoincrement ? result.getString(i) : result.getObject(i))
26
+ row[n] = if v
27
+ if p
28
+ p.call(v)
29
+ elsif p.nil?
30
+ cols[i-1][2] = p = convert_type_proc(v)
31
+ if p
32
+ p.call(v)
33
+ else
34
+ v
35
+ end
36
+ else
37
+ v
38
+ end
39
+ else
40
+ v
41
+ end
42
+ end
43
+ row.delete(rn) if rn
44
+ yield row
45
+ end
46
+ end
47
+
48
+ def process_result_set_no_convert(cols, result, rn)
49
+ while result.next
50
+ row = {}
51
+ cols.each do |n, i|
52
+ row[n] = (n == :is_autoincrement ? result.getString(i) : result.getObject(i))
28
53
  end
54
+ row.delete(rn) if rn
55
+ yield row
29
56
  end
30
57
  end
31
- ds
58
+ end
59
+
60
+ def metadata_dataset
61
+ super.extend(MetadataDatasetMethods)
32
62
  end
33
63
  end
34
64
 
@@ -92,8 +92,9 @@ module Sequel
92
92
  # :numrows :: Call #numrows= with the value
93
93
  # :extend :: A module the object is extended with.
94
94
  # :sqls :: The array to store the SQL queries in.
95
- def initialize(opts=nil)
95
+ def initialize(opts={})
96
96
  super
97
+ opts = @opts
97
98
  self.autoid = opts[:autoid]
98
99
  self.columns = opts[:columns]
99
100
  self.fetch = opts[:fetch]
@@ -347,8 +347,10 @@ module Sequel
347
347
  end
348
348
 
349
349
  # Handle correct quoting of strings using ::MySQL.quote.
350
- def literal_string(v)
351
- "'#{::Mysql.quote(v)}'"
350
+ def literal_string_append(sql, v)
351
+ sql << "'"
352
+ sql << ::Mysql.quote(v)
353
+ sql << "'"
352
354
  end
353
355
 
354
356
  # Yield each row of the given result set r with columns cols
@@ -172,8 +172,8 @@ module Sequel
172
172
  end
173
173
 
174
174
  # Handle correct quoting of strings using ::Mysql2::Client#escape.
175
- def literal_string(v)
176
- db.synchronize{|c| "'#{c.escape(v)}'"}
175
+ def literal_string_append(sql, v)
176
+ sql << "'" << db.synchronize{|c| c.escape(v)} << "'"
177
177
  end
178
178
  end
179
179
  end
@@ -7,7 +7,7 @@ module Sequel
7
7
  module MSSQL
8
8
  module DatabaseMethods
9
9
  include Sequel::MSSQL::DatabaseMethods
10
- LAST_INSERT_ID_SQL='SELECT SCOPE_IDENTITY()'
10
+ LAST_INSERT_ID_SQL='SELECT SCOPE_IDENTITY()'.freeze
11
11
 
12
12
  # Return the last inserted identity value.
13
13
  def execute_insert(sql, opts={})
@@ -32,7 +32,7 @@ module Sequel
32
32
  end
33
33
 
34
34
  class Dataset < Sequel::Dataset
35
- SELECT_CLAUSE_METHODS = clause_methods(:select, %w'distinct columns from join where group having compounds order limit')
35
+ SELECT_CLAUSE_METHODS = clause_methods(:select, %w'select distinct columns from join where group having compounds order limit')
36
36
 
37
37
  Database::DatasetClass = self
38
38
 
@@ -317,17 +317,17 @@ module Sequel
317
317
  # Run execute_select on the database with the given SQL and the stored
318
318
  # bind arguments.
319
319
  def execute(sql, opts={}, &block)
320
- super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
320
+ super(prepared_sql, {:arguments=>bind_arguments}.merge(opts), &block)
321
321
  end
322
322
 
323
323
  # Same as execute, explicit due to intricacies of alias and super.
324
324
  def execute_dui(sql, opts={}, &block)
325
- super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
325
+ super(prepared_sql, {:arguments=>bind_arguments}.merge(opts), &block)
326
326
  end
327
327
 
328
328
  # Same as execute, explicit due to intricacies of alias and super.
329
329
  def execute_insert(sql, opts={}, &block)
330
- super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
330
+ super(prepared_sql, {:arguments=>bind_arguments}.merge(opts), &block)
331
331
  end
332
332
  end
333
333
 
@@ -414,13 +414,13 @@ module Sequel
414
414
 
415
415
  private
416
416
 
417
- def literal_other(v)
417
+ def literal_other_append(sql, v)
418
418
  case v
419
419
  when OraDate
420
- literal(db.to_application_timestamp(v))
420
+ literal_append(sql, db.to_application_timestamp(v))
421
421
  when OCI8::CLOB
422
422
  v.rewind
423
- literal(v.read)
423
+ literal_append(sql, v.read)
424
424
  else
425
425
  super
426
426
  end
@@ -218,14 +218,27 @@ module Sequel
218
218
  # client encoding for the connection.
219
219
  def connect(server)
220
220
  opts = server_opts(server)
221
- conn = Adapter.connect(
222
- (opts[:host] unless blank_object?(opts[:host])),
223
- opts[:port] || 5432,
224
- nil, '',
225
- opts[:database],
226
- opts[:user],
227
- opts[:password]
228
- )
221
+ conn = if SEQUEL_POSTGRES_USES_PG
222
+ connection_params = {
223
+ :host => opts[:host],
224
+ :port => opts[:port] || 5432,
225
+ :tty => '',
226
+ :dbname => opts[:database],
227
+ :user => opts[:user],
228
+ :password => opts[:password],
229
+ :connect_timeout => opts[:connect_timeout] || 20
230
+ }.delete_if { |key, value| blank_object?(value) }
231
+ Adapter.connect(connection_params)
232
+ else
233
+ Adapter.connect(
234
+ (opts[:host] unless blank_object?(opts[:host])),
235
+ opts[:port] || 5432,
236
+ nil, '',
237
+ opts[:database],
238
+ opts[:user],
239
+ opts[:password]
240
+ )
241
+ end
229
242
  if encoding = opts[:encoding] || opts[:charset]
230
243
  if conn.respond_to?(:set_client_encoding)
231
244
  conn.set_client_encoding(encoding)
@@ -639,13 +652,13 @@ module Sequel
639
652
  end
640
653
 
641
654
  # Use the driver's escape_bytea
642
- def literal_blob(v)
643
- db.synchronize{|c| "'#{c.escape_bytea(v)}'"}
655
+ def literal_blob_append(sql, v)
656
+ sql << "'" << db.synchronize{|c| c.escape_bytea(v)} << "'"
644
657
  end
645
658
 
646
659
  # Use the driver's escape_string
647
- def literal_string(v)
648
- db.synchronize{|c| "'#{c.escape_string(v)}'"}
660
+ def literal_string_append(sql, v)
661
+ sql << "'" << db.synchronize{|c| c.escape_string(v)} << "'"
649
662
  end
650
663
 
651
664
  # For each row in the result set, yield a hash with column name symbol
@@ -28,7 +28,12 @@ module Sequel
28
28
  end
29
29
 
30
30
  module DatasetMethods
31
- SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'limit distinct columns from join where group order having compounds')
31
+ SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'select limit distinct columns from join where group order having compounds')
32
+ DATE_FORMAT = '#%Y-%m-%d#'.freeze
33
+ TIMESTAMP_FORMAT = '#%Y-%m-%d %H:%M:%S#'.freeze
34
+ TOP = " TOP ".freeze
35
+ BRACKET_CLOSE = Dataset::BRACKET_CLOSE
36
+ BRACKET_OPEN = Dataset::BRACKET_OPEN
32
37
 
33
38
  # Access doesn't support INTERSECT or EXCEPT
34
39
  def supports_intersect_except?
@@ -39,23 +44,26 @@ module Sequel
39
44
 
40
45
  # Access uses # to quote dates
41
46
  def literal_date(d)
42
- d.strftime('#%Y-%m-%d#')
47
+ d.strftime(DATE_FORMAT)
43
48
  end
44
49
 
45
50
  # Access uses # to quote datetimes
46
51
  def literal_datetime(t)
47
- t.strftime('#%Y-%m-%d %H:%M:%S#')
52
+ t.strftime(TIMESTAMP_FORMAT)
48
53
  end
49
54
  alias literal_time literal_datetime
50
55
 
51
56
  # Access uses TOP for limits
52
57
  def select_limit_sql(sql)
53
- sql << " TOP #{@opts[:limit]}" if @opts[:limit]
58
+ if l = @opts[:limit]
59
+ sql << TOP
60
+ literal_append(sql, l)
61
+ end
54
62
  end
55
63
 
56
64
  # Access uses [] for quoting identifiers
57
- def quoted_identifier(v)
58
- "[#{v}]"
65
+ def quoted_identifier_append(sql, v)
66
+ sql << BRACKET_OPEN << v.to_s << BRACKET_CLOSE
59
67
  end
60
68
 
61
69
  # Access requires the limit clause come before other clauses
@@ -175,35 +175,52 @@ module Sequel
175
175
  module DatasetMethods
176
176
  include EmulateOffsetWithRowNumber
177
177
 
178
+ PAREN_CLOSE = Dataset::PAREN_CLOSE
179
+ PAREN_OPEN = Dataset::PAREN_OPEN
178
180
  BITWISE_METHOD_MAP = {:& =>:BITAND, :| => :BITOR, :^ => :BITXOR, :'B~'=>:BITNOT}
179
181
  BOOL_TRUE = '1'.freeze
180
182
  BOOL_FALSE = '0'.freeze
181
-
183
+ CAST_STRING_OPEN = "RTRIM(CHAR(".freeze
184
+ CAST_STRING_CLOSE = "))".freeze
185
+ FETCH_FIRST_ROW_ONLY = " FETCH FIRST ROW ONLY".freeze
186
+ FETCH_FIRST = " FETCH FIRST ".freeze
187
+ ROWS_ONLY = " ROWS ONLY".freeze
188
+ EMPTY_FROM_TABLE = ' FROM "SYSIBM"."SYSDUMMY1"'.freeze
189
+
182
190
  # DB2 casts strings using RTRIM and CHAR instead of VARCHAR.
183
- def cast_sql(expr, type)
184
- type == String ? "RTRIM(CHAR(#{literal(expr)}))" : super
191
+ def cast_sql_append(sql, expr, type)
192
+ if(type == String)
193
+ sql << CAST_STRING_OPEN
194
+ literal_append(sql, expr)
195
+ sql << CAST_STRING_CLOSE
196
+ else
197
+ super
198
+ end
185
199
  end
186
200
 
187
201
  # Handle DB2 specific LIKE and bitwise operator support, and
188
202
  # emulate the extract method, which DB2 doesn't natively support.
189
- def complex_expression_sql(op, args)
203
+ def complex_expression_sql_append(sql, op, args)
190
204
  case op
191
205
  when :ILIKE
192
- super(:LIKE, [SQL::Function.new(:upper, args.at(0)), SQL::Function.new(:upper, args.at(1)) ])
206
+ super(sql, :LIKE, [SQL::Function.new(:upper, args.at(0)), SQL::Function.new(:upper, args.at(1)) ])
193
207
  when :"NOT ILIKE"
194
- super(:"NOT LIKE", [SQL::Function.new(:upper, args.at(0)), SQL::Function.new(:upper, args.at(1)) ])
208
+ super(sql, :"NOT LIKE", [SQL::Function.new(:upper, args.at(0)), SQL::Function.new(:upper, args.at(1)) ])
195
209
  when :&, :|, :^
196
210
  # works with db2 v9.5 and after
197
211
  op = BITWISE_METHOD_MAP[op]
198
- complex_expression_arg_pairs(args){|a, b| literal(SQL::Function.new(op, a, b))}
212
+ sql << complex_expression_arg_pairs(args){|a, b| literal(SQL::Function.new(op, a, b))}
199
213
  when :<<
200
- complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} * POWER(2, #{literal(b)}))"}
214
+ sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} * POWER(2, #{literal(b)}))"}
201
215
  when :>>
202
- complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} / POWER(2, #{literal(b)}))"}
216
+ sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} / POWER(2, #{literal(b)}))"}
203
217
  when :'B~'
204
- literal(SQL::Function.new(:BITNOT, *args))
218
+ literal_append(sql, SQL::Function.new(:BITNOT, *args))
205
219
  when :extract
206
- "#{args.at(0)}(#{literal(args.at(1))})"
220
+ sql << args.at(0).to_s
221
+ sql << PAREN_OPEN
222
+ literal_append(sql, args.at(1))
223
+ sql << PAREN_CLOSE
207
224
  else
208
225
  super
209
226
  end
@@ -259,7 +276,7 @@ module Sequel
259
276
 
260
277
  # Add a fallback table for empty from situation
261
278
  def select_from_sql(sql)
262
- @opts[:from] ? super : (sql << ' FROM "SYSIBM"."SYSDUMMY1"')
279
+ @opts[:from] ? super : (sql << EMPTY_FROM_TABLE)
263
280
  end
264
281
 
265
282
  # Modify the sql to limit the number of rows returned
@@ -274,7 +291,13 @@ module Sequel
274
291
  # Support for this feature is not used in this adapter however.
275
292
  def select_limit_sql(sql)
276
293
  if l = @opts[:limit]
277
- sql << " FETCH FIRST #{l == 1 ? 'ROW' : "#{literal(l)} ROWS"} ONLY"
294
+ if l == 1
295
+ sql << FETCH_FIRST_ROW_ONLY
296
+ elsif l > 1
297
+ sql << FETCH_FIRST
298
+ literal_append(sql, l)
299
+ sql << ROWS_ONLY
300
+ end
278
301
  end
279
302
  end
280
303
 
@@ -150,9 +150,10 @@ module Sequel
150
150
  BOOL_TRUE = '1'.freeze
151
151
  BOOL_FALSE = '0'.freeze
152
152
  NULL = LiteralString.new('NULL').freeze
153
- COMMA_SEPARATOR = ', '.freeze
154
- SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with distinct limit columns from join where group having compounds order')
155
- INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'into columns values returning')
153
+ SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with select distinct limit columns from join where group having compounds order')
154
+ INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'insert into columns values returning')
155
+ FIRST = " FIRST ".freeze
156
+ SKIP = " SKIP ".freeze
156
157
 
157
158
  # Insert given values into the database.
158
159
  def insert(*values)
@@ -206,8 +207,14 @@ module Sequel
206
207
  end
207
208
 
208
209
  def select_limit_sql(sql)
209
- sql << " FIRST #{@opts[:limit]}" if @opts[:limit]
210
- sql << " SKIP #{@opts[:offset]}" if @opts[:offset]
210
+ if l = @opts[:limit]
211
+ sql << FIRST
212
+ literal_append(sql, l)
213
+ end
214
+ if o = @opts[:offset]
215
+ sql << SKIP
216
+ literal_append(sql, o)
217
+ end
211
218
  end
212
219
  end
213
220
  end
@@ -23,7 +23,9 @@ module Sequel
23
23
  end
24
24
 
25
25
  module DatasetMethods
26
- SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'limit distinct columns from join where having group compounds order')
26
+ SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'select limit distinct columns from join where having group compounds order')
27
+ FIRST = " FIRST ".freeze
28
+ SKIP = " SKIP ".freeze
27
29
 
28
30
  private
29
31
 
@@ -37,8 +39,14 @@ module Sequel
37
39
  end
38
40
 
39
41
  def select_limit_sql(sql)
40
- sql << " SKIP #{@opts[:offset]}" if @opts[:offset]
41
- sql << " FIRST #{@opts[:limit]}" if @opts[:limit]
42
+ if o = @opts[:offset]
43
+ sql << SKIP
44
+ literal_append(sql, o)
45
+ end
46
+ if l = @opts[:limit]
47
+ sql << FIRST
48
+ literal_append(sql, l)
49
+ end
42
50
  end
43
51
  end
44
52
  end
@@ -249,15 +249,42 @@ module Sequel
249
249
  BOOL_TRUE = '1'.freeze
250
250
  BOOL_FALSE = '0'.freeze
251
251
  COMMA_SEPARATOR = ', '.freeze
252
- DELETE_CLAUSE_METHODS = Dataset.clause_methods(:delete, %w'with from output from2 where')
253
- INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'with into columns output values')
254
- SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with distinct limit columns into from lock join where group having order compounds')
255
- UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'with table set output from where')
252
+ DELETE_CLAUSE_METHODS = Dataset.clause_methods(:delete, %w'with delete from output from2 where')
253
+ INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'with insert into columns output values')
254
+ SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with select distinct limit columns into from lock join where group having order compounds')
255
+ UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'with update table set output from where')
256
256
  NOLOCK = ' WITH (NOLOCK)'.freeze
257
257
  UPDLOCK = ' WITH (UPDLOCK)'.freeze
258
258
  WILDCARD = LiteralString.new('*').freeze
259
259
  CONSTANT_MAP = {:CURRENT_DATE=>'CAST(CURRENT_TIMESTAMP AS DATE)'.freeze, :CURRENT_TIME=>'CAST(CURRENT_TIMESTAMP AS TIME)'.freeze}
260
260
  EXTRACT_MAP = {:year=>"yy", :month=>"m", :day=>"d", :hour=>"hh", :minute=>"n", :second=>"s"}
261
+ BRACKET_CLOSE = Dataset::BRACKET_CLOSE
262
+ BRACKET_OPEN = Dataset::BRACKET_OPEN
263
+ COMMA = Dataset::COMMA
264
+ PAREN_CLOSE = Dataset::PAREN_CLOSE
265
+ PAREN_SPACE_OPEN = Dataset::PAREN_SPACE_OPEN
266
+ SPACE = Dataset::SPACE
267
+ FROM = Dataset::FROM
268
+ APOS = Dataset::APOS
269
+ APOS_RE = Dataset::APOS_RE
270
+ DOUBLE_APOS = Dataset::DOUBLE_APOS
271
+ INTO = Dataset::INTO
272
+ DATEPART_SECOND_OPEN = "CAST((datepart(".freeze
273
+ DATEPART_SECOND_MIDDLE = ') + datepart(ns, '.freeze
274
+ DATEPART_SECOND_CLOSE = ")/1000000000.0) AS double precision)".freeze
275
+ DATEPART_OPEN = "datepart(".freeze
276
+ UNION_ALL = ' UNION ALL '.freeze
277
+ SELECT_SPACE = 'SELECT '.freeze
278
+ TIMESTAMP_USEC_FORMAT = ".%03d".freeze
279
+ OUTPUT_INSERTED = " OUTPUT INSERTED.*".freeze
280
+ HEX_START = '0x'.freeze
281
+ UNICODE_STRING_START = "N'".freeze
282
+ TOP_PAREN = " TOP (".freeze
283
+ TOP = " TOP ".freeze
284
+ OUTPUT = " OUTPUT ".freeze
285
+ HSTAR = "H*".freeze
286
+ CASE_SENSITIVE_COLLATION = 'Latin1_General_CS_AS'.freeze
287
+ CASE_INSENSITIVE_COLLATION = 'Latin1_General_CI_AS'.freeze
261
288
 
262
289
  # Allow overriding of the mssql_unicode_strings option at the dataset level.
263
290
  attr_accessor :mssql_unicode_strings
@@ -269,33 +296,41 @@ module Sequel
269
296
  end
270
297
 
271
298
  # MSSQL uses + for string concatenation, and LIKE is case insensitive by default.
272
- def complex_expression_sql(op, args)
299
+ def complex_expression_sql_append(sql, op, args)
273
300
  case op
274
301
  when :'||'
275
- super(:+, args)
302
+ super(sql, :+, args)
276
303
  when :LIKE, :"NOT LIKE"
277
- super(op, args.map{|a| LiteralString.new("(#{literal(a)} COLLATE Latin1_General_CS_AS)")})
304
+ super(sql, op, args.map{|a| LiteralString.new("(#{literal(a)} COLLATE #{CASE_SENSITIVE_COLLATION})")})
278
305
  when :ILIKE, :"NOT ILIKE"
279
- super((op == :ILIKE ? :LIKE : :"NOT LIKE"), args)
306
+ super(sql, (op == :ILIKE ? :LIKE : :"NOT LIKE"), args.map{|a| LiteralString.new("(#{literal(a)} COLLATE #{CASE_INSENSITIVE_COLLATION})")})
280
307
  when :<<
281
- complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} * POWER(2, #{literal(b)}))"}
308
+ sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} * POWER(2, #{literal(b)}))"}
282
309
  when :>>
283
- complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} / POWER(2, #{literal(b)}))"}
310
+ sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} / POWER(2, #{literal(b)}))"}
284
311
  when :extract
285
312
  part = args.at(0)
286
313
  raise(Sequel::Error, "unsupported extract argument: #{part.inspect}") unless format = EXTRACT_MAP[part]
287
- expr = literal(args.at(1))
288
- s = "datepart(#{format}, #{expr})"
289
- s = "CAST((#{s} + datepart(ns, #{expr})/1000000000.0) AS double precision)" if part == :second
290
- s
314
+ if part == :second
315
+ expr = literal(args.at(1))
316
+ sql << DATEPART_SECOND_OPEN << format.to_s << COMMA << expr << DATEPART_SECOND_MIDDLE << expr << DATEPART_SECOND_CLOSE
317
+ else
318
+ sql << DATEPART_OPEN << format.to_s << COMMA
319
+ literal_append(sql, args.at(1))
320
+ sql << PAREN_CLOSE
321
+ end
291
322
  else
292
- super(op, args)
323
+ super
293
324
  end
294
325
  end
295
326
 
296
327
  # MSSQL doesn't support the SQL standard CURRENT_DATE or CURRENT_TIME
297
- def constant_sql(constant)
298
- CONSTANT_MAP[constant] || super
328
+ def constant_sql_append(sql, constant)
329
+ if c = CONSTANT_MAP[constant]
330
+ sql << c
331
+ else
332
+ super
333
+ end
299
334
  end
300
335
 
301
336
  # Disable the use of INSERT OUTPUT
@@ -326,7 +361,16 @@ module Sequel
326
361
 
327
362
  # MSSQL uses a UNION ALL statement to insert multiple values at once.
328
363
  def multi_insert_sql(columns, values)
329
- [insert_sql(columns, LiteralString.new(values.map {|r| "SELECT #{expression_list(r)}" }.join(" UNION ALL ")))]
364
+ c = false
365
+ sql = LiteralString.new('')
366
+ u = UNION_ALL
367
+ values.each do |v|
368
+ sql << u if c
369
+ sql << SELECT_SPACE
370
+ expression_list_append(sql, v)
371
+ c ||= true
372
+ end
373
+ [insert_sql(columns, sql)]
330
374
  end
331
375
 
332
376
  # Allows you to do a dirty read of uncommitted data using WITH (NOLOCK).
@@ -365,8 +409,8 @@ module Sequel
365
409
  end
366
410
 
367
411
  # MSSQL uses [] to quote identifiers
368
- def quoted_identifier(name)
369
- "[#{name}]"
412
+ def quoted_identifier_append(sql, name)
413
+ sql << BRACKET_OPEN << name.to_s << BRACKET_CLOSE
370
414
  end
371
415
 
372
416
  # The version of the database server.
@@ -439,7 +483,8 @@ module Sequel
439
483
 
440
484
  # Only include the primary table in the main delete clause
441
485
  def delete_from_sql(sql)
442
- sql << " FROM #{source_list(@opts[:from][0..0])}"
486
+ sql << FROM
487
+ source_list_append(sql, @opts[:from][0..0])
443
488
  end
444
489
 
445
490
  # MSSQL supports FROM clauses in DELETE and UPDATE statements.
@@ -451,19 +496,11 @@ module Sequel
451
496
  end
452
497
  alias update_from_sql delete_from2_sql
453
498
 
454
- # Handle the with clause for delete, insert, and update statements
455
- # to be the same as the insert statement.
456
- def delete_with_sql(sql)
457
- select_with_sql(sql)
458
- end
459
- alias insert_with_sql delete_with_sql
460
- alias update_with_sql delete_with_sql
461
-
462
499
  # MSSQL raises an error if you try to provide more than 3 decimal places
463
500
  # for a fractional timestamp. This probably doesn't work for smalldatetime
464
501
  # fields.
465
502
  def format_timestamp_usec(usec)
466
- sprintf(".%03d", usec/1000)
503
+ sprintf(TIMESTAMP_USEC_FORMAT, usec/1000)
467
504
  end
468
505
 
469
506
  # MSSQL supports the OUTPUT clause for INSERT statements.
@@ -476,23 +513,22 @@ module Sequel
476
513
  # for use with the prepared statement code.
477
514
  def insert_output_sql(sql)
478
515
  if @opts.has_key?(:returning)
479
- sql << " OUTPUT INSERTED.*"
516
+ sql << OUTPUT_INSERTED
480
517
  else
481
518
  output_sql(sql)
482
519
  end
483
520
  end
484
521
 
485
522
  # MSSQL uses a literal hexidecimal number for blob strings
486
- def literal_blob(v)
487
- blob = '0x'
488
- v.each_byte{|x| blob << sprintf('%02x', x)}
489
- blob
523
+ def literal_blob_append(sql, v)
524
+ sql << HEX_START << v.unpack(HSTAR).first
490
525
  end
491
526
 
492
527
  # Optionally use unicode string syntax for all strings. Don't double
493
528
  # backslashes.
494
- def literal_string(v)
495
- "#{'N' if mssql_unicode_strings}'#{v.gsub(/'/, "''")}'"
529
+ def literal_string_append(sql, v)
530
+ sql << (mssql_unicode_strings ? UNICODE_STRING_START : APOS)
531
+ sql << v.gsub(APOS_RE, DOUBLE_APOS) << APOS
496
532
  end
497
533
 
498
534
  # Use 0 for false on MSSQL
@@ -511,16 +547,24 @@ module Sequel
511
547
  end
512
548
 
513
549
  def select_into_sql(sql)
514
- sql << " INTO #{table_ref(@opts[:into])}" if @opts[:into]
550
+ if i = @opts[:into]
551
+ sql << INTO
552
+ table_ref_append(sql, i)
553
+ end
515
554
  end
516
555
 
517
556
  # MSSQL uses TOP N for limit. For MSSQL 2005+ TOP (N) is used
518
557
  # to allow the limit to be a bound variable.
519
558
  def select_limit_sql(sql)
520
559
  if l = @opts[:limit]
521
- l = literal(l)
522
- l = "(#{l})" if server_version >= 9000000
523
- sql << " TOP #{l}"
560
+ if is_2005_or_later?
561
+ sql << TOP_PAREN
562
+ literal_append(sql, l)
563
+ sql << PAREN_CLOSE
564
+ else
565
+ sql << TOP
566
+ literal_append(sql, l)
567
+ end
524
568
  end
525
569
  end
526
570
 
@@ -540,13 +584,15 @@ module Sequel
540
584
  def output_sql(sql)
541
585
  return unless supports_output_clause?
542
586
  return unless output = @opts[:output]
543
- sql << " OUTPUT #{column_list(output[:select_list])}"
587
+ sql << OUTPUT
588
+ column_list_append(sql, output[:select_list])
544
589
  if into = output[:into]
545
- sql << " INTO #{table_ref(into)}"
590
+ sql << INTO
591
+ table_ref_append(sql, into)
546
592
  if column_list = output[:column_list]
547
- cl = []
548
- column_list.each { |k, v| cl << literal(String === k ? k.to_sym : k) }
549
- sql << " (#{cl.join(COMMA_SEPARATOR)})"
593
+ sql << PAREN_SPACE_OPEN
594
+ source_list_append(sql, column_list)
595
+ sql << PAREN_CLOSE
550
596
  end
551
597
  end
552
598
  end
@@ -561,7 +607,8 @@ module Sequel
561
607
 
562
608
  # Only include the primary table in the main update clause
563
609
  def update_table_sql(sql)
564
- sql << " #{source_list(@opts[:from][0..0])}"
610
+ sql << SPACE
611
+ source_list_append(sql, @opts[:from][0..0])
565
612
  end
566
613
  end
567
614
  end