sequel 3.4.0 → 3.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +84 -0
- data/Rakefile +1 -1
- data/doc/cheat_sheet.rdoc +5 -2
- data/doc/opening_databases.rdoc +2 -0
- data/doc/release_notes/3.5.0.txt +510 -0
- data/lib/sequel/adapters/ado.rb +3 -1
- data/lib/sequel/adapters/ado/mssql.rb +2 -2
- data/lib/sequel/adapters/do.rb +2 -11
- data/lib/sequel/adapters/do/mysql.rb +7 -0
- data/lib/sequel/adapters/do/postgres.rb +2 -2
- data/lib/sequel/adapters/firebird.rb +3 -3
- data/lib/sequel/adapters/informix.rb +3 -3
- data/lib/sequel/adapters/jdbc/h2.rb +3 -3
- data/lib/sequel/adapters/jdbc/mssql.rb +7 -0
- data/lib/sequel/adapters/mysql.rb +60 -21
- data/lib/sequel/adapters/odbc.rb +1 -1
- data/lib/sequel/adapters/openbase.rb +3 -3
- data/lib/sequel/adapters/oracle.rb +1 -5
- data/lib/sequel/adapters/postgres.rb +3 -3
- data/lib/sequel/adapters/shared/mssql.rb +142 -33
- data/lib/sequel/adapters/shared/mysql.rb +54 -31
- data/lib/sequel/adapters/shared/oracle.rb +17 -6
- data/lib/sequel/adapters/shared/postgres.rb +7 -7
- data/lib/sequel/adapters/shared/progress.rb +3 -3
- data/lib/sequel/adapters/shared/sqlite.rb +3 -17
- data/lib/sequel/connection_pool.rb +4 -6
- data/lib/sequel/core.rb +29 -113
- data/lib/sequel/database.rb +14 -12
- data/lib/sequel/dataset.rb +8 -21
- data/lib/sequel/dataset/convenience.rb +1 -1
- data/lib/sequel/dataset/graph.rb +9 -2
- data/lib/sequel/dataset/sql.rb +170 -104
- data/lib/sequel/exceptions.rb +3 -0
- data/lib/sequel/extensions/looser_typecasting.rb +21 -0
- data/lib/sequel/extensions/named_timezones.rb +61 -0
- data/lib/sequel/extensions/schema_dumper.rb +7 -1
- data/lib/sequel/extensions/sql_expr.rb +122 -0
- data/lib/sequel/extensions/string_date_time.rb +4 -4
- data/lib/sequel/extensions/thread_local_timezones.rb +48 -0
- data/lib/sequel/model/associations.rb +105 -45
- data/lib/sequel/model/base.rb +37 -28
- data/lib/sequel/plugins/active_model.rb +35 -0
- data/lib/sequel/plugins/association_dependencies.rb +96 -0
- data/lib/sequel/plugins/class_table_inheritance.rb +214 -0
- data/lib/sequel/plugins/force_encoding.rb +61 -0
- data/lib/sequel/plugins/many_through_many.rb +32 -11
- data/lib/sequel/plugins/nested_attributes.rb +7 -2
- data/lib/sequel/plugins/subclasses.rb +45 -0
- data/lib/sequel/plugins/touch.rb +118 -0
- data/lib/sequel/plugins/typecast_on_load.rb +61 -0
- data/lib/sequel/sql.rb +31 -30
- data/lib/sequel/timezones.rb +161 -0
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +262 -0
- data/spec/adapters/mysql_spec.rb +46 -8
- data/spec/adapters/postgres_spec.rb +6 -3
- data/spec/adapters/spec_helper.rb +21 -0
- data/spec/adapters/sqlite_spec.rb +1 -1
- data/spec/core/connection_pool_spec.rb +1 -1
- data/spec/core/database_spec.rb +27 -1
- data/spec/core/dataset_spec.rb +63 -1
- data/spec/core/object_graph_spec.rb +1 -1
- data/spec/core/schema_spec.rb +1 -0
- data/spec/extensions/active_model_spec.rb +47 -0
- data/spec/extensions/association_dependencies_spec.rb +108 -0
- data/spec/extensions/class_table_inheritance_spec.rb +252 -0
- data/spec/extensions/force_encoding_spec.rb +75 -0
- data/spec/extensions/looser_typecasting_spec.rb +39 -0
- data/spec/extensions/many_through_many_spec.rb +60 -2
- data/spec/extensions/named_timezones_spec.rb +72 -0
- data/spec/extensions/nested_attributes_spec.rb +29 -1
- data/spec/extensions/schema_dumper_spec.rb +10 -0
- data/spec/extensions/spec_helper.rb +1 -1
- data/spec/extensions/sql_expr_spec.rb +89 -0
- data/spec/extensions/subclasses_spec.rb +52 -0
- data/spec/extensions/thread_local_timezones_spec.rb +45 -0
- data/spec/extensions/touch_spec.rb +155 -0
- data/spec/extensions/typecast_on_load_spec.rb +60 -0
- data/spec/integration/database_test.rb +8 -0
- data/spec/integration/dataset_test.rb +9 -9
- data/spec/integration/plugin_test.rb +139 -0
- data/spec/integration/schema_test.rb +7 -7
- data/spec/integration/spec_helper.rb +32 -1
- data/spec/integration/timezone_test.rb +3 -3
- data/spec/integration/transaction_test.rb +1 -1
- data/spec/integration/type_test.rb +6 -6
- data/spec/model/association_reflection_spec.rb +18 -0
- data/spec/model/associations_spec.rb +169 -9
- data/spec/model/base_spec.rb +2 -0
- data/spec/model/eager_loading_spec.rb +82 -2
- data/spec/model/model_spec.rb +8 -1
- data/spec/model/record_spec.rb +52 -9
- metadata +33 -23
data/lib/sequel/adapters/ado.rb
CHANGED
@@ -23,10 +23,12 @@ module Sequel
|
|
23
23
|
# to execute a command before cancelling the attempt and generating
|
24
24
|
# an error. Specifically, it sets the ADO CommandTimeout property.
|
25
25
|
# If this property is not set, the default of 30 seconds is used.
|
26
|
+
# * :conn_string - The full ADO connection string. If this is provided,
|
27
|
+
# the usual options are ignored.
|
26
28
|
# * :provider - Sets the Provider of this ADO connection (for example, "SQLOLEDB")
|
27
29
|
def connect(server)
|
28
30
|
opts = server_opts(server)
|
29
|
-
s = "driver=#{opts[:driver]};server=#{opts[:host]};database=#{opts[:database]}#{";uid=#{opts[:user]};pwd=#{opts[:password]}" if opts[:user]}"
|
31
|
+
s = opts[:conn_string] || "driver=#{opts[:driver]};server=#{opts[:host]};database=#{opts[:database]}#{";uid=#{opts[:user]};pwd=#{opts[:password]}" if opts[:user]}"
|
30
32
|
handle = WIN32OLE.new('ADODB.Connection')
|
31
33
|
handle.CommandTimeout = opts[:command_timeout] if opts[:command_timeout]
|
32
34
|
handle.Provider = opts[:provider] if opts[:provider]
|
@@ -20,9 +20,9 @@ module Sequel
|
|
20
20
|
# Use a nasty hack of multiple SQL statements in the same call and
|
21
21
|
# having the last one return the most recently inserted id. This
|
22
22
|
# is necessary as ADO doesn't provide a consistent native connection.
|
23
|
-
def insert(values
|
23
|
+
def insert(*values)
|
24
24
|
return super if @opts[:sql]
|
25
|
-
with_sql("SET NOCOUNT ON; #{insert_sql(values)}; SELECT CAST(SCOPE_IDENTITY() AS INTEGER)").single_value
|
25
|
+
with_sql("SET NOCOUNT ON; #{insert_sql(*values)}; SELECT CAST(SCOPE_IDENTITY() AS INTEGER)").single_value
|
26
26
|
end
|
27
27
|
end
|
28
28
|
end
|
data/lib/sequel/adapters/do.rb
CHANGED
@@ -16,19 +16,16 @@ module Sequel
|
|
16
16
|
DATABASE_SETUP = {:postgres=>proc do |db|
|
17
17
|
require 'do_postgres'
|
18
18
|
Sequel.require 'adapters/do/postgres'
|
19
|
-
db.converted_exceptions << PostgresError
|
20
19
|
db.extend(Sequel::DataObjects::Postgres::DatabaseMethods)
|
21
20
|
end,
|
22
21
|
:mysql=>proc do |db|
|
23
22
|
require 'do_mysql'
|
24
23
|
Sequel.require 'adapters/do/mysql'
|
25
|
-
db.converted_exceptions << MysqlError
|
26
24
|
db.extend(Sequel::DataObjects::MySQL::DatabaseMethods)
|
27
25
|
end,
|
28
26
|
:sqlite3=>proc do |db|
|
29
27
|
require 'do_sqlite3'
|
30
28
|
Sequel.require 'adapters/do/sqlite'
|
31
|
-
db.converted_exceptions << Sqlite3Error
|
32
29
|
db.extend(Sequel::DataObjects::SQLite::DatabaseMethods)
|
33
30
|
end
|
34
31
|
}
|
@@ -41,18 +38,12 @@ module Sequel
|
|
41
38
|
class Database < Sequel::Database
|
42
39
|
set_adapter_scheme :do
|
43
40
|
|
44
|
-
# Convert the given exceptions to Sequel:Errors, necessary
|
45
|
-
# because DO raises errors specific to database types in
|
46
|
-
# certain cases.
|
47
|
-
attr_accessor :converted_exceptions
|
48
|
-
|
49
41
|
# Call the DATABASE_SETUP proc directly after initialization,
|
50
42
|
# so the object always uses sub adapter specific code. Also,
|
51
43
|
# raise an error immediately if the connection doesn't have a
|
52
44
|
# uri, since DataObjects requires one.
|
53
45
|
def initialize(opts)
|
54
46
|
@opts = opts
|
55
|
-
@converted_exceptions = []
|
56
47
|
raise(Error, "No connection string specified") unless uri
|
57
48
|
if prok = DATABASE_SETUP[subadapter.to_sym]
|
58
49
|
prok.call(self)
|
@@ -81,8 +72,8 @@ module Sequel
|
|
81
72
|
begin
|
82
73
|
command = conn.create_command(sql)
|
83
74
|
res = block_given? ? command.execute_reader : command.execute_non_query
|
84
|
-
rescue
|
85
|
-
raise_error(e
|
75
|
+
rescue ::DataObjects::Error => e
|
76
|
+
raise_error(e)
|
86
77
|
end
|
87
78
|
if block_given?
|
88
79
|
begin
|
@@ -32,6 +32,13 @@ module Sequel
|
|
32
32
|
def replace(*args)
|
33
33
|
execute_insert(replace_sql(*args))
|
34
34
|
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# do_mysql sets NO_BACKSLASH_ESCAPES, so use standard SQL string escaping
|
39
|
+
def literal_string(s)
|
40
|
+
"'#{s.gsub("'", "''")}'"
|
41
|
+
end
|
35
42
|
end
|
36
43
|
end
|
37
44
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
Sequel.require 'adapters/shared/postgres'
|
2
2
|
|
3
3
|
module Sequel
|
4
|
-
Postgres::CONVERTED_EXCEPTIONS <<
|
4
|
+
Postgres::CONVERTED_EXCEPTIONS << ::DataObjects::Error
|
5
5
|
|
6
6
|
module DataObjects
|
7
7
|
# Adapter, Database, and Dataset support for accessing a PostgreSQL
|
@@ -27,7 +27,7 @@ module Sequel
|
|
27
27
|
else
|
28
28
|
command.execute_non_query
|
29
29
|
end
|
30
|
-
rescue
|
30
|
+
rescue ::DataObjects::Error => e
|
31
31
|
raise_error(e)
|
32
32
|
end
|
33
33
|
end
|
@@ -202,7 +202,7 @@ module Sequel
|
|
202
202
|
BOOL_FALSE = '0'.freeze
|
203
203
|
NULL = LiteralString.new('NULL').freeze
|
204
204
|
COMMA_SEPARATOR = ', '.freeze
|
205
|
-
|
205
|
+
SELECT_CLAUSE_METHODS = clause_methods(:select, %w'with distinct limit columns from join where group having compounds order')
|
206
206
|
|
207
207
|
# Yield all rows returned by executing the given SQL and converting
|
208
208
|
# the types.
|
@@ -253,8 +253,8 @@ module Sequel
|
|
253
253
|
end
|
254
254
|
|
255
255
|
# The order of clauses in the SELECT SQL statement
|
256
|
-
def
|
257
|
-
|
256
|
+
def select_clause_methods
|
257
|
+
SELECT_CLAUSE_METHODS
|
258
258
|
end
|
259
259
|
|
260
260
|
def select_limit_sql(sql)
|
@@ -37,7 +37,7 @@ module Sequel
|
|
37
37
|
end
|
38
38
|
|
39
39
|
class Dataset < Sequel::Dataset
|
40
|
-
|
40
|
+
SELECT_CLAUSE_METHODS = clause_methods(:select, %w'limit distinct columns from join where having group compounds order')
|
41
41
|
|
42
42
|
def fetch_rows(sql, &block)
|
43
43
|
execute(sql) do |cursor|
|
@@ -66,8 +66,8 @@ module Sequel
|
|
66
66
|
false
|
67
67
|
end
|
68
68
|
|
69
|
-
def
|
70
|
-
|
69
|
+
def select_clause_methods
|
70
|
+
SELECT_CLAUSE_METHODS
|
71
71
|
end
|
72
72
|
|
73
73
|
def select_limit_sql(sql)
|
@@ -74,7 +74,7 @@ module Sequel
|
|
74
74
|
|
75
75
|
# Dataset class for H2 datasets accessed via JDBC.
|
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
79
|
# H2 requires SQL standard datetimes
|
80
80
|
def requires_sql_standard_datetimes?
|
@@ -88,8 +88,8 @@ module Sequel
|
|
88
88
|
|
89
89
|
private
|
90
90
|
|
91
|
-
def
|
92
|
-
|
91
|
+
def select_clause_methods
|
92
|
+
SELECT_CLAUSE_METHODS
|
93
93
|
end
|
94
94
|
end
|
95
95
|
end
|
@@ -12,6 +12,8 @@ module Sequel
|
|
12
12
|
module MSSQL
|
13
13
|
# Database instance methods for MSSQL databases accessed via JDBC.
|
14
14
|
module DatabaseMethods
|
15
|
+
PRIMARY_KEY_INDEX_RE = /\Apk__/i.freeze
|
16
|
+
|
15
17
|
include Sequel::MSSQL::DatabaseMethods
|
16
18
|
|
17
19
|
# Return instance of Sequel::JDBC::MSSQL::Dataset with the given opts.
|
@@ -40,6 +42,11 @@ module Sequel
|
|
40
42
|
def schema_parse_table(table, opts={})
|
41
43
|
jdbc_schema_parse_table(table, opts)
|
42
44
|
end
|
45
|
+
|
46
|
+
# Primary key indexes appear to start with pk__ on MSSQL
|
47
|
+
def primary_key_index_re
|
48
|
+
PRIMARY_KEY_INDEX_RE
|
49
|
+
end
|
43
50
|
end
|
44
51
|
|
45
52
|
# Dataset class for MSSQL datasets accessed via JDBC.
|
@@ -67,7 +67,7 @@ module Sequel
|
|
67
67
|
include Sequel::MySQL::DatabaseMethods
|
68
68
|
|
69
69
|
# Mysql::Error messages that indicate the current connection should be disconnected
|
70
|
-
MYSQL_DATABASE_DISCONNECT_ERRORS = /\
|
70
|
+
MYSQL_DATABASE_DISCONNECT_ERRORS = /\A(Commands out of sync; you can't run this command now\z|Can't connect to local MySQL server through socket)/
|
71
71
|
|
72
72
|
set_adapter_scheme :mysql
|
73
73
|
|
@@ -132,12 +132,12 @@ module Sequel
|
|
132
132
|
# Executes the given SQL using an available connection, yielding the
|
133
133
|
# connection if the block is given.
|
134
134
|
def execute(sql, opts={}, &block)
|
135
|
-
|
136
|
-
|
137
|
-
|
135
|
+
if opts[:sproc]
|
136
|
+
call_sproc(sql, opts, &block)
|
137
|
+
elsif sql.is_a?(Symbol)
|
138
|
+
execute_prepared_statement(sql, opts, &block)
|
139
|
+
else
|
138
140
|
synchronize(opts[:server]){|conn| _execute(conn, sql, opts, &block)}
|
139
|
-
rescue Mysql::Error => e
|
140
|
-
raise_error(e)
|
141
141
|
end
|
142
142
|
end
|
143
143
|
|
@@ -178,7 +178,16 @@ module Sequel
|
|
178
178
|
rescue Mysql::Error => e
|
179
179
|
raise_error(e, :disconnect=>MYSQL_DATABASE_DISCONNECT_ERRORS.match(e.message))
|
180
180
|
ensure
|
181
|
-
|
181
|
+
if r
|
182
|
+
r.free
|
183
|
+
# Use up all results to avoid a commands out of sync message.
|
184
|
+
if conn.respond_to?(:next_result)
|
185
|
+
while conn.next_result
|
186
|
+
r = conn.use_result
|
187
|
+
r.free if r
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
182
191
|
end
|
183
192
|
end
|
184
193
|
|
@@ -221,16 +230,10 @@ module Sequel
|
|
221
230
|
synchronize(opts[:server]) do |conn|
|
222
231
|
unless conn.prepared_statements[ps_name] == sql
|
223
232
|
conn.prepared_statements[ps_name] = sql
|
224
|
-
|
225
|
-
log_info(s)
|
226
|
-
conn.query(s)
|
233
|
+
_execute(conn, "PREPARE #{ps_name} FROM '#{::Mysql.quote(sql)}'", opts)
|
227
234
|
end
|
228
235
|
i = 0
|
229
|
-
args.
|
230
|
-
s = "SET @sequel_arg_#{i+=1} = #{literal(arg)}"
|
231
|
-
log_info(s)
|
232
|
-
conn.query(s)
|
233
|
-
end
|
236
|
+
_execute(conn, "SET " + args.map {|arg| "@sequel_arg_#{i+=1} = #{literal(arg)}"}.join(", "), opts) unless args.empty?
|
234
237
|
_execute(conn, "EXECUTE #{ps_name}#{" USING #{(1..i).map{|j| "@sequel_arg_#{j}"}.join(', ')}" unless i == 0}", opts, &block)
|
235
238
|
end
|
236
239
|
end
|
@@ -310,21 +313,31 @@ module Sequel
|
|
310
313
|
execute_dui(delete_sql){|c| c.affected_rows}
|
311
314
|
end
|
312
315
|
|
313
|
-
# Yield all rows matching this dataset
|
314
|
-
|
316
|
+
# Yield all rows matching this dataset. If the dataset is set to
|
317
|
+
# split multiple statements, yield arrays of hashes one per statement
|
318
|
+
# instead of yielding results for all statements as hashes.
|
319
|
+
def fetch_rows(sql, &block)
|
315
320
|
execute(sql) do |r|
|
316
321
|
i = -1
|
317
322
|
cols = r.fetch_fields.map{|f| [output_identifier(f.name), MYSQL_TYPES[f.type], i+=1]}
|
318
323
|
@columns = cols.map{|c| c.first}
|
319
|
-
|
320
|
-
|
321
|
-
cols
|
322
|
-
yield
|
324
|
+
if opts[:split_multiple_result_sets]
|
325
|
+
s = []
|
326
|
+
yield_rows(r, cols){|h| s << h}
|
327
|
+
yield s
|
328
|
+
else
|
329
|
+
yield_rows(r, cols, &block)
|
323
330
|
end
|
324
331
|
end
|
325
332
|
self
|
326
333
|
end
|
327
334
|
|
335
|
+
# Don't allow graphing a dataset that splits multiple statements
|
336
|
+
def graph(*)
|
337
|
+
raise(Error, "Can't graph a dataset that splits multiple result sets") if opts[:split_multiple_result_sets]
|
338
|
+
super
|
339
|
+
end
|
340
|
+
|
328
341
|
# Insert a new value into this dataset
|
329
342
|
def insert(*values)
|
330
343
|
execute_dui(insert_sql(*values)){|c| c.insert_id}
|
@@ -347,6 +360,22 @@ module Sequel
|
|
347
360
|
execute_dui(replace_sql(*args)){|c| c.insert_id}
|
348
361
|
end
|
349
362
|
|
363
|
+
# Makes each yield arrays of rows, with each array containing the rows
|
364
|
+
# for a given result set. Does not work with graphing. So you can submit
|
365
|
+
# SQL with multiple statements and easily determine which statement
|
366
|
+
# returned which results.
|
367
|
+
#
|
368
|
+
# Modifies the row_proc of the returned dataset so that it still works
|
369
|
+
# as expected (running on the hashes instead of on the arrays of hashes).
|
370
|
+
# If you modify the row_proc afterward, note that it will receive an array
|
371
|
+
# of hashes instead of a hash.
|
372
|
+
def split_multiple_result_sets
|
373
|
+
raise(Error, "Can't split multiple statements on a graphed dataset") if opts[:graph]
|
374
|
+
ds = clone(:split_multiple_result_sets=>true)
|
375
|
+
ds.row_proc = proc{|x| x.map{|h| row_proc.call(h)}} if row_proc
|
376
|
+
ds
|
377
|
+
end
|
378
|
+
|
350
379
|
# Update the matching rows.
|
351
380
|
def update(values={})
|
352
381
|
execute_dui(update_sql(values)){|c| c.affected_rows}
|
@@ -373,6 +402,16 @@ module Sequel
|
|
373
402
|
def prepare_extend_sproc(ds)
|
374
403
|
ds.extend(StoredProcedureMethods)
|
375
404
|
end
|
405
|
+
|
406
|
+
# Yield each row of the given result set r with columns cols
|
407
|
+
# as a hash with symbol keys
|
408
|
+
def yield_rows(r, cols)
|
409
|
+
while row = r.fetch_row
|
410
|
+
h = {}
|
411
|
+
cols.each{|n, p, i| v = row[i]; h[n] = (v && p) ? p.call(v) : v}
|
412
|
+
yield h
|
413
|
+
end
|
414
|
+
end
|
376
415
|
end
|
377
416
|
end
|
378
417
|
end
|
data/lib/sequel/adapters/odbc.rb
CHANGED
@@ -90,7 +90,7 @@ module Sequel
|
|
90
90
|
BOOL_TRUE = '1'.freeze
|
91
91
|
BOOL_FALSE = '0'.freeze
|
92
92
|
ODBC_DATE_FORMAT = "{d '%Y-%m-%d'}".freeze
|
93
|
-
TIMESTAMP_FORMAT="{ts '%Y-%m-%d %H:%M:%S
|
93
|
+
TIMESTAMP_FORMAT="{ts '%Y-%m-%d %H:%M:%S'}".freeze
|
94
94
|
|
95
95
|
def fetch_rows(sql, &block)
|
96
96
|
execute(sql) do |s|
|
@@ -37,7 +37,7 @@ module Sequel
|
|
37
37
|
end
|
38
38
|
|
39
39
|
class Dataset < Sequel::Dataset
|
40
|
-
|
40
|
+
SELECT_CLAUSE_METHODS = clause_methods(:select, %w'distinct columns from join where group having compounds order limit')
|
41
41
|
|
42
42
|
def fetch_rows(sql)
|
43
43
|
execute(sql) do |result|
|
@@ -57,8 +57,8 @@ module Sequel
|
|
57
57
|
|
58
58
|
private
|
59
59
|
|
60
|
-
def
|
61
|
-
|
60
|
+
def select_clause_methods
|
61
|
+
SELECT_CLAUSE_METHODS
|
62
62
|
end
|
63
63
|
end
|
64
64
|
end
|
@@ -67,11 +67,7 @@ module Sequel
|
|
67
67
|
yield(r) if block_given?
|
68
68
|
r
|
69
69
|
rescue OCIException => e
|
70
|
-
|
71
|
-
raise(Sequel::DatabaseDisconnectError)
|
72
|
-
else
|
73
|
-
raise
|
74
|
-
end
|
70
|
+
raise_error(e, :disconnect=>CONNECTION_ERROR_CODES.include?(e.code))
|
75
71
|
end
|
76
72
|
end
|
77
73
|
end
|
@@ -144,14 +144,14 @@ module Sequel
|
|
144
144
|
q = nil
|
145
145
|
begin
|
146
146
|
q = args ? async_exec(sql, args) : async_exec(sql)
|
147
|
-
rescue PGError
|
147
|
+
rescue PGError => e
|
148
148
|
begin
|
149
149
|
s = status
|
150
150
|
rescue PGError
|
151
|
-
raise(Sequel::DatabaseDisconnectError)
|
151
|
+
raise Sequel.convert_exception_class(e, Sequel::DatabaseDisconnectError)
|
152
152
|
end
|
153
153
|
status_ok = (s == Adapter::CONNECTION_OK)
|
154
|
-
status_ok ? raise :
|
154
|
+
status_ok ? raise : Sequel.convert_exception_class(e, Sequel::DatabaseDisconnectError)
|
155
155
|
ensure
|
156
156
|
block if status_ok
|
157
157
|
end
|
@@ -2,6 +2,8 @@ module Sequel
|
|
2
2
|
module MSSQL
|
3
3
|
module DatabaseMethods
|
4
4
|
AUTO_INCREMENT = 'IDENTITY(1,1)'.freeze
|
5
|
+
SERVER_VERSION_RE = /^(\d+)\.(\d+)\.(\d+)/.freeze
|
6
|
+
SERVER_VERSION_SQL = "SELECT CAST(SERVERPROPERTY('ProductVersion') AS varchar)".freeze
|
5
7
|
SQL_BEGIN = "BEGIN TRANSACTION".freeze
|
6
8
|
SQL_COMMIT = "COMMIT TRANSACTION".freeze
|
7
9
|
SQL_ROLLBACK = "ROLLBACK TRANSACTION".freeze
|
@@ -13,7 +15,26 @@ module Sequel
|
|
13
15
|
def database_type
|
14
16
|
:mssql
|
15
17
|
end
|
16
|
-
|
18
|
+
|
19
|
+
# The version of the MSSQL server, as an integer (e.g. 10001600 for
|
20
|
+
# SQL Server 2008 Express).
|
21
|
+
def server_version(server=nil)
|
22
|
+
return @server_version if @server_version
|
23
|
+
@server_version = synchronize(server) do |conn|
|
24
|
+
(conn.server_version rescue nil) if conn.respond_to?(:server_version)
|
25
|
+
end
|
26
|
+
unless @server_version
|
27
|
+
m = SERVER_VERSION_RE.match(fetch(SERVER_VERSION_SQL).single_value.to_s)
|
28
|
+
@server_version = (m[1].to_i * 1000000) + (m[2].to_i * 10000) + m[3].to_i
|
29
|
+
end
|
30
|
+
@server_version
|
31
|
+
end
|
32
|
+
|
33
|
+
# MSSQL supports savepoints, though it doesn't support committing/releasing them savepoint
|
34
|
+
def supports_savepoints?
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
17
38
|
# Microsoft SQL Server supports using the INFORMATION_SCHEMA to get
|
18
39
|
# information on tables.
|
19
40
|
def tables(opts={})
|
@@ -23,11 +44,6 @@ module Sequel
|
|
23
44
|
filter(:table_type=>'BASE TABLE', :table_schema=>(opts[:schema]||default_schema||'dbo').to_s).
|
24
45
|
map{|x| m.call(x[:table_name])}
|
25
46
|
end
|
26
|
-
|
27
|
-
# MSSQL supports savepoints, though it doesn't support committing/releasing them savepoint
|
28
|
-
def supports_savepoints?
|
29
|
-
true
|
30
|
-
end
|
31
47
|
|
32
48
|
private
|
33
49
|
|
@@ -83,6 +99,13 @@ module Sequel
|
|
83
99
|
"DROP INDEX #{quote_identifier(op[:name] || default_index_name(table, op[:columns]))} ON #{quote_schema_table(table)}"
|
84
100
|
end
|
85
101
|
|
102
|
+
# Always quote identifiers in the metadata_dataset, so schema parsing works.
|
103
|
+
def metadata_dataset
|
104
|
+
ds = super
|
105
|
+
ds.quote_identifiers = true
|
106
|
+
ds
|
107
|
+
end
|
108
|
+
|
86
109
|
# SQL to rollback to a savepoint
|
87
110
|
def rollback_savepoint_sql(depth)
|
88
111
|
SQL_ROLLBACK_TO_SAVEPOINT % depth
|
@@ -113,7 +136,7 @@ module Sequel
|
|
113
136
|
[m.call(row.delete(:column)), row]
|
114
137
|
end
|
115
138
|
end
|
116
|
-
|
139
|
+
|
117
140
|
# SQL fragment for marking a table as temporary
|
118
141
|
def temporary_table_sql
|
119
142
|
TEMPORARY
|
@@ -145,11 +168,14 @@ module Sequel
|
|
145
168
|
module DatasetMethods
|
146
169
|
BOOL_TRUE = '1'.freeze
|
147
170
|
BOOL_FALSE = '0'.freeze
|
148
|
-
|
149
|
-
|
171
|
+
COMMA_SEPARATOR = ', '.freeze
|
172
|
+
DELETE_CLAUSE_METHODS = Dataset.clause_methods(:delete, %w'with from output from2 where')
|
173
|
+
INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'with into columns output values')
|
174
|
+
SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with limit distinct columns from table_options join where group order having compounds')
|
175
|
+
UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'with table set output from where')
|
150
176
|
WILDCARD = LiteralString.new('*').freeze
|
151
177
|
CONSTANT_MAP = {:CURRENT_DATE=>'CAST(CURRENT_TIMESTAMP AS DATE)'.freeze, :CURRENT_TIME=>'CAST(CURRENT_TIMESTAMP AS TIME)'.freeze}
|
152
|
-
|
178
|
+
|
153
179
|
# MSSQL uses + for string concatenation
|
154
180
|
def complex_expression_sql(op, args)
|
155
181
|
case op
|
@@ -167,8 +193,8 @@ module Sequel
|
|
167
193
|
|
168
194
|
# When returning all rows, if an offset is used, delete the row_number column
|
169
195
|
# before yielding the row.
|
170
|
-
def
|
171
|
-
@opts[:offset] ? super{|r| r.delete(row_number_column); yield r} : super(&block)
|
196
|
+
def fetch_rows(sql, &block)
|
197
|
+
@opts[:offset] ? super(sql){|r| r.delete(row_number_column); yield r} : super(sql, &block)
|
172
198
|
end
|
173
199
|
|
174
200
|
# MSSQL uses the CONTAINS keyword for full text search
|
@@ -178,15 +204,43 @@ module Sequel
|
|
178
204
|
|
179
205
|
# MSSQL uses a UNION ALL statement to insert multiple values at once.
|
180
206
|
def multi_insert_sql(columns, values)
|
181
|
-
|
182
|
-
["#{insert_sql_base}#{source_list(@opts[:from])} (#{identifier_list(columns)}) #{values}"]
|
207
|
+
[insert_sql(columns, LiteralString.new(values.map {|r| "SELECT #{expression_list(r)}" }.join(" UNION ALL ")))]
|
183
208
|
end
|
184
209
|
|
185
210
|
# Allows you to do .nolock on a query
|
186
211
|
def nolock
|
187
212
|
clone(:table_options => "(NOLOCK)")
|
188
213
|
end
|
189
|
-
|
214
|
+
|
215
|
+
# Include an OUTPUT clause in the eventual INSERT, UPDATE, or DELETE query.
|
216
|
+
#
|
217
|
+
# The first argument is the table to output into, and the second argument
|
218
|
+
# is either an Array of column values to select, or a Hash which maps output
|
219
|
+
# column names to selected values, in the style of #insert or #update.
|
220
|
+
#
|
221
|
+
# Output into a returned result set is not currently supported.
|
222
|
+
#
|
223
|
+
# Examples:
|
224
|
+
#
|
225
|
+
# dataset.output(:output_table, [:deleted__id, :deleted__name])
|
226
|
+
# dataset.output(:output_table, :id => :inserted__id, :name => :inserted__name)
|
227
|
+
def output(into, values)
|
228
|
+
output = {}
|
229
|
+
case values
|
230
|
+
when Hash:
|
231
|
+
output[:column_list], output[:select_list] = values.keys, values.values
|
232
|
+
when Array:
|
233
|
+
output[:select_list] = values
|
234
|
+
end
|
235
|
+
output[:into] = into
|
236
|
+
clone({:output => output})
|
237
|
+
end
|
238
|
+
|
239
|
+
# An output method that modifies the receiver.
|
240
|
+
def output!(into, values)
|
241
|
+
mutation_method(:output, into, values)
|
242
|
+
end
|
243
|
+
|
190
244
|
# MSSQL uses [] to quote identifiers
|
191
245
|
def quoted_identifier(name)
|
192
246
|
"[#{name}]"
|
@@ -218,6 +272,11 @@ module Sequel
|
|
218
272
|
select_sql
|
219
273
|
end
|
220
274
|
|
275
|
+
# The version of the database server.
|
276
|
+
def server_version
|
277
|
+
db.server_version(@opts[:server])
|
278
|
+
end
|
279
|
+
|
221
280
|
# Microsoft SQL Server does not support INTERSECT or EXCEPT
|
222
281
|
def supports_intersect_except?
|
223
282
|
false
|
@@ -227,19 +286,56 @@ module Sequel
|
|
227
286
|
def supports_is_true?
|
228
287
|
false
|
229
288
|
end
|
230
|
-
|
231
|
-
# MSSQL supports timezones in literal timestamps
|
232
|
-
def supports_timestamp_timezones?
|
233
|
-
true
|
234
|
-
end
|
235
|
-
|
289
|
+
|
236
290
|
# MSSQL 2005+ supports window functions
|
237
291
|
def supports_window_functions?
|
238
292
|
true
|
239
293
|
end
|
240
294
|
|
241
295
|
private
|
296
|
+
|
297
|
+
# MSSQL can modify joined datasets
|
298
|
+
def check_modification_allowed!
|
299
|
+
raise(InvalidOperation, "Grouped datasets cannot be modified") if opts[:group]
|
300
|
+
end
|
301
|
+
|
302
|
+
# MSSQL supports the OUTPUT clause for DELETE statements.
|
303
|
+
# It also allows prepending a WITH clause.
|
304
|
+
def delete_clause_methods
|
305
|
+
DELETE_CLAUSE_METHODS
|
306
|
+
end
|
307
|
+
|
308
|
+
# Handle the with clause for delete, insert, and update statements
|
309
|
+
# to be the same as the insert statement.
|
310
|
+
def delete_with_sql(sql)
|
311
|
+
select_with_sql(sql)
|
312
|
+
end
|
313
|
+
alias insert_with_sql delete_with_sql
|
314
|
+
alias update_with_sql delete_with_sql
|
242
315
|
|
316
|
+
# MSSQL raises an error if you try to provide more than 3 decimal places
|
317
|
+
# for a fractional timestamp. This probably doesn't work for smalldatetime
|
318
|
+
# fields.
|
319
|
+
def format_timestamp_usec(usec)
|
320
|
+
sprintf(".%03d", usec/1000)
|
321
|
+
end
|
322
|
+
|
323
|
+
# MSSQL supports FROM clauses in DELETE and UPDATE statements.
|
324
|
+
def from_sql(sql)
|
325
|
+
if (opts[:from].is_a?(Array) && opts[:from].size > 1) || opts[:join]
|
326
|
+
select_from_sql(sql)
|
327
|
+
select_join_sql(sql)
|
328
|
+
end
|
329
|
+
end
|
330
|
+
alias delete_from2_sql from_sql
|
331
|
+
alias update_from_sql from_sql
|
332
|
+
|
333
|
+
# MSSQL supports the OUTPUT clause for INSERT statements.
|
334
|
+
# It also allows prepending a WITH clause.
|
335
|
+
def insert_clause_methods
|
336
|
+
INSERT_CLAUSE_METHODS
|
337
|
+
end
|
338
|
+
|
243
339
|
# MSSQL uses a literal hexidecimal number for blob strings
|
244
340
|
def literal_blob(v)
|
245
341
|
blob = '0x'
|
@@ -252,20 +348,10 @@ module Sequel
|
|
252
348
|
"N#{super}"
|
253
349
|
end
|
254
350
|
|
255
|
-
# Use MSSQL Timestamp format
|
256
|
-
def literal_datetime(v)
|
257
|
-
v.strftime(TIMESTAMP_FORMAT)
|
258
|
-
end
|
259
|
-
|
260
351
|
# Use 0 for false on MSSQL
|
261
352
|
def literal_false
|
262
353
|
BOOL_FALSE
|
263
354
|
end
|
264
|
-
|
265
|
-
# Use MSSQL Timestamp format
|
266
|
-
def literal_time(v)
|
267
|
-
v.strftime(TIMESTAMP_FORMAT)
|
268
|
-
end
|
269
355
|
|
270
356
|
# Use 1 for true on MSSQL
|
271
357
|
def literal_true
|
@@ -278,8 +364,8 @@ module Sequel
|
|
278
364
|
end
|
279
365
|
|
280
366
|
# MSSQL adds the limit before the columns
|
281
|
-
def
|
282
|
-
|
367
|
+
def select_clause_methods
|
368
|
+
SELECT_CLAUSE_METHODS
|
283
369
|
end
|
284
370
|
|
285
371
|
# MSSQL uses TOP for limit
|
@@ -291,6 +377,29 @@ module Sequel
|
|
291
377
|
def select_table_options_sql(sql)
|
292
378
|
sql << " WITH #{@opts[:table_options]}" if @opts[:table_options]
|
293
379
|
end
|
380
|
+
|
381
|
+
# SQL fragment for MSSQL's OUTPUT clause.
|
382
|
+
def output_sql(sql)
|
383
|
+
return unless output = @opts[:output]
|
384
|
+
sql << " OUTPUT #{column_list(output[:select_list])}"
|
385
|
+
if into = output[:into]
|
386
|
+
sql << " INTO #{table_ref(into)}"
|
387
|
+
if column_list = output[:column_list]
|
388
|
+
cl = []
|
389
|
+
column_list.each { |k, v| cl << literal(String === k ? k.to_sym : k) }
|
390
|
+
sql << " (#{cl.join(COMMA_SEPARATOR)})"
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|
394
|
+
alias delete_output_sql output_sql
|
395
|
+
alias update_output_sql output_sql
|
396
|
+
alias insert_output_sql output_sql
|
397
|
+
|
398
|
+
# MSSQL supports the OUTPUT clause for UPDATE statements.
|
399
|
+
# It also allows prepending a WITH clause.
|
400
|
+
def update_clause_methods
|
401
|
+
UPDATE_CLAUSE_METHODS
|
402
|
+
end
|
294
403
|
end
|
295
404
|
end
|
296
405
|
end
|