pg_objects 1.0.3 → 1.2.1

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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +11 -0
  3. data/.github/workflows/ci.yml +1 -1
  4. data/.rubocop.yml +4 -1
  5. data/Gemfile +3 -1
  6. data/Gemfile.lock +189 -74
  7. data/lib/generators/pg_objects/install/install_generator.rb +7 -9
  8. data/lib/pg_objects/config.rb +8 -10
  9. data/lib/pg_objects/container.rb +30 -0
  10. data/lib/pg_objects/db_object.rb +40 -22
  11. data/lib/pg_objects/db_object_factory.rb +13 -0
  12. data/lib/pg_objects/logger.rb +7 -14
  13. data/lib/pg_objects/manager.rb +51 -51
  14. data/lib/pg_objects/parsed_object/aggregate.rb +8 -0
  15. data/lib/pg_objects/parsed_object/base.rb +16 -0
  16. data/lib/pg_objects/parsed_object/conversion.rb +8 -0
  17. data/lib/pg_objects/parsed_object/event_trigger.rb +8 -0
  18. data/lib/pg_objects/parsed_object/function.rb +8 -0
  19. data/lib/pg_objects/parsed_object/materialized_view.rb +8 -0
  20. data/lib/pg_objects/parsed_object/operator.rb +8 -0
  21. data/lib/pg_objects/parsed_object/operator_class.rb +8 -0
  22. data/lib/pg_objects/parsed_object/table.rb +8 -0
  23. data/lib/pg_objects/parsed_object/text_search_parser.rb +8 -0
  24. data/lib/pg_objects/parsed_object/text_search_template.rb +8 -0
  25. data/lib/pg_objects/parsed_object/trigger.rb +8 -0
  26. data/lib/pg_objects/parsed_object/type.rb +8 -0
  27. data/lib/pg_objects/parsed_object/view.rb +8 -0
  28. data/lib/pg_objects/parsed_object.rb +18 -0
  29. data/lib/pg_objects/parsed_object_factory.rb +103 -0
  30. data/lib/pg_objects/parser.rb +46 -43
  31. data/lib/pg_objects/railtie.rb +5 -10
  32. data/lib/pg_objects/version.rb +1 -1
  33. data/lib/pg_objects.rb +9 -0
  34. data/pg_objects.gemspec +9 -4
  35. metadata +100 -11
@@ -1,69 +1,69 @@
1
- module PgObjects
1
+ ##
2
+ # Manages process to create objects
3
+ #
4
+ # Usage:
5
+ #
6
+ # Manager.new(config, logger).load_files(:before).create_objects
7
+ #
8
+ # or
9
+ #
10
+ # Manager.new(config, logger).load_files(:after).create_objects
11
+ class PgObjects::Manager
12
+ include Import['db_object_factory', 'config', 'logger']
13
+
2
14
  ##
3
- # Manages process to create objects
4
- #
5
- # Usage:
6
- #
7
- # Manager.new.load_files(:before).create_objects
15
+ # event: +:before+ or +:after+
8
16
  #
9
- # or
10
- #
11
- # Manager.new.load_files(:after).create_objects
12
- class Manager
13
- attr_reader :objects, :config, :log
14
-
15
- def initialize
16
- raise UnsupportedAdapterError if ActiveRecord::Base.connection.adapter_name != 'PostgreSQL'
17
+ # used to reference configuration settings +before_path+ and +after_path+
18
+ def load_files(event)
19
+ validate_workability
17
20
 
18
- @objects = []
19
- @config = PgObjects.config
20
- @log = Logger.new(silent: config.silent)
21
+ dir = config.send "#{event}_path"
22
+ Dir[File.join(dir, '**', "*.{#{config.extensions.join(',')}}")].each do |path|
23
+ objects << db_object_factory.create_instance(path)
21
24
  end
22
25
 
23
- ##
24
- # event: +:before+ or +:after+
25
- #
26
- # used to reference configuration settings +before_path+ and +after_path+
27
- def load_files(event)
28
- dir = config.send "#{event}_path"
29
- Dir[File.join(dir, '**', "*.{#{config.extensions.join(',')}}")].each do |path|
30
- @objects << PgObjects::DbObject.new(path)
31
- end
32
-
33
- self
34
- end
26
+ self
27
+ end
35
28
 
36
- def create_objects
37
- @objects.each { |obj| create_object obj }
38
- end
29
+ def create_objects
30
+ objects.each { create_object(_1) }
31
+ end
39
32
 
40
- private
33
+ def objects
34
+ @objects ||= []
35
+ end
41
36
 
42
- def create_object(obj)
43
- return if obj.status == :done
44
- raise CyclicDependencyError, obj.name if obj.status == :processing
37
+ private
45
38
 
46
- obj.status = :processing
39
+ def validate_workability
40
+ raise PgObjects::UnsupportedAdapterError if ActiveRecord::Base.connection.adapter_name != 'PostgreSQL'
41
+ end
47
42
 
48
- create_dependencies(obj.dependencies)
43
+ def create_object(obj)
44
+ return if obj.status == :done
45
+ raise PgObjects::CyclicDependencyError, obj.name if obj.status == :processing
49
46
 
50
- log.write("creating #{obj.name}")
51
- ActiveRecord::Base.connection.execute obj.sql_query
47
+ obj.status = :processing
52
48
 
53
- obj.status = :done
54
- end
49
+ create_dependencies(obj.dependencies)
55
50
 
56
- def create_dependencies(dependencies)
57
- dependencies.each { |dep_name| create_object find_object(dep_name) }
58
- end
51
+ logger.write("creating #{obj.name}")
52
+ ActiveRecord::Base.connection.execute(obj.sql_query)
59
53
 
60
- def find_object(dep_name)
61
- result = @objects.select { |obj| [obj.name, obj.full_name, obj.object_name].compact.include? dep_name }
54
+ obj.status = :done
55
+ end
62
56
 
63
- raise AmbiguousDependencyError, dep_name if result.size > 1
64
- raise DependencyNotExistError, dep_name if result.empty?
57
+ def create_dependencies(dependencies)
58
+ dependencies.each { |dep_name| create_object(find_object(dep_name)) }
59
+ end
65
60
 
66
- result[0]
67
- end
61
+ def find_object(dep_name)
62
+ result = objects.select { |obj| [obj.name, obj.full_name, obj.object_name].compact.include? dep_name }
63
+
64
+ raise PgObjects::AmbiguousDependencyError, dep_name if result.size > 1
65
+ raise PgObjects::DependencyNotExistError, dep_name if result.empty?
66
+
67
+ result[0]
68
68
  end
69
69
  end
@@ -0,0 +1,8 @@
1
+ #
2
+ # AGGREGATE object representation
3
+ #
4
+ class PgObjects::ParsedObject::Aggregate < PgObjects::ParsedObject::Base
5
+ def name
6
+ stmt.define_stmt.defnames[0].string.sval
7
+ end
8
+ end
@@ -0,0 +1,16 @@
1
+ #
2
+ # Base class for parsed objects
3
+ #
4
+ class PgObjects::ParsedObject::Base
5
+ def initialize(stmt)
6
+ @stmt = stmt
7
+ end
8
+
9
+ def name
10
+ raise NotImplementedError
11
+ end
12
+
13
+ private
14
+
15
+ attr_reader :stmt
16
+ end
@@ -0,0 +1,8 @@
1
+ #
2
+ # CONVERSION object representation
3
+ #
4
+ class PgObjects::ParsedObject::Conversion < PgObjects::ParsedObject::Base
5
+ def name
6
+ stmt.create_conversion_stmt.conversion_name[0].string.sval
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ #
2
+ # EVENT TRIGGER object representation
3
+ #
4
+ class PgObjects::ParsedObject::EventTrigger < PgObjects::ParsedObject::Base
5
+ def name
6
+ stmt.create_event_trig_stmt.trigname
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ #
2
+ # FUNCTION object representation
3
+ #
4
+ class PgObjects::ParsedObject::Function < PgObjects::ParsedObject::Base
5
+ def name
6
+ stmt.create_function_stmt.funcname[0].string.sval
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ #
2
+ # MATERIALIZED VIEW object representation
3
+ #
4
+ class PgObjects::ParsedObject::MaterializedView < PgObjects::ParsedObject::Base
5
+ def name
6
+ stmt.create_table_as_stmt.into.rel.relname
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ #
2
+ # OPERATOR object representation
3
+ #
4
+ class PgObjects::ParsedObject::Operator < PgObjects::ParsedObject::Base
5
+ def name
6
+ stmt.define_stmt.defnames[0].string.sval
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ #
2
+ # OPERATOR CLASS object representation
3
+ #
4
+ class PgObjects::ParsedObject::OperatorClass < PgObjects::ParsedObject::Base
5
+ def name
6
+ stmt.create_op_class_stmt.opclassname[0].string.sval
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ #
2
+ # TABLE object representation
3
+ #
4
+ class PgObjects::ParsedObject::Table < PgObjects::ParsedObject::Base
5
+ def name
6
+ stmt.create_stmt.relation.relname
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ #
2
+ # TEXT SEARCH PARSER object representation
3
+ #
4
+ class PgObjects::ParsedObject::TextSearchParser < PgObjects::ParsedObject::Base
5
+ def name
6
+ stmt.define_stmt.defnames[0].string.sval
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ #
2
+ # TEXT SEARCH TEMPLATE object representation
3
+ #
4
+ class PgObjects::ParsedObject::TextSearchTemplate < PgObjects::ParsedObject::Base
5
+ def name
6
+ stmt.define_stmt.defnames[0].string.sval
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ #
2
+ # TRIGGER object representation
3
+ #
4
+ class PgObjects::ParsedObject::Trigger < PgObjects::ParsedObject::Base
5
+ def name
6
+ stmt.create_trig_stmt.trigname
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ #
2
+ # TYPE object representation
3
+ #
4
+ class PgObjects::ParsedObject::Type < PgObjects::ParsedObject::Base
5
+ def name
6
+ stmt.composite_type_stmt.typevar.relname
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ #
2
+ # VIEW object representation
3
+ #
4
+ class PgObjects::ParsedObject::View < PgObjects::ParsedObject::Base
5
+ def name
6
+ stmt.view_stmt.view.relname
7
+ end
8
+ end
@@ -0,0 +1,18 @@
1
+ module PgObjects::ParsedObject # rubocop: disable Style/Documentation
2
+ end
3
+
4
+ require 'pg_objects/parsed_object/base'
5
+ require 'pg_objects/parsed_object/aggregate'
6
+ require 'pg_objects/parsed_object/conversion'
7
+ require 'pg_objects/parsed_object/event_trigger'
8
+ require 'pg_objects/parsed_object/function'
9
+ require 'pg_objects/parsed_object/materialized_view'
10
+ require 'pg_objects/parsed_object/operator'
11
+ require 'pg_objects/parsed_object/operator_class'
12
+ require 'pg_objects/parsed_object/table'
13
+ require 'pg_objects/parsed_object/text_search_parser'
14
+ require 'pg_objects/parsed_object/text_search_template'
15
+ require 'pg_objects/parsed_object/trigger'
16
+ require 'pg_objects/parsed_object/type'
17
+ require 'pg_objects/parsed_object/view'
18
+ require 'pg_objects/parsed_object_factory'
@@ -0,0 +1,103 @@
1
+ #
2
+ # Returns an object of the respective class based on the provided parsed query
3
+ #
4
+ class PgObjects::ParsedObjectFactory
5
+ class << self
6
+ include Dry::Monads[:try, :result]
7
+
8
+ SUPPORTED_TYPES = %i[
9
+ aggregate
10
+ conversion
11
+ event_trigger
12
+ function
13
+ materialized_view
14
+ operator
15
+ operator_class
16
+ table
17
+ text_search_parser
18
+ text_search_template
19
+ trigger
20
+ type
21
+ view
22
+ ].freeze
23
+
24
+ def create_object(input_data)
25
+ @input_data = input_data
26
+ @stmt = input_data.tree.stmts[0].stmt
27
+
28
+ determine_class.new(stmt)
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :stmt, :input_data
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
80
+
81
+ def text_search_template?
82
+ try_check_get_result { stmt.define_stmt.kind == :OBJECT_TSTEMPLATE }
83
+ end
84
+
85
+ def trigger?
86
+ try_check_get_result { stmt.create_trig_stmt.trigname.present? }
87
+ end
88
+
89
+ def type?
90
+ try_check_get_result { stmt.composite_type_stmt.typevar.present? }
91
+ end
92
+
93
+ def view?
94
+ try_check_get_result { stmt.view_stmt.view.present? }
95
+ end
96
+
97
+ def try_check_get_result(&)
98
+ result = Try(&).to_result
99
+
100
+ result.success? && result.value!
101
+ end
102
+ end
103
+ end
@@ -1,47 +1,50 @@
1
1
  require 'pg_query'
2
2
 
3
- module PgObjects
4
- ##
5
- # Reads directives from SQL-comments
6
- #
7
- # --!depends_on [name_of_dependency]
8
- #
9
- # name_of_dependency: short or full name of object as well as object_name
10
- #
11
- class Parser
12
- # rubocop: disable Style/WordArray
13
- ROUTES = [
14
- ['DefineStmt', 'defnames', 0, 'String', 'str'],
15
- ['CreateFunctionStmt', 'funcname', 0, 'String', 'str'],
16
- ['CreateTrigStmt', 'trigname'],
17
- ['CreateEventTrigStmt', 'trigname'],
18
- ['CompositeTypeStmt', 'typevar', 'RangeVar', 'relname'],
19
- ['ViewStmt', 'view', 'RangeVar', 'relname'],
20
- ['CreateConversionStmt', 'conversion_name', 0, 'String', 'str'],
21
- ['CreateTableAsStmt', 'into', 'IntoClause', 'rel', 'RangeVar', 'relname'],
22
- ['CreateOpClassStmt', 'opclassname', 0, 'String', 'str']
23
- ].freeze
24
- # rubocop: enable Style/WordArray
25
-
26
- class << self
27
- def fetch_directives(text)
28
- {
29
- depends_on: fetch_dependencies(text)
30
- }
31
- end
32
-
33
- def fetch_object_name(text)
34
- parsed = PgQuery.parse(text).tree.dig(0, 'RawStmt', 'stmt')
35
- ROUTES.map { |route| parsed.dig(*route) }.compact[0]
36
- rescue PgQuery::ParseError, NoMethodError
37
- nil
38
- end
39
-
40
- private
41
-
42
- def fetch_dependencies(text)
43
- text.split("\n").grep(/^(--|#)!/).map { |ln| ln.split[1] if ln =~ /!depends_on/ }.compact
44
- end
45
- end
3
+ ##
4
+ # Reads directives from SQL-comments
5
+ #
6
+ # --!depends_on [name_of_dependency]
7
+ #
8
+ # name_of_dependency: short or full name of object as well as object_name
9
+ #
10
+ class PgObjects::Parser
11
+ include Import['parsed_object_factory']
12
+ include Memery
13
+
14
+ PG_ENTITIES = %i[operator_class trigger define_statement conversion event_trigger type function table].freeze
15
+
16
+ def load(source)
17
+ @source = source
18
+ self
19
+ end
20
+
21
+ def fetch_directives
22
+ {
23
+ depends_on: fetch_dependencies
24
+ }
25
+ end
26
+
27
+ def fetch_object_name
28
+ parse_query
29
+ parsed_object.name
30
+ rescue PgQuery::ParseError, NoMethodError
31
+ nil
32
+ end
33
+
34
+ private
35
+
36
+ attr_reader :parsed
37
+
38
+ def parse_query
39
+ @parsed = PgQuery.parse(@source)
40
+ end
41
+
42
+ memoize
43
+ def parsed_object
44
+ parsed_object_factory.create_object(parsed)
45
+ end
46
+
47
+ def fetch_dependencies
48
+ @source.split("\n").grep(/^(--|#)!/).map { |ln| ln.split[1] if ln =~ /!depends_on/ }.compact
46
49
  end
47
50
  end
@@ -1,12 +1,7 @@
1
- module PgObjects
2
- ##
3
- # Brings rake tasks to rails app
4
- class Railtie < Rails::Railtie
5
- # initializer 'pg_objects.initialization' do |app|
6
- # end
7
-
8
- rake_tasks do
9
- load 'tasks/pg_objects_tasks.rake'
10
- end
1
+ ##
2
+ # Brings rake tasks to rails app
3
+ class PgObjects::Railtie < Rails::Railtie
4
+ rake_tasks do
5
+ load 'tasks/pg_objects_tasks.rake'
11
6
  end
12
7
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PgObjects
4
- VERSION = '1.0.3'
4
+ VERSION = '1.2.1'
5
5
  end
data/lib/pg_objects.rb CHANGED
@@ -1,10 +1,19 @@
1
1
  require 'pg_objects/version'
2
2
  require 'pg_objects/railtie' if defined?(Rails)
3
3
 
4
+ require 'dry-configurable'
5
+ require 'dry-container'
6
+ require 'dry-auto_inject'
7
+ require 'dry/monads'
8
+ require 'memery'
9
+
10
+ require 'pg_objects/container'
4
11
  require 'pg_objects/config'
5
12
  require 'pg_objects/db_object'
13
+ require 'pg_objects/db_object_factory'
6
14
  require 'pg_objects/logger'
7
15
  require 'pg_objects/manager'
16
+ require 'pg_objects/parsed_object'
8
17
  require 'pg_objects/parser'
9
18
 
10
19
  module PgObjects
data/pg_objects.gemspec CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
11
11
  spec.summary = %q(Simple manager for PostgreSQL objects like triggers and functions)
12
12
  spec.homepage = 'https://github.com/marinazzio/pg_objects'
13
13
 
14
- spec.required_ruby_version = '>= 2.7.0'
14
+ spec.required_ruby_version = '>= 3.1.0'
15
15
 
16
16
  spec.metadata = {
17
17
  'bug_tracker_uri' => 'https://github.com/marinazzio/pg_objects/issues',
@@ -43,8 +43,13 @@ Gem::Specification.new do |spec|
43
43
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
44
44
  spec.require_paths = ['lib']
45
45
 
46
- spec.add_dependency 'activerecord', '>= 6.0.3.5', '< 7'
47
- spec.add_dependency 'pg_query', '~> 1'
48
- spec.add_dependency 'railties', '>= 4', '< 7'
46
+ spec.add_dependency 'activerecord', '>= 6.1.7.0', '< 8'
47
+ spec.add_dependency 'dry-auto_inject', '~> 1'
48
+ spec.add_dependency 'dry-configurable', '~> 1'
49
+ spec.add_dependency 'dry-container', '0.11.0'
50
+ spec.add_dependency 'dry-monads', '~> 1.6'
51
+ spec.add_dependency 'memery', '~> 1.5.0'
52
+ spec.add_dependency 'pg_query', '~> 5'
53
+ spec.add_dependency 'railties', '>= 4', '< 8'
49
54
  spec.add_dependency 'rake-hooks', '~> 1'
50
55
  end