pod4 1.0.3 → 1.0.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7f7db5348478c3d4dba39f248760d0f148f14f21866246e95ec3ea8c69505979
4
- data.tar.gz: 7cbace08f7fb4151bfb7b9713bed4d5262d4d4bc7b4a0a87e79723e6c990fabf
3
+ metadata.gz: '059cee54af33433ab352687ba5634d12a30720f913a71f77df04d5121c05c07e'
4
+ data.tar.gz: a154f4c8bcc9c6cd327124218e5e2ebdaf536d75771704e42c3c5c6bb9d81e45
5
5
  SHA512:
6
- metadata.gz: 9b0d0707ae21d1060cbc5df5fa0bee569c85a5dc6f9af3db288c4ba4025c047a1cbb3f3390535df81589b1143053bc29337a407b3f345a4bbcfa594ae7051e83
7
- data.tar.gz: 983c421cdfca75e40b211d19f9a80ed63bbd14068a539b3861be446803231b361d323e34f91de1675ad1bbc528cd62e9f6dd9601d0b7e252d3985287e8ccbcc8
6
+ metadata.gz: a3d59bd322a578230457cdee63faa25f47712345bf0738e6043400b529d6bb3ec7650e84c195eb4aa3da7e661998b9c19ab1d9e062a6ca341c7b391c7e62b789
7
+ data.tar.gz: 7e9dcb3d43858175ad581ef093dbbe39e54cd14f4bd1b887cc3e6976217d270710776bcdd8e2803e980be3e96657ad87efa8037cbd37216bc464d82e0a8bb823
data/README.md CHANGED
@@ -183,6 +183,9 @@ three. There is no validation or error control.
183
183
  Note that we have to require pg_interface and pg seperately. I won't bother to show this in any
184
184
  more model examples.
185
185
 
186
+ Note also that we are passing the PG connection string to the interface. This is not best
187
+ practice. Ideally, you should set up a connection pool, instead. See below.
188
+
186
189
  ### Interface ###
187
190
 
188
191
  Let's start with the interface definition. Remember, the interface class is only there to
@@ -475,35 +478,34 @@ if you had them, would share the same connection. Note that we are talking in th
475
478
  this second wrinkle, because of:
476
479
 
477
480
 
478
- ### The Connection Object ###
481
+ ### The Connection Pool Object ###
479
482
 
480
- The solution to both of these wrinkles is the Pod4::Connection object.
483
+ The solution to both of these wrinkles is the Pod4::ConnectionPool object.
481
484
 
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.)
485
+ You can pass a database connection parameter to an interface, and it will create a Connection
486
+ object for you and use it internally.
487
487
 
488
- You can set this up manually, if you wish:
488
+ This will, with the exception of SequelInterface, which just lets Sequel do its own thing, give you
489
+ one connection pool per interface. This will solve all the above problems, but it is rather
490
+ generous; what you probably want is one connection pool per database for your entire application.
491
+ You can pass this to your interface instead of the DB connection parameters.
489
492
 
490
493
  #
491
494
  # init.rb -- bootup for my project
492
495
  #
493
496
 
494
497
  # Require libraries
495
- require "sequel"
498
+ require "PG""
496
499
  require "pod4"
497
- require "pod4/sequel_interface"
498
- require "pod4/connection"
500
+ require "pod4/pg_interface"
501
+ require "pod4/connection_pool"
499
502
 
500
503
  # require models
501
- $conn = Pod4::Connection.new(interface: Pod4::SequelInterface)
504
+ $conn = Pod4::ConnectionPool.new
502
505
  require_relative "models/customer"
503
506
 
504
507
  # set up database connection
505
- hash = get_sequel_params
506
- $conn.data_layer_options = Sequel.connect(hash)
508
+ $conn.data_layer_options = get_pg_param_hash
507
509
 
508
510
  #############
509
511
 
@@ -512,21 +514,17 @@ You can set this up manually, if you wish:
512
514
  #
513
515
 
514
516
  class Foo < Pod4::Model
515
- class Interface < Pod4::SequelInterface
517
+ class FooInterface < Pod4::PgInterface
516
518
  set_table :foo
517
- set_id_field :id, autoincrement: true
519
+ set_id_field :id
518
520
  end
519
521
 
520
- set_interface Interface.new($conn)
522
+ set_interface FooInterface.new($conn)
521
523
 
522
- TL;DR: the only code that needs to go in the middle of your requires is the line defining a
524
+ TL;DR: the only code that needs to go in the middle of your requires are the lines defining a
523
525
  Connection object; you pass that to the interface instead. You can set up the parameters
524
526
  the interface needs and pass them to the connection object afterwards.
525
527
 
526
- With TdsInterface and PgInterface you can pass the same connection to multiple models and they will
527
- share it. These interfaces take a Pod4::ConnectionPool instead, but otherwise the code looks
528
- exactly the same as the above example.
529
-
530
528
 
531
529
  BasicModel
532
530
  ----------
@@ -15,14 +15,20 @@ module Pod4
15
15
  #
16
16
  # `conn = Pod4::Connection.new(interface: MyInterface)`
17
17
  #
18
- def initialize(args)
19
- raise ArgumentError, "Connection#new needs a Hash" unless args.is_a? Hash
20
- raise ArgumentError, "You must pass a Pod4::Interface" \
21
- unless args[:interface] \
22
- && args[:interface].is_a?(Class) \
23
- && args[:interface].ancestors.include?(Interface)
24
-
25
- @interface_class = args[:interface]
18
+ def initialize(args=nil)
19
+ if args
20
+ raise ArgumentError, "Connection#new argument needs to be a Hash" unless args.is_a? Hash
21
+
22
+ if args[:interface]
23
+ raise ArgumentError, "You must pass a Pod4::Interface" \
24
+ unless args[:interface] \
25
+ && args[:interface].is_a?(Class) \
26
+ && args[:interface].ancestors.include?(Interface)
27
+ end
28
+
29
+ @interface_class = args[:interface]
30
+ end
31
+
26
32
  @data_layer_options = nil
27
33
  @client = nil
28
34
  @options = nil
@@ -43,7 +49,6 @@ module Pod4
43
49
 
44
50
  ##
45
51
  # Close the connection.
46
- # In the case of a single connection, this is probably not going to get used much. But.
47
52
  #
48
53
  def close(interface)
49
54
  fail_bad_interfaces(interface)
@@ -56,7 +61,7 @@ module Pod4
56
61
 
57
62
  def fail_bad_interfaces(f)
58
63
  raise ArgumentError, "That's not a #@interface_class", caller \
59
- unless f.kind_of?(@interface_class)
64
+ if @interface_class && !f.kind_of?(@interface_class)
60
65
 
61
66
  end
62
67
 
@@ -33,6 +33,10 @@ module Pod4
33
33
  get(Thread.current.object_id)
34
34
  end
35
35
 
36
+ def get_oldest
37
+ @items.sort{|a,b| a.stamp <=> b.stamp}.first
38
+ end
39
+
36
40
  def get_free
37
41
  @mutex.synchronize do
38
42
  pi = @items.find{|x| x.thread_id.nil? }
@@ -46,16 +50,6 @@ module Pod4
46
50
  pi.thread_id = nil if pi
47
51
  end
48
52
 
49
- def release_oldest
50
- pi = @items.sort{|a,b| a.stamp <=> b.stamp}.first
51
- pi.thread_id = nil if pi
52
- end
53
-
54
- def drop(id=nil)
55
- id ||= Thread.current.object_id
56
- @items.delete_if{|x| x.thread_id == id }
57
- end
58
-
59
53
  def size
60
54
  @items.size
61
55
  end
@@ -80,6 +74,9 @@ module Pod4
80
74
  # * max_wait -- throw a Pod4::PoolTimeout if you wait more than this time in seconds.
81
75
  # Pass nil to wait forever. Default is nil, because you would need to handle that timeout.
82
76
  #
77
+ # Note that the :interface parameter is optional here. You probably want one pool for all your
78
+ # models and interfaces, so you should leave it out.
79
+ #
83
80
  def initialize(args)
84
81
  super(args)
85
82
 
@@ -97,22 +94,27 @@ module Pod4
97
94
  # Failing that, if we've set a timeout, wait for a client to be freed; if we have not, release
98
95
  # the oldest client and use that.
99
96
  #
100
- # Note: The interface passes itself in case we want to call it back to get a new client; but
101
- # clients are assigned to a _thread_. Every interface in a given thread gets the same pool
102
- # item, the same client object.
97
+ # Note: 'interface' is the instance of the interface class. It passes itself in case we want to
98
+ # call it back to get a new client or to close a client; but clients are assigned to a
99
+ # _thread_, not an interface. Every interface in a given thread gets the same pool item, the
100
+ # same client object.
103
101
  #
104
102
  def client(interface)
105
103
  time = Time.now
106
104
  cl = nil
107
105
 
106
+ Pod4.logger.debug(__FILE__){ "Pool size: #{@pool.size} Thread: #{Thread.current.object_id}" }
107
+
108
108
  # NB: We are constrained to use loop in order for our test to work
109
109
  loop do
110
110
  if (pi = @pool.get_current)
111
+ Pod4.logger.debug(__FILE__){ "get current: #{pi.inspect}" }
111
112
  cl = pi.client
112
113
  break
113
114
  end
114
115
 
115
116
  if (pi = @pool.get_free)
117
+ Pod4.logger.debug(__FILE__){ "get free: #{pi.inspect}" }
116
118
  cl = pi.client
117
119
  break
118
120
  end
@@ -125,38 +127,42 @@ module Pod4
125
127
  next
126
128
  else
127
129
  Pod4.logger.debug(__FILE__){ "releasing oldest client" }
128
- @pool.release_oldest
130
+ oldest = @pool.get_oldest
131
+ interface.close_connection oldest.client
132
+ @pool.release(oldest.thread_id)
129
133
  next
130
134
  end
131
135
  end
132
136
 
137
+ Pod4.logger.debug(__FILE__){ "new connection" }
133
138
  cl = interface.new_connection(@data_layer_options)
134
139
  @pool << cl
135
140
  break
136
141
  end # of loop
137
142
 
143
+ Pod4.logger.debug(__FILE__){ "Got client: #{cl.inspect}" }
138
144
  cl
139
145
  end
140
146
 
141
147
  ##
142
148
  # De-assign the client for the current thread from that thread.
143
149
  #
144
- # We never ask the interface to close the connection to the database. There is no advantage in
145
- # doing that for us.
146
- #
147
- # Note: The interface passes itself in case we want to call it back to actually close the
148
- # client; but clients are assigned to a _thread_.
150
+ # Note: 'interface' is the instance of the interface class. This is so we can call it
151
+ # and get it to close the client; we don't know how to do that.
149
152
  #
150
153
  def close(interface)
151
- @pool.release
154
+ current = @pool.get_current
155
+ interface.close_connection current.client
156
+ @pool.release current.thread_id
152
157
  end
153
158
 
154
- ##
155
- # Remove the client object entirely -- for example, because the connection to the database has
156
- # expired.
159
+ ##
160
+ # Remove the client from the pool but don't try to close it.
161
+ # we provide this for if the Interface finds that a connection is no longer open; TdsInterface
162
+ # uses it.
157
163
  #
158
164
  def drop(interface)
159
- @pool.drop
165
+ @pool.release
160
166
  end
161
167
 
162
168
  ##
@@ -1,4 +1,4 @@
1
- require "octothorpe"
1
+ require "octothorpe" #bamf
2
2
  require "date"
3
3
  require "time"
4
4
  require "bigdecimal"
@@ -319,7 +319,7 @@ module Pod4
319
319
  ##
320
320
  # Close the connection to the database.
321
321
  #
322
- # Pod4 itself doesn't use this(?)
322
+ # This is called from a Connection Object.
323
323
  #
324
324
  def close_connection(conn)
325
325
  Pod4.logger.info(__FILE__){ "Closing connection to DB" }
@@ -360,11 +360,7 @@ module Pod4
360
360
  def ensure_connection
361
361
  client = @connection.client(self)
362
362
 
363
- if client.nil?
364
- open
365
- elsif ! connected?(client)
366
- client.reset
367
- end
363
+ client.reset unless connected?(client)
368
364
 
369
365
  client
370
366
  end
@@ -320,8 +320,7 @@ module Pod4
320
320
  ##
321
321
  # Close the connection to the database.
322
322
  #
323
- # We don't actually use this. Theoretically it would be called by ConnectionPool, but we
324
- # don't. I've left it in for completeness.
323
+ # This is called by ConnectionPool.
325
324
  #
326
325
  def close_connection(conn)
327
326
  Pod4.logger.info(__FILE__){ "Closing connection to DB" }
data/lib/pod4/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Pod4
2
- VERSION = '1.0.3'
2
+ VERSION = '1.0.4'
3
3
  end
@@ -60,7 +60,7 @@ describe Pod4::ConnectionPool do
60
60
  let(:ifce_class) do
61
61
  Class.new Pod4::Interface do
62
62
  def initialize; end
63
- def close_connection; end
63
+ def close_connection(int); end
64
64
  def new_connection(opts); @conn; end
65
65
 
66
66
  def set_conn(c); @conn = c; end
@@ -6,7 +6,7 @@ describe Pod4::ConnectionPool do
6
6
  let(:ifce_class) do
7
7
  Class.new Pod4::Interface do
8
8
  def initialize; end
9
- def close_connection; end
9
+ def close_connection(int); end
10
10
  def new_connection(opts); @conn; end
11
11
 
12
12
  def set_conn(c); @conn = c; end
@@ -228,6 +228,13 @@ describe Pod4::ConnectionPool do
228
228
  expect( conn.thread_id ).to eq Thread.current.object_id
229
229
  end
230
230
 
231
+ it "calls close_connection on the interface with the oldest client ID" do
232
+ oldest = @connection._pool.sort_by{|x| x.stamp}.first
233
+ expect( @interface ).to receive(:close_connection).with(oldest.client)
234
+
235
+ @connection.client(@interface)
236
+ end
237
+
231
238
  end # of when we reach the maximum and no max_wait is set
232
239
 
233
240
  end # of when max_clients != nil, there is no client for this thread and none free
@@ -253,6 +260,11 @@ describe Pod4::ConnectionPool do
253
260
  expect( @connection._pool.first.thread_id ).to be_nil
254
261
  end
255
262
 
263
+ it "calls #close_connection on the interface" do
264
+ expect( @interface).to receive(:close_connection)
265
+ @connection.close(@interface)
266
+ end
267
+
256
268
  end # of #close
257
269
 
258
270
 
@@ -267,16 +279,19 @@ describe Pod4::ConnectionPool do
267
279
  expect( @connection._pool.size ).to eq 1
268
280
  expect( @connection._pool.first.thread_id ).to eq Thread.current.object_id
269
281
  end
270
-
271
- it "removes the client object from the pool entirely" do
282
+
283
+ it "removes the client object from the pool" do
272
284
  @connection.drop(@interface)
273
- expect( @connection._pool.size ).to eq 0
285
+ expect( @connection._pool.size ).to eq 1
286
+ expect( @connection._pool.first.thread_id ).to be_nil
274
287
  end
275
288
 
276
289
  end # of #drop
277
290
 
278
291
 
279
-
292
+
293
+
294
+
280
295
 
281
296
  end
282
297
 
@@ -28,12 +28,13 @@ describe Pod4::Connection do
28
28
 
29
29
  describe "#new" do
30
30
 
31
- it "takes a hash" do
32
- expect{ Pod4::Connection.new }.to raise_error ArgumentError
33
- expect{ Pod4::Connection.new(:foo) }.to raise_error ArgumentError
31
+ it "takes an optional hash" do
32
+ expect{ Pod4::Connection.new }.not_to raise_error
33
+ expect{ Pod4::Connection.new(foo: 4) }.not_to raise_error
34
+ expect{ Pod4::Connection.new(:foo) }.to raise_error ArgumentError
34
35
  end
35
36
 
36
- it "needs :interface, a Pod4::Interface class" do
37
+ it "the :interface parameter must be a Pod4::Interface class" do
37
38
  expect{ Pod4::Connection.new(interface: "foo") }.to raise_error ArgumentError
38
39
  expect{ Pod4::Connection.new(interface: Array) }.to raise_error ArgumentError
39
40
  expect{ Pod4::Connection.new(interface: ConnectionTestingI) }.not_to raise_error
@@ -0,0 +1,81 @@
1
+ require "pod4/pg_interface"
2
+ require "pg"
3
+
4
+ require_relative "../fixtures/database"
5
+
6
+
7
+ describe "PgInterface" do
8
+
9
+ def db_setup(connect)
10
+ client = PG.connect(connect)
11
+
12
+ client.exec(%Q|
13
+ drop table if exists flobert;
14
+
15
+ create table flobert (
16
+ id serial primary key,
17
+ code text,
18
+ name text );
19
+
20
+ insert into flobert (code, name)
21
+ values ( 'one', 'first code');| )
22
+
23
+ ensure
24
+ client.finish if client
25
+ end
26
+
27
+ # we can't use Pod4 to get the connections because it will reuse the current connection!
28
+ def get_connections(connect)
29
+ client = PG.connect(connect)
30
+ result = client.exec %Q|select * from pg_stat_activity
31
+ where datname = 'pod4_test'
32
+ and query like '%flobert%'
33
+ and not (query like '%pg_stat_activity%');|
34
+
35
+ rows = result.map{it}
36
+ client.finish
37
+ rows
38
+ end
39
+
40
+ let(:pg_interface_class) do
41
+ Class.new PgInterface do
42
+ set_table :flobert
43
+ set_id_fld :id
44
+ end
45
+ end
46
+
47
+ let(:interface) do
48
+ pg_interface_class.new(@pool)
49
+ end
50
+
51
+ before(:all) do
52
+ @connect_hash = DB[:pg]
53
+ db_setup(@connect_hash)
54
+
55
+ @pool = ConnectionPool.new(interface: PgInterface)
56
+ @pool.data_layer_options = @connect_hash
57
+ end
58
+
59
+
60
+ describe "#close" do
61
+ # we're specifically and only testing here that the connection goes away after manually calling
62
+ # close. In the common unit tests we test that #client calls #close_connection on the
63
+ # interface when it needs to release a connection.
64
+
65
+ it "drops the database connection" do
66
+ list = interface.list
67
+
68
+ rows = get_connections(@connect_hash)
69
+ expect( rows.count ).to eq 1
70
+
71
+ @pool.close interface
72
+
73
+ rows = get_connections(@connect_hash)
74
+ expect( rows.count ).to eq 0
75
+ end
76
+
77
+ end # of #close
78
+
79
+
80
+ end
81
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pod4
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Jones
@@ -104,6 +104,7 @@ files:
104
104
  - spec/jruby/sequel_encrypting_jdbc_pg_spec.rb
105
105
  - spec/jruby/sequel_interface_jdbc_ms_spec.rb
106
106
  - spec/jruby/sequel_interface_jdbc_pg_spec.rb
107
+ - spec/mri/connection_pool_spec.rb
107
108
  - spec/mri/pg_encrypting_spec.rb
108
109
  - spec/mri/pg_interface_spec.rb
109
110
  - spec/mri/sequel_encrypting_spec.rb
@@ -158,6 +159,7 @@ test_files:
158
159
  - spec/jruby/sequel_encrypting_jdbc_pg_spec.rb
159
160
  - spec/jruby/sequel_interface_jdbc_ms_spec.rb
160
161
  - spec/jruby/sequel_interface_jdbc_pg_spec.rb
162
+ - spec/mri/connection_pool_spec.rb
161
163
  - spec/mri/pg_encrypting_spec.rb
162
164
  - spec/mri/pg_interface_spec.rb
163
165
  - spec/mri/sequel_encrypting_spec.rb