pg_objects 1.4.7 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.evilution.yml +74 -0
- data/.github/workflows/ci.yml +1 -1
- data/.gitignore +3 -0
- data/.rubocop.yml +13 -2
- data/CHANGELOG.md +78 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +96 -78
- data/README.md +174 -3
- data/Rakefile +2 -0
- data/bin/benchmark +42 -7
- data/bin/console +1 -0
- data/lib/generators/pg_objects/install/install_generator.rb +2 -0
- data/lib/pg_objects/config.rb +53 -2
- data/lib/pg_objects/db_object.rb +6 -1
- data/lib/pg_objects/db_object_factory.rb +11 -3
- data/lib/pg_objects/logger.rb +34 -3
- data/lib/pg_objects/manager.rb +66 -15
- data/lib/pg_objects/parsed_object/aggregate.rb +9 -1
- data/lib/pg_objects/parsed_object/base.rb +38 -0
- data/lib/pg_objects/parsed_object/base_type.rb +17 -0
- data/lib/pg_objects/parsed_object/conversion.rb +9 -1
- data/lib/pg_objects/parsed_object/domain.rb +16 -0
- data/lib/pg_objects/parsed_object/enum_type.rb +16 -0
- data/lib/pg_objects/parsed_object/event_trigger.rb +3 -1
- data/lib/pg_objects/parsed_object/extension.rb +10 -0
- data/lib/pg_objects/parsed_object/function.rb +9 -1
- data/lib/pg_objects/parsed_object/index.rb +17 -0
- data/lib/pg_objects/parsed_object/materialized_view.rb +9 -1
- data/lib/pg_objects/parsed_object/operator.rb +9 -1
- data/lib/pg_objects/parsed_object/operator_class.rb +9 -1
- data/lib/pg_objects/parsed_object/policy.rb +10 -0
- data/lib/pg_objects/parsed_object/range_type.rb +16 -0
- data/lib/pg_objects/parsed_object/rule.rb +10 -0
- data/lib/pg_objects/parsed_object/sequence.rb +16 -0
- data/lib/pg_objects/parsed_object/table.rb +9 -1
- data/lib/pg_objects/parsed_object/text_search_parser.rb +9 -1
- data/lib/pg_objects/parsed_object/text_search_template.rb +9 -1
- data/lib/pg_objects/parsed_object/trigger.rb +3 -1
- data/lib/pg_objects/parsed_object/type.rb +9 -1
- data/lib/pg_objects/parsed_object/view.rb +9 -1
- data/lib/pg_objects/parsed_object.rb +11 -0
- data/lib/pg_objects/parsed_object_factory.rb +52 -90
- data/lib/pg_objects/parser.rb +28 -12
- data/lib/pg_objects/railtie.rb +2 -0
- data/lib/pg_objects/version.rb +3 -1
- data/lib/pg_objects/yaml_configurable.rb +25 -7
- data/lib/pg_objects.rb +54 -4
- data/lib/tasks/pg_objects_tasks.rake +33 -8
- data/pg_objects.gemspec +5 -3
- metadata +17 -20
- data/.github/copilot-instructions.md +0 -131
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# A module that provides a method to load configuration settings from a YAML file.
|
|
2
4
|
module YamlConfigurable
|
|
3
5
|
# Loads configuration settings from a YAML file.
|
|
@@ -6,17 +8,33 @@ module YamlConfigurable
|
|
|
6
8
|
def load_from_yaml(file_path)
|
|
7
9
|
return unless File.exist?(file_path)
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
set_if_present(config, :silent, config_hash['silent'])
|
|
11
|
+
settings_from(YAML.load_file(file_path)).each do |key, value|
|
|
12
|
+
set_if_present(config, key, value)
|
|
13
|
+
end
|
|
14
|
+
rescue Psych::SyntaxError => e
|
|
15
|
+
warn "[pg_objects] Ignoring malformed YAML config #{file_path}: #{e.message}"
|
|
15
16
|
end
|
|
16
17
|
|
|
17
18
|
private
|
|
18
19
|
|
|
20
|
+
# Maps configuration keys to their values in the parsed YAML hash.
|
|
21
|
+
def settings_from(config_hash)
|
|
22
|
+
{
|
|
23
|
+
before_path: config_hash.dig('directories', 'before'),
|
|
24
|
+
after_path: config_hash.dig('directories', 'after'),
|
|
25
|
+
extensions: config_hash['extensions'],
|
|
26
|
+
silent: config_hash['silent'],
|
|
27
|
+
transactional: config_hash['transactional'],
|
|
28
|
+
auto_hook_migrations: config_hash['auto_hook_migrations']
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Applies the value unless it is nil or an empty string/array. Booleans
|
|
33
|
+
# (including +false+) have no +empty?+ and are always applied.
|
|
19
34
|
def set_if_present(config, key, value)
|
|
20
|
-
|
|
35
|
+
return if value.nil?
|
|
36
|
+
return if value.respond_to?(:empty?) && value.empty?
|
|
37
|
+
|
|
38
|
+
config.public_send("#{key}=", value)
|
|
21
39
|
end
|
|
22
40
|
end
|
data/lib/pg_objects.rb
CHANGED
|
@@ -1,10 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative 'pg_objects/version'
|
|
2
4
|
|
|
3
5
|
module PgObjects
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
# Raised when a dependency name resolves to more than one object. Carries
|
|
7
|
+
# the matching objects' file paths via +candidates+ and the file that
|
|
8
|
+
# declared the dependency via +referrer+; the message lists them all. Also
|
|
9
|
+
# accepts a bare name for compatibility.
|
|
10
|
+
class AmbiguousDependencyError < StandardError
|
|
11
|
+
attr_reader :candidates, :referrer
|
|
12
|
+
|
|
13
|
+
def initialize(dep_name = nil, candidates: [], referrer: nil)
|
|
14
|
+
@candidates = candidates
|
|
15
|
+
@referrer = referrer
|
|
16
|
+
message = build_message(dep_name)
|
|
17
|
+
message.empty? ? super() : super(message)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def build_message(dep_name)
|
|
23
|
+
message = dep_name.to_s
|
|
24
|
+
message += " (referenced by #{referrer})" if referrer
|
|
25
|
+
message += " matches: #{candidates.join(', ')}" unless candidates.empty?
|
|
26
|
+
message
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Raised when object dependencies form a cycle. Carries the resolution chain
|
|
31
|
+
# that closed the cycle (e.g. ["a", "b", "a"]) via +cycle_path+; the message
|
|
32
|
+
# renders it as "a -> b -> a". Also accepts a single name for compatibility.
|
|
33
|
+
class CyclicDependencyError < StandardError
|
|
34
|
+
attr_reader :cycle_path
|
|
35
|
+
|
|
36
|
+
def initialize(cycle_path = nil)
|
|
37
|
+
@cycle_path = Array(cycle_path)
|
|
38
|
+
@cycle_path.empty? ? super() : super(@cycle_path.join(' -> '))
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Raised when a declared dependency matches no loaded object. Carries the
|
|
43
|
+
# file that declared the dependency via +referrer+ and includes it in the
|
|
44
|
+
# message. Also accepts a bare name for compatibility.
|
|
45
|
+
class DependencyNotExistError < StandardError
|
|
46
|
+
attr_reader :referrer
|
|
47
|
+
|
|
48
|
+
def initialize(dep_name = nil, referrer: nil)
|
|
49
|
+
@referrer = referrer
|
|
50
|
+
message = dep_name.to_s
|
|
51
|
+
message += " (referenced by #{referrer})" unless referrer.to_s.empty?
|
|
52
|
+
message.empty? ? super() : super(message)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
7
56
|
class UnsupportedAdapterError < StandardError; end
|
|
57
|
+
class UnknownObjectTypeError < StandardError; end
|
|
58
|
+
class MalformedStatementError < StandardError; end
|
|
8
59
|
end
|
|
9
60
|
|
|
10
61
|
require 'pg_objects/railtie' if defined?(Rails)
|
|
@@ -12,7 +63,6 @@ require 'pg_objects/railtie' if defined?(Rails)
|
|
|
12
63
|
require 'dry-configurable'
|
|
13
64
|
require 'dry-container'
|
|
14
65
|
require 'dry-auto_inject'
|
|
15
|
-
require 'dry/monads'
|
|
16
66
|
require 'memery'
|
|
17
67
|
|
|
18
68
|
require 'pg_objects/container'
|
|
@@ -1,23 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Resolves the connection to run against. Set PG_OBJECTS_CONNECTION_CLASS to
|
|
4
|
+
# the name of an Active Record class (e.g. "AnimalsRecord") to target that
|
|
5
|
+
# class's database in a multi-DB setup; unset, the global connection is used.
|
|
6
|
+
pg_objects_connection = lambda do
|
|
7
|
+
class_name = ENV.fetch('PG_OBJECTS_CONNECTION_CLASS', nil)
|
|
8
|
+
next nil if class_name.nil? || class_name.strip.empty?
|
|
9
|
+
|
|
10
|
+
begin
|
|
11
|
+
Object.const_get(class_name).connection
|
|
12
|
+
rescue NameError
|
|
13
|
+
raise ArgumentError, "PG_OBJECTS_CONNECTION_CLASS is set to unknown class #{class_name.inspect}"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
1
17
|
namespace :db do
|
|
2
18
|
namespace :create_objects do
|
|
3
19
|
desc 'Create all the database objects from "before" folder'
|
|
4
20
|
task before: :environment do
|
|
5
|
-
PgObjects::Manager.new.load_files(:before).create_objects
|
|
21
|
+
PgObjects::Manager.new(connection: pg_objects_connection.call).load_files(:before).create_objects
|
|
6
22
|
end
|
|
7
23
|
|
|
8
24
|
desc 'Create all the database objects from "after" folder'
|
|
9
25
|
task after: :environment do
|
|
10
|
-
PgObjects::Manager.new.load_files(:after).create_objects
|
|
26
|
+
PgObjects::Manager.new(connection: pg_objects_connection.call).load_files(:after).create_objects
|
|
11
27
|
end
|
|
12
28
|
end
|
|
13
29
|
end
|
|
14
30
|
|
|
15
31
|
require 'rake/hooks'
|
|
16
32
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
33
|
+
# Attach object-creation hooks to the tasks configured in
|
|
34
|
+
# PgObjects::Config.config.hook_tasks (override in an initializer to opt out).
|
|
35
|
+
# Each stage (:before/:after) maps to both the rake-hooks DSL method and the
|
|
36
|
+
# matching db:create_objects:<stage> task. Set config.auto_hook_migrations to
|
|
37
|
+
# false to install no hooks at all.
|
|
38
|
+
if PgObjects::Config.config.auto_hook_migrations
|
|
39
|
+
PgObjects::Config.config.hook_tasks.each do |task_name, stages|
|
|
40
|
+
stages.each do |stage|
|
|
41
|
+
send(stage, task_name) do
|
|
42
|
+
task = Rake::Task["db:create_objects:#{stage}"]
|
|
43
|
+
task.reenable # allow the hook to run again within composed/repeated task runs
|
|
44
|
+
task.invoke
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
23
48
|
end
|
data/pg_objects.gemspec
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
lib = File.expand_path('lib', __dir__)
|
|
2
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
3
5
|
|
|
@@ -12,11 +14,12 @@ Gem::Specification.new do |spec|
|
|
|
12
14
|
spec.summary = %q(Simple manager for PostgreSQL objects like triggers and functions)
|
|
13
15
|
spec.homepage = 'https://github.com/marinazzio/pg_objects'
|
|
14
16
|
|
|
15
|
-
spec.required_ruby_version = '>= 3.
|
|
17
|
+
spec.required_ruby_version = '>= 3.3.0'
|
|
16
18
|
|
|
17
19
|
spec.metadata = {
|
|
18
20
|
'allowed_push_host' => 'https://rubygems.org',
|
|
19
21
|
'bug_tracker_uri' => 'https://github.com/marinazzio/pg_objects/issues',
|
|
22
|
+
'changelog_uri' => 'https://github.com/marinazzio/pg_objects/blob/master/CHANGELOG.md',
|
|
20
23
|
'documentation_uri' => 'https://github.com/marinazzio/pg_objects/blob/master/README.md',
|
|
21
24
|
'homepage_uri' => 'https://github.com/marinazzio/pg_objects',
|
|
22
25
|
'rubygems_mfa_required' => 'true',
|
|
@@ -46,8 +49,7 @@ Gem::Specification.new do |spec|
|
|
|
46
49
|
spec.add_dependency 'activerecord', '>= 6.1.7.0', '< 9'
|
|
47
50
|
spec.add_dependency 'dry-auto_inject', '~> 1'
|
|
48
51
|
spec.add_dependency 'dry-configurable', '~> 1'
|
|
49
|
-
spec.add_dependency 'dry-container', '0.11
|
|
50
|
-
spec.add_dependency 'dry-monads', '~> 1.6'
|
|
52
|
+
spec.add_dependency 'dry-container', '~> 0.11'
|
|
51
53
|
spec.add_dependency 'memery', '>= 1.5', '< 1.9'
|
|
52
54
|
spec.add_dependency 'pg_query', '>= 5', '< 7'
|
|
53
55
|
spec.add_dependency 'railties', '>= 4', '< 9'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pg_objects
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Denis Kiselyov
|
|
@@ -59,32 +59,18 @@ dependencies:
|
|
|
59
59
|
version: '1'
|
|
60
60
|
- !ruby/object:Gem::Dependency
|
|
61
61
|
name: dry-container
|
|
62
|
-
requirement: !ruby/object:Gem::Requirement
|
|
63
|
-
requirements:
|
|
64
|
-
- - '='
|
|
65
|
-
- !ruby/object:Gem::Version
|
|
66
|
-
version: 0.11.0
|
|
67
|
-
type: :runtime
|
|
68
|
-
prerelease: false
|
|
69
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
70
|
-
requirements:
|
|
71
|
-
- - '='
|
|
72
|
-
- !ruby/object:Gem::Version
|
|
73
|
-
version: 0.11.0
|
|
74
|
-
- !ruby/object:Gem::Dependency
|
|
75
|
-
name: dry-monads
|
|
76
62
|
requirement: !ruby/object:Gem::Requirement
|
|
77
63
|
requirements:
|
|
78
64
|
- - "~>"
|
|
79
65
|
- !ruby/object:Gem::Version
|
|
80
|
-
version: '
|
|
66
|
+
version: '0.11'
|
|
81
67
|
type: :runtime
|
|
82
68
|
prerelease: false
|
|
83
69
|
version_requirements: !ruby/object:Gem::Requirement
|
|
84
70
|
requirements:
|
|
85
71
|
- - "~>"
|
|
86
72
|
- !ruby/object:Gem::Version
|
|
87
|
-
version: '
|
|
73
|
+
version: '0.11'
|
|
88
74
|
- !ruby/object:Gem::Dependency
|
|
89
75
|
name: memery
|
|
90
76
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -165,7 +151,7 @@ executables: []
|
|
|
165
151
|
extensions: []
|
|
166
152
|
extra_rdoc_files: []
|
|
167
153
|
files:
|
|
168
|
-
- ".
|
|
154
|
+
- ".evilution.yml"
|
|
169
155
|
- ".github/dependabot.yml"
|
|
170
156
|
- ".github/workflows/bundle_audit.yml"
|
|
171
157
|
- ".github/workflows/ci.yml"
|
|
@@ -174,6 +160,7 @@ files:
|
|
|
174
160
|
- ".rspec"
|
|
175
161
|
- ".rubocop.yml"
|
|
176
162
|
- ".rubocop_todo.yml"
|
|
163
|
+
- CHANGELOG.md
|
|
177
164
|
- Gemfile
|
|
178
165
|
- Gemfile.lock
|
|
179
166
|
- LICENSE
|
|
@@ -195,12 +182,21 @@ files:
|
|
|
195
182
|
- lib/pg_objects/parsed_object.rb
|
|
196
183
|
- lib/pg_objects/parsed_object/aggregate.rb
|
|
197
184
|
- lib/pg_objects/parsed_object/base.rb
|
|
185
|
+
- lib/pg_objects/parsed_object/base_type.rb
|
|
198
186
|
- lib/pg_objects/parsed_object/conversion.rb
|
|
187
|
+
- lib/pg_objects/parsed_object/domain.rb
|
|
188
|
+
- lib/pg_objects/parsed_object/enum_type.rb
|
|
199
189
|
- lib/pg_objects/parsed_object/event_trigger.rb
|
|
190
|
+
- lib/pg_objects/parsed_object/extension.rb
|
|
200
191
|
- lib/pg_objects/parsed_object/function.rb
|
|
192
|
+
- lib/pg_objects/parsed_object/index.rb
|
|
201
193
|
- lib/pg_objects/parsed_object/materialized_view.rb
|
|
202
194
|
- lib/pg_objects/parsed_object/operator.rb
|
|
203
195
|
- lib/pg_objects/parsed_object/operator_class.rb
|
|
196
|
+
- lib/pg_objects/parsed_object/policy.rb
|
|
197
|
+
- lib/pg_objects/parsed_object/range_type.rb
|
|
198
|
+
- lib/pg_objects/parsed_object/rule.rb
|
|
199
|
+
- lib/pg_objects/parsed_object/sequence.rb
|
|
204
200
|
- lib/pg_objects/parsed_object/table.rb
|
|
205
201
|
- lib/pg_objects/parsed_object/text_search_parser.rb
|
|
206
202
|
- lib/pg_objects/parsed_object/text_search_template.rb
|
|
@@ -220,6 +216,7 @@ licenses:
|
|
|
220
216
|
metadata:
|
|
221
217
|
allowed_push_host: https://rubygems.org
|
|
222
218
|
bug_tracker_uri: https://github.com/marinazzio/pg_objects/issues
|
|
219
|
+
changelog_uri: https://github.com/marinazzio/pg_objects/blob/master/CHANGELOG.md
|
|
223
220
|
documentation_uri: https://github.com/marinazzio/pg_objects/blob/master/README.md
|
|
224
221
|
homepage_uri: https://github.com/marinazzio/pg_objects
|
|
225
222
|
rubygems_mfa_required: 'true'
|
|
@@ -236,14 +233,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
236
233
|
requirements:
|
|
237
234
|
- - ">="
|
|
238
235
|
- !ruby/object:Gem::Version
|
|
239
|
-
version: 3.
|
|
236
|
+
version: 3.3.0
|
|
240
237
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
241
238
|
requirements:
|
|
242
239
|
- - ">="
|
|
243
240
|
- !ruby/object:Gem::Version
|
|
244
241
|
version: '0'
|
|
245
242
|
requirements: []
|
|
246
|
-
rubygems_version: 4.0.
|
|
243
|
+
rubygems_version: 4.0.16
|
|
247
244
|
specification_version: 4
|
|
248
245
|
summary: Simple manager for PostgreSQL objects like triggers and functions
|
|
249
246
|
test_files: []
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
# PG Objects
|
|
2
|
-
PG Objects is a Ruby gem for managing PostgreSQL database objects like triggers and functions. It provides a simple manager that handles dependencies between database objects and integrates with Rails applications.
|
|
3
|
-
|
|
4
|
-
Always reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.
|
|
5
|
-
|
|
6
|
-
## Working Effectively
|
|
7
|
-
- Bootstrap, build, and test the repository:
|
|
8
|
-
- `gem install --user-install bundler`
|
|
9
|
-
- `export PATH="$HOME/.local/share/gem/ruby/3.2.0/bin:$PATH"`
|
|
10
|
-
- `bundle config set --local path 'vendor/bundle'`
|
|
11
|
-
- `bundle install` -- takes 45-60 seconds. NEVER CANCEL. Set timeout to 120+ seconds.
|
|
12
|
-
- `bundle exec rspec spec` -- takes 4 seconds. NEVER CANCEL. Set timeout to 60+ seconds.
|
|
13
|
-
- `bundle exec rubocop` -- takes 3 seconds. NEVER CANCEL. Set timeout to 60+ seconds.
|
|
14
|
-
- `bundle exec bundle-audit check` -- takes 2 seconds (first run downloads advisory database). NEVER CANCEL. Set timeout to 180+ seconds for first run.
|
|
15
|
-
- Performance benchmarking:
|
|
16
|
-
- `bundle exec rake benchmark` -- takes 1 second. NEVER CANCEL. Set timeout to 30+ seconds.
|
|
17
|
-
- Interactive console:
|
|
18
|
-
- `./bin/console` -- launches IRB with pg_objects loaded
|
|
19
|
-
- Install gem locally:
|
|
20
|
-
- `bundle exec rake install` -- takes 9 seconds. NEVER CANCEL. Set timeout to 120+ seconds.
|
|
21
|
-
|
|
22
|
-
## Validation
|
|
23
|
-
- Always run through the complete test suite after making changes: `bundle exec rspec spec`
|
|
24
|
-
- ALWAYS run linting before completing work: `bundle exec rubocop`
|
|
25
|
-
- Always run bundle audit to check for security vulnerabilities: `bundle exec bundle-audit check`
|
|
26
|
-
- Test parsing functionality with sample SQL files to ensure changes work correctly
|
|
27
|
-
- NEVER CANCEL builds or tests - they complete quickly (under 60 seconds)
|
|
28
|
-
|
|
29
|
-
## Common Tasks
|
|
30
|
-
The following are outputs from frequently run commands. Reference them instead of viewing, searching, or running bash commands to save time.
|
|
31
|
-
|
|
32
|
-
### Repository Root Structure
|
|
33
|
-
```
|
|
34
|
-
.
|
|
35
|
-
├── .github/ # CI/CD workflows (ci.yml, bundle_audit.yml, publish.yml)
|
|
36
|
-
├── .rspec # RSpec configuration
|
|
37
|
-
├── .rubocop.yml # RuboCop linting configuration
|
|
38
|
-
├── bin/
|
|
39
|
-
│ ├── setup # Setup script (runs bundle install)
|
|
40
|
-
│ ├── console # Interactive console
|
|
41
|
-
│ └── benchmark # Performance benchmark tool
|
|
42
|
-
├── lib/
|
|
43
|
-
│ ├── pg_objects.rb # Main entry point
|
|
44
|
-
│ ├── pg_objects/ # Core library files
|
|
45
|
-
│ │ ├── config.rb
|
|
46
|
-
│ │ ├── manager.rb
|
|
47
|
-
│ │ ├── parser.rb
|
|
48
|
-
│ │ └── parsed_object/ # SQL object parsers
|
|
49
|
-
│ └── generators/pg_objects/install/ # Rails generator
|
|
50
|
-
├── spec/ # RSpec test files
|
|
51
|
-
├── Gemfile # Gem dependencies
|
|
52
|
-
├── pg_objects.gemspec # Gem specification
|
|
53
|
-
├── Rakefile # Rake tasks (spec, benchmark)
|
|
54
|
-
└── README.md # Documentation
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
### Key Files and Directories
|
|
58
|
-
- **lib/pg_objects.rb**: Main entry point that requires all components
|
|
59
|
-
- **lib/pg_objects/manager.rb**: Core manager for database objects
|
|
60
|
-
- **lib/pg_objects/parser.rb**: SQL parsing and dependency extraction
|
|
61
|
-
- **lib/pg_objects/parsed_object/**: Specific parsers for different SQL object types
|
|
62
|
-
- **spec/**: Complete test suite with fixtures
|
|
63
|
-
- **bin/benchmark**: Performance benchmarking tool with detailed metrics
|
|
64
|
-
- **Gemfile**: Development and test dependencies (RSpec, RuboCop, etc.)
|
|
65
|
-
|
|
66
|
-
### Gemfile Dependencies
|
|
67
|
-
- **Runtime**: activerecord, dry-auto_inject, dry-configurable, pg_query, railties
|
|
68
|
-
- **Development/Test**: rspec, rubocop, bundler-audit, faker, pry-byebug
|
|
69
|
-
|
|
70
|
-
### Common Command Outputs
|
|
71
|
-
#### `bundle exec rspec spec` (Expected: ~4 seconds, 54 examples, 0 failures)
|
|
72
|
-
```
|
|
73
|
-
54 examples, 0 failures
|
|
74
|
-
Finished in 2.24 seconds
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
#### `bundle exec rubocop` (Expected: ~3 seconds, 60 files, no offenses)
|
|
78
|
-
```
|
|
79
|
-
60 files inspected, no offenses detected
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
#### `bundle exec bundle-audit check` (Expected: ~2 seconds after initial setup)
|
|
83
|
-
```
|
|
84
|
-
No vulnerabilities found
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
#### `bundle exec rake benchmark` (Expected: ~1 second)
|
|
88
|
-
```
|
|
89
|
-
PG Objects Performance Benchmark
|
|
90
|
-
==================================================
|
|
91
|
-
File I/O Performance: ~100,000+ files/s
|
|
92
|
-
Parsing Performance: ~7,000 files/s
|
|
93
|
-
Full Workflow Performance: ~6,000 objects/s
|
|
94
|
-
Benchmark completed successfully!
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
## Development Workflow
|
|
98
|
-
1. Always run `bundle install` after cloning or changing dependencies
|
|
99
|
-
2. Make changes to code in lib/ directory
|
|
100
|
-
3. Add or update tests in spec/ directory for any changes
|
|
101
|
-
4. Run `bundle exec rspec spec` to ensure all tests pass
|
|
102
|
-
5. Run `bundle exec rubocop` to ensure code style compliance
|
|
103
|
-
6. Use `bundle exec rake benchmark` to test performance impact
|
|
104
|
-
7. Run `bundle exec bundle-audit check` for security validation
|
|
105
|
-
|
|
106
|
-
## Troubleshooting
|
|
107
|
-
- If bundler is not found: `gem install --user-install bundler && export PATH="$HOME/.local/share/gem/ruby/3.2.0/bin:$PATH"`
|
|
108
|
-
- If bundle install fails with permission errors: `bundle config set --local path 'vendor/bundle'`
|
|
109
|
-
- Ruby version required: >= 3.2.0 (tested with 3.2, 3.3, 3.4)
|
|
110
|
-
- The gem requires PostgreSQL and uses pg_query for SQL parsing
|
|
111
|
-
- Dependencies include ActiveRecord, dry gems, and memery for caching
|
|
112
|
-
|
|
113
|
-
## Testing SQL Parsing
|
|
114
|
-
Create test SQL files with dependencies:
|
|
115
|
-
```sql
|
|
116
|
-
--!depends_on other_function
|
|
117
|
-
CREATE OR REPLACE FUNCTION my_function(param INTEGER)
|
|
118
|
-
RETURNS INTEGER AS $$
|
|
119
|
-
BEGIN
|
|
120
|
-
RETURN param * 2;
|
|
121
|
-
END;
|
|
122
|
-
$$ LANGUAGE plpgsql;
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
Test parsing with:
|
|
126
|
-
```ruby
|
|
127
|
-
parser = PgObjects::Parser.new
|
|
128
|
-
content = File.read('path/to/file.sql')
|
|
129
|
-
object_name = parser.load(content).fetch_object_name
|
|
130
|
-
dependencies = parser.fetch_directives[:depends_on]
|
|
131
|
-
```
|