dwh 0.5.0 → 0.5.1

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: d91ce7dc2866f584744c0636918a6e4d5a2d189d3356b08fcee2b60ee40b5317
4
- data.tar.gz: 51e0c508c1a77e5302493868a3e493547ec00fc022195a1b31f581b49abb843b
3
+ metadata.gz: fd8f7098719fc4a93610c3295a6bcbbf14e8487f07cb8dec86c9b95df7805b4e
4
+ data.tar.gz: a18967ab069c582d12bd3eaaa7d97f5242e3380c9f348e1fd2809da4c82ace88
5
5
  SHA512:
6
- metadata.gz: 1ac97b11067710faab814ff16f4e17e46ffa70acc7281e3c45c86e32861feb5eb7207d9f372f29849bc7453f891241c5715624024fc82f5d660d098d812990e5
7
- data.tar.gz: 2aa0820ef26779facc52dc41797403930ebb39186fc0eb81146b310395e8a0052c2d23c1468ee3991ec901204cd9d4f1ce079cf1d86d2c69c7a6616a07b2093e
6
+ metadata.gz: 27599ae5ac8ddd1db8fddf7d9714318eb36f98cb66ff73ed71026dddb0ab4f97e8f2f060dde216e6217485b1627cdc5ce7031254aca92da20395334a61d9bb57
7
+ data.tar.gz: a560342b601b038bd8e1d6d30708822433b09d83bef6094a3a1839e5cd0b09dae4bdbffce45e945f43ec1b6fc6e894e1def9d1c3d2a4c76d38a6a2c7cac3f3b6
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.5.1] - 2026-08-03
4
+
5
+ ### Fixed
6
+
7
+ - **Factory#shutdown**: `case pool.class` never matched (every call fell into `else`); undefined `c` NameError when closing live connections; symbol branch called `delete` on the pool instead of the map. Shutdown now matches on the argument, removes the map entry before closing, and tolerates unknown names.
8
+ - **Factory#pool**: check-then-set race could orphan a pool under concurrent first-use; creation is now mutex-guarded.
9
+ - **Factory#start_reaper**: no longer checks out a connection just to log stats (which created connections and reset idle clocks); Idle/Available labels use `pool.idle` / `pool.available`; iterates a snapshot; rescues `PoolShuttingDownError` per pool so a retired pool cannot kill the reaper thread.
10
+ - **DuckDb#close**: use `@connection&.disconnect` so closing an already-closed adapter does not open a new connection.
11
+ - **DuckDb.close_all**: close then clear instead of deleting while iterating (which could skip entries).
12
+
3
13
  ## [0.5.0] - 2026-06-19
4
14
 
5
15
  ### Added
data/README.md CHANGED
@@ -33,11 +33,11 @@ The adapter only has 5 core methods (6 including the connection method). A YAML
33
33
  - **PostgreSQL** - Full-featured RDBMS with advanced SQL support
34
34
  - **MySQL** - Popular open-source database
35
35
  - **SQL Server** - Microsoft's enterprise database
36
+ - **ClickHouse** - High performance analytical database
37
+ - **Databricks** - Lakehouse SQL warehouse
36
38
 
37
39
  ## Integrations Coming Soon
38
40
 
39
- - **ClickHouse** - High performance analytical db
40
- - **Databricks** - Big data compute engine
41
41
  - **MotherDuck** - Hosted DuckDB service
42
42
 
43
43
  ## Quick Start
@@ -137,4 +137,4 @@ This project is available as open source under the terms of the MIT License.
137
137
 
138
138
  ## Version
139
139
 
140
- Current version: 0.1.0
140
+ Current version: 0.5.0
@@ -56,10 +56,9 @@ module DWH
56
56
  # we open one instance but many connections. Use this
57
57
  # method to close them all.
58
58
  def self.close_all
59
- databases.each do |key, db|
60
- db.close
61
- databases.delete(key)
62
- end
59
+ # Iterate values then clear — mutating the hash while each-ing skips entries.
60
+ databases.each_value(&:close)
61
+ databases.clear
63
62
  end
64
63
 
65
64
  # This disconnects the current connection but
@@ -68,7 +67,9 @@ module DWH
68
67
  #
69
68
  # (see Adapter#close)
70
69
  def close
71
- connection.disconnect
70
+ # Use @connection directly: #connection opens a new handle when nil,
71
+ # which would re-open just to close.
72
+ @connection&.disconnect
72
73
  @connection = nil
73
74
  end
74
75
 
data/lib/dwh/factory.rb CHANGED
@@ -46,6 +46,11 @@ module DWH
46
46
  @pools ||= {}
47
47
  end
48
48
 
49
+ # Mutex guarding pool creation / map mutation.
50
+ def pool_mutex
51
+ @pool_mutex ||= Mutex.new
52
+ end
53
+
49
54
  # The canonical way of creating an adapter instance
50
55
  # in DWH.
51
56
  # @param adapter_name [String, Symbol]
@@ -64,62 +69,80 @@ module DWH
64
69
  # Create a pool of connections for a given name and adapter.
65
70
  # Returns existing pool if it was already created.
66
71
  #
67
- # @param name [String] custom name for your pool
72
+ # @param name [String, Symbol] custom name for your pool (stored as String)
68
73
  # @param adapter_name [String, Symbol]
69
74
  # @param config [Hash] connection options
70
75
  # @param timeout [Integer] pool checkout time out
71
76
  # @param size [Integer] size of the pool
72
77
  def pool(name, adapter_name, config, timeout: 5, size: 10)
73
- if pools.key?(name)
74
- pools[name]
75
- else
76
- pools[name] = ConnectionPool.new(size: size, timeout: timeout) do
77
- create(adapter_name, config)
78
+ name = name.to_s
79
+ pool_mutex.synchronize do
80
+ if pools.key?(name)
81
+ pools[name]
82
+ else
83
+ pools[name] = ConnectionPool.new(size: size, timeout: timeout) do
84
+ create(adapter_name, config)
85
+ end
78
86
  end
79
87
  end
80
88
  end
81
89
 
82
90
  # Shutdown a specific pool or all pools
83
- # @param pool [String, ConnectionPool, nil] pool or name of pool
91
+ # @param pool [String, Symbol, ConnectionPool, nil] pool or name of pool
84
92
  # or nil to shut everything down
85
93
  def shutdown(pool = nil)
86
- case pool.class
87
- when String
88
- pools[pool].shutdown { it.close }
89
- pools.delete(pool)
90
- when Symbol
91
- pools[pool.to_s].shutdown { it.close }
92
- pools[pool.to_s].delete
93
- when ConnectionPool
94
- pool.shutdown { it.close }
95
- pools.delete(pools.key(pool))
96
- else
97
- pools.each_value do |val|
98
- val.shutdown { c.close }
94
+ # Mutate the map under the same mutex as pool creation so a concurrent
95
+ # create cannot be orphaned by @pools = {} / delete racing it.
96
+ # Close outside the lock — ConnectionPool#shutdown can wait on check-in.
97
+ to_close = pool_mutex.synchronize do
98
+ case pool
99
+ when String, Symbol
100
+ # Delete first so a raising close cannot leave a dead pool in the map.
101
+ removed = pools.delete(pool.to_s)
102
+ removed ? [removed] : []
103
+ when ConnectionPool
104
+ key = pools.key(pool)
105
+ pools.delete(key) if key
106
+ [pool]
107
+ else
108
+ closing = pools.values
109
+ @pools = {}
110
+ closing
99
111
  end
100
- @pools = {}
101
112
  end
113
+ to_close.each { |p| p.shutdown { it.close } }
102
114
  end
103
115
 
104
116
  # Start reaper that will periodically clean up
105
117
  # unused or idle connections.
106
118
  # @param frequency [Integer] defaults to 300 seconds
119
+ # @return [Thread] the reaper thread
107
120
  def start_reaper(frequency = 300)
108
121
  logger.info 'Starting DB Adapter reaper process'
109
122
  Thread.new do
110
123
  loop do
111
- pools.each do |name, pool|
112
- logger.info "DB POOL FOR #{name} STATS:"
113
- pool.with do
114
- logger.info "\tSize: #{pool.size}"
115
- logger.info "\tIdle: #{pool.available}"
116
- logger.info "\tAvailable: #{pool.available}"
117
- end
118
- pool.reap(frequency) { it.close }
119
- end
124
+ reaper_tick(frequency)
120
125
  sleep frequency
121
126
  end
122
127
  end
123
128
  end
129
+
130
+ # One reaper cycle: log pool stats (without checking out) and reap idle connections.
131
+ # Safe to call from tests; rescues per-pool so a shut-down pool cannot kill the loop.
132
+ # @param frequency [Integer] idle threshold passed to ConnectionPool#reap
133
+ def reaper_tick(frequency = 300)
134
+ # Snapshot so concurrent shutdown deletions do not mutate while we iterate.
135
+ pools.to_a.each do |name, pool|
136
+ logger.info "DB POOL FOR #{name} STATS:"
137
+ logger.info "\tSize: #{pool.size}"
138
+ logger.info "\tIdle: #{pool.idle}"
139
+ logger.info "\tAvailable: #{pool.available}"
140
+ pool.reap(frequency) { it.close }
141
+ rescue ConnectionPool::PoolShuttingDownError => e
142
+ logger.info "Skipping reaper for pool #{name}: #{e.class}"
143
+ rescue StandardError => e
144
+ logger.error "Reaper error for pool #{name}: #{e.class}: #{e.message}"
145
+ end
146
+ end
124
147
  end
125
148
  end
data/lib/dwh/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DWH
4
- VERSION = '0.5.0'
4
+ VERSION = '0.5.1'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dwh
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ajo Abraham