sequel 2.7.1 → 2.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|