pgtk 0.14.0 → 0.16.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.
- checksums.yaml +4 -4
- data/.github/workflows/typos.yml +19 -0
- data/.gitignore +4 -2
- data/.rubocop.yml +6 -6
- data/Gemfile +1 -0
- data/Gemfile.lock +25 -16
- data/README.md +46 -9
- data/REUSE.toml +8 -7
- data/Rakefile +0 -2
- data/lib/pgtk/impatient.rb +91 -0
- data/lib/pgtk/liquibase_task.rb +31 -1
- data/lib/pgtk/pgsql_task.rb +46 -2
- data/lib/pgtk/pool.rb +34 -1
- data/lib/pgtk/spy.rb +50 -1
- data/lib/pgtk/stash.rb +107 -0
- data/lib/pgtk/version.rb +1 -1
- data/lib/pgtk/wire.rb +24 -7
- data/pgtk.gemspec +3 -0
- data/test/test__helper.rb +62 -4
- data/test/test_impatient.rb +49 -0
- data/test/test_liquibase_task.rb +4 -4
- data/test/test_pgsql_task.rb +4 -4
- data/test/test_pool.rb +23 -41
- data/test/test_stash.rb +89 -0
- data/test/test_wire.rb +37 -0
- metadata +52 -5
- data/.simplecov +0 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f5d316eba88319837e0e0076de690cb533c6d384e84c3bf02caf4bc71737ce76
|
4
|
+
data.tar.gz: ffee85649afc871e9a639769ebee131019e93ad11a23a93726d4cc0768eb1186
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 38d446cdd29cd57eff8ba863f7c6f9a1fb9b18f490e6655eac0259c6945d5da2f6f0f5c18a6a4e7f55f7184c0408bc5f03228514ccbb1375c3974081b176450f
|
7
|
+
data.tar.gz: d8fba0d7db2ea8645f33f843d2e304a24d3b4579854b0581a408258fa2d28eb455b05005b6a7a70ff44ec2643268632c2ab8bfc8f0ac7ddaf7f566414d03bb26
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2019-2025 Yegor Bugayenko
|
2
|
+
# SPDX-License-Identifier: MIT
|
3
|
+
---
|
4
|
+
# yamllint disable rule:line-length
|
5
|
+
name: typos
|
6
|
+
'on':
|
7
|
+
push:
|
8
|
+
branches:
|
9
|
+
- master
|
10
|
+
pull_request:
|
11
|
+
branches:
|
12
|
+
- master
|
13
|
+
jobs:
|
14
|
+
typos:
|
15
|
+
timeout-minutes: 15
|
16
|
+
runs-on: ubuntu-24.04
|
17
|
+
steps:
|
18
|
+
- uses: actions/checkout@v4
|
19
|
+
- uses: crate-ci/typos@v1.32.0
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
@@ -10,6 +10,10 @@ AllCops:
|
|
10
10
|
TargetRubyVersion: 2.3
|
11
11
|
SuggestExtensions: false
|
12
12
|
NewCops: enable
|
13
|
+
plugins:
|
14
|
+
- rubocop-rake
|
15
|
+
- rubocop-minitest
|
16
|
+
- rubocop-performance
|
13
17
|
Minitest/EmptyLineBeforeAssertionMethods:
|
14
18
|
Enabled: false
|
15
19
|
Style/ClassAndModuleChildren:
|
@@ -23,17 +27,13 @@ Layout/EmptyLineAfterGuardClause:
|
|
23
27
|
Metrics/AbcSize:
|
24
28
|
Max: 100
|
25
29
|
Metrics/CyclomaticComplexity:
|
26
|
-
Max:
|
30
|
+
Max: 20
|
27
31
|
Metrics/ClassLength:
|
28
32
|
Max: 200
|
29
33
|
Metrics/MethodLength:
|
30
34
|
Max: 100
|
31
35
|
Metrics/PerceivedComplexity:
|
32
|
-
Max:
|
36
|
+
Max: 20
|
33
37
|
Metrics/ParameterLists:
|
34
38
|
Max: 6
|
35
|
-
plugins:
|
36
|
-
- rubocop-rake
|
37
|
-
- rubocop-minitest
|
38
|
-
- rubocop-performance
|
39
39
|
require: []
|
data/Gemfile
CHANGED
@@ -17,5 +17,6 @@ gem 'rubocop-rake', '>0', require: false
|
|
17
17
|
gem 'rubocop-rspec', '>0', require: false
|
18
18
|
gem 'simplecov', '~>0.22', require: false
|
19
19
|
gem 'simplecov-cobertura', '~>2.1'
|
20
|
+
gem 'timeout', '>0'
|
20
21
|
gem 'xcop', '>0', require: false
|
21
22
|
gem 'yard', '~>0.9', require: false
|
data/Gemfile.lock
CHANGED
@@ -3,6 +3,9 @@ PATH
|
|
3
3
|
specs:
|
4
4
|
pgtk (0.0.0)
|
5
5
|
backtrace (> 0)
|
6
|
+
concurrent-ruby (> 0)
|
7
|
+
joined (> 0)
|
8
|
+
logger (> 0)
|
6
9
|
loog (> 0)
|
7
10
|
pg (~> 1.1)
|
8
11
|
qbash (> 0)
|
@@ -15,29 +18,33 @@ GEM
|
|
15
18
|
ast (2.4.3)
|
16
19
|
backtrace (0.4.0)
|
17
20
|
builder (3.3.0)
|
21
|
+
concurrent-ruby (1.3.5)
|
18
22
|
differ (0.1.2)
|
19
23
|
docile (1.4.1)
|
20
24
|
elapsed (0.0.1)
|
21
25
|
loog (> 0)
|
22
26
|
tago (> 0)
|
23
|
-
|
27
|
+
joined (0.1.0)
|
28
|
+
json (2.11.3)
|
24
29
|
language_server-protocol (3.17.0.4)
|
25
30
|
lint_roller (1.1.0)
|
26
|
-
|
31
|
+
logger (1.7.0)
|
32
|
+
loog (0.6.1)
|
33
|
+
logger (~> 1.0)
|
27
34
|
minitest (5.25.5)
|
28
35
|
minitest-reporters (1.7.1)
|
29
36
|
ansi
|
30
37
|
builder
|
31
38
|
minitest (>= 5.0)
|
32
39
|
ruby-progressbar
|
33
|
-
nokogiri (1.18.
|
40
|
+
nokogiri (1.18.8-arm64-darwin)
|
34
41
|
racc (~> 1.4)
|
35
|
-
nokogiri (1.18.
|
42
|
+
nokogiri (1.18.8-x64-mingw-ucrt)
|
36
43
|
racc (~> 1.4)
|
37
|
-
nokogiri (1.18.
|
44
|
+
nokogiri (1.18.8-x86_64-linux-gnu)
|
38
45
|
racc (~> 1.4)
|
39
|
-
parallel (1.
|
40
|
-
parser (3.3.
|
46
|
+
parallel (1.27.0)
|
47
|
+
parser (3.3.8.0)
|
41
48
|
ast (~> 2.4.1)
|
42
49
|
racc
|
43
50
|
pg (1.5.9)
|
@@ -49,14 +56,14 @@ GEM
|
|
49
56
|
loog (> 0)
|
50
57
|
tago (> 0)
|
51
58
|
racc (1.8.1)
|
52
|
-
rack (3.1.
|
59
|
+
rack (3.1.14)
|
53
60
|
rainbow (3.1.1)
|
54
61
|
rake (13.2.1)
|
55
62
|
random-port (0.7.5)
|
56
63
|
tago (> 0)
|
57
64
|
regexp_parser (2.10.0)
|
58
65
|
rexml (3.4.1)
|
59
|
-
rubocop (1.75.
|
66
|
+
rubocop (1.75.5)
|
60
67
|
json (~> 2.3)
|
61
68
|
language_server-protocol (~> 3.17.0.2)
|
62
69
|
lint_roller (~> 1.1.0)
|
@@ -64,24 +71,24 @@ GEM
|
|
64
71
|
parser (>= 3.3.0.2)
|
65
72
|
rainbow (>= 2.2.2, < 4.0)
|
66
73
|
regexp_parser (>= 2.9.3, < 3.0)
|
67
|
-
rubocop-ast (>= 1.
|
74
|
+
rubocop-ast (>= 1.44.0, < 2.0)
|
68
75
|
ruby-progressbar (~> 1.7)
|
69
76
|
unicode-display_width (>= 2.4.0, < 4.0)
|
70
|
-
rubocop-ast (1.
|
77
|
+
rubocop-ast (1.44.1)
|
71
78
|
parser (>= 3.3.7.2)
|
72
79
|
prism (~> 1.4)
|
73
|
-
rubocop-minitest (0.
|
80
|
+
rubocop-minitest (0.38.0)
|
74
81
|
lint_roller (~> 1.1)
|
75
|
-
rubocop (>= 1.
|
82
|
+
rubocop (>= 1.75.0, < 2.0)
|
76
83
|
rubocop-ast (>= 1.38.0, < 2.0)
|
77
|
-
rubocop-performance (1.
|
84
|
+
rubocop-performance (1.25.0)
|
78
85
|
lint_roller (~> 1.1)
|
79
|
-
rubocop (>= 1.
|
86
|
+
rubocop (>= 1.75.0, < 2.0)
|
80
87
|
rubocop-ast (>= 1.38.0, < 2.0)
|
81
88
|
rubocop-rake (0.7.1)
|
82
89
|
lint_roller (~> 1.1)
|
83
90
|
rubocop (>= 1.72.1)
|
84
|
-
rubocop-rspec (3.
|
91
|
+
rubocop-rspec (3.6.0)
|
85
92
|
lint_roller (~> 1.1)
|
86
93
|
rubocop (~> 1.72, >= 1.72.1)
|
87
94
|
ruby-progressbar (1.13.0)
|
@@ -96,6 +103,7 @@ GEM
|
|
96
103
|
simplecov_json_formatter (0.1.4)
|
97
104
|
slop (4.10.1)
|
98
105
|
tago (0.1.0)
|
106
|
+
timeout (0.4.3)
|
99
107
|
unicode-display_width (3.1.4)
|
100
108
|
unicode-emoji (~> 4.0, >= 4.0.4)
|
101
109
|
unicode-emoji (4.0.4)
|
@@ -126,6 +134,7 @@ DEPENDENCIES
|
|
126
134
|
rubocop-rspec (> 0)
|
127
135
|
simplecov (~> 0.22)
|
128
136
|
simplecov-cobertura (~> 2.1)
|
137
|
+
timeout (> 0)
|
129
138
|
xcop (> 0)
|
130
139
|
yard (~> 0.9)
|
131
140
|
|
data/README.md
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
# Ruby + PostgreSQL + Liquibase + Rake
|
2
2
|
|
3
3
|
[](https://www.elegantobjects.org)
|
4
|
-
[](https://www.rultor.com/p/yegor256/pgtk)
|
5
5
|
[](https://www.jetbrains.com/ruby/)
|
6
6
|
|
7
7
|
[](https://github.com/yegor256/pgtk/actions/workflows/rake.yml)
|
8
|
-
[](
|
8
|
+
[](https://www.0pdd.com/p?name=yegor256/pgtk)
|
9
|
+
[](https://badge.fury.io/rb/pgtk)
|
10
10
|
[](https://codeclimate.com/github/yegor256/pgtk/maintainability)
|
11
11
|
[](https://github.com/yegor256/pgtk/blob/master/LICENSE.txt)
|
12
12
|
[](https://codecov.io/github/yegor256/pgtk?branch=master)
|
@@ -99,7 +99,7 @@ bundle exec rake pgsql liquibase
|
|
99
99
|
|
100
100
|
A temporary PostgreSQL server will be started and the entire set of
|
101
101
|
Liquibase SQL changes will be applied. You will be able to connect
|
102
|
-
to it from your application, using the file `target/config.yml`.
|
102
|
+
to it from your application, using the file `target/pgsql-config.yml`.
|
103
103
|
|
104
104
|
From inside your app you may find this class useful:
|
105
105
|
|
@@ -123,7 +123,7 @@ Now you can fetch some data from the DB:
|
|
123
123
|
name = pgsql.exec('SELECT name FROM user WHERE id = $1', [id])[0]['name']
|
124
124
|
```
|
125
125
|
|
126
|
-
You may also use it
|
126
|
+
You may also use it when you need to run a transaction:
|
127
127
|
|
128
128
|
```ruby
|
129
129
|
pgsql.transaction do |t|
|
@@ -132,7 +132,7 @@ pgsql.transaction do |t|
|
|
132
132
|
end
|
133
133
|
```
|
134
134
|
|
135
|
-
To make your PostgreSQL database visible in your unit
|
135
|
+
To make your PostgreSQL database visible in your unit tests, I would
|
136
136
|
recommend you create a method `test_pgsql` in your `test__helper.rb` file
|
137
137
|
(which is `required` in all unit tests) and implement it like this:
|
138
138
|
|
@@ -143,7 +143,6 @@ require 'pgtk/pool'
|
|
143
143
|
module Minitest
|
144
144
|
class Test
|
145
145
|
def test_pgsql
|
146
|
-
config = YAML.load_file()
|
147
146
|
@@test_pgsql ||= Pgtk::Pool.new(
|
148
147
|
Pgtk::Wire::Yaml.new('target/pgsql-config.yml')
|
149
148
|
).start
|
@@ -152,7 +151,10 @@ module Minitest
|
|
152
151
|
end
|
153
152
|
```
|
154
153
|
|
155
|
-
|
154
|
+
## Logging with `Pgtk::Spy`
|
155
|
+
|
156
|
+
You can also track all SQL queries sent through the pool,
|
157
|
+
with the help of `Pgtk::Spy`:
|
156
158
|
|
157
159
|
```ruby
|
158
160
|
require 'pgtk/spy'
|
@@ -161,11 +163,46 @@ pool = Pgtk::Spy.new(pool) do |sql|
|
|
161
163
|
end
|
162
164
|
```
|
163
165
|
|
164
|
-
|
166
|
+
## Query Caching with `Pgtk::Stash`
|
167
|
+
|
168
|
+
For applications with frequent read queries,
|
169
|
+
you can use `Pgtk::Stash` to add a caching layer:
|
170
|
+
|
171
|
+
```ruby
|
172
|
+
require 'pgtk/stash'
|
173
|
+
stash = Pgtk::Stash.new(pgsql)
|
174
|
+
```
|
175
|
+
|
176
|
+
`Stash` automatically caches read queries and invalidates the cache
|
177
|
+
when tables are modified:
|
178
|
+
|
179
|
+
```ruby
|
180
|
+
# First execution runs the query against the database
|
181
|
+
result1 = stash.exec('SELECT * FROM users WHERE id = $1', [123])
|
182
|
+
# Second execution with the same query and parameters returns cached result
|
183
|
+
result2 = stash.exec('SELECT * FROM users WHERE id = $1', [123])
|
184
|
+
# This modifies the 'users' table, invalidating any cached queries for that table
|
185
|
+
stash.exec('UPDATE users SET name = $1 WHERE id = $2', ['John', 123])
|
186
|
+
# This will execute against the database again since cache was invalidated
|
187
|
+
result3 = stash.exec('SELECT * FROM users WHERE id = $1', [123])
|
188
|
+
```
|
189
|
+
|
190
|
+
Note that the caching implementation is basic and only suitable
|
191
|
+
for simple queries:
|
192
|
+
|
193
|
+
1. Queries must reference tables (using `FROM` or `JOIN`)
|
194
|
+
2. Cache is invalidated by table, not by specific rows
|
195
|
+
3. Write operations (`INSERT`, `UPDATE`, `DELETE`) bypass
|
196
|
+
the cache and invalidate all cached queries for affected tables
|
197
|
+
|
198
|
+
## Some Examples
|
199
|
+
|
200
|
+
This library works in
|
165
201
|
[netbout.com](https://github.com/yegor256/netbout),
|
166
202
|
[wts.zold.io](https://github.com/zold-io/wts.zold.io),
|
167
203
|
[mailanes.com](https://github.com/yegor256/mailanes), and
|
168
204
|
[0rsk.com](https://github.com/yegor256/0rsk).
|
205
|
+
|
169
206
|
They are all open source, you can see how they use `pgtk`.
|
170
207
|
|
171
208
|
## How to contribute
|
data/REUSE.toml
CHANGED
@@ -4,9 +4,17 @@
|
|
4
4
|
version = 1
|
5
5
|
[[annotations]]
|
6
6
|
path = [
|
7
|
+
".DS_Store",
|
8
|
+
".gitattributes",
|
9
|
+
".gitignore",
|
10
|
+
".pdd",
|
7
11
|
"**.json",
|
8
12
|
"**.md",
|
13
|
+
"**.png",
|
9
14
|
"**.txt",
|
15
|
+
"**/.DS_Store",
|
16
|
+
"**/.gitignore",
|
17
|
+
"**/.pdd",
|
10
18
|
"**/*.csv",
|
11
19
|
"**/*.jpg",
|
12
20
|
"**/*.json",
|
@@ -16,15 +24,8 @@ path = [
|
|
16
24
|
"**/*.svg",
|
17
25
|
"**/*.txt",
|
18
26
|
"**/*.vm",
|
19
|
-
"**/.DS_Store",
|
20
|
-
"**/.gitignore",
|
21
|
-
"**/.pdd",
|
22
27
|
"**/CNAME",
|
23
28
|
"**/Gemfile.lock",
|
24
|
-
".DS_Store",
|
25
|
-
".gitattributes",
|
26
|
-
".gitignore",
|
27
|
-
".pdd",
|
28
29
|
"Gemfile.lock",
|
29
30
|
"README.md",
|
30
31
|
"renovate.json",
|
data/Rakefile
CHANGED
@@ -5,7 +5,6 @@
|
|
5
5
|
|
6
6
|
require 'rubygems'
|
7
7
|
require 'rake'
|
8
|
-
require 'rdoc'
|
9
8
|
require 'rake/clean'
|
10
9
|
|
11
10
|
def name
|
@@ -37,7 +36,6 @@ require 'rubocop/rake_task'
|
|
37
36
|
desc 'Run Rubocop on all directories'
|
38
37
|
RuboCop::RakeTask.new(:rubocop) do |task|
|
39
38
|
task.fail_on_error = true
|
40
|
-
task.requires << 'rubocop-rspec'
|
41
39
|
end
|
42
40
|
|
43
41
|
require 'xcop/rake_task'
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2019-2025 Yegor Bugayenko
|
4
|
+
# SPDX-License-Identifier: MIT
|
5
|
+
|
6
|
+
require 'timeout'
|
7
|
+
require_relative '../pgtk'
|
8
|
+
|
9
|
+
# Impatient is a decorator for Pool that enforces timeouts on all database operations.
|
10
|
+
# It ensures that SQL queries don't run indefinitely, which helps prevent application
|
11
|
+
# hangs and resource exhaustion when database operations are slow or stalled.
|
12
|
+
#
|
13
|
+
# This class implements the same interface as Pool but wraps each database operation
|
14
|
+
# in a timeout block. If a query exceeds the specified timeout, it raises a Timeout::Error
|
15
|
+
# exception, allowing the application to handle slow queries gracefully.
|
16
|
+
#
|
17
|
+
# Basic usage:
|
18
|
+
#
|
19
|
+
# # Create and configure a regular pool
|
20
|
+
# pool = Pgtk::Pool.new(wire).start(4)
|
21
|
+
#
|
22
|
+
# # Wrap the pool in an impatient decorator with a 2-second timeout
|
23
|
+
# impatient = Pgtk::Impatient.new(pool, 2)
|
24
|
+
#
|
25
|
+
# # Execute queries with automatic timeout enforcement
|
26
|
+
# begin
|
27
|
+
# impatient.exec('SELECT * FROM large_table WHERE complex_condition')
|
28
|
+
# rescue Timeout::Error
|
29
|
+
# puts "Query timed out after 2 seconds"
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# # Transactions also enforce timeouts on each query
|
33
|
+
# begin
|
34
|
+
# impatient.transaction do |t|
|
35
|
+
# t.exec('UPDATE large_table SET processed = true')
|
36
|
+
# t.exec('DELETE FROM queue WHERE processed = true')
|
37
|
+
# end
|
38
|
+
# rescue Timeout::Error
|
39
|
+
# puts "Transaction timed out"
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# # Combining with Spy for timeout monitoring
|
43
|
+
# spy = Pgtk::Spy.new(impatient) do |sql, duration|
|
44
|
+
# puts "Query completed in #{duration} seconds: #{sql}"
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# # Now queries are both timed and monitored
|
48
|
+
# spy.exec('SELECT * FROM users')
|
49
|
+
#
|
50
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
51
|
+
# Copyright:: Copyright (c) 2019-2025 Yegor Bugayenko
|
52
|
+
# License:: MIT
|
53
|
+
class Pgtk::Impatient
|
54
|
+
# Constructor.
|
55
|
+
#
|
56
|
+
# @param [Pgtk::Pool] pool The pool to decorate
|
57
|
+
# @param [Integer] timeout Timeout in seconds for each SQL query
|
58
|
+
def initialize(pool, timeout = 1)
|
59
|
+
@pool = pool
|
60
|
+
@timeout = timeout
|
61
|
+
end
|
62
|
+
|
63
|
+
# Get the version of PostgreSQL server.
|
64
|
+
#
|
65
|
+
# @return [String] Version of PostgreSQL server
|
66
|
+
def version
|
67
|
+
@pool.version
|
68
|
+
end
|
69
|
+
|
70
|
+
# Execute a SQL query with a timeout.
|
71
|
+
#
|
72
|
+
# @param [String] sql The SQL query with params inside (possibly)
|
73
|
+
# @param [Array] args List of arguments
|
74
|
+
# @return [Array] Result rows
|
75
|
+
# @raise [Timeout::Error] If the query takes too long
|
76
|
+
def exec(sql, *args)
|
77
|
+
Timeout.timeout(@timeout) do
|
78
|
+
@pool.exec(sql, *args)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Run a transaction with a timeout for each query.
|
83
|
+
#
|
84
|
+
# @yield [Pgtk::Impatient] Yields an impatient transaction
|
85
|
+
# @return [Object] Result of the block
|
86
|
+
def transaction
|
87
|
+
@pool.transaction do |t|
|
88
|
+
yield Pgtk::Impatient.new(t, @timeout)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/lib/pgtk/liquibase_task.rb
CHANGED
@@ -16,8 +16,38 @@ require_relative '../pgtk'
|
|
16
16
|
# Copyright:: Copyright (c) 2019-2025 Yegor Bugayenko
|
17
17
|
# License:: MIT
|
18
18
|
class Pgtk::LiquibaseTask < Rake::TaskLib
|
19
|
-
|
19
|
+
# Task name
|
20
|
+
# @return [Symbol]
|
21
|
+
attr_accessor :name
|
20
22
|
|
23
|
+
# Path to Liquibase master XML file
|
24
|
+
# @return [String]
|
25
|
+
attr_accessor :master
|
26
|
+
|
27
|
+
# Path to YAML file with PostgreSQL connection details
|
28
|
+
# @return [String, Array<String>]
|
29
|
+
attr_accessor :yaml
|
30
|
+
|
31
|
+
# Whether to suppress output
|
32
|
+
# @return [Boolean]
|
33
|
+
attr_accessor :quiet
|
34
|
+
|
35
|
+
# Liquibase version to use
|
36
|
+
# @return [String]
|
37
|
+
attr_accessor :liquibase_version
|
38
|
+
|
39
|
+
# PostgreSQL JDBC driver version to use
|
40
|
+
# @return [String]
|
41
|
+
attr_accessor :postgresql_version
|
42
|
+
|
43
|
+
# Liquibase contexts to apply
|
44
|
+
# @return [String]
|
45
|
+
attr_accessor :contexts
|
46
|
+
|
47
|
+
# Initialize a new Liquibase task.
|
48
|
+
#
|
49
|
+
# @param [Array] args Task arguments
|
50
|
+
# @yield [Pgtk::LiquibaseTask, Object] Yields self and task arguments
|
21
51
|
def initialize(*args, &task_block)
|
22
52
|
super()
|
23
53
|
@name = args.shift || :liquibase
|
data/lib/pgtk/pgsql_task.rb
CHANGED
@@ -19,8 +19,50 @@ require_relative '../pgtk'
|
|
19
19
|
# Copyright:: Copyright (c) 2019-2025 Yegor Bugayenko
|
20
20
|
# License:: MIT
|
21
21
|
class Pgtk::PgsqlTask < Rake::TaskLib
|
22
|
-
|
22
|
+
# Task name
|
23
|
+
# @return [Symbol]
|
24
|
+
attr_accessor :name
|
23
25
|
|
26
|
+
# Directory where PostgreSQL server files will be stored
|
27
|
+
# @return [String]
|
28
|
+
attr_accessor :dir
|
29
|
+
|
30
|
+
# Whether to delete the PostgreSQL data directory on each run
|
31
|
+
# @return [Boolean]
|
32
|
+
attr_accessor :fresh_start
|
33
|
+
|
34
|
+
# PostgreSQL username
|
35
|
+
# @return [String]
|
36
|
+
attr_accessor :user
|
37
|
+
|
38
|
+
# PostgreSQL password
|
39
|
+
# @return [String]
|
40
|
+
attr_accessor :password
|
41
|
+
|
42
|
+
# PostgreSQL database name
|
43
|
+
# @return [String]
|
44
|
+
attr_accessor :dbname
|
45
|
+
|
46
|
+
# Path to YAML file where configuration will be written
|
47
|
+
# @return [String]
|
48
|
+
attr_accessor :yaml
|
49
|
+
|
50
|
+
# Whether to suppress output
|
51
|
+
# @return [Boolean]
|
52
|
+
attr_accessor :quiet
|
53
|
+
|
54
|
+
# TCP port for PostgreSQL server (random if nil)
|
55
|
+
# @return [Integer, nil]
|
56
|
+
attr_accessor :port
|
57
|
+
|
58
|
+
# Configuration options for PostgreSQL server
|
59
|
+
# @return [Hash]
|
60
|
+
attr_accessor :config
|
61
|
+
|
62
|
+
# Initialize a new PostgreSQL server task.
|
63
|
+
#
|
64
|
+
# @param [Array] args Task arguments
|
65
|
+
# @yield [Pgtk::PgsqlTask, Object] Yields self and task arguments
|
24
66
|
def initialize(*args, &task_block)
|
25
67
|
super()
|
26
68
|
@name = args.shift || :pgsql
|
@@ -129,6 +171,8 @@ class Pgtk::PgsqlTask < Rake::TaskLib
|
|
129
171
|
}
|
130
172
|
}.to_yaml
|
131
173
|
)
|
132
|
-
|
174
|
+
return if @quiet
|
175
|
+
puts "PostgreSQL has been started in process ##{pid}, port #{port}"
|
176
|
+
puts "YAML config saved to #{@yaml}"
|
133
177
|
end
|
134
178
|
end
|
data/lib/pgtk/pool.rb
CHANGED
@@ -8,7 +8,40 @@ require 'loog'
|
|
8
8
|
require_relative '../pgtk'
|
9
9
|
require_relative 'wire'
|
10
10
|
|
11
|
-
# Pool.
|
11
|
+
# Pool provides a connection pool for PostgreSQL database connections.
|
12
|
+
# It manages a fixed number of connections to optimize performance and
|
13
|
+
# resource usage while providing a simple interface for database operations.
|
14
|
+
#
|
15
|
+
# The Pool class handles connection lifecycle, reconnects on errors,
|
16
|
+
# and provides transaction support. It's the core class for interacting
|
17
|
+
# with a PostgreSQL database in this library.
|
18
|
+
#
|
19
|
+
# Basic usage:
|
20
|
+
#
|
21
|
+
# # Create a wire that knows how to connect to PostgreSQL
|
22
|
+
# wire = Pgtk::Wire::Direct.new(
|
23
|
+
# host: 'localhost',
|
24
|
+
# port: 5432,
|
25
|
+
# dbname: 'mydatabase',
|
26
|
+
# user: 'postgres',
|
27
|
+
# password: 'secret'
|
28
|
+
# )
|
29
|
+
#
|
30
|
+
# # Create and start a connection pool with 4 connections
|
31
|
+
# pool = Pgtk::Pool.new(wire).start(4)
|
32
|
+
#
|
33
|
+
# # Execute a simple query
|
34
|
+
# pool.exec('SELECT * FROM users')
|
35
|
+
#
|
36
|
+
# # Execute a parameterized query
|
37
|
+
# pool.exec('SELECT * FROM users WHERE email = $1', ['user@example.com'])
|
38
|
+
#
|
39
|
+
# # Use transactions for multiple operations
|
40
|
+
# pool.transaction do |t|
|
41
|
+
# t.exec('UPDATE accounts SET balance = balance - $1 WHERE id = $2', [100, 42])
|
42
|
+
# t.exec('UPDATE accounts SET balance = balance + $1 WHERE id = $2', [100, 43])
|
43
|
+
# end
|
44
|
+
#
|
12
45
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
13
46
|
# Copyright:: Copyright (c) 2019-2025 Yegor Bugayenko
|
14
47
|
# License:: MIT
|
data/lib/pgtk/spy.rb
CHANGED
@@ -8,20 +8,65 @@ require 'loog'
|
|
8
8
|
require_relative '../pgtk'
|
9
9
|
require_relative 'wire'
|
10
10
|
|
11
|
-
#
|
11
|
+
# Spy is a decorator for Pool that intercepts and tracks SQL queries.
|
12
|
+
# It provides observability into database operations by invoking a callback
|
13
|
+
# with the SQL query and its execution time for each database operation.
|
14
|
+
#
|
15
|
+
# This class implements the same interface as Pool, but adds instrumentation
|
16
|
+
# functionality while delegating actual database operations to the decorated pool.
|
17
|
+
# Use Spy for debugging, performance monitoring, or audit logging.
|
18
|
+
#
|
19
|
+
# Basic usage:
|
20
|
+
#
|
21
|
+
# # Create and configure a regular pool
|
22
|
+
# pool = Pgtk::Pool.new(wire).start(4)
|
23
|
+
#
|
24
|
+
# # Wrap the pool in a spy that tracks all executed queries
|
25
|
+
# queries = []
|
26
|
+
# spy = Pgtk::Spy.new(pool) do |sql, duration|
|
27
|
+
# puts "Query: #{sql}"
|
28
|
+
# puts "Duration: #{duration} seconds"
|
29
|
+
# queries << sql
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# # Use the spy just like a regular pool, with automatic tracking
|
33
|
+
# spy.exec('SELECT * FROM users')
|
34
|
+
#
|
35
|
+
# # Transactions also track each query inside the transaction
|
36
|
+
# spy.transaction do |t|
|
37
|
+
# t.exec('UPDATE users SET active = true WHERE id = $1', [42])
|
38
|
+
# t.exec('INSERT INTO audit_log (user_id, action) VALUES ($1, $2)', [42, 'activated'])
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# # Examine collected queries for analysis
|
42
|
+
# puts "Total queries: #{queries.size}"
|
43
|
+
# puts "First query: #{queries.first}"
|
44
|
+
#
|
12
45
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
13
46
|
# Copyright:: Copyright (c) 2019-2025 Yegor Bugayenko
|
14
47
|
# License:: MIT
|
15
48
|
class Pgtk::Spy
|
49
|
+
# Constructor.
|
50
|
+
#
|
51
|
+
# @param [Pgtk::Pool] pool The pool to spy on
|
52
|
+
# @yield [String, Float] Yields the SQL query and execution time
|
16
53
|
def initialize(pool, &block)
|
17
54
|
@pool = pool
|
18
55
|
@block = block
|
19
56
|
end
|
20
57
|
|
58
|
+
# Get the version of PostgreSQL server.
|
59
|
+
#
|
60
|
+
# @return [String] Version of PostgreSQL server
|
21
61
|
def version
|
22
62
|
@pool.version
|
23
63
|
end
|
24
64
|
|
65
|
+
# Execute a SQL query and track its execution.
|
66
|
+
#
|
67
|
+
# @param [String] sql The SQL query with params inside (possibly)
|
68
|
+
# @param [Array] args List of arguments
|
69
|
+
# @return [Array] Result rows
|
25
70
|
def exec(sql, *args)
|
26
71
|
start = Time.now
|
27
72
|
ret = @pool.exec(sql, *args)
|
@@ -29,6 +74,10 @@ class Pgtk::Spy
|
|
29
74
|
ret
|
30
75
|
end
|
31
76
|
|
77
|
+
# Run a transaction with spying on each SQL query.
|
78
|
+
#
|
79
|
+
# @yield [Pgtk::Spy] Yields a spy transaction
|
80
|
+
# @return [Object] Result of the block
|
32
81
|
def transaction
|
33
82
|
@pool.transaction do |t|
|
34
83
|
yield Pgtk::Spy.new(t, &@block)
|