sparkql 1.3.0 → 1.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5988efc1b20cbe3cd2baec8537c3d9331564962a1d502d2ceaf63d78f520d218
4
- data.tar.gz: e1b20183b39463bf221eeddbcbdbb32a3d06457f2108c5c2ae6da471fdfdebf3
3
+ metadata.gz: 0f8f460c07a1a833cd0a80040f686b12a9579db3bc0ae60d1cc7aa1c73fe644d
4
+ data.tar.gz: 66a6701ad74dc5454d9e4dd2a14c0ea133715e10ba5babb676c90aed62e917bd
5
5
  SHA512:
6
- metadata.gz: 707c7f59f776ff70d9c4d164c4177a4800a81da80b2a0b4c7a99fdf7f1c7e632a67e155f11ead5c836fb48cc2ceec364adb8d359ecea6d714cc86f2bea2e0c58
7
- data.tar.gz: e74cf6657fd5e9cd81dbd23cbdb07c0506ac84280d7651d1b07e11dc8656f56105bde0b6273e7d4fdfd83a91edaa1742e77a4ce6717ca94daf60e5124c34f313
6
+ metadata.gz: 661e847ae1aa0f336706d94129c0afdc7d0ba040c0c8f62b7a6e9248dcc990d8f0179ef153355e050bae2b8a2f676c7e358081280418986c6c0bbf27393ac572
7
+ data.tar.gz: 4b64da6af2453236ec0b9c0e240238241609c79e0d4b8cca2c126ecc9b5e3b7e9c585836cc0dceb9a5136cb9decb993bffdef0145720de8dd734b2b2f291f340
@@ -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.5.8
1
+ 2.7.2
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ v1.3.1, 2025-08-06
2
+ -------------------
3
+ * [BUGFIX] Evaluator fix for Not expressions
4
+
1
5
  v1.3.0, 2022-02-01
2
6
  -------------------
3
7
  * [BUGFIX] Redesign FunctionResolver to better support other timezones
data/Gemfile CHANGED
@@ -1,3 +1,3 @@
1
- source 'http://rubygems.org'
1
+ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.3.0
1
+ 1.3.1
@@ -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,145 @@ class Sparkql::Evaluator
25
11
  end
26
12
 
27
13
  def evaluate(expressions)
28
- @dropped_expression = nil
29
14
  @processed_count = 0
30
- @index = Node.new(0, 0, "And", 0, true, false, 0, nil)
31
- @groups = [@index]
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
- evaluate_expression(expression)
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
- cleanup
41
- @index[:match]
25
+
26
+ final_result
42
27
  end
43
28
 
44
29
  private
45
30
 
46
- # prepare the group stack for the next expression
47
- def handle_group(expression)
48
- if @index[:block_group] == expression[:block_group]
49
- # Noop
50
- elsif @index[:block_group] < expression[:block_group]
51
- @index = new_group(expression)
52
- @groups.push(@index)
53
- else
54
- # Turn the group into an expression, resolve down to previous group(s)
55
- smoosh_group(expression)
56
- end
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
- # Here's the real meat. We use an internal stack to represent the result of
60
- # each block_group. This logic is re-used when merging the final result of one
61
- # block group with the previous.
62
- def evaluate_expression(expression)
63
- @processed_count += 1
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
- def evaluate_node(node, result)
68
- if result == :drop
69
- @dropped_expression = node
70
- return result
71
- end
72
- if node[:unary] == "Not"
73
- result = !result
74
- end
75
- if node[:conjunction] == 'Not' &&
76
- (node[:conjunction_level] == node[:level] ||
77
- node[:conjunction_level] == @index[:level])
78
- @index[:match] = !result if @index[:match]
79
- elsif node[:conjunction] == 'And' || (@index[:expressions]).zero?
80
- @index[:match] = result if @index[:match]
81
- elsif node[:conjunction] == 'Or' && result
82
- @index[:match] = result
83
- end
84
- @index[:expressions] += 1
85
- result
86
- end
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
+ while current_level >= 0
64
+ current_level -= 1
65
+ levels[current_level] ||= []
66
+ last_block_group_id = levels[current_level].last
67
+ if last_block_group_id
68
+ block_groups[last_block_group_id][:expressions] << block_group
69
+ break
70
+ else
71
+ block_id = "placeholder_for_#{block}_#{current_level}"
72
+ placeholder_block = block_builder(block_group, current_level)
73
+ placeholder_block[:expressions] << block_group
87
74
 
88
- # Optimization logic, once we find any set of And'd expressions that pass and
89
- # run into an Or at the same level, we can skip further processing at that
90
- # level.
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
75
+ levels[current_level] << block_id
76
+ block_groups[block_id] = placeholder_block
100
77
  end
101
78
  end
102
79
  end
103
- if !good_index.nil? && (good_index[:expressions]).positive? && good_index[:match]
104
- good_index[:good_ors] = true
105
- end
80
+
81
+ block_group[:expressions] << expression
106
82
  end
107
83
  end
108
84
 
109
- # We can skip further expression processing when And-d with a false expression
110
- # or a "good Or" was already encountered.
111
- def skip?(expression)
112
- @index[:good_ors] ||
113
- !@index[:match] && expression[:conjunction] == 'And'
114
- end
85
+ # Starting from the deepest levels, we process block groups expressions and
86
+ # reduce the block group to a result. This result is used in our placeholder
87
+ # block groups at levels above, ending in a single final result.
88
+ def process_structures(levels, block_groups)
89
+ final_result = nil
115
90
 
116
- def new_group(expression)
117
- Node.new(expression[:level], expression[:block_group],
118
- expression[:conjunction], expression[:conjunction_level],
119
- true, false, 0, nil)
120
- end
91
+ # Now go through each level starting with the deepest and working back up.
92
+ levels.keys.sort.reverse.each do |level|
93
+ # Process each block group at this level and resolve the expressions in the group
94
+ levels[level].each do |block|
95
+ block_group = block_groups[block]
121
96
 
122
- # When the last expression was dropped, we need to repair the filter by
123
- # stealing the conjunction of that dropped field.
124
- def adjust_expression_for_dropped_field(expression)
125
- if @dropped_expression.nil?
126
- return
127
- elsif @dropped_expression[:block_group] == expression[:block_group]
128
- expression[:conjunction] = @dropped_expression[:conjunction]
129
- expression[:conjunction_level] = @dropped_expression[:conjunction_level]
130
- end
97
+ block_result = nil
98
+ block_group[:expressions].each do |expression|
99
+ # If we encounter any or's in the same block group, we can cheat at
100
+ # resolving the rest, if we are at a true
101
+ if block_result && expression[:conjunction] == 'Or'
102
+ break
103
+ end
131
104
 
132
- @dropped_expression = nil
133
- end
105
+ expression_result = if expression.key?(:result)
106
+ # This is a reduced block group, just pass on
107
+ # the result
108
+ expression[:result]
109
+ else
110
+ @processed_count += 1
111
+ @resolver.resolve(expression) # true, false, :drop
112
+ end
113
+ next if expression_result == :drop
114
+
115
+ if expression[:unary] == "Not"
116
+ expression_result = !expression_result
117
+ end
134
118
 
135
- # This is similar to the cleanup step, but happens when we return from a
136
- # nesting level. Before we can proceed, we need wrap up the result of the
137
- # nested group.
138
- def smoosh_group(expression)
139
- until @groups.last[:block_group] == expression[:block_group]
140
- last = @groups.pop
141
- @index = @groups.last
142
- evaluate_node(last, last[:match])
119
+ if block_result.nil?
120
+ block_result = expression_result
121
+ next
122
+ end
123
+
124
+ case expression[:conjunction]
125
+ when 'Not'
126
+ block_result &= !expression_result
127
+ when 'And'
128
+ block_result &= expression_result
129
+ when 'Or'
130
+ block_result |= expression_result
131
+ else
132
+ # Not a supported conjunction. We skip over this for backwards
133
+ # compatibility.
134
+ end
135
+ end
136
+
137
+ # block_group.delete(:expressions)
138
+ block_group[:result] = block_result
139
+ final_result = block_result
140
+ end
143
141
  end
142
+
143
+ final_result
144
144
  end
145
145
 
146
- # pop off the group stack, evaluating each group with the previous as we go.
147
- def cleanup
148
- while @groups.size > 1
149
- last = @groups.pop
150
- @index = @groups.last
151
- evaluate_node(last, last[:match])
152
- end
153
- @groups.last[:match]
146
+ def block_builder(expressionable, level)
147
+ {
148
+ conjunction: expressionable[:conjunction],
149
+ conjunction_level: expressionable[:conjunction_level],
150
+ level: level,
151
+ expressions: [],
152
+ result: nil
153
+ }
154
154
  end
155
155
  end