sequel 3.4.0 → 3.5.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 +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
|