mongo-parser-rb 0.0.11 → 0.0.12
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 +6 -0
- data/lib/mongo-parser-rb.rb +5 -5
- data/lib/mongo-parser-rb/field.rb +0 -20
- data/lib/mongo-parser-rb/query.rb +23 -6
- data/lib/mongo-parser-rb/query/expression.rb +15 -71
- data/lib/mongo-parser-rb/version.rb +1 -1
- data/performance/perf.rb +49 -0
- data/test/mongo-parser-rb/field_test.rb +12 -12
- data/test/mongo-parser-rb/query_test.rb +18 -6
- metadata +8 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 377864fec17861222eb64d45223d527e95e1fc53
|
4
|
+
data.tar.gz: 96bbf223ea627086c2107a43e720b877986b5587
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4625ff07d26acb1a8e632b5e02ccbe8c3965a03dd3dd5ecdcd6d008e29be1f361dcd23b00f65237322e791e82d6895e58db9454a944ee2bc36166f57869eb7ed
|
7
|
+
data.tar.gz: 2b35a891d28081c32956e64a5efef2bd9692b7bb5a2d68ba1b05823f91ce4609e353fccb4e429f1a8aa8932d3f2b2230a97825819efad38aa7eb7ee6b0827def
|
data/Rakefile
CHANGED
data/lib/mongo-parser-rb.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
require_relative "mongo-parser-rb/version"
|
2
|
+
require_relative "mongo-parser-rb/exceptions"
|
3
|
+
require_relative "mongo-parser-rb/field"
|
4
|
+
require_relative "mongo-parser-rb/query/expression"
|
5
|
+
require_relative "mongo-parser-rb/query"
|
6
6
|
|
7
7
|
module MongoParserRB
|
8
8
|
end
|
@@ -7,7 +7,6 @@ module MongoParserRB
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def value_in_document(document)
|
10
|
-
document = stringify_keys(document)
|
11
10
|
@field_parts.reduce(document) do |value, field|
|
12
11
|
case value
|
13
12
|
when Array
|
@@ -22,8 +21,6 @@ module MongoParserRB
|
|
22
21
|
end
|
23
22
|
|
24
23
|
def in_document?(document)
|
25
|
-
document = stringify_keys(document)
|
26
|
-
|
27
24
|
@field_parts.reduce(document) do |value, field|
|
28
25
|
return false unless value.has_key?(field)
|
29
26
|
value[field]
|
@@ -32,22 +29,5 @@ module MongoParserRB
|
|
32
29
|
true
|
33
30
|
end
|
34
31
|
|
35
|
-
private
|
36
|
-
|
37
|
-
def stringify_keys(document)
|
38
|
-
document.reduce({}) do |new_document, (k,v)|
|
39
|
-
new_document[k.to_s] = case v
|
40
|
-
when Hash
|
41
|
-
stringify_keys(v)
|
42
|
-
when Array
|
43
|
-
v.map { |e| e.kind_of?(Hash) ? stringify_keys(e) : e }
|
44
|
-
else
|
45
|
-
v
|
46
|
-
end
|
47
|
-
|
48
|
-
new_document
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
32
|
end
|
53
33
|
end
|
@@ -31,9 +31,11 @@ module MongoParserRB
|
|
31
31
|
# @return [Boolean]
|
32
32
|
def matches_document?(document)
|
33
33
|
raise NotParsedError, "Query not parsed (run parse!)" if @expression_tree.nil?
|
34
|
+
|
35
|
+
document = stringify_keys(document)
|
34
36
|
@expression_tree.evaluate(document)
|
35
37
|
end
|
36
|
-
|
38
|
+
|
37
39
|
private
|
38
40
|
|
39
41
|
def parse_root_expression(query, field = nil)
|
@@ -43,17 +45,17 @@ module MongoParserRB
|
|
43
45
|
end
|
44
46
|
|
45
47
|
def parse_sub_expression(key, value, field = nil)
|
46
|
-
if Expression.
|
47
|
-
case key
|
48
|
-
when *Expression
|
48
|
+
if Expression::ALL_OPERATORS.include?(key)
|
49
|
+
case key
|
50
|
+
when *Expression::CONJUNCTION_OPERATORS
|
49
51
|
Expression.new(key, value.map { |v| parse_root_expression(v) })
|
50
|
-
when *Expression
|
52
|
+
when *Expression::INVERSION_OPERATORS
|
51
53
|
if value.kind_of?(Hash)
|
52
54
|
Expression.new(:$not, field, parse_root_expression(value, field))
|
53
55
|
else
|
54
56
|
Expression.new(:$not, field, Expression.new(:$eq, field, value))
|
55
57
|
end
|
56
|
-
when *Expression
|
58
|
+
when *Expression::ELEM_MATCH_OPERATORS
|
57
59
|
Expression.new(:$elemMatch, field, value.to_a.map do |(key, value)|
|
58
60
|
parse_sub_expression(key, value)
|
59
61
|
end)
|
@@ -67,5 +69,20 @@ module MongoParserRB
|
|
67
69
|
end
|
68
70
|
end
|
69
71
|
|
72
|
+
def stringify_keys(document)
|
73
|
+
document.reduce({}) do |new_document, (k,v)|
|
74
|
+
new_document[k.to_s] = case v
|
75
|
+
when Hash
|
76
|
+
stringify_keys(v)
|
77
|
+
when Array
|
78
|
+
v.map { |e| e.kind_of?(Hash) ? stringify_keys(e) : e }
|
79
|
+
else
|
80
|
+
v
|
81
|
+
end
|
82
|
+
|
83
|
+
new_document
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
70
87
|
end
|
71
88
|
end
|
@@ -2,71 +2,17 @@ module MongoParserRB
|
|
2
2
|
class Query
|
3
3
|
class Expression
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
end
|
12
|
-
|
13
|
-
def inversion_operator?(operator)
|
14
|
-
inversion_operators.include?(operator)
|
15
|
-
end
|
16
|
-
|
17
|
-
def conjunction_operators
|
18
|
-
@conjunction_operators ||= [
|
19
|
-
:$and,
|
20
|
-
:$or
|
21
|
-
]
|
22
|
-
end
|
23
|
-
|
24
|
-
def conjunction_operator?(operator)
|
25
|
-
conjunction_operators.include?(operator)
|
26
|
-
end
|
27
|
-
|
28
|
-
def negative_equality_operators
|
29
|
-
@negative_equality_operators ||= [
|
30
|
-
:$nin,
|
31
|
-
:$ne
|
32
|
-
]
|
33
|
-
end
|
34
|
-
|
35
|
-
def equality_operators
|
36
|
-
@equality_operators ||= [
|
37
|
-
:$eq,
|
38
|
-
:$gt,
|
39
|
-
:$lt,
|
40
|
-
:$gte,
|
41
|
-
:$lte,
|
42
|
-
:$in
|
43
|
-
] | negative_equality_operators
|
44
|
-
end
|
45
|
-
|
46
|
-
def equality_operator?(operator)
|
47
|
-
equality_operators.include?(operator)
|
48
|
-
end
|
49
|
-
|
50
|
-
def elemMatch_operators
|
51
|
-
@elemMatch_operators ||= [
|
52
|
-
:$elemMatch
|
53
|
-
]
|
54
|
-
end
|
55
|
-
|
56
|
-
def elemMatch_operator?(operator)
|
57
|
-
elemMatch_operators.include?(operator)
|
58
|
-
end
|
59
|
-
|
60
|
-
def operator?(operator)
|
61
|
-
equality_operator?(operator) || conjunction_operator?(operator) || inversion_operator?(operator) || elemMatch_operator?(operator)
|
62
|
-
end
|
63
|
-
|
64
|
-
end
|
5
|
+
INVERSION_OPERATORS = [:$not].freeze
|
6
|
+
CONJUNCTION_OPERATORS = [:$and, :$or].freeze
|
7
|
+
NEGATIVE_EQUALITY_OPERATORS = [:$nin, :$ne].freeze
|
8
|
+
EQUALITY_OPERATORS = [:$eq, :$gt, :$lt, :$gte, :$lte, :$in, :$nin, :$ne].freeze
|
9
|
+
ELEM_MATCH_OPERATORS = [:$elemMatch].freeze
|
10
|
+
ALL_OPERATORS = [:$eq, :$gt, :$lt, :$gte, :$lte, :$in, :$nin, :$ne, :$and, :$or, :$not, :$elemMatch].freeze
|
65
11
|
|
66
12
|
def initialize(operator, *args)
|
67
13
|
@operator = operator
|
68
14
|
|
69
|
-
if
|
15
|
+
if CONJUNCTION_OPERATORS.include?(@operator)
|
70
16
|
@arguments = args[0]
|
71
17
|
else
|
72
18
|
@field = Field.new(args[0])
|
@@ -76,15 +22,15 @@ module MongoParserRB
|
|
76
22
|
|
77
23
|
def evaluate(document)
|
78
24
|
case @operator
|
79
|
-
when *Expression
|
25
|
+
when *Expression::CONJUNCTION_OPERATORS
|
80
26
|
evaluate_conjunction(document)
|
81
|
-
when *Expression
|
27
|
+
when *Expression::NEGATIVE_EQUALITY_OPERATORS
|
82
28
|
evaluate_negative_equality(document)
|
83
|
-
when *Expression
|
29
|
+
when *Expression::EQUALITY_OPERATORS
|
84
30
|
evaluate_equality(document)
|
85
|
-
when *Expression
|
31
|
+
when *Expression::INVERSION_OPERATORS
|
86
32
|
evaluate_inversion(document)
|
87
|
-
when *Expression
|
33
|
+
when *Expression::ELEM_MATCH_OPERATORS
|
88
34
|
evaluate_elemMatch(document)
|
89
35
|
end
|
90
36
|
rescue NoMethodError, TypeError
|
@@ -100,7 +46,7 @@ module MongoParserRB
|
|
100
46
|
end
|
101
47
|
end
|
102
48
|
end
|
103
|
-
|
49
|
+
|
104
50
|
def evaluate_inversion(document)
|
105
51
|
# Mongo negative equality operators return true when
|
106
52
|
# the specified field does not exist on a document.
|
@@ -130,8 +76,7 @@ module MongoParserRB
|
|
130
76
|
# the specified field does not exist on a document.
|
131
77
|
return true if !value_for_field && !@field.in_document?(document)
|
132
78
|
|
133
|
-
if value_for_field.kind_of?(Array) &&
|
134
|
-
!@arguments.kind_of?(Array)
|
79
|
+
if value_for_field.kind_of?(Array) && !@arguments.kind_of?(Array)
|
135
80
|
!value_for_field.include?(@arguments)
|
136
81
|
else
|
137
82
|
value_for_field != @arguments
|
@@ -152,8 +97,7 @@ module MongoParserRB
|
|
152
97
|
when :$eq
|
153
98
|
if @arguments.kind_of?(Regexp)
|
154
99
|
!!(value_for_field =~ @arguments)
|
155
|
-
elsif value_for_field.kind_of?(Array) &&
|
156
|
-
!@arguments.kind_of?(Array)
|
100
|
+
elsif value_for_field.kind_of?(Array) && !@arguments.kind_of?(Array)
|
157
101
|
value_for_field.include?(@arguments)
|
158
102
|
else
|
159
103
|
value_for_field == @arguments
|
data/performance/perf.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
require_relative '../lib/mongo-parser-rb'
|
3
|
+
|
4
|
+
iterations = 500_000
|
5
|
+
|
6
|
+
Benchmark.bm(22) do |bm|
|
7
|
+
bm.report('simple query parsing') do
|
8
|
+
query_hash = {
|
9
|
+
:$or => [
|
10
|
+
{ aaa: { :$gt => 20 } },
|
11
|
+
{ bbb: {:$not => {:$gt => 10 }} },
|
12
|
+
{ ccc: 11 },
|
13
|
+
{ ddd: 12 },
|
14
|
+
{ eee: 13 },
|
15
|
+
{ :fff => { :$in => [1] } }
|
16
|
+
]
|
17
|
+
}
|
18
|
+
|
19
|
+
iterations.times do
|
20
|
+
MongoParserRB::Query.parse(query_hash)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
bm.report('simple query matching') do
|
25
|
+
query = MongoParserRB::Query.parse({
|
26
|
+
:$or => [
|
27
|
+
{ aaa: { :$gt => 20 } },
|
28
|
+
{ bbb: 10 },
|
29
|
+
{ ccc: 11 },
|
30
|
+
{ ddd: 12 },
|
31
|
+
{ eee: 13 }
|
32
|
+
]
|
33
|
+
})
|
34
|
+
|
35
|
+
document = {
|
36
|
+
aaa: 1, bbb: 2, ccc: 3, ddd: 4, eee: 5,
|
37
|
+
nested1: {
|
38
|
+
aaa: 1, bbb: 2, ccc: 3, ddd: 4, eee: 5,
|
39
|
+
nested: {
|
40
|
+
aaa: 1, bbb: 2, ccc: 3, ddd: 4, eee: 5
|
41
|
+
}
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
iterations.times do
|
46
|
+
query.matches_document?(document)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -1,41 +1,41 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
|
-
class FieldTest <
|
3
|
+
class FieldTest < Minitest::Test
|
4
4
|
|
5
5
|
def test_returns_value_when_key_present
|
6
6
|
field = MongoParserRB::Field.new(:"custom_data.tracked_users")
|
7
|
-
assert_equal 10, field.value_in_document(
|
7
|
+
assert_equal 10, field.value_in_document('custom_data' => {'tracked_users' => 10})
|
8
8
|
end
|
9
9
|
|
10
10
|
def test_returns_nil_when_key_not_present
|
11
11
|
field = MongoParserRB::Field.new(:"custom_data.tracked_users")
|
12
|
-
assert_nil field.value_in_document(
|
13
|
-
assert_nil field.value_in_document(
|
12
|
+
assert_nil field.value_in_document('custom_data' => {'something_else' => 10})
|
13
|
+
assert_nil field.value_in_document('name' => "Ben")
|
14
14
|
end
|
15
15
|
|
16
16
|
def test_document_has_a_field
|
17
17
|
field = MongoParserRB::Field.new(:"custom_data.tracked_users")
|
18
|
-
assert field.in_document?(
|
19
|
-
refute field.in_document?(
|
18
|
+
assert field.in_document?('custom_data' => {'tracked_users' => 10})
|
19
|
+
refute field.in_document?('not_custom_data' => {'tracked_users' => 10})
|
20
20
|
|
21
21
|
field = MongoParserRB::Field.new(:"session_count")
|
22
|
-
assert field.in_document?(
|
23
|
-
refute field.in_document?(
|
22
|
+
assert field.in_document?('custom_data' => {'tracked_users' => 10}, 'session_count' => 5)
|
23
|
+
refute field.in_document?('custom_data' => {'tracked_users' => 10})
|
24
24
|
end
|
25
25
|
|
26
26
|
def test_returning_array_element
|
27
27
|
field = MongoParserRB::Field.new(:"something.array.0")
|
28
|
-
assert_equal 1, field.value_in_document(
|
28
|
+
assert_equal 1, field.value_in_document('something' => {'array' => [1,2]})
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
def test_returning_empty_array
|
32
32
|
field = MongoParserRB::Field.new(:"something.array.name")
|
33
|
-
assert_equal [], field.value_in_document(
|
33
|
+
assert_equal [], field.value_in_document('something' => {'array' => []})
|
34
34
|
end
|
35
35
|
|
36
36
|
def test_returning_hash_in_array
|
37
37
|
field = MongoParserRB::Field.new(:"something.array.0.key")
|
38
|
-
document = {
|
38
|
+
document = {'something' => {'array' => [{'key' => 'hello world'}, {'key' => 'bye world'}]}}
|
39
39
|
assert_equal 'hello world', field.value_in_document(document)
|
40
40
|
|
41
41
|
field = MongoParserRB::Field.new(:"something.array.1.key")
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
|
-
class QueryTest <
|
3
|
+
class QueryTest < Minitest::Test
|
4
4
|
def test_raises_if_not_parsed
|
5
5
|
assert_raises(MongoParserRB::NotParsedError) do
|
6
6
|
query = MongoParserRB::Query.new(:integer_key => 10)
|
@@ -113,6 +113,18 @@ class QueryTest < MiniTest::Unit::TestCase
|
|
113
113
|
refute query.matches_document?(:integer_key => 5)
|
114
114
|
end
|
115
115
|
|
116
|
+
def test_nested_or
|
117
|
+
query = MongoParserRB::Query.parse({
|
118
|
+
:$or=>[{ :name => 'cormac' }, { :name => 'john' }],
|
119
|
+
:anonymous => { :$in => [false, nil] },
|
120
|
+
:app_id => 6
|
121
|
+
})
|
122
|
+
assert query.matches_document?(:name => 'cormac', :app_id => 6)
|
123
|
+
assert query.matches_document?(:name => 'john', :app_id => 6)
|
124
|
+
refute query.matches_document?(:name => 'john', :app_id => 7)
|
125
|
+
refute query.matches_document?(:name => 'john', :app_id => 6, :anonymous => true)
|
126
|
+
end
|
127
|
+
|
116
128
|
def test_boolean_eq
|
117
129
|
query = MongoParserRB::Query.parse(:boolean_key => false)
|
118
130
|
assert query.matches_document?(:boolean_key => false)
|
@@ -137,12 +149,12 @@ class QueryTest < MiniTest::Unit::TestCase
|
|
137
149
|
refute query.matches_document?(:array_key => [1,4,5])
|
138
150
|
end
|
139
151
|
|
140
|
-
def test_nil_by_absence_nin
|
152
|
+
def test_nil_by_absence_nin
|
141
153
|
query = MongoParserRB::Query.parse(:"custom_data.value" => {:$nin => [nil]})
|
142
154
|
assert query.matches_document?(:custom_data => {:value => 5})
|
143
155
|
refute query.matches_document?({})
|
144
156
|
end
|
145
|
-
|
157
|
+
|
146
158
|
def test_nil_by_empty
|
147
159
|
query = MongoParserRB::Query.parse({:"user_event_summaries.name" => {:$ne => "second-support"}})
|
148
160
|
assert query.matches_document?({"user_event_summaries" => []})
|
@@ -224,19 +236,19 @@ class QueryTest < MiniTest::Unit::TestCase
|
|
224
236
|
query = MongoParserRB::Query.parse(:array_key => {:$ne => [1]})
|
225
237
|
assert query.matches_document?(:array_key => [1,2])
|
226
238
|
end
|
227
|
-
|
239
|
+
|
228
240
|
def test_elemMatch
|
229
241
|
query = MongoParserRB::Query.parse({:user_event_summaries => {:$elemMatch=> {:name => "second-support", :count => 3}}})
|
230
242
|
refute query.matches_document?(:user_event_summaries => [{:name => "first-support", :count => 3}, {:name => "second-support", :count => 4}])
|
231
243
|
assert query.matches_document?(:user_event_summaries => [{:name => "first-support", :count => 4}, {:name => "second-support", :count => 3}])
|
232
244
|
end
|
233
|
-
|
245
|
+
|
234
246
|
def test_elemMatch_inequality
|
235
247
|
query = MongoParserRB::Query.parse(:user_event_summaries => {:$elemMatch=> {:count => {:$ne => 2}, :name => "second-support"}})
|
236
248
|
refute query.matches_document?(:user_event_summaries => [{:name => "first-support", :count => 3}, {:name => "second-support", :count => 2}])
|
237
249
|
assert query.matches_document?(:user_event_summaries => [{:name => "first-support", :count => 3}, {:name => "second-support", :count => 3}])
|
238
250
|
end
|
239
|
-
|
251
|
+
|
240
252
|
def test_datatype_mismatch
|
241
253
|
query = MongoParserRB::Query.parse(:integer_key => {:$gt => 5})
|
242
254
|
refute query.matches_document?(:integer_key => "hello")
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mongo-parser-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.12
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben McRedmond
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-07-07 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Parse and evaluate mongo queries in Ruby
|
14
14
|
email:
|
@@ -17,8 +17,8 @@ executables: []
|
|
17
17
|
extensions: []
|
18
18
|
extra_rdoc_files: []
|
19
19
|
files:
|
20
|
-
- .gitignore
|
21
|
-
- .yardopts
|
20
|
+
- ".gitignore"
|
21
|
+
- ".yardopts"
|
22
22
|
- Gemfile
|
23
23
|
- LICENSE.txt
|
24
24
|
- README.md
|
@@ -30,6 +30,7 @@ files:
|
|
30
30
|
- lib/mongo-parser-rb/query/expression.rb
|
31
31
|
- lib/mongo-parser-rb/version.rb
|
32
32
|
- mongo-parser-rb.gemspec
|
33
|
+
- performance/perf.rb
|
33
34
|
- test/mongo-parser-rb/field_test.rb
|
34
35
|
- test/mongo-parser-rb/query_test.rb
|
35
36
|
- test/test_helper.rb
|
@@ -42,17 +43,17 @@ require_paths:
|
|
42
43
|
- lib
|
43
44
|
required_ruby_version: !ruby/object:Gem::Requirement
|
44
45
|
requirements:
|
45
|
-
- -
|
46
|
+
- - ">="
|
46
47
|
- !ruby/object:Gem::Version
|
47
48
|
version: '0'
|
48
49
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
49
50
|
requirements:
|
50
|
-
- -
|
51
|
+
- - ">="
|
51
52
|
- !ruby/object:Gem::Version
|
52
53
|
version: '0'
|
53
54
|
requirements: []
|
54
55
|
rubyforge_project:
|
55
|
-
rubygems_version: 2.
|
56
|
+
rubygems_version: 2.2.2
|
56
57
|
signing_key:
|
57
58
|
specification_version: 4
|
58
59
|
summary: Parse and evaluate mongo queries in Ruby
|