ddql 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.travis.yml +2 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +34 -22
- data/README.md +2 -0
- data/bin/console +10 -5
- data/ddql.gemspec +0 -3
- data/lib/ddql.rb +9 -2
- data/lib/ddql/agg_operator.rb +79 -0
- data/lib/ddql/coalesce_operator.rb +26 -0
- data/lib/ddql/infix_float_map_operator.rb +12 -0
- data/lib/ddql/infix_string_map_operator.rb +7 -0
- data/lib/ddql/lexer.rb +22 -0
- data/lib/ddql/linked_list.rb +171 -0
- data/lib/ddql/list_operator.rb +7 -0
- data/lib/ddql/lookup_operator.rb +22 -0
- data/lib/ddql/operator.rb +173 -0
- data/lib/ddql/operators.rb +113 -0
- data/lib/ddql/parser.rb +43 -264
- data/lib/ddql/postfix_null_type_operator.rb +11 -0
- data/lib/ddql/query_expression_error.rb +15 -0
- data/lib/ddql/string_refinements.rb +17 -0
- data/lib/ddql/token.rb +73 -0
- data/lib/ddql/token_type.rb +575 -0
- data/lib/ddql/version.rb +1 -1
- metadata +17 -30
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative 'agg_operator'
|
2
|
+
|
3
|
+
module DDQL
|
4
|
+
class LookupOperator < AggOperator
|
5
|
+
def initialize
|
6
|
+
super("LOOKUP BY", "Lookup", type: :infix, return_type: :string)
|
7
|
+
end
|
8
|
+
|
9
|
+
def parse(parser, token, expression: nil)
|
10
|
+
precedence = self.precedence
|
11
|
+
precedence -= 1 if right?
|
12
|
+
next_expression = parser.parse(precedence: precedence)
|
13
|
+
op_expression = token.as_hash
|
14
|
+
|
15
|
+
{
|
16
|
+
left: expression,
|
17
|
+
op: op_expression[:op],
|
18
|
+
right: next_expression.delete(:left) || next_expression,
|
19
|
+
}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
module DDQL
|
2
|
+
class Operator
|
3
|
+
attr_reader :associativity, :symbol, :name, :type, :precedence, :return_type, :ordinal
|
4
|
+
|
5
|
+
def initialize(symbol, name, type, precedence, right, return_type, ordinal=0)
|
6
|
+
@symbol = symbol
|
7
|
+
@name = name
|
8
|
+
@type = type
|
9
|
+
@precedence = precedence
|
10
|
+
@associativity = right ? :right : :left
|
11
|
+
@return_type = return_type
|
12
|
+
@ordinal = ordinal
|
13
|
+
end
|
14
|
+
|
15
|
+
def boolean?
|
16
|
+
return_type == :boolean
|
17
|
+
end
|
18
|
+
|
19
|
+
def comparison?
|
20
|
+
(infix? || postfix?) && boolean?
|
21
|
+
end
|
22
|
+
|
23
|
+
def complex_comparison?
|
24
|
+
comparison? && !simple_comparison?
|
25
|
+
end
|
26
|
+
|
27
|
+
def infix?
|
28
|
+
@name == :infixoperator || @type == :infix
|
29
|
+
end
|
30
|
+
|
31
|
+
def left?
|
32
|
+
!right?
|
33
|
+
end
|
34
|
+
|
35
|
+
def math?
|
36
|
+
math_op?(symbol)
|
37
|
+
end
|
38
|
+
|
39
|
+
def parse(parser, token, expression: nil)
|
40
|
+
case type
|
41
|
+
when :infix; parse_infix(parser, token, expression)
|
42
|
+
when :prefix; parse_prefix(parser, token, expression)
|
43
|
+
when :postfix; parse_postfix(parser, token, expression)
|
44
|
+
else
|
45
|
+
raise "unsupported operator type[#{type}]"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def pattern
|
50
|
+
symbol
|
51
|
+
end
|
52
|
+
|
53
|
+
def postfix?
|
54
|
+
@name == :postfixoperator || @type == :postfix
|
55
|
+
end
|
56
|
+
|
57
|
+
def prefix?
|
58
|
+
@name == :prefixoperator || @type == :prefix
|
59
|
+
end
|
60
|
+
|
61
|
+
def register(hash)
|
62
|
+
hash[symbol] = self
|
63
|
+
hash
|
64
|
+
end
|
65
|
+
|
66
|
+
def right?
|
67
|
+
@associativity == :right
|
68
|
+
end
|
69
|
+
|
70
|
+
def simple_comparison?
|
71
|
+
case symbol
|
72
|
+
when "==", "=", "!=", "<=", "<", ">=", ">"
|
73
|
+
true
|
74
|
+
else
|
75
|
+
false
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def type?(incoming)
|
80
|
+
type == incoming
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def boolean_stmt(left, op, right)
|
86
|
+
# the following avoids unnecessarily deeply nested statements
|
87
|
+
while left.key?(:lstatement) && !left.key?(:rstatement)
|
88
|
+
left = left[:lstatement]
|
89
|
+
end
|
90
|
+
while right.key?(:lstatement) && !right.key?(:rstatement)
|
91
|
+
right = right[:lstatement]
|
92
|
+
end
|
93
|
+
{
|
94
|
+
lstatement: left,
|
95
|
+
boolean_operator: op[:op],
|
96
|
+
rstatement: right,
|
97
|
+
}
|
98
|
+
end
|
99
|
+
|
100
|
+
def math_op?(str)
|
101
|
+
case str
|
102
|
+
when '+', '-', '*', '/', '%', '^'
|
103
|
+
true
|
104
|
+
else
|
105
|
+
false
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def merged_negation(left, right, op)
|
110
|
+
needs_statement_wrapper = lambda do |expr|
|
111
|
+
(expr.key?(:lstatement) && expr.key?(:op_not)) || !expr.key?(:lstatement)
|
112
|
+
end
|
113
|
+
|
114
|
+
left = {lstatement: left} if needs_statement_wrapper[left]
|
115
|
+
right = {rstatement: right} if needs_statement_wrapper[right]
|
116
|
+
left.merge(boolean_operator: op[:op]).merge(right)
|
117
|
+
end
|
118
|
+
|
119
|
+
def parse_infix(parser, token, expression)
|
120
|
+
precedence = self.precedence
|
121
|
+
precedence -= 1 if right?
|
122
|
+
next_expression = parser.parse(precedence: precedence)
|
123
|
+
op_expression = token.as_hash
|
124
|
+
|
125
|
+
if token.and? || token.or?
|
126
|
+
return boolean_stmt(expression, op_expression, next_expression)
|
127
|
+
end
|
128
|
+
|
129
|
+
if expression.key?(:op_not) || next_expression.key?(:op_not)
|
130
|
+
return merged_negation(expression, next_expression, op_expression)
|
131
|
+
end
|
132
|
+
|
133
|
+
if next_expression&.key?(:lstatement) && expression&.key?(:lstatement)
|
134
|
+
left = expression.merge(boolean_operator: op_expression[:op], rstatement: next_expression[:lstatement])
|
135
|
+
return next_expression.key?(:rstatement) ?
|
136
|
+
left.merge(
|
137
|
+
boolean_operator: next_expression[:boolean_operator][:op],
|
138
|
+
rstatement: next_expression[:rstatement]
|
139
|
+
) :
|
140
|
+
left
|
141
|
+
end
|
142
|
+
|
143
|
+
expression = {left: expression} unless redundant?(expression, :left)
|
144
|
+
next_expression = {right: next_expression} unless redundant?(next_expression, :right)
|
145
|
+
expression.merge(op_expression).merge(next_expression)
|
146
|
+
end
|
147
|
+
|
148
|
+
def parse_postfix(parser, token, expression)
|
149
|
+
op_expression = token.as_hash
|
150
|
+
if expression && !expression.empty?
|
151
|
+
expression = {left: expression} unless expression.key?(:left)
|
152
|
+
expression.merge(op_expression)
|
153
|
+
else
|
154
|
+
op_expression
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def parse_prefix(parser, token, _expression)
|
159
|
+
op_expression = token.type.as_hash(token.data)
|
160
|
+
next_expression = parser.parse(precedence: precedence)
|
161
|
+
if next_expression.key?(:lstatement) && !next_expression.key?(:rstatement)
|
162
|
+
next_expression = next_expression[:lstatement]
|
163
|
+
elsif next_expression.key?(:rstatement) && !next_expression.key?(:lstatement)
|
164
|
+
next_expression = next_expression[:rstatement]
|
165
|
+
end
|
166
|
+
op_expression[:op].merge(next_expression)
|
167
|
+
end
|
168
|
+
|
169
|
+
def redundant?(expression, *keys)
|
170
|
+
expression.keys == keys
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
require_relative 'operator'
|
4
|
+
require_relative 'agg_operator'
|
5
|
+
require_relative 'coalesce_operator'
|
6
|
+
require_relative 'infix_float_map_operator'
|
7
|
+
require_relative 'infix_string_map_operator'
|
8
|
+
require_relative 'list_operator'
|
9
|
+
require_relative 'lookup_operator'
|
10
|
+
require_relative 'postfix_null_type_operator'
|
11
|
+
|
12
|
+
module DDQL
|
13
|
+
class Operators
|
14
|
+
include Singleton
|
15
|
+
|
16
|
+
attr_reader :cache
|
17
|
+
|
18
|
+
def self.float_map_ops
|
19
|
+
instance.cache.select { |_, v| v.is_a? InfixFloatMapOperator }
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.operator_regex(operator_type)
|
23
|
+
ops = instance.cache.select { |k, v| v.type?(operator_type) }
|
24
|
+
|
25
|
+
if ops.empty?
|
26
|
+
# if there are no registered operators of the given type use negative
|
27
|
+
# lookahead to generate a regex pattern that can never be matched
|
28
|
+
return /(?!x)x/
|
29
|
+
else
|
30
|
+
ops.keys[1..-1].inject(ops.keys.first) { |a, e| Regexp.union(a, ops[e].pattern) }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize
|
35
|
+
@cache = build_cache
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def build_cache
|
40
|
+
cache = {}
|
41
|
+
Operator.new("==", "Double Equals", :infix, 3, false, :boolean, 0).register(cache)
|
42
|
+
Operator.new("=", "Single Equals", :infix, 3, false, :boolean, 0).register(cache)
|
43
|
+
Operator.new("!=", "Not Equals", :infix, 3, false, :boolean, 1).register(cache)
|
44
|
+
Operator.new("<=", "Less Than or Equals", :infix, 4, false, :boolean, 4).register(cache)
|
45
|
+
Operator.new("<", "Less Than", :infix, 4, false, :boolean, 2).register(cache)
|
46
|
+
Operator.new(">=", "Greater Than or Equals", :infix, 4, false, :boolean, 5).register(cache)
|
47
|
+
Operator.new(">", "Greater Than", :infix, 4, false, :boolean, 3).register(cache)
|
48
|
+
# Operator.new("GROUP BY", "Group By", :infix, 7, false, :double).register(cache)
|
49
|
+
Operator.new("CTN", "Contains", :infix, 4, false, :boolean, 6).register(cache)
|
50
|
+
Operator.new("STW", "Starts With", :infix, 4, false, :boolean, 7).register(cache)
|
51
|
+
Operator.new("IN", "In Any", :infix, 4, false, :boolean, 8).register(cache)
|
52
|
+
Operator.new("LCTN", "Contains", :infix, 4, false, :boolean, 6).register(cache)
|
53
|
+
Operator.new("ON", "On Date", :infix, 4, false, :boolean, 0).register(cache)
|
54
|
+
Operator.new("EPST", "On Or After Date", :infix, 4, false, :boolean, 1).register(cache)
|
55
|
+
Operator.new("PST", "After Date", :infix, 4, false, :boolean, 1).register(cache)
|
56
|
+
Operator.new("EPRE", "On Or Before Date", :infix, 4, false, :boolean, 1).register(cache)
|
57
|
+
Operator.new("PRE", "Before Date", :infix, 4, false, :boolean, 0).register(cache)
|
58
|
+
Operator.new("OR", "Or", :infix, 1, false, :boolean).register(cache)
|
59
|
+
Operator.new("AND", "And", :infix, 2, false, :boolean).register(cache)
|
60
|
+
Operator.new("IS NULL", "Is Null", :postfix, 9, false, :boolean, 0).register(cache)
|
61
|
+
Operator.new("IS NOT NULL", "Is Not Null", :postfix, 9, false, :boolean, 1).register(cache)
|
62
|
+
Operator.new("+", "Plus", :infix, 5, false, :double).register(cache)
|
63
|
+
Operator.new("-", "Minus", :infix, 5, false, :double).register(cache)
|
64
|
+
Operator.new("*", "Multiplied By", :infix, 6, false, :double).register(cache)
|
65
|
+
Operator.new("/", "Divided By", :infix, 6, false, :double).register(cache)
|
66
|
+
Operator.new("%", "Modulus", :infix, 6, false, :integer).register(cache)
|
67
|
+
Operator.new("^", "To the Power of", :infix, 7, true, :double).register(cache)
|
68
|
+
AggOperator.new("EXISTS", "Exists", return_type: :boolean).register(cache)
|
69
|
+
AggOperator.new("CNT", "Count", return_type: :integer).register(cache)
|
70
|
+
AggOperator.new("AVG", "Mean", return_type: :double).register(cache)
|
71
|
+
AggOperator.new("MED", "Median", return_type: :double).register(cache)
|
72
|
+
AggOperator.new("MAX", "Max", return_type: :double).register(cache)
|
73
|
+
AggOperator.new("MIN", "Min", return_type: :double).register(cache)
|
74
|
+
AggOperator.new("SUM", "Sum", return_type: :double).register(cache)
|
75
|
+
AggOperator.new("MERGE", "Merge", return_type: :string_list).register(cache)
|
76
|
+
LookupOperator.new.register(cache)
|
77
|
+
CoalesceOperator.new.register(cache)
|
78
|
+
# PNT & RATIO don't work and aren't used in DD PROD as of 2020-07-24
|
79
|
+
# AggOperator.new("PNT", "Percent", return_type: :double).register(cache)
|
80
|
+
# AggOperator.new("RATIO", "Ratio", return_type: :double).register(cache)
|
81
|
+
InfixStringMapOperator.new("ANY_MAP", "Has Any", 0).register(cache)
|
82
|
+
InfixStringMapOperator.new("ALL_MAP", "Has All", 1).register(cache)
|
83
|
+
InfixStringMapOperator.new("NONE_MAP", "Has None", 2).register(cache)
|
84
|
+
InfixFloatMapOperator.new("ANY_GREATER_THAN_FLOAT_MAP", "Has Any Greater Than", 1, :any, :gt).register(cache)
|
85
|
+
InfixFloatMapOperator.new("ALL_GREATER_THAN_FLOAT_MAP", "Has All Greater Than", 2, :all, :gt).register(cache)
|
86
|
+
InfixFloatMapOperator.new("NONE_GREATER_THAN_FLOAT_MAP", "Has None Greater Than", 3, :none, :gt).register(cache)
|
87
|
+
InfixFloatMapOperator.new("ANY_GREATER_THAN_OR_EQUAL_FLOAT_MAP", "Has Any Greater Than Or Equal To", 4, :any, :ge).register(cache)
|
88
|
+
InfixFloatMapOperator.new("ALL_GREATER_THAN_OR_EQUAL_FLOAT_MAP", "Has All Greater Than Or Equal To", 5, :all, :ge).register(cache)
|
89
|
+
InfixFloatMapOperator.new("NONE_GREATER_THAN_OR_EQUAL_FLOAT_MAP", "Has None Greater Than Or Equal To", 6, :none, :ge).register(cache)
|
90
|
+
InfixFloatMapOperator.new("ANY_LESS_THAN_FLOAT_MAP", "Has Any Less Than", 7, :any, :lt).register(cache)
|
91
|
+
InfixFloatMapOperator.new("ALL_LESS_THAN_FLOAT_MAP", "Has All Less Than", 8, :all, :lt).register(cache)
|
92
|
+
InfixFloatMapOperator.new("NONE_LESS_THAN_FLOAT_MAP", "Has None Less Than", 9, :none, :lt).register(cache)
|
93
|
+
InfixFloatMapOperator.new("ANY_LESS_THAN_OR_EQUAL_FLOAT_MAP", "Has Any Less Than Or Equal To", 10, :any, :le).register(cache)
|
94
|
+
InfixFloatMapOperator.new("ALL_LESS_THAN_OR_EQUAL_FLOAT_MAP", "Has All Less Than Or Equal To", 11, :all, :le).register(cache)
|
95
|
+
InfixFloatMapOperator.new("NONE_LESS_THAN_OR_EQUAL_FLOAT_MAP", "Has None Less Than Or Equal To", 12, :none, :le).register(cache)
|
96
|
+
InfixFloatMapOperator.new("ANY_EQUAL_FLOAT_MAP", "Has Any Equal To", 13, :any, :eq).register(cache)
|
97
|
+
InfixFloatMapOperator.new("ALL_EQUAL_FLOAT_MAP", "Has All Equal To", 14, :all, :eq).register(cache)
|
98
|
+
InfixFloatMapOperator.new("NONE_EQUAL_FLOAT_MAP", "Has None Equal To", 15, :none, :eq).register(cache)
|
99
|
+
ListOperator.new("ANY", "Has Any", :infix, 0).register(cache)
|
100
|
+
ListOperator.new("ALL", "Has All", :infix, 1).register(cache)
|
101
|
+
ListOperator.new("NONE", "Has None", :infix, 2).register(cache)
|
102
|
+
ListOperator.new("EMPTY", "Is Empty", :postfix, 3).register(cache)
|
103
|
+
PostfixNullTypeOperator.new("NO_INFORMATION", :NoInformation, 99).register(cache)
|
104
|
+
PostfixNullTypeOperator.new("NOT_APPLICABLE", :NotApplicable, 101).register(cache)
|
105
|
+
PostfixNullTypeOperator.new("NOT_COLLECTED", :NotCollected, 103).register(cache)
|
106
|
+
PostfixNullTypeOperator.new("NOT_DISCLOSED", :NotDisclosed, 100).register(cache)
|
107
|
+
PostfixNullTypeOperator.new("NOT_MEANINGFUL", :NotMeaningful, 102).register(cache)
|
108
|
+
Operator.new("NOT", "Not", :prefix, 9, false, :boolean).register(cache)
|
109
|
+
Operator.new("YES", "Is True", :postfix, 9, false, :boolean, 0).register(cache)
|
110
|
+
Operator.new("NO", "Is False", :postfix, 9, false, :boolean, 1).register(cache)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
data/lib/ddql/parser.rb
CHANGED
@@ -1,282 +1,61 @@
|
|
1
1
|
module DDQL
|
2
|
-
class Parser
|
3
|
-
|
4
|
-
rule(:eq) { (str('==') | str('=')) }
|
5
|
-
rule(:ge) { str('>=') }
|
6
|
-
rule(:gt) { str('>') }
|
7
|
-
rule(:is) { str('IS') }
|
8
|
-
rule(:isnt) { str('IS NOT') }
|
9
|
-
rule(:le) { str('<=') }
|
10
|
-
rule(:lt) { str('<') }
|
11
|
-
rule(:ne) { str('!=') }
|
12
|
-
rule(:no) { str('NO') }
|
13
|
-
rule(:null) { str('NULL') }
|
14
|
-
rule(:yes) { str('YES') }
|
15
|
-
|
16
|
-
# constants
|
17
|
-
rule(:comma) { str(',') }
|
18
|
-
rule(:digit) { match('[0-9]') }
|
19
|
-
rule(:group_by) { str('GROUP BY') }
|
20
|
-
rule(:spaces) { match('\s').repeat(1) }
|
21
|
-
rule(:spaces?) { spaces.maybe }
|
22
|
-
rule(:tick) { str("'") }
|
23
|
-
rule(:string) { (tick >> (tick.absent? >> any).repeat.as(:string) >> tick) }
|
24
|
-
|
25
|
-
# brackets
|
26
|
-
rule(:lbracket) { str('[') }
|
27
|
-
rule(:lparen) { spaces? >> str('(') >> spaces? }
|
28
|
-
rule(:lsquiggly) { spaces? >> str('{') >> spaces? }
|
29
|
-
rule(:rbracket) { str(']') }
|
30
|
-
rule(:rparen) { spaces? >> str(')') >> spaces? }
|
31
|
-
rule(:rsquiggly) { spaces? >> str('}') >> spaces? }
|
32
|
-
|
33
|
-
# math ops
|
34
|
-
rule(:math_add) { str('+').as(:op_add) }
|
35
|
-
rule(:math_subtract) { str('-').as(:op_subtract) }
|
36
|
-
rule(:math_multiply) { str('*').as(:op_multiply) }
|
37
|
-
rule(:math_divide) { str('/').as(:op_divide) }
|
38
|
-
rule(:math_mod) { str('%').as(:op_mod) }
|
39
|
-
rule(:math_power) { str('^').as(:op_power) }
|
40
|
-
rule(:math_operation) do
|
41
|
-
math_add | math_subtract | math_multiply | math_divide | math_mod | math_power
|
42
|
-
end
|
43
|
-
|
44
|
-
# boolean operators
|
45
|
-
rule(:bool_not) { spaces? >> str('NOT').as(:op_not) >> spaces? }
|
46
|
-
rule(:or_operator) { spaces? >> str('OR').as(:op_or) >> spaces? }
|
47
|
-
rule(:and_operator) { spaces? >> str('AND').as(:op_and) >> spaces? }
|
48
|
-
|
49
|
-
# string operators
|
50
|
-
rule(:ctn_operator) { spaces? >> (str('CTN') | str('LCTN')).as(:op_ctn) >> spaces? }
|
51
|
-
rule(:stw_operator) { spaces? >> str('STW').as(:op_stw) >> spaces? }
|
52
|
-
|
53
|
-
# date operators
|
54
|
-
rule(:date_on_operator) { spaces? >> str('ON').as(:op_date_on) >> spaces? }
|
55
|
-
rule(:date_after_operator) { spaces? >> str('PST').as(:op_date_after) >> spaces? }
|
56
|
-
rule(:date_after_operator_or_on) { spaces? >> str('EPST').as(:op_date_after_or_on) >> spaces? }
|
57
|
-
rule(:date_before_operator) { spaces? >> str('PRE').as(:op_date_before) >> spaces? }
|
58
|
-
rule(:date_before_operator_or_on) { spaces? >> str('EPRE').as(:op_date_before_or_on) >> spaces? }
|
59
|
-
|
60
|
-
# currency support
|
61
|
-
rule(:currency_code) { match('[A-Z]').repeat(3) }
|
62
|
-
rule(:currency) do
|
63
|
-
tick.maybe >>
|
64
|
-
(
|
65
|
-
tick.absent? >>
|
66
|
-
currency_code.as(:currency_code) >>
|
67
|
-
str(':') >>
|
68
|
-
(float_numeric | int_numeric).as(:currency_value)
|
69
|
-
) >>
|
70
|
-
tick.maybe
|
71
|
-
end
|
72
|
-
|
73
|
-
# numbers
|
74
|
-
rule(:float_numeric) do
|
75
|
-
(str('-').maybe >> (
|
76
|
-
str('0') | (match('[1-9]') >> digit.repeat)
|
77
|
-
) >> (
|
78
|
-
str('.') >> digit.repeat(1)
|
79
|
-
) >> (
|
80
|
-
match('[eE]') >> (str('+') | str('-')).maybe >> digit.repeat(1)
|
81
|
-
).maybe
|
82
|
-
).as(:float)
|
83
|
-
end
|
84
|
-
rule(:float) do
|
85
|
-
(tick.maybe >> (tick.absent? >> float_numeric >> tick)) | float_numeric
|
86
|
-
end
|
87
|
-
rule(:int_numeric) { (str('-').maybe >> digit.repeat(1)).as(:int) }
|
88
|
-
rule(:int) do
|
89
|
-
(tick.maybe >> (tick.absent? >> int_numeric >> tick)) | int_numeric
|
90
|
-
end
|
91
|
-
|
92
|
-
# special markers
|
93
|
-
rule(:current_date) { str('$CURRENT_DATE').as(:current_date) }
|
94
|
-
rule(:current_quarter) { str('$CURRENT_QUARTER').as(:current_quarter) }
|
95
|
-
rule(:current_year) { str('$CURRENT_YEAR').as(:current_year) }
|
96
|
-
rule(:relative_end_date) { str('$END_DATE').as(:relative_end_date) }
|
97
|
-
rule(:relative_end_quarter) { str('$END_QUARTER').as(:relative_end_quarter) }
|
98
|
-
rule(:relative_end_year) { str('$END_YEAR').as(:relative_end_year) }
|
99
|
-
rule(:relative_start_date) { str('$START_DATE').as(:relative_start_date) }
|
100
|
-
rule(:relative_start_quarter) { str('$START_QUARTER').as(:relative_start_quarter) }
|
101
|
-
rule(:relative_start_year) { str('$START_YEAR').as(:relative_start_year) }
|
102
|
-
rule(:special_marker) do
|
103
|
-
(
|
104
|
-
current_year | current_date | current_quarter |
|
105
|
-
relative_end_date | relative_end_quarter | relative_end_year |
|
106
|
-
relative_start_date | relative_start_quarter | relative_start_year
|
107
|
-
).as(:special_marker)
|
108
|
-
end
|
109
|
-
|
110
|
-
# nulls
|
111
|
-
rule(:null_no_information) { str('NO_INFORMATION') }
|
112
|
-
rule(:null_not_applicable) { str('NOT_APPLICABLE') }
|
113
|
-
rule(:null_not_collected) { str('NOT_COLLECTED') }
|
114
|
-
rule(:null_not_disclosed) { str('NOT_DISCLOSED') }
|
115
|
-
rule(:null_not_meaningful) { str('NOT_MEANINGFUL') }
|
116
|
-
rule(:null_value_type) do
|
117
|
-
(
|
118
|
-
null_no_information |
|
119
|
-
null_not_applicable |
|
120
|
-
null_not_collected |
|
121
|
-
null_not_disclosed |
|
122
|
-
null_not_meaningful
|
123
|
-
).as(:null_value_type)
|
124
|
-
end
|
125
|
-
|
126
|
-
# array operators
|
127
|
-
rule(:arrays_all_operator) { str('ALL').as(:op_all) }
|
128
|
-
rule(:arrays_any_operator) { str('ANY').as(:op_any) }
|
129
|
-
rule(:arrays_in_operator) { str('IN').as(:op_in) }
|
130
|
-
rule(:arrays_none_operator) { str('NONE').as(:op_none) }
|
131
|
-
rule(:arrays_operator) do
|
132
|
-
arrays_all_operator | arrays_any_operator | arrays_in_operator | arrays_none_operator
|
2
|
+
class Parser
|
3
|
+
class ParseException < StandardError
|
133
4
|
end
|
134
5
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
rule(:ge_operator) { ge.as(:op_ge) }
|
140
|
-
rule(:gt_operator) { gt.as(:op_gt) }
|
141
|
-
rule(:is_not_null_operator) { (isnt >> spaces >> null).as(:op_is_not_null) }
|
142
|
-
rule(:is_null_operator) { (is >> spaces >> null).as(:op_is_null) }
|
143
|
-
rule(:is_operator) { is.as(:op_is) }
|
144
|
-
rule(:le_operator) { le.as(:op_le) }
|
145
|
-
rule(:lt_operator) { lt.as(:op_lt) }
|
146
|
-
rule(:ne_operator) { ne.as(:op_ne) }
|
147
|
-
rule(:no_operator) { no.as(:op_no) }
|
148
|
-
rule(:relational_operator) { eq_operator | le_operator | ge_operator | lt_operator | gt_operator | ne_operator }
|
149
|
-
rule(:string_rel_operator) { ctn_operator | stw_operator }
|
150
|
-
rule(:yes_operator) { yes.as(:op_yes) }
|
151
|
-
|
152
|
-
# aggregations
|
153
|
-
rule(:avg_operator) { str('AVG').as(:op_avg) }
|
154
|
-
rule(:cnt_operator) { str('CNT').as(:op_cnt) }
|
155
|
-
rule(:ext_operator) { str('EXISTS').as(:op_exist) }
|
156
|
-
rule(:max_operator) { str('MAX').as(:op_max) }
|
157
|
-
rule(:med_operator) { str('MED').as(:op_med) }
|
158
|
-
rule(:min_operator) { str('MIN').as(:op_min) }
|
159
|
-
rule(:sum_operator) { str('SUM').as(:op_sum) }
|
160
|
-
rule(:grouping_operator) { (max_operator | min_operator).as(:agg) }
|
161
|
-
rule(:agg_operator) do
|
162
|
-
(avg_operator | cnt_operator | ext_operator | med_operator | sum_operator).as(:agg)
|
163
|
-
end
|
164
|
-
rule(:aggregation_operator) do
|
165
|
-
# Examples:
|
166
|
-
# CNT {type: IssuerCase, fields: [], expression: ([CaseFlag] == 'Amber' AND [CaseFlag] == 'RED' AND [CaseFlag] == 'Green')} >= '0'
|
167
|
-
# CNT {type: IssuerPerson, expression: [ipAssociationType] == 'Director'}
|
168
|
-
(agg_operator >>
|
169
|
-
lsquiggly >>
|
170
|
-
(rsquiggly.absent? >> any).repeat(1).as(:sub_query_info) >>
|
171
|
-
rsquiggly >>
|
172
|
-
relational_operator.as(:op) >>
|
173
|
-
value.as(:right)
|
174
|
-
) | (agg_operator >>
|
175
|
-
lsquiggly >>
|
176
|
-
(rsquiggly.absent? >> any).repeat(1).as(:sub_query_info) >>
|
177
|
-
rsquiggly
|
178
|
-
) | (grouping_operator >>
|
179
|
-
lsquiggly >>
|
180
|
-
(rsquiggly.absent? >> any).repeat(1).as(:sub_query_info) >>
|
181
|
-
rsquiggly >>
|
182
|
-
spaces? >>
|
183
|
-
group_by >>
|
184
|
-
spaces? >>
|
185
|
-
factor.as(:group_by)
|
186
|
-
)
|
6
|
+
class NoTokenException < ParseException
|
7
|
+
def initialize
|
8
|
+
super 'No token found - ensure literals are surrounded by single quotes'
|
9
|
+
end
|
187
10
|
end
|
188
11
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
rule(:sub_query_fields) do
|
194
|
-
spaces? >> str('fields:') >> spaces? >> (factor | (lbracket >> spaces?.as(:factor) >> rbracket)).as(:fields) >> spaces?
|
195
|
-
end
|
196
|
-
rule(:sub_query_qualifier) do
|
197
|
-
spaces? >> str('qualifier:') >> spaces? >> string.as(:assoc_qualifier)
|
198
|
-
end
|
199
|
-
rule(:sub_query_expression) do
|
200
|
-
spaces? >> str('expression:') >> spaces? >> top.as(:expression)
|
201
|
-
end
|
202
|
-
rule(:sub_query_info_parsing) do
|
203
|
-
(sub_query_type >> (comma >> sub_query_qualifier).maybe >> comma >> sub_query_expression >> comma >> sub_query_fields) |
|
204
|
-
(sub_query_type >> (comma >> sub_query_qualifier).maybe >> comma >> sub_query_expression >> comma.absent?) |
|
205
|
-
(sub_query_type >> (comma >> sub_query_qualifier).maybe >> comma >> (sub_query_fields >> comma).maybe >> sub_query_expression) |
|
206
|
-
(sub_query_fields >> comma >> sub_query_type >> (comma >> sub_query_qualifier).maybe >> comma >> sub_query_expression) |
|
207
|
-
(sub_query_fields >> comma >> sub_query_expression >> comma >> sub_query_type >> (comma >> sub_query_qualifier).maybe) |
|
208
|
-
(sub_query_expression >> comma >> sub_query_type >> (comma >> sub_query_qualifier).maybe >> (comma >> sub_query_fields).maybe) |
|
209
|
-
(sub_query_expression >> comma >> (sub_query_fields >> comma).maybe >> sub_query_type >> (comma >> sub_query_qualifier).maybe) |
|
210
|
-
(sub_query_type >> comma >> sub_query_fields) | # for MIN/MAX {...} GROUP BY [...]
|
211
|
-
(sub_query_fields >> comma >> sub_query_type) # for MIN/MAX {...} GROUP BY [...]
|
12
|
+
def initialize(expression)
|
13
|
+
@expression = expression
|
14
|
+
@tokens = Lexer.lex(expression)
|
15
|
+
@depth = 0
|
212
16
|
end
|
213
17
|
|
214
|
-
|
215
|
-
|
216
|
-
|
18
|
+
def consume(token_type)
|
19
|
+
token = @tokens.poll
|
20
|
+
if token.nil? || token_type != token.type
|
21
|
+
message = "Expected token[#{token_type.name}], got #{token.nil? ? 'nil' : "[#{token.type.name}]"}\n"
|
22
|
+
if token
|
23
|
+
message += " #{@expression}\n"
|
24
|
+
message += " #{' ' * token.location}^"
|
25
|
+
end
|
26
|
+
raise ParseException, message
|
27
|
+
end
|
28
|
+
token
|
217
29
|
end
|
218
30
|
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
end
|
31
|
+
def parse(precedence: 0)
|
32
|
+
@depth += 1
|
33
|
+
token = @tokens.poll
|
34
|
+
raise NoTokenException if token.nil?
|
224
35
|
|
225
|
-
|
226
|
-
|
227
|
-
|
36
|
+
expression = token.parse(self)
|
37
|
+
while precedence < next_precedence
|
38
|
+
token = @tokens.poll
|
39
|
+
expression = token.parse(self, expression: expression)
|
40
|
+
end
|
41
|
+
@depth -= 1
|
228
42
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
rule(:math_operand) do
|
234
|
-
spaces? >> (special_marker | float | int | factor | aggregation_operator) >> spaces?
|
235
|
-
end
|
236
|
-
|
237
|
-
rule(:equation) do
|
238
|
-
(math_operand.as(:left) >> spaces? >> math_operation >> spaces? >> math_operand.as(:right)) |
|
239
|
-
(math_operand.as(:left) >> spaces? >> math_operation >> spaces? >> equation.as(:right)) #|
|
240
|
-
end
|
241
|
-
|
242
|
-
rule(:factor_comparison) do
|
243
|
-
(factor.as(:left) >> spaces >> is_not_null_operator.as(:op)) |
|
244
|
-
(factor.as(:left) >> spaces >> is_null_operator.as(:op)) |
|
245
|
-
(factor.as(:left) >> spaces >> is_operator.as(:op) >> spaces >> null_value_type.as(:right)) |
|
246
|
-
(value.as(:left) >> operator >> value.as(:right)) |
|
247
|
-
(factor.as(:left) >> (string_rel_operator | date_rel_operator).as(:op) >> string.as(:right)) |
|
248
|
-
(factor.as(:left) >> spaces? >> (no_operator | yes_operator).as(:yes_no_op))
|
249
|
-
end
|
43
|
+
if @depth == 0 && !peek.nil?
|
44
|
+
raise ParseException, "Unable to fully parse expression; token[#{peek}], possible cause: invalid operator"
|
45
|
+
end
|
250
46
|
|
251
|
-
|
252
|
-
equation |
|
253
|
-
sub_query_info_parsing |
|
254
|
-
(
|
255
|
-
bool_not.maybe >> (
|
256
|
-
(lparen >> entity >> rparen) |
|
257
|
-
aggregation_operator |
|
258
|
-
(factor_comparison) |
|
259
|
-
factor.as(:value_of) |
|
260
|
-
special_marker |
|
261
|
-
top
|
262
|
-
)
|
263
|
-
)
|
47
|
+
expression
|
264
48
|
end
|
265
49
|
|
266
|
-
|
267
|
-
|
268
|
-
(lparen >> spaces? >> (top | entity | factor) >> spaces? >> rparen) |
|
269
|
-
entity
|
270
|
-
) >> spaces?
|
50
|
+
def peek
|
51
|
+
@tokens.peek
|
271
52
|
end
|
272
53
|
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
54
|
+
private
|
55
|
+
def next_precedence
|
56
|
+
token = peek
|
57
|
+
return 0 if token.nil?
|
58
|
+
Operators.instance.cache[token.op_data]&.precedence || 0
|
278
59
|
end
|
279
|
-
|
280
|
-
root(:top)
|
281
60
|
end
|
282
|
-
end
|
61
|
+
end
|