sequel 2.3.0 → 2.4.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 +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
|