sequel 2.3.0 → 2.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +16 -0
- data/README +4 -1
- data/Rakefile +17 -19
- data/doc/prepared_statements.rdoc +104 -0
- data/doc/sharding.rdoc +113 -0
- data/lib/sequel_core/adapters/ado.rb +24 -17
- data/lib/sequel_core/adapters/db2.rb +30 -33
- data/lib/sequel_core/adapters/dbi.rb +15 -13
- data/lib/sequel_core/adapters/informix.rb +13 -14
- data/lib/sequel_core/adapters/jdbc.rb +243 -60
- data/lib/sequel_core/adapters/jdbc/mysql.rb +32 -24
- data/lib/sequel_core/adapters/jdbc/postgresql.rb +32 -2
- data/lib/sequel_core/adapters/jdbc/sqlite.rb +16 -20
- data/lib/sequel_core/adapters/mysql.rb +164 -76
- data/lib/sequel_core/adapters/odbc.rb +21 -34
- data/lib/sequel_core/adapters/openbase.rb +10 -7
- data/lib/sequel_core/adapters/oracle.rb +17 -23
- data/lib/sequel_core/adapters/postgres.rb +246 -35
- data/lib/sequel_core/adapters/shared/mssql.rb +106 -0
- data/lib/sequel_core/adapters/shared/mysql.rb +34 -26
- data/lib/sequel_core/adapters/shared/postgres.rb +82 -38
- data/lib/sequel_core/adapters/shared/sqlite.rb +48 -16
- data/lib/sequel_core/adapters/sqlite.rb +141 -44
- data/lib/sequel_core/connection_pool.rb +85 -63
- data/lib/sequel_core/database.rb +46 -17
- data/lib/sequel_core/dataset.rb +21 -40
- data/lib/sequel_core/dataset/convenience.rb +3 -3
- data/lib/sequel_core/dataset/prepared_statements.rb +218 -0
- data/lib/sequel_core/exceptions.rb +0 -12
- data/lib/sequel_model/base.rb +1 -2
- data/lib/sequel_model/plugins.rb +1 -1
- data/spec/adapters/ado_spec.rb +32 -3
- data/spec/adapters/mysql_spec.rb +7 -8
- data/spec/integration/prepared_statement_test.rb +106 -0
- data/spec/sequel_core/connection_pool_spec.rb +105 -3
- data/spec/sequel_core/database_spec.rb +41 -3
- data/spec/sequel_core/dataset_spec.rb +117 -7
- data/spec/sequel_core/spec_helper.rb +2 -2
- data/spec/sequel_model/model_spec.rb +0 -6
- data/spec/sequel_model/spec_helper.rb +1 -1
- metadata +11 -6
- data/lib/sequel_core/adapters/adapter_skeleton.rb +0 -54
- data/lib/sequel_core/adapters/odbc_mssql.rb +0 -106
@@ -7,6 +7,10 @@ module Sequel
|
|
7
7
|
TABLES_FILTER = "type = 'table' AND NOT name = 'sqlite_sequence'"
|
8
8
|
TEMP_STORE = {'0' => :default, '1' => :file, '2' => :memory}.freeze
|
9
9
|
|
10
|
+
# SQLite supports limited table modification. You can add a column
|
11
|
+
# or an index. Dropping columns is supported by copying the table into
|
12
|
+
# a temporary table, dropping the table, and creating a new table without
|
13
|
+
# the column inside of a transaction.
|
10
14
|
def alter_table_sql(table, op)
|
11
15
|
case op[:op]
|
12
16
|
when :add_column
|
@@ -28,51 +32,59 @@ module Sequel
|
|
28
32
|
end
|
29
33
|
end
|
30
34
|
|
35
|
+
# A symbol signifying the value of the auto_vacuum PRAGMA.
|
31
36
|
def auto_vacuum
|
32
37
|
AUTO_VACUUM[pragma_get(:auto_vacuum).to_s]
|
33
38
|
end
|
34
39
|
|
40
|
+
# Set the auto_vacuum PRAGMA using the given symbol (:none, :full, or
|
41
|
+
# :incremental).
|
35
42
|
def auto_vacuum=(value)
|
36
43
|
value = AUTO_VACUUM.key(value) || (raise Error, "Invalid value for auto_vacuum option. Please specify one of :none, :full, :incremental.")
|
37
44
|
pragma_set(:auto_vacuum, value)
|
38
45
|
end
|
39
46
|
|
47
|
+
# Get the value of the given PRAGMA.
|
40
48
|
def pragma_get(name)
|
41
49
|
self["PRAGMA #{name}"].single_value
|
42
50
|
end
|
43
51
|
|
52
|
+
# Set the value of the given PRAGMA to value.
|
44
53
|
def pragma_set(name, value)
|
45
54
|
execute_ddl("PRAGMA #{name} = #{value}")
|
46
55
|
end
|
47
56
|
|
48
|
-
|
49
|
-
{:primary_key => true, :type => :integer, :auto_increment => true}
|
50
|
-
end
|
51
|
-
|
57
|
+
# A symbol signifying the value of the synchronous PRAGMA.
|
52
58
|
def synchronous
|
53
59
|
SYNCHRONOUS[pragma_get(:synchronous).to_s]
|
54
60
|
end
|
55
61
|
|
62
|
+
# Set the synchronous PRAGMA using the given symbol (:off, :normal, or :full).
|
56
63
|
def synchronous=(value)
|
57
64
|
value = SYNCHRONOUS.key(value) || (raise Error, "Invalid value for synchronous option. Please specify one of :off, :normal, :full.")
|
58
65
|
pragma_set(:synchronous, value)
|
59
66
|
end
|
60
|
-
|
67
|
+
|
68
|
+
# Array of symbols specifying the table names in the current database.
|
61
69
|
def tables
|
62
70
|
self[:sqlite_master].filter(TABLES_FILTER).map {|r| r[:name].to_sym}
|
63
71
|
end
|
64
72
|
|
73
|
+
# A symbol signifying the value of the temp_store PRAGMA.
|
65
74
|
def temp_store
|
66
75
|
TEMP_STORE[pragma_get(:temp_store).to_s]
|
67
76
|
end
|
68
77
|
|
78
|
+
# Set the temp_store PRAGMA using the given symbol (:default, :file, or :memory).
|
69
79
|
def temp_store=(value)
|
70
80
|
value = TEMP_STORE.key(value) || (raise Error, "Invalid value for temp_store option. Please specify one of :default, :file, :memory.")
|
71
81
|
pragma_set(:temp_store, value)
|
72
82
|
end
|
73
83
|
|
74
84
|
private
|
75
|
-
|
85
|
+
|
86
|
+
# SQLite supports schema parsing using the table_info PRAGMA, so
|
87
|
+
# parse the output of that into the format Sequel expects.
|
76
88
|
def schema_parse_table(table_name, opts)
|
77
89
|
rows = self["PRAGMA table_info(?)", table_name].collect do |row|
|
78
90
|
row.delete(:cid)
|
@@ -92,15 +104,21 @@ module Sequel
|
|
92
104
|
end
|
93
105
|
schema_parse_rows(rows)
|
94
106
|
end
|
95
|
-
|
107
|
+
|
108
|
+
# SQLite doesn't support getting the schema of all tables at once,
|
109
|
+
# so loop through the output of #tables to get them.
|
96
110
|
def schema_parse_tables(opts)
|
97
111
|
schemas = {}
|
98
112
|
tables.each{|table| schemas[table] = schema_parse_table(table, opts)}
|
99
113
|
schemas
|
100
114
|
end
|
101
115
|
end
|
102
|
-
|
103
|
-
|
116
|
+
|
117
|
+
# Instance methods for datasets that connect to an SQLite database
|
118
|
+
module DatasetMethods
|
119
|
+
# SQLite does not support pattern matching via regular expressions.
|
120
|
+
# SQLite is case insensitive (depending on pragma), so use LIKE for
|
121
|
+
# ILIKE.
|
104
122
|
def complex_expression_sql(op, args)
|
105
123
|
case op
|
106
124
|
when :~, :'!~', :'~*', :'!~*'
|
@@ -113,23 +131,29 @@ module Sequel
|
|
113
131
|
end
|
114
132
|
end
|
115
133
|
|
116
|
-
|
134
|
+
# SQLite performs a TRUNCATE style DELETE if no filter is specified.
|
135
|
+
# Since we want to always return the count of records, do a specific
|
136
|
+
# count in the case of no filter.
|
137
|
+
def delete(opts = {})
|
117
138
|
# check if no filter is specified
|
118
|
-
|
119
|
-
|
139
|
+
opts = @opts.merge(opts)
|
140
|
+
unless opts[:where]
|
141
|
+
@db.transaction(opts[:server]) do
|
120
142
|
unfiltered_count = count
|
121
|
-
|
143
|
+
execute_dui(delete_sql(opts))
|
122
144
|
unfiltered_count
|
123
145
|
end
|
124
146
|
else
|
125
|
-
|
147
|
+
execute_dui(delete_sql(opts))
|
126
148
|
end
|
127
149
|
end
|
128
150
|
|
151
|
+
# Insert the values into the database.
|
129
152
|
def insert(*values)
|
130
|
-
|
153
|
+
execute_insert(insert_sql(*values))
|
131
154
|
end
|
132
|
-
|
155
|
+
|
156
|
+
# Allow inserting of values directly from a dataset.
|
133
157
|
def insert_sql(*values)
|
134
158
|
if (values.size == 1) && values.first.is_a?(Sequel::Dataset)
|
135
159
|
"INSERT INTO #{source_list(@opts[:from])} #{values.first.sql};"
|
@@ -138,9 +162,17 @@ module Sequel
|
|
138
162
|
end
|
139
163
|
end
|
140
164
|
|
165
|
+
# SQLite uses the nonstandard ` (backtick) for quoting identifiers.
|
141
166
|
def quoted_identifier(c)
|
142
167
|
"`#{c}`"
|
143
168
|
end
|
169
|
+
|
170
|
+
private
|
171
|
+
|
172
|
+
# Call execute_insert on the database with the given SQL.
|
173
|
+
def execute_insert(sql, opts={})
|
174
|
+
@db.execute_insert(sql, {:server=>@opts[:server] || :default}.merge(opts))
|
175
|
+
end
|
144
176
|
end
|
145
177
|
end
|
146
178
|
end
|
@@ -2,22 +2,33 @@ require 'sqlite3'
|
|
2
2
|
require 'sequel_core/adapters/shared/sqlite'
|
3
3
|
|
4
4
|
module Sequel
|
5
|
+
# Top level module for holding all SQLite-related modules and classes
|
6
|
+
# for Sequel.
|
5
7
|
module SQLite
|
8
|
+
# Database class for PostgreSQL databases used with Sequel and the
|
9
|
+
# ruby-sqlite3 driver.
|
6
10
|
class Database < Sequel::Database
|
7
11
|
include ::Sequel::SQLite::DatabaseMethods
|
8
12
|
|
9
13
|
set_adapter_scheme :sqlite
|
10
14
|
|
15
|
+
# Mimic the file:// uri, by having 2 preceding slashes specify a relative
|
16
|
+
# path, and 3 preceding slashes specify an absolute path.
|
11
17
|
def self.uri_to_options(uri) # :nodoc:
|
12
18
|
{ :database => (uri.host.nil? && uri.path == '/') ? nil : "#{uri.host}#{uri.path}" }
|
13
19
|
end
|
14
|
-
|
20
|
+
|
15
21
|
private_class_method :uri_to_options
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
22
|
+
|
23
|
+
# Connect to the database. Since SQLite is a file based database,
|
24
|
+
# the only options available are :database (to specify the database
|
25
|
+
# name), and :timeout, to specify how long to wait for the database to
|
26
|
+
# be available if it is locked, given in milliseconds (default is 5000).
|
27
|
+
def connect(server)
|
28
|
+
opts = server_opts(server)
|
29
|
+
opts[:database] = ':memory:' if opts[:database].blank?
|
30
|
+
db = ::SQLite3::Database.new(opts[:database])
|
31
|
+
db.busy_timeout(opts.fetch(:timeout, 5000))
|
21
32
|
db.type_translation = true
|
22
33
|
# fix for timestamp translation
|
23
34
|
db.translator.add_translator("timestamp") do |t, v|
|
@@ -26,58 +37,45 @@ module Sequel
|
|
26
37
|
db
|
27
38
|
end
|
28
39
|
|
40
|
+
# Return instance of Sequel::SQLite::Dataset with the given options.
|
29
41
|
def dataset(opts = nil)
|
30
42
|
SQLite::Dataset.new(self, opts)
|
31
43
|
end
|
32
44
|
|
45
|
+
# Disconnect all connections from the database.
|
33
46
|
def disconnect
|
34
47
|
@pool.disconnect {|c| c.close}
|
35
48
|
end
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
@pool.hold {|conn| conn.execute_batch(sql); conn.changes}
|
41
|
-
rescue SQLite3::Exception => e
|
42
|
-
raise Error::InvalidStatement, "#{sql}\r\n#{e.message}"
|
43
|
-
end
|
49
|
+
|
50
|
+
# Run the given SQL with the given arguments and return the number of changed rows.
|
51
|
+
def execute_dui(sql, opts={})
|
52
|
+
_execute(sql, opts){|conn| conn.execute_batch(sql, opts[:arguments]); conn.changes}
|
44
53
|
end
|
45
54
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
@pool.hold {|conn| conn.execute(sql); conn.last_insert_row_id}
|
50
|
-
rescue SQLite3::Exception => e
|
51
|
-
raise Error::InvalidStatement, "#{sql}\r\n#{e.message}"
|
52
|
-
end
|
55
|
+
# Run the given SQL with the given arguments and return the last inserted row id.
|
56
|
+
def execute_insert(sql, opts={})
|
57
|
+
_execute(sql, opts){|conn| conn.execute(sql, opts[:arguments]); conn.last_insert_row_id}
|
53
58
|
end
|
54
59
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
@pool.hold {|conn| conn.get_first_value(sql)}
|
59
|
-
rescue SQLite3::Exception => e
|
60
|
-
raise Error::InvalidStatement, "#{sql}\r\n#{e.message}"
|
61
|
-
end
|
60
|
+
# Run the given SQL with the given arguments and yield each row.
|
61
|
+
def execute(sql, opts={}, &block)
|
62
|
+
_execute(sql, opts){|conn| conn.query(sql, opts[:arguments], &block)}
|
62
63
|
end
|
63
64
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
@pool.hold {|conn| conn.query(sql, &block)}
|
68
|
-
rescue SQLite3::Exception => e
|
69
|
-
raise Error::InvalidStatement, "#{sql}\r\n#{e.message}"
|
70
|
-
end
|
65
|
+
# Run the given SQL with the given arguments and return the first value of the first row.
|
66
|
+
def single_value(sql, opts={})
|
67
|
+
_execute(sql, opts){|conn| conn.get_first_value(sql, opts[:arguments])}
|
71
68
|
end
|
72
69
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
70
|
+
# Use the native driver transaction method if there isn't already a transaction
|
71
|
+
# in progress on the connection, always yielding a connection inside a transaction
|
72
|
+
# transaction.
|
73
|
+
def transaction(server=nil, &block)
|
74
|
+
synchronize(server) do |conn|
|
75
|
+
return yield(conn) if conn.transaction_active?
|
78
76
|
begin
|
79
77
|
result = nil
|
80
|
-
conn.transaction
|
78
|
+
conn.transaction{result = yield(conn)}
|
81
79
|
result
|
82
80
|
rescue ::Exception => e
|
83
81
|
raise (SQLite3::Exception === e ? Error.new(e.message) : e) unless Error::Rollback === e
|
@@ -87,6 +85,20 @@ module Sequel
|
|
87
85
|
|
88
86
|
private
|
89
87
|
|
88
|
+
# Log the SQL and the arguments, and yield an available connection. Rescue
|
89
|
+
# any SQLite3::Exceptions and turn the into Error::InvalidStatements.
|
90
|
+
def _execute(sql, opts)
|
91
|
+
begin
|
92
|
+
log_info(sql, opts[:arguments])
|
93
|
+
synchronize(opts[:server]){|conn| yield conn}
|
94
|
+
rescue SQLite3::Exception => e
|
95
|
+
raise Error::InvalidStatement, "#{sql}\r\n#{e.message}"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# SQLite does not need the pool to convert exceptions.
|
100
|
+
# Also, force the max connections to 1 if a memory database is being
|
101
|
+
# used, as otherwise each connection gets a separate database.
|
90
102
|
def connection_pool_default_options
|
91
103
|
o = super.merge(:pool_convert_exceptions=>false)
|
92
104
|
# Default to only a single connection if a memory database is used,
|
@@ -96,19 +108,85 @@ module Sequel
|
|
96
108
|
end
|
97
109
|
end
|
98
110
|
|
111
|
+
# Dataset class for SQLite datasets that use the ruby-sqlite3 driver.
|
99
112
|
class Dataset < Sequel::Dataset
|
100
113
|
include ::Sequel::SQLite::DatasetMethods
|
101
114
|
|
102
115
|
EXPLAIN = 'EXPLAIN %s'.freeze
|
116
|
+
PREPARED_ARG_PLACEHOLDER = ':'.freeze
|
117
|
+
|
118
|
+
# SQLite already supports named bind arguments, so use directly.
|
119
|
+
module ArgumentMapper
|
120
|
+
include Sequel::Dataset::ArgumentMapper
|
121
|
+
|
122
|
+
protected
|
123
|
+
|
124
|
+
# Return a hash with the same values as the given hash,
|
125
|
+
# but with the keys converted to strings.
|
126
|
+
def map_to_prepared_args(hash)
|
127
|
+
args = {}
|
128
|
+
hash.each{|k,v| args[k.to_s] = v}
|
129
|
+
args
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
|
134
|
+
# Work around for the default prepared statement and argument
|
135
|
+
# mapper code, which wants a hash that maps. SQLite doesn't
|
136
|
+
# need to do this, but still requires a value for the argument
|
137
|
+
# in order for the substitution to work correctly.
|
138
|
+
def prepared_args_hash
|
139
|
+
true
|
140
|
+
end
|
141
|
+
|
142
|
+
# SQLite uses a : before the name of the argument for named
|
143
|
+
# arguments.
|
144
|
+
def prepared_arg(k)
|
145
|
+
"#{prepared_arg_placeholder}#{k}".lit
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# SQLite prepared statement uses a new prepared statement each time
|
150
|
+
# it is called, but it does use the bind arguments.
|
151
|
+
module PreparedStatementMethods
|
152
|
+
include ArgumentMapper
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
# Run execute_select on the database with the given SQL and the stored
|
157
|
+
# bind arguments.
|
158
|
+
def execute(sql, opts={}, &block)
|
159
|
+
super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
|
160
|
+
end
|
161
|
+
|
162
|
+
# Same as execute, explicit due to intricacies of alias and super.
|
163
|
+
def execute_dui(sql, opts={}, &block)
|
164
|
+
super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
|
165
|
+
end
|
166
|
+
|
167
|
+
# Same as execute, explicit due to intricacies of alias and super.
|
168
|
+
def execute_insert(sql, opts={}, &block)
|
169
|
+
super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
|
170
|
+
end
|
171
|
+
end
|
103
172
|
|
173
|
+
# Prepare an unnamed statement of the given type and call it with the
|
174
|
+
# given values.
|
175
|
+
def call(type, hash, values=nil, &block)
|
176
|
+
prepare(type, nil, values).call(hash, &block)
|
177
|
+
end
|
178
|
+
|
179
|
+
# Return an array of strings specifying a query explanation for the
|
180
|
+
# current dataset.
|
104
181
|
def explain
|
105
182
|
res = []
|
106
183
|
@db.result_set(EXPLAIN % select_sql(opts), nil) {|r| res << r}
|
107
184
|
res
|
108
185
|
end
|
109
|
-
|
186
|
+
|
187
|
+
# Yield a hash for each row in the dataset.
|
110
188
|
def fetch_rows(sql)
|
111
|
-
|
189
|
+
execute(sql) do |result|
|
112
190
|
@columns = result.columns.map {|c| c.to_sym}
|
113
191
|
column_count = @columns.size
|
114
192
|
result.each do |values|
|
@@ -118,7 +196,9 @@ module Sequel
|
|
118
196
|
end
|
119
197
|
end
|
120
198
|
end
|
121
|
-
|
199
|
+
|
200
|
+
# Use the ISO format for dates and timestamps, and quote strings
|
201
|
+
# using the ::SQLite3::Database.quote method.
|
122
202
|
def literal(v)
|
123
203
|
case v
|
124
204
|
when LiteralString
|
@@ -133,6 +213,23 @@ module Sequel
|
|
133
213
|
super
|
134
214
|
end
|
135
215
|
end
|
216
|
+
|
217
|
+
# Prepare the given type of query with the given name and store
|
218
|
+
# it in the database. Note that a new native prepared statement is
|
219
|
+
# created on each call to this prepared statement.
|
220
|
+
def prepare(type, name, values=nil)
|
221
|
+
ps = to_prepared_statement(type, values)
|
222
|
+
ps.extend(PreparedStatementMethods)
|
223
|
+
db.prepared_statements[name] = ps if name
|
224
|
+
ps
|
225
|
+
end
|
226
|
+
|
227
|
+
private
|
228
|
+
|
229
|
+
# SQLite uses a : before the name of the argument as a placeholder.
|
230
|
+
def prepared_arg_placeholder
|
231
|
+
PREPARED_ARG_PLACEHOLDER
|
232
|
+
end
|
136
233
|
end
|
137
234
|
end
|
138
235
|
end
|
@@ -2,21 +2,8 @@
|
|
2
2
|
# multiple connections and giving threads exclusive access to each
|
3
3
|
# connection.
|
4
4
|
class Sequel::ConnectionPool
|
5
|
-
# A hash of connections currently being used, key is the Thread,
|
6
|
-
# value is the connection.
|
7
|
-
attr_reader :allocated
|
8
|
-
|
9
|
-
# An array of connections opened but not currently used
|
10
|
-
attr_reader :available_connections
|
11
|
-
|
12
5
|
# The proc used to create a new database connection.
|
13
6
|
attr_accessor :connection_proc
|
14
|
-
|
15
|
-
# The total number of connections opened, should
|
16
|
-
# be equal to available_connections.length +
|
17
|
-
# allocated.length
|
18
|
-
attr_reader :created_count
|
19
|
-
alias_method :size, :created_count
|
20
7
|
|
21
8
|
# The maximum number of connections.
|
22
9
|
attr_reader :max_size
|
@@ -25,7 +12,6 @@ class Sequel::ConnectionPool
|
|
25
12
|
# this if you want to manipulate the variables safely.
|
26
13
|
attr_reader :mutex
|
27
14
|
|
28
|
-
|
29
15
|
# Constructs a new pool with a maximum size. If a block is supplied, it
|
30
16
|
# is used to create new connections as they are needed.
|
31
17
|
#
|
@@ -47,21 +33,50 @@ class Sequel::ConnectionPool
|
|
47
33
|
# a connection again (default 0.001)
|
48
34
|
# * :pool_timeout - The amount of seconds to wait to acquire a connection
|
49
35
|
# before raising a PoolTimeoutError (default 5)
|
36
|
+
# * :servers - A hash of servers to use. Keys should be symbols. If not
|
37
|
+
# present, will use a single :default server. The server name symbol will
|
38
|
+
# be passed to the connection_proc.
|
50
39
|
def initialize(opts = {}, &block)
|
51
40
|
@max_size = opts[:max_connections] || 4
|
52
41
|
@mutex = Mutex.new
|
53
42
|
@connection_proc = block
|
54
|
-
|
55
|
-
@
|
56
|
-
@
|
57
|
-
@
|
43
|
+
@servers = [:default]
|
44
|
+
@servers += opts[:servers].keys - @servers if opts[:servers]
|
45
|
+
@available_connections = Hash.new{|h,k| h[:default]}
|
46
|
+
@allocated = Hash.new{|h,k| h[:default]}
|
47
|
+
@created_count = Hash.new{|h,k| h[:default]}
|
48
|
+
@servers.each do |s|
|
49
|
+
@available_connections[s] = []
|
50
|
+
@allocated[s] = {}
|
51
|
+
@created_count[s] = 0
|
52
|
+
end
|
58
53
|
@timeout = opts[:pool_timeout] || 5
|
59
54
|
@sleep_time = opts[:pool_sleep_time] || 0.001
|
60
55
|
@convert_exceptions = opts.include?(:pool_convert_exceptions) ? opts[:pool_convert_exceptions] : true
|
61
56
|
end
|
62
57
|
|
63
|
-
#
|
64
|
-
#
|
58
|
+
# A hash of connections currently being used for the given server, key is the
|
59
|
+
# Thread, value is the connection.
|
60
|
+
def allocated(server=:default)
|
61
|
+
@allocated[server]
|
62
|
+
end
|
63
|
+
|
64
|
+
# An array of connections opened but not currently used, for the given
|
65
|
+
# server.
|
66
|
+
def available_connections(server=:default)
|
67
|
+
@available_connections[server]
|
68
|
+
end
|
69
|
+
|
70
|
+
# The total number of connections opened for the given server, should
|
71
|
+
# be equal to available_connections.length + allocated.length
|
72
|
+
def created_count(server=:default)
|
73
|
+
@created_count[server]
|
74
|
+
end
|
75
|
+
alias size created_count
|
76
|
+
|
77
|
+
# Chooses the first available connection to the given server, or if none are
|
78
|
+
# available, creates a new connection. Passes the connection to the supplied
|
79
|
+
# block:
|
65
80
|
#
|
66
81
|
# pool.hold {|conn| conn.execute('DROP TABLE posts')}
|
67
82
|
#
|
@@ -73,78 +88,83 @@ class Sequel::ConnectionPool
|
|
73
88
|
# is available or the timeout expires. If the timeout expires before a
|
74
89
|
# connection can be acquired, a Sequel::Error::PoolTimeoutError is
|
75
90
|
# raised.
|
76
|
-
def hold
|
91
|
+
def hold(server=:default)
|
77
92
|
begin
|
78
93
|
t = Thread.current
|
79
94
|
time = Time.new
|
80
95
|
timeout = time + @timeout
|
81
96
|
sleep_time = @sleep_time
|
82
|
-
if conn = owned_connection(t)
|
97
|
+
if conn = owned_connection(t, server)
|
83
98
|
return yield(conn)
|
84
99
|
end
|
85
|
-
until conn = acquire(t)
|
100
|
+
until conn = acquire(t, server)
|
86
101
|
raise(::Sequel::Error::PoolTimeoutError) if Time.new > timeout
|
87
102
|
sleep sleep_time
|
88
103
|
end
|
89
104
|
begin
|
90
105
|
yield conn
|
91
106
|
ensure
|
92
|
-
release(t, conn)
|
107
|
+
release(t, conn, server)
|
93
108
|
end
|
94
109
|
rescue Exception => e
|
95
110
|
raise(@convert_exceptions && !e.is_a?(StandardError) ? RuntimeError.new(e.message) : e)
|
96
111
|
end
|
97
112
|
end
|
98
113
|
|
99
|
-
# Removes all connection currently available
|
100
|
-
# connection to the given block. This method has the effect of
|
101
|
-
# disconnecting from the database
|
102
|
-
#
|
103
|
-
|
114
|
+
# Removes all connection currently available on all servers, optionally
|
115
|
+
# yielding each connection to the given block. This method has the effect of
|
116
|
+
# disconnecting from the database, assuming that no connections are currently
|
117
|
+
# being used. Once a connection is requested using #hold, the connection pool
|
118
|
+
# creates new connections to the database.
|
119
|
+
def disconnect
|
104
120
|
@mutex.synchronize do
|
105
|
-
@available_connections.each
|
106
|
-
|
107
|
-
|
121
|
+
@available_connections.each do |server, conns|
|
122
|
+
conns.each{|c| yield(c)} if block_given?
|
123
|
+
conns.clear
|
124
|
+
@created_count[server] = allocated(server).length
|
125
|
+
end
|
108
126
|
end
|
109
127
|
end
|
110
128
|
|
111
129
|
private
|
112
130
|
|
113
|
-
# Returns the connection owned by the supplied thread
|
114
|
-
|
115
|
-
|
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]}
|
116
135
|
end
|
117
136
|
|
118
|
-
# Assigns a connection to the supplied thread, if one
|
119
|
-
|
137
|
+
# Assigns a connection to the supplied thread for the given server, if one
|
138
|
+
# is available.
|
139
|
+
def acquire(thread, server)
|
120
140
|
@mutex.synchronize do
|
121
|
-
if conn = available
|
122
|
-
|
141
|
+
if conn = available(server)
|
142
|
+
allocated(server)[thread] = conn
|
123
143
|
end
|
124
144
|
end
|
125
145
|
end
|
126
146
|
|
127
|
-
# Returns an available connection. If no connection is
|
128
|
-
# tries to create a new connection.
|
129
|
-
def available
|
130
|
-
|
147
|
+
# Returns an available connection to the given server. If no connection is
|
148
|
+
# available, tries to create a new connection.
|
149
|
+
def available(server)
|
150
|
+
available_connections(server).pop || make_new(server)
|
131
151
|
end
|
132
152
|
|
133
|
-
# Creates a new connection if the size of the pool
|
134
|
-
# maximum size.
|
135
|
-
def make_new
|
136
|
-
if @created_count < @max_size
|
137
|
-
@created_count += 1
|
138
|
-
@connection_proc ? @connection_proc.call : \
|
153
|
+
# Creates a new connection to the given server if the size of the pool for
|
154
|
+
# the server is less than the maximum size of the pool.
|
155
|
+
def make_new(server)
|
156
|
+
if @created_count[server] < @max_size
|
157
|
+
@created_count[server] += 1
|
158
|
+
@connection_proc ? @connection_proc.call(server) : \
|
139
159
|
(raise Error, "No connection proc specified")
|
140
160
|
end
|
141
161
|
end
|
142
162
|
|
143
|
-
# Releases the connection assigned to the supplied thread.
|
144
|
-
def release(thread, conn)
|
163
|
+
# Releases the connection assigned to the supplied thread and server.
|
164
|
+
def release(thread, conn, server)
|
145
165
|
@mutex.synchronize do
|
146
|
-
|
147
|
-
|
166
|
+
allocated(server).delete(thread)
|
167
|
+
available_connections(server) << conn
|
148
168
|
end
|
149
169
|
end
|
150
170
|
end
|
@@ -156,9 +176,6 @@ end
|
|
156
176
|
# Note that using a single threaded pool with some adapters can cause
|
157
177
|
# errors in certain cases, see Sequel.single_threaded=.
|
158
178
|
class Sequel::SingleThreadedPool
|
159
|
-
# The single database connection for the pool
|
160
|
-
attr_reader :conn
|
161
|
-
|
162
179
|
# The proc used to create a new database connection
|
163
180
|
attr_writer :connection_proc
|
164
181
|
|
@@ -170,15 +187,20 @@ class Sequel::SingleThreadedPool
|
|
170
187
|
# to RuntimeError exceptions (default true)
|
171
188
|
def initialize(opts={}, &block)
|
172
189
|
@connection_proc = block
|
190
|
+
@conns = {}
|
173
191
|
@convert_exceptions = opts.include?(:pool_convert_exceptions) ? opts[:pool_convert_exceptions] : true
|
174
192
|
end
|
175
193
|
|
176
|
-
#
|
177
|
-
|
178
|
-
|
194
|
+
# The connection for the given server.
|
195
|
+
def conn(server=:default)
|
196
|
+
@conns[server]
|
197
|
+
end
|
198
|
+
|
199
|
+
# Yields the connection to the supplied block for the given server.
|
200
|
+
# This method simulates the ConnectionPool#hold API.
|
201
|
+
def hold(server=:default)
|
179
202
|
begin
|
180
|
-
@
|
181
|
-
yield @conn
|
203
|
+
yield(@conns[server] ||= @connection_proc.call(server))
|
182
204
|
rescue Exception => e
|
183
205
|
# if the error is not a StandardError it is converted into RuntimeError.
|
184
206
|
raise(@convert_exceptions && !e.is_a?(StandardError) ? RuntimeError.new(e.message) : e)
|
@@ -188,7 +210,7 @@ class Sequel::SingleThreadedPool
|
|
188
210
|
# Disconnects from the database. Once a connection is requested using
|
189
211
|
# #hold, the connection is reestablished.
|
190
212
|
def disconnect(&block)
|
191
|
-
|
192
|
-
@
|
213
|
+
@conns.values.each{|conn| yield(conn) if block_given?}
|
214
|
+
@conns = {}
|
193
215
|
end
|
194
216
|
end
|