sequel 3.2.0 → 3.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +40 -0
- data/Rakefile +1 -1
- data/doc/opening_databases.rdoc +7 -0
- data/doc/release_notes/3.3.0.txt +192 -0
- data/lib/sequel/adapters/ado.rb +34 -39
- data/lib/sequel/adapters/ado/mssql.rb +30 -0
- data/lib/sequel/adapters/jdbc.rb +27 -4
- data/lib/sequel/adapters/jdbc/h2.rb +14 -3
- data/lib/sequel/adapters/jdbc/mssql.rb +51 -0
- data/lib/sequel/adapters/mysql.rb +28 -12
- data/lib/sequel/adapters/odbc.rb +36 -30
- data/lib/sequel/adapters/odbc/mssql.rb +44 -0
- data/lib/sequel/adapters/shared/mssql.rb +185 -10
- data/lib/sequel/adapters/shared/mysql.rb +9 -9
- data/lib/sequel/adapters/shared/sqlite.rb +45 -47
- data/lib/sequel/connection_pool.rb +8 -5
- data/lib/sequel/core.rb +2 -8
- data/lib/sequel/database.rb +9 -10
- data/lib/sequel/database/schema_sql.rb +3 -2
- data/lib/sequel/dataset.rb +1 -0
- data/lib/sequel/dataset/sql.rb +15 -6
- data/lib/sequel/extensions/schema_dumper.rb +7 -7
- data/lib/sequel/model/associations.rb +16 -14
- data/lib/sequel/model/base.rb +25 -7
- data/lib/sequel/plugins/association_proxies.rb +41 -0
- data/lib/sequel/plugins/many_through_many.rb +0 -1
- data/lib/sequel/sql.rb +8 -11
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mysql_spec.rb +42 -38
- data/spec/adapters/sqlite_spec.rb +0 -4
- data/spec/core/database_spec.rb +22 -1
- data/spec/core/dataset_spec.rb +37 -12
- data/spec/core/expression_filters_spec.rb +5 -0
- data/spec/core/schema_spec.rb +15 -8
- data/spec/extensions/association_proxies_spec.rb +47 -0
- data/spec/extensions/caching_spec.rb +2 -2
- data/spec/extensions/hook_class_methods_spec.rb +6 -6
- data/spec/extensions/many_through_many_spec.rb +13 -0
- data/spec/extensions/schema_dumper_spec.rb +12 -4
- data/spec/extensions/validation_class_methods_spec.rb +3 -3
- data/spec/integration/dataset_test.rb +47 -17
- data/spec/integration/prepared_statement_test.rb +5 -5
- data/spec/integration/schema_test.rb +111 -34
- data/spec/model/associations_spec.rb +128 -11
- data/spec/model/hooks_spec.rb +7 -6
- data/spec/model/model_spec.rb +54 -4
- data/spec/model/record_spec.rb +2 -3
- data/spec/model/validations_spec.rb +4 -4
- metadata +109 -101
- data/spec/adapters/ado_spec.rb +0 -93
@@ -27,12 +27,23 @@ module Sequel
|
|
27
27
|
def alter_table_sql(table, op)
|
28
28
|
case op[:op]
|
29
29
|
when :add_column
|
30
|
-
if op.delete(:primary_key)
|
31
|
-
|
32
|
-
|
30
|
+
if (pk = op.delete(:primary_key)) || (ref = op.delete(:table))
|
31
|
+
sqls = [super(table, op)]
|
32
|
+
sqls << "ALTER TABLE #{quote_schema_table(table)} ADD PRIMARY KEY (#{quote_identifier(op[:name])})" if pk
|
33
|
+
if ref
|
34
|
+
op[:table] = ref
|
35
|
+
sqls << "ALTER TABLE #{quote_schema_table(table)} ADD FOREIGN KEY (#{quote_identifier(op[:name])}) #{column_references_sql(op)}"
|
36
|
+
end
|
37
|
+
sqls
|
33
38
|
else
|
34
39
|
super(table, op)
|
35
40
|
end
|
41
|
+
when :rename_column
|
42
|
+
"ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(op[:name])} RENAME TO #{quote_identifier(op[:new_name])}"
|
43
|
+
when :set_column_null
|
44
|
+
"ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(op[:name])} SET#{' NOT' unless op[:null]} NULL"
|
45
|
+
when :set_column_type
|
46
|
+
"ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(op[:name])} #{type_literal(op)}"
|
36
47
|
else
|
37
48
|
super(table, op)
|
38
49
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
Sequel.require 'adapters/shared/mssql'
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
module JDBC
|
5
|
+
class Database
|
6
|
+
# Alias the generic JDBC version so it can be called directly later
|
7
|
+
alias jdbc_schema_parse_table schema_parse_table
|
8
|
+
end
|
9
|
+
|
10
|
+
# Database and Dataset instance methods for MSSQL specific
|
11
|
+
# support via JDBC.
|
12
|
+
module MSSQL
|
13
|
+
# Database instance methods for MSSQL databases accessed via JDBC.
|
14
|
+
module DatabaseMethods
|
15
|
+
include Sequel::MSSQL::DatabaseMethods
|
16
|
+
|
17
|
+
# Return instance of Sequel::JDBC::MSSQL::Dataset with the given opts.
|
18
|
+
def dataset(opts=nil)
|
19
|
+
Sequel::JDBC::MSSQL::Dataset.new(self, opts)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# Get the last inserted id using SCOPE_IDENTITY().
|
25
|
+
def last_insert_id(conn, opts={})
|
26
|
+
stmt = conn.createStatement
|
27
|
+
begin
|
28
|
+
sql = opts[:prepared] ? 'SELECT @@IDENTITY' : 'SELECT SCOPE_IDENTITY()'
|
29
|
+
log_info(sql)
|
30
|
+
rs = stmt.executeQuery(sql)
|
31
|
+
rs.next
|
32
|
+
rs.getInt(1)
|
33
|
+
ensure
|
34
|
+
stmt.close
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Call the generic JDBC version instead of MSSQL version,
|
39
|
+
# since the JDBC version handles primary keys.
|
40
|
+
def schema_parse_table(table, opts={})
|
41
|
+
jdbc_schema_parse_table(table, opts)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Dataset class for MSSQL datasets accessed via JDBC.
|
46
|
+
class Dataset < JDBC::Dataset
|
47
|
+
include Sequel::MSSQL::DatasetMethods
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -7,31 +7,40 @@ module Sequel
|
|
7
7
|
# A class level convert_invalid_date_time accessor exists if
|
8
8
|
# the native adapter is used. If set to nil or :nil, the adapter treats dates
|
9
9
|
# like 0000-00-00 and times like 838:00:00 as nil values. If set to :string,
|
10
|
-
# it returns the strings as is.
|
10
|
+
# it returns the strings as is. It is false by default, which means that
|
11
11
|
# invalid dates and times will raise errors.
|
12
|
+
#
|
13
|
+
# Sequel::MySQL.convert_invalid_date_time = true
|
14
|
+
#
|
15
|
+
# Sequel converts the column type tinyint(1) to a boolean by default when
|
16
|
+
# using the native MySQL adapter. You can turn off the conversion to use
|
17
|
+
# tinyint as an integer:
|
18
|
+
#
|
19
|
+
# Sequel.convert_tinyint_to_bool = false
|
12
20
|
module MySQL
|
13
21
|
# Mapping of type numbers to conversion procs
|
14
22
|
MYSQL_TYPES = {}
|
15
23
|
|
16
24
|
# Use only a single proc for each type to save on memory
|
17
25
|
MYSQL_TYPE_PROCS = {
|
18
|
-
[0, 246] => lambda{|v| BigDecimal.new(v)},
|
19
|
-
[1] => lambda{|v|
|
20
|
-
[2, 3, 8, 9, 13, 247, 248] => lambda{|v| v.to_i},
|
21
|
-
[4, 5] => lambda{|v| v.to_f},
|
22
|
-
[10, 14] => lambda{|v| convert_date_time(:string_to_date, v)},
|
23
|
-
[7, 12] => lambda{|v| convert_date_time(:string_to_datetime, v)},
|
24
|
-
[11] => lambda{|v| convert_date_time(:string_to_time, v)},
|
25
|
-
[249, 250, 251, 252] => lambda{|v| Sequel::SQL::Blob.new(v)}
|
26
|
+
[0, 246] => lambda{|v| BigDecimal.new(v)}, # decimal
|
27
|
+
[1] => lambda{|v| convert_tinyint_to_bool ? v.to_i != 0 : v.to_i}, # tinyint
|
28
|
+
[2, 3, 8, 9, 13, 247, 248] => lambda{|v| v.to_i}, # integer
|
29
|
+
[4, 5] => lambda{|v| v.to_f}, # float
|
30
|
+
[10, 14] => lambda{|v| convert_date_time(:string_to_date, v)}, # date
|
31
|
+
[7, 12] => lambda{|v| convert_date_time(:string_to_datetime, v)}, # datetime
|
32
|
+
[11] => lambda{|v| convert_date_time(:string_to_time, v)}, # time
|
33
|
+
[249, 250, 251, 252] => lambda{|v| Sequel::SQL::Blob.new(v)} # blob
|
26
34
|
}
|
27
35
|
MYSQL_TYPE_PROCS.each do |k,v|
|
28
36
|
k.each{|n| MYSQL_TYPES[n] = v}
|
29
37
|
end
|
30
38
|
|
31
39
|
@convert_invalid_date_time = false
|
40
|
+
@convert_tinyint_to_bool = true
|
32
41
|
|
33
42
|
class << self
|
34
|
-
attr_accessor :convert_invalid_date_time
|
43
|
+
attr_accessor :convert_invalid_date_time, :convert_tinyint_to_bool
|
35
44
|
end
|
36
45
|
|
37
46
|
# If convert_invalid_date_time is nil, :nil, or :string and
|
@@ -149,8 +158,10 @@ module Sequel
|
|
149
158
|
yield r if r
|
150
159
|
if conn.respond_to?(:next_result) && conn.next_result
|
151
160
|
loop do
|
152
|
-
r
|
153
|
-
|
161
|
+
if r
|
162
|
+
r.free
|
163
|
+
r = nil
|
164
|
+
end
|
154
165
|
begin
|
155
166
|
r = conn.use_result
|
156
167
|
rescue Mysql::Error
|
@@ -222,6 +233,11 @@ module Sequel
|
|
222
233
|
_execute(conn, "EXECUTE #{ps_name}#{" USING #{(1..i).map{|j| "@sequel_arg_#{j}"}.join(', ')}" unless i == 0}", opts, &block)
|
223
234
|
end
|
224
235
|
end
|
236
|
+
|
237
|
+
# Convert tinyint(1) type to boolean if convert_tinyint_to_bool is true
|
238
|
+
def schema_column_type(db_type)
|
239
|
+
Sequel::MySQL.convert_tinyint_to_bool && db_type == 'tinyint(1)' ? :boolean : super
|
240
|
+
end
|
225
241
|
end
|
226
242
|
|
227
243
|
# Dataset class for MySQL datasets accessed via the native driver.
|
data/lib/sequel/adapters/odbc.rb
CHANGED
@@ -12,8 +12,8 @@ module Sequel
|
|
12
12
|
super(opts)
|
13
13
|
case opts[:db_type]
|
14
14
|
when 'mssql'
|
15
|
-
Sequel.require 'adapters/
|
16
|
-
extend Sequel::MSSQL::DatabaseMethods
|
15
|
+
Sequel.require 'adapters/odbc/mssql'
|
16
|
+
extend Sequel::ODBC::MSSQL::DatabaseMethods
|
17
17
|
when 'progress'
|
18
18
|
Sequel.require 'adapters/shared/progress'
|
19
19
|
extend Sequel::Progress::DatabaseMethods
|
@@ -50,6 +50,8 @@ module Sequel
|
|
50
50
|
begin
|
51
51
|
r = conn.run(sql)
|
52
52
|
yield(r) if block_given?
|
53
|
+
rescue ::ODBC::Error => e
|
54
|
+
raise_error(e)
|
53
55
|
ensure
|
54
56
|
r.drop if r
|
55
57
|
end
|
@@ -59,12 +61,22 @@ module Sequel
|
|
59
61
|
|
60
62
|
def execute_dui(sql, opts={})
|
61
63
|
log_info(sql)
|
62
|
-
synchronize(opts[:server])
|
64
|
+
synchronize(opts[:server]) do |conn|
|
65
|
+
begin
|
66
|
+
conn.do(sql)
|
67
|
+
rescue ::ODBC::Error => e
|
68
|
+
raise_error(e)
|
69
|
+
end
|
70
|
+
end
|
63
71
|
end
|
64
|
-
|
72
|
+
alias do execute_dui
|
65
73
|
|
66
74
|
private
|
67
75
|
|
76
|
+
def connection_pool_default_options
|
77
|
+
super.merge(:pool_convert_exceptions=>false)
|
78
|
+
end
|
79
|
+
|
68
80
|
def connection_execute_method
|
69
81
|
:do
|
70
82
|
end
|
@@ -78,27 +90,34 @@ module Sequel
|
|
78
90
|
BOOL_TRUE = '1'.freeze
|
79
91
|
BOOL_FALSE = '0'.freeze
|
80
92
|
ODBC_TIMESTAMP_FORMAT = "{ts '%Y-%m-%d %H:%M:%S'}".freeze
|
81
|
-
|
82
|
-
ODBC_TIMESTAMP_FORMAT.index( '%S' ).succ - ODBC_TIMESTAMP_FORMAT.length
|
93
|
+
ODBC_TIMESTAMP_FORMAT_USEC = "{ts '%Y-%m-%d %H:%M:%S.%%i'}".freeze
|
83
94
|
ODBC_DATE_FORMAT = "{d '%Y-%m-%d'}".freeze
|
84
|
-
UNTITLED_COLUMN = 'untitled_%d'.freeze
|
85
95
|
|
86
96
|
def fetch_rows(sql, &block)
|
87
97
|
execute(sql) do |s|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
98
|
+
i = -1
|
99
|
+
cols = s.columns(true).map{|c| [output_identifier(c.name), i+=1]}
|
100
|
+
@columns = cols.map{|c| c.at(0)}
|
101
|
+
if rows = s.fetch_all
|
102
|
+
rows.each do |row|
|
103
|
+
hash = {}
|
104
|
+
cols.each{|n,i| hash[n] = convert_odbc_value(row[i])}
|
105
|
+
yield hash
|
92
106
|
end
|
93
|
-
output_identifier(n)
|
94
107
|
end
|
95
|
-
rows = s.fetch_all
|
96
|
-
rows.each {|row| yield hash_row(row)} if rows
|
97
108
|
end
|
98
109
|
self
|
99
110
|
end
|
100
111
|
|
101
112
|
private
|
113
|
+
|
114
|
+
def _literal_datetime(v, usec)
|
115
|
+
if usec >= 1000
|
116
|
+
v.strftime(ODBC_TIMESTAMP_FORMAT_USEC) % (usec.to_f/1000).round
|
117
|
+
else
|
118
|
+
v.strftime(ODBC_TIMESTAMP_FORMAT)
|
119
|
+
end
|
120
|
+
end
|
102
121
|
|
103
122
|
def convert_odbc_value(v)
|
104
123
|
# When fetching a result set, the Ruby ODBC driver converts all ODBC
|
@@ -119,24 +138,13 @@ module Sequel
|
|
119
138
|
v
|
120
139
|
end
|
121
140
|
end
|
122
|
-
|
123
|
-
def hash_row(row)
|
124
|
-
hash = {}
|
125
|
-
row.each_with_index do |v, idx|
|
126
|
-
hash[@columns[idx]] = convert_odbc_value(v)
|
127
|
-
end
|
128
|
-
hash
|
129
|
-
end
|
130
|
-
|
141
|
+
|
131
142
|
def literal_date(v)
|
132
143
|
v.strftime(ODBC_DATE_FORMAT)
|
133
144
|
end
|
134
145
|
|
135
146
|
def literal_datetime(v)
|
136
|
-
|
137
|
-
usec = v.sec_fraction * 86400000000
|
138
|
-
formatted.insert(ODBC_TIMESTAMP_AFTER_SECONDS, ".#{(usec.to_f/1000).round}") if usec >= 1000
|
139
|
-
formatted
|
147
|
+
_literal_datetime(v, v.sec_fraction * 86400000000)
|
140
148
|
end
|
141
149
|
|
142
150
|
def literal_false
|
@@ -148,9 +156,7 @@ module Sequel
|
|
148
156
|
end
|
149
157
|
|
150
158
|
def literal_time(v)
|
151
|
-
|
152
|
-
formatted.insert(ODBC_TIMESTAMP_AFTER_SECONDS, ".#{(v.usec.to_f/1000).round}") if usec >= 1000
|
153
|
-
formatted
|
159
|
+
_literal_datetime(v, v.usec)
|
154
160
|
end
|
155
161
|
end
|
156
162
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
Sequel.require 'adapters/shared/mssql'
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
module ODBC
|
5
|
+
# Database and Dataset instance methods for MSSQL specific
|
6
|
+
# support via ODBC.
|
7
|
+
module MSSQL
|
8
|
+
module DatabaseMethods
|
9
|
+
include Sequel::MSSQL::DatabaseMethods
|
10
|
+
LAST_INSERT_ID_SQL='SELECT SCOPE_IDENTITY()'
|
11
|
+
|
12
|
+
# Return an instance of Sequel::ODBC::MSSQL::Dataset with the given opts.
|
13
|
+
def dataset(opts=nil)
|
14
|
+
Sequel::ODBC::MSSQL::Dataset.new(self, opts)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Return the last inserted identity value.
|
18
|
+
def execute_insert(sql, opts={})
|
19
|
+
log_info(sql)
|
20
|
+
synchronize(opts[:server]) do |conn|
|
21
|
+
begin
|
22
|
+
conn.do(sql)
|
23
|
+
log_info(LAST_INSERT_ID_SQL)
|
24
|
+
begin
|
25
|
+
s = conn.run(LAST_INSERT_ID_SQL)
|
26
|
+
if (rows = s.fetch_all) and (row = rows.first)
|
27
|
+
Integer(row.first)
|
28
|
+
end
|
29
|
+
ensure
|
30
|
+
s.drop if s
|
31
|
+
end
|
32
|
+
rescue ::ODBC::Error => e
|
33
|
+
raise_error(e)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Dataset < ODBC::Dataset
|
40
|
+
include Sequel::MSSQL::DatasetMethods
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -5,6 +5,8 @@ module Sequel
|
|
5
5
|
SQL_BEGIN = "BEGIN TRANSACTION".freeze
|
6
6
|
SQL_COMMIT = "COMMIT TRANSACTION".freeze
|
7
7
|
SQL_ROLLBACK = "ROLLBACK TRANSACTION".freeze
|
8
|
+
SQL_ROLLBACK_TO_SAVEPOINT = 'ROLLBACK TRANSACTION autopoint_%d'.freeze
|
9
|
+
SQL_SAVEPOINT = 'SAVE TRANSACTION autopoint_%d'.freeze
|
8
10
|
TEMPORARY = "#".freeze
|
9
11
|
|
10
12
|
# Microsoft SQL Server uses the :mssql type.
|
@@ -12,42 +14,142 @@ module Sequel
|
|
12
14
|
:mssql
|
13
15
|
end
|
14
16
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
17
|
+
# Microsoft SQL Server supports using the INFORMATION_SCHEMA to get
|
18
|
+
# information on tables.
|
19
|
+
def tables(opts={})
|
20
|
+
m = output_identifier_meth
|
21
|
+
metadata_dataset.from(:information_schema__tables___t).
|
22
|
+
select(:table_name).
|
23
|
+
filter(:table_type=>'BASE TABLE', :table_schema=>(opts[:schema]||default_schema||'dbo').to_s).
|
24
|
+
map{|x| m.call(x[:table_name])}
|
25
|
+
end
|
26
|
+
|
27
|
+
# MSSQL supports savepoints, though it doesn't support committing/releasing them savepoint
|
28
|
+
def supports_savepoints?
|
29
|
+
true
|
19
30
|
end
|
20
31
|
|
21
32
|
private
|
22
|
-
|
33
|
+
|
34
|
+
# MSSQL uses the IDENTITY(1,1) column for autoincrementing columns.
|
23
35
|
def auto_increment_sql
|
24
36
|
AUTO_INCREMENT
|
25
37
|
end
|
38
|
+
|
39
|
+
# MSSQL specific syntax for altering tables.
|
40
|
+
def alter_table_sql(table, op)
|
41
|
+
case op[:op]
|
42
|
+
when :add_column
|
43
|
+
"ALTER TABLE #{quote_schema_table(table)} ADD #{column_definition_sql(op)}"
|
44
|
+
when :rename_column
|
45
|
+
"SP_RENAME #{literal("#{quote_schema_table(table)}.#{quote_identifier(op[:name])}")}, #{literal(op[:new_name].to_s)}, 'COLUMN'"
|
46
|
+
when :set_column_type
|
47
|
+
"ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(op[:name])} #{type_literal(op)}"
|
48
|
+
when :set_column_null
|
49
|
+
sch = schema(table).find{|k,v| k.to_s == op[:name].to_s}.last
|
50
|
+
type = {:type=>sch[:db_type]}
|
51
|
+
type[:size] = sch[:max_chars] if sch[:max_chars]
|
52
|
+
"ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(op[:name])} #{type_literal(type)} #{'NOT ' unless op[:null]}NULL"
|
53
|
+
when :set_column_default
|
54
|
+
"ALTER TABLE #{quote_schema_table(table)} ADD CONSTRAINT #{quote_identifier("sequel_#{table}_#{op[:name]}_def")} DEFAULT #{literal(op[:default])} FOR #{quote_identifier(op[:name])}"
|
55
|
+
else
|
56
|
+
super(table, op)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# SQL to start a new savepoint
|
61
|
+
def begin_savepoint_sql(depth)
|
62
|
+
SQL_SAVEPOINT % depth
|
63
|
+
end
|
26
64
|
|
27
65
|
# SQL to BEGIN a transaction.
|
28
66
|
def begin_transaction_sql
|
29
67
|
SQL_BEGIN
|
30
68
|
end
|
69
|
+
|
70
|
+
# Commit the active transaction on the connection, does not commit/release
|
71
|
+
# savepoints.
|
72
|
+
def commit_transaction(conn)
|
73
|
+
log_connection_execute(conn, commit_transaction_sql) unless Thread.current[:sequel_transaction_depth] > 1
|
74
|
+
end
|
31
75
|
|
32
76
|
# SQL to COMMIT a transaction.
|
33
77
|
def commit_transaction_sql
|
34
78
|
SQL_COMMIT
|
35
79
|
end
|
36
80
|
|
81
|
+
# The SQL to drop an index for the table.
|
82
|
+
def drop_index_sql(table, op)
|
83
|
+
"DROP INDEX #{quote_identifier(op[:name] || default_index_name(table, op[:columns]))} ON #{quote_schema_table(table)}"
|
84
|
+
end
|
85
|
+
|
86
|
+
# SQL to rollback to a savepoint
|
87
|
+
def rollback_savepoint_sql(depth)
|
88
|
+
SQL_ROLLBACK_TO_SAVEPOINT % depth
|
89
|
+
end
|
90
|
+
|
37
91
|
# SQL to ROLLBACK a transaction.
|
38
92
|
def rollback_transaction_sql
|
39
93
|
SQL_ROLLBACK
|
40
94
|
end
|
95
|
+
|
96
|
+
# MSSQL uses the INFORMATION_SCHEMA to hold column information. This method does
|
97
|
+
# not support the parsing of primary key information.
|
98
|
+
def schema_parse_table(table_name, opts)
|
99
|
+
m = output_identifier_meth
|
100
|
+
m2 = input_identifier_meth
|
101
|
+
ds = metadata_dataset.from(:information_schema__tables___t).
|
102
|
+
join(:information_schema__columns___c, :table_catalog=>:table_catalog,
|
103
|
+
:table_schema => :table_schema, :table_name => :table_name).
|
104
|
+
select(:column_name___column, :data_type___db_type, :character_maximum_length___max_chars, :column_default___default, :is_nullable___allow_null).
|
105
|
+
filter(:c__table_name=>m2.call(table_name.to_s))
|
106
|
+
if schema = opts[:schema] || default_schema
|
107
|
+
ds.filter!(:table_schema=>schema)
|
108
|
+
end
|
109
|
+
ds.map do |row|
|
110
|
+
row[:allow_null] = row[:allow_null] == 'YES' ? true : false
|
111
|
+
row[:default] = nil if blank_object?(row[:default])
|
112
|
+
row[:type] = schema_column_type(row[:db_type])
|
113
|
+
[m.call(row.delete(:column)), row]
|
114
|
+
end
|
115
|
+
end
|
41
116
|
|
42
117
|
# SQL fragment for marking a table as temporary
|
43
118
|
def temporary_table_sql
|
44
119
|
TEMPORARY
|
45
120
|
end
|
121
|
+
|
122
|
+
# MSSQL has both datetime and timestamp classes, most people are going
|
123
|
+
# to want datetime
|
124
|
+
def type_literal_generic_datetime(column)
|
125
|
+
:datetime
|
126
|
+
end
|
127
|
+
|
128
|
+
# MSSQL has both datetime and timestamp classes, most people are going
|
129
|
+
# to want datetime
|
130
|
+
def type_literal_generic_time(column)
|
131
|
+
column[:only_time] ? :time : :datetime
|
132
|
+
end
|
133
|
+
|
134
|
+
# MSSQL doesn't have a true boolean class, so it uses bit
|
135
|
+
def type_literal_generic_trueclass(column)
|
136
|
+
:bit
|
137
|
+
end
|
138
|
+
|
139
|
+
# MSSQL uses image type for blobs
|
140
|
+
def type_literal_generic_file(column)
|
141
|
+
:image
|
142
|
+
end
|
46
143
|
end
|
47
144
|
|
48
145
|
module DatasetMethods
|
146
|
+
BOOL_TRUE = '1'.freeze
|
147
|
+
BOOL_FALSE = '0'.freeze
|
49
148
|
SELECT_CLAUSE_ORDER = %w'with limit distinct columns from table_options join where group order having compounds'.freeze
|
50
|
-
|
149
|
+
TIMESTAMP_FORMAT = "'%Y-%m-%d %H:%M:%S'".freeze
|
150
|
+
WILDCARD = LiteralString.new('*').freeze
|
151
|
+
|
152
|
+
# MSSQL uses + for string concatenation
|
51
153
|
def complex_expression_sql(op, args)
|
52
154
|
case op
|
53
155
|
when :'||'
|
@@ -57,10 +159,18 @@ module Sequel
|
|
57
159
|
end
|
58
160
|
end
|
59
161
|
|
162
|
+
# When returning all rows, if an offset is used, delete the row_number column
|
163
|
+
# before yielding the row.
|
164
|
+
def each(&block)
|
165
|
+
@opts[:offset] ? super{|r| r.delete(row_number_column); yield r} : super(&block)
|
166
|
+
end
|
167
|
+
|
168
|
+
# MSSQL uses the CONTAINS keyword for full text search
|
60
169
|
def full_text_search(cols, terms, opts = {})
|
61
170
|
filter("CONTAINS (#{literal(cols)}, #{literal(terms)})")
|
62
171
|
end
|
63
172
|
|
173
|
+
# MSSQL uses a UNION ALL statement to insert multiple values at once.
|
64
174
|
def multi_insert_sql(columns, values)
|
65
175
|
values = values.map {|r| "SELECT #{expression_list(r)}" }.join(" UNION ALL ")
|
66
176
|
["#{insert_sql_base}#{source_list(@opts[:from])} (#{identifier_list(columns)}) #{values}"]
|
@@ -70,34 +180,99 @@ module Sequel
|
|
70
180
|
def nolock
|
71
181
|
clone(:table_options => "(NOLOCK)")
|
72
182
|
end
|
73
|
-
|
183
|
+
|
184
|
+
# MSSQL uses [] to quote identifiers
|
74
185
|
def quoted_identifier(name)
|
75
186
|
"[#{name}]"
|
76
187
|
end
|
188
|
+
|
189
|
+
# MSSQL Requires the use of the ROW_NUMBER window function to emulate
|
190
|
+
# an offset. This implementation requires MSSQL 2005 or greater (offset
|
191
|
+
# can't be emulated well in MSSQL 2000).
|
192
|
+
#
|
193
|
+
# The implementation is ugly, cloning the current dataset and modifying
|
194
|
+
# the clone to add a ROW_NUMBER window function (and some other things),
|
195
|
+
# then using the modified clone in a CTE which is selected from.
|
196
|
+
#
|
197
|
+
# If offset is used, an order must be provided, because the use of ROW_NUMBER
|
198
|
+
# requires an order.
|
199
|
+
def select_sql
|
200
|
+
return super unless o = @opts[:offset]
|
201
|
+
raise(Error, 'MSSQL requires an order be provided if using an offset') unless order = @opts[:order]
|
202
|
+
dsa1 = dataset_alias(1)
|
203
|
+
dsa2 = dataset_alias(2)
|
204
|
+
rn = row_number_column
|
205
|
+
unlimited.
|
206
|
+
unordered.
|
207
|
+
from_self(:alias=>dsa2).
|
208
|
+
select{[WILDCARD, ROW_NUMBER(:over, :order=>order){}.as(rn)]}.
|
209
|
+
from_self(:alias=>dsa1).
|
210
|
+
limit(@opts[:limit]).
|
211
|
+
where(rn > o).
|
212
|
+
select_sql
|
213
|
+
end
|
77
214
|
|
78
215
|
# Microsoft SQL Server does not support INTERSECT or EXCEPT
|
79
216
|
def supports_intersect_except?
|
80
217
|
false
|
81
218
|
end
|
82
219
|
|
220
|
+
# MSSQL does not support IS TRUE
|
221
|
+
def supports_is_true?
|
222
|
+
false
|
223
|
+
end
|
224
|
+
|
83
225
|
# MSSQL 2005+ supports window functions
|
84
226
|
def supports_window_functions?
|
85
227
|
true
|
86
228
|
end
|
87
229
|
|
88
230
|
private
|
89
|
-
|
231
|
+
|
232
|
+
# MSSQL uses a literal hexidecimal number for blob strings
|
233
|
+
def literal_blob(v)
|
234
|
+
blob = '0x'
|
235
|
+
v.each_byte{|x| blob << sprintf('%02x', x)}
|
236
|
+
blob
|
237
|
+
end
|
238
|
+
|
239
|
+
# Use unicode string syntax for all strings
|
90
240
|
def literal_string(v)
|
91
241
|
"N#{super}"
|
92
242
|
end
|
243
|
+
|
244
|
+
# Use MSSQL Timestamp format
|
245
|
+
def literal_datetime(v)
|
246
|
+
v.strftime(TIMESTAMP_FORMAT)
|
247
|
+
end
|
248
|
+
|
249
|
+
# Use 0 for false on MSSQL
|
250
|
+
def literal_false
|
251
|
+
BOOL_FALSE
|
252
|
+
end
|
253
|
+
|
254
|
+
# Use MSSQL Timestamp format
|
255
|
+
def literal_time(v)
|
256
|
+
v.strftime(TIMESTAMP_FORMAT)
|
257
|
+
end
|
258
|
+
|
259
|
+
# Use 1 for true on MSSQL
|
260
|
+
def literal_true
|
261
|
+
BOOL_TRUE
|
262
|
+
end
|
263
|
+
|
264
|
+
# The alias to use for the row_number column when emulating OFFSET
|
265
|
+
def row_number_column
|
266
|
+
:x_sequel_row_number_x
|
267
|
+
end
|
93
268
|
|
269
|
+
# MSSQL adds the limit before the columns
|
94
270
|
def select_clause_order
|
95
271
|
SELECT_CLAUSE_ORDER
|
96
272
|
end
|
97
273
|
|
98
|
-
# MSSQL uses TOP for limit
|
274
|
+
# MSSQL uses TOP for limit
|
99
275
|
def select_limit_sql(sql)
|
100
|
-
raise(Error, "OFFSET not supported") if @opts[:offset]
|
101
276
|
sql << " TOP #{@opts[:limit]}" if @opts[:limit]
|
102
277
|
end
|
103
278
|
|