pod4 1.0.0 → 1.0.2
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.
- checksums.yaml +4 -4
- data/.bugs/bugs +1 -0
- data/.hgtags +1 -0
- data/Gemfile +13 -5
- data/README.md +16 -16
- data/lib/pod4/connection_pool.rb +29 -10
- data/lib/pod4/sequel_interface.rb +2 -1
- data/lib/pod4/typecasting.rb +1 -1
- data/lib/pod4/version.rb +1 -1
- data/pod4.gemspec +1 -0
- data/spec/common/connection_pool_spec.rb +38 -2
- data/spec/common/sequel_interface_pg_spec.rb +13 -5
- data/spec/fixtures/database.rb +6 -6
- metadata +5 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7f9748ac25c58c39c322e50a32b47d0fa805defb67fcbcea795752b46f00dff5
|
4
|
+
data.tar.gz: ef8a3e1abcb0409663a3fd16178a2017bc4319aadac065adc8581c0676ab2259
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5b86b655f9773b47a482a87a8d79a5c61e4d6284164b554cbd20f1827f6fabe5812bda22cd92f4e9676a8e23e479d79b7cff6757a46e11865f2f1a8a6f4cf749
|
7
|
+
data.tar.gz: bdc18c5e1616a9a6ea538eb6466473683bc7a97e7a22d06758498f8020fb506049065d25c6f1fb61c619a7cae91b2db7dd987ca87b94458deb22af079d6b8fd2
|
data/.bugs/bugs
CHANGED
@@ -1,3 +1,4 @@
|
|
1
1
|
Fixnum is Deprecated | owner:Andy Jones <andy.jones@jameshall.co.uk>, open:True, id:274eb8828bd4e7d879e4f7a99317c75eb2a2e2b0, time:1533817328.12
|
2
2
|
BigDecimal.new is deprecated | owner:Andy Jones <andy.jones@jameshall.co.uk>, open:False, id:3979ce1679bc4f8c4aef8436e344e10e3773480d, time:1533817342.0
|
3
|
+
Remove id field from record in update if its autoincrement | owner:Andy Jones <andy.jones@jameshall.co.uk>, open:True, id:a383d9f88db5b73e9391a824310133ed2e348eda, time:1570013165.41
|
3
4
|
Connection Pool Parallelism tests crash rspec when run as part of the suite | owner:Andy Jones <andy.jones@jameshall.co.uk>, open:False, id:b5368c7ef19065fc597b5692314da71772660963, time:1554280671.5
|
data/.hgtags
CHANGED
data/Gemfile
CHANGED
@@ -6,14 +6,16 @@ gemspec
|
|
6
6
|
group :development, :test do
|
7
7
|
|
8
8
|
# for bundler, management, etc etc
|
9
|
-
gem "bundler", "~> 2"
|
10
|
-
gem "rake", "~>
|
11
|
-
gem "rspec", "~> 3.
|
9
|
+
gem "bundler", "~> 2.2"
|
10
|
+
gem "rake", "~> 13.0"
|
11
|
+
gem "rspec", "~> 3.10"
|
12
12
|
gem 'pry'
|
13
13
|
gem "pry-doc"
|
14
|
+
gem "base64"
|
15
|
+
gem "logger"
|
14
16
|
|
15
17
|
# For testing
|
16
|
-
gem "sequel", "~> 5.
|
18
|
+
gem "sequel", "~> 5.4"
|
17
19
|
gem "nebulous_stomp", "~> 3"
|
18
20
|
|
19
21
|
platforms :ruby do
|
@@ -26,7 +28,13 @@ group :development, :test do
|
|
26
28
|
gem "jruby-lint"
|
27
29
|
gem "jeremyevans-postgres-pr"
|
28
30
|
gem 'jdbc-mssqlserver'
|
29
|
-
|
31
|
+
|
32
|
+
# Note that this gem is part of a larger project, for Activerecord, and their version history
|
33
|
+
# makes no sense at all. See the RubyGems site to make sense of that. Note: there are
|
34
|
+
# alternative gems. But this is by the jRuby team; so that's a powerful reason to tolerate BS.
|
35
|
+
# If you want to go back to the previous version we used you'll have to pin it here; Bundler
|
36
|
+
# can't figure out their version history, either.
|
37
|
+
gem 'jdbc-postgres', '42.2.14'
|
30
38
|
end
|
31
39
|
|
32
40
|
|
data/README.md
CHANGED
@@ -466,18 +466,26 @@ where I was going.
|
|
466
466
|
In practice this means you need to get your DB connection details from somewhere, maybe create your
|
467
467
|
Sequel DB object; and only then can you require your models.
|
468
468
|
|
469
|
-
|
470
|
-
interface
|
471
|
-
a bit here) one database connection for each model class.
|
472
|
-
|
473
|
-
|
469
|
+
As it stood, this meant that, except when using Sequel (which behaves differently) each
|
470
|
+
interface had its own connection to the database. This means that your application had (simplifying
|
471
|
+
a bit here) one database connection for each model class.
|
472
|
+
|
473
|
+
This scaled surprisingly well, but (again, excepting Sequel) was not thread safe; multiple threads,
|
474
|
+
if you had them, would share the same connection. Note that we are talking in the past tense for
|
475
|
+
this second wrinkle, because of:
|
474
476
|
|
475
|
-
I'm finding that, generally, this scales about right. But if you have a lot of different models and a
|
476
|
-
database that runs out of connections easily, it might be problematic.
|
477
477
|
|
478
478
|
### The Connection Object ###
|
479
479
|
|
480
|
-
The solution to both of these wrinkles
|
480
|
+
The solution to both of these wrinkles is the Pod4::Connection object.
|
481
|
+
|
482
|
+
By default any interface other than SequelInterface will create a pool of connections and connect
|
483
|
+
to the database with one your thread is currently using. (Sequel uses it's own connection pool, so
|
484
|
+
will create one internally. It uses its own thread pool, of course, and we only use the one Sequel
|
485
|
+
DB object for the whole of Pod4, so it doesn't need any of that. That's why it uses
|
486
|
+
Pod4::Connection, instead.)
|
487
|
+
|
488
|
+
You can set this up manually, if you wish:
|
481
489
|
|
482
490
|
#
|
483
491
|
# init.rb -- bootup for my project
|
@@ -519,14 +527,6 @@ With TdsInterface and PgInterface you can pass the same connection to multiple m
|
|
519
527
|
share it. These interfaces take a Pod4::ConnectionPool instead, but otherwise the code looks
|
520
528
|
exactly the same as the above example.
|
521
529
|
|
522
|
-
(Technical note: the ConnectionPool object will actually provide multiple connections, one per
|
523
|
-
thread that uses it. This satisfies the requirement of the underlying libraries that connections
|
524
|
-
are not shared between threads, and therefore ensures that Pod4 is more or less thread safe. You
|
525
|
-
get this functionality automatically -- if you don't define a ConnectionPool, then the interface
|
526
|
-
will create one internally. Sequel uses its own thread pool, of course, and we only use the one
|
527
|
-
Sequel DB object for the whole of Pod4, so it doesn't need any of that. That's why it uses
|
528
|
-
Pod4::Connection, instead.)
|
529
|
-
|
530
530
|
|
531
531
|
BasicModel
|
532
532
|
----------
|
data/lib/pod4/connection_pool.rb
CHANGED
@@ -11,7 +11,7 @@ module Pod4
|
|
11
11
|
|
12
12
|
class ConnectionPool < Connection
|
13
13
|
|
14
|
-
PoolItem = Struct.new(:client, :thread_id)
|
14
|
+
PoolItem = Struct.new(:client, :thread_id, :stamp)
|
15
15
|
|
16
16
|
class Pool
|
17
17
|
def initialize
|
@@ -21,12 +21,16 @@ module Pod4
|
|
21
21
|
|
22
22
|
def <<(cl)
|
23
23
|
@mutex.synchronize do
|
24
|
-
@items << PoolItem.new(cl, Thread.current.object_id)
|
24
|
+
@items << PoolItem.new(cl, Thread.current.object_id, Time.now)
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
+
def get(id)
|
29
|
+
@items.find{|x| x.thread_id == id }
|
30
|
+
end
|
31
|
+
|
28
32
|
def get_current
|
29
|
-
|
33
|
+
get(Thread.current.object_id)
|
30
34
|
end
|
31
35
|
|
32
36
|
def get_free
|
@@ -37,13 +41,19 @@ module Pod4
|
|
37
41
|
end
|
38
42
|
end
|
39
43
|
|
40
|
-
def release
|
41
|
-
pi = get_current
|
44
|
+
def release(id=nil)
|
45
|
+
pi = id.nil? ? get_current : get(id)
|
46
|
+
pi.thread_id = nil if pi
|
47
|
+
end
|
48
|
+
|
49
|
+
def release_oldest
|
50
|
+
pi = @items.sort{|a,b| a.stamp <=> b.stamp}.first
|
42
51
|
pi.thread_id = nil if pi
|
43
52
|
end
|
44
53
|
|
45
|
-
def drop
|
46
|
-
|
54
|
+
def drop(id=nil)
|
55
|
+
id ||= Thread.current.object_id
|
56
|
+
@items.delete_if{|x| x.thread_id == id }
|
47
57
|
end
|
48
58
|
|
49
59
|
def size
|
@@ -84,6 +94,8 @@ module Pod4
|
|
84
94
|
# Return the client we gave this thread before.
|
85
95
|
# Failing that, assign a free one from the pool.
|
86
96
|
# Failing that, ask the interface to give us a new client.
|
97
|
+
# Failing that, if we've set a timeout, wait for a client to be freed; if we have not, release
|
98
|
+
# the oldest client and use that.
|
87
99
|
#
|
88
100
|
# Note: The interface passes itself in case we want to call it back to get a new client; but
|
89
101
|
# clients are assigned to a _thread_. Every interface in a given thread gets the same pool
|
@@ -106,9 +118,16 @@ module Pod4
|
|
106
118
|
end
|
107
119
|
|
108
120
|
if @max_clients && @pool.size >= @max_clients
|
109
|
-
|
110
|
-
|
111
|
-
|
121
|
+
if @max_wait
|
122
|
+
raise Pod4::PoolTimeout if @max_wait && (Time.now - time > @max_wait)
|
123
|
+
Pod4.logger.warn(__FILE__){ "waiting for a free client..." }
|
124
|
+
sleep 1
|
125
|
+
next
|
126
|
+
else
|
127
|
+
Pod4.logger.debug(__FILE__){ "releasing oldest client" }
|
128
|
+
@pool.release_oldest
|
129
|
+
next
|
130
|
+
end
|
112
131
|
end
|
113
132
|
|
114
133
|
cl = interface.new_connection(@data_layer_options)
|
@@ -234,7 +234,8 @@ module Pod4
|
|
234
234
|
#
|
235
235
|
# Use ? as a placeholder in the SQL
|
236
236
|
# mode is either :insert :update or :delete
|
237
|
-
#
|
237
|
+
# Sequel appears to quote these if you don't but bear in mind that, apparently, symbols get
|
238
|
+
# "quoted" as if they were columns while strings get 'quoted' as if they are, well, strings.
|
238
239
|
#
|
239
240
|
# "update and delete should return the number of rows affected, and insert should return the
|
240
241
|
# autogenerated primary integer key for the row inserted (if any)"
|
data/lib/pod4/typecasting.rb
CHANGED
@@ -222,7 +222,7 @@ module Pod4
|
|
222
222
|
return thing if thing.nil?
|
223
223
|
|
224
224
|
# For all current cases, attempting to typecast a blank string should return nil
|
225
|
-
return nil if thing =~ /\A\s*\Z/
|
225
|
+
return nil if thing.is_a?(String) && thing =~ /\A\s*\Z/
|
226
226
|
|
227
227
|
# The order we try these in matters
|
228
228
|
return tc_bigdecimal(thing) if type == BigDecimal
|
data/lib/pod4/version.rb
CHANGED
data/pod4.gemspec
CHANGED
@@ -5,6 +5,7 @@ require 'pod4/version'
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
6
|
spec.name = "pod4"
|
7
7
|
spec.version = Pod4::VERSION
|
8
|
+
spec.required_ruby_version = "2.7.4"
|
8
9
|
spec.authors = ["Andy Jones"]
|
9
10
|
spec.email = ["andy.jones@twosticksconsulting.co.uk"]
|
10
11
|
spec.summary = %q|Totally not an ORM|
|
@@ -127,9 +127,9 @@ describe Pod4::ConnectionPool do
|
|
127
127
|
end
|
128
128
|
end
|
129
129
|
|
130
|
-
context "when we reach the maximum" do
|
130
|
+
context "when we reach the maximum and there is a max_wait" do
|
131
131
|
before(:each) do
|
132
|
-
@connection = ConnectionPool.new(interface: ifce_class, max_clients: 1)
|
132
|
+
@connection = ConnectionPool.new(interface: ifce_class, max_clients: 1, max_wait: 10)
|
133
133
|
@connection.data_layer_options = "meh"
|
134
134
|
@interface = ifce_class.new
|
135
135
|
@interface.set_conn "boz"
|
@@ -194,6 +194,42 @@ describe Pod4::ConnectionPool do
|
|
194
194
|
end
|
195
195
|
end # of when we reach the maximum, max_wait is set and the time is up
|
196
196
|
|
197
|
+
context "when we reach the maximum and no max_wait is set" do
|
198
|
+
before(:each) do
|
199
|
+
@connection = ConnectionPool.new(interface: ifce_class, max_clients: 2)
|
200
|
+
@connection.data_layer_options = "meh"
|
201
|
+
@interface = ifce_class.new
|
202
|
+
@interface.set_conn "foo"
|
203
|
+
|
204
|
+
# assign our 2 clients in the pool to a different thread
|
205
|
+
@threads = []
|
206
|
+
2.times do
|
207
|
+
@threads << Thread.new { @connection.client(@interface); Thread.stop }
|
208
|
+
end
|
209
|
+
sleep 0.1 until @threads.all?{|t| t.stop? }
|
210
|
+
|
211
|
+
expect( @connection._pool.size ).to eq 2
|
212
|
+
@threads.each do |t|
|
213
|
+
expect( t ).not_to be_nil
|
214
|
+
expect( t ).not_to eq Thread.current.object_id
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
after(:each) { @threads.each{|t| t.kill} }
|
219
|
+
|
220
|
+
it "releases the oldest connection" do
|
221
|
+
oldest = @connection._pool.sort_by{|x| x.stamp}.first
|
222
|
+
expect( oldest.thread_id ).not_to eq Thread.current.object_id
|
223
|
+
|
224
|
+
@connection.client(@interface)
|
225
|
+
expect( @connection._pool.size ).to eq 2
|
226
|
+
|
227
|
+
conn = @connection._pool.find{|x| x.stamp == oldest.stamp }
|
228
|
+
expect( conn.thread_id ).to eq Thread.current.object_id
|
229
|
+
end
|
230
|
+
|
231
|
+
end # of when we reach the maximum and no max_wait is set
|
232
|
+
|
197
233
|
end # of when max_clients != nil, there is no client for this thread and none free
|
198
234
|
|
199
235
|
end # of #client
|
@@ -92,10 +92,18 @@ describe "SequelInterface (Pg)" do
|
|
92
92
|
d
|
93
93
|
end
|
94
94
|
|
95
|
-
let(:db_url) { "postgres://
|
95
|
+
#let(:db_url) { "postgres://pod4_test:#why#ring#@postgresql01/pod4_test?search_path=public" }
|
96
|
+
|
97
|
+
let(:db_hash) do
|
98
|
+
{ host: 'postgresql01',
|
99
|
+
user: 'pod4_test',
|
100
|
+
password: '#why#ring#',
|
101
|
+
adapter: :postgres,
|
102
|
+
database: 'pod4_test' }
|
103
|
+
end
|
96
104
|
|
97
105
|
let(:db) do
|
98
|
-
db = Sequel.connect(
|
106
|
+
db = Sequel.connect(db_hash)
|
99
107
|
db_setup(db)
|
100
108
|
db
|
101
109
|
end
|
@@ -119,7 +127,7 @@ describe "SequelInterface (Pg)" do
|
|
119
127
|
describe "#new" do
|
120
128
|
|
121
129
|
context "when passed a Sequel DB object" do
|
122
|
-
let(:ifce) { sequel_interface_class.new(Sequel.connect
|
130
|
+
let(:ifce) { sequel_interface_class.new(Sequel.connect db_hash) }
|
123
131
|
|
124
132
|
it "uses it to create a connection" do
|
125
133
|
expect( ifce._connection ).to be_a Connection
|
@@ -133,7 +141,7 @@ describe "SequelInterface (Pg)" do
|
|
133
141
|
end
|
134
142
|
|
135
143
|
context "when passed a String" do
|
136
|
-
let(:ifce) { sequel_interface_class.new(
|
144
|
+
let(:ifce) { sequel_interface_class.new(db_hash) }
|
137
145
|
|
138
146
|
it "uses it to create a connection" do
|
139
147
|
expect( ifce._connection ).to be_a Connection
|
@@ -160,7 +168,7 @@ describe "SequelInterface (Pg)" do
|
|
160
168
|
# Normally we'd expect on _every_ use, but Sequel is different
|
161
169
|
it "calls Connection#client on first use" do
|
162
170
|
# When we pass a connection object we are expected to set the data layer option
|
163
|
-
conn.data_layer_options = Sequel.connect(
|
171
|
+
conn.data_layer_options = Sequel.connect(db_hash)
|
164
172
|
|
165
173
|
expect( ifce._connection ).to receive(:client).with(ifce).and_call_original
|
166
174
|
|
data/spec/fixtures/database.rb
CHANGED
@@ -6,13 +6,13 @@ DB[:tds] =
|
|
6
6
|
password: 'pod4test' }
|
7
7
|
|
8
8
|
DB[:pg] =
|
9
|
-
{ host: '
|
9
|
+
{ host: 'postgresql01.jhallpr.com',
|
10
10
|
dbname: 'pod4_test',
|
11
|
-
user: '
|
12
|
-
password: '
|
11
|
+
user: 'pod4_test',
|
12
|
+
password: '#why#ring#' }
|
13
13
|
|
14
14
|
DB[:sequel] =
|
15
|
-
{ host: '
|
16
|
-
user: '
|
17
|
-
password: '
|
15
|
+
{ host: 'postgresql01',
|
16
|
+
user: 'pod4_test',
|
17
|
+
password: '#why#ring#' }
|
18
18
|
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pod4
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andy Jones
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: devnull
|
@@ -117,24 +116,21 @@ homepage: https://bitbucket.org/andy-twosticks/pod4
|
|
117
116
|
licenses:
|
118
117
|
- MIT
|
119
118
|
metadata: {}
|
120
|
-
post_install_message:
|
121
119
|
rdoc_options: []
|
122
120
|
require_paths:
|
123
121
|
- lib
|
124
122
|
required_ruby_version: !ruby/object:Gem::Requirement
|
125
123
|
requirements:
|
126
|
-
- -
|
124
|
+
- - '='
|
127
125
|
- !ruby/object:Gem::Version
|
128
|
-
version:
|
126
|
+
version: 2.7.4
|
129
127
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
130
128
|
requirements:
|
131
129
|
- - ">="
|
132
130
|
- !ruby/object:Gem::Version
|
133
131
|
version: '0'
|
134
132
|
requirements: []
|
135
|
-
|
136
|
-
rubygems_version: 2.7.6
|
137
|
-
signing_key:
|
133
|
+
rubygems_version: 3.6.7
|
138
134
|
specification_version: 4
|
139
135
|
summary: Totally not an ORM
|
140
136
|
test_files:
|