mongory 0.7.3-aarch64-linux
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.
Potentially problematic release.
This version of mongory might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +88 -0
- data/.yardopts +7 -0
- data/CHANGELOG.md +364 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/LICENSE.txt +21 -0
- data/README.md +488 -0
- data/Rakefile +107 -0
- data/SUBMODULE_INTEGRATION.md +325 -0
- data/docs/advanced_usage.md +40 -0
- data/docs/clang_bridge.md +69 -0
- data/docs/field_names.md +30 -0
- data/docs/migration.md +30 -0
- data/docs/performance.md +61 -0
- data/examples/README.md +41 -0
- data/examples/benchmark-rails.rb +52 -0
- data/examples/benchmark.rb +184 -0
- data/ext/mongory_ext/extconf.rb +91 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/array.h +122 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/config.h +161 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/error.h +79 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/memory_pool.h +95 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/table.h +127 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/value.h +175 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/matchers/matcher.h +76 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core.h +12 -0
- data/ext/mongory_ext/mongory-core/src/foundations/array.c +287 -0
- data/ext/mongory_ext/mongory-core/src/foundations/array_private.h +19 -0
- data/ext/mongory_ext/mongory-core/src/foundations/config.c +270 -0
- data/ext/mongory_ext/mongory-core/src/foundations/config_private.h +48 -0
- data/ext/mongory_ext/mongory-core/src/foundations/error.c +38 -0
- data/ext/mongory_ext/mongory-core/src/foundations/memory_pool.c +298 -0
- data/ext/mongory_ext/mongory-core/src/foundations/string_buffer.c +65 -0
- data/ext/mongory_ext/mongory-core/src/foundations/string_buffer.h +49 -0
- data/ext/mongory_ext/mongory-core/src/foundations/table.c +498 -0
- data/ext/mongory_ext/mongory-core/src/foundations/utils.c +210 -0
- data/ext/mongory_ext/mongory-core/src/foundations/utils.h +70 -0
- data/ext/mongory_ext/mongory-core/src/foundations/value.c +500 -0
- data/ext/mongory_ext/mongory-core/src/matchers/array_record_matcher.c +164 -0
- data/ext/mongory_ext/mongory-core/src/matchers/array_record_matcher.h +47 -0
- data/ext/mongory_ext/mongory-core/src/matchers/base_matcher.c +122 -0
- data/ext/mongory_ext/mongory-core/src/matchers/base_matcher.h +100 -0
- data/ext/mongory_ext/mongory-core/src/matchers/compare_matcher.c +217 -0
- data/ext/mongory_ext/mongory-core/src/matchers/compare_matcher.h +83 -0
- data/ext/mongory_ext/mongory-core/src/matchers/composite_matcher.c +573 -0
- data/ext/mongory_ext/mongory-core/src/matchers/composite_matcher.h +125 -0
- data/ext/mongory_ext/mongory-core/src/matchers/existance_matcher.c +147 -0
- data/ext/mongory_ext/mongory-core/src/matchers/existance_matcher.h +48 -0
- data/ext/mongory_ext/mongory-core/src/matchers/external_matcher.c +124 -0
- data/ext/mongory_ext/mongory-core/src/matchers/external_matcher.h +46 -0
- data/ext/mongory_ext/mongory-core/src/matchers/inclusion_matcher.c +126 -0
- data/ext/mongory_ext/mongory-core/src/matchers/inclusion_matcher.h +46 -0
- data/ext/mongory_ext/mongory-core/src/matchers/literal_matcher.c +314 -0
- data/ext/mongory_ext/mongory-core/src/matchers/literal_matcher.h +97 -0
- data/ext/mongory_ext/mongory-core/src/matchers/matcher.c +252 -0
- data/ext/mongory_ext/mongory-core/src/matchers/matcher_explainable.c +79 -0
- data/ext/mongory_ext/mongory-core/src/matchers/matcher_explainable.h +23 -0
- data/ext/mongory_ext/mongory-core/src/matchers/matcher_traversable.c +60 -0
- data/ext/mongory_ext/mongory-core/src/matchers/matcher_traversable.h +23 -0
- data/ext/mongory_ext/mongory_ext.c +683 -0
- data/lib/generators/mongory/install/install_generator.rb +42 -0
- data/lib/generators/mongory/install/templates/initializer.rb.erb +83 -0
- data/lib/generators/mongory/matcher/matcher_generator.rb +56 -0
- data/lib/generators/mongory/matcher/templates/matcher.rb.erb +92 -0
- data/lib/generators/mongory/matcher/templates/matcher_spec.rb.erb +17 -0
- data/lib/mongory/c_query_builder.rb +44 -0
- data/lib/mongory/converters/abstract_converter.rb +111 -0
- data/lib/mongory/converters/condition_converter.rb +64 -0
- data/lib/mongory/converters/converted.rb +81 -0
- data/lib/mongory/converters/data_converter.rb +37 -0
- data/lib/mongory/converters/key_converter.rb +87 -0
- data/lib/mongory/converters/value_converter.rb +52 -0
- data/lib/mongory/converters.rb +8 -0
- data/lib/mongory/matchers/abstract_matcher.rb +219 -0
- data/lib/mongory/matchers/abstract_multi_matcher.rb +124 -0
- data/lib/mongory/matchers/and_matcher.rb +72 -0
- data/lib/mongory/matchers/array_record_matcher.rb +93 -0
- data/lib/mongory/matchers/elem_match_matcher.rb +55 -0
- data/lib/mongory/matchers/eq_matcher.rb +46 -0
- data/lib/mongory/matchers/every_matcher.rb +56 -0
- data/lib/mongory/matchers/exists_matcher.rb +53 -0
- data/lib/mongory/matchers/field_matcher.rb +147 -0
- data/lib/mongory/matchers/gt_matcher.rb +41 -0
- data/lib/mongory/matchers/gte_matcher.rb +41 -0
- data/lib/mongory/matchers/hash_condition_matcher.rb +62 -0
- data/lib/mongory/matchers/in_matcher.rb +68 -0
- data/lib/mongory/matchers/literal_matcher.rb +121 -0
- data/lib/mongory/matchers/lt_matcher.rb +41 -0
- data/lib/mongory/matchers/lte_matcher.rb +41 -0
- data/lib/mongory/matchers/ne_matcher.rb +38 -0
- data/lib/mongory/matchers/nin_matcher.rb +68 -0
- data/lib/mongory/matchers/not_matcher.rb +40 -0
- data/lib/mongory/matchers/or_matcher.rb +68 -0
- data/lib/mongory/matchers/present_matcher.rb +55 -0
- data/lib/mongory/matchers/regex_matcher.rb +80 -0
- data/lib/mongory/matchers/size_matcher.rb +54 -0
- data/lib/mongory/matchers.rb +176 -0
- data/lib/mongory/mongoid.rb +19 -0
- data/lib/mongory/query_builder.rb +257 -0
- data/lib/mongory/query_matcher.rb +93 -0
- data/lib/mongory/query_operator.rb +28 -0
- data/lib/mongory/rails.rb +15 -0
- data/lib/mongory/utils/context.rb +48 -0
- data/lib/mongory/utils/debugger.rb +125 -0
- data/lib/mongory/utils/rails_patch.rb +22 -0
- data/lib/mongory/utils/singleton_builder.rb +31 -0
- data/lib/mongory/utils.rb +76 -0
- data/lib/mongory/version.rb +5 -0
- data/lib/mongory.rb +123 -0
- data/lib/mongory_ext.so +0 -0
- data/mongory.gemspec +62 -0
- data/scripts/build_with_core.sh +292 -0
- data/sig/mongory.rbs +4 -0
- metadata +159 -0
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongory
|
4
|
+
module Matchers
|
5
|
+
# LtMatcher implements the `$lt` (less than) operator.
|
6
|
+
#
|
7
|
+
# It returns true if the record is strictly less than the condition value.
|
8
|
+
#
|
9
|
+
# This matcher inherits from AbstractMatcher and uses the `<` operator.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# matcher = LtMatcher.build(10)
|
13
|
+
# matcher.match?(9) #=> true
|
14
|
+
# matcher.match?(10) #=> false
|
15
|
+
# matcher.match?(11) #=> false
|
16
|
+
#
|
17
|
+
# @see AbstractMatcher
|
18
|
+
class LtMatcher < AbstractMatcher
|
19
|
+
# Creates a raw Proc that performs the less-than comparison.
|
20
|
+
# The Proc uses the `<` operator to compare values.
|
21
|
+
#
|
22
|
+
# @return [Proc] A proc that performs less-than comparison with error handling
|
23
|
+
# @note The proc includes error handling for invalid comparisons
|
24
|
+
def raw_proc
|
25
|
+
condition = @condition
|
26
|
+
|
27
|
+
Proc.new do |record|
|
28
|
+
record < condition
|
29
|
+
rescue StandardError
|
30
|
+
false
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def priority
|
35
|
+
3
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
register(:lt, '$lt', LtMatcher)
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongory
|
4
|
+
module Matchers
|
5
|
+
# LteMatcher implements the `$lte` (less than or equal to) operator.
|
6
|
+
#
|
7
|
+
# It returns true if the record is less than or equal to the condition value.
|
8
|
+
#
|
9
|
+
# This matcher inherits from AbstractMatcher and uses the `<=` operator.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# matcher = LteMatcher.build(10)
|
13
|
+
# matcher.match?(9) #=> true
|
14
|
+
# matcher.match?(10) #=> true
|
15
|
+
# matcher.match?(11) #=> false
|
16
|
+
#
|
17
|
+
# @see AbstractMatcher
|
18
|
+
class LteMatcher < AbstractMatcher
|
19
|
+
# Creates a raw Proc that performs the less-than-or-equal comparison.
|
20
|
+
# The Proc uses the `<=` operator to compare values.
|
21
|
+
#
|
22
|
+
# @return [Proc] A proc that performs less-than-or-equal comparison with error handling
|
23
|
+
# @note The proc includes error handling for invalid comparisons
|
24
|
+
def raw_proc
|
25
|
+
condition = @condition
|
26
|
+
|
27
|
+
Proc.new do |record|
|
28
|
+
record <= condition
|
29
|
+
rescue StandardError
|
30
|
+
false
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def priority
|
35
|
+
3
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
register(:lte, '$lte', LteMatcher)
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongory
|
4
|
+
module Matchers
|
5
|
+
# NeMatcher implements the `$ne` (not equal) operator.
|
6
|
+
#
|
7
|
+
# It returns true if the record is *not equal* to the condition.
|
8
|
+
#
|
9
|
+
# This matcher inherits its logic from AbstractMatcher
|
10
|
+
# and uses Ruby's `!=` operator for comparison.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# matcher = NeMatcher.build(42)
|
14
|
+
# matcher.match?(41) #=> true
|
15
|
+
# matcher.match?(42) #=> false
|
16
|
+
#
|
17
|
+
# @see AbstractMatcher
|
18
|
+
class NeMatcher < AbstractMatcher
|
19
|
+
# Creates a raw Proc that performs the not-equal comparison.
|
20
|
+
# The Proc uses the `!=` operator to compare values.
|
21
|
+
#
|
22
|
+
# @return [Proc] A proc that performs not-equal comparison
|
23
|
+
def raw_proc
|
24
|
+
condition = @condition
|
25
|
+
|
26
|
+
Proc.new do |record|
|
27
|
+
record != condition
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def priority
|
32
|
+
1
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
register(:ne, '$ne', NeMatcher)
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongory
|
4
|
+
module Matchers
|
5
|
+
# NinMatcher implements the `$nin` (not in) operator.
|
6
|
+
#
|
7
|
+
# It succeeds only if the record does not match any value in the condition array.
|
8
|
+
# If the record is an array, it fails if any element overlaps with the condition.
|
9
|
+
# If the record is a single value (including `nil`), it fails if it is included in the condition.
|
10
|
+
#
|
11
|
+
# @example Match single value
|
12
|
+
# matcher = NinMatcher.build([1, 2, 3])
|
13
|
+
# matcher.match?(4) #=> true
|
14
|
+
# matcher.match?(2) #=> false
|
15
|
+
#
|
16
|
+
# @example Match nil
|
17
|
+
# matcher = NinMatcher.build([nil])
|
18
|
+
# matcher.match?(nil) #=> false
|
19
|
+
#
|
20
|
+
# @example Match with array
|
21
|
+
# matcher = NinMatcher.build([2, 4])
|
22
|
+
# matcher.match?([1, 3, 5]) #=> true
|
23
|
+
# matcher.match?([4, 5]) #=> false
|
24
|
+
#
|
25
|
+
# @see AbstractMatcher
|
26
|
+
class NinMatcher < AbstractMatcher
|
27
|
+
def self.build(condition, *args)
|
28
|
+
return super unless condition.is_a?(Range)
|
29
|
+
|
30
|
+
end_op = condition.exclude_end? ? '$gte' : '$gt'
|
31
|
+
head, tail = [condition.first, condition.last].sort
|
32
|
+
OrMatcher.build([{ '$lt' => head }, { end_op => tail }], *args)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Creates a raw Proc that performs the not-in matching operation.
|
36
|
+
# The Proc checks if the record has no elements in common with the condition array.
|
37
|
+
#
|
38
|
+
# @return [Proc] A proc that performs not-in matching
|
39
|
+
def raw_proc
|
40
|
+
condition = Set.new(@condition)
|
41
|
+
|
42
|
+
Proc.new do |record|
|
43
|
+
if record.is_a?(Array)
|
44
|
+
is_blank?(condition & record)
|
45
|
+
else
|
46
|
+
!condition.include?(record)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def priority
|
52
|
+
1 + Math.log(@condition.size + 1, 1.5)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Ensures the condition is a valid array or range.
|
56
|
+
#
|
57
|
+
# @raise [TypeError] if the condition is not an array nor a range
|
58
|
+
# @return [void]
|
59
|
+
def check_validity!
|
60
|
+
return if @condition.is_a?(Array)
|
61
|
+
|
62
|
+
raise TypeError, '$nin needs an array or range'
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
register(:nin, '$nin', NinMatcher)
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongory
|
4
|
+
module Matchers
|
5
|
+
# NotMatcher implements the `$not` logical operator.
|
6
|
+
#
|
7
|
+
# It returns true if the wrapped matcher fails, effectively inverting the result.
|
8
|
+
#
|
9
|
+
# It delegates to LiteralMatcher and simply negates the outcome.
|
10
|
+
#
|
11
|
+
# This allows constructs like:
|
12
|
+
# { age: { :$not => { :$gte => 30 } } }
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# matcher = NotMatcher.build({ :$gte => 10 })
|
16
|
+
# matcher.match?(5) #=> true
|
17
|
+
# matcher.match?(15) #=> false
|
18
|
+
#
|
19
|
+
# @see LiteralMatcher
|
20
|
+
class NotMatcher < LiteralMatcher
|
21
|
+
# Creates a raw Proc that performs the not-matching operation.
|
22
|
+
# The Proc inverts the result of the wrapped matcher.
|
23
|
+
#
|
24
|
+
# @return [Proc] A proc that performs not-matching
|
25
|
+
def raw_proc
|
26
|
+
super_proc = super
|
27
|
+
|
28
|
+
Proc.new do |record|
|
29
|
+
!super_proc.call(record)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def priority
|
34
|
+
1 + super
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
register(:not, '$not', NotMatcher)
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongory
|
4
|
+
module Matchers
|
5
|
+
# OrMatcher implements the `$or` logical operator.
|
6
|
+
#
|
7
|
+
# It evaluates an array of subconditions and returns true
|
8
|
+
# if *any one* of them matches. For empty conditions, it returns false
|
9
|
+
# (using FALSE_PROC).
|
10
|
+
#
|
11
|
+
# Each subcondition is handled by a HashConditionMatcher with conversion disabled,
|
12
|
+
# since the parent matcher already manages data conversion.
|
13
|
+
#
|
14
|
+
# This matcher inherits submatcher dispatch and evaluation logic
|
15
|
+
# from AbstractMultiMatcher.
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# matcher = OrMatcher.build([
|
19
|
+
# { age: { :$lt => 18 } },
|
20
|
+
# { admin: true }
|
21
|
+
# ])
|
22
|
+
# matcher.match?(record) #=> true if either condition matches
|
23
|
+
#
|
24
|
+
# @example Empty conditions
|
25
|
+
# matcher = OrMatcher.build([])
|
26
|
+
# matcher.match?(record) #=> false (uses FALSE_PROC)
|
27
|
+
#
|
28
|
+
# @see AbstractMultiMatcher
|
29
|
+
class OrMatcher < AbstractMultiMatcher
|
30
|
+
enable_unwrap!
|
31
|
+
|
32
|
+
# Creates a raw Proc that performs the or-matching operation.
|
33
|
+
# The Proc combines all submatcher Procs and returns true if any match.
|
34
|
+
# For empty conditions, returns FALSE_PROC.
|
35
|
+
#
|
36
|
+
# @return [Proc] a Proc that performs the or-matching operation
|
37
|
+
def raw_proc
|
38
|
+
combine_procs_with_or(*matchers.map(&:to_proc))
|
39
|
+
end
|
40
|
+
|
41
|
+
# Builds an array of matchers from the subconditions.
|
42
|
+
# Each subcondition is wrapped in a HashConditionMatcher.
|
43
|
+
#
|
44
|
+
# @return [Array<AbstractMatcher>] array of submatchers
|
45
|
+
define_instance_cache_method(:matchers) do
|
46
|
+
@condition.map do |condition|
|
47
|
+
HashConditionMatcher.build(condition, context: @context)
|
48
|
+
end.sort_by(&:priority)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Ensures the condition is an array of hashes.
|
52
|
+
#
|
53
|
+
# @raise [Mongory::TypeError] if not valid
|
54
|
+
# @return [void]
|
55
|
+
def check_validity!
|
56
|
+
raise TypeError, '$or needs an array' unless @condition.is_a?(Array)
|
57
|
+
|
58
|
+
@condition.each do |sub_condition|
|
59
|
+
raise TypeError, '$or needs an array of hash' unless sub_condition.is_a?(Hash)
|
60
|
+
end
|
61
|
+
|
62
|
+
super
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
register(:or, '$or', OrMatcher)
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongory
|
4
|
+
module Matchers
|
5
|
+
# PresentMatcher implements the `$present` operator.
|
6
|
+
#
|
7
|
+
# It returns true if the record value is considered "present"
|
8
|
+
# (i.e., not nil, not empty, not KEY_NOT_FOUND), and matches
|
9
|
+
# the expected boolean condition.
|
10
|
+
#
|
11
|
+
# This is similar to `$exists`, but evaluates truthiness
|
12
|
+
# of the value instead of mere existence.
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# matcher = PresentMatcher.build(true)
|
16
|
+
# matcher.match?('hello') #=> true
|
17
|
+
# matcher.match?(nil) #=> false
|
18
|
+
# matcher.match?([]) #=> false
|
19
|
+
#
|
20
|
+
# matcher = PresentMatcher.build(false)
|
21
|
+
# matcher.match?(nil) #=> true
|
22
|
+
#
|
23
|
+
# @see AbstractMatcher
|
24
|
+
class PresentMatcher < AbstractMatcher
|
25
|
+
# Creates a raw Proc that performs the presence check.
|
26
|
+
# The Proc checks if the record's presence matches the condition.
|
27
|
+
#
|
28
|
+
# @return [Proc] A proc that performs presence check
|
29
|
+
def raw_proc
|
30
|
+
condition = @condition
|
31
|
+
|
32
|
+
Proc.new do |record|
|
33
|
+
record = nil if record == KEY_NOT_FOUND
|
34
|
+
is_present?(record) == condition
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def priority
|
39
|
+
2
|
40
|
+
end
|
41
|
+
|
42
|
+
# Ensures that the condition value is a boolean.
|
43
|
+
#
|
44
|
+
# @raise [TypeError] if condition is not true or false
|
45
|
+
# @return [void]
|
46
|
+
def check_validity!
|
47
|
+
return if [true, false].include?(@condition)
|
48
|
+
|
49
|
+
raise TypeError, '$present needs a boolean'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
register(:present, '$present', PresentMatcher)
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongory
|
4
|
+
module Matchers
|
5
|
+
# RegexMatcher implements the `$regex` operator and also handles raw Regexp values.
|
6
|
+
#
|
7
|
+
# This matcher checks whether a string record matches a regular expression.
|
8
|
+
# It supports both:
|
9
|
+
# - Explicit queries using `:field.regex => /pattern/i`
|
10
|
+
# - Implicit literal Regexp values like `{ field: /pattern/i }`
|
11
|
+
#
|
12
|
+
# If a string is provided instead of a Regexp, it will be converted via `Regexp.new(...)`.
|
13
|
+
# This ensures consistent behavior for queries like `:field.regex => "foo"` and `:field.regex => /foo/`.
|
14
|
+
#
|
15
|
+
# @example Basic regex matching
|
16
|
+
# matcher = RegexMatcher.build(/^foo/)
|
17
|
+
# matcher.match?('foobar') #=> true
|
18
|
+
# matcher.match?('barfoo') #=> false
|
19
|
+
#
|
20
|
+
# @example Case-insensitive matching
|
21
|
+
# matcher = RegexMatcher.build(/admin/i)
|
22
|
+
# matcher.match?("ADMIN") #=> true
|
23
|
+
#
|
24
|
+
# @example String pattern
|
25
|
+
# matcher = RegexMatcher.build("^foo")
|
26
|
+
# matcher.match?("foobar") #=> true
|
27
|
+
#
|
28
|
+
# @example Non-string input
|
29
|
+
# matcher = RegexMatcher.build(/\d+/)
|
30
|
+
# matcher.match?(123) #=> false (not a string)
|
31
|
+
#
|
32
|
+
# @see AbstractMatcher
|
33
|
+
class RegexMatcher < AbstractMatcher
|
34
|
+
# Initializes the matcher with a regex pattern.
|
35
|
+
# Converts string patterns to Regexp objects.
|
36
|
+
#
|
37
|
+
# @param condition [String, Regexp] the regex pattern to match against
|
38
|
+
# @param context [Context] the query context
|
39
|
+
# @raise [TypeError] if condition is not a string or Regexp
|
40
|
+
def initialize(condition, context: Context.new)
|
41
|
+
super
|
42
|
+
@condition = Regexp.new(condition) if condition.is_a?(String)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Creates a raw Proc that performs the regex matching operation.
|
46
|
+
# The Proc checks if the record is a string that matches the pattern.
|
47
|
+
# Returns false for non-string inputs or if the match fails.
|
48
|
+
#
|
49
|
+
# @return [Proc] a Proc that performs regex matching
|
50
|
+
def raw_proc
|
51
|
+
condition = @condition
|
52
|
+
|
53
|
+
Proc.new do |record|
|
54
|
+
next false unless record.is_a?(String)
|
55
|
+
|
56
|
+
record.match?(condition)
|
57
|
+
rescue StandardError
|
58
|
+
false
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def priority
|
63
|
+
@condition.source.start_with?('^') ? 8 : 20
|
64
|
+
end
|
65
|
+
|
66
|
+
# Ensures the condition is a valid regex pattern (Regexp or String).
|
67
|
+
#
|
68
|
+
# @raise [TypeError] if condition is not a string or Regexp
|
69
|
+
# @return [void]
|
70
|
+
def check_validity!
|
71
|
+
return if @condition.is_a?(Regexp)
|
72
|
+
return if @condition.is_a?(String)
|
73
|
+
|
74
|
+
raise TypeError, '$regex needs a Regexp or string'
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
register(:regex, '$regex', RegexMatcher)
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongory
|
4
|
+
module Matchers
|
5
|
+
# Matcher for the `$size` operator.
|
6
|
+
#
|
7
|
+
# This matcher expects the input to be an array, and delegates the comparison
|
8
|
+
# to a literal matcher using the array's size as the value.
|
9
|
+
#
|
10
|
+
# For example, the condition `{ tags: { '$size' => 3 } }` will match any
|
11
|
+
# document where `tags` is an array of length 3.
|
12
|
+
#
|
13
|
+
# ### Supported compound usages:
|
14
|
+
#
|
15
|
+
# ```ruby
|
16
|
+
# Mongory.where(tags: { '$size' => 3 }) # exactly 3 elements
|
17
|
+
# Mongory.where(tags: { '$size' => { '$gt' => 1 } }) # more than 1
|
18
|
+
# Mongory.where(comments: { '$size' => { '$gt' => 1, '$lte' => 5 } }) # more than 1, up to 5 elements
|
19
|
+
# Mongory.where(tags: { '$size' => { '$in' => [1, 2, 3] } }) # 1, 2, or 3 elements
|
20
|
+
# ```
|
21
|
+
#
|
22
|
+
# @see LiteralMatcher
|
23
|
+
#
|
24
|
+
# @note Ruby's Symbol class already defines a `#size` method,
|
25
|
+
# that will return the size of the symbol object.
|
26
|
+
# So, this is the only operator that cannot be used with
|
27
|
+
# the symbol snippet syntax (e.g. `:tags.size`).
|
28
|
+
#
|
29
|
+
# Use string key syntax instead: `:"tags.$size" => ...`
|
30
|
+
class SizeMatcher < LiteralMatcher
|
31
|
+
# Creates a raw Proc that performs the size matching operation.
|
32
|
+
#
|
33
|
+
# The returned Proc checks if the input is an Array. If so, it calculates
|
34
|
+
# the array's size and passes it to the wrapped literal matcher Proc.
|
35
|
+
#
|
36
|
+
# @return [Proc] A proc that performs size-based matching
|
37
|
+
def raw_proc
|
38
|
+
super_proc = super
|
39
|
+
|
40
|
+
Proc.new do |record|
|
41
|
+
next false unless record.is_a?(Array)
|
42
|
+
|
43
|
+
super_proc.call(record.size)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def priority
|
48
|
+
2 + super
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
register(:size, '$size', SizeMatcher)
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongory
|
4
|
+
# Provides matcher registration and operator-to-class lookup for query evaluation.
|
5
|
+
#
|
6
|
+
# This module is responsible for:
|
7
|
+
# - Mapping Mongo-style operators like "$gt" to matcher classes
|
8
|
+
# - Dynamically extending Symbol with query operator snippets (e.g., :age.gt)
|
9
|
+
# - Safely isolating symbol extension behind an explicit opt-in flag
|
10
|
+
#
|
11
|
+
# Matchers are registered using `Matchers.registry(method_sym, operator, klass)`
|
12
|
+
# and can be looked up via `Matchers.lookup(operator)`.
|
13
|
+
#
|
14
|
+
# Symbol snippets are only enabled if `Matchers.enable_symbol_snippets!` is called,
|
15
|
+
# preventing namespace pollution unless explicitly requested.
|
16
|
+
module Matchers
|
17
|
+
@operator_mapping = {}
|
18
|
+
@registries = []
|
19
|
+
|
20
|
+
# Registers a matcher class for a given operator and method symbol.
|
21
|
+
#
|
22
|
+
# @param method_sym [Symbol] the method name to be added to Symbol (e.g., :gt)
|
23
|
+
# @param operator [String] the Mongo-style operator (e.g., "$gt")
|
24
|
+
# @param klass [Class] the matcher class to associate with the operator
|
25
|
+
# @return [void]
|
26
|
+
# @raise [ArgumentError] if validations fail
|
27
|
+
def self.register(method_sym, operator, klass)
|
28
|
+
Validator.validate_method(method_sym)
|
29
|
+
Validator.validate_operator(operator)
|
30
|
+
Validator.validate_class(klass)
|
31
|
+
|
32
|
+
@operator_mapping[operator] = klass
|
33
|
+
registry = Registry.new(method_sym, operator)
|
34
|
+
@registries << registry
|
35
|
+
return unless @enable_symbol_snippets
|
36
|
+
|
37
|
+
registry.apply!
|
38
|
+
end
|
39
|
+
|
40
|
+
# Enables dynamic symbol snippet generation for registered operators.
|
41
|
+
# This defines methods like `:age.gt => QueryOperator.new(...)`.
|
42
|
+
#
|
43
|
+
# @return [void]
|
44
|
+
def self.enable_symbol_snippets!
|
45
|
+
@enable_symbol_snippets = true
|
46
|
+
@registries.each(&:apply!)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Retrieves the matcher class associated with a Mongo-style operator.
|
50
|
+
#
|
51
|
+
# @param operator [String]
|
52
|
+
# @return [Class, nil] the registered matcher class or nil if not found
|
53
|
+
def self.lookup(operator)
|
54
|
+
@operator_mapping[operator]
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns all registered operator keys.
|
58
|
+
#
|
59
|
+
# @return [Array<String>]
|
60
|
+
def self.operators
|
61
|
+
@operator_mapping.keys
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.freeze
|
65
|
+
super
|
66
|
+
@operator_mapping.freeze
|
67
|
+
@registries.freeze
|
68
|
+
end
|
69
|
+
|
70
|
+
# @private
|
71
|
+
#
|
72
|
+
# Internal helper module used by `Matchers.registry` to validate matcher registration parameters.
|
73
|
+
#
|
74
|
+
# This includes:
|
75
|
+
# - Ensuring operators are valid Mongo-style strings (e.g., "$gt")
|
76
|
+
# - Verifying matcher class inheritance
|
77
|
+
# - Enforcing naming rules for symbol snippets (e.g., :gt, :not_match)
|
78
|
+
#
|
79
|
+
# These validations protect against incorrect matcher setup and prevent unsafe symbol definitions.
|
80
|
+
#
|
81
|
+
# @see Matchers.registry
|
82
|
+
module Validator
|
83
|
+
# Validates the given operator string.
|
84
|
+
# Ensures it matches the Mongo-style format like "$gt".
|
85
|
+
# Warns on duplicate registration.
|
86
|
+
#
|
87
|
+
# @param operator [String]
|
88
|
+
# @return [void]
|
89
|
+
# @raise [Mongory::TypeError] if operator format is invalid
|
90
|
+
def self.validate_operator(operator)
|
91
|
+
if Matchers.lookup(operator)
|
92
|
+
warn "Duplicate operator registration: #{operator} (#{Matchers.lookup(operator)} vs #{klass})"
|
93
|
+
end
|
94
|
+
|
95
|
+
return if operator.is_a?(String) && operator.match?(/^\$[a-z]+([A-Z][a-z]+)*$/)
|
96
|
+
|
97
|
+
raise Mongory::TypeError, "Operator must match /^\$[a-z]+([A-Z][a-z]*)*$/, but got #{operator.inspect}"
|
98
|
+
end
|
99
|
+
|
100
|
+
# Validates the matcher class to ensure it is a subclass of AbstractMatcher.
|
101
|
+
#
|
102
|
+
# @param klass [Class]
|
103
|
+
# @return [void]
|
104
|
+
# @raise [Mongory::TypeError] if class is not valid
|
105
|
+
def self.validate_class(klass)
|
106
|
+
return if klass.is_a?(Class) && klass < AbstractMatcher
|
107
|
+
|
108
|
+
raise Mongory::TypeError, "Matcher class must be a subclass of AbstractMatcher, but got #{klass}"
|
109
|
+
end
|
110
|
+
|
111
|
+
# Validates the method symbol to ensure it is a valid lowercase underscore symbol (e.g., :gt, :not_match).
|
112
|
+
#
|
113
|
+
# @param method_sym [Symbol]
|
114
|
+
# @return [void]
|
115
|
+
# @raise [Mongory::TypeError] if symbol format is invalid
|
116
|
+
def self.validate_method(method_sym)
|
117
|
+
return if method_sym.is_a?(Symbol) && method_sym.match?(/^([a-z]+_)*[a-z]+$/)
|
118
|
+
|
119
|
+
raise Mongory::TypeError, "Method symbol must match /^([a-z]+_)*[a-z]+$/, but got #{method_sym.inspect}"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# @private
|
124
|
+
#
|
125
|
+
# Internal helper representing a registration of an operator and its associated symbol snippet method.
|
126
|
+
# Used to delay method definition on Symbol until explicitly enabled.
|
127
|
+
#
|
128
|
+
# Each instance holds:
|
129
|
+
# - the method symbol (e.g., `:gt`)
|
130
|
+
# - the corresponding Mongo-style operator (e.g., `"$gt"`)
|
131
|
+
#
|
132
|
+
# These instances are collected and replayed upon calling `Matchers.enable_symbol_snippets!`.
|
133
|
+
#
|
134
|
+
# @!attribute method_sym
|
135
|
+
# @return [Symbol] the symbol method name (e.g., :in, :gt, :exists)
|
136
|
+
# @!attribute operator
|
137
|
+
# @return [String] the Mongo-style operator this snippet maps to (e.g., "$in")
|
138
|
+
Registry = Struct.new(:method_sym, :operator) do
|
139
|
+
# Defines a method on Symbol to support operator snippet expansion.
|
140
|
+
#
|
141
|
+
# @return [void]
|
142
|
+
def apply!
|
143
|
+
return if Symbol.method_defined?(method_sym)
|
144
|
+
|
145
|
+
operator = operator()
|
146
|
+
Symbol.define_method(method_sym) do
|
147
|
+
Mongory::QueryOperator.new(to_s, operator)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
require_relative 'matchers/abstract_matcher'
|
155
|
+
require_relative 'matchers/abstract_multi_matcher'
|
156
|
+
require_relative 'matchers/literal_matcher'
|
157
|
+
require_relative 'matchers/hash_condition_matcher'
|
158
|
+
require_relative 'matchers/and_matcher'
|
159
|
+
require_relative 'matchers/array_record_matcher'
|
160
|
+
require_relative 'matchers/elem_match_matcher'
|
161
|
+
require_relative 'matchers/every_matcher'
|
162
|
+
require_relative 'matchers/eq_matcher'
|
163
|
+
require_relative 'matchers/exists_matcher'
|
164
|
+
require_relative 'matchers/gt_matcher'
|
165
|
+
require_relative 'matchers/gte_matcher'
|
166
|
+
require_relative 'matchers/in_matcher'
|
167
|
+
require_relative 'matchers/field_matcher'
|
168
|
+
require_relative 'matchers/lt_matcher'
|
169
|
+
require_relative 'matchers/lte_matcher'
|
170
|
+
require_relative 'matchers/ne_matcher'
|
171
|
+
require_relative 'matchers/nin_matcher'
|
172
|
+
require_relative 'matchers/not_matcher'
|
173
|
+
require_relative 'matchers/or_matcher'
|
174
|
+
require_relative 'matchers/present_matcher'
|
175
|
+
require_relative 'matchers/regex_matcher'
|
176
|
+
require_relative 'matchers/size_matcher'
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongory
|
4
|
+
# Only loaded when Mongoid is present
|
5
|
+
module MongoidPatch
|
6
|
+
# Regist Mongoid operator key object into KeyConverter
|
7
|
+
# @see Converters::KeyConverter
|
8
|
+
# @return [void]
|
9
|
+
def self.patch!
|
10
|
+
kc = Mongory::Converters::KeyConverter.instance
|
11
|
+
# It's Mongoid built-in key operator that born from `:key.gt`
|
12
|
+
kc.register(::Mongoid::Criteria::Queryable::Key) do |v|
|
13
|
+
kc.convert(@name.to_s, @operator => v)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
patch!
|
18
|
+
end
|
19
|
+
end
|