pgtk 0.30.6 → 0.30.7
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 +1 -0
- data/Gemfile.lock +17 -13
- data/Rakefile +2 -2
- data/lib/pgtk/impatient.rb +5 -5
- data/lib/pgtk/liquibase_task.rb +83 -93
- data/lib/pgtk/liquicheck_task.rb +59 -62
- data/lib/pgtk/pgsql_task.rb +63 -89
- data/lib/pgtk/pool.rb +12 -12
- data/lib/pgtk/retry.rb +3 -3
- data/lib/pgtk/spy.rb +2 -2
- data/lib/pgtk/stash.rb +217 -202
- data/lib/pgtk/version.rb +1 -2
- data/lib/pgtk/wire.rb +81 -85
- data/lib/pgtk.rb +0 -1
- data/pgtk.gemspec +13 -13
- metadata +1 -1
data/lib/pgtk/pgsql_task.rb
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'English'
|
|
3
4
|
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
5
|
# SPDX-License-Identifier: MIT
|
|
5
6
|
|
|
6
7
|
require 'cgi'
|
|
7
|
-
require 'English'
|
|
8
8
|
require 'qbash'
|
|
9
9
|
require 'rake'
|
|
10
10
|
require 'rake/tasklib'
|
|
11
11
|
require 'random-port'
|
|
12
|
-
require 'shellwords'
|
|
13
12
|
require 'securerandom'
|
|
13
|
+
require 'shellwords'
|
|
14
14
|
require 'tempfile'
|
|
15
|
-
require 'yaml'
|
|
16
15
|
require 'waitutil'
|
|
16
|
+
require 'yaml'
|
|
17
17
|
require_relative '../pgtk'
|
|
18
18
|
|
|
19
19
|
# Pgsql rake task.
|
|
@@ -21,49 +21,7 @@ require_relative '../pgtk'
|
|
|
21
21
|
# Copyright:: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
22
22
|
# License:: MIT
|
|
23
23
|
class Pgtk::PgsqlTask < Rake::TaskLib
|
|
24
|
-
|
|
25
|
-
# @return [Symbol]
|
|
26
|
-
attr_accessor :name
|
|
27
|
-
|
|
28
|
-
# Directory where PostgreSQL server files will be stored
|
|
29
|
-
# @return [String]
|
|
30
|
-
attr_accessor :dir
|
|
31
|
-
|
|
32
|
-
# Whether to delete the PostgreSQL data directory on each run
|
|
33
|
-
# @return [Boolean]
|
|
34
|
-
attr_accessor :fresh_start
|
|
35
|
-
|
|
36
|
-
# PostgreSQL username
|
|
37
|
-
# @return [String]
|
|
38
|
-
attr_accessor :user
|
|
39
|
-
|
|
40
|
-
# PostgreSQL password
|
|
41
|
-
# @return [String]
|
|
42
|
-
attr_accessor :password
|
|
43
|
-
|
|
44
|
-
# PostgreSQL database name
|
|
45
|
-
# @return [String]
|
|
46
|
-
attr_accessor :dbname
|
|
47
|
-
|
|
48
|
-
# Path to YAML file where configuration will be written
|
|
49
|
-
# @return [String]
|
|
50
|
-
attr_accessor :yaml
|
|
51
|
-
|
|
52
|
-
# Whether to suppress output
|
|
53
|
-
# @return [Boolean]
|
|
54
|
-
attr_accessor :quiet
|
|
55
|
-
|
|
56
|
-
# TCP port for PostgreSQL server (random if nil)
|
|
57
|
-
# @return [Integer, nil]
|
|
58
|
-
attr_accessor :port
|
|
59
|
-
|
|
60
|
-
# Configuration options for PostgreSQL server
|
|
61
|
-
# @return [Hash]
|
|
62
|
-
attr_accessor :config
|
|
63
|
-
|
|
64
|
-
# Use docker (set to either :never, :always, or :maybe)
|
|
65
|
-
# @return [Symbol]
|
|
66
|
-
attr_accessor :docker
|
|
24
|
+
attr_accessor :name, :dir, :fresh, :user, :password, :dbname, :yaml, :quiet, :port, :config, :docker
|
|
67
25
|
|
|
68
26
|
# Initialize a new PostgreSQL server task.
|
|
69
27
|
#
|
|
@@ -73,16 +31,16 @@ class Pgtk::PgsqlTask < Rake::TaskLib
|
|
|
73
31
|
super()
|
|
74
32
|
@docker ||= :maybe
|
|
75
33
|
@name = args.shift || :pgsql
|
|
76
|
-
@
|
|
34
|
+
@fresh = false
|
|
77
35
|
@quiet = false
|
|
78
36
|
@user = 'test'
|
|
79
37
|
@config = {}
|
|
80
38
|
@password = 'test'
|
|
81
39
|
@dbname = 'test'
|
|
82
40
|
@port = nil
|
|
83
|
-
desc
|
|
41
|
+
desc('Start a local PostgreSQL server') unless ::Rake.application.last_description
|
|
84
42
|
task(name, *args) do |_, task_args|
|
|
85
|
-
RakeFileUtils.
|
|
43
|
+
RakeFileUtils.verbose(true) do
|
|
86
44
|
yield(*[self, task_args].slice(0, task_block.arity)) if block_given?
|
|
87
45
|
run
|
|
88
46
|
end
|
|
@@ -92,35 +50,52 @@ class Pgtk::PgsqlTask < Rake::TaskLib
|
|
|
92
50
|
private
|
|
93
51
|
|
|
94
52
|
def run
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
docker = docker_out[1].zero?
|
|
99
|
-
unless local || docker
|
|
100
|
-
raise \
|
|
101
|
-
"Failed to find either PostgreSQL or Docker:\n#{pg_out.first}\n#{docker_out.first}"
|
|
102
|
-
end
|
|
103
|
-
raise 'You cannot force Docker to run, because it is not installed locally' if @docker == :always && !docker
|
|
104
|
-
raise "Option 'dir' is mandatory" unless @dir
|
|
105
|
-
raise "Option 'yaml' is mandatory" unless @yaml
|
|
53
|
+
local = detect(:local)
|
|
54
|
+
docker = detect(:docker)
|
|
55
|
+
preflight(local, docker)
|
|
106
56
|
home = File.expand_path(@dir)
|
|
107
|
-
FileUtils.rm_rf(home) if @
|
|
108
|
-
raise "Directory/file #{home} is present, use
|
|
57
|
+
FileUtils.rm_rf(home) if @fresh
|
|
58
|
+
raise(ArgumentError, "Directory/file #{home} is present, use fresh=true") if File.exist?(home)
|
|
109
59
|
stdout = @quiet ? nil : $stdout
|
|
60
|
+
port = acquire
|
|
61
|
+
place = launch(local, home, stdout, port)
|
|
62
|
+
save(port)
|
|
63
|
+
return if @quiet
|
|
64
|
+
puts("PostgreSQL has been started #{place}, port #{port}")
|
|
65
|
+
puts("YAML config saved to #{@yaml}")
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def preflight(local, docker)
|
|
69
|
+
raise(IOError, 'Failed to find either PostgreSQL or Docker') unless local || docker
|
|
70
|
+
if @docker == :always && !docker
|
|
71
|
+
raise(ArgumentError, 'You cannot force Docker to run, because it is not installed locally')
|
|
72
|
+
end
|
|
73
|
+
raise(ArgumentError, "Option 'dir' is mandatory") unless @dir
|
|
74
|
+
raise(ArgumentError, "Option 'yaml' is mandatory") unless @yaml
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def acquire
|
|
110
78
|
port = @port
|
|
111
79
|
if port.nil?
|
|
112
80
|
port = RandomPort::Pool::SINGLETON.acquire
|
|
113
|
-
puts
|
|
81
|
+
puts("Random TCP port #{port} is used for PostgreSQL server") unless @quiet
|
|
114
82
|
else
|
|
115
|
-
puts
|
|
83
|
+
puts("Required TCP port #{port} is used for PostgreSQL server") unless @quiet
|
|
116
84
|
end
|
|
85
|
+
port
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def launch(local, home, stdout, port)
|
|
117
89
|
if (local && @docker != :always) || @docker == :never
|
|
118
|
-
pid =
|
|
119
|
-
|
|
90
|
+
pid = localize(home, stdout, port)
|
|
91
|
+
"in process ##{pid}"
|
|
120
92
|
else
|
|
121
|
-
container =
|
|
122
|
-
|
|
93
|
+
container = dockerize(home, stdout, port)
|
|
94
|
+
"in container #{container}"
|
|
123
95
|
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def save(port)
|
|
124
99
|
File.write(
|
|
125
100
|
@yaml,
|
|
126
101
|
{
|
|
@@ -130,19 +105,22 @@ class Pgtk::PgsqlTask < Rake::TaskLib
|
|
|
130
105
|
'dbname' => @dbname,
|
|
131
106
|
'user' => @user,
|
|
132
107
|
'password' => @password,
|
|
133
|
-
'url' => [
|
|
134
|
-
"jdbc:postgresql://localhost:#{port}/",
|
|
135
|
-
"#{CGI.escape(@dbname)}?user=#{CGI.escape(@user)}"
|
|
136
|
-
].join
|
|
108
|
+
'url' => ["jdbc:postgresql://localhost:#{port}/", "#{CGI.escape(@dbname)}?user=#{CGI.escape(@user)}"].join
|
|
137
109
|
}
|
|
138
110
|
}.to_yaml
|
|
139
111
|
)
|
|
140
|
-
return if @quiet
|
|
141
|
-
puts "PostgreSQL has been started #{place}, port #{port}"
|
|
142
|
-
puts "YAML config saved to #{@yaml}"
|
|
143
112
|
end
|
|
144
113
|
|
|
145
|
-
def
|
|
114
|
+
def detect(what)
|
|
115
|
+
case what
|
|
116
|
+
when :local
|
|
117
|
+
qbash('postgres -V; initdb -V', accept: nil, both: true)[1].zero?
|
|
118
|
+
when :docker
|
|
119
|
+
qbash('docker -v', accept: nil, both: true)[1].zero?
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def dockerize(home, stdout, port)
|
|
146
124
|
FileUtils.mkdir_p(home)
|
|
147
125
|
out =
|
|
148
126
|
qbash(
|
|
@@ -166,18 +144,18 @@ class Pgtk::PgsqlTask < Rake::TaskLib
|
|
|
166
144
|
both: true, accept: nil
|
|
167
145
|
)[1].zero?
|
|
168
146
|
qbash("docker stop #{Shellwords.escape(container)}")
|
|
169
|
-
puts
|
|
147
|
+
puts("PostgreSQL docker container #{container.inspect} was stopped") unless @quiet
|
|
170
148
|
end
|
|
171
149
|
end
|
|
172
150
|
begin
|
|
173
151
|
WaitUtil.wait_for_service('PG in Docker', 'localhost', port, timeout_sec: 10, delay_sec: 0.1)
|
|
174
152
|
rescue WaitUtil::TimeoutError => e
|
|
175
|
-
raise "Failed to start PostgreSQL Docker container #{container.inspect}: #{e.message}"
|
|
153
|
+
raise(IOError, "Failed to start PostgreSQL Docker container #{container.inspect}: #{e.message}")
|
|
176
154
|
end
|
|
177
155
|
container
|
|
178
156
|
end
|
|
179
157
|
|
|
180
|
-
def
|
|
158
|
+
def localize(home, stdout, port)
|
|
181
159
|
Tempfile.open do |pwfile|
|
|
182
160
|
File.write(pwfile.path, @password)
|
|
183
161
|
qbash(
|
|
@@ -200,23 +178,19 @@ class Pgtk::PgsqlTask < Rake::TaskLib
|
|
|
200
178
|
@config.map { |k, v| "-c #{Shellwords.escape("#{k}=#{v}")}" },
|
|
201
179
|
"--port=#{port}"
|
|
202
180
|
].join(' ')
|
|
203
|
-
pid = Process.spawn(
|
|
204
|
-
cmd,
|
|
205
|
-
$stdout => File.join(home, 'stdout.txt'),
|
|
206
|
-
$stderr => File.join(home, 'stderr.txt')
|
|
207
|
-
)
|
|
181
|
+
pid = Process.spawn(cmd, $stdout => File.join(home, 'stdout.txt'), $stderr => File.join(home, 'stderr.txt'))
|
|
208
182
|
File.write(File.join(@dir, 'pid'), pid)
|
|
209
183
|
at_exit do
|
|
210
184
|
qbash("kill -TERM #{Shellwords.escape(pid)}", stdout:)
|
|
211
|
-
puts
|
|
185
|
+
puts("PostgreSQL killed in PID #{pid}") unless @quiet
|
|
212
186
|
end
|
|
213
187
|
begin
|
|
214
188
|
WaitUtil.wait_for_service('PG in local', 'localhost', port, timeout_sec: 10, delay_sec: 0.1)
|
|
215
189
|
rescue WaitUtil::TimeoutError => e
|
|
216
|
-
puts
|
|
217
|
-
puts
|
|
218
|
-
puts
|
|
219
|
-
raise "Failed to start PostgreSQL database server on port #{port}: #{e.message}"
|
|
190
|
+
puts("+ #{cmd}")
|
|
191
|
+
puts("stdout:\n#{File.read(File.join(home, 'stdout.txt'))}")
|
|
192
|
+
puts("stderr:\n#{File.read(File.join(home, 'stderr.txt'))}")
|
|
193
|
+
raise(IOError, "Failed to start PostgreSQL database server on port #{port}: #{e.message}")
|
|
220
194
|
end
|
|
221
195
|
qbash(
|
|
222
196
|
'createdb',
|
data/lib/pgtk/pool.rb
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'loog'
|
|
3
4
|
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
5
|
# SPDX-License-Identifier: MIT
|
|
5
6
|
|
|
6
7
|
require 'pg'
|
|
7
|
-
require 'loog'
|
|
8
8
|
require 'tago'
|
|
9
9
|
require_relative '../pgtk'
|
|
10
10
|
require_relative 'version'
|
|
@@ -130,7 +130,7 @@ class Pgtk::Pool
|
|
|
130
130
|
def start!
|
|
131
131
|
return if @started
|
|
132
132
|
@max.times do
|
|
133
|
-
@pool
|
|
133
|
+
@pool.push(@wire.connection)
|
|
134
134
|
end
|
|
135
135
|
@started = true
|
|
136
136
|
@log.debug("PostgreSQL pool started with #{@max} connections")
|
|
@@ -210,12 +210,12 @@ class Pgtk::Pool
|
|
|
210
210
|
t = Txn.new(c, @log)
|
|
211
211
|
t.exec('START TRANSACTION')
|
|
212
212
|
begin
|
|
213
|
-
r = yield
|
|
213
|
+
r = yield(t)
|
|
214
214
|
t.exec('COMMIT')
|
|
215
215
|
r
|
|
216
216
|
rescue StandardError => e
|
|
217
217
|
t.exec('ROLLBACK')
|
|
218
|
-
raise
|
|
218
|
+
raise(e)
|
|
219
219
|
end
|
|
220
220
|
end
|
|
221
221
|
end
|
|
@@ -239,7 +239,7 @@ class Pgtk::Pool
|
|
|
239
239
|
@condition = ConditionVariable.new
|
|
240
240
|
end
|
|
241
241
|
|
|
242
|
-
def
|
|
242
|
+
def push(item)
|
|
243
243
|
@mutex.synchronize do
|
|
244
244
|
if @items.size < @size
|
|
245
245
|
@items << item
|
|
@@ -248,7 +248,7 @@ class Pgtk::Pool
|
|
|
248
248
|
index = @items.index(item)
|
|
249
249
|
if index.nil?
|
|
250
250
|
index = @taken.index(true)
|
|
251
|
-
raise 'No taken slot found' if index.nil?
|
|
251
|
+
raise(StandardError, 'No taken slot found') if index.nil?
|
|
252
252
|
@items[index] = item
|
|
253
253
|
end
|
|
254
254
|
@taken[index] = false
|
|
@@ -299,7 +299,7 @@ class Pgtk::Pool
|
|
|
299
299
|
if args.empty?
|
|
300
300
|
@conn.exec(sql) do |res|
|
|
301
301
|
if block_given?
|
|
302
|
-
yield
|
|
302
|
+
yield(res)
|
|
303
303
|
else
|
|
304
304
|
res.each.to_a
|
|
305
305
|
end
|
|
@@ -307,7 +307,7 @@ class Pgtk::Pool
|
|
|
307
307
|
else
|
|
308
308
|
@conn.exec_params(sql, args, result) do |res|
|
|
309
309
|
if block_given?
|
|
310
|
-
yield
|
|
310
|
+
yield(res)
|
|
311
311
|
else
|
|
312
312
|
res.each.to_a
|
|
313
313
|
end
|
|
@@ -315,7 +315,7 @@ class Pgtk::Pool
|
|
|
315
315
|
end
|
|
316
316
|
rescue StandardError => e
|
|
317
317
|
@log.error("#{sql} -> #{e.message}")
|
|
318
|
-
raise
|
|
318
|
+
raise(e)
|
|
319
319
|
end
|
|
320
320
|
lag = Time.now - start
|
|
321
321
|
if lag < 1
|
|
@@ -332,12 +332,12 @@ class Pgtk::Pool
|
|
|
332
332
|
def connect
|
|
333
333
|
conn = @pool.pop
|
|
334
334
|
begin
|
|
335
|
-
yield
|
|
335
|
+
yield(conn)
|
|
336
336
|
rescue StandardError => e
|
|
337
337
|
conn = renew(conn)
|
|
338
|
-
raise
|
|
338
|
+
raise(e)
|
|
339
339
|
ensure
|
|
340
|
-
@pool
|
|
340
|
+
@pool.push(conn)
|
|
341
341
|
end
|
|
342
342
|
end
|
|
343
343
|
|
data/lib/pgtk/retry.rb
CHANGED
|
@@ -91,12 +91,12 @@ class Pgtk::Retry
|
|
|
91
91
|
@pool.exec(sql, *)
|
|
92
92
|
rescue PG::ConnectionBad => e
|
|
93
93
|
attempt += 1
|
|
94
|
-
raise
|
|
94
|
+
raise(e) if attempt >= @attempts
|
|
95
95
|
retry
|
|
96
96
|
rescue StandardError => e
|
|
97
|
-
raise
|
|
97
|
+
raise(e) unless query.strip.upcase.start_with?('SELECT')
|
|
98
98
|
attempt += 1
|
|
99
|
-
raise
|
|
99
|
+
raise(e) if attempt >= @attempts
|
|
100
100
|
retry
|
|
101
101
|
end
|
|
102
102
|
end
|
data/lib/pgtk/spy.rb
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'loog'
|
|
3
4
|
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
5
|
# SPDX-License-Identifier: MIT
|
|
5
6
|
|
|
6
7
|
require 'pg'
|
|
7
|
-
require 'loog'
|
|
8
8
|
require_relative '../pgtk'
|
|
9
9
|
require_relative 'wire'
|
|
10
10
|
|
|
@@ -94,7 +94,7 @@ class Pgtk::Spy
|
|
|
94
94
|
# @return [Object] Result of the block
|
|
95
95
|
def transaction
|
|
96
96
|
@pool.transaction do |t|
|
|
97
|
-
yield
|
|
97
|
+
yield(Pgtk::Spy.new(t, &@block))
|
|
98
98
|
end
|
|
99
99
|
end
|
|
100
100
|
end
|