sparkql 1.3.0 → 1.3.2
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/.github/workflows/ci.yml +51 -0
- data/.ruby-version +1 -1
- data/CHANGELOG.md +9 -0
- data/Gemfile +1 -1
- data/VERSION +1 -1
- data/lib/sparkql/evaluator.rb +119 -117
- data/lib/sparkql/parser.rb +499 -522
- data/script/bootstrap +3 -2
- data/sparkql.gemspec +1 -0
- data/test/unit/evaluator_test.rb +16 -5
- metadata +22 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d4f82b65f3fa050b24c53c7c5cb1e4a113d99e5e4ada4510695179c100aac711
|
4
|
+
data.tar.gz: 6fb9758b1883beadbbf9f864a9b1b782bfbc6f29b1a0061f9f66f33e1a0ed0b3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9752471d2b83c520c9ac52fa6fbc0219ffd914648bd8801f7953f41799a31ecd3de242e9a62e89cf5a7752c29337e85b14cba254779583ad914ac3cc4c06dc5b
|
7
|
+
data.tar.gz: 3ccd9092e28b7fd3064768deb46eb4d7d5205be213adad251bb568958d4975e109080a74759613fb6c0e425a0ddda0ca7af37253b71bbe2ce18fd57891e0f6e3
|
@@ -0,0 +1,51 @@
|
|
1
|
+
name: CI
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches: [ master ]
|
6
|
+
pull_request:
|
7
|
+
release:
|
8
|
+
types: published
|
9
|
+
|
10
|
+
|
11
|
+
jobs:
|
12
|
+
build:
|
13
|
+
runs-on: ubuntu-latest
|
14
|
+
|
15
|
+
steps:
|
16
|
+
- uses: actions/checkout@v3
|
17
|
+
- uses: ruby/setup-ruby@v1
|
18
|
+
with:
|
19
|
+
ruby-version: '2.7'
|
20
|
+
bundler-cache: true
|
21
|
+
|
22
|
+
- run: |
|
23
|
+
gem install bundler -v 2.2.31
|
24
|
+
./script/ci_build
|
25
|
+
|
26
|
+
publish:
|
27
|
+
runs-on: ubuntu-latest
|
28
|
+
env:
|
29
|
+
RUBYGEMS_API_KEY: ${{secrets.RUBYGEMS_API_KEY}}
|
30
|
+
|
31
|
+
# only run if this is a release event
|
32
|
+
if: github.event_name == 'release'
|
33
|
+
|
34
|
+
# require that the build job passed
|
35
|
+
needs: build
|
36
|
+
|
37
|
+
steps:
|
38
|
+
- uses: actions/checkout@v3
|
39
|
+
- uses: ruby/setup-ruby@v1
|
40
|
+
with:
|
41
|
+
ruby-version: '2.7'
|
42
|
+
|
43
|
+
- run: |
|
44
|
+
./script/bootstrap
|
45
|
+
bundle exec rake build
|
46
|
+
|
47
|
+
mkdir -p $HOME/.gem
|
48
|
+
touch $HOME/.gem/credentials
|
49
|
+
printf -- "---\n:rubygems_api_key: ${RUBYGEMS_API_KEY}\n" > $HOME/.gem/credentials
|
50
|
+
chmod 0600 $HOME/.gem/credentials
|
51
|
+
gem push pkg/*.gem
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.7.2
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
v1.3.2, 2025-08-06
|
2
|
+
-------------------
|
3
|
+
* [BUGFIX] More Evaluator fixes
|
4
|
+
* [BUGFIX] fixed the build.
|
5
|
+
|
6
|
+
v1.3.1, 2025-08-06
|
7
|
+
-------------------
|
8
|
+
* [BUGFIX] Evaluator fix for Not expressions
|
9
|
+
|
1
10
|
v1.3.0, 2022-02-01
|
2
11
|
-------------------
|
3
12
|
* [BUGFIX] Redesign FunctionResolver to better support other timezones
|
data/Gemfile
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.3.
|
1
|
+
1.3.2
|
data/lib/sparkql/evaluator.rb
CHANGED
@@ -4,20 +4,6 @@
|
|
4
4
|
# fields. Plus, it has some optimizations built in to skip the processing for
|
5
5
|
# any expressions that don't contribute to the net result of the filter.
|
6
6
|
class Sparkql::Evaluator
|
7
|
-
# The struct here mimics some of the parser information about an expression,
|
8
|
-
# but should not be confused for an expression. Nodes reduce the expressions
|
9
|
-
# to a result based on conjunction logic, and only one exists per block group.
|
10
|
-
Node = Struct.new(
|
11
|
-
:level,
|
12
|
-
:block_group,
|
13
|
-
:conjunction,
|
14
|
-
:conjunction_level,
|
15
|
-
:match,
|
16
|
-
:good_ors,
|
17
|
-
:expressions,
|
18
|
-
:unary
|
19
|
-
)
|
20
|
-
|
21
7
|
attr_reader :processed_count
|
22
8
|
|
23
9
|
def initialize(expression_resolver)
|
@@ -25,131 +11,147 @@ class Sparkql::Evaluator
|
|
25
11
|
end
|
26
12
|
|
27
13
|
def evaluate(expressions)
|
28
|
-
@dropped_expression = nil
|
29
14
|
@processed_count = 0
|
30
|
-
|
31
|
-
|
32
|
-
expressions.each do |expression|
|
33
|
-
handle_group(expression)
|
34
|
-
adjust_expression_for_dropped_field(expression)
|
35
|
-
check_for_good_ors(expression)
|
36
|
-
next if skip?(expression)
|
15
|
+
levels = {}
|
16
|
+
block_groups = {}
|
37
17
|
|
38
|
-
|
18
|
+
build_structures(levels, block_groups, expressions)
|
19
|
+
|
20
|
+
final_result = process_structures(levels, block_groups)
|
21
|
+
# If we didn't process anything, we consider that a success
|
22
|
+
if final_result.nil?
|
23
|
+
final_result = true
|
39
24
|
end
|
40
|
-
|
41
|
-
|
25
|
+
|
26
|
+
final_result
|
42
27
|
end
|
43
28
|
|
44
29
|
private
|
45
30
|
|
46
|
-
#
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
end
|
31
|
+
# Take all the expressions and organize them into "chunks" appropriate for
|
32
|
+
# evaluation. Each block group should process it's expressions, and every
|
33
|
+
# block group injects itself as a placeholder expression in the block group a
|
34
|
+
# level above it.
|
35
|
+
#
|
36
|
+
# When no block groups exist above, we must stub one out for processing.
|
37
|
+
def build_structures(levels, block_groups, expressions)
|
38
|
+
expressions.each do |expression|
|
39
|
+
level = expression[:level]
|
40
|
+
block = expression[:block_group]
|
41
|
+
block_group = block_groups[block]
|
58
42
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
evaluate_node(expression, @resolver.resolve(expression))
|
65
|
-
end
|
43
|
+
unless block_group
|
44
|
+
block_groups[block] ||= block_builder(expression, level)
|
45
|
+
block_group = block_groups[block]
|
46
|
+
levels[level] ||= []
|
47
|
+
levels[level] << block
|
66
48
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
49
|
+
# When dealing with Not expression conjunctions at the block level,
|
50
|
+
# it's far simpler to convert it into the equivalent "And Not"
|
51
|
+
if block_group[:conjunction] == "Not"
|
52
|
+
block_group[:unary] = "Not"
|
53
|
+
block_group[:conjunction] = "And"
|
54
|
+
end
|
55
|
+
|
56
|
+
# Every block group _must_ be seen as an expression in another block
|
57
|
+
# group.This aids in final resolution order when processing the levels
|
58
|
+
#
|
59
|
+
# This is even true if there's only one block group. We always end up
|
60
|
+
# with a level -1 here to turn the top level expressions into a block
|
61
|
+
# group for processing.
|
62
|
+
current_level = level
|
63
|
+
last_block_group = block_group
|
64
|
+
while current_level >= 0
|
65
|
+
current_level -= 1
|
66
|
+
levels[current_level] ||= []
|
67
|
+
last_block_group_id = levels[current_level].last
|
68
|
+
if last_block_group_id
|
69
|
+
block_groups[last_block_group_id][:expressions] << last_block_group
|
70
|
+
break
|
71
|
+
else
|
72
|
+
block_id = "placeholder_for_#{block}_#{current_level}"
|
73
|
+
placeholder_block = block_builder(last_block_group, current_level)
|
74
|
+
placeholder_block[:expressions] << last_block_group
|
87
75
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
def check_for_good_ors(expression)
|
92
|
-
if expression[:conjunction] == 'Or'
|
93
|
-
good_index = @index
|
94
|
-
unless expression[:conjunction_level] == @index[:level]
|
95
|
-
good_index = nil
|
96
|
-
# Well crap, now we need to go back and find that level by hand
|
97
|
-
@groups.reverse_each do |i|
|
98
|
-
if i[:level] == expression[:conjunction_level]
|
99
|
-
good_index = i
|
76
|
+
levels[current_level] << block_id
|
77
|
+
block_groups[block_id] = placeholder_block
|
78
|
+
last_block_group = placeholder_block
|
100
79
|
end
|
101
80
|
end
|
102
81
|
end
|
103
|
-
|
104
|
-
|
105
|
-
end
|
82
|
+
|
83
|
+
block_group[:expressions] << expression
|
106
84
|
end
|
107
85
|
end
|
108
86
|
|
109
|
-
#
|
110
|
-
#
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
end
|
87
|
+
# Starting from the deepest levels, we process block groups expressions and
|
88
|
+
# reduce the block group to a result. This result is used in our placeholder
|
89
|
+
# block groups at levels above, ending in a single final result.
|
90
|
+
def process_structures(levels, block_groups)
|
91
|
+
final_result = nil
|
115
92
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
93
|
+
# Now go through each level starting with the deepest and working back up.
|
94
|
+
levels.keys.sort.reverse.each do |level|
|
95
|
+
# Process each block group at this level and resolve the expressions in the group
|
96
|
+
levels[level].each do |block|
|
97
|
+
block_group = block_groups[block]
|
121
98
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
expression[:conjunction_level] = @dropped_expression[:conjunction_level]
|
130
|
-
end
|
99
|
+
block_result = nil
|
100
|
+
block_group[:expressions].each do |expression|
|
101
|
+
# If we encounter any or's in the same block group, we can cheat at
|
102
|
+
# resolving the rest, if we are at a true
|
103
|
+
if block_result && expression[:conjunction] == 'Or'
|
104
|
+
break
|
105
|
+
end
|
131
106
|
|
132
|
-
|
133
|
-
|
107
|
+
expression_result = if expression.key?(:result)
|
108
|
+
# This is a reduced block group, just pass on
|
109
|
+
# the result
|
110
|
+
expression[:result]
|
111
|
+
else
|
112
|
+
@processed_count += 1
|
113
|
+
@resolver.resolve(expression) # true, false, :drop
|
114
|
+
end
|
115
|
+
next if expression_result == :drop
|
116
|
+
|
117
|
+
if expression[:unary] == "Not"
|
118
|
+
expression_result = !expression_result
|
119
|
+
end
|
134
120
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
121
|
+
if block_result.nil?
|
122
|
+
block_result = expression_result
|
123
|
+
next
|
124
|
+
end
|
125
|
+
|
126
|
+
case expression[:conjunction]
|
127
|
+
when 'Not'
|
128
|
+
block_result &= !expression_result
|
129
|
+
when 'And'
|
130
|
+
block_result &= expression_result
|
131
|
+
when 'Or'
|
132
|
+
block_result |= expression_result
|
133
|
+
else
|
134
|
+
# Not a supported conjunction. We skip over this for backwards
|
135
|
+
# compatibility.
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# block_group.delete(:expressions)
|
140
|
+
block_group[:result] = block_result
|
141
|
+
final_result = block_result
|
142
|
+
end
|
143
143
|
end
|
144
|
+
|
145
|
+
final_result
|
144
146
|
end
|
145
147
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
148
|
+
def block_builder(expressionable, level)
|
149
|
+
{
|
150
|
+
conjunction: expressionable[:conjunction],
|
151
|
+
conjunction_level: expressionable[:conjunction_level],
|
152
|
+
level: level,
|
153
|
+
expressions: [],
|
154
|
+
result: nil
|
155
|
+
}
|
154
156
|
end
|
155
157
|
end
|