sequel 3.2.0 → 3.3.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.
- 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
|
|