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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5988efc1b20cbe3cd2baec8537c3d9331564962a1d502d2ceaf63d78f520d218
4
- data.tar.gz: e1b20183b39463bf221eeddbcbdbb32a3d06457f2108c5c2ae6da471fdfdebf3
3
+ metadata.gz: d4f82b65f3fa050b24c53c7c5cb1e4a113d99e5e4ada4510695179c100aac711
4
+ data.tar.gz: 6fb9758b1883beadbbf9f864a9b1b782bfbc6f29b1a0061f9f66f33e1a0ed0b3
5
5
  SHA512:
6
- metadata.gz: 707c7f59f776ff70d9c4d164c4177a4800a81da80b2a0b4c7a99fdf7f1c7e632a67e155f11ead5c836fb48cc2ceec364adb8d359ecea6d714cc86f2bea2e0c58
7
- data.tar.gz: e74cf6657fd5e9cd81dbd23cbdb07c0506ac84280d7651d1b07e11dc8656f56105bde0b6273e7d4fdfd83a91edaa1742e77a4ce6717ca94daf60e5124c34f313
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.5.8
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
@@ -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.2
@@ -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
- @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
+ 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
- # 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
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
- if !good_index.nil? && (good_index[:expressions]).positive? && good_index[:match]
104
- good_index[:good_ors] = true
105
- end
82
+
83
+ block_group[:expressions] << expression
106
84
  end
107
85
  end
108
86
 
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
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
- 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
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
- # 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
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
- @dropped_expression = nil
133
- end
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
- # 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])
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
- # 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]
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