pgtk 0.14.0 → 0.15.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 49420b7b7c01a8b32c8d7557ca7a7520cfaeca0a389b7ed9fc613728ce961e19
4
- data.tar.gz: b2a61b0c8c6412bea801612a07b14ae67e9549c5060d3364c907a9dceb4ab040
3
+ metadata.gz: eb389fe1825d50f64cd532e633a3dec67a4f928035159e750adc6e5efeec35d5
4
+ data.tar.gz: 96d345dbcb2215e77398394e4a15b0e96de53d9beb014860778920b69376355c
5
5
  SHA512:
6
- metadata.gz: cc8fb8ab39be7276878f5031680cb9d9f2e4471863e87289a0d939aaf0c21a5b4cf4ec066c2a74fef959df6c1bafa74dba5309fc7abdacc201dd7eb214cb9b3e
7
- data.tar.gz: 128dbf6deb488e9798afc7ca7a76b9f409703591c2e16839e70755dac4c50b9a88baabe308d0503a0aafed241422fa53b2cfe2499172ce6cd81b7fad69ba0185
6
+ metadata.gz: 64eef80f0ceccb5da54db1705b58877519a6db87b6af8cc9ccce7314181abdd0715d10bbae34dc638a2f25c55a9622c6b4666ce18ce4d35e57ce2f3ae4c90693
7
+ data.tar.gz: 915a3ba9b2b9c30183a12fadb9e7cd7f91da96d7e1d5a9a5bc1ce145219231629bf574524e0c64f24928c5b7f83d9a428eb380bd8b8eacb11c25b9f144f2278d
@@ -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
@@ -1,8 +1,8 @@
1
- *.gem
2
- .DS_Store
3
1
  .bundle/
2
+ .DS_Store
4
3
  .idea/
5
4
  .yardoc/
5
+ *.gem
6
6
  coverage/
7
7
  doc/
8
8
  node_modules/
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:
@@ -32,8 +36,4 @@ Metrics/PerceivedComplexity:
32
36
  Max: 15
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
@@ -20,7 +20,7 @@ GEM
20
20
  elapsed (0.0.1)
21
21
  loog (> 0)
22
22
  tago (> 0)
23
- json (2.10.2)
23
+ json (2.11.3)
24
24
  language_server-protocol (3.17.0.4)
25
25
  lint_roller (1.1.0)
26
26
  loog (0.6.0)
@@ -30,14 +30,14 @@ GEM
30
30
  builder
31
31
  minitest (>= 5.0)
32
32
  ruby-progressbar
33
- nokogiri (1.18.6-arm64-darwin)
33
+ nokogiri (1.18.8-arm64-darwin)
34
34
  racc (~> 1.4)
35
- nokogiri (1.18.6-x64-mingw-ucrt)
35
+ nokogiri (1.18.8-x64-mingw-ucrt)
36
36
  racc (~> 1.4)
37
- nokogiri (1.18.6-x86_64-linux-gnu)
37
+ nokogiri (1.18.8-x86_64-linux-gnu)
38
38
  racc (~> 1.4)
39
- parallel (1.26.3)
40
- parser (3.3.7.3)
39
+ parallel (1.27.0)
40
+ parser (3.3.8.0)
41
41
  ast (~> 2.4.1)
42
42
  racc
43
43
  pg (1.5.9)
@@ -49,14 +49,14 @@ GEM
49
49
  loog (> 0)
50
50
  tago (> 0)
51
51
  racc (1.8.1)
52
- rack (3.1.12)
52
+ rack (3.1.13)
53
53
  rainbow (3.1.1)
54
54
  rake (13.2.1)
55
55
  random-port (0.7.5)
56
56
  tago (> 0)
57
57
  regexp_parser (2.10.0)
58
58
  rexml (3.4.1)
59
- rubocop (1.75.1)
59
+ rubocop (1.75.5)
60
60
  json (~> 2.3)
61
61
  language_server-protocol (~> 3.17.0.2)
62
62
  lint_roller (~> 1.1.0)
@@ -64,24 +64,24 @@ GEM
64
64
  parser (>= 3.3.0.2)
65
65
  rainbow (>= 2.2.2, < 4.0)
66
66
  regexp_parser (>= 2.9.3, < 3.0)
67
- rubocop-ast (>= 1.43.0, < 2.0)
67
+ rubocop-ast (>= 1.44.0, < 2.0)
68
68
  ruby-progressbar (~> 1.7)
69
69
  unicode-display_width (>= 2.4.0, < 4.0)
70
- rubocop-ast (1.43.0)
70
+ rubocop-ast (1.44.1)
71
71
  parser (>= 3.3.7.2)
72
72
  prism (~> 1.4)
73
- rubocop-minitest (0.37.1)
73
+ rubocop-minitest (0.38.0)
74
74
  lint_roller (~> 1.1)
75
- rubocop (>= 1.72.1, < 2.0)
75
+ rubocop (>= 1.75.0, < 2.0)
76
76
  rubocop-ast (>= 1.38.0, < 2.0)
77
- rubocop-performance (1.24.0)
77
+ rubocop-performance (1.25.0)
78
78
  lint_roller (~> 1.1)
79
- rubocop (>= 1.72.1, < 2.0)
79
+ rubocop (>= 1.75.0, < 2.0)
80
80
  rubocop-ast (>= 1.38.0, < 2.0)
81
81
  rubocop-rake (0.7.1)
82
82
  lint_roller (~> 1.1)
83
83
  rubocop (>= 1.72.1)
84
- rubocop-rspec (3.5.0)
84
+ rubocop-rspec (3.6.0)
85
85
  lint_roller (~> 1.1)
86
86
  rubocop (~> 1.72, >= 1.72.1)
87
87
  ruby-progressbar (1.13.0)
@@ -96,6 +96,7 @@ GEM
96
96
  simplecov_json_formatter (0.1.4)
97
97
  slop (4.10.1)
98
98
  tago (0.1.0)
99
+ timeout (0.4.3)
99
100
  unicode-display_width (3.1.4)
100
101
  unicode-emoji (~> 4.0, >= 4.0.4)
101
102
  unicode-emoji (4.0.4)
@@ -126,6 +127,7 @@ DEPENDENCIES
126
127
  rubocop-rspec (> 0)
127
128
  simplecov (~> 0.22)
128
129
  simplecov-cobertura (~> 2.1)
130
+ timeout (> 0)
129
131
  xcop (> 0)
130
132
  yard (~> 0.9)
131
133
 
data/README.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # Ruby + PostgreSQL + Liquibase + Rake
2
2
 
3
3
  [![EO principles respected here](https://www.elegantobjects.org/badge.svg)](https://www.elegantobjects.org)
4
- [![DevOps By Rultor.com](http://www.rultor.com/b/yegor256/pgtk)](http://www.rultor.com/p/yegor256/pgtk)
4
+ [![DevOps By Rultor.com](https://www.rultor.com/b/yegor256/pgtk)](https://www.rultor.com/p/yegor256/pgtk)
5
5
  [![We recommend RubyMine](https://www.elegantobjects.org/rubymine.svg)](https://www.jetbrains.com/ruby/)
6
6
 
7
7
  [![rake](https://github.com/yegor256/pgtk/actions/workflows/rake.yml/badge.svg)](https://github.com/yegor256/pgtk/actions/workflows/rake.yml)
8
- [![PDD status](http://www.0pdd.com/svg?name=yegor256/pgtk)](http://www.0pdd.com/p?name=yegor256/pgtk)
9
- [![Gem Version](https://badge.fury.io/rb/pgtk.svg)](http://badge.fury.io/rb/pgtk)
8
+ [![PDD status](https://www.0pdd.com/svg?name=yegor256/pgtk)](https://www.0pdd.com/p?name=yegor256/pgtk)
9
+ [![Gem Version](https://badge.fury.io/rb/pgtk.svg)](https://badge.fury.io/rb/pgtk)
10
10
  [![Maintainability](https://api.codeclimate.com/v1/badges/3a5bebac001e5288b00d/maintainability)](https://codeclimate.com/github/yegor256/pgtk/maintainability)
11
11
  [![License](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/yegor256/pgtk/blob/master/LICENSE.txt)
12
12
  [![Test Coverage](https://img.shields.io/codecov/c/github/yegor256/pgtk.svg)](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 if you need to run a transaction:
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 test, I would
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,7 @@ module Minitest
152
151
  end
153
152
  ```
154
153
 
155
- You can also track all SQL queries sent through, with the help of `Pgtk::Pool`:
154
+ You can also track all SQL queries sent through the pool, with the help of `Pgtk::Spy`:
156
155
 
157
156
  ```ruby
158
157
  require 'pgtk/spy'
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
@@ -37,7 +37,6 @@ require 'rubocop/rake_task'
37
37
  desc 'Run Rubocop on all directories'
38
38
  RuboCop::RakeTask.new(:rubocop) do |task|
39
39
  task.fail_on_error = true
40
- task.requires << 'rubocop-rspec'
41
40
  end
42
41
 
43
42
  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
@@ -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
- attr_accessor :name, :master, :yaml, :quiet, :liquibase_version, :postgresql_version, :contexts
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
@@ -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
- attr_accessor :name, :dir, :fresh_start, :user, :password, :dbname, :yaml, :quiet, :port, :config
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
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
- # A pool tha spies on another pool.
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)
data/lib/pgtk/version.rb CHANGED
@@ -11,5 +11,5 @@ require_relative '../pgtk'
11
11
  # License:: MIT
12
12
  module Pgtk
13
13
  # Current version of the library.
14
- VERSION = '0.14.0'
14
+ VERSION = '0.15.0'
15
15
  end
data/lib/pgtk/wire.rb CHANGED
@@ -21,10 +21,16 @@ end
21
21
  # License:: MIT
22
22
  class Pgtk::Wire::Direct
23
23
  # Constructor.
24
+ #
25
+ # @param [String] host Host name of the PostgreSQL server
26
+ # @param [Integer] port Port number of the PostgreSQL server
27
+ # @param [String] dbname Database name
28
+ # @param [String] user Username
29
+ # @param [String] password Password
24
30
  def initialize(host:, port:, dbname:, user:, password:)
25
31
  raise "The host can't be nil" if host.nil?
26
32
  @host = host
27
- raise "The host can't be nil" if host.nil?
33
+ raise "The port can't be nil" if port.nil?
28
34
  @port = port
29
35
  @dbname = dbname
30
36
  @user = user
@@ -46,8 +52,10 @@ end
46
52
  # License:: MIT
47
53
  class Pgtk::Wire::Env
48
54
  # Constructor.
55
+ #
56
+ # @param [String] var The name of the environment variable with the connection URL
49
57
  def initialize(var = 'DATABASE_URL')
50
- raise "The name of the environmant variable can't be nil" if var.nil?
58
+ raise "The name of the environment variable can't be nil" if var.nil?
51
59
  @var = var
52
60
  end
53
61
 
@@ -72,6 +80,9 @@ end
72
80
  # License:: MIT
73
81
  class Pgtk::Wire::Yaml
74
82
  # Constructor.
83
+ #
84
+ # @param [String] file Path to the YAML configuration file
85
+ # @param [String] node The root node name in the YAML file containing PostgreSQL configuration
75
86
  def initialize(file, node = 'pgsql')
76
87
  raise "The name of the file can't be nil" if file.nil?
77
88
  @file = file
@@ -83,12 +94,13 @@ class Pgtk::Wire::Yaml
83
94
  def connection
84
95
  raise "The file #{@file.inspect} not found" unless File.exist?(@file)
85
96
  cfg = YAML.load_file(@file)
97
+ raise "The node '#{@node}' not found in YAML file #{@file.inspect}" unless cfg[@node]
86
98
  Pgtk::Wire::Direct.new(
87
- host: cfg['pgsql']['host'],
88
- port: cfg['pgsql']['port'],
89
- dbname: cfg['pgsql']['dbname'],
90
- user: cfg['pgsql']['user'],
91
- password: cfg['pgsql']['password']
99
+ host: cfg[@node]['host'],
100
+ port: cfg[@node]['port'],
101
+ dbname: cfg[@node]['dbname'],
102
+ user: cfg[@node]['user'],
103
+ password: cfg[@node]['password']
92
104
  ).connection
93
105
  end
94
106
  end
data/test/test__helper.rb CHANGED
@@ -6,13 +6,62 @@
6
6
  $stdout.sync = true
7
7
 
8
8
  require 'simplecov'
9
- SimpleCov.start
10
-
11
9
  require 'simplecov-cobertura'
12
- SimpleCov.formatter = SimpleCov::Formatter::CoberturaFormatter
10
+ unless SimpleCov.running || ENV['PICKS']
11
+ SimpleCov.command_name('test')
12
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new(
13
+ [
14
+ SimpleCov::Formatter::HTMLFormatter,
15
+ SimpleCov::Formatter::CoberturaFormatter
16
+ ]
17
+ )
18
+ SimpleCov.minimum_coverage 90
19
+ SimpleCov.minimum_coverage_by_file 70
20
+ SimpleCov.start do
21
+ add_filter 'test/'
22
+ add_filter 'vendor/'
23
+ add_filter 'target/'
24
+ track_files 'lib/**/*.rb'
25
+ track_files '*.rb'
26
+ end
27
+ end
13
28
 
29
+ require 'minitest/autorun'
14
30
  require 'minitest/reporters'
15
31
  Minitest::Reporters.use! [Minitest::Reporters::SpecReporter.new]
16
32
 
17
- require 'minitest/autorun'
33
+ require 'loog'
34
+ require 'rake'
35
+ require 'rake/tasklib'
18
36
  require_relative '../lib/pgtk'
37
+ require_relative '../lib/pgtk/liquibase_task'
38
+ require_relative '../lib/pgtk/pgsql_task'
39
+
40
+ class Pgtk::Test < Minitest::Test
41
+ def bootstrap(log: Loog::NULL)
42
+ Dir.mktmpdir 'test' do |dir|
43
+ id = rand(100..999)
44
+ Pgtk::PgsqlTask.new("pgsql#{id}") do |t|
45
+ t.dir = File.join(dir, 'pgsql')
46
+ t.user = 'hello'
47
+ t.password = 'A B C привет ! & | !'
48
+ t.dbname = 'test'
49
+ t.yaml = File.join(dir, 'cfg.yml')
50
+ t.quiet = true
51
+ end
52
+ Rake::Task["pgsql#{id}"].invoke
53
+ Pgtk::LiquibaseTask.new("liquibase#{id}") do |t|
54
+ t.master = File.join(__dir__, '../test-resources/master.xml')
55
+ t.yaml = File.join(dir, 'cfg.yml')
56
+ t.quiet = true
57
+ end
58
+ Rake::Task["liquibase#{id}"].invoke
59
+ pool = Pgtk::Pool.new(
60
+ Pgtk::Wire::Yaml.new(File.join(dir, 'cfg.yml')),
61
+ log: log
62
+ )
63
+ pool.start(1)
64
+ yield pool
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: Copyright (c) 2019-2025 Yegor Bugayenko
4
+ # SPDX-License-Identifier: MIT
5
+
6
+ require 'loog'
7
+ require 'pg'
8
+ require 'qbash'
9
+ require 'rake'
10
+ require 'tmpdir'
11
+ require 'yaml'
12
+ require_relative 'test__helper'
13
+ require_relative '../lib/pgtk/pool'
14
+ require_relative '../lib/pgtk/impatient'
15
+
16
+ # Pool test.
17
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
18
+ # Copyright:: Copyright (c) 2017-2025 Yegor Bugayenko
19
+ # License:: MIT
20
+ class TestImpatient < Pgtk::Test
21
+ def test_doesnt_interrupt
22
+ bootstrap do |pool|
23
+ id = Pgtk::Impatient.new(pool).exec(
24
+ 'INSERT INTO book (title) VALUES ($1) RETURNING id',
25
+ ['1984']
26
+ ).first['id'].to_i
27
+ assert_predicate(id, :positive?)
28
+ end
29
+ end
30
+ end
@@ -3,10 +3,10 @@
3
3
  # SPDX-FileCopyrightText: Copyright (c) 2019-2025 Yegor Bugayenko
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
- require 'minitest/autorun'
7
6
  require 'tmpdir'
8
7
  require 'rake'
9
8
  require 'yaml'
9
+ require_relative 'test__helper'
10
10
  require_relative '../lib/pgtk/pgsql_task'
11
11
  require_relative '../lib/pgtk/liquibase_task'
12
12
 
@@ -14,7 +14,7 @@ require_relative '../lib/pgtk/liquibase_task'
14
14
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
15
15
  # Copyright:: Copyright (c) 2017-2025 Yegor Bugayenko
16
16
  # License:: MIT
17
- class TestLiquibaseTask < Minitest::Test
17
+ class TestLiquibaseTask < Pgtk::Test
18
18
  def test_basic
19
19
  Dir.mktmpdir 'test' do |dir|
20
20
  Pgtk::PgsqlTask.new(:pgsql2) do |t|
@@ -3,17 +3,17 @@
3
3
  # SPDX-FileCopyrightText: Copyright (c) 2019-2025 Yegor Bugayenko
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
- require 'minitest/autorun'
7
6
  require 'rake'
8
7
  require 'tmpdir'
9
8
  require 'yaml'
9
+ require_relative 'test__helper'
10
10
  require_relative '../lib/pgtk/pgsql_task'
11
11
 
12
12
  # Pgsql rake task test.
13
13
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
14
14
  # Copyright:: Copyright (c) 2017-2025 Yegor Bugayenko
15
15
  # License:: MIT
16
- class TestPgsqlTask < Minitest::Test
16
+ class TestPgsqlTask < Pgtk::Test
17
17
  def test_basic
18
18
  Dir.mktmpdir 'test' do |dir|
19
19
  Pgtk::PgsqlTask.new(:p2) do |t|
data/test/test_pool.rb CHANGED
@@ -4,12 +4,12 @@
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
6
  require 'loog'
7
- require 'minitest/autorun'
8
7
  require 'pg'
9
8
  require 'qbash'
10
9
  require 'rake'
11
10
  require 'tmpdir'
12
11
  require 'yaml'
12
+ require_relative 'test__helper'
13
13
  require_relative '../lib/pgtk/liquibase_task'
14
14
  require_relative '../lib/pgtk/pgsql_task'
15
15
  require_relative '../lib/pgtk/pool'
@@ -19,7 +19,7 @@ require_relative '../lib/pgtk/spy'
19
19
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
20
20
  # Copyright:: Copyright (c) 2017-2025 Yegor Bugayenko
21
21
  # License:: MIT
22
- class TestPool < Minitest::Test
22
+ class TestPool < Pgtk::Test
23
23
  def test_reads_version
24
24
  bootstrap do |pool|
25
25
  ver = pool.version
@@ -181,33 +181,4 @@ class TestPool < Minitest::Test
181
181
  end
182
182
  end
183
183
  end
184
-
185
- private
186
-
187
- def bootstrap(log: Loog::NULL)
188
- Dir.mktmpdir 'test' do |dir|
189
- id = rand(100..999)
190
- Pgtk::PgsqlTask.new("pgsql#{id}") do |t|
191
- t.dir = File.join(dir, 'pgsql')
192
- t.user = 'hello'
193
- t.password = 'A B C привет ! & | !'
194
- t.dbname = 'test'
195
- t.yaml = File.join(dir, 'cfg.yml')
196
- t.quiet = true
197
- end
198
- Rake::Task["pgsql#{id}"].invoke
199
- Pgtk::LiquibaseTask.new("liquibase#{id}") do |t|
200
- t.master = File.join(__dir__, '../test-resources/master.xml')
201
- t.yaml = File.join(dir, 'cfg.yml')
202
- t.quiet = true
203
- end
204
- Rake::Task["liquibase#{id}"].invoke
205
- pool = Pgtk::Pool.new(
206
- Pgtk::Wire::Yaml.new(File.join(dir, 'cfg.yml')),
207
- log: log
208
- )
209
- pool.start(1)
210
- yield pool
211
- end
212
- end
213
184
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pgtk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.0
4
+ version: 0.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-03-27 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: backtrace
@@ -86,8 +86,8 @@ email: yegor256@gmail.com
86
86
  executables: []
87
87
  extensions: []
88
88
  extra_rdoc_files:
89
- - README.md
90
89
  - LICENSE.txt
90
+ - README.md
91
91
  files:
92
92
  - ".0pdd.yml"
93
93
  - ".gitattributes"
@@ -99,13 +99,13 @@ files:
99
99
  - ".github/workflows/pdd.yml"
100
100
  - ".github/workflows/rake.yml"
101
101
  - ".github/workflows/reuse.yml"
102
+ - ".github/workflows/typos.yml"
102
103
  - ".github/workflows/xcop.yml"
103
104
  - ".github/workflows/yamllint.yml"
104
105
  - ".gitignore"
105
106
  - ".pdd"
106
107
  - ".rubocop.yml"
107
108
  - ".rultor.yml"
108
- - ".simplecov"
109
109
  - ".yamllint.yml"
110
110
  - Gemfile
111
111
  - Gemfile.lock
@@ -116,6 +116,7 @@ files:
116
116
  - Rakefile
117
117
  - cucumber.yml
118
118
  - lib/pgtk.rb
119
+ - lib/pgtk/impatient.rb
119
120
  - lib/pgtk/liquibase_task.rb
120
121
  - lib/pgtk/pgsql_task.rb
121
122
  - lib/pgtk/pool.rb
@@ -128,6 +129,7 @@ files:
128
129
  - test-resources/2019/01-test.xml
129
130
  - test-resources/master.xml
130
131
  - test/test__helper.rb
132
+ - test/test_impatient.rb
131
133
  - test/test_liquibase_task.rb
132
134
  - test/test_pgsql_task.rb
133
135
  - test/test_pool.rb
@@ -151,7 +153,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
151
153
  - !ruby/object:Gem::Version
152
154
  version: '0'
153
155
  requirements: []
154
- rubygems_version: 3.6.2
156
+ rubygems_version: 3.6.7
155
157
  specification_version: 4
156
158
  summary: PostgreSQL ToolKit for Ruby apps
157
159
  test_files: []
data/.simplecov DELETED
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # SPDX-FileCopyrightText: Copyright (c) 2019-2025 Yegor Bugayenko
4
- # SPDX-License-Identifier: MIT
5
-
6
- if Gem.win_platform?
7
- SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
8
- SimpleCov::Formatter::HTMLFormatter
9
- ]
10
- SimpleCov.start do
11
- add_filter '/test/'
12
- add_filter '/features/'
13
- end
14
- else
15
- SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new(
16
- [SimpleCov::Formatter::HTMLFormatter]
17
- )
18
- SimpleCov.start do
19
- add_filter '/test/'
20
- add_filter '/features/'
21
- minimum_coverage 60
22
- end
23
- end