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
data/spec/tests/ltree_spec.rb
CHANGED
|
@@ -7,6 +7,8 @@ RSpec.describe 'LTree' do
|
|
|
7
7
|
|
|
8
8
|
let(:ltree) { Torque::PostgreSQL::LTree }
|
|
9
9
|
let(:lquery) { Torque::PostgreSQL::LQuery }
|
|
10
|
+
let(:ltree_type) { Torque::PostgreSQL::Adapter::OID::Ltree.new }
|
|
11
|
+
let(:lquery_type) { Torque::PostgreSQL::Adapter::OID::Lquery.new }
|
|
10
12
|
|
|
11
13
|
context 'on table definition' do
|
|
12
14
|
subject { table_definition.new(connection, 'articles') }
|
|
@@ -50,6 +52,13 @@ RSpec.describe 'LTree' do
|
|
|
50
52
|
expect(dump_io.string).to match(/t\.ltree +"path"/)
|
|
51
53
|
end
|
|
52
54
|
|
|
55
|
+
it 'dumps the column as an lquery' do
|
|
56
|
+
dump_io = StringIO.new
|
|
57
|
+
ActiveRecord::SchemaDumper.dump(source, dump_io)
|
|
58
|
+
expect(dump_io.string).to match(/t\.lquery +"pattern"/)
|
|
59
|
+
expect(dump_io.string).to match(/t\.lquery +"patterns", array: true/)
|
|
60
|
+
end
|
|
61
|
+
|
|
53
62
|
it 'dumps an array of paths' do
|
|
54
63
|
dump_io = StringIO.new
|
|
55
64
|
ActiveRecord::SchemaDumper.dump(source, dump_io)
|
|
@@ -63,9 +72,19 @@ RSpec.describe 'LTree' do
|
|
|
63
72
|
end
|
|
64
73
|
|
|
65
74
|
context 'on the path' do
|
|
66
|
-
it '
|
|
67
|
-
|
|
75
|
+
it 'enumerates its labels' do
|
|
76
|
+
subject = ltree.new('Top.Science')
|
|
77
|
+
expect(subject).to be_a(Enumerable)
|
|
78
|
+
expect(subject.items).to be_eql(%w[Top Science])
|
|
79
|
+
expect(subject.to_a).to be_eql(%w[Top Science])
|
|
80
|
+
expect(subject.map(&:downcase)).to be_eql(%w[top science])
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it 'compares with a plain array' do
|
|
68
84
|
expect(ltree.new('Top.Science')).to be_eql(%w[Top Science])
|
|
85
|
+
expect(ltree.new('Top.Science')).to be_eql(ltree[:Top, :Science])
|
|
86
|
+
expect(ltree.new('Top.Science')).not_to be_eql(%w[Top])
|
|
87
|
+
expect(ltree.new('Top.Science').hash).to be_eql(ltree[:Top, :Science].hash)
|
|
69
88
|
end
|
|
70
89
|
|
|
71
90
|
it 'accepts a string, an array or symbols' do
|
|
@@ -74,6 +93,10 @@ RSpec.describe 'LTree' do
|
|
|
74
93
|
expect(ltree[:Top, :Science].to_s).to be_eql('Top.Science')
|
|
75
94
|
end
|
|
76
95
|
|
|
96
|
+
it 'flattens nested arrays into labels' do
|
|
97
|
+
expect(ltree.new(['Top', %w[Science Astro]]).to_s).to be_eql('Top.Science.Astro')
|
|
98
|
+
end
|
|
99
|
+
|
|
77
100
|
it 'knows its depth' do
|
|
78
101
|
expect(ltree.new('a.b.c').depth).to be_eql(3)
|
|
79
102
|
expect(ltree.new('').depth).to be_eql(0)
|
|
@@ -123,13 +146,6 @@ RSpec.describe 'LTree' do
|
|
|
123
146
|
expect(subject.index_of('5.6', -4)).to be_eql(9)
|
|
124
147
|
expect(subject.index_of('9.9')).to be_eql(-1)
|
|
125
148
|
end
|
|
126
|
-
|
|
127
|
-
it 'rejects anything that is not a plain sequence of labels' do
|
|
128
|
-
expect { ltree.new('a.*') }.to raise_error(ArgumentError, /not a valid ltree label/)
|
|
129
|
-
expect { ltree.new('a b') }.to raise_error(ArgumentError, /not a valid ltree label/)
|
|
130
|
-
expect { ltree.new(['a', %w[b c]]) }.to raise_error(ArgumentError, /only make sense on an lquery/)
|
|
131
|
-
expect { ltree.new(['a', 1..2]) }.to raise_error(ArgumentError, /only make sense on an lquery/)
|
|
132
|
-
end
|
|
133
149
|
end
|
|
134
150
|
|
|
135
151
|
context 'on the pattern' do
|
|
@@ -162,29 +178,179 @@ RSpec.describe 'LTree' do
|
|
|
162
178
|
expect(subject.to_s).to be_eql('Top.*{0,2}.sport*@.!football|tennis{1,}.Russ*|Spain')
|
|
163
179
|
end
|
|
164
180
|
|
|
181
|
+
it 'holds the pure items' do
|
|
182
|
+
expect(lquery['Top', :any].items).to be_eql(['Top', :any])
|
|
183
|
+
expect(lquery.new('Top.*{1,2}.a|b').items).to be_eql(['Top', 1..2, %w[a b]])
|
|
184
|
+
expect(lquery.new('Top.*').to_a).to be_eql(['Top', :any])
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
it 'enumerates its items' do
|
|
188
|
+
expect(lquery['Top', :any]).to be_a(Enumerable)
|
|
189
|
+
expect(lquery['Top', :any].map(&:to_s)).to be_eql(%w[Top any])
|
|
190
|
+
end
|
|
191
|
+
|
|
165
192
|
it 'accepts its own text form' do
|
|
193
|
+
expect(lquery.new('Top.*.sport*@').items).to be_eql(['Top', :any, 'sport*@'])
|
|
166
194
|
expect(lquery.new('Top.*.sport*@').to_s).to be_eql('Top.*.sport*@')
|
|
167
195
|
end
|
|
168
196
|
|
|
169
|
-
it 'compares by its
|
|
197
|
+
it 'compares by its items' do
|
|
170
198
|
expect(lquery['Top', :any]).to be_eql(lquery.new('Top.*'))
|
|
199
|
+
expect(lquery['Top', :any]).to be_eql(['Top', :any])
|
|
171
200
|
expect(lquery['Top', :any].hash).to be_eql(lquery.new('Top.*').hash)
|
|
172
201
|
end
|
|
173
202
|
|
|
174
|
-
it '
|
|
175
|
-
expect
|
|
176
|
-
expect
|
|
177
|
-
expect {
|
|
178
|
-
|
|
203
|
+
it 'leaves the validation to the database' do
|
|
204
|
+
expect(lquery['a b'].to_s).to be_eql('a b')
|
|
205
|
+
expect(lquery[2..0].to_s).to be_eql('*{2,0}')
|
|
206
|
+
expect { Category.where(path: ['a b']).count }.to raise_error(ActiveRecord::StatementInvalid)
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
context 'on matching' do
|
|
211
|
+
let(:root) { Category.create!(title: 'Top') }
|
|
212
|
+
|
|
213
|
+
after { Torque::PostgreSQL.config.ltree.sanitize = nil }
|
|
214
|
+
|
|
215
|
+
it 'compiles into a regular expression over the text of a path' do
|
|
216
|
+
subject = lquery.new('Top.*{1,}.sport*@')
|
|
217
|
+
expect(subject.pattern).to be_a(Regexp)
|
|
218
|
+
expect(subject.pattern).to be_equal(subject.pattern)
|
|
219
|
+
expect(subject.pattern).to match('Top.Science.Sports')
|
|
220
|
+
expect(subject.pattern).not_to match('Top.Sports')
|
|
221
|
+
expect(subject.pattern).not_to match('Other.Top.Science.Sports')
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
it 'matches a string, an array or a path' do
|
|
225
|
+
subject = lquery['Top', :any]
|
|
226
|
+
expect(subject.match?('Top.Science')).to be_truthy
|
|
227
|
+
expect(subject.match?(%w[Top Science])).to be_truthy
|
|
228
|
+
expect(subject.match?(ltree['Top'])).to be_truthy
|
|
229
|
+
expect(subject.match?('Other')).to be_falsey
|
|
230
|
+
expect(subject.match?(nil)).to be_falsey
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
it 'works like a regular expression on the match operator' do
|
|
234
|
+
subject = lquery['Top', :any]
|
|
235
|
+
expect(subject =~ 'Top.Science').to be_eql(0)
|
|
236
|
+
expect(subject =~ 'Other').to be_nil
|
|
237
|
+
expect('Top.Science' =~ subject).to be_eql(0)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
it 'matches a record by its primary key' do
|
|
241
|
+
expect(lquery[root, :any].match?([root, 'Science'])).to be_truthy
|
|
242
|
+
expect(lquery[root, :any].match?(root)).to be_truthy
|
|
243
|
+
expect(lquery['Top', :any].match?(root)).to be_falsey
|
|
179
244
|
end
|
|
180
245
|
|
|
181
|
-
it '
|
|
182
|
-
|
|
183
|
-
expect
|
|
246
|
+
it 'sanitizes the path like a condition would' do
|
|
247
|
+
Torque::PostgreSQL.config.ltree.sanitize = { '-' => '_' }
|
|
248
|
+
expect(lquery.new('top.my-slug').match?('top.my_slug')).to be_truthy
|
|
249
|
+
expect(lquery.new('top.my_slug').match?('top.my-slug')).to be_truthy
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
samples = [
|
|
253
|
+
['a.b.c.d.e', 'a.b.c.d.e', true],
|
|
254
|
+
['a.b.c.d.e', 'A.b.c.d.e', false],
|
|
255
|
+
['a.b.c.d.e', 'A@.b.c.d.e', true],
|
|
256
|
+
['aa.b.c.d.e', 'A@.b.c.d.e', false],
|
|
257
|
+
['aa.b.c.d.e', 'A*.b.c.d.e', false],
|
|
258
|
+
['aa.b.c.d.e', 'A*@.b.c.d.e', true],
|
|
259
|
+
['aa.b.c.d.e', 'A*@|g.b.c.d.e', true],
|
|
260
|
+
['g.b.c.d.e', 'A*@|g.b.c.d.e', true],
|
|
261
|
+
['a.b.c.d.e', 'a.*.e', true],
|
|
262
|
+
['a.b.c.d.e', 'a.*{3}.e', true],
|
|
263
|
+
['a.b.c.d.e', 'a.*{2}.e', false],
|
|
264
|
+
['a.b.c.d.e', 'a.*{4}.e', false],
|
|
265
|
+
['a.b.c.d.e', 'a.*{,4}.e', true],
|
|
266
|
+
['a.b.c.d.e', 'a.*{2,}.e', true],
|
|
267
|
+
['a.b.c.d.e', 'a.*{2,4}.e', true],
|
|
268
|
+
['a.b.c.d.e', 'a.*{2,3}.e', true],
|
|
269
|
+
['a.b.c.d.e', 'a.*{2,3}', false],
|
|
270
|
+
['a.b.c.d.e', 'a.*{2,4}', true],
|
|
271
|
+
['a.b.c.d.e', 'a.*{2,5}', true],
|
|
272
|
+
['a.b.c.d.e', '*{2,3}.e', false],
|
|
273
|
+
['a.b.c.d.e', '*{2,4}.e', true],
|
|
274
|
+
['a.b.c.d.e', '*{2,5}.e', true],
|
|
275
|
+
['a.b.c.d.e', '*.e', true],
|
|
276
|
+
['a.b.c.d.e', '*.e.*', true],
|
|
277
|
+
['a.b.c.d.e', '*.d.*', true],
|
|
278
|
+
['a.b.c.d.e', '*.a.*.d.*', true],
|
|
279
|
+
['a.b.c.d.e', '*.!d.*', true],
|
|
280
|
+
['a.b.c.d.e', '*.!d', true],
|
|
281
|
+
['a.b.c.d.e', '!d.*', true],
|
|
282
|
+
['a.b.c.d.e', '!a.*', false],
|
|
283
|
+
['a.b.c.d.e', '*.!e', false],
|
|
284
|
+
['a.b.c.d.e', '*.!e.*', true],
|
|
285
|
+
['a.b.c.d.e', 'a.*.!e', false],
|
|
286
|
+
['a.b.c.d.e', 'a.*.!d', true],
|
|
287
|
+
['a.b.c.d.e', 'a.*.!d.*', true],
|
|
288
|
+
['a.b.c.d.e', 'a.*.!f.*', true],
|
|
289
|
+
['a.b.c.d.e', '*.a.*.!f.*', true],
|
|
290
|
+
['a.b.c.d.e', '*.a.*.!d.*', true],
|
|
291
|
+
['a.b.c.d.e', '*.a.!d.*', true],
|
|
292
|
+
['a.b.c.d.e', '*.a.!d', false],
|
|
293
|
+
['a.b.c.d.e', 'a.!d.*', true],
|
|
294
|
+
['a.b.c.d.e', '*.!b.*', true],
|
|
295
|
+
['a.b.c.d.e', '*.!b.c.*', false],
|
|
296
|
+
['a.b.c.d.e', '*.!b.*.c.*', true],
|
|
297
|
+
['a.b.c.d.e', '!b.*.c.*', true],
|
|
298
|
+
['a.b.c.d.e', '!b.b.*', true],
|
|
299
|
+
['a.b.c.d.e', '!b.*.e', true],
|
|
300
|
+
['a.b.c.d.e', '!b.!c.*.e', true],
|
|
301
|
+
['a.b.c.d.e', '!b.*.!c.*.e', true],
|
|
302
|
+
['a.b.c.d.e', '*{2}.!b.*.!c.*.e', true],
|
|
303
|
+
['a.b.c.d.e', '*{1}.!b.*.!c.*.e', false],
|
|
304
|
+
['a.b.c.d.e', '*{1}.!b.*{1}.!c.*.e', false],
|
|
305
|
+
['a.b.c.d.e', 'a.!b.*{1}.!c.*.e', false],
|
|
306
|
+
['a.b.c.d.e', '!b.*{1}.!c.*.e', false],
|
|
307
|
+
['a.b.c.d.e', '*.!b.*{1}.!c.*.e', false],
|
|
308
|
+
['a.b.c.d.e', '*.!b.*.!c.*.e', true],
|
|
309
|
+
['a.b.c.d.e', '!b.!c.*', true],
|
|
310
|
+
['a.b.c.d.e', '!b.*.!c.*', true],
|
|
311
|
+
['a.b.c.d.e', '*{2}.!b.*.!c.*', true],
|
|
312
|
+
['a.b.c.d.e', '*{1}.!b.*.!c.*', false],
|
|
313
|
+
['a.b.c.d.e', '*{1}.!b.*{1}.!c.*', false],
|
|
314
|
+
['a.b.c.d.e', 'a.!b.*{1}.!c.*', false],
|
|
315
|
+
['a.b.c.d.e', '!b.*{1}.!c.*', false],
|
|
316
|
+
['a.b.c.d.e', '*.!b.*{1}.!c.*', true],
|
|
317
|
+
['a.b.c.d.e', '*.!b.*.!c.*', true],
|
|
318
|
+
['a.b.c.d.e', 'a.*{2}.*{2}', true],
|
|
319
|
+
['a.b.c.d.e', 'a.*{1}.*{2}.e', true],
|
|
320
|
+
['a.b.c.d.e', 'a.*{1}.*{4}', false],
|
|
321
|
+
['a.b.c.d.e', 'a.*{5}.*', false],
|
|
322
|
+
['5.0.1.0', '5.!0.!0.0', false],
|
|
323
|
+
['a.b', '!a.!a', false],
|
|
324
|
+
['a.b.c.d.e', 'a{,}', false],
|
|
325
|
+
['a.b.c.d.e', 'a{1,}.*', true],
|
|
326
|
+
['a.b.c.d.e', 'a{,}.!a{,}', true],
|
|
327
|
+
['a.b.c.d.a', 'a{,}.!a{,}', false],
|
|
328
|
+
['a.b.c.d.a', 'a{,2}.!a{1,}', false],
|
|
329
|
+
['a.b.c.d.e', 'a{,2}.!a{1,}', true],
|
|
330
|
+
['a.b.c.d.e', '!x{,}', true],
|
|
331
|
+
['a.b.c.d.e', '!c{,}', false],
|
|
332
|
+
['a.b.c.d.e', '!c{0,3}.!a{2,}', true],
|
|
333
|
+
['a.b.c.d.e', '!c{0,3}.!d{2,}.*', true],
|
|
334
|
+
['QWER_TY', 'q%@*', true],
|
|
335
|
+
['QWER_TY', 'q%@*%@*', true],
|
|
336
|
+
['QWER_TY', 'Q_t%@*', true],
|
|
337
|
+
['QWER_GY', 'q_t%@*', false],
|
|
338
|
+
]
|
|
339
|
+
|
|
340
|
+
samples.each do |path, query, expected|
|
|
341
|
+
it "agrees with PostgreSQL that '#{path}' ~ '#{query}' is #{expected}" do
|
|
342
|
+
expect(lquery.new(query).match?(path)).to be(expected)
|
|
343
|
+
end
|
|
184
344
|
end
|
|
185
345
|
|
|
186
|
-
it '
|
|
187
|
-
|
|
346
|
+
it 'holds the same outcomes as the database for every sample' do
|
|
347
|
+
rows = samples.map { |path, query, _| "('#{path}', '#{query}')" }
|
|
348
|
+
result = connection.select_values(<<~SQL)
|
|
349
|
+
SELECT path::ltree ~ query::lquery
|
|
350
|
+
FROM (VALUES #{rows.join(', ')}) AS samples(path, query)
|
|
351
|
+
SQL
|
|
352
|
+
|
|
353
|
+
expect(result).to be_eql(samples.map(&:last))
|
|
188
354
|
end
|
|
189
355
|
end
|
|
190
356
|
|
|
@@ -291,8 +457,9 @@ RSpec.describe 'LTree' do
|
|
|
291
457
|
expect((ltree.new('top') / path_like.new('app.users')).to_s).to be_eql('top.app.users')
|
|
292
458
|
end
|
|
293
459
|
|
|
294
|
-
it 'contributes every
|
|
460
|
+
it 'contributes every item of a pattern' do
|
|
295
461
|
expect(lquery[path_like.new('app.users'), :any].to_s).to be_eql('app.users.*')
|
|
462
|
+
expect(lquery[path_like.new(['app', :any])].items).to be_eql(['app', :any])
|
|
296
463
|
end
|
|
297
464
|
|
|
298
465
|
it 'turns the condition into a match when it describes a pattern' do
|
|
@@ -317,25 +484,24 @@ RSpec.describe 'LTree' do
|
|
|
317
484
|
|
|
318
485
|
it 'is ignored when the config has no method' do
|
|
319
486
|
Torque::PostgreSQL.config.ltree.compatible_method = nil
|
|
320
|
-
expect
|
|
321
|
-
.to raise_error(ArgumentError, /only make sense on an lquery/)
|
|
487
|
+
expect(ltree.new(path_like.new('app.users'))).not_to be_eql(%w[app users])
|
|
322
488
|
end
|
|
323
489
|
end
|
|
324
490
|
|
|
325
491
|
context 'on markers' do
|
|
326
492
|
it 'treats a plain path as a path' do
|
|
327
|
-
expect(lquery.
|
|
328
|
-
expect(lquery.
|
|
329
|
-
expect(lquery.
|
|
493
|
+
expect(lquery.new('Top.Science')).not_to be_pattern
|
|
494
|
+
expect(lquery.new(%w[Top Science])).not_to be_pattern
|
|
495
|
+
expect(lquery.new(%i[Top Science])).not_to be_pattern
|
|
330
496
|
end
|
|
331
497
|
|
|
332
498
|
it 'treats anything with a query feature as a pattern' do
|
|
333
|
-
expect(lquery.
|
|
334
|
-
expect(lquery.
|
|
335
|
-
expect(lquery.
|
|
336
|
-
expect(lquery.
|
|
337
|
-
expect(lquery.
|
|
338
|
-
expect(lquery.
|
|
499
|
+
expect(lquery.new(['Top', :any])).to be_pattern
|
|
500
|
+
expect(lquery.new(['Top', 1..])).to be_pattern
|
|
501
|
+
expect(lquery.new(['Top', %w[a b]])).to be_pattern
|
|
502
|
+
expect(lquery.new('Top.*')).to be_pattern
|
|
503
|
+
expect(lquery.new('!Top')).to be_pattern
|
|
504
|
+
expect(lquery.new('Top@')).to be_pattern
|
|
339
505
|
end
|
|
340
506
|
end
|
|
341
507
|
|
|
@@ -361,39 +527,59 @@ RSpec.describe 'LTree' do
|
|
|
361
527
|
expect(lquery['a-b*', %w[c-d e]].to_s).to be_eql('a_b*.c_d|e')
|
|
362
528
|
end
|
|
363
529
|
|
|
364
|
-
it 'never rewrites a quantifier' do
|
|
365
|
-
Torque::PostgreSQL.config.ltree.sanitize = { '1' => '9' }
|
|
366
|
-
expect(lquery['foo{1,2}'].to_s).to be_eql('foo{1,2}')
|
|
367
|
-
end
|
|
368
|
-
|
|
369
530
|
it 'does not apply to values read from the database' do
|
|
370
531
|
Torque::PostgreSQL.config.ltree.sanitize = { 'a' => 'z' }
|
|
371
|
-
expect(
|
|
532
|
+
expect(ltree_type.deserialize('a.b').to_s).to be_eql('a.b')
|
|
533
|
+
expect(lquery_type.deserialize('a.*').to_s).to be_eql('a.*')
|
|
372
534
|
end
|
|
373
535
|
end
|
|
374
536
|
|
|
375
537
|
context 'on OID' do
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
expect(subject.type).to be_eql(:ltree)
|
|
538
|
+
it 'has the right types' do
|
|
539
|
+
expect(ltree_type.type).to be_eql(:ltree)
|
|
540
|
+
expect(lquery_type.type).to be_eql(:lquery)
|
|
380
541
|
end
|
|
381
542
|
|
|
382
543
|
it 'deserializes into a path' do
|
|
383
|
-
expect(
|
|
384
|
-
expect(
|
|
385
|
-
expect(
|
|
544
|
+
expect(ltree_type.deserialize('Top.Science')).to be_a(ltree)
|
|
545
|
+
expect(ltree_type.deserialize('Top.Science')).to be_eql(%w[Top Science])
|
|
546
|
+
expect(ltree_type.deserialize(nil)).to be_nil
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
it 'serializes a path back into text' do
|
|
550
|
+
expect(ltree_type.serialize(%w[Top Science])).to be_eql('Top.Science')
|
|
551
|
+
expect(ltree_type.serialize('Top.Science')).to be_eql('Top.Science')
|
|
552
|
+
expect(ltree_type.serialize(nil)).to be_nil
|
|
553
|
+
expect(ltree_type.serialize('')).to be_nil
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
it 'deserializes a pattern into its items' do
|
|
557
|
+
expect(lquery_type.deserialize('users.*')).to be_a(lquery)
|
|
558
|
+
expect(lquery_type.deserialize('users.*')).to be_eql(['users', :any])
|
|
559
|
+
expect(lquery_type.deserialize('a.*{2}')).to be_eql(['a', 2..2])
|
|
560
|
+
expect(lquery_type.deserialize('a.*{1,}')).to be_eql(['a', 1..])
|
|
561
|
+
expect(lquery_type.deserialize('a.*{,3}')).to be_eql(['a', ..3])
|
|
562
|
+
expect(lquery_type.deserialize('a.*{0,2}')).to be_eql(['a', 0..2])
|
|
563
|
+
expect(lquery_type.deserialize('a|b.c')).to be_eql([%w[a b], 'c'])
|
|
564
|
+
expect(lquery_type.deserialize(nil)).to be_nil
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
it 'keeps negated and quantified groups as text' do
|
|
568
|
+
expect(lquery_type.deserialize('!a|b.c{1,}')).to be_eql(['!a|b', 'c{1,}'])
|
|
386
569
|
end
|
|
387
570
|
|
|
388
|
-
it '
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
expect(
|
|
571
|
+
it 'round trips the example from the PostgreSQL manual' do
|
|
572
|
+
text = 'Top.*{0,2}.sport*@.!football|tennis{1,}.Russ*|Spain'
|
|
573
|
+
items = ['Top', 0..2, 'sport*@', '!football|tennis{1,}', %w[Russ* Spain]]
|
|
574
|
+
|
|
575
|
+
expect(lquery_type.deserialize(text)).to be_eql(items)
|
|
576
|
+
expect(lquery_type.serialize(lquery_type.deserialize(text))).to be_eql(text)
|
|
393
577
|
end
|
|
394
578
|
|
|
395
|
-
it '
|
|
396
|
-
expect
|
|
579
|
+
it 'serializes a pattern back into text' do
|
|
580
|
+
expect(lquery_type.serialize(['Top', :any])).to be_eql('Top.*')
|
|
581
|
+
expect(lquery_type.serialize(lquery['Top', 1..])).to be_eql('Top.*{1,}')
|
|
582
|
+
expect(lquery_type.serialize(nil)).to be_nil
|
|
397
583
|
end
|
|
398
584
|
end
|
|
399
585
|
|
|
@@ -402,7 +588,7 @@ RSpec.describe 'LTree' do
|
|
|
402
588
|
|
|
403
589
|
it 'reads and writes a path' do
|
|
404
590
|
subject.path = 'Top.Science'
|
|
405
|
-
expect(subject.path).to be_a(
|
|
591
|
+
expect(subject.path).to be_a(ltree)
|
|
406
592
|
expect(subject.path).to be_eql(%w[Top Science])
|
|
407
593
|
end
|
|
408
594
|
|
|
@@ -416,17 +602,47 @@ RSpec.describe 'LTree' do
|
|
|
416
602
|
expect(Category.find(subject.id).path).to be_eql(%w[Top Science])
|
|
417
603
|
end
|
|
418
604
|
|
|
419
|
-
it 'round trips an array of paths
|
|
605
|
+
it 'round trips an array of paths' do
|
|
420
606
|
user = User.create!(name: 'Rick', permissions: ['app.users.write', %w[app posts]])
|
|
421
607
|
reloaded = User.find(user.id)
|
|
422
608
|
|
|
423
609
|
expect(reloaded.permissions).to be_eql([%w[app users write], %w[app posts]])
|
|
424
|
-
expect(reloaded.permissions.first).to be_a(
|
|
610
|
+
expect(reloaded.permissions.first).to be_a(ltree)
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
it 'reads and writes a pattern' do
|
|
614
|
+
subject.pattern = ['users', :any]
|
|
615
|
+
expect(subject.pattern).to be_a(lquery)
|
|
616
|
+
expect(subject.pattern.items).to be_eql(['users', :any])
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
it 'round trips a pattern as its items' do
|
|
620
|
+
subject.update!(title: 'Rule', pattern: ['users', :any, 1..2])
|
|
621
|
+
expect(Category.find(subject.id).pattern.items).to be_eql(['users', :any, 1..2])
|
|
622
|
+
end
|
|
623
|
+
|
|
624
|
+
it 'round trips an array of patterns' do
|
|
625
|
+
subject.update!(title: 'Rule', patterns: [['users', :any], 'posts.*{1,}'])
|
|
626
|
+
reloaded = Category.find(subject.id)
|
|
627
|
+
|
|
628
|
+
expect(reloaded.patterns).to be_eql([['users', :any], ['posts', 1..]])
|
|
629
|
+
expect(reloaded.patterns.first).to be_a(lquery)
|
|
630
|
+
end
|
|
631
|
+
|
|
632
|
+
it 'tracks changes by the compiled form' do
|
|
633
|
+
subject.update!(title: 'Rule', path: 'Top.Science', pattern: ['Top', :any])
|
|
634
|
+
subject.path = %w[Top Science]
|
|
635
|
+
subject.pattern = 'Top.*'
|
|
636
|
+
expect(subject).not_to be_changed
|
|
637
|
+
|
|
638
|
+
subject.pattern = ['Top', 1..]
|
|
639
|
+
expect(subject).to be_changed
|
|
425
640
|
end
|
|
426
641
|
|
|
427
|
-
it '
|
|
642
|
+
it 'leaves the validation to the database' do
|
|
428
643
|
subject.path = 'Top.*'
|
|
429
|
-
expect
|
|
644
|
+
expect(subject.path).to be_eql(['Top', '*'])
|
|
645
|
+
expect { subject.save! }.to raise_error(ActiveRecord::StatementInvalid)
|
|
430
646
|
end
|
|
431
647
|
end
|
|
432
648
|
|
|
@@ -468,6 +684,27 @@ RSpec.describe 'LTree' do
|
|
|
468
684
|
.to include(%[WHERE "categories"."path" ~ 'Top.*'::lquery])
|
|
469
685
|
end
|
|
470
686
|
|
|
687
|
+
it 'takes a plain array as one path' do
|
|
688
|
+
expect(subject.where(path: %w[Top Science]).to_sql).not_to include('IN')
|
|
689
|
+
end
|
|
690
|
+
|
|
691
|
+
it 'lists paths when the first entry is a whole one' do
|
|
692
|
+
expect(subject.where(path: [%w[Top a], %w[Top b]]).to_sql)
|
|
693
|
+
.to include(%[WHERE "categories"."path" IN ('Top.a', 'Top.b')])
|
|
694
|
+
expect(subject.where(path: [ltree['Top'], 'Other']).to_sql)
|
|
695
|
+
.to include(%[WHERE "categories"."path" IN ('Top', 'Other')])
|
|
696
|
+
end
|
|
697
|
+
|
|
698
|
+
it 'tests a list of patterns at once' do
|
|
699
|
+
expect(subject.where(path: [['Top', :any], lquery['Other', 1..]]).to_sql)
|
|
700
|
+
.to include(%[WHERE "categories"."path" ? '{Top.*,"Other.*{1,}"}'::lquery[]])
|
|
701
|
+
end
|
|
702
|
+
|
|
703
|
+
it 'handles nil and an empty list' do
|
|
704
|
+
expect(subject.where(path: nil).to_sql).to include(%[WHERE "categories"."path" IS NULL])
|
|
705
|
+
expect(subject.where(path: []).to_sql).to include('WHERE 1=0')
|
|
706
|
+
end
|
|
707
|
+
|
|
471
708
|
it 'finds the records that match' do
|
|
472
709
|
Category.create!(title: 'Science', path: 'Top.Science')
|
|
473
710
|
Category.create!(title: 'Astronomy', path: 'Top.Science.Astronomy')
|
|
@@ -476,15 +713,113 @@ RSpec.describe 'LTree' do
|
|
|
476
713
|
expect(Category.where(path: ['Top', :any]).count).to be_eql(3)
|
|
477
714
|
expect(Category.where(path: %w[Top Science]).count).to be_eql(1)
|
|
478
715
|
expect(Category.where(path: ['Top', 'Science', :any]).count).to be_eql(2)
|
|
716
|
+
expect(Category.where(path: [%w[Top Science], %w[Top Hobbies]]).count).to be_eql(2)
|
|
717
|
+
expect(Category.where(path: [['Top', 'Sci*'], ['Nope', :any]]).count).to be_eql(1)
|
|
718
|
+
end
|
|
719
|
+
|
|
720
|
+
context 'on an array of paths' do
|
|
721
|
+
subject { User.all }
|
|
722
|
+
|
|
723
|
+
it 'asks whether any entry is the path' do
|
|
724
|
+
expect(subject.where(permissions: 'app.users').to_sql)
|
|
725
|
+
.to include(%[WHERE 'app.users' = ANY("users"."permissions")])
|
|
726
|
+
end
|
|
727
|
+
|
|
728
|
+
it 'matches the entries against a pattern' do
|
|
729
|
+
expect(subject.where(permissions: ['app', :any]).to_sql)
|
|
730
|
+
.to include(%[WHERE "users"."permissions" ~ 'app.*'::lquery])
|
|
731
|
+
end
|
|
732
|
+
|
|
733
|
+
it 'overlaps with a list of paths' do
|
|
734
|
+
expect(subject.where(permissions: [%w[app users], %w[app posts]]).to_sql)
|
|
735
|
+
.to include(%[WHERE "users"."permissions" && '{app.users,app.posts}'])
|
|
736
|
+
end
|
|
737
|
+
|
|
738
|
+
it 'tests the entries against a list of patterns' do
|
|
739
|
+
expect(subject.where(permissions: [['app', :any], ['other', :any]]).to_sql)
|
|
740
|
+
.to include(%[WHERE "users"."permissions" ? '{app.*,other.*}'::lquery[]])
|
|
741
|
+
end
|
|
742
|
+
|
|
743
|
+
it 'handles nil and an empty list' do
|
|
744
|
+
expect(subject.where(permissions: nil).to_sql)
|
|
745
|
+
.to include(%[WHERE "users"."permissions" IS NULL])
|
|
746
|
+
expect(subject.where(permissions: []).to_sql)
|
|
747
|
+
.to include(%[WHERE CARDINALITY("users"."permissions") = 0])
|
|
748
|
+
end
|
|
749
|
+
|
|
750
|
+
it 'finds the records that match' do
|
|
751
|
+
User.create!(name: 'Editor', permissions: ['app.posts', 'app.users'])
|
|
752
|
+
User.create!(name: 'Guest', permissions: ['app.posts'])
|
|
753
|
+
|
|
754
|
+
expect(User.where(permissions: 'app.users').pluck(:name)).to be_eql(%w[Editor])
|
|
755
|
+
expect(User.where(permissions: ['app', 'u*']).pluck(:name)).to be_eql(%w[Editor])
|
|
756
|
+
expect(User.where(permissions: [%w[app users], %w[nope]]).pluck(:name)).to be_eql(%w[Editor])
|
|
757
|
+
expect(User.where(permissions: [['app', :any]]).count).to be_eql(2)
|
|
758
|
+
end
|
|
479
759
|
end
|
|
480
760
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
.
|
|
761
|
+
context 'on a pattern column' do
|
|
762
|
+
it 'compares a pattern by its text' do
|
|
763
|
+
expect(subject.where(pattern: ['users', :any]).to_sql)
|
|
764
|
+
.to include(%[WHERE "categories"."pattern"::text = 'users.*'])
|
|
765
|
+
end
|
|
766
|
+
|
|
767
|
+
it 'matches a path against the stored pattern' do
|
|
768
|
+
expect(subject.where(pattern: 'users.admin').to_sql)
|
|
769
|
+
.to include(%[WHERE "categories"."pattern" ~ 'users.admin'::ltree])
|
|
770
|
+
end
|
|
771
|
+
|
|
772
|
+
it 'lists patterns by their text' do
|
|
773
|
+
expect(subject.where(pattern: [['users', :any], ['posts', :any]]).to_sql)
|
|
774
|
+
.to include(%[WHERE "categories"."pattern"::text IN ('users.*', 'posts.*')])
|
|
775
|
+
end
|
|
776
|
+
|
|
777
|
+
it 'matches any of the paths' do
|
|
778
|
+
expect(subject.where(pattern: [%w[users admin], %w[posts new]]).to_sql)
|
|
779
|
+
.to include(%[WHERE "categories"."pattern" ~ ANY('{users.admin,posts.new}'::ltree[])])
|
|
780
|
+
end
|
|
781
|
+
|
|
782
|
+
it 'finds the patterns that match' do
|
|
783
|
+
Category.create!(title: 'Users', pattern: ['users', :any])
|
|
784
|
+
Category.create!(title: 'Posts', pattern: ['posts', 1..])
|
|
785
|
+
|
|
786
|
+
expect(Category.where(pattern: 'users.admin').pluck(:title)).to be_eql(%w[Users])
|
|
787
|
+
expect(Category.where(pattern: ['users', :any]).pluck(:title)).to be_eql(%w[Users])
|
|
788
|
+
expect(Category.where(pattern: [%w[users admin], %w[posts new]]).pluck(:title))
|
|
789
|
+
.to match_array(%w[Users Posts])
|
|
790
|
+
end
|
|
484
791
|
end
|
|
485
792
|
|
|
486
|
-
|
|
487
|
-
|
|
793
|
+
context 'on an array of patterns' do
|
|
794
|
+
it 'asks whether any entry is the pattern' do
|
|
795
|
+
expect(subject.where(patterns: ['users', :any]).to_sql)
|
|
796
|
+
.to include(%[WHERE 'users.*' = ANY("categories"."patterns"::text[])])
|
|
797
|
+
end
|
|
798
|
+
|
|
799
|
+
it 'matches a path against any entry' do
|
|
800
|
+
expect(subject.where(patterns: 'users.admin').to_sql)
|
|
801
|
+
.to include(%[WHERE 'users.admin'::ltree ~ ANY("categories"."patterns")])
|
|
802
|
+
end
|
|
803
|
+
|
|
804
|
+
it 'overlaps with a list of patterns by their text' do
|
|
805
|
+
expect(subject.where(patterns: [['users', :any], ['posts', :any]]).to_sql)
|
|
806
|
+
.to include(%[WHERE "categories"."patterns"::text[] && '{users.*,posts.*}'])
|
|
807
|
+
end
|
|
808
|
+
|
|
809
|
+
it 'tests the entries against a list of paths' do
|
|
810
|
+
expect(subject.where(patterns: [%w[users admin], %w[posts new]]).to_sql)
|
|
811
|
+
.to include(%[WHERE "categories"."patterns" ? '{users.admin,posts.new}'::ltree[]])
|
|
812
|
+
end
|
|
813
|
+
|
|
814
|
+
it 'finds the records whose patterns match' do
|
|
815
|
+
Category.create!(title: 'Rule', patterns: [['users', :any], ['posts', 1..]])
|
|
816
|
+
|
|
817
|
+
expect(Category.where(patterns: 'users.admin').count).to be_eql(1)
|
|
818
|
+
expect(Category.where(patterns: 'nope.x').count).to be_eql(0)
|
|
819
|
+
expect(Category.where(patterns: ['users', :any]).count).to be_eql(1)
|
|
820
|
+
expect(Category.where(patterns: [%w[posts new], %w[nope]]).count).to be_eql(1)
|
|
821
|
+
expect(Category.where(patterns: [['users', :any], ['x', :any]]).count).to be_eql(1)
|
|
822
|
+
end
|
|
488
823
|
end
|
|
489
824
|
end
|
|
490
825
|
|
|
@@ -545,8 +880,7 @@ RSpec.describe 'LTree' do
|
|
|
545
880
|
end
|
|
546
881
|
|
|
547
882
|
it 'matches grants against a pattern' do
|
|
548
|
-
|
|
549
|
-
expect(User.where(condition).pluck(:name)).to match_array(%w[Editor])
|
|
883
|
+
expect(User.where(permissions: ['app', 'users', :any]).pluck(:name)).to match_array(%w[Editor])
|
|
550
884
|
end
|
|
551
885
|
end
|
|
552
886
|
end
|