sequel 3.5.0 → 3.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. data/CHANGELOG +108 -0
  2. data/README.rdoc +25 -14
  3. data/Rakefile +20 -1
  4. data/doc/advanced_associations.rdoc +61 -64
  5. data/doc/cheat_sheet.rdoc +16 -7
  6. data/doc/opening_databases.rdoc +3 -3
  7. data/doc/prepared_statements.rdoc +1 -1
  8. data/doc/reflection.rdoc +2 -1
  9. data/doc/release_notes/3.6.0.txt +366 -0
  10. data/doc/schema.rdoc +19 -14
  11. data/lib/sequel/adapters/amalgalite.rb +5 -27
  12. data/lib/sequel/adapters/jdbc.rb +13 -3
  13. data/lib/sequel/adapters/jdbc/h2.rb +17 -0
  14. data/lib/sequel/adapters/jdbc/mysql.rb +20 -7
  15. data/lib/sequel/adapters/mysql.rb +4 -3
  16. data/lib/sequel/adapters/oracle.rb +1 -1
  17. data/lib/sequel/adapters/postgres.rb +87 -28
  18. data/lib/sequel/adapters/shared/mssql.rb +47 -6
  19. data/lib/sequel/adapters/shared/mysql.rb +12 -31
  20. data/lib/sequel/adapters/shared/postgres.rb +15 -12
  21. data/lib/sequel/adapters/shared/sqlite.rb +18 -0
  22. data/lib/sequel/adapters/sqlite.rb +1 -16
  23. data/lib/sequel/connection_pool.rb +1 -1
  24. data/lib/sequel/core.rb +1 -1
  25. data/lib/sequel/database.rb +1 -1
  26. data/lib/sequel/database/schema_generator.rb +2 -0
  27. data/lib/sequel/database/schema_sql.rb +1 -1
  28. data/lib/sequel/dataset.rb +5 -179
  29. data/lib/sequel/dataset/actions.rb +123 -0
  30. data/lib/sequel/dataset/convenience.rb +18 -10
  31. data/lib/sequel/dataset/features.rb +65 -0
  32. data/lib/sequel/dataset/prepared_statements.rb +29 -23
  33. data/lib/sequel/dataset/query.rb +429 -0
  34. data/lib/sequel/dataset/sql.rb +67 -435
  35. data/lib/sequel/model/associations.rb +77 -13
  36. data/lib/sequel/model/base.rb +30 -8
  37. data/lib/sequel/model/errors.rb +4 -4
  38. data/lib/sequel/plugins/caching.rb +38 -15
  39. data/lib/sequel/plugins/force_encoding.rb +18 -7
  40. data/lib/sequel/plugins/hook_class_methods.rb +4 -0
  41. data/lib/sequel/plugins/many_through_many.rb +1 -1
  42. data/lib/sequel/plugins/nested_attributes.rb +40 -11
  43. data/lib/sequel/plugins/serialization.rb +17 -3
  44. data/lib/sequel/plugins/validation_helpers.rb +65 -18
  45. data/lib/sequel/sql.rb +23 -1
  46. data/lib/sequel/version.rb +1 -1
  47. data/spec/adapters/mssql_spec.rb +96 -10
  48. data/spec/adapters/mysql_spec.rb +19 -0
  49. data/spec/adapters/postgres_spec.rb +65 -2
  50. data/spec/adapters/sqlite_spec.rb +10 -0
  51. data/spec/core/core_sql_spec.rb +9 -0
  52. data/spec/core/database_spec.rb +8 -4
  53. data/spec/core/dataset_spec.rb +122 -29
  54. data/spec/core/expression_filters_spec.rb +17 -0
  55. data/spec/extensions/caching_spec.rb +43 -3
  56. data/spec/extensions/force_encoding_spec.rb +43 -1
  57. data/spec/extensions/nested_attributes_spec.rb +55 -2
  58. data/spec/extensions/validation_helpers_spec.rb +71 -0
  59. data/spec/integration/associations_test.rb +281 -0
  60. data/spec/integration/dataset_test.rb +383 -9
  61. data/spec/integration/eager_loader_test.rb +0 -65
  62. data/spec/integration/model_test.rb +110 -0
  63. data/spec/integration/plugin_test.rb +306 -0
  64. data/spec/integration/prepared_statement_test.rb +32 -0
  65. data/spec/integration/schema_test.rb +8 -3
  66. data/spec/integration/spec_helper.rb +1 -25
  67. data/spec/model/association_reflection_spec.rb +38 -0
  68. data/spec/model/associations_spec.rb +184 -8
  69. data/spec/model/eager_loading_spec.rb +23 -0
  70. data/spec/model/model_spec.rb +8 -0
  71. data/spec/model/record_spec.rb +84 -1
  72. metadata +9 -2
@@ -11,21 +11,26 @@ Migrations are not required, you can just call the schema modification methods d
11
11
  Sequel has the ability to have database independent migrations using ruby classes as types. When you use a ruby class as a type, Sequel translates it to the most comparable type in the database you are using. Here's an example using all supported types:
12
12
 
13
13
  DB.create_table(:cats) do
14
- primary_key :id, :type=>Integer # integer
15
- String :a # varchar(255)
16
- column :b, File # blob
17
- Fixnum :c # integer
14
+ primary_key :id, :type=>Integer # integer
15
+ String :a # varchar(255)
16
+ String :a2, :size=>50 # varchar(50)
17
+ String :a3, :fixed=>true # char(255)
18
+ String :a4, :fixed=>true, :size=>50 # char(50)
19
+ String :a5, :text=>true # text
20
+ column :b, File # blob
21
+ Fixnum :c # integer
18
22
  foreign_key :d, :other_table, :type=>Bignum # bigint
19
- Float :e # double precision
20
- BigDecimal :f # numeric
21
- Date :g # date
22
- DateTime :h # timestamp
23
- Time :i # timestamp
24
- Numeric :j # numeric
25
- TrueClass :k # boolean
26
- FalseClass :l # boolean
23
+ Float :e # double precision
24
+ BigDecimal :f # numeric
25
+ BigDecimal :f2, :size=>10 # numeric(10)
26
+ BigDecimal :f3, :size=>[10, 2] # numeric(10, 2)
27
+ Date :g # date
28
+ DateTime :h # timestamp
29
+ Time :i # timestamp
30
+ Time :i2, :only_time=>true # time
31
+ Numeric :j # numeric
32
+ TrueClass :k # boolean
33
+ FalseClass :l # boolean
27
34
  end
28
35
 
29
36
  Basically, if you use one of the ruby classes above, it will translate into a database specific type. If you use a lowercase method, symbol, or string to specify the type, Sequel won't attempt to translate it.
30
-
31
- Note that if you use a ruby class, you shouldn't use a :size option. Using a ruby class means that you want Sequel to pick the database type to use. If you want to specify the size, you are no longer in database independent territory, and you need to specify the type as well, using a lowercase method, symbol, or string.
@@ -82,9 +82,9 @@ module Sequel
82
82
  Amalgalite::Dataset.new(self, opts)
83
83
  end
84
84
 
85
- # Run the given SQL with the given arguments and reload the schema. Returns nil.
85
+ # Run the given SQL with the given arguments. Returns nil.
86
86
  def execute_ddl(sql, opts={})
87
- _execute(sql, opts){|conn| conn.execute_batch(sql); conn.reload_schema!}
87
+ _execute(sql, opts){|conn| conn.execute_batch(sql);}
88
88
  nil
89
89
  end
90
90
 
@@ -99,19 +99,8 @@ module Sequel
99
99
  end
100
100
 
101
101
  # Run the given SQL with the given arguments and yield each row.
102
- def execute(sql, opts={})
103
- retried = false
104
- _execute(sql, opts) do |conn|
105
- conn.prepare(sql) do |stmt|
106
- begin
107
- stmt.result_meta
108
- rescue NoMethodError
109
- conn.reload_schema!
110
- stmt.result_meta
111
- end
112
- yield stmt
113
- end
114
- end
102
+ def execute(sql, opts={}, &block)
103
+ _execute(sql, opts){|conn| conn.prepare(sql, &block)}
115
104
  end
116
105
 
117
106
  # Run the given SQL with the given arguments and return the first value of the first row.
@@ -158,20 +147,9 @@ module Sequel
158
147
  class Dataset < Sequel::Dataset
159
148
  include ::Sequel::SQLite::DatasetMethods
160
149
 
161
- EXPLAIN = 'EXPLAIN %s'.freeze
162
-
163
- # Return an array of strings specifying a query explanation for the
164
- # current dataset.
165
- def explain
166
- res = []
167
- @db.result_set(EXPLAIN % select_sql(opts), nil) {|r| res << r}
168
- res
169
- end
170
-
171
150
  # Yield a hash for each row in the dataset.
172
151
  def fetch_rows(sql)
173
152
  execute(sql) do |stmt|
174
- stmt.result_meta
175
153
  @columns = cols = stmt.result_fields.map{|c| output_identifier(c)}
176
154
  col_count = cols.size
177
155
  stmt.each do |result|
@@ -186,7 +164,7 @@ module Sequel
186
164
 
187
165
  # Quote the string using the adapter instance method.
188
166
  def literal_string(v)
189
- "#{db.synchronize{|c| c.quote(v)}}"
167
+ db.synchronize{|c| c.quote(v)}
190
168
  end
191
169
  end
192
170
  end
@@ -167,7 +167,7 @@ module Sequel
167
167
  stmt.execute(sql)
168
168
  when :insert
169
169
  stmt.executeUpdate(sql)
170
- last_insert_id(conn, opts)
170
+ last_insert_id(conn, opts.merge(:stmt=>stmt))
171
171
  else
172
172
  stmt.executeUpdate(sql)
173
173
  end
@@ -303,6 +303,14 @@ module Sequel
303
303
  end
304
304
  end
305
305
 
306
+ # Support fractional seconds for Time objects used in bound variables
307
+ def java_sql_timestamp(time)
308
+ millis = time.to_i * 1000
309
+ ts = java.sql.Timestamp.new(millis)
310
+ ts.setNanos(time.usec * 1000)
311
+ ts
312
+ end
313
+
306
314
  # By default, there is no support for determining the last inserted
307
315
  # id, so return nil. This method should be overridden in
308
316
  # sub adapters.
@@ -335,8 +343,10 @@ module Sequel
335
343
  cps.setString(i, arg)
336
344
  when Date, Java::JavaSql::Date
337
345
  cps.setDate(i, arg)
338
- when Time, DateTime, Java::JavaSql::Timestamp
346
+ when DateTime, Java::JavaSql::Timestamp
339
347
  cps.setTimestamp(i, arg)
348
+ when Time
349
+ cps.setTimestamp(i, java_sql_timestamp(arg))
340
350
  when Float
341
351
  cps.setDouble(i, arg)
342
352
  when TrueClass, FalseClass
@@ -455,7 +465,7 @@ module Sequel
455
465
 
456
466
  # Create a named prepared statement that is stored in the
457
467
  # database (and connection) for reuse.
458
- def prepare(type, name=nil, values=nil)
468
+ def prepare(type, name=nil, *values)
459
469
  ps = to_prepared_statement(type, values)
460
470
  ps.extend(PreparedStatementMethods)
461
471
  if name
@@ -76,6 +76,18 @@ module Sequel
76
76
  class Dataset < JDBC::Dataset
77
77
  SELECT_CLAUSE_METHODS = clause_methods(:select, %w'distinct columns from join where group having compounds order limit')
78
78
 
79
+ # Work around H2's lack of a case insensitive LIKE operator
80
+ def complex_expression_sql(op, args)
81
+ case op
82
+ when :ILIKE
83
+ super(:LIKE, [SQL::PlaceholderLiteralString.new("CAST(? AS VARCHAR_IGNORECASE)", [args.at(0)]), args.at(1)])
84
+ when :"NOT ILIKE"
85
+ super(:"NOT LIKE", [SQL::PlaceholderLiteralString.new("CAST(? AS VARCHAR_IGNORECASE)", [args.at(0)]), args.at(1)])
86
+ else
87
+ super(op, args)
88
+ end
89
+ end
90
+
79
91
  # H2 requires SQL standard datetimes
80
92
  def requires_sql_standard_datetimes?
81
93
  true
@@ -86,6 +98,11 @@ module Sequel
86
98
  false
87
99
  end
88
100
 
101
+ # H2 doesn't support JOIN USING
102
+ def supports_join_using?
103
+ false
104
+ end
105
+
89
106
  private
90
107
 
91
108
  def select_clause_methods
@@ -26,13 +26,26 @@ module Sequel
26
26
 
27
27
  # Get the last inserted id using LAST_INSERT_ID().
28
28
  def last_insert_id(conn, opts={})
29
- stmt = conn.createStatement
30
- begin
31
- rs = stmt.executeQuery('SELECT LAST_INSERT_ID()')
32
- rs.next
33
- rs.getInt(1)
34
- ensure
35
- stmt.close
29
+ if stmt = opts[:stmt]
30
+ rs = stmt.getGeneratedKeys
31
+ begin
32
+ if rs.next
33
+ rs.getInt(1)
34
+ else
35
+ 0
36
+ end
37
+ ensure
38
+ rs.close
39
+ end
40
+ else
41
+ stmt = conn.createStatement
42
+ begin
43
+ rs = stmt.executeQuery('SELECT LAST_INSERT_ID()')
44
+ rs.next
45
+ rs.getInt(1)
46
+ ensure
47
+ stmt.close
48
+ end
36
49
  end
37
50
  end
38
51
  end
@@ -257,6 +257,7 @@ module Sequel
257
257
  def subselect_sql(ds)
258
258
  ps = ds.to_prepared_statement(:select)
259
259
  ps.extend(CallableStatementMethods)
260
+ ps = ps.bind(@opts[:bind_vars]) if @opts[:bind_vars]
260
261
  ps.prepared_args = prepared_args
261
262
  ps.prepared_sql
262
263
  end
@@ -302,10 +303,10 @@ module Sequel
302
303
  # breaks the use of subselects in prepared statements, so extend the
303
304
  # temporary prepared statement that this creates with a module that
304
305
  # fixes it.
305
- def call(type, bind_arguments={}, values=nil)
306
+ def call(type, bind_arguments={}, *values, &block)
306
307
  ps = to_prepared_statement(type, values)
307
308
  ps.extend(CallableStatementMethods)
308
- ps.call(bind_arguments)
309
+ ps.call(bind_arguments, &block)
309
310
  end
310
311
 
311
312
  # Delete rows matching this dataset
@@ -345,7 +346,7 @@ module Sequel
345
346
 
346
347
  # Store the given type of prepared statement in the associated database
347
348
  # with the given name.
348
- def prepare(type, name=nil, values=nil)
349
+ def prepare(type, name=nil, *values)
349
350
  ps = to_prepared_statement(type, values)
350
351
  ps.extend(PreparedStatementMethods)
351
352
  if name
@@ -91,7 +91,7 @@ module Sequel
91
91
  end
92
92
 
93
93
  def remove_transaction(conn)
94
- conn.autocommit = true
94
+ conn.autocommit = true if conn
95
95
  super
96
96
  end
97
97
 
@@ -84,6 +84,7 @@ rescue LoadError => e
84
84
  end
85
85
 
86
86
  module Sequel
87
+ Dataset::NON_SQL_OPTIONS << :cursor
87
88
  module Postgres
88
89
  CONVERTED_EXCEPTIONS << PGError
89
90
 
@@ -304,22 +305,28 @@ module Sequel
304
305
 
305
306
  # Yield all rows returned by executing the given SQL and converting
306
307
  # the types.
307
- def fetch_rows(sql)
308
- cols = []
309
- execute(sql) do |res|
310
- res.nfields.times do |fieldnum|
311
- cols << [fieldnum, PG_TYPES[res.ftype(fieldnum)], output_identifier(res.fname(fieldnum))]
312
- end
313
- @columns = cols.map{|c| c.at(2)}
314
- res.ntuples.times do |recnum|
315
- converted_rec = {}
316
- cols.each do |fieldnum, type_proc, fieldsym|
317
- value = res.getvalue(recnum, fieldnum)
318
- converted_rec[fieldsym] = (value && type_proc) ? type_proc.call(value) : value
319
- end
320
- yield converted_rec
321
- end
322
- end
308
+ def fetch_rows(sql, &block)
309
+ return cursor_fetch_rows(sql, &block) if @opts[:cursor]
310
+ execute(sql){|res| yield_hash_rows(res, fetch_rows_set_cols(res), &block)}
311
+ end
312
+
313
+ # Uses a cursor for fetching records, instead of fetching the entire result
314
+ # set at once. Can be used to process large datasets without holding
315
+ # all rows in memory (which is what the underlying drivers do
316
+ # by default). Options:
317
+ #
318
+ # * :rows_per_fetch - the number of rows per fetch (default 1000). Higher
319
+ # numbers result in fewer queries but greater memory use.
320
+ #
321
+ # Usage:
322
+ #
323
+ # DB[:huge_table].use_cursor.each{|row| p row}
324
+ # DB[:huge_table].use_cursor(:rows_per_fetch=>10000).each{|row| p row}
325
+ #
326
+ # This is untested with the prepared statement/bound variable support,
327
+ # and unlikely to work with either.
328
+ def use_cursor(opts={})
329
+ clone(:cursor=>{:rows_per_fetch=>1000}.merge(opts))
323
330
  end
324
331
 
325
332
  if SEQUEL_POSTGRES_USES_PG
@@ -334,10 +341,9 @@ module Sequel
334
341
 
335
342
  protected
336
343
 
337
- # Return an array of strings for each of the hash values, inserting
338
- # them to the correct position in the array.
344
+ # An array of bound variable values for this query, in the correct order.
339
345
  def map_to_prepared_args(hash)
340
- @prepared_args.map{|k| hash[k.to_sym].to_s}
346
+ prepared_args.map{|k| hash[k.to_sym]}
341
347
  end
342
348
 
343
349
  private
@@ -349,11 +355,11 @@ module Sequel
349
355
  # elminate ambiguity (and PostgreSQL from raising an exception).
350
356
  def prepared_arg(k)
351
357
  y, type = k.to_s.split("__")
352
- if i = @prepared_args.index(y)
358
+ if i = prepared_args.index(y)
353
359
  i += 1
354
360
  else
355
- @prepared_args << y
356
- i = @prepared_args.length
361
+ prepared_args << y
362
+ i = prepared_args.length
357
363
  end
358
364
  LiteralString.new("#{prepared_arg_placeholder}#{i}#{"::#{type}" if type}")
359
365
  end
@@ -407,15 +413,15 @@ module Sequel
407
413
  end
408
414
 
409
415
  # Execute the given type of statement with the hash of values.
410
- def call(type, hash, values=nil, &block)
416
+ def call(type, bind_vars={}, *values, &block)
411
417
  ps = to_prepared_statement(type, values)
412
418
  ps.extend(BindArgumentMethods)
413
- ps.call(hash, &block)
419
+ ps.call(bind_vars, &block)
414
420
  end
415
421
 
416
422
  # Prepare the given type of statement with the given name, and store
417
423
  # it in the database to be called later.
418
- def prepare(type, name=nil, values=nil)
424
+ def prepare(type, name=nil, *values)
419
425
  ps = to_prepared_statement(type, values)
420
426
  ps.extend(PreparedStatementMethods)
421
427
  if name
@@ -435,15 +441,68 @@ module Sequel
435
441
  end
436
442
 
437
443
  private
438
-
444
+
445
+ # Use a cursor to fetch groups of records at a time, yielding them to the block.
446
+ def cursor_fetch_rows(sql, &block)
447
+ server_opts = {:server=>@opts[:server] || :read_only}
448
+ db.transaction(server_opts) do
449
+ begin
450
+ execute_ddl("DECLARE sequel_cursor NO SCROLL CURSOR WITHOUT HOLD FOR #{sql}", server_opts)
451
+ rows_per_fetch = @opts[:cursor][:rows_per_fetch].to_i
452
+ rows_per_fetch = 1000 if rows_per_fetch <= 0
453
+ fetch_sql = "FETCH FORWARD #{rows_per_fetch} FROM sequel_cursor"
454
+ cols = nil
455
+ # Load columns only in the first fetch, so subsequent fetches are faster
456
+ execute(fetch_sql) do |res|
457
+ cols = fetch_rows_set_cols(res)
458
+ yield_hash_rows(res, cols, &block)
459
+ return if res.ntuples < rows_per_fetch
460
+ end
461
+ loop do
462
+ execute(fetch_sql) do |res|
463
+ yield_hash_rows(res, cols, &block)
464
+ return if res.ntuples < rows_per_fetch
465
+ end
466
+ end
467
+ ensure
468
+ execute_ddl("CLOSE sequel_cursor", server_opts)
469
+ end
470
+ end
471
+ end
472
+
473
+ # Set the @columns based on the result set, and return the array of
474
+ # field numers, type conversion procs, and name symbol arrays.
475
+ def fetch_rows_set_cols(res)
476
+ cols = []
477
+ res.nfields.times do |fieldnum|
478
+ cols << [fieldnum, PG_TYPES[res.ftype(fieldnum)], output_identifier(res.fname(fieldnum))]
479
+ end
480
+ @columns = cols.map{|c| c.at(2)}
481
+ cols
482
+ end
483
+
484
+ # Use the driver's escape_bytea
439
485
  def literal_blob(v)
440
486
  db.synchronize{|c| "'#{c.escape_bytea(v)}'"}
441
487
  end
442
-
488
+
489
+ # Use the driver's escape_string
443
490
  def literal_string(v)
444
491
  db.synchronize{|c| "'#{c.escape_string(v)}'"}
445
492
  end
446
-
493
+
494
+ # For each row in the result set, yield a hash with column name symbol
495
+ # keys and typecasted values.
496
+ def yield_hash_rows(res, cols)
497
+ res.ntuples.times do |recnum|
498
+ converted_rec = {}
499
+ cols.each do |fieldnum, type_proc, fieldsym|
500
+ value = res.getvalue(recnum, fieldnum)
501
+ converted_rec[fieldsym] = (value && type_proc) ? type_proc.call(value) : value
502
+ end
503
+ yield converted_rec
504
+ end
505
+ end
447
506
  end
448
507
  end
449
508
  end
@@ -1,4 +1,5 @@
1
1
  module Sequel
2
+ Dataset::NON_SQL_OPTIONS << :disable_insert_output
2
3
  module MSSQL
3
4
  module DatabaseMethods
4
5
  AUTO_INCREMENT = 'IDENTITY(1,1)'.freeze
@@ -6,7 +7,7 @@ module Sequel
6
7
  SERVER_VERSION_SQL = "SELECT CAST(SERVERPROPERTY('ProductVersion') AS varchar)".freeze
7
8
  SQL_BEGIN = "BEGIN TRANSACTION".freeze
8
9
  SQL_COMMIT = "COMMIT TRANSACTION".freeze
9
- SQL_ROLLBACK = "ROLLBACK TRANSACTION".freeze
10
+ SQL_ROLLBACK = "IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION".freeze
10
11
  SQL_ROLLBACK_TO_SAVEPOINT = 'ROLLBACK TRANSACTION autopoint_%d'.freeze
11
12
  SQL_SAVEPOINT = 'SAVE TRANSACTION autopoint_%d'.freeze
12
13
  TEMPORARY = "#".freeze
@@ -127,7 +128,7 @@ module Sequel
127
128
  select(:column_name___column, :data_type___db_type, :character_maximum_length___max_chars, :column_default___default, :is_nullable___allow_null).
128
129
  filter(:c__table_name=>m2.call(table_name.to_s))
129
130
  if schema = opts[:schema] || default_schema
130
- ds.filter!(:table_schema=>schema)
131
+ ds.filter!(:c__table_schema=>schema)
131
132
  end
132
133
  ds.map do |row|
133
134
  row[:allow_null] = row[:allow_null] == 'YES' ? true : false
@@ -163,6 +164,17 @@ module Sequel
163
164
  def type_literal_generic_file(column)
164
165
  :image
165
166
  end
167
+
168
+ # support for clustered index type
169
+ def index_definition_sql(table_name, index)
170
+ index_name = index[:name] || default_index_name(table_name, index[:columns])
171
+ clustered = index[:type] == :clustered
172
+ if index[:where]
173
+ raise Error, "Partial indexes are not supported for this database"
174
+ else
175
+ "CREATE #{'UNIQUE ' if index[:unique]}#{'CLUSTERED ' if clustered}INDEX #{quote_identifier(index_name)} ON #{quote_schema_table(table_name)} #{literal(index[:columns])}"
176
+ end
177
+ end
166
178
  end
167
179
 
168
180
  module DatasetMethods
@@ -176,11 +188,15 @@ module Sequel
176
188
  WILDCARD = LiteralString.new('*').freeze
177
189
  CONSTANT_MAP = {:CURRENT_DATE=>'CAST(CURRENT_TIMESTAMP AS DATE)'.freeze, :CURRENT_TIME=>'CAST(CURRENT_TIMESTAMP AS TIME)'.freeze}
178
190
 
179
- # MSSQL uses + for string concatenation
191
+ # MSSQL uses + for string concatenation, and LIKE is case insensitive by default.
180
192
  def complex_expression_sql(op, args)
181
193
  case op
182
194
  when :'||'
183
195
  super(:+, args)
196
+ when :ILIKE
197
+ super(:LIKE, args)
198
+ when :"NOT ILIKE"
199
+ super(:"NOT LIKE", args)
184
200
  else
185
201
  super(op, args)
186
202
  end
@@ -191,6 +207,16 @@ module Sequel
191
207
  CONSTANT_MAP[constant] || super
192
208
  end
193
209
 
210
+ # Disable the use of INSERT OUTPUT
211
+ def disable_insert_output
212
+ clone(:disable_insert_output=>true)
213
+ end
214
+
215
+ # Disable the use of INSERT OUTPUT, modifying the receiver
216
+ def disable_insert_output!
217
+ mutation_method(:disable_insert_output)
218
+ end
219
+
194
220
  # When returning all rows, if an offset is used, delete the row_number column
195
221
  # before yielding the row.
196
222
  def fetch_rows(sql, &block)
@@ -201,7 +227,12 @@ module Sequel
201
227
  def full_text_search(cols, terms, opts = {})
202
228
  filter("CONTAINS (#{literal(cols)}, #{literal(terms)})")
203
229
  end
204
-
230
+
231
+ # Use the OUTPUT clause to get the value of all columns for the newly inserted record.
232
+ def insert_select(*values)
233
+ naked.clone(default_server_opts(:sql=>output(nil, [:inserted.*]).insert_sql(*values))).single_record unless opts[:disable_insert_output]
234
+ end
235
+
205
236
  # MSSQL uses a UNION ALL statement to insert multiple values at once.
206
237
  def multi_insert_sql(columns, values)
207
238
  [insert_sql(columns, LiteralString.new(values.map {|r| "SELECT #{expression_list(r)}" }.join(" UNION ALL ")))]
@@ -227,9 +258,9 @@ module Sequel
227
258
  def output(into, values)
228
259
  output = {}
229
260
  case values
230
- when Hash:
261
+ when Hash
231
262
  output[:column_list], output[:select_list] = values.keys, values.values
232
- when Array:
263
+ when Array
233
264
  output[:select_list] = values
234
265
  end
235
266
  output[:into] = into
@@ -286,6 +317,16 @@ module Sequel
286
317
  def supports_is_true?
287
318
  false
288
319
  end
320
+
321
+ # MSSQL doesn't support JOIN USING
322
+ def supports_join_using?
323
+ false
324
+ end
325
+
326
+ # MSSQL does not support multiple columns for the IN/NOT IN operators
327
+ def supports_multiple_column_in?
328
+ false
329
+ end
289
330
 
290
331
  # MSSQL 2005+ supports window functions
291
332
  def supports_window_functions?