torque-postgresql 4.1.0 → 4.1.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.
- checksums.yaml +4 -4
- data/Rakefile +28 -0
- data/lib/generators/torque/skill_generator.rb +17 -0
- data/lib/generators/torque/templates/trigger.sql.erb +3 -0
- data/lib/generators/torque/trigger_generator.rb +13 -0
- data/lib/torque/postgresql/adapter/database_statements.rb +9 -0
- data/lib/torque/postgresql/adapter/oid/array.rb +0 -4
- data/lib/torque/postgresql/adapter/oid/composite.rb +2 -1
- data/lib/torque/postgresql/adapter/oid/enum.rb +1 -1
- data/lib/torque/postgresql/adapter/oid/enum_set.rb +2 -2
- data/lib/torque/postgresql/adapter/oid/interval.rb +1 -1
- data/lib/torque/postgresql/adapter/oid/lquery.rb +45 -28
- data/lib/torque/postgresql/adapter/oid/ltree.rb +67 -9
- data/lib/torque/postgresql/adapter/oid/struct.rb +1 -1
- data/lib/torque/postgresql/adapter/oid.rb +13 -0
- data/lib/torque/postgresql/adapter/schema_definitions.rb +5 -0
- data/lib/torque/postgresql/adapter/schema_dumper.rb +1 -0
- data/lib/torque/postgresql/arel/nodes.rb +51 -0
- data/lib/torque/postgresql/arel/visitors.rb +46 -0
- data/lib/torque/postgresql/attributes/base.rb +10 -7
- data/lib/torque/postgresql/attributes/composite.rb +1 -5
- data/lib/torque/postgresql/attributes/enum.rb +1 -6
- data/lib/torque/postgresql/attributes/lquery.rb +67 -127
- data/lib/torque/postgresql/attributes/ltree.rb +35 -83
- data/lib/torque/postgresql/base.rb +9 -1
- data/lib/torque/postgresql/config.rb +21 -3
- data/lib/torque/postgresql/geometry_builder.rb +4 -10
- data/lib/torque/postgresql/inheritance.rb +5 -5
- data/lib/torque/postgresql/predicate_builder/arel_attribute_handler.rb +1 -1
- data/lib/torque/postgresql/predicate_builder/array_handler.rb +16 -8
- data/lib/torque/postgresql/predicate_builder/composite_handler.rb +18 -5
- data/lib/torque/postgresql/predicate_builder/ltree_handler.rb +86 -18
- data/lib/torque/postgresql/predicate_builder/struct_handler.rb +29 -9
- data/lib/torque/postgresql/predicate_builder.rb +38 -11
- data/lib/torque/postgresql/railtie.rb +17 -0
- data/lib/torque/postgresql/relation/auxiliary_statement.rb +7 -3
- data/lib/torque/postgresql/relation/buckets.rb +2 -1
- data/lib/torque/postgresql/relation/cross_join.rb +193 -0
- data/lib/torque/postgresql/relation/inheritance.rb +1 -2
- data/lib/torque/postgresql/relation/join_series.rb +1 -0
- data/lib/torque/postgresql/relation/merger.rb +9 -0
- data/lib/torque/postgresql/relation/segments.rb +140 -0
- data/lib/torque/postgresql/relation.rb +23 -4
- data/lib/torque/postgresql/version.rb +1 -1
- data/lib/torque/postgresql/versioned_commands/command_migration.rb +23 -0
- data/lib/torque/postgresql/versioned_commands.rb +39 -0
- data/lib/torque/postgresql.rb +0 -2
- data/sig/generators/torque/function_generator.rbs +9 -0
- data/sig/generators/torque/skill_generator.rbs +7 -0
- data/sig/generators/torque/trigger_generator.rbs +9 -0
- data/sig/generators/torque/type_generator.rbs +9 -0
- data/sig/generators/torque/view_generator.rbs +9 -0
- data/sig/manifest.yaml +7 -0
- data/sig/torque/postgresql/adapter/database_statements.rbs +42 -0
- data/sig/torque/postgresql/adapter/inheritance_statements.rbs +36 -0
- data/sig/torque/postgresql/adapter/oid/array.rbs +20 -0
- data/sig/torque/postgresql/adapter/oid/box.rbs +28 -0
- data/sig/torque/postgresql/adapter/oid/circle.rbs +33 -0
- data/sig/torque/postgresql/adapter/oid/composite.rbs +35 -0
- data/sig/torque/postgresql/adapter/oid/enum.rbs +28 -0
- data/sig/torque/postgresql/adapter/oid/enum_set.rbs +19 -0
- data/sig/torque/postgresql/adapter/oid/interval.rbs +20 -0
- data/sig/torque/postgresql/adapter/oid/line.rbs +33 -0
- data/sig/torque/postgresql/adapter/oid/lquery.rbs +21 -0
- data/sig/torque/postgresql/adapter/oid/ltree.rbs +32 -0
- data/sig/torque/postgresql/adapter/oid/range.rbs +28 -0
- data/sig/torque/postgresql/adapter/oid/segment.rbs +38 -0
- data/sig/torque/postgresql/adapter/oid/struct.rbs +41 -0
- data/sig/torque/postgresql/adapter/oid/struct_list.rbs +20 -0
- data/sig/torque/postgresql/adapter/oid/struct_set.rbs +18 -0
- data/sig/torque/postgresql/adapter/oid.rbs +9 -0
- data/sig/torque/postgresql/adapter/quoting.rbs +20 -0
- data/sig/torque/postgresql/adapter/schema_creation.rbs +13 -0
- data/sig/torque/postgresql/adapter/schema_definitions.rbs +55 -0
- data/sig/torque/postgresql/adapter/schema_dumper.rbs +37 -0
- data/sig/torque/postgresql/adapter/schema_overrides.rbs +46 -0
- data/sig/torque/postgresql/adapter/schema_statements.rbs +50 -0
- data/sig/torque/postgresql/adapter.rbs +24 -0
- data/sig/torque/postgresql/arel/infix_operation.rbs +79 -0
- data/sig/torque/postgresql/arel/join_source.rbs +19 -0
- data/sig/torque/postgresql/arel/nodes.rbs +83 -0
- data/sig/torque/postgresql/arel/operations.rbs +28 -0
- data/sig/torque/postgresql/arel/select_manager.rbs +15 -0
- data/sig/torque/postgresql/arel/visitors.rbs +30 -0
- data/sig/torque/postgresql/arel.rbs +6 -0
- data/sig/torque/postgresql/associations/association_scope.rbs +28 -0
- data/sig/torque/postgresql/associations/belongs_to_many_association.rbs +51 -0
- data/sig/torque/postgresql/associations/builder/belongs_to_many.rbs +24 -0
- data/sig/torque/postgresql/associations/builder/has_many.rbs +15 -0
- data/sig/torque/postgresql/associations/builder.rbs +8 -0
- data/sig/torque/postgresql/associations/foreign_association.rbs +16 -0
- data/sig/torque/postgresql/associations/join_dependency.rbs +16 -0
- data/sig/torque/postgresql/associations/preloader/association.rbs +32 -0
- data/sig/torque/postgresql/associations/preloader/loader_query.rbs +20 -0
- data/sig/torque/postgresql/associations/preloader.rbs +8 -0
- data/sig/torque/postgresql/associations.rbs +17 -0
- data/sig/torque/postgresql/attributes/base.rbs +37 -0
- data/sig/torque/postgresql/attributes/builder/enum.rbs +38 -0
- data/sig/torque/postgresql/attributes/builder/full_text_search.rbs +27 -0
- data/sig/torque/postgresql/attributes/builder/period.rbs +106 -0
- data/sig/torque/postgresql/attributes/builder.rbs +19 -0
- data/sig/torque/postgresql/attributes/composite.rbs +28 -0
- data/sig/torque/postgresql/attributes/enum.rbs +69 -0
- data/sig/torque/postgresql/attributes/enum_set.rbs +75 -0
- data/sig/torque/postgresql/attributes/full_text_search.rbs +13 -0
- data/sig/torque/postgresql/attributes/lazy.rbs +17 -0
- data/sig/torque/postgresql/attributes/lquery.rbs +48 -0
- data/sig/torque/postgresql/attributes/ltree.rbs +46 -0
- data/sig/torque/postgresql/attributes/period.rbs +13 -0
- data/sig/torque/postgresql/attributes/simple_enum.rbs +39 -0
- data/sig/torque/postgresql/attributes/struct.rbs +38 -0
- data/sig/torque/postgresql/attributes.rbs +6 -0
- data/sig/torque/postgresql/autosave_association.rbs +16 -0
- data/sig/torque/postgresql/auxiliary_statement/recursive.rbs +22 -0
- data/sig/torque/postgresql/auxiliary_statement/settings.rbs +63 -0
- data/sig/torque/postgresql/auxiliary_statement.rbs +57 -0
- data/sig/torque/postgresql/base.rbs +41 -0
- data/sig/torque/postgresql/collector.rbs +7 -0
- data/sig/torque/postgresql/config.rbs +9 -0
- data/sig/torque/postgresql/function.rbs +21 -0
- data/sig/torque/postgresql/geometry_builder.rbs +20 -0
- data/sig/torque/postgresql/i18n.rbs +15 -0
- data/sig/torque/postgresql/inheritance/expander.rbs +23 -0
- data/sig/torque/postgresql/inheritance/record.rbs +20 -0
- data/sig/torque/postgresql/inheritance.rbs +47 -0
- data/sig/torque/postgresql/insert_all.rbs +22 -0
- data/sig/torque/postgresql/migration/command_recorder.rbs +30 -0
- data/sig/torque/postgresql/migration.rbs +6 -0
- data/sig/torque/postgresql/predicate_builder/arel_attribute_handler.rbs +15 -0
- data/sig/torque/postgresql/predicate_builder/array_handler.rbs +26 -0
- data/sig/torque/postgresql/predicate_builder/composite_handler.rbs +26 -0
- data/sig/torque/postgresql/predicate_builder/enumerator_lazy_handler.rbs +18 -0
- data/sig/torque/postgresql/predicate_builder/ltree_handler.rbs +36 -0
- data/sig/torque/postgresql/predicate_builder/regexp_handler.rbs +16 -0
- data/sig/torque/postgresql/predicate_builder/struct_handler.rbs +24 -0
- data/sig/torque/postgresql/predicate_builder.rbs +23 -0
- data/sig/torque/postgresql/predicate_table.rbs +24 -0
- data/sig/torque/postgresql/railtie.rbs +10 -0
- data/sig/torque/postgresql/reflection/abstract_reflection.rbs +22 -0
- data/sig/torque/postgresql/reflection/association_reflection.rbs +18 -0
- data/sig/torque/postgresql/reflection/belongs_to_many_reflection.rbs +30 -0
- data/sig/torque/postgresql/reflection/has_many_reflection.rbs +14 -0
- data/sig/torque/postgresql/reflection/runtime_reflection.rbs +18 -0
- data/sig/torque/postgresql/reflection/through_reflection.rbs +14 -0
- data/sig/torque/postgresql/reflection.rbs +11 -0
- data/sig/torque/postgresql/relation/auxiliary_statement.rbs +29 -0
- data/sig/torque/postgresql/relation/buckets.rbs +35 -0
- data/sig/torque/postgresql/relation/cross_join.rbs +63 -0
- data/sig/torque/postgresql/relation/distinct_on.rbs +16 -0
- data/sig/torque/postgresql/relation/inheritance.rbs +48 -0
- data/sig/torque/postgresql/relation/join_series.rbs +28 -0
- data/sig/torque/postgresql/relation/merger.rbs +23 -0
- data/sig/torque/postgresql/relation/segments.rbs +27 -0
- data/sig/torque/postgresql/relation.rbs +48 -0
- data/sig/torque/postgresql/schema_cache/bound_schema_reflection.rbs +14 -0
- data/sig/torque/postgresql/schema_cache/inheritance.rbs +12 -0
- data/sig/torque/postgresql/schema_cache/schema_reflection.rbs +14 -0
- data/sig/torque/postgresql/schema_cache.rbs +43 -0
- data/sig/torque/postgresql/table_name.rbs +20 -0
- data/sig/torque/postgresql/validations.rbs +13 -0
- data/sig/torque/postgresql/version.rbs +5 -0
- data/sig/torque/postgresql/versioned_commands/command_migration.rbs +54 -0
- data/sig/torque/postgresql/versioned_commands/generator.rbs +17 -0
- data/sig/torque/postgresql/versioned_commands/migration_context.rbs +30 -0
- data/sig/torque/postgresql/versioned_commands/migrator.rbs +19 -0
- data/sig/torque/postgresql/versioned_commands/schema_table.rbs +23 -0
- data/sig/torque/postgresql/versioned_commands.rbs +25 -0
- data/sig/torque/postgresql.rbs +26 -0
- data/skills/torque-postgresql/SKILL.md +70 -0
- data/spec/fixtures/migrations/20250101000008_create_trigger_audit_users_v1.sql +3 -0
- data/spec/initialize.rb +9 -0
- data/spec/mocks/cache_query.rb +1 -1
- data/spec/rbs_helper.rb +8 -0
- data/spec/schema.rb +2 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/tests/arel_spec.rb +1 -3
- data/spec/tests/auxiliary_statement_spec.rb +12 -0
- data/spec/tests/buckets_spec.rb +122 -0
- data/spec/tests/composite_spec.rb +67 -0
- data/spec/tests/cross_join_spec.rb +185 -0
- data/spec/tests/distinct_on_spec.rb +5 -0
- data/spec/tests/enum_set_spec.rb +4 -0
- data/spec/tests/enum_spec.rb +20 -4
- data/spec/tests/geometric_builder_spec.rb +16 -5
- data/spec/tests/has_many_spec.rb +2 -1
- data/spec/tests/interval_spec.rb +6 -0
- data/spec/tests/ltree_spec.rb +398 -64
- data/spec/tests/predicate_builder_spec.rb +120 -0
- data/spec/tests/relation_spec.rb +25 -69
- data/spec/tests/segments_spec.rb +264 -0
- data/spec/tests/struct_spec.rb +202 -0
- data/spec/tests/table_inheritance_spec.rb +10 -0
- data/spec/tests/versioned_commands_spec.rb +145 -0
- metadata +153 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c79e4ae9bb15bd5111bdd7f99e93b2be6aeb8dbe8446005f1692b3d419a6a096
|
|
4
|
+
data.tar.gz: fc41a9e09f887d8d6d9dd3920e2bc3b2d51e3b17af48b76e875c37d7a37a992a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7b98f253f6b1b47a1ed18a07e1850793b127847b144a0a75b61f3a96a439b6473117ab769f5a17db17820bd498a81d7adf06f73222c48020fff0b6505fa210e2
|
|
7
|
+
data.tar.gz: eeb9cbb8dfff58d3d3836e5e7f8199fbf77a7048b387dbde41de77a98ff1254784cf02f0c702d6513c4623b7724da8f70e1829e9d16be26fd0d6f55a9e8cd046
|
data/Rakefile
CHANGED
|
@@ -56,3 +56,31 @@ end
|
|
|
56
56
|
require 'rspec/core/rake_task'
|
|
57
57
|
RSpec::Core::RakeTask.new(:spec)
|
|
58
58
|
task default: :spec
|
|
59
|
+
|
|
60
|
+
RBS_TEST_ENV = {
|
|
61
|
+
'RBS_TEST' => '1',
|
|
62
|
+
'RBS_TEST_TARGET' => 'Torque::PostgreSQL::*,Torque::Generators::*',
|
|
63
|
+
'RBS_TEST_SKIP' => '::Torque::PostgreSQL::Attributes::Lazy,::Torque::PostgreSQL::Adapter::OID::Range::Comparison',
|
|
64
|
+
'RBS_TEST_OPT' => '-I sig',
|
|
65
|
+
'RBS_TEST_LOGLEVEL' => ENV.fetch('RBS_TEST_LOGLEVEL', 'warn'),
|
|
66
|
+
}.freeze
|
|
67
|
+
|
|
68
|
+
namespace :rbs do
|
|
69
|
+
desc 'Install the third-party signatures listed in rbs_collection.yaml'
|
|
70
|
+
task :collection do
|
|
71
|
+
sh 'bundle', 'exec', 'rbs', 'collection', 'install'
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
desc 'Validate sig/ against the collection and the local stubs'
|
|
75
|
+
task validate: :collection do
|
|
76
|
+
sh 'bundle', 'exec', 'rbs', '-I', 'sig', 'validate', '--exit-error-on-syntax-error'
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
desc 'Run the spec suite with runtime type checking of every signature'
|
|
80
|
+
task test: :collection do
|
|
81
|
+
sh RBS_TEST_ENV, 'bundle', 'exec', 'rspec'
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
desc 'Validate the signatures and run the typed spec suite'
|
|
86
|
+
task rbs: %w[rbs:validate rbs:test]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails/generators/base'
|
|
4
|
+
|
|
5
|
+
module Torque
|
|
6
|
+
module Generators
|
|
7
|
+
class SkillGenerator < Rails::Generators::Base
|
|
8
|
+
source_root File.expand_path('../../../skills', __dir__)
|
|
9
|
+
|
|
10
|
+
desc 'Copies the torque-postgresql agent skill into .claude/skills'
|
|
11
|
+
|
|
12
|
+
def copy_skill
|
|
13
|
+
directory 'torque-postgresql', '.claude/skills/torque-postgresql'
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'torque/postgresql/versioned_commands/generator'
|
|
4
|
+
|
|
5
|
+
module Torque
|
|
6
|
+
module Generators
|
|
7
|
+
class TriggerGenerator < Rails::Generators::Base
|
|
8
|
+
include Torque::PostgreSQL::VersionedCommands::Generator
|
|
9
|
+
|
|
10
|
+
alias create_trigger_file create_migration_file
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -253,6 +253,15 @@ module Torque
|
|
|
253
253
|
WHERE 1=1 AND c.relkind IN ('v', 'm')
|
|
254
254
|
AND #{filter_by_schema.join(' AND ')};
|
|
255
255
|
SQL
|
|
256
|
+
when :trigger
|
|
257
|
+
<<-SQL.squish
|
|
258
|
+
SELECT n.nspname AS schema, t.tgname AS name
|
|
259
|
+
FROM pg_trigger t
|
|
260
|
+
INNER JOIN pg_class c ON c.oid = t.tgrelid
|
|
261
|
+
INNER JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
262
|
+
WHERE 1=1 AND NOT t.tgisinternal
|
|
263
|
+
AND #{filter_by_schema.join(' AND ')};
|
|
264
|
+
SQL
|
|
256
265
|
end
|
|
257
266
|
|
|
258
267
|
select_rows(query, 'SCHEMA')
|
|
@@ -11,10 +11,6 @@ module Torque
|
|
|
11
11
|
|
|
12
12
|
private
|
|
13
13
|
|
|
14
|
-
# A label path is an Array of labels, so the regular recursion would
|
|
15
|
-
# take each of its labels as another dimension of the column. Paths
|
|
16
|
-
# are a single value to PostgreSQL, which means that the entries of
|
|
17
|
-
# the array are never a dimension of their own
|
|
18
14
|
def type_cast_array(value, method)
|
|
19
15
|
return super unless path_subtype?
|
|
20
16
|
return subtype.public_send(method, value) unless value.is_a?(::Array)
|
|
@@ -13,8 +13,9 @@ module Torque
|
|
|
13
13
|
attr_reader :name
|
|
14
14
|
|
|
15
15
|
# The composite type behind the given type, if there is one, wrapped
|
|
16
|
-
# or not by an array
|
|
16
|
+
# or not by an array or a delegator
|
|
17
17
|
def self.from(type)
|
|
18
|
+
type = OID.unwrap(type)
|
|
18
19
|
type = type.subtype if type.is_a?(ARRAY)
|
|
19
20
|
type if type.is_a?(self)
|
|
20
21
|
end
|
|
@@ -1,50 +1,67 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'ltree'
|
|
4
|
+
|
|
3
5
|
module Torque
|
|
4
6
|
module PostgreSQL
|
|
5
7
|
module Adapter
|
|
6
8
|
module OID
|
|
7
|
-
|
|
9
|
+
# Same as a path, but the items are the pure parts of a pattern: +:any+
|
|
10
|
+
# for a star, a Range for a quantified star and an Array for a group of
|
|
11
|
+
# alternatives. Everything else is a label, written as it appears in SQL
|
|
12
|
+
class Lquery < Ltree
|
|
13
|
+
STAR = /\A\*\{(?<min>\d+)?(?<comma>,)?(?<max>\d+)?\}\z/
|
|
8
14
|
|
|
9
15
|
def type
|
|
10
16
|
:lquery
|
|
11
17
|
end
|
|
12
18
|
|
|
13
|
-
|
|
14
|
-
return if value.blank?
|
|
15
|
-
return value if value.is_a?(LQuery)
|
|
19
|
+
private
|
|
16
20
|
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
def value_class
|
|
22
|
+
LQuery
|
|
23
|
+
end
|
|
19
24
|
|
|
20
|
-
|
|
21
|
-
|
|
25
|
+
def items_of(entry, sanitize)
|
|
26
|
+
case entry
|
|
27
|
+
when ::Symbol then entry == :any ? [:any] : super
|
|
28
|
+
when ::Range then [entry]
|
|
29
|
+
when ::Array then [entry.flat_map { |item| items_of(item, sanitize) }]
|
|
30
|
+
else super
|
|
31
|
+
end
|
|
32
|
+
end
|
|
22
33
|
|
|
23
|
-
|
|
24
|
-
|
|
34
|
+
def compile(item)
|
|
35
|
+
case item
|
|
36
|
+
when ::Symbol then item == :any ? '*' : item.to_s
|
|
37
|
+
when ::Range then quantifier(item)
|
|
38
|
+
when ::Array then item.map { |entry| compile(entry) }.join('|')
|
|
39
|
+
else item.to_s
|
|
40
|
+
end
|
|
41
|
+
end
|
|
25
42
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
43
|
+
def quantifier(range)
|
|
44
|
+
min = range.begin
|
|
45
|
+
max = range.end
|
|
46
|
+
max -= 1 if max && range.exclude_end?
|
|
29
47
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
end
|
|
48
|
+
return '*' if min.nil? && max.nil?
|
|
49
|
+
return "*{#{min}}" if min == max
|
|
33
50
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
end
|
|
51
|
+
"*{#{min},#{max}}"
|
|
52
|
+
end
|
|
37
53
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
other.is_a?(self.class)
|
|
42
|
-
end
|
|
43
|
-
alias eql? ==
|
|
54
|
+
def parse(text)
|
|
55
|
+
return :any if text == '*'
|
|
56
|
+
return text.split('|') if text.include?('|') && !text.match?(/[!{]/)
|
|
44
57
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
58
|
+
match = text.match(STAR)
|
|
59
|
+
return text if match.nil?
|
|
60
|
+
|
|
61
|
+
min = match[:min]&.to_i
|
|
62
|
+
max = match[:comma] ? match[:max]&.to_i : min
|
|
63
|
+
min..max
|
|
64
|
+
end
|
|
48
65
|
|
|
49
66
|
end
|
|
50
67
|
end
|
|
@@ -4,6 +4,9 @@ module Torque
|
|
|
4
4
|
module PostgreSQL
|
|
5
5
|
module Adapter
|
|
6
6
|
module OID
|
|
7
|
+
# Moves a label path between the shapes the gem accepts, its pure list
|
|
8
|
+
# of items and the text form PostgreSQL stores. Nothing is validated
|
|
9
|
+
# here, that is left to PostgreSQL
|
|
7
10
|
class Ltree < ActiveModel::Type::Value
|
|
8
11
|
|
|
9
12
|
def type
|
|
@@ -11,34 +14,37 @@ module Torque
|
|
|
11
14
|
end
|
|
12
15
|
|
|
13
16
|
def cast(value)
|
|
17
|
+
return value if value.is_a?(value_class)
|
|
14
18
|
return if value.blank?
|
|
15
|
-
return value if value.is_a?(LTree)
|
|
16
19
|
|
|
17
|
-
|
|
20
|
+
value_class.new(value)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def serialize(value)
|
|
24
|
+
path = cast(value)
|
|
25
|
+
path.map { |item| compile(item) }.join('.') unless path.nil?
|
|
18
26
|
end
|
|
19
27
|
|
|
20
28
|
def deserialize(value)
|
|
21
29
|
return if value.nil?
|
|
22
30
|
|
|
23
|
-
|
|
31
|
+
value_class.new(value, sanitize: false)
|
|
24
32
|
end
|
|
25
33
|
|
|
26
|
-
def
|
|
27
|
-
|
|
34
|
+
def items_for(value, sanitize: true)
|
|
35
|
+
::Array.wrap(value).flat_map { |entry| items_of(entry, sanitize) }
|
|
28
36
|
end
|
|
29
37
|
|
|
30
38
|
def type_cast_for_schema(value)
|
|
31
|
-
|
|
39
|
+
serialize(value).inspect
|
|
32
40
|
end
|
|
33
41
|
|
|
34
42
|
def changed_in_place?(raw_old_value, new_value)
|
|
35
43
|
raw_old_value != serialize(new_value)
|
|
36
44
|
end
|
|
37
45
|
|
|
38
|
-
# Types resolved through the type map are deduplicated and frozen, so
|
|
39
|
-
# they need to be comparable
|
|
40
46
|
def ==(other)
|
|
41
|
-
other.
|
|
47
|
+
other.class == self.class
|
|
42
48
|
end
|
|
43
49
|
alias eql? ==
|
|
44
50
|
|
|
@@ -46,6 +52,58 @@ module Torque
|
|
|
46
52
|
self.class.hash
|
|
47
53
|
end
|
|
48
54
|
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def value_class
|
|
58
|
+
LTree
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def items_of(entry, sanitize)
|
|
62
|
+
return items_for(LTree.compatible(entry), sanitize: sanitize) if LTree.compatible?(entry)
|
|
63
|
+
|
|
64
|
+
case entry
|
|
65
|
+
when LTree, LQuery then entry.items
|
|
66
|
+
when ::Array then items_for(entry, sanitize: sanitize)
|
|
67
|
+
when ::ActiveRecord::Base then labels_of(record_label(entry), sanitize)
|
|
68
|
+
else labels_of(entry.to_s, sanitize)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def labels_of(text, sanitize)
|
|
73
|
+
text = sanitize(text) if sanitize
|
|
74
|
+
text.split('.').map { |item| parse(item) }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def record_label(record)
|
|
78
|
+
id = record.id
|
|
79
|
+
raise ArgumentError, <<~MSG.squish if id.nil?
|
|
80
|
+
Unable to use #{record.class.name} as a label because its
|
|
81
|
+
#{record.class.primary_key} is still empty.
|
|
82
|
+
MSG
|
|
83
|
+
|
|
84
|
+
raise ArgumentError, <<~MSG.squish if id.is_a?(::Array)
|
|
85
|
+
Unable to use #{record.class.name} as a label because it has a
|
|
86
|
+
composite primary key, which cannot be a single label.
|
|
87
|
+
MSG
|
|
88
|
+
|
|
89
|
+
id.to_s
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def sanitize(text)
|
|
93
|
+
replacements = PostgreSQL.config.ltree.sanitize
|
|
94
|
+
return text if replacements.blank?
|
|
95
|
+
|
|
96
|
+
text.gsub(Regexp.union(replacements.keys), replacements)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def compile(item)
|
|
100
|
+
item.to_s
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def parse(text)
|
|
104
|
+
text
|
|
105
|
+
end
|
|
106
|
+
|
|
49
107
|
end
|
|
50
108
|
end
|
|
51
109
|
end
|
|
@@ -79,7 +79,7 @@ module Torque
|
|
|
79
79
|
set = value.instance_variable_get(:@attributes)
|
|
80
80
|
set.keys.to_h do |name|
|
|
81
81
|
attribute = set[name]
|
|
82
|
-
type = attribute.type
|
|
82
|
+
type = OID.unwrap(attribute.type)
|
|
83
83
|
|
|
84
84
|
next [name, type.document_for(attribute.value)] if type.is_a?(Struct)
|
|
85
85
|
[name, attribute.value_for_database]
|
|
@@ -115,6 +115,11 @@ module Torque
|
|
|
115
115
|
execute VersionedCommands.fetch_command(dir, :view, name, version)
|
|
116
116
|
end
|
|
117
117
|
|
|
118
|
+
def create_trigger(name, version:, dir: pool.migrations_paths)
|
|
119
|
+
return super unless VersionedCommands.valid_type?(:trigger)
|
|
120
|
+
execute VersionedCommands.fetch_command(dir, :trigger, name, version)
|
|
121
|
+
end
|
|
122
|
+
|
|
118
123
|
end
|
|
119
124
|
|
|
120
125
|
ActiveRecord::ConnectionAdapters::PostgreSQL::Table.include ColumnMethods
|
|
@@ -81,6 +81,57 @@ module Torque
|
|
|
81
81
|
end
|
|
82
82
|
end
|
|
83
83
|
|
|
84
|
+
# A cross join, which PostgreSQL needs spelled out because an inner
|
|
85
|
+
# join without a condition is a syntax error
|
|
86
|
+
class CrossJoin < ::Arel::Nodes::Join
|
|
87
|
+
def initialize(left, right = nil)
|
|
88
|
+
super
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# An aliased FROM item. +definitions+ maps a column name to the SQL
|
|
93
|
+
# type a function has to be told to return, +columns+ are the plain
|
|
94
|
+
# names the alias exposes, +ordinality+ names the appended counter,
|
|
95
|
+
# and +lateral+ is only needed when the source is a subquery
|
|
96
|
+
class FromItem < ::Arel::Nodes::Binary
|
|
97
|
+
attr_reader :columns, :definitions, :ordinality, :lateral
|
|
98
|
+
|
|
99
|
+
alias name right
|
|
100
|
+
|
|
101
|
+
def initialize(source, name, columns: nil, definitions: nil, ordinality: nil, lateral: false)
|
|
102
|
+
@columns = Array.wrap(columns).presence&.freeze
|
|
103
|
+
@definitions = definitions.presence&.freeze
|
|
104
|
+
@ordinality = ordinality&.to_s
|
|
105
|
+
@lateral = lateral
|
|
106
|
+
|
|
107
|
+
super(source, name.to_s)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# The definitions can only be moved into the source when the counter
|
|
111
|
+
# forces the +ROWS FROM+ form, since PostgreSQL refuses a column
|
|
112
|
+
# definition list next to +WITH ORDINALITY+
|
|
113
|
+
def rows_from?
|
|
114
|
+
ordinality.present? && definitions.present?
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# What the alias exposes, as name => type, where a +nil+ type means
|
|
118
|
+
# the name stands on its own. Types are only spelled out here when
|
|
119
|
+
# they have not been moved into the +ROWS FROM+ source.
|
|
120
|
+
#
|
|
121
|
+
# The list is all or nothing, because PostgreSQL matches it
|
|
122
|
+
# positionally: a partial list silently renames the columns it does
|
|
123
|
+
# reach instead of naming the ones it does not
|
|
124
|
+
def alias_columns
|
|
125
|
+
return {} if columns.nil? && definitions.nil?
|
|
126
|
+
|
|
127
|
+
plain = rows_from? ? definitions.keys : columns
|
|
128
|
+
result = plain.nil? ? definitions : plain.to_h { |name| [name.to_s, nil] }
|
|
129
|
+
|
|
130
|
+
return result if ordinality.blank?
|
|
131
|
+
result.merge(ordinality => nil)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
84
135
|
class Ref < ::Arel::Nodes::Unary
|
|
85
136
|
attr_reader :reference
|
|
86
137
|
alias to_s expr
|
|
@@ -40,6 +40,33 @@ module Torque
|
|
|
40
40
|
collector
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
+
# A cross join only ever carries its source
|
|
44
|
+
def visit_Torque_PostgreSQL_Arel_Nodes_CrossJoin(o, collector)
|
|
45
|
+
collector << 'CROSS JOIN '
|
|
46
|
+
visit(o.left, collector)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Every part here is written only when it is actually needed, since
|
|
50
|
+
# LATERAL is a noise word on functions and ROWS FROM is only forced
|
|
51
|
+
# by the counter
|
|
52
|
+
def visit_Torque_PostgreSQL_Arel_Nodes_FromItem(o, collector)
|
|
53
|
+
collector << 'LATERAL ' if o.lateral
|
|
54
|
+
|
|
55
|
+
collector << 'ROWS FROM (' if o.rows_from?
|
|
56
|
+
visit(o.left, collector)
|
|
57
|
+
collector = visit_definitions(o.definitions, collector) << ')' if o.rows_from?
|
|
58
|
+
|
|
59
|
+
collector << ' WITH ORDINALITY' if o.ordinality.present?
|
|
60
|
+
collector << ' ' << quote_table_name(o.name)
|
|
61
|
+
|
|
62
|
+
columns = o.alias_columns
|
|
63
|
+
return collector if columns.blank?
|
|
64
|
+
|
|
65
|
+
collector << '('
|
|
66
|
+
visit_alias_columns(columns, collector)
|
|
67
|
+
collector << ')'
|
|
68
|
+
end
|
|
69
|
+
|
|
43
70
|
# Allow casting any node
|
|
44
71
|
def visit_Torque_PostgreSQL_Arel_Nodes_Cast(o, collector)
|
|
45
72
|
visit(o.left, collector) << '::' << o.right
|
|
@@ -47,6 +74,25 @@ module Torque
|
|
|
47
74
|
|
|
48
75
|
private
|
|
49
76
|
|
|
77
|
+
# The column definition list a function has to be told to return
|
|
78
|
+
def visit_definitions(definitions, collector)
|
|
79
|
+
collector << ' AS ('
|
|
80
|
+
visit_alias_columns(definitions, collector)
|
|
81
|
+
collector << ')'
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# A name on its own when it has no type, otherwise the name and the
|
|
85
|
+
# type it is being given
|
|
86
|
+
def visit_alias_columns(columns, collector)
|
|
87
|
+
columns.each_with_index do |(name, type), index|
|
|
88
|
+
collector << ', ' unless index.zero?
|
|
89
|
+
collector << quote_column_name(name)
|
|
90
|
+
collector << ' ' << type if type.present?
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
collector
|
|
94
|
+
end
|
|
95
|
+
|
|
50
96
|
def quote_array(value, collector)
|
|
51
97
|
value = value.map(&::Arel::Nodes.method(:build_quoted))
|
|
52
98
|
|
|
@@ -19,11 +19,11 @@ module Torque
|
|
|
19
19
|
|
|
20
20
|
include SimpleEnum
|
|
21
21
|
include ActiveRecord::Store
|
|
22
|
-
# Rails 8.1 moved normalization to Active Model, which is where these
|
|
23
|
-
# classes belong anyway
|
|
24
22
|
include(AR810 ? ActiveModel::Attributes::Normalization : ActiveRecord::Normalization)
|
|
25
23
|
include ActiveRecord::Encryption::EncryptableRecord
|
|
26
24
|
|
|
25
|
+
class_attribute :filter_attributes, instance_accessor: false, default: []
|
|
26
|
+
|
|
27
27
|
class << self
|
|
28
28
|
# These classes are not backed by a table, so nothing here has the
|
|
29
29
|
# extra information that a column would provide
|
|
@@ -43,6 +43,14 @@ module Torque
|
|
|
43
43
|
|
|
44
44
|
super
|
|
45
45
|
end
|
|
46
|
+
|
|
47
|
+
def inspection_filter
|
|
48
|
+
ActiveSupport::ParameterFilter.new(filter_attributes)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def inspect
|
|
53
|
+
self.class.inspection_filter.filter(to_h).inspect
|
|
46
54
|
end
|
|
47
55
|
|
|
48
56
|
# Plain access to any property, declared or not
|
|
@@ -72,11 +80,6 @@ module Torque
|
|
|
72
80
|
attributes.symbolize_keys
|
|
73
81
|
end
|
|
74
82
|
|
|
75
|
-
def inspect
|
|
76
|
-
entries = attributes.map { |key, value| "#{key}: #{value.inspect}" }
|
|
77
|
-
"#<#{self.class.name} #{entries.join(', ')}>"
|
|
78
|
-
end
|
|
79
|
-
|
|
80
83
|
def type_for_attribute(name, &block)
|
|
81
84
|
self.class.type_for_attribute(name, &block)
|
|
82
85
|
end
|
|
@@ -48,7 +48,7 @@ module Torque
|
|
|
48
48
|
@load_columns_monitor.synchronize do
|
|
49
49
|
next if defined?(@columns)
|
|
50
50
|
|
|
51
|
-
columns =
|
|
51
|
+
columns = ActiveRecord::Base.with_connection { |c| c.composite_column_types(type_name) }
|
|
52
52
|
columns.each do |attr_name, attr_type|
|
|
53
53
|
attribute(attr_name, attr_type) unless attribute_names.include?(attr_name)
|
|
54
54
|
end
|
|
@@ -89,10 +89,6 @@ module Torque
|
|
|
89
89
|
klass.type_name = name
|
|
90
90
|
klass
|
|
91
91
|
end
|
|
92
|
-
|
|
93
|
-
def connection
|
|
94
|
-
ActiveRecord::Base.connection
|
|
95
|
-
end
|
|
96
92
|
end
|
|
97
93
|
end
|
|
98
94
|
end
|
|
@@ -51,7 +51,7 @@ module Torque
|
|
|
51
51
|
|
|
52
52
|
@load_values_monitor.synchronize do
|
|
53
53
|
next if defined?(@values)
|
|
54
|
-
@values =
|
|
54
|
+
@values = ::ActiveRecord::Base.with_connection { |c| c.enum_values(type_name) }.freeze
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
@values
|
|
@@ -115,11 +115,6 @@ module Torque
|
|
|
115
115
|
valid?(method_name) ? new(method_name.to_s) : super
|
|
116
116
|
end
|
|
117
117
|
|
|
118
|
-
# Get a connection based on its name
|
|
119
|
-
def connection
|
|
120
|
-
::ActiveRecord::Base.connection
|
|
121
|
-
end
|
|
122
|
-
|
|
123
118
|
end
|
|
124
119
|
|
|
125
120
|
# Override string initializer to check for a valid value
|