ddql 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/Gemfile.lock +20 -20
- data/lib/ddql.rb +2 -1
- data/lib/ddql/agg_operator.rb +69 -11
- data/lib/ddql/blank_refinements.rb +21 -0
- data/lib/ddql/coalesce_operator.rb +5 -6
- data/lib/ddql/infix_float_map_operator.rb +2 -2
- data/lib/ddql/infix_string_map_operator.rb +2 -2
- data/lib/ddql/linked_list.rb +134 -30
- data/lib/ddql/list_operator.rb +2 -2
- data/lib/ddql/lookup_operator.rb +8 -6
- data/lib/ddql/operator.rb +14 -4
- data/lib/ddql/operators.rb +53 -52
- data/lib/ddql/parser.rb +68 -2
- data/lib/ddql/postfix_null_type_operator.rb +2 -2
- data/lib/ddql/string_refinements.rb +4 -0
- data/lib/ddql/token.rb +15 -3
- data/lib/ddql/token_type.rb +125 -33
- data/lib/ddql/version.rb +1 -1
- metadata +8 -7
data/lib/ddql/list_operator.rb
CHANGED
data/lib/ddql/lookup_operator.rb
CHANGED
@@ -6,16 +6,18 @@ module DDQL
|
|
6
6
|
super("LOOKUP BY", "Lookup", type: :infix, return_type: :string)
|
7
7
|
end
|
8
8
|
|
9
|
-
def parse(parser,
|
9
|
+
def parse(parser, _token, expression: nil)
|
10
10
|
precedence = self.precedence
|
11
11
|
precedence -= 1 if right?
|
12
12
|
next_expression = parser.parse(precedence: precedence)
|
13
|
-
|
14
|
-
|
13
|
+
|
14
|
+
foreign_value_factor = expression[:factor]
|
15
|
+
foreign_key_factor = (next_expression.delete(:left) || next_expression)[:factor]
|
15
16
|
{
|
16
|
-
|
17
|
-
|
18
|
-
|
17
|
+
op_lookup_by: {
|
18
|
+
foreign_key: {factor: foreign_key_factor},
|
19
|
+
foreign_value: {factor: foreign_value_factor},
|
20
|
+
},
|
19
21
|
}
|
20
22
|
end
|
21
23
|
end
|
data/lib/ddql/operator.rb
CHANGED
@@ -1,15 +1,18 @@
|
|
1
1
|
module DDQL
|
2
2
|
class Operator
|
3
|
-
attr_reader :associativity, :symbol, :name, :type, :precedence, :return_type
|
3
|
+
attr_reader :associativity, :symbol, :name, :type, :precedence, :return_type
|
4
4
|
|
5
|
-
def initialize(symbol, name, type, precedence, right, return_type
|
5
|
+
def initialize(symbol, name, type, precedence, right, return_type)
|
6
6
|
@symbol = symbol
|
7
7
|
@name = name
|
8
8
|
@type = type
|
9
9
|
@precedence = precedence
|
10
10
|
@associativity = right ? :right : :left
|
11
11
|
@return_type = return_type
|
12
|
-
|
12
|
+
end
|
13
|
+
|
14
|
+
def any_type?
|
15
|
+
return_type == :any
|
13
16
|
end
|
14
17
|
|
15
18
|
def boolean?
|
@@ -118,10 +121,14 @@ module DDQL
|
|
118
121
|
|
119
122
|
def parse_infix(parser, token, expression)
|
120
123
|
precedence = self.precedence
|
121
|
-
precedence -=
|
124
|
+
precedence -= 0.05 if right?
|
122
125
|
next_expression = parser.parse(precedence: precedence)
|
123
126
|
op_expression = token.as_hash
|
124
127
|
|
128
|
+
if math? && next_expression&.dig(:left, :factor) && next_expression.size == 1
|
129
|
+
return op_expression.merge(left: expression, right: next_expression[:left])
|
130
|
+
end
|
131
|
+
|
125
132
|
if token.and? || token.or?
|
126
133
|
return boolean_stmt(expression, op_expression, next_expression)
|
127
134
|
end
|
@@ -162,6 +169,9 @@ module DDQL
|
|
162
169
|
next_expression = next_expression[:lstatement]
|
163
170
|
elsif next_expression.key?(:rstatement) && !next_expression.key?(:lstatement)
|
164
171
|
next_expression = next_expression[:rstatement]
|
172
|
+
elsif op_expression[:op].key?(:op_not) && next_expression.key?(:op_not)
|
173
|
+
next_expression.delete(:op_not)
|
174
|
+
return next_expression
|
165
175
|
end
|
166
176
|
op_expression[:op].merge(next_expression)
|
167
177
|
end
|
data/lib/ddql/operators.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'singleton'
|
2
|
-
|
3
2
|
require_relative 'operator'
|
4
3
|
require_relative 'agg_operator'
|
5
4
|
require_relative 'coalesce_operator'
|
@@ -38,33 +37,36 @@ module DDQL
|
|
38
37
|
private
|
39
38
|
def build_cache
|
40
39
|
cache = {}
|
41
|
-
Operator.new(
|
42
|
-
Operator.new(
|
43
|
-
Operator.new("
|
44
|
-
Operator.new("
|
45
|
-
Operator.new("
|
46
|
-
Operator.new("
|
47
|
-
Operator.new("
|
48
|
-
|
49
|
-
Operator.new("
|
50
|
-
Operator.new("
|
51
|
-
Operator.new("
|
52
|
-
Operator.new("
|
53
|
-
Operator.new("
|
54
|
-
Operator.new("
|
55
|
-
Operator.new("
|
56
|
-
Operator.new("
|
57
|
-
Operator.new("
|
58
|
-
Operator.new("
|
59
|
-
Operator.new("
|
60
|
-
Operator.new("
|
61
|
-
Operator.new("
|
40
|
+
Operator.new(TokenType::NESTED_OPEN_PATTERN, "Nested Query Opener", :prefix, 3, false, :none).register(cache)
|
41
|
+
Operator.new(TokenType::NESTED_CLOSE_PATTERN, "Nested Query Closer", :postfix, 3, false, :none).register(cache)
|
42
|
+
Operator.new("==", "Double Equals", :infix, 3, false, :boolean).register(cache)
|
43
|
+
Operator.new("=", "Single Equals", :infix, 3, false, :boolean).register(cache)
|
44
|
+
Operator.new("!=", "Not Equals", :infix, 3, false, :boolean).register(cache)
|
45
|
+
Operator.new("<=", "Less Than or Equals", :infix, 4, false, :boolean).register(cache)
|
46
|
+
Operator.new("<", "Less Than", :infix, 4, false, :boolean).register(cache)
|
47
|
+
Operator.new(">=", "Greater Than or Equals", :infix, 4, false, :boolean).register(cache)
|
48
|
+
Operator.new(">", "Greater Than", :infix, 4, false, :boolean).register(cache)
|
49
|
+
Operator.new("CTN", "Contains", :infix, 4, false, :boolean).register(cache)
|
50
|
+
Operator.new("STW", "Starts With", :infix, 4, false, :boolean).register(cache)
|
51
|
+
Operator.new("IN", "In Any", :infix, 4, false, :boolean).register(cache)
|
52
|
+
Operator.new("LCTN", "Contains", :infix, 4, false, :boolean).register(cache)
|
53
|
+
Operator.new("ON", "On Date", :infix, 4, false, :boolean).register(cache)
|
54
|
+
Operator.new("EPST", "On Or After Date", :infix, 4, false, :boolean).register(cache)
|
55
|
+
Operator.new("PST", "After Date", :infix, 4, false, :boolean).register(cache)
|
56
|
+
Operator.new("EPRE", "On Or Before Date", :infix, 4, false, :boolean).register(cache)
|
57
|
+
Operator.new("PRE", "Before Date", :infix, 4, false, :boolean).register(cache)
|
58
|
+
Operator.new("OR", "Or", :infix, 0.1, false, :boolean).register(cache)
|
59
|
+
Operator.new("AND", "And", :infix, 0.2, false, :boolean).register(cache)
|
60
|
+
Operator.new("NOT", "Not", :prefix, 0.3, false, :boolean).register(cache)
|
61
|
+
Operator.new("IS NULL", "Is Null", :postfix, 9, false, :boolean).register(cache)
|
62
|
+
Operator.new("IS NOT NULL", "Is Not Null", :postfix, 9, false, :boolean).register(cache)
|
62
63
|
Operator.new("+", "Plus", :infix, 5, false, :double).register(cache)
|
63
64
|
Operator.new("-", "Minus", :infix, 5, false, :double).register(cache)
|
64
65
|
Operator.new("*", "Multiplied By", :infix, 6, false, :double).register(cache)
|
65
66
|
Operator.new("/", "Divided By", :infix, 6, false, :double).register(cache)
|
66
67
|
Operator.new("%", "Modulus", :infix, 6, false, :integer).register(cache)
|
67
68
|
Operator.new("^", "To the Power of", :infix, 7, true, :double).register(cache)
|
69
|
+
AggOperator.new("ALIAS", "Alias", return_type: :any).register(cache)
|
68
70
|
AggOperator.new("EXISTS", "Exists", return_type: :boolean).register(cache)
|
69
71
|
AggOperator.new("CNT", "Count", return_type: :integer).register(cache)
|
70
72
|
AggOperator.new("AVG", "Mean", return_type: :double).register(cache)
|
@@ -78,36 +80,35 @@ module DDQL
|
|
78
80
|
# PNT & RATIO don't work and aren't used in DD PROD as of 2020-07-24
|
79
81
|
# AggOperator.new("PNT", "Percent", return_type: :double).register(cache)
|
80
82
|
# AggOperator.new("RATIO", "Ratio", return_type: :double).register(cache)
|
81
|
-
InfixStringMapOperator.new("ANY_MAP", "Has Any"
|
82
|
-
InfixStringMapOperator.new("ALL_MAP", "Has All"
|
83
|
-
InfixStringMapOperator.new("NONE_MAP", "Has None"
|
84
|
-
InfixFloatMapOperator.new("ANY_GREATER_THAN_FLOAT_MAP", "Has Any Greater Than",
|
85
|
-
InfixFloatMapOperator.new("ALL_GREATER_THAN_FLOAT_MAP", "Has All Greater Than",
|
86
|
-
InfixFloatMapOperator.new("NONE_GREATER_THAN_FLOAT_MAP", "Has None Greater Than",
|
87
|
-
InfixFloatMapOperator.new("ANY_GREATER_THAN_OR_EQUAL_FLOAT_MAP", "Has Any Greater Than Or Equal To",
|
88
|
-
InfixFloatMapOperator.new("ALL_GREATER_THAN_OR_EQUAL_FLOAT_MAP", "Has All Greater Than Or Equal To",
|
89
|
-
InfixFloatMapOperator.new("NONE_GREATER_THAN_OR_EQUAL_FLOAT_MAP", "Has None Greater Than Or Equal To",
|
90
|
-
InfixFloatMapOperator.new("ANY_LESS_THAN_FLOAT_MAP", "Has Any Less Than",
|
91
|
-
InfixFloatMapOperator.new("ALL_LESS_THAN_FLOAT_MAP", "Has All Less Than",
|
92
|
-
InfixFloatMapOperator.new("NONE_LESS_THAN_FLOAT_MAP", "Has None Less Than",
|
93
|
-
InfixFloatMapOperator.new("ANY_LESS_THAN_OR_EQUAL_FLOAT_MAP", "Has Any Less Than Or Equal To",
|
94
|
-
InfixFloatMapOperator.new("ALL_LESS_THAN_OR_EQUAL_FLOAT_MAP", "Has All Less Than Or Equal To",
|
95
|
-
InfixFloatMapOperator.new("NONE_LESS_THAN_OR_EQUAL_FLOAT_MAP", "Has None Less Than Or Equal To",
|
96
|
-
InfixFloatMapOperator.new("ANY_EQUAL_FLOAT_MAP", "Has Any Equal To",
|
97
|
-
InfixFloatMapOperator.new("ALL_EQUAL_FLOAT_MAP", "Has All Equal To",
|
98
|
-
InfixFloatMapOperator.new("NONE_EQUAL_FLOAT_MAP", "Has None Equal To",
|
99
|
-
ListOperator.new("ANY", "Has Any", :infix
|
100
|
-
ListOperator.new("ALL", "Has All", :infix
|
101
|
-
ListOperator.new("NONE", "Has None", :infix
|
102
|
-
ListOperator.new("EMPTY", "Is Empty", :postfix
|
103
|
-
PostfixNullTypeOperator.new("NO_INFORMATION", :NoInformation
|
104
|
-
PostfixNullTypeOperator.new("NOT_APPLICABLE", :NotApplicable
|
105
|
-
PostfixNullTypeOperator.new("NOT_COLLECTED", :NotCollected
|
106
|
-
PostfixNullTypeOperator.new("NOT_DISCLOSED", :NotDisclosed
|
107
|
-
PostfixNullTypeOperator.new("NOT_MEANINGFUL", :NotMeaningful
|
108
|
-
Operator.new("
|
109
|
-
Operator.new("
|
110
|
-
Operator.new("NO", "Is False", :postfix, 9, false, :boolean, 1).register(cache)
|
83
|
+
InfixStringMapOperator.new("ANY_MAP", "Has Any").register(cache)
|
84
|
+
InfixStringMapOperator.new("ALL_MAP", "Has All").register(cache)
|
85
|
+
InfixStringMapOperator.new("NONE_MAP", "Has None").register(cache)
|
86
|
+
InfixFloatMapOperator.new("ANY_GREATER_THAN_FLOAT_MAP", "Has Any Greater Than", :any, :gt).register(cache)
|
87
|
+
InfixFloatMapOperator.new("ALL_GREATER_THAN_FLOAT_MAP", "Has All Greater Than", :all, :gt).register(cache)
|
88
|
+
InfixFloatMapOperator.new("NONE_GREATER_THAN_FLOAT_MAP", "Has None Greater Than", :none, :gt).register(cache)
|
89
|
+
InfixFloatMapOperator.new("ANY_GREATER_THAN_OR_EQUAL_FLOAT_MAP", "Has Any Greater Than Or Equal To", :any, :ge).register(cache)
|
90
|
+
InfixFloatMapOperator.new("ALL_GREATER_THAN_OR_EQUAL_FLOAT_MAP", "Has All Greater Than Or Equal To", :all, :ge).register(cache)
|
91
|
+
InfixFloatMapOperator.new("NONE_GREATER_THAN_OR_EQUAL_FLOAT_MAP", "Has None Greater Than Or Equal To", :none, :ge).register(cache)
|
92
|
+
InfixFloatMapOperator.new("ANY_LESS_THAN_FLOAT_MAP", "Has Any Less Than", :any, :lt).register(cache)
|
93
|
+
InfixFloatMapOperator.new("ALL_LESS_THAN_FLOAT_MAP", "Has All Less Than", :all, :lt).register(cache)
|
94
|
+
InfixFloatMapOperator.new("NONE_LESS_THAN_FLOAT_MAP", "Has None Less Than", :none, :lt).register(cache)
|
95
|
+
InfixFloatMapOperator.new("ANY_LESS_THAN_OR_EQUAL_FLOAT_MAP", "Has Any Less Than Or Equal To", :any, :le).register(cache)
|
96
|
+
InfixFloatMapOperator.new("ALL_LESS_THAN_OR_EQUAL_FLOAT_MAP", "Has All Less Than Or Equal To", :all, :le).register(cache)
|
97
|
+
InfixFloatMapOperator.new("NONE_LESS_THAN_OR_EQUAL_FLOAT_MAP", "Has None Less Than Or Equal To", :none, :le).register(cache)
|
98
|
+
InfixFloatMapOperator.new("ANY_EQUAL_FLOAT_MAP", "Has Any Equal To", :any, :eq).register(cache)
|
99
|
+
InfixFloatMapOperator.new("ALL_EQUAL_FLOAT_MAP", "Has All Equal To", :all, :eq).register(cache)
|
100
|
+
InfixFloatMapOperator.new("NONE_EQUAL_FLOAT_MAP", "Has None Equal To", :none, :eq).register(cache)
|
101
|
+
ListOperator.new("ANY", "Has Any", :infix).register(cache)
|
102
|
+
ListOperator.new("ALL", "Has All", :infix).register(cache)
|
103
|
+
ListOperator.new("NONE", "Has None", :infix).register(cache)
|
104
|
+
ListOperator.new("EMPTY", "Is Empty", :postfix).register(cache)
|
105
|
+
PostfixNullTypeOperator.new("NO_INFORMATION", :NoInformation).register(cache)
|
106
|
+
PostfixNullTypeOperator.new("NOT_APPLICABLE", :NotApplicable).register(cache)
|
107
|
+
PostfixNullTypeOperator.new("NOT_COLLECTED", :NotCollected).register(cache)
|
108
|
+
PostfixNullTypeOperator.new("NOT_DISCLOSED", :NotDisclosed).register(cache)
|
109
|
+
PostfixNullTypeOperator.new("NOT_MEANINGFUL", :NotMeaningful).register(cache)
|
110
|
+
Operator.new("YES", "Is True", :postfix, 9, false, :boolean).register(cache)
|
111
|
+
Operator.new("NO", "Is False", :postfix, 9, false, :boolean).register(cache)
|
111
112
|
end
|
112
113
|
end
|
113
114
|
end
|
data/lib/ddql/parser.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
module DDQL
|
2
2
|
class Parser
|
3
|
+
attr_reader :tokens
|
4
|
+
|
3
5
|
class ParseException < StandardError
|
4
6
|
end
|
5
7
|
|
@@ -9,10 +11,51 @@ module DDQL
|
|
9
11
|
end
|
10
12
|
end
|
11
13
|
|
12
|
-
|
14
|
+
class ResolvedToken
|
15
|
+
attr_reader :value
|
16
|
+
|
17
|
+
def initialize(v)
|
18
|
+
@value = v
|
19
|
+
end
|
20
|
+
|
21
|
+
def parse(_)
|
22
|
+
value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.from_tokens(tokens)
|
27
|
+
opener = tokens.doubly_linked!.find { |node| node.value.type == TokenType::NESTED_OPENER }
|
28
|
+
if opener
|
29
|
+
closer = tokens.find_from_tail { |node| node.value.type == TokenType::NESTED_CLOSER }
|
30
|
+
new_tokens = DDQL::LinkedList.new
|
31
|
+
current = opener
|
32
|
+
while (current = current.next) && !(current === closer)
|
33
|
+
new_tokens << current.dup
|
34
|
+
end
|
35
|
+
new_tokens.tail.next = nil
|
36
|
+
new(tokens: tokens.replace!(
|
37
|
+
from: opener,
|
38
|
+
to: closer,
|
39
|
+
with: ResolvedToken.new(sub_query: from_tokens(new_tokens).parse)),
|
40
|
+
)
|
41
|
+
else
|
42
|
+
new(tokens: tokens)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.parse(expr)
|
47
|
+
from_tokens(Lexer.lex(expr)).record_expression(expr).parse
|
48
|
+
end
|
49
|
+
|
50
|
+
def initialize(expression: nil, tokens: nil)
|
13
51
|
@expression = expression
|
14
|
-
@tokens = Lexer.lex(expression)
|
15
52
|
@depth = 0
|
53
|
+
if expression
|
54
|
+
@tokens = Lexer.lex(expression)
|
55
|
+
else
|
56
|
+
@tokens = tokens
|
57
|
+
end
|
58
|
+
raise "tokens cannot be determined" if @tokens.nil? || @tokens.empty?
|
16
59
|
end
|
17
60
|
|
18
61
|
def consume(token_type)
|
@@ -31,6 +74,7 @@ module DDQL
|
|
31
74
|
def parse(precedence: 0)
|
32
75
|
@depth += 1
|
33
76
|
token = @tokens.poll
|
77
|
+
|
34
78
|
raise NoTokenException if token.nil?
|
35
79
|
|
36
80
|
expression = token.parse(self)
|
@@ -51,6 +95,28 @@ module DDQL
|
|
51
95
|
@tokens.peek
|
52
96
|
end
|
53
97
|
|
98
|
+
def record_expression(expr)
|
99
|
+
@expression = expr
|
100
|
+
self
|
101
|
+
end
|
102
|
+
|
103
|
+
# supports reading until the next +token_type+, does not support
|
104
|
+
# nested reads of +token_type+
|
105
|
+
#
|
106
|
+
# @raise [RuntimeError] if +token_type+ is not found
|
107
|
+
# @return [LinkedList<Token>] tokens exclusive of the final token_type
|
108
|
+
def until(token_type)
|
109
|
+
new_list = LinkedList.new.doubly_linked!
|
110
|
+
while token = @tokens.poll
|
111
|
+
if token.type?(token_type)
|
112
|
+
return new_list
|
113
|
+
else
|
114
|
+
new_list << token
|
115
|
+
end
|
116
|
+
end
|
117
|
+
raise "expected to consume tokens up to type[#{token_type.name}]"
|
118
|
+
end
|
119
|
+
|
54
120
|
private
|
55
121
|
def next_precedence
|
56
122
|
token = peek
|
@@ -2,8 +2,8 @@ module DDQL
|
|
2
2
|
class PostfixNullTypeOperator < Operator
|
3
3
|
attr_reader :pattern
|
4
4
|
|
5
|
-
def initialize(symbol, null_type
|
6
|
-
super("IS #{symbol}", "Is #{symbol}", :postfix, 9, false, :boolean
|
5
|
+
def initialize(symbol, null_type)
|
6
|
+
super("IS #{symbol}", "Is #{symbol}", :postfix, 9, false, :boolean)
|
7
7
|
@null_type = null_type
|
8
8
|
@pattern = /IS\s+#{symbol}/
|
9
9
|
end
|
data/lib/ddql/token.rb
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
require 'forwardable'
|
2
2
|
|
3
3
|
module DDQL
|
4
|
-
|
5
4
|
class Token
|
6
|
-
using StringRefinements
|
7
|
-
|
8
5
|
attr_reader :data, :type
|
9
6
|
attr_accessor :location
|
10
7
|
|
8
|
+
using ::DDQL::StringRefinements
|
9
|
+
|
11
10
|
def initialize(data:, location: nil, type:)
|
12
11
|
@data = data
|
13
12
|
@location = location
|
@@ -50,6 +49,11 @@ module DDQL
|
|
50
49
|
type.parse(parser, self, expression: expression)
|
51
50
|
end
|
52
51
|
|
52
|
+
def post_process(parser:, expression:)
|
53
|
+
raise "#{type} doesn't support post-processing" unless supports_post_processing?
|
54
|
+
type.post_process(parser: parser, expression: expression)
|
55
|
+
end
|
56
|
+
|
53
57
|
def postfix?
|
54
58
|
type.postfix?
|
55
59
|
end
|
@@ -62,6 +66,10 @@ module DDQL
|
|
62
66
|
type.simple_comparison?(data)
|
63
67
|
end
|
64
68
|
|
69
|
+
def supports_post_processing?
|
70
|
+
type.supports_post_processing?
|
71
|
+
end
|
72
|
+
|
65
73
|
def to_h
|
66
74
|
type.as_hash(data)
|
67
75
|
end
|
@@ -69,5 +77,9 @@ module DDQL
|
|
69
77
|
def to_s
|
70
78
|
"#{type.name} : #{data}"
|
71
79
|
end
|
80
|
+
|
81
|
+
def type?(token_type)
|
82
|
+
token_type == type
|
83
|
+
end
|
72
84
|
end
|
73
85
|
end
|
data/lib/ddql/token_type.rb
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
module DDQL
|
2
2
|
class TokenType
|
3
|
-
using StringRefinements
|
4
|
-
|
5
3
|
attr_reader :label, :name, :pattern
|
6
4
|
|
5
|
+
using ::DDQL::StringRefinements
|
6
|
+
|
7
7
|
FACTOR_PATTERN = /\[[^\]]+\]/
|
8
|
+
NESTED_OPEN_PATTERN = '‹'
|
9
|
+
NESTED_CLOSE_PATTERN = '›'
|
8
10
|
|
9
11
|
def self.all_types_pattern
|
10
|
-
Regexp.compile(ALL.map { |tt| "(?<#{tt.name}>#{tt.pattern})" }.join('|'))
|
12
|
+
@pattern ||= Regexp.compile(ALL.map { |tt| "(?<#{tt.name}>#{tt.pattern})" }.join('|'))
|
11
13
|
end
|
12
14
|
|
13
15
|
def initialize(name:, pattern:, &block)
|
@@ -19,16 +21,12 @@ module DDQL
|
|
19
21
|
@value_transformer = block
|
20
22
|
end
|
21
23
|
|
24
|
+
def ==(other)
|
25
|
+
name == other.name
|
26
|
+
end
|
27
|
+
|
22
28
|
def as_hash(data)
|
23
|
-
|
24
|
-
# when :string_literal; {right: {string: data}}
|
25
|
-
# when :integer_literal; {right: {int: data}}
|
26
|
-
# when :numeric_literal, :sci_num_literal; {right: {float: data}}
|
27
|
-
# # when :lparen; {lstatement: data}
|
28
|
-
# when :rparen; Hash.new
|
29
|
-
# else
|
30
|
-
raise "subclass responsibility name[#{name}] data[#{data}]"
|
31
|
-
# end
|
29
|
+
raise "subclass responsibility name[#{name}] data[#{data}]"
|
32
30
|
end
|
33
31
|
|
34
32
|
def comparison?(data)
|
@@ -96,6 +94,10 @@ module DDQL
|
|
96
94
|
self
|
97
95
|
end
|
98
96
|
|
97
|
+
def supports_post_processing?
|
98
|
+
false
|
99
|
+
end
|
100
|
+
|
99
101
|
def trimming!(range=(1..-2))
|
100
102
|
@data_range = range
|
101
103
|
self
|
@@ -110,7 +112,6 @@ module DDQL
|
|
110
112
|
end
|
111
113
|
|
112
114
|
def as_hash(data)
|
113
|
-
# {right: {data_type => data}}
|
114
115
|
{data_type => data}
|
115
116
|
end
|
116
117
|
|
@@ -125,7 +126,7 @@ module DDQL
|
|
125
126
|
|
126
127
|
class Currency < Literal
|
127
128
|
def initialize
|
128
|
-
super(name: :currency, pattern: /'(?!')(?<code>[A-Z]{3}):(\d+\.?\d
|
129
|
+
super(name: :currency, pattern: /'(?!')(?<code>[A-Z]{3}):(\d+\.?\d*)'/)
|
129
130
|
@value_transformer = lambda do |s|
|
130
131
|
s = s.split(':', 2)
|
131
132
|
{currency_code: s.first, currency_value: {float: s.last.to_f}}
|
@@ -133,14 +134,13 @@ module DDQL
|
|
133
134
|
end
|
134
135
|
|
135
136
|
def as_hash(data)
|
136
|
-
# {right: data}
|
137
137
|
data
|
138
138
|
end
|
139
139
|
end
|
140
140
|
|
141
141
|
class Integer < Literal
|
142
142
|
def initialize
|
143
|
-
super(name: :integer, pattern: /'(?!')(?>[+-]?)(\d+)'/)
|
143
|
+
super(name: :integer, pattern: /'(?!['0])(?>[+-]?)(\d+)'/)
|
144
144
|
@value_transformer = -> (s) { s.to_i }
|
145
145
|
end
|
146
146
|
|
@@ -151,7 +151,7 @@ module DDQL
|
|
151
151
|
|
152
152
|
class Numeric < Literal
|
153
153
|
def initialize
|
154
|
-
super(name: :numeric, pattern: /'(?!')((?>[+-]?)(?>(?>\d
|
154
|
+
super(name: :numeric, pattern: /'((?!('|0\d)))((?>[+-]?)(?>(?>\d*)(?>\.?)(?>\d+)))'/)
|
155
155
|
@value_transformer = -> (s) { s.to_f }
|
156
156
|
end
|
157
157
|
|
@@ -162,7 +162,7 @@ module DDQL
|
|
162
162
|
|
163
163
|
class ScientificNumeric < Literal
|
164
164
|
def initialize
|
165
|
-
super(name: :sci_num, pattern: /'(?!')([+-]?\d(\.\d+)?[Ee][+-]
|
165
|
+
super(name: :sci_num, pattern: /'(?!('|0\d))([+-]?\d(\.\d+)?[Ee][+-]?\d+)'/)
|
166
166
|
@value_transformer = -> (s) { s.to_f }
|
167
167
|
end
|
168
168
|
|
@@ -192,6 +192,14 @@ module DDQL
|
|
192
192
|
@value_transformer = -> (s) { s.gsub('\\', '') }
|
193
193
|
end
|
194
194
|
|
195
|
+
def as_hash(data)
|
196
|
+
if data&.strip.each_byte.all? { |e| e == 0x30 }
|
197
|
+
Integer.new.as_hash(data.to_i)
|
198
|
+
else
|
199
|
+
super
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
195
203
|
def data_type
|
196
204
|
name
|
197
205
|
end
|
@@ -206,7 +214,6 @@ module DDQL
|
|
206
214
|
end
|
207
215
|
|
208
216
|
def as_hash(data)
|
209
|
-
# {left: {name => data}}
|
210
217
|
{name => data}
|
211
218
|
end
|
212
219
|
|
@@ -229,9 +236,9 @@ module DDQL
|
|
229
236
|
true
|
230
237
|
end
|
231
238
|
|
232
|
-
def parse(parser,
|
239
|
+
def parse(parser, _token, expression: nil)
|
233
240
|
new_expression = parser.parse
|
234
|
-
parser.consume TokenType::RPAREN
|
241
|
+
parser.consume TokenType::RPAREN
|
235
242
|
|
236
243
|
if expression.nil?
|
237
244
|
next_token = parser.peek
|
@@ -280,7 +287,7 @@ module DDQL
|
|
280
287
|
|
281
288
|
def parse(parser, token, expression: nil)
|
282
289
|
operator = Operators.instance.cache[token.op_data]
|
283
|
-
if expression.nil? && !operator
|
290
|
+
if expression.nil? && !operator&.prefix?
|
284
291
|
raise "expected op[#{operator&.name}] to be part of an expression"
|
285
292
|
end
|
286
293
|
operator.parse(parser, token, expression: expression)
|
@@ -291,6 +298,7 @@ module DDQL
|
|
291
298
|
end
|
292
299
|
|
293
300
|
protected
|
301
|
+
|
294
302
|
def op_symbol(data)
|
295
303
|
float_map_ops = Operators.float_map_ops
|
296
304
|
|
@@ -405,6 +413,28 @@ module DDQL
|
|
405
413
|
end
|
406
414
|
end
|
407
415
|
|
416
|
+
class NestedQuery < TokenType
|
417
|
+
def initialize
|
418
|
+
super(name: :nested_opener, pattern: Regexp.compile(Regexp.escape TokenType::NESTED_OPEN_PATTERN))
|
419
|
+
trimming!
|
420
|
+
end
|
421
|
+
|
422
|
+
def as_hash(data)
|
423
|
+
require 'pry'; binding.pry
|
424
|
+
{screen: data.split('#').last.to_i}
|
425
|
+
end
|
426
|
+
|
427
|
+
def expression?
|
428
|
+
true
|
429
|
+
end
|
430
|
+
|
431
|
+
def parse(parser, token, expression: nil)
|
432
|
+
require 'pry'; binding.pry
|
433
|
+
nested_parser = parser.class.from_tokens(parser.until(TokenType::NESTED_CLOSER))
|
434
|
+
nested_parser.parse
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
408
438
|
class SubQuery < TokenType
|
409
439
|
def initialize
|
410
440
|
super(name: :lbrace, pattern: /\{/)
|
@@ -414,15 +444,12 @@ module DDQL
|
|
414
444
|
true
|
415
445
|
end
|
416
446
|
|
417
|
-
def parse(parser,
|
447
|
+
def parse(parser, _token, expression: nil)
|
418
448
|
new_expression = parser.parse
|
419
|
-
if parser.peek&.
|
420
|
-
|
421
|
-
new_expression.merge!(token.parse(parser, expression: new_expression))
|
449
|
+
if parser.peek&.supports_post_processing?
|
450
|
+
_token, new_expression = parser.peek.post_process(parser: parser, expression: new_expression)
|
422
451
|
end
|
423
452
|
|
424
|
-
parser.consume TokenType::RBRACE if parser.peek&.type == TokenType::RBRACE
|
425
|
-
|
426
453
|
if expression.nil?
|
427
454
|
next_token = parser.peek
|
428
455
|
if next_token && (next_token.and? || next_token.or?)
|
@@ -438,9 +465,43 @@ module DDQL
|
|
438
465
|
end
|
439
466
|
end
|
440
467
|
|
468
|
+
class SubQueryAlias < TokenType
|
469
|
+
def initialize
|
470
|
+
super(name: :sub_query_alias, pattern: /AS\s+(?<sub_query_alias>#{FACTOR_PATTERN})/)
|
471
|
+
trimming!
|
472
|
+
end
|
473
|
+
|
474
|
+
def as_hash(data)
|
475
|
+
factor, desc = data.split(':', 2)
|
476
|
+
desc = factor unless desc
|
477
|
+
{name => {factor: factor, desc: desc}}
|
478
|
+
end
|
479
|
+
|
480
|
+
def parse(parser, token, expression: nil)
|
481
|
+
if expression.nil?
|
482
|
+
raise 'expected AS to be part of an ALIAS expression'
|
483
|
+
end
|
484
|
+
if expression && expression.key?(:sub_query_expression)
|
485
|
+
expression[:sub_query] = parser.class.parse(expression[:sub_query_expression])
|
486
|
+
expression.delete :sub_query_expression
|
487
|
+
end
|
488
|
+
expression.merge(as_hash(token.data))
|
489
|
+
end
|
490
|
+
|
491
|
+
def post_process(parser:, expression:)
|
492
|
+
token = parser.consume TokenType::SUB_Q_ALIAS
|
493
|
+
new_expression = expression.merge!(token.parse(parser, expression: expression))
|
494
|
+
[token, new_expression]
|
495
|
+
end
|
496
|
+
|
497
|
+
def supports_post_processing?
|
498
|
+
true
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
441
502
|
class SubQueryExpression < TokenType
|
442
503
|
def initialize
|
443
|
-
super(name: :sub_query_expression, pattern: /expression:\s*(?<sub_query_expression>[^\{\}]{5,})\s*,?\s*/)
|
504
|
+
super(name: :sub_query_expression, pattern: /expression:\s*(?<sub_query_expression>[^\{\}#{Regexp.escape(TokenType::NESTED_OPEN_PATTERN)}#{Regexp.escape(TokenType::NESTED_CLOSE_PATTERN)}]{5,})\s*,?\s*/)
|
444
505
|
end
|
445
506
|
|
446
507
|
def as_hash(data)
|
@@ -448,10 +509,16 @@ module DDQL
|
|
448
509
|
end
|
449
510
|
|
450
511
|
def parse(parser, token, expression: nil)
|
512
|
+
data = token.data.strip
|
513
|
+
if data.start_with? TokenType::NESTED_OPEN_PATTERN
|
514
|
+
sub_data = data[1..(data.index(TokenType::NESTED_CLOSE_PATTERN))]
|
515
|
+
data = parser.class.parse sub_data
|
516
|
+
end
|
517
|
+
|
451
518
|
if expression.nil? || expression.keys != %i[agg sub_query_fields sub_query_type]
|
452
|
-
as_hash(
|
519
|
+
as_hash(data).merge parser.parse
|
453
520
|
else
|
454
|
-
expression.merge(as_hash(
|
521
|
+
expression.merge(as_hash(data))
|
455
522
|
end
|
456
523
|
end
|
457
524
|
end
|
@@ -491,6 +558,16 @@ module DDQL
|
|
491
558
|
end
|
492
559
|
expression.merge(as_hash(token.data))
|
493
560
|
end
|
561
|
+
|
562
|
+
def post_process(parser:, expression:)
|
563
|
+
token = parser.consume TokenType::SUB_Q_GROUP
|
564
|
+
new_expression = expression.merge!(token.parse(parser, expression: expression))
|
565
|
+
[token, new_expression]
|
566
|
+
end
|
567
|
+
|
568
|
+
def supports_post_processing?
|
569
|
+
true
|
570
|
+
end
|
494
571
|
end
|
495
572
|
|
496
573
|
class SubQueryType < TokenType
|
@@ -519,8 +596,19 @@ module DDQL
|
|
519
596
|
def as_hash(_data)
|
520
597
|
Hash.new
|
521
598
|
end
|
599
|
+
|
600
|
+
def post_process(parser:, expression:)
|
601
|
+
token = parser.consume TokenType::RBRACE
|
602
|
+
[token, expression]
|
603
|
+
end
|
604
|
+
|
605
|
+
def supports_post_processing?
|
606
|
+
true
|
607
|
+
end
|
522
608
|
end
|
523
609
|
|
610
|
+
NESTED_OPENER = NestedQuery.new
|
611
|
+
NESTED_CLOSER = new(name: :nested_closer, pattern: Regexp.compile(Regexp.escape TokenType::NESTED_CLOSE_PATTERN))
|
524
612
|
LPAREN = Group.new
|
525
613
|
RPAREN = new(name: :rparen, pattern: /(?=[^%])\)/)
|
526
614
|
LBRACE = SubQuery.new #new(name: :lbrace, pattern: /\{/)
|
@@ -539,7 +627,7 @@ module DDQL
|
|
539
627
|
PREFIXOPERATOR = PrefixOperator.new
|
540
628
|
INFIXOPERATOR = InfixOperator.new
|
541
629
|
POSTFIXOPERATOR = PostfixOperator.new
|
542
|
-
|
630
|
+
SUB_Q_ALIAS = SubQueryAlias.new
|
543
631
|
SUB_Q_EXPR = SubQueryExpression.new
|
544
632
|
SUB_Q_FIELDS = SubQueryFields.new
|
545
633
|
SUB_Q_GROUP = SubQueryGrouping.new
|
@@ -547,15 +635,20 @@ module DDQL
|
|
547
635
|
WHITESPACE = new(name: :whitespace, pattern: /[\s]/).skipping!
|
548
636
|
|
549
637
|
ALL = [
|
638
|
+
NESTED_OPENER,
|
639
|
+
NESTED_CLOSER,
|
550
640
|
LPAREN,
|
551
641
|
RPAREN,
|
552
642
|
# LCAPTURE,
|
553
643
|
# RCAPTURE,
|
644
|
+
# NESTED_OPENER,
|
645
|
+
# NESTED_CLOSER,
|
554
646
|
LBRACE,
|
555
647
|
RBRACE,
|
556
648
|
SUB_Q_EXPR,
|
557
649
|
SUB_Q_FIELDS,
|
558
650
|
SUB_Q_TYPE,
|
651
|
+
SUB_Q_ALIAS,
|
559
652
|
SUB_Q_GROUP,
|
560
653
|
CURRENCY_LITERAL,
|
561
654
|
SCI_NUM_LITERAL,
|
@@ -568,7 +661,6 @@ module DDQL
|
|
568
661
|
PREFIXOPERATOR,
|
569
662
|
INFIXOPERATOR,
|
570
663
|
POSTFIXOPERATOR,
|
571
|
-
# QUERY, # TODO: do we need this?
|
572
664
|
WHITESPACE,
|
573
665
|
]
|
574
666
|
end
|