pg_aggregates 0.2.1 → 0.2.3
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/.ruby-version +1 -1
- data/Appraisals +6 -0
- data/gemfiles/rails_6.1.gemfile +7 -4
- data/gemfiles/rails_6.1.gemfile.lock +74 -51
- data/gemfiles/rails_7.0.gemfile +6 -3
- data/gemfiles/rails_7.0.gemfile.lock +82 -74
- data/gemfiles/rails_7.1.gemfile +6 -3
- data/gemfiles/rails_7.1.gemfile.lock +101 -86
- data/gemfiles/rails_8.0.gemfile +25 -0
- data/gemfiles/rails_8.0.gemfile.lock +239 -0
- data/lib/pg_aggregates/railtie.rb +2 -4
- data/lib/pg_aggregates/schema_dumper.rb +68 -28
- data/lib/pg_aggregates/schema_statements.rb +71 -0
- data/lib/pg_aggregates/version.rb +1 -1
- data/lib/pg_aggregates.rb +20 -4
- metadata +5 -6
@@ -2,9 +2,18 @@
|
|
2
2
|
|
3
3
|
module PgAggregates
|
4
4
|
module SchemaStatements
|
5
|
+
# Reserved words to skip when checking function dependencies
|
6
|
+
RESERVED_WORDS = ["public"].freeze
|
7
|
+
|
5
8
|
def create_aggregate(name, version: nil, sql_definition: nil)
|
6
9
|
raise ArgumentError, "Must provide either sql_definition or version" if sql_definition.nil? && version.nil?
|
7
10
|
|
11
|
+
# First, check if the function already exists to avoid duplicate creation attempts
|
12
|
+
return if aggregate_exists?(name)
|
13
|
+
|
14
|
+
# Check if dependent functions exist before attempting to create
|
15
|
+
check_dependent_functions(sql_definition || read_aggregate_definition(name, version))
|
16
|
+
|
8
17
|
if sql_definition
|
9
18
|
execute sql_definition
|
10
19
|
else
|
@@ -18,6 +27,14 @@ module PgAggregates
|
|
18
27
|
|
19
28
|
execute aggregate_definition.to_sql
|
20
29
|
end
|
30
|
+
rescue ActiveRecord::StatementInvalid => e
|
31
|
+
raise unless /function .* does not exist/.match?(e.message)
|
32
|
+
|
33
|
+
puts "WARNING: Failed to create aggregate #{name} because a required function does not exist."
|
34
|
+
puts " This could indicate a dependency ordering issue."
|
35
|
+
puts " Error: #{e.message}"
|
36
|
+
|
37
|
+
raise
|
21
38
|
end
|
22
39
|
|
23
40
|
def drop_aggregate(name, *arg_types, force: false)
|
@@ -25,5 +42,59 @@ module PgAggregates
|
|
25
42
|
force_clause = force ? " CASCADE" : ""
|
26
43
|
execute "DROP AGGREGATE IF EXISTS #{name}#{arg_types_sql}#{force_clause}"
|
27
44
|
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def aggregate_exists?(name)
|
49
|
+
sql = <<-SQL
|
50
|
+
SELECT 1
|
51
|
+
FROM pg_catalog.pg_proc p
|
52
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
|
53
|
+
WHERE p.proname = '#{name}'
|
54
|
+
AND p.prokind = 'a'
|
55
|
+
SQL
|
56
|
+
|
57
|
+
result = execute(sql)
|
58
|
+
result.any?
|
59
|
+
rescue StandardError
|
60
|
+
false
|
61
|
+
end
|
62
|
+
|
63
|
+
def read_aggregate_definition(name, version)
|
64
|
+
aggregate_definition = PgAggregates::AggregateDefinition.new(name, version: version)
|
65
|
+
File.read(aggregate_definition.path) if File.exist?(aggregate_definition.path)
|
66
|
+
end
|
67
|
+
|
68
|
+
def check_dependent_functions(sql_definition)
|
69
|
+
return unless sql_definition
|
70
|
+
|
71
|
+
# Extract function names mentioned in the aggregate definition
|
72
|
+
# Be careful to extract only the function name, not schema qualifiers
|
73
|
+
function_matches = sql_definition.scan(/sfunc\s*=\s*['"]?(?:(?:[a-zA-Z0-9_]+\.)?([a-zA-Z0-9_]+))['"]?/i)
|
74
|
+
function_names = function_matches.flatten.compact.uniq
|
75
|
+
|
76
|
+
# For each referenced function, check if it exists
|
77
|
+
function_names.each do |function_name|
|
78
|
+
# Skip if function_name is empty or a reserved word
|
79
|
+
next if function_name.nil? || function_name.empty? || RESERVED_WORDS.include?(function_name.downcase)
|
80
|
+
|
81
|
+
check_sql = <<-SQL
|
82
|
+
SELECT 1#{" "}
|
83
|
+
FROM pg_catalog.pg_proc p
|
84
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
|
85
|
+
WHERE p.proname = '#{function_name}'
|
86
|
+
AND n.nspname = 'public'
|
87
|
+
SQL
|
88
|
+
|
89
|
+
result = execute(check_sql)
|
90
|
+
unless result.any?
|
91
|
+
puts "WARNING: Aggregate depends on function '#{function_name}' which does not exist"
|
92
|
+
puts " This will likely fail. Create the function first."
|
93
|
+
end
|
94
|
+
end
|
95
|
+
rescue StandardError => e
|
96
|
+
# Log the error but continue
|
97
|
+
puts "WARNING: Error checking function dependencies: #{e.message}"
|
98
|
+
end
|
28
99
|
end
|
29
100
|
end
|
data/lib/pg_aggregates.rb
CHANGED
@@ -10,11 +10,27 @@ require_relative "pg_aggregates/schema_dumper"
|
|
10
10
|
require_relative "pg_aggregates/railtie"
|
11
11
|
|
12
12
|
module PgAggregates
|
13
|
+
module_function
|
14
|
+
|
13
15
|
class Error < StandardError; end
|
14
16
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
17
|
+
def load
|
18
|
+
# This is crucial - we must ensure proper load order:
|
19
|
+
# 1. extensions
|
20
|
+
# 2. types
|
21
|
+
# 3. functions
|
22
|
+
# 4. aggregates
|
23
|
+
# 5. tables
|
24
|
+
|
25
|
+
# Add schema statements and command recorder
|
26
|
+
ActiveRecord::ConnectionAdapters::AbstractAdapter.include PgAggregates::SchemaStatements
|
27
|
+
ActiveRecord::Migration::CommandRecorder.include PgAggregates::CommandRecorder
|
28
|
+
|
29
|
+
# Hook into the schema dumper, with dependency awareness
|
30
|
+
ActiveRecord::SchemaDumper.prepend PgAggregates::SchemaDumper
|
31
|
+
end
|
32
|
+
|
33
|
+
def database
|
34
|
+
ActiveRecord::Base.connection
|
19
35
|
end
|
20
36
|
end
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pg_aggregates
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- mhenrixon
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: activerecord
|
@@ -72,6 +71,8 @@ files:
|
|
72
71
|
- gemfiles/rails_7.0.gemfile.lock
|
73
72
|
- gemfiles/rails_7.1.gemfile
|
74
73
|
- gemfiles/rails_7.1.gemfile.lock
|
74
|
+
- gemfiles/rails_8.0.gemfile
|
75
|
+
- gemfiles/rails_8.0.gemfile.lock
|
75
76
|
- lib/generators/pg/aggregate/aggregate_generator.rb
|
76
77
|
- lib/generators/pg/aggregate/templates/aggregate.sql.erb
|
77
78
|
- lib/generators/pg/aggregate/templates/migration.rb.erb
|
@@ -92,7 +93,6 @@ metadata:
|
|
92
93
|
source_code_uri: https://github.com/mhenrixon/pg_aggregates
|
93
94
|
changelog_uri: https://github.com/mhenrixon/pg_aggregates/blob/main/CHANGELOG.md
|
94
95
|
rubygems_mfa_required: 'true'
|
95
|
-
post_install_message:
|
96
96
|
rdoc_options: []
|
97
97
|
require_paths:
|
98
98
|
- lib
|
@@ -107,8 +107,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
107
107
|
- !ruby/object:Gem::Version
|
108
108
|
version: '0'
|
109
109
|
requirements: []
|
110
|
-
rubygems_version: 3.
|
111
|
-
signing_key:
|
110
|
+
rubygems_version: 3.6.7
|
112
111
|
specification_version: 4
|
113
112
|
summary: Rails integration for PostgreSQL aggregate functions
|
114
113
|
test_files: []
|