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
@@ -13,69 +13,48 @@ module Sequel
|
|
13
13
|
end
|
14
14
|
|
15
15
|
module DatasetMethods
|
16
|
+
include Dataset::UnsupportedIntersectExceptAll
|
17
|
+
|
18
|
+
SELECT_CLAUSE_ORDER = %w'distinct columns from join where group having union intersect except order limit'.freeze
|
19
|
+
|
16
20
|
def empty?
|
17
21
|
db[:dual].where(exists).get(1) == nil
|
18
22
|
end
|
19
23
|
|
20
|
-
|
21
|
-
# options.
|
22
|
-
def select_sql(opts = nil)
|
23
|
-
opts = opts ? @opts.merge(opts) : @opts
|
24
|
-
|
25
|
-
if sql = opts[:sql]
|
26
|
-
return sql
|
27
|
-
end
|
28
|
-
|
29
|
-
columns = opts[:select]
|
30
|
-
select_columns = columns ? column_list(columns) : '*'
|
31
|
-
sql = opts[:distinct] ? \
|
32
|
-
"SELECT DISTINCT #{select_columns}" : \
|
33
|
-
"SELECT #{select_columns}"
|
34
|
-
|
35
|
-
if opts[:from]
|
36
|
-
sql << " FROM #{source_list(opts[:from])}"
|
37
|
-
end
|
38
|
-
|
39
|
-
if join = opts[:join]
|
40
|
-
join.each{|j| sql << literal(j)}
|
41
|
-
end
|
42
|
-
|
43
|
-
if where = opts[:where]
|
44
|
-
sql << " WHERE #{literal(where)}"
|
45
|
-
end
|
24
|
+
private
|
46
25
|
|
47
|
-
|
48
|
-
|
49
|
-
|
26
|
+
# Oracle doesn't support the use of AS when aliasing a dataset. It doesn't require
|
27
|
+
# the use of AS anywhere, so this disables it in all cases.
|
28
|
+
def as_sql(expression, aliaz)
|
29
|
+
"#{expression} #{quote_identifier(aliaz)}"
|
30
|
+
end
|
50
31
|
|
51
|
-
|
52
|
-
|
53
|
-
|
32
|
+
def select_clause_order
|
33
|
+
SELECT_CLAUSE_ORDER
|
34
|
+
end
|
54
35
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
sql <<
|
60
|
-
" INTERSECT ALL #{intersect.sql}" : " INTERSECT #{intersect.sql}")
|
61
|
-
elsif except = opts[:except]
|
62
|
-
sql << (opts[:except_all] ? \
|
63
|
-
" EXCEPT ALL #{except.sql}" : " EXCEPT #{except.sql}")
|
36
|
+
# Oracle doesn't support DISTINCT ON
|
37
|
+
def select_distinct_sql(sql, opts)
|
38
|
+
if opts[:distinct]
|
39
|
+
raise(Error, "DISTINCT ON not supported by Oracle") unless opts[:distinct].empty?
|
40
|
+
sql << " DISTINCT"
|
64
41
|
end
|
42
|
+
end
|
65
43
|
|
66
|
-
|
67
|
-
|
68
|
-
|
44
|
+
# Oracle uses MINUS instead of EXCEPT, and doesn't support EXCEPT ALL
|
45
|
+
def select_except_sql(sql, opts)
|
46
|
+
sql << " MINUS #{opts[:except].sql}" if opts[:except]
|
47
|
+
end
|
69
48
|
|
49
|
+
# Oracle requires a subselect to do limit and offset
|
50
|
+
def select_limit_sql(sql, opts)
|
70
51
|
if limit = opts[:limit]
|
71
52
|
if (offset = opts[:offset]) && (offset > 0)
|
72
|
-
sql
|
53
|
+
sql.replace("SELECT * FROM (SELECT raw_sql_.*, ROWNUM raw_rnum_ FROM(#{sql}) raw_sql_ WHERE ROWNUM <= #{limit + offset}) WHERE raw_rnum_ > #{offset}")
|
73
54
|
else
|
74
|
-
sql
|
55
|
+
sql.replace("SELECT * FROM (#{sql}) WHERE ROWNUM <= #{limit}")
|
75
56
|
end
|
76
57
|
end
|
77
|
-
|
78
|
-
sql
|
79
58
|
end
|
80
59
|
end
|
81
60
|
end
|
@@ -4,6 +4,15 @@ module Sequel
|
|
4
4
|
# uses NativeExceptions, the native adapter uses PGError.
|
5
5
|
CONVERTED_EXCEPTIONS = []
|
6
6
|
|
7
|
+
@force_standard_strings = true
|
8
|
+
|
9
|
+
# By default, Sequel forces the use of standard strings, so that
|
10
|
+
# '\\' is interpreted as '\\' and not '\\'. While PostgreSQL defaults
|
11
|
+
# to interpreting plain strings as extended strings, this will change
|
12
|
+
# in a future version of PostgreSQL. Sequel assumes that SQL standard
|
13
|
+
# strings will be used.
|
14
|
+
metaattr_accessor :force_standard_strings
|
15
|
+
|
7
16
|
# Methods shared by adapter/connection instances.
|
8
17
|
module AdapterMethods
|
9
18
|
attr_writer :db
|
@@ -57,6 +66,20 @@ module Sequel
|
|
57
66
|
# to implement multi-level transactions with savepoints.
|
58
67
|
attr_accessor :transaction_depth
|
59
68
|
|
69
|
+
# Apply connection settings for this connection. Currently, turns
|
70
|
+
# standard_conforming_strings ON if Postgres.force_standard_strings
|
71
|
+
# is true.
|
72
|
+
def apply_connection_settings
|
73
|
+
if Postgres.force_standard_strings
|
74
|
+
sql = "SET standard_conforming_strings = ON"
|
75
|
+
@db.log_info(sql)
|
76
|
+
# This setting will only work on PostgreSQL 8.2 or greater
|
77
|
+
# and we don't know the server version at this point, so
|
78
|
+
# try it unconditionally and rescue any errors.
|
79
|
+
execute(sql) rescue nil
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
60
83
|
# Get the last inserted value for the given sequence.
|
61
84
|
def last_insert_id(sequence)
|
62
85
|
sql = SELECT_CURRVAL % sequence
|
@@ -67,6 +90,15 @@ module Sequel
|
|
67
90
|
end
|
68
91
|
end
|
69
92
|
|
93
|
+
# Get the primary key for the given table.
|
94
|
+
def primary_key(schema, table)
|
95
|
+
sql = SELECT_PK % [schema, table]
|
96
|
+
@db.log_info(sql)
|
97
|
+
execute(sql) do |r|
|
98
|
+
return single_value(r)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
70
102
|
# Get the primary key and sequence for the given table.
|
71
103
|
def sequence(schema, table)
|
72
104
|
sql = SELECT_SERIAL_SEQUENCE % [schema, table]
|
@@ -82,15 +114,6 @@ module Sequel
|
|
82
114
|
return single_value(r)
|
83
115
|
end
|
84
116
|
end
|
85
|
-
|
86
|
-
# Get the primary key for the given table.
|
87
|
-
def primary_key(schema, table)
|
88
|
-
sql = SELECT_PK % [schema, table]
|
89
|
-
@db.log_info(sql)
|
90
|
-
execute(sql) do |r|
|
91
|
-
return single_value(r)
|
92
|
-
end
|
93
|
-
end
|
94
117
|
end
|
95
118
|
|
96
119
|
# Methods shared by Database instances that connect to PostgreSQL.
|
@@ -110,11 +133,6 @@ module Sequel
|
|
110
133
|
@default_schema ||= :public
|
111
134
|
end
|
112
135
|
|
113
|
-
# Set a new default schema to use.
|
114
|
-
def default_schema=(schema)
|
115
|
-
@default_schema = schema
|
116
|
-
end
|
117
|
-
|
118
136
|
# Remove the cached entries for primary keys and sequences when dropping a table.
|
119
137
|
def drop_table(*names)
|
120
138
|
names.each do |name|
|
@@ -161,12 +179,11 @@ module Sequel
|
|
161
179
|
synchronize(server){|conn| primary_key_for_table(conn, table)}
|
162
180
|
end
|
163
181
|
|
164
|
-
#
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
end
|
182
|
+
# SQL DDL statement for renaming a table. PostgreSQL doesn't allow you to change a table's schema in
|
183
|
+
# a rename table operation, so speciying a new schema in new_name will not have an effect.
|
184
|
+
def rename_table_sql(name, new_name)
|
185
|
+
"ALTER TABLE #{quote_schema_table(name)} RENAME TO #{quote_identifier(schema_and_table(new_name).last)}"
|
186
|
+
end
|
170
187
|
|
171
188
|
# PostgreSQL uses SERIAL psuedo-type instead of AUTOINCREMENT for
|
172
189
|
# managing incrementing primary keys.
|
@@ -255,6 +272,11 @@ module Sequel
|
|
255
272
|
|
256
273
|
private
|
257
274
|
|
275
|
+
# PostgreSQL folds unquoted identifiers to lowercase, so it shouldn't need to upcase identifiers by default.
|
276
|
+
def upcase_identifiers_default
|
277
|
+
false
|
278
|
+
end
|
279
|
+
|
258
280
|
# The result of the insert for the given table and values. If values
|
259
281
|
# is an array, assume the first column is the primary key and return
|
260
282
|
# that. If values is a hash, lookup the primary key for the table. If
|
@@ -322,7 +344,7 @@ module Sequel
|
|
322
344
|
def schema_parse_tables(opts)
|
323
345
|
schemas = {}
|
324
346
|
schema_parser_dataset(nil, opts).each do |row|
|
325
|
-
(schemas[row.delete(:table)
|
347
|
+
(schemas[quote_schema_table(SQL::QualifiedIdentifier.new(row.delete(:schema), row.delete(:table)))] ||= []) << row
|
326
348
|
end
|
327
349
|
schemas.each do |table, rows|
|
328
350
|
schemas[table] = schema_parse_rows(rows)
|
@@ -348,9 +370,9 @@ module Sequel
|
|
348
370
|
if table_name
|
349
371
|
ds.filter!(:pg_class__relname=>table_name.to_s)
|
350
372
|
else
|
351
|
-
ds.select_more!(:pg_class__relname___table)
|
373
|
+
ds.select_more!(:pg_class__relname___table, :pg_namespace__nspname___schema)
|
374
|
+
ds.join!(:pg_namespace, :oid=>:pg_class__relnamespace, :nspname=>(opts[:schema] || default_schema).to_s)
|
352
375
|
end
|
353
|
-
ds.join!(:pg_namespace, :oid=>:pg_class__relnamespace, :nspname=>(opts[:schema] || default_schema).to_s) if opts[:schema] || !opts.include?(:schema)
|
354
376
|
ds
|
355
377
|
end
|
356
378
|
end
|
@@ -372,6 +394,7 @@ module Sequel
|
|
372
394
|
QUERY_PLAN = 'QUERY PLAN'.to_sym
|
373
395
|
ROW_EXCLUSIVE = 'ROW EXCLUSIVE'.freeze
|
374
396
|
ROW_SHARE = 'ROW SHARE'.freeze
|
397
|
+
SELECT_CLAUSE_ORDER = %w'distinct columns from join where group having intersect union except order limit lock'.freeze
|
375
398
|
SHARE = 'SHARE'.freeze
|
376
399
|
SHARE_ROW_EXCLUSIVE = 'SHARE ROW EXCLUSIVE'.freeze
|
377
400
|
SHARE_UPDATE_EXCLUSIVE = 'SHARE UPDATE EXCLUSIVE'.freeze
|
@@ -452,8 +475,10 @@ module Sequel
|
|
452
475
|
case v
|
453
476
|
when LiteralString
|
454
477
|
v
|
478
|
+
when SQL::Blob
|
479
|
+
db.synchronize{|c| "E'#{c.escape_bytea(v)}'"}
|
455
480
|
when String
|
456
|
-
db.synchronize{|c| "'#{
|
481
|
+
db.synchronize{|c| "'#{c.escape_string(v)}'"}
|
457
482
|
when Time
|
458
483
|
"#{v.strftime(PG_TIMESTAMP_FORMAT)}.#{sprintf("%06d",v.usec)}'"
|
459
484
|
when DateTime
|
@@ -489,25 +514,6 @@ module Sequel
|
|
489
514
|
["INSERT INTO #{source_list(@opts[:from])} (#{identifier_list(columns)}) VALUES #{values}"]
|
490
515
|
end
|
491
516
|
|
492
|
-
# PostgreSQL assumes unquoted identifiers are lower case by default,
|
493
|
-
# so do not upcase the identifier when quoting it.
|
494
|
-
def quoted_identifier(c)
|
495
|
-
"\"#{c}\""
|
496
|
-
end
|
497
|
-
|
498
|
-
# Support lock mode, allowing FOR SHARE and FOR UPDATE queries.
|
499
|
-
def select_sql(opts = nil)
|
500
|
-
row_lock_mode = opts ? opts[:lock] : @opts[:lock]
|
501
|
-
sql = super
|
502
|
-
case row_lock_mode
|
503
|
-
when :update
|
504
|
-
sql << FOR_UPDATE
|
505
|
-
when :share
|
506
|
-
sql << FOR_SHARE
|
507
|
-
end
|
508
|
-
sql
|
509
|
-
end
|
510
|
-
|
511
517
|
private
|
512
518
|
|
513
519
|
# Call execute_insert on the database object with the given values.
|
@@ -521,6 +527,21 @@ module Sequel
|
|
521
527
|
insert_returning_sql(pk ? Sequel::SQL::Identifier.new(pk) : 'NULL'.lit, *values)
|
522
528
|
end
|
523
529
|
|
530
|
+
# The order of clauses in the SELECT SQL statement
|
531
|
+
def select_clause_order
|
532
|
+
SELECT_CLAUSE_ORDER
|
533
|
+
end
|
534
|
+
|
535
|
+
# Support lock mode, allowing FOR SHARE and FOR UPDATE queries.
|
536
|
+
def select_lock_sql(sql, opts)
|
537
|
+
case opts[:lock]
|
538
|
+
when :update
|
539
|
+
sql << FOR_UPDATE
|
540
|
+
when :share
|
541
|
+
sql << FOR_SHARE
|
542
|
+
end
|
543
|
+
end
|
544
|
+
|
524
545
|
# The version of the database server
|
525
546
|
def server_version
|
526
547
|
db.server_version(@opts[:server])
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Sequel
|
2
|
+
module Progress
|
3
|
+
module DatabaseMethods
|
4
|
+
|
5
|
+
def dataset(opts = nil)
|
6
|
+
ds = super
|
7
|
+
ds.extend(DatasetMethods)
|
8
|
+
ds
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module DatasetMethods
|
13
|
+
include Dataset::UnsupportedIntersectExcept
|
14
|
+
|
15
|
+
SELECT_CLAUSE_ORDER = %w'limit distinct columns from join where group order having union'.freeze
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def select_clause_order
|
20
|
+
SELECT_CLAUSE_ORDER
|
21
|
+
end
|
22
|
+
|
23
|
+
# Progress uses TOP for limit, but it is only supported in Progress 10.
|
24
|
+
# The Progress adapter targets Progress 9, so it silently ignores the option.
|
25
|
+
def select_limit_sql(sql, opts)
|
26
|
+
raise(Error, "OFFSET not supported") if opts[:offset]
|
27
|
+
#sql << " TOP #{opts[:limit]}" if opts[:limit]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -6,6 +6,12 @@ module Sequel
|
|
6
6
|
TABLES_FILTER = "type = 'table' AND NOT name = 'sqlite_sequence'"
|
7
7
|
TEMP_STORE = {'0' => :default, '1' => :file, '2' => :memory}.freeze
|
8
8
|
|
9
|
+
# Run all alter_table commands in a transaction. This is technically only
|
10
|
+
# needed for drop column.
|
11
|
+
def alter_table(name, generator=nil, &block)
|
12
|
+
transaction{super}
|
13
|
+
end
|
14
|
+
|
9
15
|
# SQLite supports limited table modification. You can add a column
|
10
16
|
# or an index. Dropping columns is supported by copying the table into
|
11
17
|
# a temporary table, dropping the table, and creating a new table without
|
@@ -18,14 +24,12 @@ module Sequel
|
|
18
24
|
index_definition_sql(table, op)
|
19
25
|
when :drop_column
|
20
26
|
columns_str = (schema_parse_table(table, {}).map{|c| c[0]} - Array(op[:name])).join(",")
|
21
|
-
|
22
|
-
"CREATE TEMPORARY TABLE #{table}_backup(#{columns_str})",
|
27
|
+
["CREATE TEMPORARY TABLE #{table}_backup(#{columns_str})",
|
23
28
|
"INSERT INTO #{table}_backup SELECT #{columns_str} FROM #{table}",
|
24
29
|
"DROP TABLE #{table}",
|
25
30
|
"CREATE TABLE #{table}(#{columns_str})",
|
26
31
|
"INSERT INTO #{table} SELECT #{columns_str} FROM #{table}_backup",
|
27
|
-
"DROP TABLE #{table}_backup"
|
28
|
-
"COMMIT"]
|
32
|
+
"DROP TABLE #{table}_backup"]
|
29
33
|
else
|
30
34
|
raise Error, "Unsupported ALTER TABLE operation"
|
31
35
|
end
|
@@ -82,6 +86,11 @@ module Sequel
|
|
82
86
|
|
83
87
|
private
|
84
88
|
|
89
|
+
# SQLite folds unquoted identifiers to lowercase, so it shouldn't need to upcase identifiers by default.
|
90
|
+
def upcase_identifiers_default
|
91
|
+
false
|
92
|
+
end
|
93
|
+
|
85
94
|
# SQLite supports schema parsing using the table_info PRAGMA, so
|
86
95
|
# parse the output of that into the format Sequel expects.
|
87
96
|
def schema_parse_table(table_name, opts)
|
@@ -100,6 +109,8 @@ module Sequel
|
|
100
109
|
|
101
110
|
# Instance methods for datasets that connect to an SQLite database
|
102
111
|
module DatasetMethods
|
112
|
+
include Dataset::UnsupportedIntersectExceptAll
|
113
|
+
|
103
114
|
# SQLite does not support pattern matching via regular expressions.
|
104
115
|
# SQLite is case insensitive (depending on pragma), so use LIKE for
|
105
116
|
# ILIKE.
|
@@ -46,11 +46,6 @@ module Sequel
|
|
46
46
|
SQLite::Dataset.new(self, opts)
|
47
47
|
end
|
48
48
|
|
49
|
-
# Disconnect all connections from the database.
|
50
|
-
def disconnect
|
51
|
-
@pool.disconnect {|c| c.close}
|
52
|
-
end
|
53
|
-
|
54
49
|
# Run the given SQL with the given arguments and return the number of changed rows.
|
55
50
|
def execute_dui(sql, opts={})
|
56
51
|
_execute(sql, opts){|conn| conn.execute_batch(sql, opts[:arguments]); conn.changes}
|
@@ -110,6 +105,11 @@ module Sequel
|
|
110
105
|
o[:max_connections] = 1 if @opts[:database] == ':memory:' || @opts[:database].blank?
|
111
106
|
o
|
112
107
|
end
|
108
|
+
|
109
|
+
# Disconnect given connections from the database.
|
110
|
+
def disconnect_connection(c)
|
111
|
+
c.close
|
112
|
+
end
|
113
113
|
end
|
114
114
|
|
115
115
|
# Dataset class for SQLite datasets that use the ruby-sqlite3 driver.
|
@@ -135,14 +135,6 @@ module Sequel
|
|
135
135
|
|
136
136
|
private
|
137
137
|
|
138
|
-
# Work around for the default prepared statement and argument
|
139
|
-
# mapper code, which wants a hash that maps. SQLite doesn't
|
140
|
-
# need to do this, but still requires a value for the argument
|
141
|
-
# in order for the substitution to work correctly.
|
142
|
-
def prepared_args_hash
|
143
|
-
true
|
144
|
-
end
|
145
|
-
|
146
138
|
# SQLite uses a : before the name of the argument for named
|
147
139
|
# arguments.
|
148
140
|
def prepared_arg(k)
|
@@ -221,7 +213,7 @@ module Sequel
|
|
221
213
|
# Prepare the given type of query with the given name and store
|
222
214
|
# it in the database. Note that a new native prepared statement is
|
223
215
|
# created on each call to this prepared statement.
|
224
|
-
def prepare(type, name, values=nil)
|
216
|
+
def prepare(type, name=nil, values=nil)
|
225
217
|
ps = to_prepared_statement(type, values)
|
226
218
|
ps.extend(PreparedStatementMethods)
|
227
219
|
db.prepared_statements[name] = ps if name
|
@@ -5,6 +5,9 @@ class Sequel::ConnectionPool
|
|
5
5
|
# The proc used to create a new database connection.
|
6
6
|
attr_accessor :connection_proc
|
7
7
|
|
8
|
+
# The proc used to disconnect a database connection.
|
9
|
+
attr_accessor :disconnection_proc
|
10
|
+
|
8
11
|
# The maximum number of connections.
|
9
12
|
attr_reader :max_size
|
10
13
|
|
@@ -40,6 +43,7 @@ class Sequel::ConnectionPool
|
|
40
43
|
@max_size = opts[:max_connections] || 4
|
41
44
|
@mutex = Mutex.new
|
42
45
|
@connection_proc = block
|
46
|
+
@disconnection_proc = opts[:disconnection_proc]
|
43
47
|
@servers = [:default]
|
44
48
|
@servers += opts[:servers].keys - @servers if opts[:servers]
|
45
49
|
@available_connections = Hash.new{|h,k| h[:default]}
|
@@ -103,8 +107,11 @@ class Sequel::ConnectionPool
|
|
103
107
|
end
|
104
108
|
begin
|
105
109
|
yield conn
|
110
|
+
rescue Sequel::DatabaseDisconnectError => dde
|
111
|
+
remove(t, conn, server)
|
112
|
+
raise
|
106
113
|
ensure
|
107
|
-
release(t, conn, server)
|
114
|
+
release(t, conn, server) unless dde
|
108
115
|
end
|
109
116
|
rescue Exception => e
|
110
117
|
raise(@convert_exceptions && !e.is_a?(StandardError) ? RuntimeError.new(e.message) : e)
|
@@ -116,24 +123,19 @@ class Sequel::ConnectionPool
|
|
116
123
|
# disconnecting from the database, assuming that no connections are currently
|
117
124
|
# being used. Once a connection is requested using #hold, the connection pool
|
118
125
|
# creates new connections to the database.
|
119
|
-
def disconnect
|
126
|
+
def disconnect(&block)
|
127
|
+
block ||= @disconnection_proc
|
120
128
|
@mutex.synchronize do
|
121
129
|
@available_connections.each do |server, conns|
|
122
|
-
conns.each{|c|
|
130
|
+
conns.each{|c| block.call(c)} if block
|
123
131
|
conns.clear
|
124
|
-
|
132
|
+
set_created_count(server, allocated(server).length)
|
125
133
|
end
|
126
134
|
end
|
127
135
|
end
|
128
136
|
|
129
137
|
private
|
130
138
|
|
131
|
-
# Returns the connection owned by the supplied thread for the given server,
|
132
|
-
# if any.
|
133
|
-
def owned_connection(thread, server)
|
134
|
-
@mutex.synchronize{@allocated[server][thread]}
|
135
|
-
end
|
136
|
-
|
137
139
|
# Assigns a connection to the supplied thread for the given server, if one
|
138
140
|
# is available.
|
139
141
|
def acquire(thread, server)
|
@@ -154,12 +156,18 @@ class Sequel::ConnectionPool
|
|
154
156
|
# the server is less than the maximum size of the pool.
|
155
157
|
def make_new(server)
|
156
158
|
if @created_count[server] < @max_size
|
157
|
-
@created_count[server]
|
159
|
+
set_created_count(server, @created_count[server] + 1)
|
158
160
|
@connection_proc ? @connection_proc.call(server) : \
|
159
161
|
(raise Error, "No connection proc specified")
|
160
162
|
end
|
161
163
|
end
|
162
164
|
|
165
|
+
# Returns the connection owned by the supplied thread for the given server,
|
166
|
+
# if any.
|
167
|
+
def owned_connection(thread, server)
|
168
|
+
@mutex.synchronize{@allocated[server][thread]}
|
169
|
+
end
|
170
|
+
|
163
171
|
# Releases the connection assigned to the supplied thread and server.
|
164
172
|
def release(thread, conn, server)
|
165
173
|
@mutex.synchronize do
|
@@ -167,6 +175,21 @@ class Sequel::ConnectionPool
|
|
167
175
|
available_connections(server) << conn
|
168
176
|
end
|
169
177
|
end
|
178
|
+
|
179
|
+
# Removes the currently allocated connection from the connection pool.
|
180
|
+
def remove(thread, conn, server)
|
181
|
+
@mutex.synchronize do
|
182
|
+
allocated(server).delete(thread)
|
183
|
+
set_created_count(server, @created_count[server] - 1)
|
184
|
+
@disconnection_proc.call(conn) if @disconnection_proc
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Set the created count for the given server type
|
189
|
+
def set_created_count(server, value)
|
190
|
+
server = :default unless @created_count.include?(server)
|
191
|
+
@created_count[server] = value
|
192
|
+
end
|
170
193
|
end
|
171
194
|
|
172
195
|
# A SingleThreadedPool acts as a replacement for a ConnectionPool for use
|
@@ -179,6 +202,9 @@ class Sequel::SingleThreadedPool
|
|
179
202
|
# The proc used to create a new database connection
|
180
203
|
attr_writer :connection_proc
|
181
204
|
|
205
|
+
# The proc used to disconnect a database connection.
|
206
|
+
attr_accessor :disconnection_proc
|
207
|
+
|
182
208
|
# Initializes the instance with the supplied block as the connection_proc.
|
183
209
|
#
|
184
210
|
# The single threaded pool takes the following options:
|
@@ -187,6 +213,7 @@ class Sequel::SingleThreadedPool
|
|
187
213
|
# to RuntimeError exceptions (default true)
|
188
214
|
def initialize(opts={}, &block)
|
189
215
|
@connection_proc = block
|
216
|
+
@disconnection_proc = opts[:disconnection_proc]
|
190
217
|
@conns = {}
|
191
218
|
@convert_exceptions = opts.include?(:pool_convert_exceptions) ? opts[:pool_convert_exceptions] : true
|
192
219
|
end
|
@@ -200,7 +227,13 @@ class Sequel::SingleThreadedPool
|
|
200
227
|
# This method simulates the ConnectionPool#hold API.
|
201
228
|
def hold(server=:default)
|
202
229
|
begin
|
203
|
-
|
230
|
+
begin
|
231
|
+
yield(c = (@conns[server] ||= @connection_proc.call(server)))
|
232
|
+
rescue Sequel::DatabaseDisconnectError => dde
|
233
|
+
@conns.delete(server)
|
234
|
+
@disconnection_proc.call(c) if @disconnection_proc
|
235
|
+
raise
|
236
|
+
end
|
204
237
|
rescue Exception => e
|
205
238
|
# if the error is not a StandardError it is converted into RuntimeError.
|
206
239
|
raise(@convert_exceptions && !e.is_a?(StandardError) ? RuntimeError.new(e.message) : e)
|
@@ -210,7 +243,8 @@ class Sequel::SingleThreadedPool
|
|
210
243
|
# Disconnects from the database. Once a connection is requested using
|
211
244
|
# #hold, the connection is reestablished.
|
212
245
|
def disconnect(&block)
|
213
|
-
|
246
|
+
block ||= @disconnection_proc
|
247
|
+
@conns.values.each{|conn| block.call(conn) if block}
|
214
248
|
@conns = {}
|
215
249
|
end
|
216
250
|
end
|