pgtk 0.32.4 → 0.32.5
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/Gemfile.lock +3 -3
- data/lib/pgtk/impatient/too_slow.rb +18 -0
- data/lib/pgtk/impatient.rb +12 -11
- data/lib/pgtk/liquibase_task.rb +3 -2
- data/lib/pgtk/liquicheck_task/must_error.rb +17 -0
- data/lib/pgtk/liquicheck_task.rb +2 -4
- data/lib/pgtk/pgsql_task.rb +23 -27
- data/lib/pgtk/pool/busy.rb +17 -0
- data/lib/pgtk/pool/iterable_queue.rb +83 -0
- data/lib/pgtk/pool/txn.rb +62 -0
- data/lib/pgtk/pool.rb +20 -137
- data/lib/pgtk/retry/exhausted.rb +17 -0
- data/lib/pgtk/retry.rb +2 -5
- data/lib/pgtk/spy.rb +2 -4
- data/lib/pgtk/stash.rb +11 -17
- data/lib/pgtk/version.rb +1 -1
- data/lib/pgtk/wire/direct.rb +41 -0
- data/lib/pgtk/wire/env.rb +48 -0
- data/lib/pgtk/wire/yaml.rb +42 -0
- data/lib/pgtk/wire.rb +4 -103
- metadata +10 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b1997e1fa9261d1881e5df4495a43723ea737ad754806bc9908a986c4e782fa6
|
|
4
|
+
data.tar.gz: 51b31ffb42b6eb9d2ea625c82be4664384bc190d2c1187ab642cb7cbeaaeb9ab
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f6245814e1907f27948dd369e8ff00cf584e759c04c7b22befac1c4da3de828ef6192f8d3510fe6760b04cdbab871045a7963ed54c00cd1d64b982b2ccfb483d
|
|
7
|
+
data.tar.gz: 5f97bf03bf00fc4e4da554b1807896474d4221748ebacc3cbda22da56e6d55e3067dd5e277082c7a4184aabe4617df9ece91e6dfaaec718f2530f7acc4410664
|
data/Gemfile.lock
CHANGED
|
@@ -69,7 +69,7 @@ GEM
|
|
|
69
69
|
nokogiri (1.19.3-x86_64-linux-musl)
|
|
70
70
|
racc (~> 1.4)
|
|
71
71
|
os (1.1.4)
|
|
72
|
-
parallel (2.0
|
|
72
|
+
parallel (2.1.0)
|
|
73
73
|
parser (3.3.11.1)
|
|
74
74
|
ast (~> 2.4.1)
|
|
75
75
|
racc
|
|
@@ -81,7 +81,7 @@ GEM
|
|
|
81
81
|
pg (1.6.3-x86_64-linux)
|
|
82
82
|
pg (1.6.3-x86_64-linux-musl)
|
|
83
83
|
prism (1.9.0)
|
|
84
|
-
qbash (0.8.
|
|
84
|
+
qbash (0.8.4)
|
|
85
85
|
backtrace (> 0)
|
|
86
86
|
elapsed (> 0)
|
|
87
87
|
loog (> 0)
|
|
@@ -108,7 +108,7 @@ GEM
|
|
|
108
108
|
rubocop-ast (1.49.1)
|
|
109
109
|
parser (>= 3.3.7.2)
|
|
110
110
|
prism (~> 1.7)
|
|
111
|
-
rubocop-elegant (0.0
|
|
111
|
+
rubocop-elegant (0.5.0)
|
|
112
112
|
lint_roller (~> 1.1)
|
|
113
113
|
rubocop (~> 1.75)
|
|
114
114
|
rubocop-minitest (0.39.1)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
require_relative '../../pgtk'
|
|
7
|
+
|
|
8
|
+
class Pgtk::Impatient; end
|
|
9
|
+
|
|
10
|
+
# Raised by Pgtk::Impatient#exec when the query takes longer than the
|
|
11
|
+
# configured timeout. The deadline is enforced server-side via
|
|
12
|
+
# +SET LOCAL statement_timeout+, so the underlying +PG::QueryCanceled+
|
|
13
|
+
# is translated into this error for the caller.
|
|
14
|
+
#
|
|
15
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
16
|
+
# Copyright:: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
17
|
+
# License:: MIT
|
|
18
|
+
class Pgtk::Impatient::TooSlow < StandardError; end
|
data/lib/pgtk/impatient.rb
CHANGED
|
@@ -58,9 +58,6 @@ require_relative '../pgtk'
|
|
|
58
58
|
# Copyright:: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
59
59
|
# License:: MIT
|
|
60
60
|
class Pgtk::Impatient
|
|
61
|
-
# If timed out
|
|
62
|
-
class TooSlow < StandardError; end
|
|
63
|
-
|
|
64
61
|
# Constructor.
|
|
65
62
|
#
|
|
66
63
|
# @param [Pgtk::Pool] pool The pool to decorate
|
|
@@ -118,14 +115,16 @@ class Pgtk::Impatient
|
|
|
118
115
|
t.exec(sql, *args)
|
|
119
116
|
end
|
|
120
117
|
rescue PG::QueryCanceled
|
|
121
|
-
raise(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
118
|
+
raise(
|
|
119
|
+
TooSlow, [
|
|
120
|
+
'SQL query',
|
|
121
|
+
("with #{args.count} argument#{'s' if args.count > 1}" unless args.empty?),
|
|
122
|
+
'was terminated after',
|
|
123
|
+
start.ago,
|
|
124
|
+
'of waiting:',
|
|
125
|
+
sql.ellipsized(50).inspect
|
|
126
|
+
].compact.join(' ')
|
|
127
|
+
)
|
|
129
128
|
end
|
|
130
129
|
end
|
|
131
130
|
|
|
@@ -146,3 +145,5 @@ class Pgtk::Impatient
|
|
|
146
145
|
end
|
|
147
146
|
end
|
|
148
147
|
end
|
|
148
|
+
|
|
149
|
+
require_relative 'impatient/too_slow'
|
data/lib/pgtk/liquibase_task.rb
CHANGED
|
@@ -127,14 +127,15 @@ class Pgtk::LiquibaseTask < Rake::TaskLib
|
|
|
127
127
|
password = yml['pgsql']['password']
|
|
128
128
|
host = yml.dig('pgsql', 'host')
|
|
129
129
|
Dir.chdir(File.dirname(@schema)) do
|
|
130
|
-
|
|
130
|
+
File.write(
|
|
131
|
+
@schema,
|
|
131
132
|
if (local && @docker != :always) || @docker == :never
|
|
132
133
|
pgdump(yml, host, password)
|
|
133
134
|
else
|
|
134
135
|
host = donce_host if OS.mac? && ['localhost', '127.0.0.1'].include?(host)
|
|
135
136
|
dockerdump(yml, host, password)
|
|
136
137
|
end
|
|
137
|
-
|
|
138
|
+
)
|
|
138
139
|
end
|
|
139
140
|
end
|
|
140
141
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
require_relative '../../pgtk'
|
|
7
|
+
|
|
8
|
+
class Pgtk::LiquicheckTask; end
|
|
9
|
+
|
|
10
|
+
# Internal error raised by Pgtk::LiquicheckTask validation helpers and
|
|
11
|
+
# captured per-file by the +on+ helper to accumulate readable diagnostics.
|
|
12
|
+
#
|
|
13
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
14
|
+
# Copyright:: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
15
|
+
# License:: MIT
|
|
16
|
+
class Pgtk::LiquicheckTask::MustError < StandardError
|
|
17
|
+
end
|
data/lib/pgtk/liquicheck_task.rb
CHANGED
|
@@ -110,8 +110,6 @@ class Pgtk::LiquicheckTask < Rake::TaskLib
|
|
|
110
110
|
def confirm(prop, regex, msg)
|
|
111
111
|
raise(MustError, msg) unless prop.match?(regex)
|
|
112
112
|
end
|
|
113
|
-
|
|
114
|
-
class MustError < StandardError
|
|
115
|
-
end
|
|
116
|
-
private_constant :MustError
|
|
117
113
|
end
|
|
114
|
+
|
|
115
|
+
require_relative 'liquicheck_task/must_error'
|
data/lib/pgtk/pgsql_task.rb
CHANGED
|
@@ -51,18 +51,18 @@ class Pgtk::PgsqlTask < Rake::TaskLib
|
|
|
51
51
|
|
|
52
52
|
def run
|
|
53
53
|
local = detect(:local)
|
|
54
|
-
|
|
55
|
-
preflight(local, docker)
|
|
54
|
+
preflight(local, detect(:docker))
|
|
56
55
|
home = File.expand_path(@dir)
|
|
57
56
|
FileUtils.rm_rf(home) if @fresh
|
|
58
57
|
raise(ArgumentError, "Directory/file #{home} is present, use fresh=true") if File.exist?(home)
|
|
59
|
-
stdout = @quiet ? nil : $stdout
|
|
60
58
|
port = acquire
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
59
|
+
launch(local, home, @quiet ? nil : $stdout, port).then do |place|
|
|
60
|
+
save(port)
|
|
61
|
+
unless @quiet
|
|
62
|
+
puts("PostgreSQL has been started #{place}, port #{port}")
|
|
63
|
+
puts("YAML config saved to #{@yaml}")
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
def preflight(local, docker)
|
|
@@ -87,11 +87,9 @@ class Pgtk::PgsqlTask < Rake::TaskLib
|
|
|
87
87
|
|
|
88
88
|
def launch(local, home, stdout, port)
|
|
89
89
|
if (local && @docker != :always) || @docker == :never
|
|
90
|
-
|
|
91
|
-
"in process ##{pid}"
|
|
90
|
+
"in process ##{localize(home, stdout, port)}"
|
|
92
91
|
else
|
|
93
|
-
container
|
|
94
|
-
"in container #{container}"
|
|
92
|
+
"in container #{dockerize(home, stdout, port)}"
|
|
95
93
|
end
|
|
96
94
|
end
|
|
97
95
|
|
|
@@ -122,21 +120,19 @@ class Pgtk::PgsqlTask < Rake::TaskLib
|
|
|
122
120
|
|
|
123
121
|
def dockerize(home, stdout, port)
|
|
124
122
|
FileUtils.mkdir_p(home)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
)
|
|
139
|
-
container = out.scan(/[a-f0-9]+\Z/).first
|
|
123
|
+
container = qbash(
|
|
124
|
+
'docker',
|
|
125
|
+
'run',
|
|
126
|
+
"--publish #{Shellwords.escape("#{port}:5432")}",
|
|
127
|
+
"-e POSTGRES_USER=#{Shellwords.escape(@user)}",
|
|
128
|
+
"-e POSTGRES_PASSWORD=#{Shellwords.escape(@password)}",
|
|
129
|
+
"-e POSTGRES_DB=#{Shellwords.escape(@dbname)}",
|
|
130
|
+
'--detach',
|
|
131
|
+
'--rm',
|
|
132
|
+
'postgres:18.1',
|
|
133
|
+
@config.map { |k, v| "-c #{Shellwords.escape("#{k}=#{v}")}" },
|
|
134
|
+
stdout:
|
|
135
|
+
).scan(/[a-f0-9]+\Z/).first
|
|
140
136
|
File.write(File.join(home, 'docker-container'), container)
|
|
141
137
|
at_exit do
|
|
142
138
|
if qbash(
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
require_relative '../../pgtk'
|
|
7
|
+
|
|
8
|
+
class Pgtk::Pool; end
|
|
9
|
+
|
|
10
|
+
# Raised when no connection becomes available from the pool within
|
|
11
|
+
# the configured timeout. Indicates that all connections are currently
|
|
12
|
+
# taken by other threads and none was returned in time.
|
|
13
|
+
#
|
|
14
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
15
|
+
# Copyright:: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
16
|
+
# License:: MIT
|
|
17
|
+
class Pgtk::Pool::Busy < StandardError; end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
require_relative '../../pgtk'
|
|
7
|
+
require_relative 'busy'
|
|
8
|
+
|
|
9
|
+
class Pgtk::Pool; end
|
|
10
|
+
|
|
11
|
+
# Thread-safe queue implementation that supports iteration.
|
|
12
|
+
# Unlike Ruby's Queue class, this implementation allows safe iteration
|
|
13
|
+
# over all elements while maintaining thread safety for concurrent access.
|
|
14
|
+
#
|
|
15
|
+
# This class is used internally by Pool to store database connections
|
|
16
|
+
# and provide the ability to iterate over them for inspection purposes.
|
|
17
|
+
#
|
|
18
|
+
# The queue is bounded by size. When an item is taken out, it remains in
|
|
19
|
+
# the internal array but is marked as "taken". When returned, it's placed
|
|
20
|
+
# back in its original slot and marked as available.
|
|
21
|
+
#
|
|
22
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
23
|
+
# Copyright:: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
24
|
+
# License:: MIT
|
|
25
|
+
class Pgtk::Pool::IterableQueue
|
|
26
|
+
def initialize(size, timeout)
|
|
27
|
+
@size = size
|
|
28
|
+
@timeout = timeout
|
|
29
|
+
@items = []
|
|
30
|
+
@taken = []
|
|
31
|
+
@free = []
|
|
32
|
+
@mutex = Mutex.new
|
|
33
|
+
@condition = ConditionVariable.new
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def push(item)
|
|
37
|
+
@mutex.synchronize do
|
|
38
|
+
if @items.size < @size
|
|
39
|
+
@items << item
|
|
40
|
+
@taken << false
|
|
41
|
+
@free << (@items.size - 1)
|
|
42
|
+
else
|
|
43
|
+
index = @items.index(item)
|
|
44
|
+
if index.nil?
|
|
45
|
+
index = @taken.index(true)
|
|
46
|
+
raise(StandardError, 'No taken slot found') if index.nil?
|
|
47
|
+
@items[index] = item
|
|
48
|
+
end
|
|
49
|
+
@taken[index] = false
|
|
50
|
+
@free << index
|
|
51
|
+
end
|
|
52
|
+
@condition.signal
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def pop
|
|
57
|
+
@mutex.synchronize do
|
|
58
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + @timeout
|
|
59
|
+
while @free.empty?
|
|
60
|
+
remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
61
|
+
if remaining <= 0
|
|
62
|
+
raise(Pgtk::Pool::Busy, "No free connection appeared in the pool after #{@timeout}s of waiting")
|
|
63
|
+
end
|
|
64
|
+
@condition.wait(@mutex, remaining)
|
|
65
|
+
end
|
|
66
|
+
index = @free.shift
|
|
67
|
+
@taken[index] = true
|
|
68
|
+
@items[index]
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def size
|
|
73
|
+
@mutex.synchronize do
|
|
74
|
+
@items.size
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def map(&)
|
|
79
|
+
@mutex.synchronize do
|
|
80
|
+
@items.map(&)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
require 'tago'
|
|
7
|
+
require_relative '../../pgtk'
|
|
8
|
+
|
|
9
|
+
class Pgtk::Pool; end
|
|
10
|
+
|
|
11
|
+
# A temporary class to execute a single SQL request.
|
|
12
|
+
#
|
|
13
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
14
|
+
# Copyright:: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
15
|
+
# License:: MIT
|
|
16
|
+
class Pgtk::Pool::Txn
|
|
17
|
+
def initialize(conn, log)
|
|
18
|
+
@conn = conn
|
|
19
|
+
@log = log
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Exec a single parameterized command.
|
|
23
|
+
# @param [String] query The SQL query with params inside (possibly)
|
|
24
|
+
# @param [Array] args List of arguments
|
|
25
|
+
# @param [Integer] result Should be 0 for text results, 1 for binary
|
|
26
|
+
# @yield [Hash] Rows
|
|
27
|
+
def exec(query, args = [], result = 0)
|
|
28
|
+
start = Time.now
|
|
29
|
+
sql = query.is_a?(Array) ? query.join(' ') : query
|
|
30
|
+
@conn.instance_variable_set(:@pgtk_last_query, sql)
|
|
31
|
+
@conn.instance_variable_set(:@pgtk_started_at, start)
|
|
32
|
+
begin
|
|
33
|
+
out =
|
|
34
|
+
if args.empty?
|
|
35
|
+
@conn.exec(sql) do |res|
|
|
36
|
+
if block_given?
|
|
37
|
+
yield(res)
|
|
38
|
+
else
|
|
39
|
+
res.each.to_a
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
else
|
|
43
|
+
@conn.exec_params(sql, args, result) do |res|
|
|
44
|
+
if block_given?
|
|
45
|
+
yield(res)
|
|
46
|
+
else
|
|
47
|
+
res.each.to_a
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
rescue StandardError => e
|
|
52
|
+
@log.error("#{sql} -> #{e.message}")
|
|
53
|
+
raise(e)
|
|
54
|
+
end
|
|
55
|
+
if (Time.now - start) < 1
|
|
56
|
+
@log.debug("#{sql} >> #{start.ago} / #{@conn.object_id}")
|
|
57
|
+
else
|
|
58
|
+
@log.info("#{sql} >> #{start.ago}")
|
|
59
|
+
end
|
|
60
|
+
out
|
|
61
|
+
end
|
|
62
|
+
end
|
data/lib/pgtk/pool.rb
CHANGED
|
@@ -50,11 +50,6 @@ require_relative 'wire'
|
|
|
50
50
|
# Copyright:: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
51
51
|
# License:: MIT
|
|
52
52
|
class Pgtk::Pool
|
|
53
|
-
# Raised when no connection becomes available from the pool within
|
|
54
|
-
# the configured timeout. Indicates that all connections are currently
|
|
55
|
-
# taken by other threads and none was returned in time.
|
|
56
|
-
class Busy < StandardError; end
|
|
57
|
-
|
|
58
53
|
# Constructor.
|
|
59
54
|
#
|
|
60
55
|
# The +idle+ option guards against the cold-slot SSL desync that bites
|
|
@@ -198,9 +193,7 @@ class Pgtk::Pool
|
|
|
198
193
|
t = Txn.new(c, @log)
|
|
199
194
|
t.exec('START TRANSACTION')
|
|
200
195
|
begin
|
|
201
|
-
|
|
202
|
-
t.exec('COMMIT')
|
|
203
|
-
r
|
|
196
|
+
yield(t).tap { t.exec('COMMIT') }
|
|
204
197
|
ensure
|
|
205
198
|
if c.transaction_status != PG::Constants::PQTRANS_IDLE
|
|
206
199
|
begin
|
|
@@ -213,124 +206,6 @@ class Pgtk::Pool
|
|
|
213
206
|
end
|
|
214
207
|
end
|
|
215
208
|
|
|
216
|
-
# Thread-safe queue implementation that supports iteration.
|
|
217
|
-
# Unlike Ruby's Queue class, this implementation allows safe iteration
|
|
218
|
-
# over all elements while maintaining thread safety for concurrent access.
|
|
219
|
-
#
|
|
220
|
-
# This class is used internally by Pool to store database connections
|
|
221
|
-
# and provide the ability to iterate over them for inspection purposes.
|
|
222
|
-
#
|
|
223
|
-
# The queue is bounded by size. When an item is taken out, it remains in
|
|
224
|
-
# the internal array but is marked as "taken". When returned, it's placed
|
|
225
|
-
# back in its original slot and marked as available.
|
|
226
|
-
class IterableQueue
|
|
227
|
-
def initialize(size, timeout)
|
|
228
|
-
@size = size
|
|
229
|
-
@timeout = timeout
|
|
230
|
-
@items = []
|
|
231
|
-
@taken = []
|
|
232
|
-
@free = []
|
|
233
|
-
@mutex = Mutex.new
|
|
234
|
-
@condition = ConditionVariable.new
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
def push(item)
|
|
238
|
-
@mutex.synchronize do
|
|
239
|
-
if @items.size < @size
|
|
240
|
-
@items << item
|
|
241
|
-
@taken << false
|
|
242
|
-
@free << (@items.size - 1)
|
|
243
|
-
else
|
|
244
|
-
index = @items.index(item)
|
|
245
|
-
if index.nil?
|
|
246
|
-
index = @taken.index(true)
|
|
247
|
-
raise(StandardError, 'No taken slot found') if index.nil?
|
|
248
|
-
@items[index] = item
|
|
249
|
-
end
|
|
250
|
-
@taken[index] = false
|
|
251
|
-
@free << index
|
|
252
|
-
end
|
|
253
|
-
@condition.signal
|
|
254
|
-
end
|
|
255
|
-
end
|
|
256
|
-
|
|
257
|
-
def pop
|
|
258
|
-
@mutex.synchronize do
|
|
259
|
-
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + @timeout
|
|
260
|
-
while @free.empty?
|
|
261
|
-
remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
262
|
-
raise(Busy, "No free connection appeared in the pool after #{@timeout}s of waiting") if remaining <= 0
|
|
263
|
-
@condition.wait(@mutex, remaining)
|
|
264
|
-
end
|
|
265
|
-
index = @free.shift
|
|
266
|
-
@taken[index] = true
|
|
267
|
-
@items[index]
|
|
268
|
-
end
|
|
269
|
-
end
|
|
270
|
-
|
|
271
|
-
def size
|
|
272
|
-
@mutex.synchronize do
|
|
273
|
-
@items.size
|
|
274
|
-
end
|
|
275
|
-
end
|
|
276
|
-
|
|
277
|
-
def map(&)
|
|
278
|
-
@mutex.synchronize do
|
|
279
|
-
@items.map(&)
|
|
280
|
-
end
|
|
281
|
-
end
|
|
282
|
-
end
|
|
283
|
-
|
|
284
|
-
# A temporary class to execute a single SQL request.
|
|
285
|
-
class Txn
|
|
286
|
-
def initialize(conn, log)
|
|
287
|
-
@conn = conn
|
|
288
|
-
@log = log
|
|
289
|
-
end
|
|
290
|
-
|
|
291
|
-
# Exec a single parameterized command.
|
|
292
|
-
# @param [String] query The SQL query with params inside (possibly)
|
|
293
|
-
# @param [Array] args List of arguments
|
|
294
|
-
# @param [Integer] result Should be 0 for text results, 1 for binary
|
|
295
|
-
# @yield [Hash] Rows
|
|
296
|
-
def exec(query, args = [], result = 0)
|
|
297
|
-
start = Time.now
|
|
298
|
-
sql = query.is_a?(Array) ? query.join(' ') : query
|
|
299
|
-
@conn.instance_variable_set(:@pgtk_last_query, sql)
|
|
300
|
-
@conn.instance_variable_set(:@pgtk_started_at, start)
|
|
301
|
-
begin
|
|
302
|
-
out =
|
|
303
|
-
if args.empty?
|
|
304
|
-
@conn.exec(sql) do |res|
|
|
305
|
-
if block_given?
|
|
306
|
-
yield(res)
|
|
307
|
-
else
|
|
308
|
-
res.each.to_a
|
|
309
|
-
end
|
|
310
|
-
end
|
|
311
|
-
else
|
|
312
|
-
@conn.exec_params(sql, args, result) do |res|
|
|
313
|
-
if block_given?
|
|
314
|
-
yield(res)
|
|
315
|
-
else
|
|
316
|
-
res.each.to_a
|
|
317
|
-
end
|
|
318
|
-
end
|
|
319
|
-
end
|
|
320
|
-
rescue StandardError => e
|
|
321
|
-
@log.error("#{sql} -> #{e.message}")
|
|
322
|
-
raise(e)
|
|
323
|
-
end
|
|
324
|
-
lag = Time.now - start
|
|
325
|
-
if lag < 1
|
|
326
|
-
@log.debug("#{sql} >> #{start.ago} / #{@conn.object_id}")
|
|
327
|
-
else
|
|
328
|
-
@log.info("#{sql} >> #{start.ago}")
|
|
329
|
-
end
|
|
330
|
-
out
|
|
331
|
-
end
|
|
332
|
-
end
|
|
333
|
-
|
|
334
209
|
private
|
|
335
210
|
|
|
336
211
|
def connect
|
|
@@ -371,9 +246,9 @@ class Pgtk::Pool
|
|
|
371
246
|
end
|
|
372
247
|
|
|
373
248
|
def stale(conn)
|
|
374
|
-
return
|
|
249
|
+
return if @idle.nil?
|
|
375
250
|
last = conn.instance_variable_get(:@pgtk_last_used)
|
|
376
|
-
return
|
|
251
|
+
return if last.nil? || Time.now - last < @idle
|
|
377
252
|
begin
|
|
378
253
|
conn.exec('SELECT 1')
|
|
379
254
|
nil
|
|
@@ -383,19 +258,23 @@ class Pgtk::Pool
|
|
|
383
258
|
end
|
|
384
259
|
|
|
385
260
|
def info(conn)
|
|
386
|
-
pipelines = { PG::Constants::PQ_PIPELINE_ON => 'ON', PG::Constants::PQ_PIPELINE_OFF => 'OFF',
|
|
387
|
-
PG::Constants::PQ_PIPELINE_ABORTED => 'ABORTED' }
|
|
388
|
-
statuses = { PG::Constants::CONNECTION_OK => 'OK', PG::Constants::CONNECTION_BAD => 'BAD' }
|
|
389
|
-
transactions = { PG::Constants::PQTRANS_IDLE => 'IDLE', PG::Constants::PQTRANS_ACTIVE => 'ACTIVE',
|
|
390
|
-
PG::Constants::PQTRANS_INTRANS => 'INTRANS', PG::Constants::PQTRANS_INERROR => 'INERROR',
|
|
391
|
-
PG::Constants::PQTRANS_UNKNOWN => 'UNKNOWN' }
|
|
392
261
|
conn.instance_variable_set(:@pgtk_pid, conn.backend_pid)
|
|
393
262
|
parts = [
|
|
394
263
|
' ',
|
|
395
264
|
"##{conn.backend_pid}",
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
265
|
+
{
|
|
266
|
+
PG::Constants::PQ_PIPELINE_ON => 'ON', PG::Constants::PQ_PIPELINE_OFF => 'OFF',
|
|
267
|
+
PG::Constants::PQ_PIPELINE_ABORTED => 'ABORTED'
|
|
268
|
+
}.fetch(conn.pipeline_status, "pipeline_status=#{conn.pipeline_status}"),
|
|
269
|
+
{ PG::Constants::CONNECTION_OK => 'OK', PG::Constants::CONNECTION_BAD => 'BAD' }.fetch(
|
|
270
|
+
conn.status,
|
|
271
|
+
"status=#{conn.status}"
|
|
272
|
+
),
|
|
273
|
+
{
|
|
274
|
+
PG::Constants::PQTRANS_IDLE => 'IDLE', PG::Constants::PQTRANS_ACTIVE => 'ACTIVE',
|
|
275
|
+
PG::Constants::PQTRANS_INTRANS => 'INTRANS', PG::Constants::PQTRANS_INERROR => 'INERROR',
|
|
276
|
+
PG::Constants::PQTRANS_UNKNOWN => 'UNKNOWN'
|
|
277
|
+
}.fetch(conn.transaction_status, "transaction_status=#{conn.transaction_status}")
|
|
399
278
|
]
|
|
400
279
|
if conn.transaction_status != PG::Constants::PQTRANS_IDLE
|
|
401
280
|
started = conn.instance_variable_get(:@pgtk_started_at)
|
|
@@ -434,3 +313,7 @@ class Pgtk::Pool
|
|
|
434
313
|
@wire.connection
|
|
435
314
|
end
|
|
436
315
|
end
|
|
316
|
+
|
|
317
|
+
require_relative 'pool/busy'
|
|
318
|
+
require_relative 'pool/iterable_queue'
|
|
319
|
+
require_relative 'pool/txn'
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
require_relative '../../pgtk'
|
|
7
|
+
|
|
8
|
+
class Pgtk::Retry; end
|
|
9
|
+
|
|
10
|
+
# Raised when all retry attempts have been exhausted. The original
|
|
11
|
+
# exception that caused the last failure is available via #cause,
|
|
12
|
+
# so its message and stack trace are preserved for debugging.
|
|
13
|
+
#
|
|
14
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
15
|
+
# Copyright:: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
16
|
+
# License:: MIT
|
|
17
|
+
class Pgtk::Retry::Exhausted < StandardError; end
|
data/lib/pgtk/retry.rb
CHANGED
|
@@ -49,11 +49,6 @@ require_relative 'impatient'
|
|
|
49
49
|
# Copyright:: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
50
50
|
# License:: MIT
|
|
51
51
|
class Pgtk::Retry
|
|
52
|
-
# Raised when all retry attempts have been exhausted. The original
|
|
53
|
-
# exception that caused the last failure is available via #cause,
|
|
54
|
-
# so its message and stack trace are preserved for debugging.
|
|
55
|
-
class Exhausted < StandardError; end
|
|
56
|
-
|
|
57
52
|
BACKOFFS = [0.05, 0.2, 1.0].freeze
|
|
58
53
|
|
|
59
54
|
# Constructor.
|
|
@@ -122,3 +117,5 @@ class Pgtk::Retry
|
|
|
122
117
|
@pool.transaction(&)
|
|
123
118
|
end
|
|
124
119
|
end
|
|
120
|
+
|
|
121
|
+
require_relative 'retry/exhausted'
|
data/lib/pgtk/spy.rb
CHANGED
|
@@ -82,10 +82,8 @@ class Pgtk::Spy
|
|
|
82
82
|
# @param [String] sql The SQL query with params inside (possibly)
|
|
83
83
|
# @return [Array] Result rows
|
|
84
84
|
def exec(sql, *)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
@block&.call(sql.is_a?(Array) ? sql.join(' ') : sql, Time.now - start)
|
|
88
|
-
ret
|
|
85
|
+
@block&.call(sql.is_a?(Array) ? sql.join(' ') : sql, Time.now - Time.now)
|
|
86
|
+
@pool.exec(sql, *)
|
|
89
87
|
end
|
|
90
88
|
|
|
91
89
|
# Run a transaction with spying on each SQL query.
|
data/lib/pgtk/stash.rb
CHANGED
|
@@ -109,8 +109,7 @@ class Pgtk::Stash
|
|
|
109
109
|
# @return [String] Multi-line text representation of the current cache state
|
|
110
110
|
def dump
|
|
111
111
|
@entrance.with_read_lock do
|
|
112
|
-
|
|
113
|
-
body(qq)
|
|
112
|
+
body(queries)
|
|
114
113
|
end
|
|
115
114
|
end
|
|
116
115
|
|
|
@@ -227,22 +226,20 @@ class Pgtk::Stash
|
|
|
227
226
|
affected.each { |t| @stash[:table_inflight][t] = (@stash[:table_inflight][t] || 0) + 1 }
|
|
228
227
|
end
|
|
229
228
|
begin
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
@stash[:queries][q][key][:stale] = stamp
|
|
229
|
+
@pool.exec(pure, params, result).tap do
|
|
230
|
+
now = Time.now
|
|
231
|
+
@entrance.with_write_lock do
|
|
232
|
+
affected.each do |t|
|
|
233
|
+
@stash[:table_inflight][t] -= 1
|
|
234
|
+
@stash[:table_mod][t] = (@stash[:table_mod][t] || 0) + 1
|
|
235
|
+
@stash[:tables][t]&.each do |q|
|
|
236
|
+
@stash[:queries][q]&.each_key do |key|
|
|
237
|
+
@stash[:queries][q][key][:stale] = now
|
|
238
|
+
end
|
|
241
239
|
end
|
|
242
240
|
end
|
|
243
241
|
end
|
|
244
242
|
end
|
|
245
|
-
ret
|
|
246
243
|
rescue StandardError
|
|
247
244
|
@entrance.with_write_lock do
|
|
248
245
|
affected.each { |t| @stash[:table_inflight][t] -= 1 }
|
|
@@ -306,8 +303,6 @@ class Pgtk::Stash
|
|
|
306
303
|
|
|
307
304
|
# Discover ON DELETE CASCADE / ON UPDATE CASCADE foreign keys so that a
|
|
308
305
|
# modify on the parent table also invalidates cached queries on children.
|
|
309
|
-
#
|
|
310
|
-
# @return [nil]
|
|
311
306
|
def cascade!
|
|
312
307
|
direct = Hash.new { |h, k| h[k] = [] }
|
|
313
308
|
@pool.exec(<<~SQL).each { |r| direct[r['parent']] << r['child'] }
|
|
@@ -324,7 +319,6 @@ class Pgtk::Stash
|
|
|
324
319
|
AND tc.table_schema NOT IN ('pg_catalog', 'information_schema')
|
|
325
320
|
SQL
|
|
326
321
|
@cascades = direct.keys.to_h { |p| [p, transitive(p, direct, []).uniq] }
|
|
327
|
-
nil
|
|
328
322
|
end
|
|
329
323
|
|
|
330
324
|
def transitive(parent, direct, seen)
|
data/lib/pgtk/version.rb
CHANGED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
require 'pg'
|
|
7
|
+
require_relative '../../pgtk'
|
|
8
|
+
|
|
9
|
+
module Pgtk::Wire; end
|
|
10
|
+
|
|
11
|
+
# Simple wire with details.
|
|
12
|
+
#
|
|
13
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
14
|
+
# Copyright:: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
15
|
+
# License:: MIT
|
|
16
|
+
class Pgtk::Wire::Direct
|
|
17
|
+
# Constructor.
|
|
18
|
+
#
|
|
19
|
+
# @param [String] host Host name of the PostgreSQL server
|
|
20
|
+
# @param [Integer] port Port number of the PostgreSQL server
|
|
21
|
+
# @param [String] dbname Database name
|
|
22
|
+
# @param [String] user Username
|
|
23
|
+
# @param [String] password Password
|
|
24
|
+
# @param [Hash] opts Extra options forwarded to +PG.connect+ (e.g. +sslmode+,
|
|
25
|
+
# +connect_timeout+, +keepalives+, +keepalives_idle+, +application_name+)
|
|
26
|
+
def initialize(host:, port:, dbname:, user:, password:, **opts)
|
|
27
|
+
raise(ArgumentError, "The host can't be nil") if host.nil?
|
|
28
|
+
@host = host
|
|
29
|
+
raise(ArgumentError, "The port can't be nil") if port.nil?
|
|
30
|
+
@port = port
|
|
31
|
+
@dbname = dbname
|
|
32
|
+
@user = user
|
|
33
|
+
@password = password
|
|
34
|
+
@opts = opts
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Create a new connection to PostgreSQL server.
|
|
38
|
+
def connection
|
|
39
|
+
PG.connect(dbname: @dbname, host: @host, port: @port, user: @user, password: @password, **@opts)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
require 'cgi'
|
|
7
|
+
require 'uri'
|
|
8
|
+
require_relative '../../pgtk'
|
|
9
|
+
require_relative 'direct'
|
|
10
|
+
|
|
11
|
+
module Pgtk::Wire; end
|
|
12
|
+
|
|
13
|
+
# Using ENV variable.
|
|
14
|
+
#
|
|
15
|
+
# The value of the variable should be in this format:
|
|
16
|
+
#
|
|
17
|
+
# postgres://user:password@host:port/dbname
|
|
18
|
+
#
|
|
19
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
20
|
+
# Copyright:: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
21
|
+
# License:: MIT
|
|
22
|
+
class Pgtk::Wire::Env
|
|
23
|
+
# Constructor.
|
|
24
|
+
#
|
|
25
|
+
# @param [String] var The name of the environment variable with the connection URL
|
|
26
|
+
# @param [Hash] opts Extra options forwarded to +PG.connect+ (e.g. +sslmode+,
|
|
27
|
+
# +connect_timeout+, +keepalives+, +keepalives_idle+, +application_name+).
|
|
28
|
+
# Explicit kwargs win over options carried in the URL query string on conflict.
|
|
29
|
+
def initialize(var = 'DATABASE_URL', **opts)
|
|
30
|
+
raise(ArgumentError, "The name of the environment variable can't be nil") if var.nil?
|
|
31
|
+
@value = ENV.fetch(var, nil)
|
|
32
|
+
raise(ArgumentError, "The environment variable #{@value.inspect} is not set") if @value.nil?
|
|
33
|
+
@opts = opts
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Create a new connection to PostgreSQL server.
|
|
37
|
+
def connection
|
|
38
|
+
uri = URI(@value)
|
|
39
|
+
Pgtk::Wire::Direct.new(
|
|
40
|
+
host: CGI.unescape(uri.host),
|
|
41
|
+
port: uri.port || 5432,
|
|
42
|
+
dbname: CGI.unescape(uri.path[1..]),
|
|
43
|
+
user: CGI.unescape(uri.userinfo.split(':')[0]),
|
|
44
|
+
password: CGI.unescape(uri.userinfo.split(':')[1]),
|
|
45
|
+
**(uri.query ? URI.decode_www_form(uri.query).to_h.transform_keys(&:to_sym) : {}).merge(@opts)
|
|
46
|
+
).connection
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
require 'yaml'
|
|
7
|
+
require_relative '../../pgtk'
|
|
8
|
+
require_relative 'direct'
|
|
9
|
+
|
|
10
|
+
module Pgtk::Wire; end
|
|
11
|
+
|
|
12
|
+
# Using configuration from YAML file.
|
|
13
|
+
#
|
|
14
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
15
|
+
# Copyright:: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
16
|
+
# License:: MIT
|
|
17
|
+
class Pgtk::Wire::Yaml
|
|
18
|
+
# Constructor.
|
|
19
|
+
#
|
|
20
|
+
# @param [String] file Path to the YAML configuration file
|
|
21
|
+
# @param [String] node The root node name in the YAML file containing PostgreSQL configuration
|
|
22
|
+
def initialize(file, node = 'pgsql')
|
|
23
|
+
raise(ArgumentError, "The name of the file can't be nil") if file.nil?
|
|
24
|
+
@file = file
|
|
25
|
+
raise(ArgumentError, "The name of the node in the YAML file can't be nil") if node.nil?
|
|
26
|
+
@node = node
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Create a new connection to PostgreSQL server.
|
|
30
|
+
def connection
|
|
31
|
+
raise(ArgumentError, "The file #{@file.inspect} not found") unless File.exist?(@file)
|
|
32
|
+
cfg = ::YAML.load_file(@file)
|
|
33
|
+
raise(ArgumentError, "The node '#{@node}' not found in YAML file #{@file.inspect}") unless cfg[@node]
|
|
34
|
+
Pgtk::Wire::Direct.new(
|
|
35
|
+
host: cfg[@node]['host'],
|
|
36
|
+
port: cfg[@node]['port'],
|
|
37
|
+
dbname: cfg[@node]['dbname'],
|
|
38
|
+
user: cfg[@node]['user'],
|
|
39
|
+
password: cfg[@node]['password']
|
|
40
|
+
).connection
|
|
41
|
+
end
|
|
42
|
+
end
|
data/lib/pgtk/wire.rb
CHANGED
|
@@ -3,10 +3,6 @@
|
|
|
3
3
|
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
4
|
# SPDX-License-Identifier: MIT
|
|
5
5
|
|
|
6
|
-
require 'cgi'
|
|
7
|
-
require 'pg'
|
|
8
|
-
require 'uri'
|
|
9
|
-
require 'yaml'
|
|
10
6
|
require_relative '../pgtk'
|
|
11
7
|
|
|
12
8
|
# Wires.
|
|
@@ -14,103 +10,8 @@ require_relative '../pgtk'
|
|
|
14
10
|
# Copyright:: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
15
11
|
# License:: MIT
|
|
16
12
|
module Pgtk::Wire
|
|
17
|
-
# Simple wire with details.
|
|
18
|
-
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
19
|
-
# Copyright:: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
20
|
-
# License:: MIT
|
|
21
|
-
class Direct
|
|
22
|
-
# Constructor.
|
|
23
|
-
#
|
|
24
|
-
# @param [String] host Host name of the PostgreSQL server
|
|
25
|
-
# @param [Integer] port Port number of the PostgreSQL server
|
|
26
|
-
# @param [String] dbname Database name
|
|
27
|
-
# @param [String] user Username
|
|
28
|
-
# @param [String] password Password
|
|
29
|
-
# @param [Hash] opts Extra options forwarded to +PG.connect+ (e.g. +sslmode+,
|
|
30
|
-
# +connect_timeout+, +keepalives+, +keepalives_idle+, +application_name+)
|
|
31
|
-
def initialize(host:, port:, dbname:, user:, password:, **opts)
|
|
32
|
-
raise(ArgumentError, "The host can't be nil") if host.nil?
|
|
33
|
-
@host = host
|
|
34
|
-
raise(ArgumentError, "The port can't be nil") if port.nil?
|
|
35
|
-
@port = port
|
|
36
|
-
@dbname = dbname
|
|
37
|
-
@user = user
|
|
38
|
-
@password = password
|
|
39
|
-
@opts = opts
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# Create a new connection to PostgreSQL server.
|
|
43
|
-
def connection
|
|
44
|
-
PG.connect(dbname: @dbname, host: @host, port: @port, user: @user, password: @password, **@opts)
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Using ENV variable.
|
|
49
|
-
#
|
|
50
|
-
# The value of the variable should be in this format:
|
|
51
|
-
#
|
|
52
|
-
# postgres://user:password@host:port/dbname
|
|
53
|
-
#
|
|
54
|
-
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
55
|
-
# Copyright:: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
56
|
-
# License:: MIT
|
|
57
|
-
class Env
|
|
58
|
-
# Constructor.
|
|
59
|
-
#
|
|
60
|
-
# @param [String] var The name of the environment variable with the connection URL
|
|
61
|
-
# @param [Hash] opts Extra options forwarded to +PG.connect+ (e.g. +sslmode+,
|
|
62
|
-
# +connect_timeout+, +keepalives+, +keepalives_idle+, +application_name+).
|
|
63
|
-
# Explicit kwargs win over options carried in the URL query string on conflict.
|
|
64
|
-
def initialize(var = 'DATABASE_URL', **opts)
|
|
65
|
-
raise(ArgumentError, "The name of the environment variable can't be nil") if var.nil?
|
|
66
|
-
@value = ENV.fetch(var, nil)
|
|
67
|
-
raise(ArgumentError, "The environment variable #{@value.inspect} is not set") if @value.nil?
|
|
68
|
-
@opts = opts
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
# Create a new connection to PostgreSQL server.
|
|
72
|
-
def connection
|
|
73
|
-
uri = URI(@value)
|
|
74
|
-
extras = uri.query ? URI.decode_www_form(uri.query).to_h.transform_keys(&:to_sym) : {}
|
|
75
|
-
Pgtk::Wire::Direct.new(
|
|
76
|
-
host: CGI.unescape(uri.host),
|
|
77
|
-
port: uri.port || 5432,
|
|
78
|
-
dbname: CGI.unescape(uri.path[1..]),
|
|
79
|
-
user: CGI.unescape(uri.userinfo.split(':')[0]),
|
|
80
|
-
password: CGI.unescape(uri.userinfo.split(':')[1]),
|
|
81
|
-
**extras.merge(@opts)
|
|
82
|
-
).connection
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
# Using configuration from YAML file.
|
|
87
|
-
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
88
|
-
# Copyright:: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
89
|
-
# License:: MIT
|
|
90
|
-
class Yaml
|
|
91
|
-
# Constructor.
|
|
92
|
-
#
|
|
93
|
-
# @param [String] file Path to the YAML configuration file
|
|
94
|
-
# @param [String] node The root node name in the YAML file containing PostgreSQL configuration
|
|
95
|
-
def initialize(file, node = 'pgsql')
|
|
96
|
-
raise(ArgumentError, "The name of the file can't be nil") if file.nil?
|
|
97
|
-
@file = file
|
|
98
|
-
raise(ArgumentError, "The name of the node in the YAML file can't be nil") if node.nil?
|
|
99
|
-
@node = node
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
# Create a new connection to PostgreSQL server.
|
|
103
|
-
def connection
|
|
104
|
-
raise(ArgumentError, "The file #{@file.inspect} not found") unless File.exist?(@file)
|
|
105
|
-
cfg = YAML.load_file(@file)
|
|
106
|
-
raise(ArgumentError, "The node '#{@node}' not found in YAML file #{@file.inspect}") unless cfg[@node]
|
|
107
|
-
Pgtk::Wire::Direct.new(
|
|
108
|
-
host: cfg[@node]['host'],
|
|
109
|
-
port: cfg[@node]['port'],
|
|
110
|
-
dbname: cfg[@node]['dbname'],
|
|
111
|
-
user: cfg[@node]['user'],
|
|
112
|
-
password: cfg[@node]['password']
|
|
113
|
-
).connection
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
13
|
end
|
|
14
|
+
|
|
15
|
+
require_relative 'wire/direct'
|
|
16
|
+
require_relative 'wire/env'
|
|
17
|
+
require_relative 'wire/yaml'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pgtk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.32.
|
|
4
|
+
version: 0.32.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yegor Bugayenko
|
|
@@ -197,15 +197,24 @@ files:
|
|
|
197
197
|
- cucumber.yml
|
|
198
198
|
- lib/pgtk.rb
|
|
199
199
|
- lib/pgtk/impatient.rb
|
|
200
|
+
- lib/pgtk/impatient/too_slow.rb
|
|
200
201
|
- lib/pgtk/liquibase_task.rb
|
|
201
202
|
- lib/pgtk/liquicheck_task.rb
|
|
203
|
+
- lib/pgtk/liquicheck_task/must_error.rb
|
|
202
204
|
- lib/pgtk/pgsql_task.rb
|
|
203
205
|
- lib/pgtk/pool.rb
|
|
206
|
+
- lib/pgtk/pool/busy.rb
|
|
207
|
+
- lib/pgtk/pool/iterable_queue.rb
|
|
208
|
+
- lib/pgtk/pool/txn.rb
|
|
204
209
|
- lib/pgtk/retry.rb
|
|
210
|
+
- lib/pgtk/retry/exhausted.rb
|
|
205
211
|
- lib/pgtk/spy.rb
|
|
206
212
|
- lib/pgtk/stash.rb
|
|
207
213
|
- lib/pgtk/version.rb
|
|
208
214
|
- lib/pgtk/wire.rb
|
|
215
|
+
- lib/pgtk/wire/direct.rb
|
|
216
|
+
- lib/pgtk/wire/env.rb
|
|
217
|
+
- lib/pgtk/wire/yaml.rb
|
|
209
218
|
- pgtk.gemspec
|
|
210
219
|
- resources/pom.xml
|
|
211
220
|
- test-resources/2019/01-test.xml
|