sequel 2.7.1 → 2.8.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 +56 -0
- data/README +1 -0
- data/Rakefile +1 -1
- data/lib/sequel_core.rb +9 -16
- data/lib/sequel_core/adapters/ado.rb +6 -15
- data/lib/sequel_core/adapters/db2.rb +8 -10
- data/lib/sequel_core/adapters/dbi.rb +6 -4
- data/lib/sequel_core/adapters/informix.rb +21 -22
- data/lib/sequel_core/adapters/jdbc.rb +69 -10
- data/lib/sequel_core/adapters/jdbc/postgresql.rb +1 -0
- data/lib/sequel_core/adapters/mysql.rb +81 -13
- data/lib/sequel_core/adapters/odbc.rb +32 -4
- data/lib/sequel_core/adapters/openbase.rb +6 -5
- data/lib/sequel_core/adapters/oracle.rb +23 -7
- data/lib/sequel_core/adapters/postgres.rb +42 -32
- data/lib/sequel_core/adapters/shared/mssql.rb +37 -62
- data/lib/sequel_core/adapters/shared/mysql.rb +22 -7
- data/lib/sequel_core/adapters/shared/oracle.rb +27 -48
- data/lib/sequel_core/adapters/shared/postgres.rb +64 -43
- data/lib/sequel_core/adapters/shared/progress.rb +31 -0
- data/lib/sequel_core/adapters/shared/sqlite.rb +15 -4
- data/lib/sequel_core/adapters/sqlite.rb +6 -14
- data/lib/sequel_core/connection_pool.rb +47 -13
- data/lib/sequel_core/database.rb +60 -35
- data/lib/sequel_core/database/schema.rb +4 -4
- data/lib/sequel_core/dataset.rb +12 -3
- data/lib/sequel_core/dataset/convenience.rb +4 -13
- data/lib/sequel_core/dataset/prepared_statements.rb +30 -28
- data/lib/sequel_core/dataset/sql.rb +144 -85
- data/lib/sequel_core/dataset/stored_procedures.rb +75 -0
- data/lib/sequel_core/dataset/unsupported.rb +31 -0
- data/lib/sequel_core/exceptions.rb +6 -0
- data/lib/sequel_core/schema/generator.rb +4 -3
- data/lib/sequel_core/schema/sql.rb +41 -23
- data/lib/sequel_core/sql.rb +29 -1
- data/lib/sequel_model/associations.rb +1 -1
- data/lib/sequel_model/record.rb +31 -28
- data/spec/adapters/mysql_spec.rb +37 -4
- data/spec/adapters/oracle_spec.rb +26 -4
- data/spec/adapters/sqlite_spec.rb +7 -0
- data/spec/integration/prepared_statement_test.rb +24 -0
- data/spec/integration/schema_test.rb +1 -1
- data/spec/sequel_core/connection_pool_spec.rb +49 -2
- data/spec/sequel_core/core_sql_spec.rb +9 -2
- data/spec/sequel_core/database_spec.rb +64 -14
- data/spec/sequel_core/dataset_spec.rb +105 -7
- data/spec/sequel_core/schema_spec.rb +40 -12
- data/spec/sequel_core/spec_helper.rb +1 -0
- data/spec/sequel_model/spec_helper.rb +1 -0
- metadata +6 -3
@@ -14,6 +14,9 @@ module Sequel
|
|
14
14
|
when 'mssql'
|
15
15
|
require 'sequel_core/adapters/shared/mssql'
|
16
16
|
extend Sequel::MSSQL::DatabaseMethods
|
17
|
+
when 'progress'
|
18
|
+
require 'sequel_core/adapters/shared/progress'
|
19
|
+
extend Sequel::Progress::DatabaseMethods
|
17
20
|
end
|
18
21
|
end
|
19
22
|
|
@@ -37,10 +40,6 @@ module Sequel
|
|
37
40
|
conn
|
38
41
|
end
|
39
42
|
|
40
|
-
def disconnect
|
41
|
-
@pool.disconnect {|c| c.disconnect}
|
42
|
-
end
|
43
|
-
|
44
43
|
def dataset(opts = nil)
|
45
44
|
ODBC::Dataset.new(self, opts)
|
46
45
|
end
|
@@ -63,6 +62,35 @@ module Sequel
|
|
63
62
|
synchronize(opts[:server]){|conn| conn.do(sql)}
|
64
63
|
end
|
65
64
|
alias_method :do, :execute_dui
|
65
|
+
|
66
|
+
# Support single level transactions on ODBC
|
67
|
+
def transaction(server=nil)
|
68
|
+
synchronize(server) do |conn|
|
69
|
+
return yield(conn) if @transactions.include?(Thread.current)
|
70
|
+
log_info(begin_transaction_sql)
|
71
|
+
conn.do(begin_transaction_sql)
|
72
|
+
begin
|
73
|
+
@transactions << Thread.current
|
74
|
+
yield(conn)
|
75
|
+
rescue ::Exception => e
|
76
|
+
log_info(rollback_transaction_sql)
|
77
|
+
conn.do(rollback_transaction_sql)
|
78
|
+
transaction_error(e)
|
79
|
+
ensure
|
80
|
+
unless e
|
81
|
+
log_info(commit_transaction_sql)
|
82
|
+
conn.do(commit_transaction_sql)
|
83
|
+
end
|
84
|
+
@transactions.delete(Thread.current)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def disconnect_connection(c)
|
92
|
+
c.disconnect
|
93
|
+
end
|
66
94
|
end
|
67
95
|
|
68
96
|
class Dataset < Sequel::Dataset
|
@@ -15,11 +15,6 @@ module Sequel
|
|
15
15
|
)
|
16
16
|
end
|
17
17
|
|
18
|
-
def disconnect
|
19
|
-
# would this work?
|
20
|
-
@pool.disconnect {|c| c.disconnect}
|
21
|
-
end
|
22
|
-
|
23
18
|
def dataset(opts = nil)
|
24
19
|
OpenBase::Dataset.new(self, opts)
|
25
20
|
end
|
@@ -33,6 +28,12 @@ module Sequel
|
|
33
28
|
end
|
34
29
|
end
|
35
30
|
alias_method :do, :execute
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def disconnect_connection(c)
|
35
|
+
c.disconnect
|
36
|
+
end
|
36
37
|
end
|
37
38
|
|
38
39
|
class Dataset < Sequel::Dataset
|
@@ -6,6 +6,12 @@ module Sequel
|
|
6
6
|
class Database < Sequel::Database
|
7
7
|
include DatabaseMethods
|
8
8
|
set_adapter_scheme :oracle
|
9
|
+
|
10
|
+
# ORA-00028: your session has been killed
|
11
|
+
# ORA-01012: not logged on
|
12
|
+
# ORA-03113: end-of-file on communication channel
|
13
|
+
# ORA-03114: not connected to ORACLE
|
14
|
+
CONNECTION_ERROR_CODES = [ 28, 1012, 3113, 3114 ]
|
9
15
|
|
10
16
|
def connect(server)
|
11
17
|
opts = server_opts(server)
|
@@ -21,10 +27,6 @@ module Sequel
|
|
21
27
|
conn
|
22
28
|
end
|
23
29
|
|
24
|
-
def disconnect
|
25
|
-
@pool.disconnect {|c| c.logoff}
|
26
|
-
end
|
27
|
-
|
28
30
|
def dataset(opts = nil)
|
29
31
|
Oracle::Dataset.new(self, opts)
|
30
32
|
end
|
@@ -32,9 +34,17 @@ module Sequel
|
|
32
34
|
def execute(sql, opts={})
|
33
35
|
log_info(sql)
|
34
36
|
synchronize(opts[:server]) do |conn|
|
35
|
-
|
36
|
-
|
37
|
-
|
37
|
+
begin
|
38
|
+
r = conn.exec(sql)
|
39
|
+
yield(r) if block_given?
|
40
|
+
r
|
41
|
+
rescue OCIException => e
|
42
|
+
if CONNECTION_ERROR_CODES.include?(e.code)
|
43
|
+
raise(Sequel::DatabaseDisconnectError)
|
44
|
+
else
|
45
|
+
raise
|
46
|
+
end
|
47
|
+
end
|
38
48
|
end
|
39
49
|
end
|
40
50
|
alias_method :do, :execute
|
@@ -57,6 +67,12 @@ module Sequel
|
|
57
67
|
end
|
58
68
|
end
|
59
69
|
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def disconnect_connection(c)
|
74
|
+
c.logoff
|
75
|
+
end
|
60
76
|
end
|
61
77
|
|
62
78
|
class Dataset < Sequel::Dataset
|
@@ -62,6 +62,9 @@ rescue LoadError => e
|
|
62
62
|
def block(timeout=nil)
|
63
63
|
end
|
64
64
|
end
|
65
|
+
unless defined?(CONNECTION_OK)
|
66
|
+
CONNECTION_OK = -1
|
67
|
+
end
|
65
68
|
end
|
66
69
|
class PGresult
|
67
70
|
alias_method :nfields, :num_fields unless method_defined?(:nfields)
|
@@ -97,7 +100,6 @@ module Sequel
|
|
97
100
|
1083 => lambda{ |s| s.to_time }, # time without time zone
|
98
101
|
1114 => lambda{ |s| s.to_sequel_time }, # timestamp without time zone
|
99
102
|
1184 => lambda{ |s| s.to_sequel_time }, # timestamp with time zone
|
100
|
-
1186 => lambda{ |s| s.to_i }, # interval
|
101
103
|
1266 => lambda{ |s| s.to_time }, # time with time zone
|
102
104
|
1700 => lambda{ |s| s.to_d }, # numeric
|
103
105
|
}
|
@@ -119,7 +121,12 @@ module Sequel
|
|
119
121
|
# the date style to ISO in order make Date object creation in ruby faster,
|
120
122
|
# if Postgres.use_iso_date_format is true.
|
121
123
|
def apply_connection_settings
|
122
|
-
|
124
|
+
super
|
125
|
+
if Postgres.use_iso_date_format
|
126
|
+
sql = "SET DateStyle = 'ISO'"
|
127
|
+
@db.log_info(sql)
|
128
|
+
execute(sql)
|
129
|
+
end
|
123
130
|
end
|
124
131
|
|
125
132
|
# Execute the given SQL with this connection. If a block is given,
|
@@ -128,12 +135,16 @@ module Sequel
|
|
128
135
|
q = nil
|
129
136
|
begin
|
130
137
|
q = args ? async_exec(sql, args) : async_exec(sql)
|
131
|
-
rescue PGError
|
132
|
-
|
133
|
-
|
134
|
-
|
138
|
+
rescue PGError
|
139
|
+
begin
|
140
|
+
s = status
|
141
|
+
rescue PGError
|
142
|
+
raise(Sequel::DatabaseDisconnectError)
|
143
|
+
end
|
144
|
+
status_ok = (s == Adapter::CONNECTION_OK)
|
145
|
+
status_ok ? raise : raise(Sequel::DatabaseDisconnectError)
|
135
146
|
ensure
|
136
|
-
block
|
147
|
+
block if status_ok
|
137
148
|
end
|
138
149
|
begin
|
139
150
|
block_given? ? yield(q) : q.cmd_tuples
|
@@ -200,8 +211,8 @@ module Sequel
|
|
200
211
|
conn.async_exec("set client_encoding to '#{encoding}'")
|
201
212
|
end
|
202
213
|
end
|
203
|
-
conn.apply_connection_settings
|
204
214
|
conn.db = self
|
215
|
+
conn.apply_connection_settings
|
205
216
|
conn
|
206
217
|
end
|
207
218
|
|
@@ -210,11 +221,6 @@ module Sequel
|
|
210
221
|
Postgres::Dataset.new(self, opts)
|
211
222
|
end
|
212
223
|
|
213
|
-
# Disconnect all active connections.
|
214
|
-
def disconnect
|
215
|
-
@pool.disconnect {|c| c.finish}
|
216
|
-
end
|
217
|
-
|
218
224
|
# Execute the given SQL with the given args on an available connection.
|
219
225
|
def execute(sql, opts={}, &block)
|
220
226
|
return execute_prepared_statement(sql, opts, &block) if Symbol === sql
|
@@ -250,6 +256,14 @@ module Sequel
|
|
250
256
|
super.merge(:pool_convert_exceptions=>false)
|
251
257
|
end
|
252
258
|
|
259
|
+
# Disconnect given connection
|
260
|
+
def disconnect_connection(conn)
|
261
|
+
begin
|
262
|
+
conn.finish
|
263
|
+
rescue PGError
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
253
267
|
# Execute the prepared statement with the given name on an available
|
254
268
|
# connection, using the given args. If the connection has not prepared
|
255
269
|
# a statement with the given name yet, prepare it. If the connection
|
@@ -329,11 +343,9 @@ module Sequel
|
|
329
343
|
# Return an array of strings for each of the hash values, inserting
|
330
344
|
# them to the correct position in the array.
|
331
345
|
def map_to_prepared_args(hash)
|
332
|
-
|
333
|
-
@prepared_args.each{|k,v| array[v] = hash[k].to_s}
|
334
|
-
array
|
346
|
+
@prepared_args.map{|k| hash[k.to_sym].to_s}
|
335
347
|
end
|
336
|
-
|
348
|
+
|
337
349
|
private
|
338
350
|
|
339
351
|
# PostgreSQL most of the time requires type information for each of
|
@@ -343,21 +355,16 @@ module Sequel
|
|
343
355
|
# elminate ambiguity (and PostgreSQL from raising an exception).
|
344
356
|
def prepared_arg(k)
|
345
357
|
y, type = k.to_s.split("__")
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
# next position in the array.
|
352
|
-
def prepared_args_hash
|
353
|
-
max_prepared_arg = 0
|
354
|
-
Hash.new do |h,k|
|
355
|
-
h[k] = max_prepared_arg
|
356
|
-
max_prepared_arg += 1
|
358
|
+
if i = @prepared_args.index(y)
|
359
|
+
i += 1
|
360
|
+
else
|
361
|
+
@prepared_args << y
|
362
|
+
i = @prepared_args.length
|
357
363
|
end
|
364
|
+
"#{prepared_arg_placeholder}#{i}#{"::#{type}" if type}".lit
|
358
365
|
end
|
359
366
|
end
|
360
|
-
|
367
|
+
|
361
368
|
# Allow use of bind arguments for PostgreSQL using the pg driver.
|
362
369
|
module BindArgumentMethods
|
363
370
|
include ArgumentMapper
|
@@ -414,11 +421,14 @@ module Sequel
|
|
414
421
|
|
415
422
|
# Prepare the given type of statement with the given name, and store
|
416
423
|
# it in the database to be called later.
|
417
|
-
def prepare(type, name, values=nil)
|
424
|
+
def prepare(type, name=nil, values=nil)
|
418
425
|
ps = to_prepared_statement(type, values)
|
419
426
|
ps.extend(PreparedStatementMethods)
|
420
|
-
|
421
|
-
|
427
|
+
if name
|
428
|
+
ps.prepared_statement_name = name
|
429
|
+
db.prepared_statements[name] = ps
|
430
|
+
end
|
431
|
+
ps
|
422
432
|
end
|
423
433
|
|
424
434
|
private
|
@@ -35,6 +35,10 @@ module Sequel
|
|
35
35
|
end
|
36
36
|
|
37
37
|
module DatasetMethods
|
38
|
+
include Dataset::UnsupportedIntersectExcept
|
39
|
+
|
40
|
+
SELECT_CLAUSE_ORDER = %w'limit distinct columns from with join where group order having union'.freeze
|
41
|
+
|
38
42
|
def complex_expression_sql(op, args)
|
39
43
|
case op
|
40
44
|
when :'||'
|
@@ -48,77 +52,48 @@ module Sequel
|
|
48
52
|
filter("CONTAINS (#{literal(cols)}, #{literal(terms)})")
|
49
53
|
end
|
50
54
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
opts = opts ? @opts.merge(opts) : @opts
|
60
|
-
|
61
|
-
if sql = opts[:sql]
|
62
|
-
return sql
|
63
|
-
end
|
64
|
-
|
65
|
-
# ADD TOP to SELECT string for LIMITS
|
66
|
-
if limit = opts[:limit]
|
67
|
-
top = "TOP #{limit} "
|
68
|
-
raise Error, "Offset not supported" if opts[:offset]
|
69
|
-
end
|
70
|
-
|
71
|
-
columns = opts[:select]
|
72
|
-
# We had to reference const WILDCARD with its full path, because
|
73
|
-
# the Ruby constant scope rules played against us (it was resolving it
|
74
|
-
# as Sequel::Dataset::DatasetMethods::WILDCARD).
|
75
|
-
select_columns = columns ? column_list(columns) : Sequel::Dataset::WILDCARD
|
76
|
-
|
77
|
-
if distinct = opts[:distinct]
|
78
|
-
distinct_clause = distinct.empty? ? "DISTINCT" : "DISTINCT ON (#{expression_list(distinct)})"
|
79
|
-
sql = "SELECT #{top}#{distinct_clause} #{select_columns}"
|
55
|
+
def literal(v)
|
56
|
+
case v
|
57
|
+
when String
|
58
|
+
"N#{super}"
|
59
|
+
when Time
|
60
|
+
literal(v.iso8601)
|
61
|
+
when Date, DateTime
|
62
|
+
literal(v.to_s)
|
80
63
|
else
|
81
|
-
|
82
|
-
end
|
83
|
-
|
84
|
-
if opts[:from]
|
85
|
-
sql << " FROM #{source_list(opts[:from])}"
|
64
|
+
super
|
86
65
|
end
|
66
|
+
end
|
87
67
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
68
|
+
def multi_insert_sql(columns, values)
|
69
|
+
values = values.map {|r| "SELECT #{expression_list(r)}" }.join(" UNION ALL ")
|
70
|
+
["INSERT INTO #{source_list(@opts[:from])} (#{identifier_list(columns)}) #{values}"]
|
71
|
+
end
|
92
72
|
|
93
|
-
|
94
|
-
|
95
|
-
|
73
|
+
# Allows you to do .nolock on a query
|
74
|
+
def nolock
|
75
|
+
clone(:with => "(NOLOCK)")
|
76
|
+
end
|
96
77
|
|
97
|
-
|
98
|
-
|
99
|
-
|
78
|
+
def quoted_identifier(name)
|
79
|
+
"[#{name}]"
|
80
|
+
end
|
100
81
|
|
101
|
-
|
102
|
-
sql << " GROUP BY #{expression_list(group)}"
|
103
|
-
end
|
82
|
+
private
|
104
83
|
|
105
|
-
|
106
|
-
|
107
|
-
|
84
|
+
def select_clause_order
|
85
|
+
SELECT_CLAUSE_ORDER
|
86
|
+
end
|
108
87
|
|
109
|
-
|
110
|
-
|
111
|
-
|
88
|
+
# MSSQL uses TOP for limit, with no offset support
|
89
|
+
def select_limit_sql(sql, opts)
|
90
|
+
raise(Error, "OFFSET not supported") if opts[:offset]
|
91
|
+
sql << " TOP #{opts[:limit]}" if opts[:limit]
|
92
|
+
end
|
112
93
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
end
|
117
|
-
|
118
|
-
raise Error, "Intersect not supported" if opts[:intersect]
|
119
|
-
raise Error, "Except not supported" if opts[:except]
|
120
|
-
|
121
|
-
sql
|
94
|
+
# MSSQL uses the WITH statement to lock tables
|
95
|
+
def select_with_sql(sql, opts)
|
96
|
+
sql << " WITH #{opts[:with]}" if opts[:with]
|
122
97
|
end
|
123
98
|
end
|
124
99
|
end
|
@@ -14,15 +14,13 @@ module Sequel
|
|
14
14
|
# Use MySQL specific syntax for rename column, set column type, and
|
15
15
|
# drop index cases.
|
16
16
|
def alter_table_sql(table, op)
|
17
|
-
quoted_table = quote_identifier(table)
|
18
|
-
quoted_name = quote_identifier(op[:name]) if op[:name]
|
19
17
|
case op[:op]
|
20
18
|
when :rename_column
|
21
|
-
"ALTER TABLE #{
|
19
|
+
"ALTER TABLE #{quote_schema_table(table)} CHANGE COLUMN #{quote_identifier(op[:name])} #{quote_identifier(op[:new_name])} #{type_literal(op)}"
|
22
20
|
when :set_column_type
|
23
|
-
"ALTER TABLE #{
|
21
|
+
"ALTER TABLE #{quote_schema_table(table)} CHANGE COLUMN #{quote_identifier(op[:name])} #{quote_identifier(op[:name])} #{type_literal(op)}"
|
24
22
|
when :drop_index
|
25
|
-
"#{drop_index_sql(table, op)} ON #{
|
23
|
+
"#{drop_index_sql(table, op)} ON #{quote_schema_table(table)}"
|
26
24
|
else
|
27
25
|
super(table, op)
|
28
26
|
end
|
@@ -50,7 +48,7 @@ module Sequel
|
|
50
48
|
using = " USING #{index[:type]}" unless index[:type] == nil
|
51
49
|
"UNIQUE " if index[:unique]
|
52
50
|
end
|
53
|
-
"CREATE #{index_type}INDEX #{index_name} ON #{
|
51
|
+
"CREATE #{index_type}INDEX #{index_name} ON #{quote_schema_table(table_name)} #{literal(index[:columns])}#{using}"
|
54
52
|
end
|
55
53
|
|
56
54
|
# Get version of MySQL server, used for determined capabilities.
|
@@ -75,9 +73,14 @@ module Sequel
|
|
75
73
|
|
76
74
|
private
|
77
75
|
|
76
|
+
# MySQL folds unquoted identifiers to lowercase, so it shouldn't need to upcase identifiers by default.
|
77
|
+
def upcase_identifiers_default
|
78
|
+
false
|
79
|
+
end
|
80
|
+
|
78
81
|
# Use the MySQL specific DESCRIBE syntax to get a table description.
|
79
82
|
def schema_parse_table(table_name, opts)
|
80
|
-
self["DESCRIBE ?", table_name].map do |row|
|
83
|
+
self["DESCRIBE ?", SQL::Identifier.new(table_name)].map do |row|
|
81
84
|
row.delete(:Extra)
|
82
85
|
row[:allow_null] = row.delete(:Null) == 'YES'
|
83
86
|
row[:default] = row.delete(:Default)
|
@@ -92,6 +95,8 @@ module Sequel
|
|
92
95
|
|
93
96
|
# Dataset methods shared by datasets that use MySQL databases.
|
94
97
|
module DatasetMethods
|
98
|
+
include Dataset::UnsupportedIntersectExcept
|
99
|
+
|
95
100
|
BOOL_TRUE = '1'.freeze
|
96
101
|
BOOL_FALSE = '0'.freeze
|
97
102
|
COMMA_SEPARATOR = ', '.freeze
|
@@ -250,6 +255,16 @@ module Sequel
|
|
250
255
|
|
251
256
|
sql
|
252
257
|
end
|
258
|
+
|
259
|
+
private
|
260
|
+
|
261
|
+
# MySQL doesn't support DISTINCT ON
|
262
|
+
def select_distinct_sql(sql, opts)
|
263
|
+
if opts[:distinct]
|
264
|
+
raise(Error, "DISTINCT ON not supported by MySQL") unless opts[:distinct].empty?
|
265
|
+
sql << " DISTINCT"
|
266
|
+
end
|
267
|
+
end
|
253
268
|
end
|
254
269
|
end
|
255
270
|
end
|