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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a257ea91453ff55380b71ef1a98e0a4b32d9688ee87b9f6d74e5141d58e73424
|
4
|
+
data.tar.gz: e6eae8733865abfb13efc7d2e252cb0a1c7b3ee1afbe2a7445248a1adef66c98
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d539c78fd753b66f5303d5d1941b75e52e8a3a77c3c506823c73aa15b8a79804d23e5e54472c28888038d5073e3a4f0dc493b934b0f6dba6e9b660077eab5474
|
7
|
+
data.tar.gz: b94754405933f0607ca4412b02219df659c2068b52cac3f4d2c2db18fc7b5d246d84131bc0af665fd8b81eb2ac74c022ef0474bc5b1f9cd717e9bdda5d64d41c
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
ruby-2.
|
1
|
+
ruby-2.7.2
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
ddql (1.0.
|
4
|
+
ddql (1.0.1)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
@@ -9,36 +9,36 @@ GEM
|
|
9
9
|
byebug (11.1.3)
|
10
10
|
coderay (1.1.3)
|
11
11
|
diff-lcs (1.4.4)
|
12
|
-
ffi (1.
|
12
|
+
ffi (1.15.3-java)
|
13
13
|
method_source (0.9.2)
|
14
|
-
pry (0.
|
14
|
+
pry (0.12.2)
|
15
15
|
coderay (~> 1.1.0)
|
16
16
|
method_source (~> 0.9.0)
|
17
|
-
pry (0.
|
17
|
+
pry (0.12.2-java)
|
18
18
|
coderay (~> 1.1.0)
|
19
19
|
method_source (~> 0.9.0)
|
20
20
|
spoon (~> 0.0)
|
21
21
|
pry-byebug (3.8.0)
|
22
22
|
byebug (~> 11.0)
|
23
23
|
pry (~> 0.10)
|
24
|
-
pry-debugger-jruby (1.2.
|
25
|
-
pry (>= 0.10, < 0.
|
26
|
-
ruby-debug-base (
|
27
|
-
rake (13.0.
|
28
|
-
rspec (3.
|
29
|
-
rspec-core (~> 3.
|
30
|
-
rspec-expectations (~> 3.
|
31
|
-
rspec-mocks (~> 3.
|
32
|
-
rspec-core (3.
|
33
|
-
rspec-support (~> 3.
|
34
|
-
rspec-expectations (3.
|
24
|
+
pry-debugger-jruby (1.2.2-java)
|
25
|
+
pry (>= 0.10, < 0.13)
|
26
|
+
ruby-debug-base (>= 0.10.4, < 0.12)
|
27
|
+
rake (13.0.3)
|
28
|
+
rspec (3.10.0)
|
29
|
+
rspec-core (~> 3.10.0)
|
30
|
+
rspec-expectations (~> 3.10.0)
|
31
|
+
rspec-mocks (~> 3.10.0)
|
32
|
+
rspec-core (3.10.1)
|
33
|
+
rspec-support (~> 3.10.0)
|
34
|
+
rspec-expectations (3.10.1)
|
35
35
|
diff-lcs (>= 1.2.0, < 2.0)
|
36
|
-
rspec-support (~> 3.
|
37
|
-
rspec-mocks (3.
|
36
|
+
rspec-support (~> 3.10.0)
|
37
|
+
rspec-mocks (3.10.2)
|
38
38
|
diff-lcs (>= 1.2.0, < 2.0)
|
39
|
-
rspec-support (~> 3.
|
40
|
-
rspec-support (3.
|
41
|
-
ruby-debug-base (0.
|
39
|
+
rspec-support (~> 3.10.0)
|
40
|
+
rspec-support (3.10.2)
|
41
|
+
ruby-debug-base (0.11.0-java)
|
42
42
|
spoon (0.0.6)
|
43
43
|
ffi
|
44
44
|
|
data/lib/ddql.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
require 'ddql/version'
|
2
|
+
require_relative 'ddql/blank_refinements'
|
3
|
+
require_relative 'ddql/string_refinements'
|
2
4
|
require_relative 'ddql/linked_list'
|
3
5
|
require_relative 'ddql/query_expression_error'
|
4
6
|
require_relative 'ddql/lexer'
|
5
7
|
require_relative 'ddql/operators'
|
6
8
|
require_relative 'ddql/parser'
|
7
|
-
require_relative 'ddql/string_refinements'
|
8
9
|
require_relative 'ddql/token_type'
|
9
10
|
require_relative 'ddql/token'
|
10
11
|
|
data/lib/ddql/agg_operator.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
module DDQL
|
2
|
+
using StringRefinements
|
3
|
+
|
2
4
|
# +AggOperators+ return a Hash structure describing the type of aggregation, an optional
|
3
5
|
# expression (filter) to apply, an optional factor (field) against which the aggregation
|
4
6
|
# is applied, and the entity type for which the aggregation is applicable.
|
@@ -13,7 +15,7 @@ module DDQL
|
|
13
15
|
# sub_query_type: 'IssuerPerson',
|
14
16
|
# }
|
15
17
|
#
|
16
|
-
# +CNT{type:Issuer,
|
18
|
+
# +CNT{type:Issuer, fields: [SomeFactor]} / CNT{type:Issuer, fields: [OtherFactor]}+
|
17
19
|
#
|
18
20
|
# {
|
19
21
|
# left: {
|
@@ -29,7 +31,7 @@ module DDQL
|
|
29
31
|
# },
|
30
32
|
# }
|
31
33
|
#
|
32
|
-
# +MAX{type:Case,
|
34
|
+
# +MAX{type:Case, fields: [SomeFactor]} > 5+
|
33
35
|
#
|
34
36
|
# {
|
35
37
|
# left: {
|
@@ -40,7 +42,18 @@ module DDQL
|
|
40
42
|
# op: {op_gt: '>'},
|
41
43
|
# right: {int: 5},
|
42
44
|
# }
|
45
|
+
#
|
46
|
+
# +ALIAS{type:Issuer, expression: [SomeFactor] > 5} AS [ThatFactor:That Factor]+
|
47
|
+
#
|
48
|
+
# {
|
49
|
+
# agg: {op_max: 'ALIAS'},
|
50
|
+
# sub_query_type: 'Issuer',
|
51
|
+
# sub_query_expression: '[SomeFactor] > 5',
|
52
|
+
# sub_query_alias: {factor: 'ThatFactor', desc: 'That Factor'},
|
53
|
+
# }
|
43
54
|
class AggOperator < Operator
|
55
|
+
using BlankRefinements
|
56
|
+
|
44
57
|
def initialize(symbol, name, return_type:, type: :prefix, precedence: 8, right: false)
|
45
58
|
super(symbol, name, type, precedence, right, return_type)
|
46
59
|
end
|
@@ -52,24 +65,69 @@ module DDQL
|
|
52
65
|
else
|
53
66
|
expression = new_expression
|
54
67
|
end
|
68
|
+
|
55
69
|
expression.merge!(agg: {:"op_#{symbol.downcase}" => symbol})
|
70
|
+
if parser.peek&.supports_post_processing?
|
71
|
+
token, expression = parser.peek.post_process(parser: parser, expression: expression)
|
72
|
+
end
|
73
|
+
as_agg(expression)
|
74
|
+
end
|
75
|
+
|
76
|
+
def as_left_op_right(expression)
|
77
|
+
new_expression = {
|
78
|
+
agg: expression.delete(:agg),
|
79
|
+
sub_query_type: expression.delete(:sub_query_type),
|
80
|
+
sub_query_fields: expression.delete(:sub_query_fields),
|
81
|
+
sub_query_expression: expression.delete(:sub_query_expression),
|
82
|
+
}.compact
|
56
83
|
|
57
|
-
if expression.key?(:op) && expression.
|
58
|
-
|
59
|
-
|
84
|
+
if expression[:lstatement].key?(:op) && expression[:lstatement][:left].blank?
|
85
|
+
{
|
86
|
+
left: new_expression,
|
87
|
+
op: expression[:lstatement][:op],
|
88
|
+
right: expression[:lstatement][:right],
|
89
|
+
}
|
90
|
+
elsif expression[:lstatement].key?(:lstatement)
|
91
|
+
if expression[:lstatement][:lstatement].empty?
|
92
|
+
expression[:lstatement][:lstatement] = new_expression
|
93
|
+
expression[:lstatement]
|
94
|
+
else
|
95
|
+
final_expression = expression[:lstatement]
|
96
|
+
final_expression[:lstatement][:left] = new_expression
|
97
|
+
final_expression
|
60
98
|
end
|
61
|
-
|
62
|
-
|
99
|
+
else
|
100
|
+
new_expression
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def as_agg(expr)
|
105
|
+
expression = expr.dup
|
106
|
+
if expression.key?(:yes_no_op)
|
107
|
+
# AGG Y/N
|
108
|
+
yes_no_op = expression.delete(:yes_no_op)
|
109
|
+
{
|
110
|
+
left: expression,
|
111
|
+
yes_no_op: yes_no_op,
|
112
|
+
}
|
113
|
+
elsif expression.key?(:op)
|
114
|
+
# AGG COMP LIT
|
115
|
+
expression.delete(:left)
|
116
|
+
op = expression.delete(:op)
|
117
|
+
right = expression.delete(:right)
|
63
118
|
{
|
64
119
|
left: expression,
|
65
120
|
op: op,
|
66
121
|
right: right,
|
67
122
|
}
|
68
|
-
elsif expression.key?(:
|
69
|
-
|
123
|
+
elsif expression.key?(:boolean_operator)
|
124
|
+
# AGG BOOL STMT
|
125
|
+
bool_op = expression.delete(:boolean_operator)
|
126
|
+
rstatement = expression.delete(:rstatement)
|
70
127
|
{
|
71
|
-
|
72
|
-
|
128
|
+
lstatement: as_left_op_right(expression),
|
129
|
+
boolean_operator: bool_op,
|
130
|
+
rstatement: rstatement,
|
73
131
|
}
|
74
132
|
else
|
75
133
|
expression
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module DDQL
|
2
|
+
module BlankRefinements
|
3
|
+
refine Enumerable do
|
4
|
+
def blank?
|
5
|
+
empty? || compact.empty?
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
refine Hash do
|
10
|
+
def blank?
|
11
|
+
empty? || compact.empty?
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
refine NilClass do
|
16
|
+
def blank?
|
17
|
+
true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -13,13 +13,12 @@ module DDQL
|
|
13
13
|
end
|
14
14
|
|
15
15
|
left_factor, right_factor = expression[:string].split('|', 2)
|
16
|
+
|
16
17
|
{
|
17
|
-
op_coalesce:
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
},
|
22
|
-
}
|
18
|
+
op_coalesce: [
|
19
|
+
{factor: left_factor},
|
20
|
+
{factor: right_factor},
|
21
|
+
]
|
23
22
|
}
|
24
23
|
end
|
25
24
|
end
|
@@ -2,8 +2,8 @@ module DDQL
|
|
2
2
|
class InfixFloatMapOperator < Operator
|
3
3
|
attr_reader :comparison, :op_type, :op_symbol
|
4
4
|
|
5
|
-
def initialize(symbol, name,
|
6
|
-
super(symbol, name, :infix, 4, false, :boolean
|
5
|
+
def initialize(symbol, name, op_type, comparison)
|
6
|
+
super(symbol, name, :infix, 4, false, :boolean)
|
7
7
|
@op_type = op_type
|
8
8
|
@comparison = comparison
|
9
9
|
@op_symbol = :"op_float_map_#{op_type}_#{comparison}"
|
data/lib/ddql/linked_list.rb
CHANGED
@@ -2,13 +2,16 @@ module DDQL
|
|
2
2
|
class LinkedList
|
3
3
|
include Enumerable
|
4
4
|
|
5
|
+
class NavigationError < StandardError
|
6
|
+
end
|
7
|
+
|
5
8
|
class Node
|
6
9
|
attr_accessor :next, :previous
|
7
10
|
attr_reader :value
|
8
11
|
|
9
|
-
def initialize(value)
|
10
|
-
@next =
|
11
|
-
@previous =
|
12
|
+
def initialize(value, next_node: nil, previous_node: nil)
|
13
|
+
@next = next_node
|
14
|
+
@previous = previous_node
|
12
15
|
@value = value
|
13
16
|
end
|
14
17
|
|
@@ -22,61 +25,110 @@ module DDQL
|
|
22
25
|
def initialize(head=nil)
|
23
26
|
@doubly_linked = false
|
24
27
|
@head = head
|
25
|
-
@size = 0
|
28
|
+
@size = head.nil? ? 0 : 1
|
29
|
+
@tail = @head
|
26
30
|
end
|
27
31
|
|
28
32
|
def append(value)
|
29
33
|
if @head
|
30
34
|
tail = find_tail
|
31
|
-
|
32
|
-
|
35
|
+
if Node === value
|
36
|
+
value.previous = tail if @doubly_linked
|
37
|
+
tail.next = value
|
38
|
+
else
|
39
|
+
tail.next = Node.new(value)
|
40
|
+
tail.next.previous = tail if @doubly_linked
|
41
|
+
end
|
42
|
+
@tail = tail.next
|
33
43
|
else
|
34
|
-
@head = Node.new(value)
|
44
|
+
@head = @tail = Node === value ? value : Node.new(value)
|
35
45
|
end
|
36
46
|
@size += 1
|
37
47
|
end
|
38
48
|
alias :<< :append
|
39
49
|
|
50
|
+
# Insert the +value+ after the +target+
|
51
|
+
#
|
52
|
+
# @return [Node|NilClass] nil if +target+ is not found, otherwise the inserted node for +value+
|
40
53
|
def append_after(target, value)
|
41
|
-
node
|
54
|
+
node = find(target)
|
42
55
|
return unless node
|
43
|
-
old_next
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
56
|
+
old_next = node.next
|
57
|
+
if @doubly_linked
|
58
|
+
if Node === value
|
59
|
+
value.previous = node
|
60
|
+
value.next = old_next
|
61
|
+
node.next = value
|
62
|
+
else
|
63
|
+
node.next = Node.new(value, previous_node: node, next_node: old_next)
|
64
|
+
end
|
65
|
+
elsif Node === value
|
66
|
+
value.next = old_next
|
67
|
+
node.next = value
|
68
|
+
else
|
69
|
+
node.next = Node.new(value, next_node: old_next)
|
53
70
|
end
|
54
|
-
@
|
55
|
-
|
71
|
+
@tail = node.next if old_next.nil?
|
72
|
+
node.next
|
56
73
|
end
|
74
|
+
alias :insert :append_after
|
57
75
|
|
58
|
-
def
|
59
|
-
|
76
|
+
def at(index)
|
77
|
+
return nil if index < 0 || index >= size
|
78
|
+
return @head if index == 0
|
79
|
+
return @tail if index == size - 1
|
80
|
+
|
81
|
+
current_index = 0
|
82
|
+
current_node = head
|
83
|
+
while current_node = current_node.next
|
84
|
+
current_index += 1
|
85
|
+
return current_node if current_index == index
|
86
|
+
end
|
60
87
|
end
|
88
|
+
alias :[] :at
|
61
89
|
|
62
90
|
def delete(value)
|
63
91
|
if value.is_a?(Node) && @head == value
|
64
92
|
@head = @head.next
|
65
93
|
value.next = value.previous = nil
|
94
|
+
@tail = @head if @head.nil? || @head.next.nil?
|
66
95
|
elsif @head.value == value
|
67
96
|
node = @head.next
|
68
97
|
@head.next = @head.previous = nil
|
69
98
|
value = @head
|
70
99
|
@head = node
|
100
|
+
@tail = @head if @head.next.nil?
|
71
101
|
else
|
72
102
|
node = find_before(value)
|
103
|
+
|
104
|
+
if node && node.next == tail
|
105
|
+
@tail = node
|
106
|
+
node.next = nil
|
107
|
+
end
|
108
|
+
|
73
109
|
node.next = node.next.next if node && node.next
|
74
|
-
node.next
|
110
|
+
node.next.previous = node if doubly_linked! && node.next
|
75
111
|
end
|
76
112
|
@size -= 1
|
77
113
|
value
|
78
114
|
end
|
79
115
|
|
116
|
+
def doubly_linked!
|
117
|
+
return self if doubly_linked?
|
118
|
+
|
119
|
+
current = nil
|
120
|
+
each_node do |node|
|
121
|
+
node.previous = current
|
122
|
+
current = node
|
123
|
+
end
|
124
|
+
@doubly_linked = true
|
125
|
+
self
|
126
|
+
end
|
127
|
+
|
128
|
+
def doubly_linked?
|
129
|
+
@doubly_linked
|
130
|
+
end
|
131
|
+
|
80
132
|
def each
|
81
133
|
return to_enum unless block_given?
|
82
134
|
node = @head
|
@@ -86,13 +138,28 @@ module DDQL
|
|
86
138
|
end
|
87
139
|
end
|
88
140
|
|
89
|
-
def
|
141
|
+
def empty?
|
142
|
+
@size == 0
|
143
|
+
end
|
144
|
+
|
145
|
+
def find(value=nil, &blk)
|
90
146
|
node = @head
|
91
147
|
is_node = value.is_a?(Node)
|
92
|
-
return false if !node.next
|
93
|
-
return node if (is_node && node == value) || node.value == value
|
148
|
+
return false if !node.next && (!blk || (blk && !yield(node)))
|
149
|
+
return node if (is_node && node == value) || node.value == value || (blk && yield(node))
|
94
150
|
while (node = node.next)
|
95
|
-
return node if (is_node && node == value) || node.value == value
|
151
|
+
return node if (is_node && node == value) || node.value == value || (blk && yield(node))
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def find_from_tail(value=nil, &blk)
|
156
|
+
raise NavigationError, "singly-linked lists don't support finding nodes from the tail" unless doubly_linked?
|
157
|
+
node = @tail
|
158
|
+
is_node = value.is_a?(Node)
|
159
|
+
return false if !node.previous && (!blk || (blk && !yield(node)))
|
160
|
+
return node if (is_node && node == value) || node.value == value || (blk && yield(node))
|
161
|
+
while (node = node.previous)
|
162
|
+
return node if (is_node && node == value) || node.value == value || (blk && yield(node))
|
96
163
|
end
|
97
164
|
end
|
98
165
|
|
@@ -113,9 +180,12 @@ module DDQL
|
|
113
180
|
end
|
114
181
|
|
115
182
|
def find_tail
|
116
|
-
|
117
|
-
|
118
|
-
|
183
|
+
if @tail.nil?
|
184
|
+
node = @head
|
185
|
+
return @tail = node if !node.next
|
186
|
+
return @tail = node if !node.next while (node = node.next)
|
187
|
+
end
|
188
|
+
@tail
|
119
189
|
end
|
120
190
|
alias :tail :find_tail
|
121
191
|
|
@@ -150,6 +220,40 @@ module DDQL
|
|
150
220
|
end
|
151
221
|
end
|
152
222
|
|
223
|
+
# Replace a range of nodes from +from+ to +to+, inclusive,
|
224
|
+
# with the given +with+. +from+, +to+, and +with+ can all
|
225
|
+
# be values or nodes.
|
226
|
+
#
|
227
|
+
# @return self
|
228
|
+
def replace!(from:, to:, with:)
|
229
|
+
first_node = find(from)
|
230
|
+
last_node = find(to)
|
231
|
+
tail = find_tail
|
232
|
+
raise 'cannot find appropriate range for replacement' if first_node.nil? || last_node.nil?
|
233
|
+
|
234
|
+
replacement = Node === with ? with : Node.new(with)
|
235
|
+
if first_node == head
|
236
|
+
@head = replacement
|
237
|
+
unless last_node == tail
|
238
|
+
new_tail = last_node.next
|
239
|
+
replacement.next = new_tail
|
240
|
+
new_tail.previous = replacement if doubly_linked?
|
241
|
+
end
|
242
|
+
return self
|
243
|
+
end
|
244
|
+
|
245
|
+
if doubly_linked?
|
246
|
+
replacement.previous = first_node.previous
|
247
|
+
replacement.previous.next = replacement
|
248
|
+
last_node.next.previous = replacement
|
249
|
+
elsif first_node != head
|
250
|
+
previous = find_before(first_node)
|
251
|
+
previous.next = replacement
|
252
|
+
end
|
253
|
+
replacement.next = last_node.next
|
254
|
+
self
|
255
|
+
end
|
256
|
+
|
153
257
|
def singly_linked!
|
154
258
|
each_node do |node|
|
155
259
|
node.previous = nil
|