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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +1 -1
  3. data/.gitignore +3 -0
  4. data/.rubocop.yml +13 -2
  5. data/CHANGELOG.md +78 -0
  6. data/Gemfile +2 -0
  7. data/Gemfile.lock +71 -54
  8. data/README.md +174 -3
  9. data/Rakefile +2 -0
  10. data/bin/benchmark +42 -7
  11. data/bin/console +1 -0
  12. data/lib/generators/pg_objects/install/install_generator.rb +2 -0
  13. data/lib/pg_objects/config.rb +53 -2
  14. data/lib/pg_objects/db_object.rb +6 -1
  15. data/lib/pg_objects/db_object_factory.rb +11 -3
  16. data/lib/pg_objects/logger.rb +34 -3
  17. data/lib/pg_objects/manager.rb +66 -15
  18. data/lib/pg_objects/parsed_object/aggregate.rb +9 -1
  19. data/lib/pg_objects/parsed_object/base.rb +38 -0
  20. data/lib/pg_objects/parsed_object/base_type.rb +17 -0
  21. data/lib/pg_objects/parsed_object/conversion.rb +9 -1
  22. data/lib/pg_objects/parsed_object/domain.rb +16 -0
  23. data/lib/pg_objects/parsed_object/enum_type.rb +16 -0
  24. data/lib/pg_objects/parsed_object/event_trigger.rb +3 -1
  25. data/lib/pg_objects/parsed_object/extension.rb +10 -0
  26. data/lib/pg_objects/parsed_object/function.rb +9 -1
  27. data/lib/pg_objects/parsed_object/index.rb +17 -0
  28. data/lib/pg_objects/parsed_object/materialized_view.rb +9 -1
  29. data/lib/pg_objects/parsed_object/operator.rb +9 -1
  30. data/lib/pg_objects/parsed_object/operator_class.rb +9 -1
  31. data/lib/pg_objects/parsed_object/policy.rb +10 -0
  32. data/lib/pg_objects/parsed_object/range_type.rb +16 -0
  33. data/lib/pg_objects/parsed_object/rule.rb +10 -0
  34. data/lib/pg_objects/parsed_object/sequence.rb +16 -0
  35. data/lib/pg_objects/parsed_object/table.rb +9 -1
  36. data/lib/pg_objects/parsed_object/text_search_parser.rb +9 -1
  37. data/lib/pg_objects/parsed_object/text_search_template.rb +9 -1
  38. data/lib/pg_objects/parsed_object/trigger.rb +3 -1
  39. data/lib/pg_objects/parsed_object/type.rb +9 -1
  40. data/lib/pg_objects/parsed_object/view.rb +9 -1
  41. data/lib/pg_objects/parsed_object.rb +11 -0
  42. data/lib/pg_objects/parsed_object_factory.rb +52 -90
  43. data/lib/pg_objects/parser.rb +28 -12
  44. data/lib/pg_objects/railtie.rb +2 -0
  45. data/lib/pg_objects/version.rb +3 -1
  46. data/lib/pg_objects/yaml_configurable.rb +25 -7
  47. data/lib/pg_objects.rb +54 -4
  48. data/lib/tasks/pg_objects_tasks.rake +33 -8
  49. data/pg_objects.gemspec +5 -3
  50. metadata +16 -20
  51. data/.github/copilot-instructions.md +0 -131
data/bin/benchmark CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'bundler/setup'
5
+ require 'active_support/core_ext/string/inflections' # String#classify / #constantize used by the parser
4
6
  require 'pg_objects'
5
7
  require 'benchmark'
6
8
  require 'fileutils'
@@ -242,6 +244,7 @@ class PgObjectsBenchmark
242
244
  parser = PgObjects::Parser.new
243
245
  successful_parses = 0
244
246
  parse_errors = 0
247
+ sample_error = nil
245
248
 
246
249
  result = Benchmark.measure do
247
250
  files.each do |file|
@@ -249,8 +252,9 @@ class PgObjectsBenchmark
249
252
  begin
250
253
  parser.load(content).fetch_object_name
251
254
  successful_parses += 1
252
- rescue StandardError
255
+ rescue StandardError => e
253
256
  parse_errors += 1
257
+ sample_error ||= "#{e.class}: #{e.message}"
254
258
  end
255
259
  end
256
260
  end
@@ -260,12 +264,14 @@ class PgObjectsBenchmark
260
264
  files_count: files.size,
261
265
  successful_parses: successful_parses,
262
266
  parse_errors: parse_errors,
267
+ sample_error: sample_error,
263
268
  throughput: files.size / result.real
264
269
  }
265
270
 
266
271
  puts "Parsed #{files.size} files in #{result.real.round(4)}s"
267
272
  puts "Successful parses: #{successful_parses}"
268
273
  puts "Parse errors: #{parse_errors}"
274
+ puts " Sample error: #{sample_error}" if sample_error
269
275
  puts "Throughput: #{(files.size / result.real).round(2)} files/second"
270
276
  puts "Average time per file: #{(result.real / files.size * 1000).round(2)}ms"
271
277
  puts
@@ -308,6 +314,8 @@ class PgObjectsBenchmark
308
314
  # Test the parsing workflow without database operations
309
315
  files = Dir[File.join(@temp_dir, '**', '*.sql')]
310
316
  db_objects = []
317
+ workflow_errors = 0
318
+ sample_error = nil
311
319
 
312
320
  result = Benchmark.measure do
313
321
  files.each do |file_path|
@@ -327,18 +335,23 @@ class PgObjectsBenchmark
327
335
  dependencies: dependencies,
328
336
  content: content
329
337
  }
330
- rescue StandardError
331
- # Count parse errors but continue
338
+ rescue StandardError => e
339
+ workflow_errors += 1
340
+ sample_error ||= "#{e.class}: #{e.message}"
332
341
  end
333
342
  end
334
343
 
335
344
  @results[:full_workflow] = {
336
345
  time: result.real,
337
346
  objects_count: db_objects.size,
347
+ errors: workflow_errors,
348
+ sample_error: sample_error,
338
349
  throughput: db_objects.size / result.real
339
350
  }
340
351
 
341
352
  puts "Processed #{db_objects.size} objects in #{result.real.round(4)}s"
353
+ puts "Errors: #{workflow_errors}"
354
+ puts " Sample error: #{sample_error}" if sample_error
342
355
  puts "Throughput: #{(db_objects.size / result.real).round(2)} objects/second"
343
356
  puts "Average time per object: #{(result.real / db_objects.size * 1000).round(2)}ms"
344
357
  puts
@@ -354,6 +367,8 @@ class PgObjectsBenchmark
354
367
  files = Dir[File.join(@temp_dir, '**', '*.sql')]
355
368
  parser = PgObjects::Parser.new
356
369
  parsed_objects = []
370
+ memory_errors = 0
371
+ sample_error = nil
357
372
 
358
373
  files.each do |file|
359
374
  content = File.read(file)
@@ -361,11 +376,15 @@ class PgObjectsBenchmark
361
376
  object_name = parser.load(content).fetch_object_name
362
377
  dependencies = parser.fetch_directives[:depends_on]
363
378
  parsed_objects << { file: file, name: object_name, deps: dependencies }
364
- rescue StandardError
365
- # Ignore parse errors for memory analysis
379
+ rescue StandardError => e
380
+ memory_errors += 1
381
+ sample_error ||= "#{e.class}: #{e.message}"
366
382
  end
367
383
  end
368
384
 
385
+ puts "Parse errors during memory analysis: #{memory_errors}" if memory_errors.positive?
386
+ puts " Sample error: #{sample_error}" if sample_error
387
+
369
388
  end_memory = memory_usage
370
389
  memory_diff = end_memory - start_memory
371
390
 
@@ -386,9 +405,25 @@ class PgObjectsBenchmark
386
405
  end
387
406
 
388
407
  def memory_usage
389
- `ps -o rss= -p #{Process.pid}`.to_i * 1024 # Convert KB to bytes
408
+ rss_from_proc_status || rss_from_ps || 0
409
+ end
410
+
411
+ # Linux: parse VmRSS from /proc/self/status (portable, no shell, locale-safe)
412
+ def rss_from_proc_status
413
+ return nil unless File.readable?('/proc/self/status')
414
+
415
+ kb = File.read('/proc/self/status')[/^VmRSS:\s+(\d+)\s+kB/, 1]
416
+ kb && (kb.to_i * 1024) # Convert KB to bytes
417
+ rescue StandardError
418
+ nil
419
+ end
420
+
421
+ # Portable fallback (e.g. macOS): RSS in KB via ps
422
+ def rss_from_ps
423
+ kb = `ps -o rss= -p #{Process.pid}`.strip
424
+ kb.empty? ? nil : kb.to_i * 1024
390
425
  rescue StandardError
391
- 0
426
+ nil
392
427
  end
393
428
 
394
429
  def format_memory(bytes)
data/bin/console CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'bundler/setup'
4
5
  require 'pg_objects'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ##
2
4
  # Creates default directories structure
3
5
  class PgObjects::InstallGenerator < Rails::Generators::Base
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'yaml_configurable'
2
4
 
3
5
  module PgObjects
@@ -10,7 +12,7 @@ module PgObjects
10
12
  # # or full
11
13
  # config.after_path = '/var/tmp/alternate/after'
12
14
  # config.extensions = ['sql', 'txt']
13
- # # suppress output to console
15
+ # # suppress non-error output to console
14
16
  # config.silent = true
15
17
  # end
16
18
  class << self
@@ -32,6 +34,55 @@ module PgObjects
32
34
  setting :extensions, default: ['sql']
33
35
  setting :silent, default: false
34
36
 
35
- load_from_yaml 'config/pg_objects.yml'
37
+ # Wraps each create_objects run in a database transaction so a failure
38
+ # mid-run rolls back every object created in that run. Set to false to
39
+ # execute statements without a wrapping transaction — required when object
40
+ # files contain statements PostgreSQL rejects inside a transaction block
41
+ # (e.g. CREATE INDEX CONCURRENTLY, VACUUM).
42
+ setting :transactional, default: true
43
+
44
+ # Master switch for the Rake hooks. When false, no hooks are installed and
45
+ # objects are created only by invoking db:create_objects:before / :after
46
+ # manually. Default true for backward compatibility.
47
+ setting :auto_hook_migrations, default: true
48
+
49
+ # Rake tasks that trigger object creation, mapped to the stages they run.
50
+ # +:before+ creates objects from the "before" folder ahead of the task,
51
+ # +:after+ creates objects from the "after" folder once the task finishes.
52
+ # Override (or empty) this to opt out. db:rollback is intentionally absent
53
+ # (a rollback should not recreate objects).
54
+ setting :hook_tasks, default: {
55
+ 'db:migrate' => %i[before after],
56
+ 'db:schema:load' => %i[after],
57
+ 'db:migrate:redo' => %i[before after]
58
+ }
59
+
60
+ DEFAULT_YAML_PATH = 'config/pg_objects.yml'
61
+
62
+ class << self
63
+ # YAML loading is deferred to the first config access so the path is
64
+ # resolved against Rails.root (when available) instead of the
65
+ # require-time working directory — preloaders like Spring may require
66
+ # the gem before the process chdirs into the app root.
67
+ def config
68
+ ensure_yaml_loaded
69
+ super
70
+ end
71
+
72
+ private
73
+
74
+ def ensure_yaml_loaded
75
+ return if @yaml_loaded
76
+
77
+ @yaml_loaded = true
78
+ load_from_yaml(yaml_config_path)
79
+ end
80
+
81
+ def yaml_config_path
82
+ return DEFAULT_YAML_PATH unless defined?(Rails) && Rails.respond_to?(:root) && Rails.root
83
+
84
+ Rails.root.join(DEFAULT_YAML_PATH).to_s
85
+ end
86
+ end
36
87
  end
37
88
  end
@@ -1,16 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ##
2
4
  # Represents DB object as it is described in file
3
5
  #
4
6
  # [name] name of file without extension
5
7
  # [full_name] full pathname of file
6
8
  # [object_name] name of function, trigger etc. if it was successfully parsed, otherwise - nil
9
+ # [qualified_object_name] schema-qualified object name (+schema.name+) when a schema is
10
+ # present, otherwise same as object_name; nil if parsing failed
7
11
  class PgObjects::DbObject
8
12
  include Memery
9
13
 
10
14
  include Import['parser']
11
15
 
12
16
  attr_accessor :status
13
- attr_reader :full_name, :object_name
17
+ attr_reader :full_name, :object_name, :qualified_object_name
14
18
 
15
19
  def initialize(path, status = :new, parser:)
16
20
  @full_name = path
@@ -21,6 +25,7 @@ class PgObjects::DbObject
21
25
  def create
22
26
  parser.load(sql_query)
23
27
  @object_name = parser.fetch_object_name
28
+ @qualified_object_name = parser.fetch_qualified_object_name
24
29
  @status = :pending
25
30
 
26
31
  self
@@ -3,11 +3,19 @@
3
3
  ##
4
4
  # Factory for DbObject
5
5
  #
6
+ # Each DbObject gets its own Parser instance so the parser's mutable `@source`
7
+ # is never shared between objects. The parser class is
8
+ # injectable so callers/tests can substitute an alternate parser.
6
9
  class PgObjects::DbObjectFactory
7
- include Import['parser']
10
+ def initialize(parser_class: PgObjects::Parser)
11
+ @parser_class = parser_class
12
+ end
8
13
 
9
14
  def create_instance(path, status: :new)
10
- db_object = PgObjects::DbObject.new(path, status, parser:)
11
- db_object.create
15
+ PgObjects::DbObject.new(path, status, parser: parser_class.new).create
12
16
  end
17
+
18
+ private
19
+
20
+ attr_reader :parser_class
13
21
  end
@@ -1,10 +1,41 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ##
2
- # Console output
3
- #
4
+ # Console output with severity levels. Writes to the injected +stream+
5
+ # (default +$stdout+). With +config.silent+ enabled, +info+/+warn+ messages
6
+ # are suppressed; +error+ messages are always written.
4
7
  class PgObjects::Logger
5
8
  include Import['config']
6
9
 
10
+ def initialize(stream: $stdout, **deps)
11
+ super(**deps)
12
+ @stream = stream
13
+ end
14
+
15
+ # Backward-compatible entry point; behaves like +info+.
7
16
  def write(str)
8
- puts "== #{str} ".ljust(80, '=') unless config.silent
17
+ log(str)
18
+ end
19
+
20
+ def info(str)
21
+ log(str)
22
+ end
23
+
24
+ def warn(str)
25
+ log("[WARN] #{str}")
26
+ end
27
+
28
+ def error(str)
29
+ log("[ERROR] #{str}", force: true)
30
+ end
31
+
32
+ private
33
+
34
+ attr_reader :stream
35
+
36
+ def log(str, force: false)
37
+ return if config.silent && !force
38
+
39
+ stream.puts "== #{str} ".ljust(80, '=')
9
40
  end
10
41
  end
@@ -1,23 +1,39 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ##
2
4
  # Manages process to create objects
3
5
  #
4
- # Usage:
6
+ # Usage (dependencies are auto-injected, keyword overrides optional):
5
7
  #
6
- # Manager.new(config, logger).load_files(:before).create_objects
8
+ # Manager.new.load_files(:before).create_objects
7
9
  #
8
10
  # or
9
11
  #
10
- # Manager.new(config, logger).load_files(:after).create_objects
12
+ # Manager.new(config: custom_config, logger: custom_logger).load_files(:after).create_objects
13
+ #
14
+ # Pass +connection:+ to run against a specific database connection instead of
15
+ # the global one (Rails 6+ multi-DB):
16
+ #
17
+ # Manager.new(connection: AnimalsRecord.connection).load_files(:before).create_objects
11
18
  class PgObjects::Manager
12
19
  include Import['db_object_factory', 'config', 'logger']
13
20
 
21
+ def initialize(connection: nil, **deps)
22
+ super(**deps)
23
+ @connection = connection
24
+ end
25
+
14
26
  ##
15
27
  # event: +:before+ or +:after+
16
28
  #
17
29
  # used to reference configuration settings +before_path+ and +after_path+
30
+ #
31
+ # Resets the object list before loading, so each call reflects only the
32
+ # files for the given event.
18
33
  def load_files(event)
19
34
  validate_workability
20
35
 
36
+ objects.clear
21
37
  dir = config.send "#{event}_path"
22
38
  Dir[File.join(dir, '**', "*.{#{config.extensions.join(',')}}")].each do |path|
23
39
  objects << db_object_factory.create_instance(path)
@@ -27,7 +43,8 @@ class PgObjects::Manager
27
43
  end
28
44
 
29
45
  def create_objects
30
- objects.each { create_object(_1) }
46
+ build_objects_index
47
+ within_transaction { objects.each { |obj| create_object(obj) } }
31
48
  end
32
49
 
33
50
  def objects
@@ -36,34 +53,68 @@ class PgObjects::Manager
36
53
 
37
54
  private
38
55
 
56
+ def connection
57
+ @connection || ActiveRecord::Base.connection
58
+ end
59
+
39
60
  def validate_workability
40
- raise PgObjects::UnsupportedAdapterError if ActiveRecord::Base.connection.adapter_name != 'PostgreSQL'
61
+ raise PgObjects::UnsupportedAdapterError if connection.adapter_name != 'PostgreSQL'
62
+ end
63
+
64
+ def within_transaction(&)
65
+ return yield unless config.transactional
66
+
67
+ connection.transaction(&)
41
68
  end
42
69
 
43
- def create_object(obj)
70
+ def create_object(obj, stack = [])
44
71
  return if obj.status == :done
45
- raise PgObjects::CyclicDependencyError, obj.name if obj.status == :processing
72
+ raise PgObjects::CyclicDependencyError, cycle_path(obj, stack) if obj.status == :processing
46
73
 
47
74
  obj.status = :processing
48
75
 
49
- create_dependencies(obj.dependencies)
76
+ create_dependencies(obj, stack + [obj.name])
50
77
 
51
78
  logger.write("creating #{obj.name}")
52
- ActiveRecord::Base.connection.execute(obj.sql_query)
79
+ connection.execute(obj.sql_query)
53
80
 
54
81
  obj.status = :done
55
82
  end
56
83
 
57
- def create_dependencies(dependencies)
58
- dependencies.each { |dep_name| create_object(find_object(dep_name)) }
84
+ def create_dependencies(obj, stack)
85
+ obj.dependencies.each { |dep_name| create_object(find_object(dep_name, obj), stack) }
59
86
  end
60
87
 
61
- def find_object(dep_name)
62
- result = objects.select { |obj| [obj.name, obj.full_name, obj.object_name].compact.include? dep_name }
88
+ # Resolution chain that closed the cycle: from the first occurrence of the
89
+ # revisited object down to the point of revisit, closed with the object
90
+ # itself (e.g. ["a", "b", "a"]).
91
+ def cycle_path(obj, stack)
92
+ first_seen = stack.index(obj.name)
93
+ (first_seen ? stack[first_seen..] : stack) + [obj.name]
94
+ end
63
95
 
64
- raise PgObjects::AmbiguousDependencyError, dep_name if result.size > 1
65
- raise PgObjects::DependencyNotExistError, dep_name if result.empty?
96
+ def build_objects_index
97
+ @objects_index = objects.each_with_object({}) do |obj, index|
98
+ [obj.name, obj.full_name, obj.object_name, obj.qualified_object_name].compact.uniq.each do |key|
99
+ (index[key] ||= []) << obj
100
+ end
101
+ end
102
+ end
103
+
104
+ def find_object(dep_name, referrer = nil)
105
+ result = @objects_index[dep_name] || []
106
+
107
+ raise ambiguous_error(dep_name, result, referrer) if result.size > 1
108
+ raise PgObjects::DependencyNotExistError.new(dep_name, referrer: referrer&.full_name) if result.empty?
66
109
 
67
110
  result[0]
68
111
  end
112
+
113
+ def ambiguous_error(dep_name, result, referrer)
114
+ PgObjects::AmbiguousDependencyError.new(
115
+ dep_name,
116
+ candidates: result.map(&:full_name),
117
+ referrer: referrer&.full_name
118
+ )
119
+ end
69
120
  end
@@ -1,8 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #
2
4
  # AGGREGATE object representation
3
5
  #
4
6
  class PgObjects::ParsedObject::Aggregate < PgObjects::ParsedObject::Base
5
7
  def name
6
- stmt.define_stmt.defnames[0].string.sval
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,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #
2
4
  # Base class for parsed objects
3
5
  #
@@ -10,7 +12,43 @@ class PgObjects::ParsedObject::Base
10
12
  raise NotImplementedError
11
13
  end
12
14
 
15
+ # Schema-qualified name (+schema.name+) when a schema is present, otherwise
16
+ # the same as +name+. Used to disambiguate same-named objects across schemas.
17
+ def qualified_name
18
+ schema_name = schema
19
+ schema_name && !schema_name.empty? ? "#{schema_name}.#{name}" : name
20
+ end
21
+
13
22
  private
14
23
 
15
24
  attr_reader :stmt
25
+
26
+ # Schema of the object, or nil when unqualified. Subclasses that carry a
27
+ # schema override this; the rest inherit the unqualified default.
28
+ def schema
29
+ nil
30
+ end
31
+
32
+ # Returns the schema element of a pg_query name list (e.g. funcname,
33
+ # defnames) when the name is qualified (+schema.name+), otherwise nil.
34
+ def qualifier(list)
35
+ list[-2].string.sval if list.size > 1
36
+ end
37
+
38
+ # Wraps name extraction so a malformed statement (nil field, empty array)
39
+ # surfaces an explicit error with context. Only NoMethodErrors from
40
+ # dereferencing nil are wrapped; genuine programming errors propagate.
41
+ def extract_name
42
+ yield
43
+ rescue NoMethodError => e
44
+ raise unless nil_dereference?(e)
45
+
46
+ raise PgObjects::MalformedStatementError, "malformed statement for #{self.class}: #{e.message}"
47
+ end
48
+
49
+ def nil_dereference?(error)
50
+ error.receiver.nil?
51
+ rescue ArgumentError
52
+ false
53
+ end
16
54
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Base (scalar) TYPE object representation (CREATE TYPE with I/O functions or
5
+ # a shell CREATE TYPE without attributes) — parsed as a define_stmt.
6
+ #
7
+ class PgObjects::ParsedObject::BaseType < PgObjects::ParsedObject::Base
8
+ def name
9
+ extract_name { stmt.define_stmt.defnames.last.string.sval }
10
+ end
11
+
12
+ private
13
+
14
+ def schema
15
+ extract_name { qualifier(stmt.define_stmt.defnames) }
16
+ end
17
+ end
@@ -1,8 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #
2
4
  # CONVERSION object representation
3
5
  #
4
6
  class PgObjects::ParsedObject::Conversion < PgObjects::ParsedObject::Base
5
7
  def name
6
- stmt.create_conversion_stmt.conversion_name[0].string.sval
8
+ extract_name { stmt.create_conversion_stmt.conversion_name.last.string.sval }
9
+ end
10
+
11
+ private
12
+
13
+ def schema
14
+ extract_name { qualifier(stmt.create_conversion_stmt.conversion_name) }
7
15
  end
8
16
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # DOMAIN object representation (CREATE DOMAIN)
5
+ #
6
+ class PgObjects::ParsedObject::Domain < PgObjects::ParsedObject::Base
7
+ def name
8
+ extract_name { stmt.create_domain_stmt.domainname.last.string.sval }
9
+ end
10
+
11
+ private
12
+
13
+ def schema
14
+ extract_name { qualifier(stmt.create_domain_stmt.domainname) }
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # ENUM TYPE object representation (CREATE TYPE ... AS ENUM)
5
+ #
6
+ class PgObjects::ParsedObject::EnumType < PgObjects::ParsedObject::Base
7
+ def name
8
+ extract_name { stmt.create_enum_stmt.type_name.last.string.sval }
9
+ end
10
+
11
+ private
12
+
13
+ def schema
14
+ extract_name { qualifier(stmt.create_enum_stmt.type_name) }
15
+ end
16
+ end
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #
2
4
  # EVENT TRIGGER object representation
3
5
  #
4
6
  class PgObjects::ParsedObject::EventTrigger < PgObjects::ParsedObject::Base
5
7
  def name
6
- stmt.create_event_trig_stmt.trigname
8
+ extract_name { stmt.create_event_trig_stmt.trigname }
7
9
  end
8
10
  end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # EXTENSION object representation
5
+ #
6
+ class PgObjects::ParsedObject::Extension < PgObjects::ParsedObject::Base
7
+ def name
8
+ extract_name { stmt.create_extension_stmt.extname }
9
+ end
10
+ end
@@ -1,8 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #
2
4
  # FUNCTION object representation
3
5
  #
4
6
  class PgObjects::ParsedObject::Function < PgObjects::ParsedObject::Base
5
7
  def name
6
- stmt.create_function_stmt.funcname[0].string.sval
8
+ extract_name { stmt.create_function_stmt.funcname.last.string.sval }
9
+ end
10
+
11
+ private
12
+
13
+ def schema
14
+ extract_name { qualifier(stmt.create_function_stmt.funcname) }
7
15
  end
8
16
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # INDEX object representation
5
+ #
6
+ class PgObjects::ParsedObject::Index < PgObjects::ParsedObject::Base
7
+ def name
8
+ extract_name { stmt.index_stmt.idxname }
9
+ end
10
+
11
+ private
12
+
13
+ # An index lives in the schema of the table it indexes.
14
+ def schema
15
+ extract_name { stmt.index_stmt.relation.schemaname }
16
+ end
17
+ end
@@ -1,8 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #
2
4
  # MATERIALIZED VIEW object representation
3
5
  #
4
6
  class PgObjects::ParsedObject::MaterializedView < PgObjects::ParsedObject::Base
5
7
  def name
6
- stmt.create_table_as_stmt.into.rel.relname
8
+ extract_name { stmt.create_table_as_stmt.into.rel.relname }
9
+ end
10
+
11
+ private
12
+
13
+ def schema
14
+ extract_name { stmt.create_table_as_stmt.into.rel.schemaname }
7
15
  end
8
16
  end
@@ -1,8 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #
2
4
  # OPERATOR object representation
3
5
  #
4
6
  class PgObjects::ParsedObject::Operator < PgObjects::ParsedObject::Base
5
7
  def name
6
- stmt.define_stmt.defnames[0].string.sval
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