pgtk 0.30.5 → 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 +13 -10
- 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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: af5dbb83c87c7ade9b9ec80e1c5d63d6c079e3e6d75963c9b20ba3306d151c2d
|
|
4
|
+
data.tar.gz: 9dfb51f191636813b50a067b484b44d1bbcfd28a810e437d6ac4e3539d2b208f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a47bb2c8234ff77dfc0dead336bc0010be2bc2e356fd10df8b37e103b9fa72484558ebe4db1e2e389c9186608bd6362690eb5e78ff354df2fe154a778318052c
|
|
7
|
+
data.tar.gz: '08edebfc99f2225668a613ccec7f79773e1969edd73d69cf7b22ef47838c157164380488f73b90274f86834069b7f3da2fb6abf9ac3dd4e5d50a0bc0786ad1b9'
|
data/Gemfile
CHANGED
|
@@ -15,6 +15,7 @@ gem 'qbash', '~>0.0', require: false
|
|
|
15
15
|
gem 'rack', '~>3.2', require: false
|
|
16
16
|
gem 'rake', '~>13.2', require: false
|
|
17
17
|
gem 'rubocop', '~>1.73', require: false
|
|
18
|
+
gem 'rubocop-elegant', '~>0.0', require: false
|
|
18
19
|
gem 'rubocop-minitest', '~>0.38', require: false
|
|
19
20
|
gem 'rubocop-performance', '~>1.25', require: false
|
|
20
21
|
gem 'rubocop-rake', '~>0.7', require: false
|
data/Gemfile.lock
CHANGED
|
@@ -35,7 +35,7 @@ GEM
|
|
|
35
35
|
tago (~> 0.1)
|
|
36
36
|
ellipsized (0.3.0)
|
|
37
37
|
joined (0.4.0)
|
|
38
|
-
json (2.19.
|
|
38
|
+
json (2.19.3)
|
|
39
39
|
language_server-protocol (3.17.0.5)
|
|
40
40
|
lint_roller (1.1.0)
|
|
41
41
|
logger (1.7.0)
|
|
@@ -52,15 +52,15 @@ GEM
|
|
|
52
52
|
minitest (>= 5.0, < 7)
|
|
53
53
|
ruby-progressbar
|
|
54
54
|
minitest-stub-const (0.6)
|
|
55
|
-
nokogiri (1.19.
|
|
55
|
+
nokogiri (1.19.2-arm64-darwin)
|
|
56
56
|
racc (~> 1.4)
|
|
57
|
-
nokogiri (1.19.
|
|
57
|
+
nokogiri (1.19.2-x64-mingw-ucrt)
|
|
58
58
|
racc (~> 1.4)
|
|
59
|
-
nokogiri (1.19.
|
|
59
|
+
nokogiri (1.19.2-x86_64-linux-gnu)
|
|
60
60
|
racc (~> 1.4)
|
|
61
61
|
os (1.1.4)
|
|
62
|
-
parallel (
|
|
63
|
-
parser (3.3.
|
|
62
|
+
parallel (2.0.1)
|
|
63
|
+
parser (3.3.11.1)
|
|
64
64
|
ast (~> 2.4.1)
|
|
65
65
|
racc
|
|
66
66
|
pg (1.6.3-arm64-darwin)
|
|
@@ -76,25 +76,28 @@ GEM
|
|
|
76
76
|
rack (3.2.6)
|
|
77
77
|
rainbow (3.1.1)
|
|
78
78
|
rake (13.3.1)
|
|
79
|
-
random-port (0.
|
|
79
|
+
random-port (0.8.2)
|
|
80
80
|
tago (~> 0.0)
|
|
81
|
-
regexp_parser (2.
|
|
81
|
+
regexp_parser (2.12.0)
|
|
82
82
|
rexml (3.4.4)
|
|
83
|
-
rubocop (1.
|
|
83
|
+
rubocop (1.86.1)
|
|
84
84
|
json (~> 2.3)
|
|
85
85
|
language_server-protocol (~> 3.17.0.2)
|
|
86
86
|
lint_roller (~> 1.1.0)
|
|
87
|
-
parallel (
|
|
87
|
+
parallel (>= 1.10)
|
|
88
88
|
parser (>= 3.3.0.2)
|
|
89
89
|
rainbow (>= 2.2.2, < 4.0)
|
|
90
90
|
regexp_parser (>= 2.9.3, < 3.0)
|
|
91
91
|
rubocop-ast (>= 1.49.0, < 2.0)
|
|
92
92
|
ruby-progressbar (~> 1.7)
|
|
93
93
|
unicode-display_width (>= 2.4.0, < 4.0)
|
|
94
|
-
rubocop-ast (1.49.
|
|
94
|
+
rubocop-ast (1.49.1)
|
|
95
95
|
parser (>= 3.3.7.2)
|
|
96
96
|
prism (~> 1.7)
|
|
97
|
-
rubocop-
|
|
97
|
+
rubocop-elegant (0.0.14)
|
|
98
|
+
lint_roller (~> 1.1)
|
|
99
|
+
rubocop (~> 1.75)
|
|
100
|
+
rubocop-minitest (0.39.1)
|
|
98
101
|
lint_roller (~> 1.1)
|
|
99
102
|
rubocop (>= 1.75.0, < 2.0)
|
|
100
103
|
rubocop-ast (>= 1.38.0, < 2.0)
|
|
@@ -130,7 +133,7 @@ GEM
|
|
|
130
133
|
nokogiri (~> 1.10)
|
|
131
134
|
rainbow (~> 3.0)
|
|
132
135
|
slop (~> 4.4)
|
|
133
|
-
yard (0.9.
|
|
136
|
+
yard (0.9.39)
|
|
134
137
|
|
|
135
138
|
PLATFORMS
|
|
136
139
|
arm64-darwin-22
|
|
@@ -150,6 +153,7 @@ DEPENDENCIES
|
|
|
150
153
|
rack (~> 3.2)
|
|
151
154
|
rake (~> 13.2)
|
|
152
155
|
rubocop (~> 1.73)
|
|
156
|
+
rubocop-elegant (~> 0.0)
|
|
153
157
|
rubocop-minitest (~> 0.38)
|
|
154
158
|
rubocop-performance (~> 1.25)
|
|
155
159
|
rubocop-rake (~> 0.7)
|
data/Rakefile
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
|
|
6
6
|
require 'os'
|
|
7
7
|
require 'qbash'
|
|
8
|
-
require 'rubygems'
|
|
9
8
|
require 'rake'
|
|
10
9
|
require 'rake/clean'
|
|
10
|
+
require 'rubygems'
|
|
11
11
|
|
|
12
12
|
def name
|
|
13
13
|
@name ||= File.basename(Dir['*.gemspec'].first, '.*')
|
|
@@ -53,7 +53,7 @@ end
|
|
|
53
53
|
|
|
54
54
|
require 'xcop/rake_task'
|
|
55
55
|
desc 'Validate all XML/XSL/XSD/HTML files for formatting'
|
|
56
|
-
Xcop::RakeTask.new
|
|
56
|
+
Xcop::RakeTask.new(:xcop) do |task|
|
|
57
57
|
task.includes = ['**/*.xml', '**/*.xsl', '**/*.xsd', '**/*.html']
|
|
58
58
|
task.excludes = ['target/**/*', 'coverage/**/*', 'vendor/**/*', 'doc/**/*']
|
|
59
59
|
end
|
data/lib/pgtk/impatient.rb
CHANGED
|
@@ -107,15 +107,15 @@ class Pgtk::Impatient
|
|
|
107
107
|
@pool.exec(sql, *args)
|
|
108
108
|
end
|
|
109
109
|
rescue Timeout::Error => e
|
|
110
|
-
raise
|
|
111
|
-
raise
|
|
110
|
+
raise(e) unless e.message == token
|
|
111
|
+
raise(TooSlow, [
|
|
112
112
|
'SQL query',
|
|
113
113
|
("with #{args.count} argument#{'s' if args.count > 1}" unless args.empty?),
|
|
114
114
|
'was terminated after',
|
|
115
115
|
start.ago,
|
|
116
116
|
'of waiting:',
|
|
117
117
|
sql.ellipsized(50).inspect
|
|
118
|
-
].compact.join(' ')
|
|
118
|
+
].compact.join(' '))
|
|
119
119
|
end
|
|
120
120
|
end
|
|
121
121
|
|
|
@@ -125,8 +125,8 @@ class Pgtk::Impatient
|
|
|
125
125
|
# @return [Object] Result of the block
|
|
126
126
|
def transaction
|
|
127
127
|
@pool.transaction do |t|
|
|
128
|
-
t.exec("SET LOCAL statement_timeout = #{(@timeout * 1000).
|
|
129
|
-
yield
|
|
128
|
+
t.exec("SET LOCAL statement_timeout = #{Integer((@timeout * 1000).to_s, 10)}")
|
|
129
|
+
yield(Pgtk::Impatient.new(t, @timeout))
|
|
130
130
|
end
|
|
131
131
|
end
|
|
132
132
|
end
|
data/lib/pgtk/liquibase_task.rb
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
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 'donce'
|
|
7
|
-
require 'English'
|
|
8
8
|
require 'loog'
|
|
9
9
|
require 'os'
|
|
10
10
|
require 'qbash'
|
|
@@ -19,41 +19,7 @@ require_relative '../pgtk'
|
|
|
19
19
|
# Copyright:: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
20
20
|
# License:: MIT
|
|
21
21
|
class Pgtk::LiquibaseTask < Rake::TaskLib
|
|
22
|
-
|
|
23
|
-
# @return [Symbol]
|
|
24
|
-
attr_accessor :name
|
|
25
|
-
|
|
26
|
-
# Path to Liquibase master XML file
|
|
27
|
-
# @return [String]
|
|
28
|
-
attr_accessor :master
|
|
29
|
-
|
|
30
|
-
# Path to YAML file with PostgreSQL connection details
|
|
31
|
-
# @return [String, Array<String>]
|
|
32
|
-
attr_accessor :yaml
|
|
33
|
-
|
|
34
|
-
# Path to PG entire SQL schema file, which will be dumped and overwritten after all migrations
|
|
35
|
-
# @return [String]
|
|
36
|
-
attr_accessor :schema
|
|
37
|
-
|
|
38
|
-
# Whether to suppress output
|
|
39
|
-
# @return [Boolean]
|
|
40
|
-
attr_accessor :quiet
|
|
41
|
-
|
|
42
|
-
# Liquibase version to use
|
|
43
|
-
# @return [String]
|
|
44
|
-
attr_accessor :liquibase_version
|
|
45
|
-
|
|
46
|
-
# PostgreSQL JDBC driver version to use
|
|
47
|
-
# @return [String]
|
|
48
|
-
attr_accessor :postgresql_version
|
|
49
|
-
|
|
50
|
-
# Liquibase contexts to apply
|
|
51
|
-
# @return [String]
|
|
52
|
-
attr_accessor :contexts
|
|
53
|
-
|
|
54
|
-
# Use docker (set to either :never, :always, or :maybe)
|
|
55
|
-
# @return [Symbol]
|
|
56
|
-
attr_accessor :docker
|
|
22
|
+
attr_accessor :name, :master, :yaml, :schema, :quiet, :liquibase, :postgresql, :contexts, :docker
|
|
57
23
|
|
|
58
24
|
# Initialize a new Liquibase task.
|
|
59
25
|
#
|
|
@@ -65,11 +31,11 @@ class Pgtk::LiquibaseTask < Rake::TaskLib
|
|
|
65
31
|
@name = args.shift || :liquibase
|
|
66
32
|
@quiet = false
|
|
67
33
|
@contexts = ''
|
|
68
|
-
@
|
|
69
|
-
@
|
|
70
|
-
desc
|
|
34
|
+
@liquibase = '3.2.2'
|
|
35
|
+
@postgresql = '42.7.0'
|
|
36
|
+
desc('Deploy Liquibase changes to the running PostgreSQL server') unless ::Rake.application.last_description
|
|
71
37
|
task(name, *args) do |_, task_args|
|
|
72
|
-
RakeFileUtils.
|
|
38
|
+
RakeFileUtils.verbose(true) do
|
|
73
39
|
yield(*[self, task_args].slice(0, task_block.arity)) if block_given?
|
|
74
40
|
run
|
|
75
41
|
end
|
|
@@ -79,34 +45,48 @@ class Pgtk::LiquibaseTask < Rake::TaskLib
|
|
|
79
45
|
private
|
|
80
46
|
|
|
81
47
|
def run
|
|
82
|
-
raise "Option 'master' is mandatory" unless @master
|
|
83
|
-
raise "Option 'yaml' is mandatory" unless @yaml
|
|
84
|
-
yml =
|
|
85
|
-
unless yml.is_a?(Hash)
|
|
86
|
-
yml = YAML.load_file(
|
|
87
|
-
if @yaml.is_a?(Array)
|
|
88
|
-
@yaml.drop_while { |f| !File.exist?(f) }.first
|
|
89
|
-
else
|
|
90
|
-
@yaml
|
|
91
|
-
end
|
|
92
|
-
)
|
|
93
|
-
end
|
|
94
|
-
raise "YAML configuration is missing the 'pgsql' section" unless yml['pgsql']
|
|
48
|
+
raise(ArgumentError, "Option 'master' is mandatory") unless @master
|
|
49
|
+
raise(ArgumentError, "Option 'yaml' is mandatory") unless @yaml
|
|
50
|
+
yml = config
|
|
95
51
|
@master = File.expand_path(@master)
|
|
52
|
+
validate(yml)
|
|
53
|
+
migrate(yml)
|
|
54
|
+
dump(yml) if @schema
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def config
|
|
58
|
+
yml = @yaml
|
|
59
|
+
return yml if yml.is_a?(Hash)
|
|
60
|
+
YAML.load_file(
|
|
61
|
+
if @yaml.is_a?(Array)
|
|
62
|
+
@yaml.drop_while { |f| !File.exist?(f) }.first
|
|
63
|
+
else
|
|
64
|
+
@yaml
|
|
65
|
+
end
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def validate(yml)
|
|
70
|
+
raise(ArgumentError, "YAML configuration is missing the 'pgsql' section") unless yml['pgsql']
|
|
96
71
|
unless File.exist?(@master)
|
|
97
|
-
raise
|
|
72
|
+
raise(
|
|
73
|
+
ArgumentError,
|
|
98
74
|
"Liquibase master is absent at '#{@master}'. " \
|
|
99
75
|
'More about this file you can find in Liquibase documentation: ' \
|
|
100
76
|
'https://docs.liquibase.com/concepts/changelogs/xml-format.html'
|
|
77
|
+
)
|
|
101
78
|
end
|
|
79
|
+
raise(ArgumentError, "The 'url' is not set in the config (YAML)") if yml['pgsql']['url'].nil?
|
|
80
|
+
raise(ArgumentError, "The 'user' is not set in the config (YAML)") if yml['pgsql']['user'].nil?
|
|
81
|
+
raise(ArgumentError, "The 'password' is not set in the config (YAML)") if yml['pgsql']['password'].nil?
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def migrate(yml)
|
|
102
85
|
pom = File.expand_path(File.join(__dir__, '../../resources/pom.xml'))
|
|
103
|
-
old = @
|
|
86
|
+
old = @liquibase.match?(/^[1-3]\..+$/)
|
|
104
87
|
url = yml['pgsql']['url']
|
|
105
|
-
raise "The 'url' is not set in the config (YAML)" if url.nil?
|
|
106
88
|
username = yml['pgsql']['user']
|
|
107
|
-
raise "The 'user' is not set in the config (YAML)" if username.nil?
|
|
108
89
|
password = yml['pgsql']['password']
|
|
109
|
-
raise "The 'password' is not set in the config (YAML)" if password.nil?
|
|
110
90
|
Dir.chdir(File.dirname(@master)) do
|
|
111
91
|
qbash(
|
|
112
92
|
'mvn', 'verify',
|
|
@@ -117,9 +97,9 @@ class Pgtk::LiquibaseTask < Rake::TaskLib
|
|
|
117
97
|
'--file',
|
|
118
98
|
Shellwords.escape(pom),
|
|
119
99
|
'--define',
|
|
120
|
-
"liquibase.version=#{@
|
|
100
|
+
"liquibase.version=#{@liquibase}",
|
|
121
101
|
'--define',
|
|
122
|
-
"postgresql.version=#{@
|
|
102
|
+
"postgresql.version=#{@postgresql}",
|
|
123
103
|
'--define',
|
|
124
104
|
Shellwords.escape("liquibase.searchPath=#{File.dirname(@master)}"),
|
|
125
105
|
'--define',
|
|
@@ -136,49 +116,59 @@ class Pgtk::LiquibaseTask < Rake::TaskLib
|
|
|
136
116
|
stderr: Loog::REGULAR
|
|
137
117
|
)
|
|
138
118
|
end
|
|
139
|
-
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def dump(yml)
|
|
140
122
|
@schema = File.expand_path(@schema)
|
|
141
|
-
|
|
142
|
-
local = dump[1].zero?
|
|
123
|
+
local = qbash('pg_dump -V', accept: nil, both: true)[1].zero?
|
|
143
124
|
docker = qbash('docker -v', accept: nil, both: true)[1].zero?
|
|
144
|
-
raise 'Cannot generate schema, install either pg_dump or Docker' unless local || docker
|
|
145
|
-
raise 'You set docker to :always, but Docker is not installed' if @docker == :always && !docker
|
|
125
|
+
raise(IOError, 'Cannot generate schema, install either pg_dump or Docker') unless local || docker
|
|
126
|
+
raise(ArgumentError, 'You set docker to :always, but Docker is not installed') if @docker == :always && !docker
|
|
127
|
+
password = yml['pgsql']['password']
|
|
146
128
|
host = yml.dig('pgsql', 'host')
|
|
147
129
|
Dir.chdir(File.dirname(@schema)) do
|
|
148
130
|
out =
|
|
149
131
|
if (local && @docker != :always) || @docker == :never
|
|
150
|
-
|
|
151
|
-
'pg_dump',
|
|
152
|
-
'-h', Shellwords.escape(host),
|
|
153
|
-
'-p', Shellwords.escape(yml.dig('pgsql', 'port').to_s),
|
|
154
|
-
'-U', Shellwords.escape(yml.dig('pgsql', 'user')),
|
|
155
|
-
'-d', Shellwords.escape(yml.dig('pgsql', 'dbname')),
|
|
156
|
-
'-n', 'public',
|
|
157
|
-
'--schema-only',
|
|
158
|
-
env: { 'PGPASSWORD' => password },
|
|
159
|
-
stdout: @quiet ? Loog::NULL : Loog::REGULAR,
|
|
160
|
-
stderr: Loog::REGULAR
|
|
161
|
-
)
|
|
132
|
+
pgdump(yml, host, password)
|
|
162
133
|
else
|
|
163
134
|
host = donce_host if OS.mac? && ['localhost', '127.0.0.1'].include?(host)
|
|
164
|
-
|
|
165
|
-
image: 'postgres:18.1',
|
|
166
|
-
args: OS.mac? ? '' : '--network=host',
|
|
167
|
-
env: { 'PGPASSWORD' => password },
|
|
168
|
-
command: [
|
|
169
|
-
'pg_dump',
|
|
170
|
-
'-h', host,
|
|
171
|
-
'-p', yml.dig('pgsql', 'port').to_s,
|
|
172
|
-
'-U', yml.dig('pgsql', 'user'),
|
|
173
|
-
'-d', yml.dig('pgsql', 'dbname'),
|
|
174
|
-
'-n', 'public',
|
|
175
|
-
'--schema-only'
|
|
176
|
-
].shelljoin,
|
|
177
|
-
stdout: @quiet ? Loog::NULL : Loog::REGULAR,
|
|
178
|
-
stderr: Loog::REGULAR
|
|
179
|
-
)
|
|
135
|
+
dockerdump(yml, host, password)
|
|
180
136
|
end
|
|
181
137
|
File.write(@schema, out)
|
|
182
138
|
end
|
|
183
139
|
end
|
|
140
|
+
|
|
141
|
+
def pgdump(yml, host, password)
|
|
142
|
+
qbash(
|
|
143
|
+
'pg_dump',
|
|
144
|
+
'-h', Shellwords.escape(host),
|
|
145
|
+
'-p', Shellwords.escape(yml.dig('pgsql', 'port').to_s),
|
|
146
|
+
'-U', Shellwords.escape(yml.dig('pgsql', 'user')),
|
|
147
|
+
'-d', Shellwords.escape(yml.dig('pgsql', 'dbname')),
|
|
148
|
+
'-n', 'public',
|
|
149
|
+
'--schema-only',
|
|
150
|
+
env: { 'PGPASSWORD' => password },
|
|
151
|
+
stdout: @quiet ? Loog::NULL : Loog::REGULAR,
|
|
152
|
+
stderr: Loog::REGULAR
|
|
153
|
+
)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def dockerdump(yml, host, password)
|
|
157
|
+
donce(
|
|
158
|
+
image: 'postgres:18.1',
|
|
159
|
+
args: OS.mac? ? '' : '--network=host',
|
|
160
|
+
env: { 'PGPASSWORD' => password },
|
|
161
|
+
command: [
|
|
162
|
+
'pg_dump',
|
|
163
|
+
'-h', host,
|
|
164
|
+
'-p', yml.dig('pgsql', 'port').to_s,
|
|
165
|
+
'-U', yml.dig('pgsql', 'user'),
|
|
166
|
+
'-d', yml.dig('pgsql', 'dbname'),
|
|
167
|
+
'-n', 'public',
|
|
168
|
+
'--schema-only'
|
|
169
|
+
].shelljoin,
|
|
170
|
+
stdout: @quiet ? Loog::NULL : Loog::REGULAR,
|
|
171
|
+
stderr: Loog::REGULAR
|
|
172
|
+
)
|
|
173
|
+
end
|
|
184
174
|
end
|
data/lib/pgtk/liquicheck_task.rb
CHANGED
|
@@ -12,26 +12,16 @@ require_relative '../pgtk'
|
|
|
12
12
|
# Copyright:: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
13
13
|
# License:: MIT
|
|
14
14
|
class Pgtk::LiquicheckTask < Rake::TaskLib
|
|
15
|
-
|
|
16
|
-
# @return [Symbol]
|
|
17
|
-
attr_accessor :name
|
|
18
|
-
|
|
19
|
-
# Base directory where Liquibase XML files will be stored
|
|
20
|
-
# @return [String]
|
|
21
|
-
attr_accessor :dir
|
|
22
|
-
|
|
23
|
-
# Migration XML files pattern
|
|
24
|
-
# @return [String]
|
|
25
|
-
attr_accessor :pattern
|
|
15
|
+
attr_accessor :name, :dir, :pattern
|
|
26
16
|
|
|
27
17
|
def initialize(*args, &task_block)
|
|
28
18
|
super()
|
|
29
19
|
@name = args.shift || :liquicheck
|
|
30
20
|
@dir = 'liquibase'
|
|
31
21
|
@pattern = '*/*.xml'
|
|
32
|
-
desc
|
|
22
|
+
desc('Check the quality of Liquibase XML files') unless ::Rake.application.last_description
|
|
33
23
|
task(name, *args) do |_, task_args|
|
|
34
|
-
RakeFileUtils.
|
|
24
|
+
RakeFileUtils.verbose(true) do
|
|
35
25
|
yield(*[self, task_args].slice(0, task_block.arity)) if block_given?
|
|
36
26
|
run
|
|
37
27
|
end
|
|
@@ -41,54 +31,62 @@ class Pgtk::LiquicheckTask < Rake::TaskLib
|
|
|
41
31
|
private
|
|
42
32
|
|
|
43
33
|
def run
|
|
44
|
-
raise "Option 'dir' is mandatory" if !@dir || @dir.empty?
|
|
45
|
-
raise "Option 'pattern' is mandatory" if !@pattern || @pattern.empty?
|
|
34
|
+
raise(ArgumentError, "Option 'dir' is mandatory") if !@dir || @dir.empty?
|
|
35
|
+
raise(ArgumentError, "Option 'pattern' is mandatory") if !@pattern || @pattern.empty?
|
|
46
36
|
errors = {}
|
|
47
37
|
Dir[File.join(File.expand_path(File.join(Dir.pwd, @dir)), @pattern)].each do |file|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
on(errors, file) do
|
|
68
|
-
must_have(author, 'author is empty')
|
|
69
|
-
must_match(
|
|
70
|
-
author,
|
|
71
|
-
/\A[-_ A-Za-z0-9]+\z/,
|
|
72
|
-
"author #{author.inspect} has illegal symbols"
|
|
73
|
-
)
|
|
74
|
-
end
|
|
75
|
-
on(errors, file) do
|
|
76
|
-
must_have(id, 'ID is empty')
|
|
77
|
-
must_have(path, 'logicalFilePath is empty')
|
|
78
|
-
must_match(
|
|
79
|
-
path.gsub(/[-_.a-z]/, ''),
|
|
80
|
-
/\A#{id.gsub(/[-a-z]/, '')}\z/,
|
|
81
|
-
"ID #{id.inspect} is not the beginning of a logicalFilePath #{path.inspect}"
|
|
82
|
-
)
|
|
83
|
-
end
|
|
84
|
-
end
|
|
38
|
+
inspect(errors, file)
|
|
39
|
+
end
|
|
40
|
+
report(errors)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def inspect(errors, file)
|
|
44
|
+
doc = Nokogiri::XML(File.open(file))
|
|
45
|
+
doc.remove_namespaces!
|
|
46
|
+
path = doc.at_xpath('databaseChangeLog')&.attr('logicalFilePath')&.to_s
|
|
47
|
+
on(errors, file) do
|
|
48
|
+
demand(path, 'logicalFilePath is empty')
|
|
49
|
+
equate(
|
|
50
|
+
path,
|
|
51
|
+
File.basename(file),
|
|
52
|
+
"logicalFilePath #{path.inspect} does not equal the xml file name #{File.basename(file).inspect}"
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
doc.xpath('databaseChangeLog/changeSet').each do |node|
|
|
56
|
+
verify(errors, file, node, path)
|
|
85
57
|
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def verify(errors, file, node, path)
|
|
61
|
+
id = node.attr('id')&.to_s
|
|
62
|
+
author = node.attr('author')&.to_s
|
|
63
|
+
context = node.attr('context')&.to_s
|
|
64
|
+
on(errors, file) do
|
|
65
|
+
demand(id, 'ID is empty')
|
|
66
|
+
confirm(id, /[-a-z]+/, "ID #{id.inspect} has not suffix in #{context} context") if context
|
|
67
|
+
end
|
|
68
|
+
on(errors, file) do
|
|
69
|
+
demand(author, 'author is empty')
|
|
70
|
+
confirm(author, /\A[-_ A-Za-z0-9]+\z/, "author #{author.inspect} has illegal symbols")
|
|
71
|
+
end
|
|
72
|
+
on(errors, file) do
|
|
73
|
+
demand(id, 'ID is empty')
|
|
74
|
+
demand(path, 'logicalFilePath is empty')
|
|
75
|
+
confirm(
|
|
76
|
+
path.gsub(/[-_.a-z]/, ''),
|
|
77
|
+
/\A#{id.gsub(/[-a-z]/, '')}\z/,
|
|
78
|
+
"ID #{id.inspect} is not the beginning of a logicalFilePath #{path.inspect}"
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def report(errors)
|
|
86
84
|
return if errors.empty?
|
|
87
|
-
puts
|
|
85
|
+
puts('There are such errors in the Liquibase XML files.')
|
|
88
86
|
errors.each do |f, e|
|
|
89
|
-
puts
|
|
87
|
+
puts("In file '#{f}':")
|
|
90
88
|
e.uniq.each do |msg|
|
|
91
|
-
puts
|
|
89
|
+
puts(" * #{msg}")
|
|
92
90
|
end
|
|
93
91
|
puts
|
|
94
92
|
end
|
|
@@ -101,20 +99,19 @@ class Pgtk::LiquicheckTask < Rake::TaskLib
|
|
|
101
99
|
(errors[file] ||= []) << e.message
|
|
102
100
|
end
|
|
103
101
|
|
|
104
|
-
def
|
|
105
|
-
(
|
|
102
|
+
def demand(prop, msg)
|
|
103
|
+
raise(MustError, msg) if prop.nil? || prop.empty?
|
|
106
104
|
end
|
|
107
105
|
|
|
108
|
-
def
|
|
109
|
-
(
|
|
106
|
+
def equate(lprop, rprop, msg)
|
|
107
|
+
raise(MustError, msg) if lprop != rprop
|
|
110
108
|
end
|
|
111
109
|
|
|
112
|
-
def
|
|
113
|
-
(
|
|
110
|
+
def confirm(prop, regex, msg)
|
|
111
|
+
raise(MustError, msg) unless prop.match?(regex)
|
|
114
112
|
end
|
|
115
113
|
|
|
116
114
|
class MustError < StandardError
|
|
117
|
-
# empty by design
|
|
118
115
|
end
|
|
119
116
|
private_constant :MustError
|
|
120
117
|
end
|