pg_objects 1.4.8 → 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/.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 +71 -54
- 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 +16 -20
- data/.github/copilot-instructions.md +0 -131
|
@@ -1,8 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
#
|
|
2
4
|
# OPERATOR CLASS object representation
|
|
3
5
|
#
|
|
4
6
|
class PgObjects::ParsedObject::OperatorClass < PgObjects::ParsedObject::Base
|
|
5
7
|
def name
|
|
6
|
-
stmt.create_op_class_stmt.opclassname
|
|
8
|
+
extract_name { stmt.create_op_class_stmt.opclassname.last.string.sval }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def schema
|
|
14
|
+
extract_name { qualifier(stmt.create_op_class_stmt.opclassname) }
|
|
7
15
|
end
|
|
8
16
|
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# RANGE TYPE object representation (CREATE TYPE ... AS RANGE)
|
|
5
|
+
#
|
|
6
|
+
class PgObjects::ParsedObject::RangeType < PgObjects::ParsedObject::Base
|
|
7
|
+
def name
|
|
8
|
+
extract_name { stmt.create_range_stmt.type_name.last.string.sval }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def schema
|
|
14
|
+
extract_name { qualifier(stmt.create_range_stmt.type_name) }
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# SEQUENCE object representation
|
|
5
|
+
#
|
|
6
|
+
class PgObjects::ParsedObject::Sequence < PgObjects::ParsedObject::Base
|
|
7
|
+
def name
|
|
8
|
+
extract_name { stmt.create_seq_stmt.sequence.relname }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def schema
|
|
14
|
+
extract_name { stmt.create_seq_stmt.sequence.schemaname }
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -1,8 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
#
|
|
2
4
|
# TABLE object representation
|
|
3
5
|
#
|
|
4
6
|
class PgObjects::ParsedObject::Table < PgObjects::ParsedObject::Base
|
|
5
7
|
def name
|
|
6
|
-
stmt.create_stmt.relation.relname
|
|
8
|
+
extract_name { stmt.create_stmt.relation.relname }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def schema
|
|
14
|
+
extract_name { stmt.create_stmt.relation.schemaname }
|
|
7
15
|
end
|
|
8
16
|
end
|
|
@@ -1,8 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
#
|
|
2
4
|
# TEXT SEARCH PARSER object representation
|
|
3
5
|
#
|
|
4
6
|
class PgObjects::ParsedObject::TextSearchParser < PgObjects::ParsedObject::Base
|
|
5
7
|
def name
|
|
6
|
-
stmt.define_stmt.defnames
|
|
8
|
+
extract_name { stmt.define_stmt.defnames.last.string.sval }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def schema
|
|
14
|
+
extract_name { qualifier(stmt.define_stmt.defnames) }
|
|
7
15
|
end
|
|
8
16
|
end
|
|
@@ -1,8 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
#
|
|
2
4
|
# TEXT SEARCH TEMPLATE object representation
|
|
3
5
|
#
|
|
4
6
|
class PgObjects::ParsedObject::TextSearchTemplate < PgObjects::ParsedObject::Base
|
|
5
7
|
def name
|
|
6
|
-
stmt.define_stmt.defnames
|
|
8
|
+
extract_name { stmt.define_stmt.defnames.last.string.sval }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def schema
|
|
14
|
+
extract_name { qualifier(stmt.define_stmt.defnames) }
|
|
7
15
|
end
|
|
8
16
|
end
|
|
@@ -1,8 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
#
|
|
2
4
|
# TYPE object representation
|
|
3
5
|
#
|
|
4
6
|
class PgObjects::ParsedObject::Type < PgObjects::ParsedObject::Base
|
|
5
7
|
def name
|
|
6
|
-
stmt.composite_type_stmt.typevar.relname
|
|
8
|
+
extract_name { stmt.composite_type_stmt.typevar.relname }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def schema
|
|
14
|
+
extract_name { stmt.composite_type_stmt.typevar.schemaname }
|
|
7
15
|
end
|
|
8
16
|
end
|
|
@@ -1,8 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
#
|
|
2
4
|
# VIEW object representation
|
|
3
5
|
#
|
|
4
6
|
class PgObjects::ParsedObject::View < PgObjects::ParsedObject::Base
|
|
5
7
|
def name
|
|
6
|
-
stmt.view_stmt.view.relname
|
|
8
|
+
extract_name { stmt.view_stmt.view.relname }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def schema
|
|
14
|
+
extract_name { stmt.view_stmt.view.schemaname }
|
|
7
15
|
end
|
|
8
16
|
end
|
|
@@ -1,14 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module PgObjects::ParsedObject # rubocop: disable Style/Documentation
|
|
2
4
|
end
|
|
3
5
|
|
|
4
6
|
require 'pg_objects/parsed_object/base'
|
|
5
7
|
require 'pg_objects/parsed_object/aggregate'
|
|
8
|
+
require 'pg_objects/parsed_object/base_type'
|
|
6
9
|
require 'pg_objects/parsed_object/conversion'
|
|
10
|
+
require 'pg_objects/parsed_object/domain'
|
|
11
|
+
require 'pg_objects/parsed_object/enum_type'
|
|
7
12
|
require 'pg_objects/parsed_object/event_trigger'
|
|
13
|
+
require 'pg_objects/parsed_object/extension'
|
|
8
14
|
require 'pg_objects/parsed_object/function'
|
|
15
|
+
require 'pg_objects/parsed_object/index'
|
|
9
16
|
require 'pg_objects/parsed_object/materialized_view'
|
|
10
17
|
require 'pg_objects/parsed_object/operator'
|
|
11
18
|
require 'pg_objects/parsed_object/operator_class'
|
|
19
|
+
require 'pg_objects/parsed_object/policy'
|
|
20
|
+
require 'pg_objects/parsed_object/range_type'
|
|
21
|
+
require 'pg_objects/parsed_object/rule'
|
|
22
|
+
require 'pg_objects/parsed_object/sequence'
|
|
12
23
|
require 'pg_objects/parsed_object/table'
|
|
13
24
|
require 'pg_objects/parsed_object/text_search_parser'
|
|
14
25
|
require 'pg_objects/parsed_object/text_search_template'
|
|
@@ -1,103 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
#
|
|
2
4
|
# Returns an object of the respective class based on the provided parsed query
|
|
3
5
|
#
|
|
4
6
|
class PgObjects::ParsedObjectFactory
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def determine_class
|
|
36
|
-
SUPPORTED_TYPES.each do |type|
|
|
37
|
-
return class_for(type) if send("#{type}?")
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def class_for(type)
|
|
42
|
-
"PgObjects::ParsedObject::#{type.to_s.classify}".constantize
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def aggregate?
|
|
46
|
-
try_check_get_result { stmt.define_stmt.kind == :OBJECT_AGGREGATE }
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def conversion?
|
|
50
|
-
try_check_get_result { stmt.create_conversion_stmt.conversion_name.present? }
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def event_trigger?
|
|
54
|
-
try_check_get_result { stmt.create_event_trig_stmt.trigname.present? }
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def function?
|
|
58
|
-
try_check_get_result { stmt.create_function_stmt.funcname.present? }
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def materialized_view?
|
|
62
|
-
try_check_get_result { stmt.create_table_as_stmt.objtype == :OBJECT_MATVIEW }
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def operator_class?
|
|
66
|
-
try_check_get_result { stmt.create_op_class_stmt.opclassname.present? }
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def operator?
|
|
70
|
-
try_check_get_result { stmt.define_stmt.kind == :OBJECT_OPERATOR }
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def table?
|
|
74
|
-
try_check_get_result { stmt.create_stmt.table_elts.present? }
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def text_search_parser?
|
|
78
|
-
try_check_get_result { stmt.define_stmt.kind == :OBJECT_TSPARSER }
|
|
79
|
-
end
|
|
7
|
+
DISPATCH = {
|
|
8
|
+
composite_type_stmt: PgObjects::ParsedObject::Type,
|
|
9
|
+
create_conversion_stmt: PgObjects::ParsedObject::Conversion,
|
|
10
|
+
create_domain_stmt: PgObjects::ParsedObject::Domain,
|
|
11
|
+
create_enum_stmt: PgObjects::ParsedObject::EnumType,
|
|
12
|
+
create_event_trig_stmt: PgObjects::ParsedObject::EventTrigger,
|
|
13
|
+
create_extension_stmt: PgObjects::ParsedObject::Extension,
|
|
14
|
+
create_function_stmt: PgObjects::ParsedObject::Function,
|
|
15
|
+
create_op_class_stmt: PgObjects::ParsedObject::OperatorClass,
|
|
16
|
+
create_policy_stmt: PgObjects::ParsedObject::Policy,
|
|
17
|
+
create_range_stmt: PgObjects::ParsedObject::RangeType,
|
|
18
|
+
create_seq_stmt: PgObjects::ParsedObject::Sequence,
|
|
19
|
+
create_stmt: PgObjects::ParsedObject::Table,
|
|
20
|
+
create_trig_stmt: PgObjects::ParsedObject::Trigger,
|
|
21
|
+
index_stmt: PgObjects::ParsedObject::Index,
|
|
22
|
+
rule_stmt: PgObjects::ParsedObject::Rule,
|
|
23
|
+
view_stmt: PgObjects::ParsedObject::View
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
26
|
+
DEFINE_STMT_DISPATCH = {
|
|
27
|
+
OBJECT_AGGREGATE: PgObjects::ParsedObject::Aggregate,
|
|
28
|
+
OBJECT_OPERATOR: PgObjects::ParsedObject::Operator,
|
|
29
|
+
OBJECT_TSPARSER: PgObjects::ParsedObject::TextSearchParser,
|
|
30
|
+
OBJECT_TSTEMPLATE: PgObjects::ParsedObject::TextSearchTemplate,
|
|
31
|
+
OBJECT_TYPE: PgObjects::ParsedObject::BaseType
|
|
32
|
+
}.freeze
|
|
33
|
+
|
|
34
|
+
def self.create_object(input_data)
|
|
35
|
+
new(input_data).create_object
|
|
36
|
+
end
|
|
80
37
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
38
|
+
def initialize(input_data)
|
|
39
|
+
@input_data = input_data
|
|
40
|
+
@stmt = input_data.tree.stmts[0].stmt
|
|
41
|
+
end
|
|
84
42
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
43
|
+
def create_object
|
|
44
|
+
determine_class.new(stmt)
|
|
45
|
+
end
|
|
88
46
|
|
|
89
|
-
|
|
90
|
-
try_check_get_result { stmt.composite_type_stmt.typevar.present? }
|
|
91
|
-
end
|
|
47
|
+
private
|
|
92
48
|
|
|
93
|
-
|
|
94
|
-
try_check_get_result { stmt.view_stmt.view.present? }
|
|
95
|
-
end
|
|
49
|
+
attr_reader :stmt, :input_data
|
|
96
50
|
|
|
97
|
-
|
|
98
|
-
|
|
51
|
+
def determine_class
|
|
52
|
+
dispatch_class || raise(PgObjects::UnknownObjectTypeError, "unknown object type: #{stmt.node}")
|
|
53
|
+
end
|
|
99
54
|
|
|
100
|
-
|
|
55
|
+
def dispatch_class
|
|
56
|
+
case stmt.node
|
|
57
|
+
when :define_stmt
|
|
58
|
+
DEFINE_STMT_DISPATCH[stmt.define_stmt.kind]
|
|
59
|
+
when :create_table_as_stmt
|
|
60
|
+
PgObjects::ParsedObject::MaterializedView if stmt.create_table_as_stmt.objtype == :OBJECT_MATVIEW
|
|
61
|
+
else
|
|
62
|
+
DISPATCH[stmt.node]
|
|
101
63
|
end
|
|
102
64
|
end
|
|
103
65
|
end
|
data/lib/pg_objects/parser.rb
CHANGED
|
@@ -1,20 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'pg_query'
|
|
2
4
|
|
|
3
5
|
##
|
|
4
6
|
# Reads directives from SQL-comments
|
|
5
7
|
#
|
|
6
|
-
# --!depends_on
|
|
8
|
+
# --!depends_on name_a
|
|
9
|
+
# --!depends_on name_a, name_b, name_c
|
|
10
|
+
# #!depends_on name_a
|
|
11
|
+
#
|
|
12
|
+
# The directive must start the line (no leading whitespace). Dependencies may
|
|
13
|
+
# be listed on one line separated by commas and/or whitespace, or across
|
|
14
|
+
# several directive lines.
|
|
7
15
|
#
|
|
8
16
|
# name_of_dependency: short or full name of object as well as object_name
|
|
9
17
|
#
|
|
10
18
|
class PgObjects::Parser
|
|
11
19
|
include Import['parsed_object_factory']
|
|
12
|
-
include Memery
|
|
13
20
|
|
|
14
|
-
|
|
21
|
+
DEPENDS_ON_DIRECTIVE = /\A(?:--|#)!depends_on\s+(.+)/
|
|
15
22
|
|
|
16
23
|
def load(source)
|
|
17
24
|
@source = source
|
|
25
|
+
@parsed = nil
|
|
26
|
+
@parsed_object = nil
|
|
18
27
|
self
|
|
19
28
|
end
|
|
20
29
|
|
|
@@ -25,26 +34,33 @@ class PgObjects::Parser
|
|
|
25
34
|
end
|
|
26
35
|
|
|
27
36
|
def fetch_object_name
|
|
28
|
-
parse_query
|
|
29
37
|
parsed_object.name
|
|
30
|
-
rescue PgQuery::ParseError,
|
|
38
|
+
rescue PgQuery::ParseError, PgObjects::UnknownObjectTypeError
|
|
31
39
|
nil
|
|
32
40
|
end
|
|
33
41
|
|
|
34
|
-
|
|
42
|
+
def fetch_qualified_object_name
|
|
43
|
+
parsed_object.qualified_name
|
|
44
|
+
rescue PgQuery::ParseError, PgObjects::UnknownObjectTypeError
|
|
45
|
+
nil
|
|
46
|
+
end
|
|
35
47
|
|
|
36
|
-
|
|
48
|
+
private
|
|
37
49
|
|
|
38
|
-
|
|
39
|
-
|
|
50
|
+
# Parse once per loaded source; both fetch_* methods reuse the result and
|
|
51
|
+
# +load+ clears it, so a new source is always reparsed (no stale cache).
|
|
52
|
+
def parsed
|
|
53
|
+
@parsed ||= PgQuery.parse(@source)
|
|
40
54
|
end
|
|
41
55
|
|
|
42
|
-
memoize
|
|
43
56
|
def parsed_object
|
|
44
|
-
parsed_object_factory.create_object(parsed)
|
|
57
|
+
@parsed_object ||= parsed_object_factory.create_object(parsed)
|
|
45
58
|
end
|
|
46
59
|
|
|
47
60
|
def fetch_dependencies
|
|
48
|
-
@source.
|
|
61
|
+
@source.each_line.flat_map do |line|
|
|
62
|
+
match = DEPENDS_ON_DIRECTIVE.match(line)
|
|
63
|
+
match ? match[1].split(/[\s,]+/).reject(&:empty?) : []
|
|
64
|
+
end
|
|
49
65
|
end
|
|
50
66
|
end
|
data/lib/pg_objects/railtie.rb
CHANGED
data/lib/pg_objects/version.rb
CHANGED
|
@@ -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'
|