sparkql 1.2.8 → 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 +4 -4
- data/.github/workflows/ci.yml +51 -0
- data/.rubocop.yml +111 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +8 -0
- data/Gemfile +1 -1
- data/Rakefile +2 -3
- data/VERSION +1 -1
- data/lib/sparkql/errors.rb +68 -71
- data/lib/sparkql/evaluator.rb +121 -117
- data/lib/sparkql/expression_resolver.rb +2 -3
- data/lib/sparkql/expression_state.rb +7 -9
- data/lib/sparkql/function_resolver.rb +15 -10
- data/lib/sparkql/geo/record_circle.rb +1 -1
- data/lib/sparkql/lexer.rb +54 -56
- data/lib/sparkql/parser.rb +497 -520
- data/lib/sparkql/parser_compatibility.rb +97 -76
- data/lib/sparkql/parser_tools.rb +159 -139
- data/lib/sparkql/token.rb +25 -25
- data/lib/sparkql/version.rb +1 -1
- data/script/bootstrap +3 -2
- data/sparkql.gemspec +2 -1
- data/test/unit/errors_test.rb +4 -5
- data/test/unit/evaluator_test.rb +26 -20
- data/test/unit/expression_state_test.rb +14 -15
- data/test/unit/function_resolver_test.rb +125 -161
- data/test/unit/geo/record_circle_test.rb +2 -2
- data/test/unit/lexer_test.rb +15 -16
- data/test/unit/parser_compatability_test.rb +177 -151
- data/test/unit/parser_test.rb +90 -90
- metadata +28 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0f8f460c07a1a833cd0a80040f686b12a9579db3bc0ae60d1cc7aa1c73fe644d
|
4
|
+
data.tar.gz: 66a6701ad74dc5454d9e4dd2a14c0ea133715e10ba5babb676c90aed62e917bd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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/.rubocop.yml
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
AllCops:
|
2
|
+
NewCops: disable
|
3
|
+
Exclude:
|
4
|
+
- 'bin/*'
|
5
|
+
- 'config/**/*'
|
6
|
+
- 'Rakefile'
|
7
|
+
- 'Capfile'
|
8
|
+
- 'Gemfile'
|
9
|
+
- 'Guardfile'
|
10
|
+
- 'test/factories/*'
|
11
|
+
- 'test/support/*'
|
12
|
+
- 'config/routes.rb'
|
13
|
+
- 'script/*'
|
14
|
+
- 'db/**/*'
|
15
|
+
- 'vendor/**/*'
|
16
|
+
|
17
|
+
Style/FrozenStringLiteralComment:
|
18
|
+
Enabled: false
|
19
|
+
|
20
|
+
Style/Documentation:
|
21
|
+
Enabled: false
|
22
|
+
|
23
|
+
Metrics/AbcSize:
|
24
|
+
Enabled: false
|
25
|
+
|
26
|
+
Metrics/BlockLength:
|
27
|
+
Enabled: false
|
28
|
+
|
29
|
+
Metrics/ClassLength:
|
30
|
+
Enabled: false
|
31
|
+
|
32
|
+
Metrics/CyclomaticComplexity:
|
33
|
+
Enabled: false
|
34
|
+
|
35
|
+
Layout/LineLength:
|
36
|
+
Enabled: false
|
37
|
+
|
38
|
+
Metrics/MethodLength:
|
39
|
+
Enabled: false
|
40
|
+
|
41
|
+
Metrics/ModuleLength:
|
42
|
+
Enabled: false
|
43
|
+
|
44
|
+
Metrics/PerceivedComplexity:
|
45
|
+
Enabled: false
|
46
|
+
|
47
|
+
# "Favor `unless` over `if` for negative conditions."
|
48
|
+
Style/NegatedIf:
|
49
|
+
Enabled: false
|
50
|
+
# safe_yaml seems to break all the things.
|
51
|
+
Security/YAMLLoad:
|
52
|
+
Enabled: false
|
53
|
+
|
54
|
+
# "Use a guard clause (`return unless extra_types.any?`) instead
|
55
|
+
# of wrapping the code inside a conditional expression."
|
56
|
+
#
|
57
|
+
# Justification: guard clauses don't work very well with long lines.
|
58
|
+
# Also, when there's an if check that (say) adds an error to a model
|
59
|
+
# validation, it makes more sense to wrap the operation in an if block
|
60
|
+
# than to guard the error entry with a double negative.
|
61
|
+
Style/GuardClause:
|
62
|
+
Enabled: false
|
63
|
+
|
64
|
+
# Justification:
|
65
|
+
#
|
66
|
+
# `class MyModule::ClassName` is a lot more concise, especially for tests
|
67
|
+
# covering a class that is within a module, than having to wrap the whole
|
68
|
+
# class in a module, and indent.
|
69
|
+
#
|
70
|
+
# "Use nested module/class definitions instead of compact style."
|
71
|
+
Style/ClassAndModuleChildren:
|
72
|
+
Enabled: false
|
73
|
+
|
74
|
+
# Justification:
|
75
|
+
#
|
76
|
+
# A single-line guard clause isn't always a good thing.
|
77
|
+
Style/IfUnlessModifier:
|
78
|
+
Enabled: false
|
79
|
+
|
80
|
+
# Justification:
|
81
|
+
#
|
82
|
+
# Hundreds of existing infractions, and it's not really that confusion to
|
83
|
+
# see regex without parens around it.
|
84
|
+
Lint/AmbiguousRegexpLiteral:
|
85
|
+
Enabled: false
|
86
|
+
|
87
|
+
# Justification:
|
88
|
+
#
|
89
|
+
# Is it so wrong to have a variable named fgo_listing_1, instead
|
90
|
+
# of fgo_listing1?
|
91
|
+
Naming/VariableNumber:
|
92
|
+
Enabled: false
|
93
|
+
|
94
|
+
# Justification:
|
95
|
+
#
|
96
|
+
# Explicit else's are much clearer than
|
97
|
+
# a branch that ends with an `elsif`, and presumes
|
98
|
+
# a nil else.
|
99
|
+
Style/EmptyElse:
|
100
|
+
Enabled: false
|
101
|
+
|
102
|
+
# Justification:
|
103
|
+
#
|
104
|
+
# We've generally prefered this, and honestly, I find
|
105
|
+
# this often makes readability much clearer to include
|
106
|
+
# it.
|
107
|
+
Style/RedundantSelf:
|
108
|
+
Enabled: false
|
109
|
+
|
110
|
+
Style/StringLiterals:
|
111
|
+
Enabled: false
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.7.2
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
v1.3.1, 2025-08-06
|
2
|
+
-------------------
|
3
|
+
* [BUGFIX] Evaluator fix for Not expressions
|
4
|
+
|
5
|
+
v1.3.0, 2022-02-01
|
6
|
+
-------------------
|
7
|
+
* [BUGFIX] Redesign FunctionResolver to better support other timezones
|
8
|
+
|
1
9
|
v1.2.8, 2021-08-11
|
2
10
|
-------------------
|
3
11
|
* [IMPROVEMENT] all() function
|
data/Gemfile
CHANGED
data/Rakefile
CHANGED
@@ -15,7 +15,7 @@ rule '.rb' => '.y' do |t|
|
|
15
15
|
end
|
16
16
|
|
17
17
|
desc "Compile the racc parser from the grammar"
|
18
|
-
task :
|
18
|
+
task compile: ["lib/sparkql/parser.rb", "grammar"]
|
19
19
|
|
20
20
|
desc "Generate grammar Documenation"
|
21
21
|
task :grammar do
|
@@ -27,5 +27,4 @@ Rake::Task[:test].prerequisites.unshift "lib/sparkql/parser.rb"
|
|
27
27
|
Rake::Task[:test].prerequisites.unshift "grammar"
|
28
28
|
|
29
29
|
desc 'Default: run unit tests.'
|
30
|
-
task :
|
31
|
-
|
30
|
+
task default: :test
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.3.1
|
data/lib/sparkql/errors.rb
CHANGED
@@ -1,87 +1,84 @@
|
|
1
1
|
module Sparkql
|
2
|
+
class ErrorsProcessor
|
3
|
+
attr_accessor :errors
|
2
4
|
|
3
|
-
|
4
|
-
|
5
|
+
def initialize(errors = [])
|
6
|
+
@errors = Array(errors)
|
7
|
+
end
|
5
8
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
+
# true if the error stack contains at least one error
|
10
|
+
def errors?
|
11
|
+
@errors.size.positive?
|
12
|
+
end
|
9
13
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
+
# true if there is at least one error of status :status in the error stack
|
15
|
+
def errors_by_status?(status)
|
16
|
+
@errors.each do |error|
|
17
|
+
return true if status == error.status
|
18
|
+
end
|
19
|
+
false
|
20
|
+
end
|
14
21
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
return true if status == error.status
|
22
|
+
# true if there is at least one :fatal error in the error stack
|
23
|
+
def fatal_errors?
|
24
|
+
errors_by_status? :fatal
|
19
25
|
end
|
20
|
-
false
|
21
|
-
end
|
22
26
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
+
# true if there is at least one :dropped error in the error stack
|
28
|
+
def dropped_errors?
|
29
|
+
errors_by_status? :dropped
|
30
|
+
end
|
27
31
|
|
28
|
-
|
29
|
-
|
30
|
-
|
32
|
+
# true if there is at least one :recovered error in the error stack
|
33
|
+
def recovered_errors?
|
34
|
+
errors_by_status? :recovered
|
35
|
+
end
|
31
36
|
end
|
32
37
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
38
|
+
class ParserError
|
39
|
+
attr_accessor :token, :token_index, :expression, :message, :status, :recovered_as,
|
40
|
+
:sparkql, :nested_errors
|
41
|
+
attr_writer :syntax, :constraint
|
37
42
|
|
38
|
-
|
43
|
+
def initialize(error_hash = {})
|
44
|
+
@token = error_hash[:token]
|
45
|
+
@token_index = error_hash[:token_index]
|
46
|
+
@expression = error_hash[:expression]
|
47
|
+
@message = error_hash[:message]
|
48
|
+
@status = error_hash[:status]
|
49
|
+
@recovered_as = error_hash[:recovered_as]
|
50
|
+
@recovered_as = error_hash[:recovered_as]
|
51
|
+
@sparkql = error_hash[:sparkql]
|
52
|
+
@nested_errors = error_hash[:nested_errors]
|
53
|
+
self.syntax = error_hash[:syntax] != false
|
54
|
+
self.constraint = error_hash[:constraint] == true
|
55
|
+
end
|
39
56
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
attr_writer :syntax, :constraint
|
57
|
+
def syntax?
|
58
|
+
@syntax
|
59
|
+
end
|
44
60
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
@expression = error_hash[:expression]
|
49
|
-
@message = error_hash[:message]
|
50
|
-
@status = error_hash[:status]
|
51
|
-
@recovered_as = error_hash[:recovered_as]
|
52
|
-
@recovered_as = error_hash[:recovered_as]
|
53
|
-
@sparkql = error_hash[:sparkql]
|
54
|
-
@nested_errors = error_hash[:nested_errors]
|
55
|
-
self.syntax= error_hash[:syntax] == false ? false : true
|
56
|
-
self.constraint= error_hash[:constraint] == true
|
57
|
-
end
|
58
|
-
|
59
|
-
def syntax?
|
60
|
-
@syntax
|
61
|
-
end
|
62
|
-
|
63
|
-
def constraint?
|
64
|
-
@constraint
|
65
|
-
end
|
61
|
+
def constraint?
|
62
|
+
@constraint
|
63
|
+
end
|
66
64
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
65
|
+
def to_s
|
66
|
+
str = case @status
|
67
|
+
# Do nothing. Dropping the expressions isn't special
|
68
|
+
when :dropped then "Dropped: "
|
69
|
+
# Fatal errors cannot be recovered from, and should cause anaylisis or
|
70
|
+
# compilation to stop.
|
71
|
+
when :fatal then "Fatal: "
|
72
|
+
# Recovered errors are those that are syntatically
|
73
|
+
# or symantically incorrect, but are ones that we could "guess" at the
|
74
|
+
# intention
|
75
|
+
when :recovered
|
76
|
+
"Recovered as #{@recovered_as}: "
|
77
|
+
else ""
|
78
|
+
end
|
79
|
+
str += "<#{@token}> in " unless @token.nil?
|
80
|
+
str += "<#{@expression}>: #{@message}."
|
81
|
+
str
|
82
|
+
end
|
84
83
|
end
|
85
84
|
end
|
86
|
-
|
87
|
-
end
|
data/lib/sparkql/evaluator.rb
CHANGED
@@ -4,148 +4,152 @@
|
|
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
|
-
|
8
|
-
# The struct here mimics some of the parser information about an expression,
|
9
|
-
# but should not be confused for an expression. Nodes reduce the expressions
|
10
|
-
# to a result based on conjunction logic, and only one exists per block group.
|
11
|
-
Node = Struct.new(
|
12
|
-
:level,
|
13
|
-
:block_group,
|
14
|
-
:conjunction,
|
15
|
-
:conjunction_level,
|
16
|
-
:match,
|
17
|
-
:good_ors,
|
18
|
-
:expressions,
|
19
|
-
:unary)
|
20
|
-
|
21
7
|
attr_reader :processed_count
|
22
8
|
|
23
|
-
def initialize
|
9
|
+
def initialize(expression_resolver)
|
24
10
|
@resolver = expression_resolver
|
25
11
|
end
|
26
12
|
|
27
13
|
def evaluate(expressions)
|
28
14
|
@processed_count = 0
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
15
|
+
levels = {}
|
16
|
+
block_groups = {}
|
17
|
+
|
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
|
37
24
|
end
|
38
|
-
|
39
|
-
|
25
|
+
|
26
|
+
final_result
|
40
27
|
end
|
41
28
|
|
42
29
|
private
|
43
30
|
|
44
|
-
#
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
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]
|
56
42
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
evaluate_node(expression, @resolver.resolve(expression))
|
63
|
-
end
|
64
|
-
def evaluate_node(node, result)
|
65
|
-
if result == :drop
|
66
|
-
@dropped_expression = node
|
67
|
-
return result
|
68
|
-
end
|
69
|
-
if node[:unary] == "Not"
|
70
|
-
result = !result
|
71
|
-
end
|
72
|
-
if node[:conjunction] == 'Not' &&
|
73
|
-
(node[:conjunction_level] == node[:level] ||
|
74
|
-
node[:conjunction_level] == @index[:level])
|
75
|
-
@index[:match] = !result if @index[:match]
|
76
|
-
elsif node[:conjunction] == 'And' || @index[:expressions] == 0
|
77
|
-
@index[:match] = result if @index[:match]
|
78
|
-
elsif node[:conjunction] == 'Or' && result
|
79
|
-
@index[:match] = result
|
80
|
-
end
|
81
|
-
@index[:expressions] += 1
|
82
|
-
result
|
83
|
-
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
|
84
48
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
#
|
94
|
-
|
95
|
-
|
96
|
-
|
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
|
74
|
+
|
75
|
+
levels[current_level] << block_id
|
76
|
+
block_groups[block_id] = placeholder_block
|
97
77
|
end
|
98
78
|
end
|
99
79
|
end
|
100
|
-
|
101
|
-
|
102
|
-
end
|
80
|
+
|
81
|
+
block_group[:expressions] << expression
|
103
82
|
end
|
104
83
|
end
|
105
84
|
|
106
|
-
#
|
107
|
-
#
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
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
|
112
90
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
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]
|
118
96
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
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
|
104
|
+
|
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
|
130
118
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
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
|
139
141
|
end
|
142
|
+
|
143
|
+
final_result
|
140
144
|
end
|
141
145
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
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
|
+
}
|
150
154
|
end
|
151
155
|
end
|
@@ -1,17 +1,16 @@
|
|
1
1
|
# Base class for handling expression resolution
|
2
2
|
class Sparkql::ExpressionResolver
|
3
|
-
|
4
3
|
# Accepted results from the resolve method:
|
5
4
|
# * true and false reflect the expression's boolean result (as all expressions
|
6
5
|
# should).
|
7
6
|
# * :drop is a special symbol indicating that the expression should be omitted
|
8
7
|
# from the filter. Special rules apply for a dropped expression, such as
|
9
8
|
# keeping the conjunction of the dropped expression.
|
10
|
-
VALID_RESULTS = [true, false, :drop]
|
9
|
+
VALID_RESULTS = [true, false, :drop].freeze
|
11
10
|
|
12
11
|
# Evaluate the result of this expression. Allows for any of the values in
|
13
12
|
# VALID_RESULTS
|
14
|
-
def resolve(
|
13
|
+
def resolve(_expression)
|
15
14
|
true
|
16
15
|
end
|
17
16
|
end
|