sequel 3.5.0 → 3.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +108 -0
- data/README.rdoc +25 -14
- data/Rakefile +20 -1
- data/doc/advanced_associations.rdoc +61 -64
- data/doc/cheat_sheet.rdoc +16 -7
- data/doc/opening_databases.rdoc +3 -3
- data/doc/prepared_statements.rdoc +1 -1
- data/doc/reflection.rdoc +2 -1
- data/doc/release_notes/3.6.0.txt +366 -0
- data/doc/schema.rdoc +19 -14
- data/lib/sequel/adapters/amalgalite.rb +5 -27
- data/lib/sequel/adapters/jdbc.rb +13 -3
- data/lib/sequel/adapters/jdbc/h2.rb +17 -0
- data/lib/sequel/adapters/jdbc/mysql.rb +20 -7
- data/lib/sequel/adapters/mysql.rb +4 -3
- data/lib/sequel/adapters/oracle.rb +1 -1
- data/lib/sequel/adapters/postgres.rb +87 -28
- data/lib/sequel/adapters/shared/mssql.rb +47 -6
- data/lib/sequel/adapters/shared/mysql.rb +12 -31
- data/lib/sequel/adapters/shared/postgres.rb +15 -12
- data/lib/sequel/adapters/shared/sqlite.rb +18 -0
- data/lib/sequel/adapters/sqlite.rb +1 -16
- data/lib/sequel/connection_pool.rb +1 -1
- data/lib/sequel/core.rb +1 -1
- data/lib/sequel/database.rb +1 -1
- data/lib/sequel/database/schema_generator.rb +2 -0
- data/lib/sequel/database/schema_sql.rb +1 -1
- data/lib/sequel/dataset.rb +5 -179
- data/lib/sequel/dataset/actions.rb +123 -0
- data/lib/sequel/dataset/convenience.rb +18 -10
- data/lib/sequel/dataset/features.rb +65 -0
- data/lib/sequel/dataset/prepared_statements.rb +29 -23
- data/lib/sequel/dataset/query.rb +429 -0
- data/lib/sequel/dataset/sql.rb +67 -435
- data/lib/sequel/model/associations.rb +77 -13
- data/lib/sequel/model/base.rb +30 -8
- data/lib/sequel/model/errors.rb +4 -4
- data/lib/sequel/plugins/caching.rb +38 -15
- data/lib/sequel/plugins/force_encoding.rb +18 -7
- data/lib/sequel/plugins/hook_class_methods.rb +4 -0
- data/lib/sequel/plugins/many_through_many.rb +1 -1
- data/lib/sequel/plugins/nested_attributes.rb +40 -11
- data/lib/sequel/plugins/serialization.rb +17 -3
- data/lib/sequel/plugins/validation_helpers.rb +65 -18
- data/lib/sequel/sql.rb +23 -1
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +96 -10
- data/spec/adapters/mysql_spec.rb +19 -0
- data/spec/adapters/postgres_spec.rb +65 -2
- data/spec/adapters/sqlite_spec.rb +10 -0
- data/spec/core/core_sql_spec.rb +9 -0
- data/spec/core/database_spec.rb +8 -4
- data/spec/core/dataset_spec.rb +122 -29
- data/spec/core/expression_filters_spec.rb +17 -0
- data/spec/extensions/caching_spec.rb +43 -3
- data/spec/extensions/force_encoding_spec.rb +43 -1
- data/spec/extensions/nested_attributes_spec.rb +55 -2
- data/spec/extensions/validation_helpers_spec.rb +71 -0
- data/spec/integration/associations_test.rb +281 -0
- data/spec/integration/dataset_test.rb +383 -9
- data/spec/integration/eager_loader_test.rb +0 -65
- data/spec/integration/model_test.rb +110 -0
- data/spec/integration/plugin_test.rb +306 -0
- data/spec/integration/prepared_statement_test.rb +32 -0
- data/spec/integration/schema_test.rb +8 -3
- data/spec/integration/spec_helper.rb +1 -25
- data/spec/model/association_reflection_spec.rb +38 -0
- data/spec/model/associations_spec.rb +184 -8
- data/spec/model/eager_loading_spec.rb +23 -0
- data/spec/model/model_spec.rb +8 -0
- data/spec/model/record_spec.rb +84 -1
- metadata +9 -2
data/doc/schema.rdoc
CHANGED
@@ -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
|
15
|
-
String :a
|
16
|
-
|
17
|
-
|
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
|
20
|
-
BigDecimal :f
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
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);
|
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
|
-
|
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
|
-
|
167
|
+
db.synchronize{|c| c.quote(v)}
|
190
168
|
end
|
191
169
|
end
|
192
170
|
end
|
data/lib/sequel/adapters/jdbc.rb
CHANGED
@@ -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
|
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
|
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 =
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
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
|
349
|
+
def prepare(type, name=nil, *values)
|
349
350
|
ps = to_prepared_statement(type, values)
|
350
351
|
ps.extend(PreparedStatementMethods)
|
351
352
|
if name
|
@@ -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
|
-
|
309
|
-
execute(sql)
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
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
|
-
#
|
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
|
-
|
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 =
|
358
|
+
if i = prepared_args.index(y)
|
353
359
|
i += 1
|
354
360
|
else
|
355
|
-
|
356
|
-
i =
|
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,
|
416
|
+
def call(type, bind_vars={}, *values, &block)
|
411
417
|
ps = to_prepared_statement(type, values)
|
412
418
|
ps.extend(BindArgumentMethods)
|
413
|
-
ps.call(
|
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
|
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!(:
|
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?
|