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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '038be470281a3216e90884f9b14e2f30c087494906a79089d7dff5ea32bdd29a'
|
4
|
+
data.tar.gz: 812b0f99b1b1f2c84bddf6ebbe0abfeecdbdbfc47d036c3d65d5a0234b961f8d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c313fd79cb88f08c98af30190e24d52a66e05365c02e29a6c1900d77bef056443f3db18bf3e204b81bdcaa0cefb4e00b778cad159c53c3cee6b9d4ddc775d76b
|
7
|
+
data.tar.gz: ced003fd8bcd3fa2b7a9e828fa76d01e101109b9eca210778b9283e226004cc4803a17f15b763ab8659a3d07506add93d28be0078fd11c15e5c04f290dc10345
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
@@ -2,3 +2,17 @@ source "https://rubygems.org"
|
|
2
2
|
|
3
3
|
# Specify your gem's dependencies in ddql.gemspec
|
4
4
|
gemspec
|
5
|
+
|
6
|
+
group :development do
|
7
|
+
platform :mri do
|
8
|
+
gem 'pry-byebug', '= 3.8.0'
|
9
|
+
# gem 'pry', '~> 0.11'
|
10
|
+
end
|
11
|
+
|
12
|
+
platform :jruby do
|
13
|
+
gem 'pry-debugger-jruby', '~> 1.2'
|
14
|
+
# gem 'pry', '0.11.3-java'
|
15
|
+
end
|
16
|
+
|
17
|
+
gem 'pry', '~> 0.10'
|
18
|
+
end
|
data/Gemfile.lock
CHANGED
@@ -1,45 +1,57 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
ddql (
|
5
|
-
parslet (~> 2.0)
|
4
|
+
ddql (1.0.0)
|
6
5
|
|
7
6
|
GEM
|
8
7
|
remote: https://rubygems.org/
|
9
8
|
specs:
|
10
9
|
byebug (11.1.3)
|
11
10
|
coderay (1.1.3)
|
12
|
-
diff-lcs (1.
|
13
|
-
|
14
|
-
|
15
|
-
pry (0.
|
16
|
-
coderay (~> 1.1)
|
17
|
-
method_source (~>
|
18
|
-
pry
|
11
|
+
diff-lcs (1.4.4)
|
12
|
+
ffi (1.13.1-java)
|
13
|
+
method_source (0.9.2)
|
14
|
+
pry (0.11.3)
|
15
|
+
coderay (~> 1.1.0)
|
16
|
+
method_source (~> 0.9.0)
|
17
|
+
pry (0.11.3-java)
|
18
|
+
coderay (~> 1.1.0)
|
19
|
+
method_source (~> 0.9.0)
|
20
|
+
spoon (~> 0.0)
|
21
|
+
pry-byebug (3.8.0)
|
19
22
|
byebug (~> 11.0)
|
20
|
-
pry (~> 0.
|
23
|
+
pry (~> 0.10)
|
24
|
+
pry-debugger-jruby (1.2.1-java)
|
25
|
+
pry (>= 0.10, < 0.12)
|
26
|
+
ruby-debug-base (~> 0.10.4)
|
21
27
|
rake (13.0.1)
|
22
|
-
rspec (3.
|
23
|
-
rspec-core (~> 3.
|
24
|
-
rspec-expectations (~> 3.
|
25
|
-
rspec-mocks (~> 3.
|
26
|
-
rspec-core (3.
|
27
|
-
rspec-support (~> 3.
|
28
|
-
rspec-expectations (3.
|
28
|
+
rspec (3.9.0)
|
29
|
+
rspec-core (~> 3.9.0)
|
30
|
+
rspec-expectations (~> 3.9.0)
|
31
|
+
rspec-mocks (~> 3.9.0)
|
32
|
+
rspec-core (3.9.2)
|
33
|
+
rspec-support (~> 3.9.3)
|
34
|
+
rspec-expectations (3.9.2)
|
29
35
|
diff-lcs (>= 1.2.0, < 2.0)
|
30
|
-
rspec-support (~> 3.
|
31
|
-
rspec-mocks (3.
|
36
|
+
rspec-support (~> 3.9.0)
|
37
|
+
rspec-mocks (3.9.1)
|
32
38
|
diff-lcs (>= 1.2.0, < 2.0)
|
33
|
-
rspec-support (~> 3.
|
34
|
-
rspec-support (3.
|
39
|
+
rspec-support (~> 3.9.0)
|
40
|
+
rspec-support (3.9.3)
|
41
|
+
ruby-debug-base (0.10.6-java)
|
42
|
+
spoon (0.0.6)
|
43
|
+
ffi
|
35
44
|
|
36
45
|
PLATFORMS
|
46
|
+
java
|
37
47
|
ruby
|
38
48
|
|
39
49
|
DEPENDENCIES
|
40
50
|
bundler (~> 2.0)
|
41
51
|
ddql!
|
42
|
-
pry
|
52
|
+
pry (~> 0.10)
|
53
|
+
pry-byebug (= 3.8.0)
|
54
|
+
pry-debugger-jruby (~> 1.2)
|
43
55
|
rake (~> 13.0)
|
44
56
|
rspec (~> 3.0)
|
45
57
|
|
data/README.md
CHANGED
data/bin/console
CHANGED
@@ -7,10 +7,15 @@ require 'ddql'
|
|
7
7
|
# with your gem easier. You can also use a different console, if you like.
|
8
8
|
|
9
9
|
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
-
|
10
|
+
|
11
11
|
PARSER = DDQL::Parser.new
|
12
|
-
puts 'Create a new instance of the parser or use the PARSER constant'
|
13
|
-
Pry.start
|
14
12
|
|
15
|
-
|
16
|
-
|
13
|
+
begin
|
14
|
+
require 'pry'
|
15
|
+
puts 'Create a new instance of the parser or use the PARSER constant'
|
16
|
+
Pry.start
|
17
|
+
rescue
|
18
|
+
require "irb"
|
19
|
+
puts 'Create a new instance of the parser or use the PARSER constant'
|
20
|
+
IRB.start(__FILE__)
|
21
|
+
end
|
data/ddql.gemspec
CHANGED
@@ -27,10 +27,7 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
28
|
spec.require_paths = ["lib"]
|
29
29
|
|
30
|
-
spec.add_dependency "parslet", "~> 2.0"
|
31
|
-
|
32
30
|
spec.add_development_dependency "bundler", "~> 2.0"
|
33
31
|
spec.add_development_dependency "rake", "~> 13.0"
|
34
32
|
spec.add_development_dependency "rspec", "~> 3.0"
|
35
|
-
spec.add_development_dependency "pry-byebug", "~> 3.9"
|
36
33
|
end
|
data/lib/ddql.rb
CHANGED
@@ -1,6 +1,13 @@
|
|
1
|
-
require 'parslet'
|
2
|
-
require 'ddql/parser'
|
3
1
|
require 'ddql/version'
|
2
|
+
require_relative 'ddql/linked_list'
|
3
|
+
require_relative 'ddql/query_expression_error'
|
4
|
+
require_relative 'ddql/lexer'
|
5
|
+
require_relative 'ddql/operators'
|
6
|
+
require_relative 'ddql/parser'
|
7
|
+
require_relative 'ddql/string_refinements'
|
8
|
+
require_relative 'ddql/token_type'
|
9
|
+
require_relative 'ddql/token'
|
10
|
+
|
4
11
|
|
5
12
|
module DDQL
|
6
13
|
class ParseError < StandardError; end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module DDQL
|
2
|
+
# +AggOperators+ return a Hash structure describing the type of aggregation, an optional
|
3
|
+
# expression (filter) to apply, an optional factor (field) against which the aggregation
|
4
|
+
# is applied, and the entity type for which the aggregation is applicable.
|
5
|
+
#
|
6
|
+
# ==== Examples
|
7
|
+
# +SUM {type: IssuerPerson, fields: [SomeFactor], expression: [OtherFactor] == 'Value'}+
|
8
|
+
#
|
9
|
+
# {
|
10
|
+
# agg: {op_sum: 'SUM'},
|
11
|
+
# sub_query_expression: "[OtherFactor] == 'Value'",
|
12
|
+
# sub_query_fields: 'SomeFactor',
|
13
|
+
# sub_query_type: 'IssuerPerson',
|
14
|
+
# }
|
15
|
+
#
|
16
|
+
# +CNT{type:Issuer, expression: [SomeFactor]} / CNT{type:Issuer, expression: [OtherFactor]}+
|
17
|
+
#
|
18
|
+
# {
|
19
|
+
# left: {
|
20
|
+
# agg: {op_cnt: 'CNT'},
|
21
|
+
# sub_query_fields: 'SomeFactor',
|
22
|
+
# sub_query_type: 'Issuer',
|
23
|
+
# },
|
24
|
+
# op: {op_divide: '/'},
|
25
|
+
# right: {
|
26
|
+
# agg: {op_cnt: 'CNT'},
|
27
|
+
# sub_query_fields: 'OtherFactor',
|
28
|
+
# sub_query_type: 'Issuer',
|
29
|
+
# },
|
30
|
+
# }
|
31
|
+
#
|
32
|
+
# +MAX{type:Case, expression: [SomeFactor]} > 5+
|
33
|
+
#
|
34
|
+
# {
|
35
|
+
# left: {
|
36
|
+
# agg: {op_max: 'MAX'},
|
37
|
+
# sub_query_fields: 'SomeFactor',
|
38
|
+
# sub_query_type: 'Case',
|
39
|
+
# },
|
40
|
+
# op: {op_gt: '>'},
|
41
|
+
# right: {int: 5},
|
42
|
+
# }
|
43
|
+
class AggOperator < Operator
|
44
|
+
def initialize(symbol, name, return_type:, type: :prefix, precedence: 8, right: false)
|
45
|
+
super(symbol, name, type, precedence, right, return_type)
|
46
|
+
end
|
47
|
+
|
48
|
+
def parse(parser, token, expression: nil)
|
49
|
+
new_expression = parser.parse(precedence: precedence)
|
50
|
+
if expression
|
51
|
+
expression = expression.merge(new_expression)
|
52
|
+
else
|
53
|
+
expression = new_expression
|
54
|
+
end
|
55
|
+
expression.merge!(agg: {:"op_#{symbol.downcase}" => symbol})
|
56
|
+
|
57
|
+
if expression.key?(:op) && expression.key?(:right)
|
58
|
+
if expression[:left]&.empty?
|
59
|
+
expression.delete :left
|
60
|
+
end
|
61
|
+
op = expression.delete :op
|
62
|
+
right = expression.delete :right
|
63
|
+
{
|
64
|
+
left: expression,
|
65
|
+
op: op,
|
66
|
+
right: right,
|
67
|
+
}
|
68
|
+
elsif expression.key?(:yes_no_op)
|
69
|
+
yes_no = expression.delete :yes_no_op
|
70
|
+
{
|
71
|
+
left: expression,
|
72
|
+
yes_no_op: yes_no,
|
73
|
+
}
|
74
|
+
else
|
75
|
+
expression
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module DDQL
|
2
|
+
class CoalesceOperator < AggOperator
|
3
|
+
def initialize
|
4
|
+
super("COALESCE", "Coalesce", return_type: :match)
|
5
|
+
end
|
6
|
+
|
7
|
+
def parse(parser, token, expression: nil)
|
8
|
+
new_expression = parser.parse(precedence: precedence)
|
9
|
+
if expression
|
10
|
+
expression = expression.merge(new_expression)
|
11
|
+
else
|
12
|
+
expression = new_expression
|
13
|
+
end
|
14
|
+
|
15
|
+
left_factor, right_factor = expression[:string].split('|', 2)
|
16
|
+
{
|
17
|
+
op_coalesce: {
|
18
|
+
factors: {
|
19
|
+
left_factor: {factor: left_factor},
|
20
|
+
right_factor: {factor: right_factor},
|
21
|
+
},
|
22
|
+
}
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module DDQL
|
2
|
+
class InfixFloatMapOperator < Operator
|
3
|
+
attr_reader :comparison, :op_type, :op_symbol
|
4
|
+
|
5
|
+
def initialize(symbol, name, ordinal, op_type, comparison)
|
6
|
+
super(symbol, name, :infix, 4, false, :boolean, ordinal)
|
7
|
+
@op_type = op_type
|
8
|
+
@comparison = comparison
|
9
|
+
@op_symbol = :"op_float_map_#{op_type}_#{comparison}"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/lib/ddql/lexer.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative 'linked_list'
|
2
|
+
|
3
|
+
module DDQL
|
4
|
+
class Lexer
|
5
|
+
def self.lex(expression, pattern: TokenType.all_types_pattern, available_types: TokenType::ALL)
|
6
|
+
tokens = LinkedList.new.doubly_linked!
|
7
|
+
md = pattern.match expression
|
8
|
+
while md
|
9
|
+
token_type = available_types.detect { |tt| tt.match?(match_data: md) }
|
10
|
+
if token_type
|
11
|
+
tokens << Token.new(
|
12
|
+
data: token_type.interpreted_data_from(match_data: md),
|
13
|
+
location: expression.length - md.string.length,
|
14
|
+
type: token_type,
|
15
|
+
)
|
16
|
+
end
|
17
|
+
md = pattern.match md.post_match
|
18
|
+
end
|
19
|
+
tokens
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
module DDQL
|
2
|
+
class LinkedList
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
class Node
|
6
|
+
attr_accessor :next, :previous
|
7
|
+
attr_reader :value
|
8
|
+
|
9
|
+
def initialize(value)
|
10
|
+
@next = nil
|
11
|
+
@previous = nil
|
12
|
+
@value = value
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
"Node[#{@value}]"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :head, :size
|
21
|
+
|
22
|
+
def initialize(head=nil)
|
23
|
+
@doubly_linked = false
|
24
|
+
@head = head
|
25
|
+
@size = 0
|
26
|
+
end
|
27
|
+
|
28
|
+
def append(value)
|
29
|
+
if @head
|
30
|
+
tail = find_tail
|
31
|
+
tail.next = Node.new(value)
|
32
|
+
tail.next.previous = tail if @doubly_linked
|
33
|
+
else
|
34
|
+
@head = Node.new(value)
|
35
|
+
end
|
36
|
+
@size += 1
|
37
|
+
end
|
38
|
+
alias :<< :append
|
39
|
+
|
40
|
+
def append_after(target, value)
|
41
|
+
node = find(target)
|
42
|
+
return unless node
|
43
|
+
old_next = node.next
|
44
|
+
node.next = Node.new(value)
|
45
|
+
node.next.next = old_next
|
46
|
+
end
|
47
|
+
|
48
|
+
def doubly_linked!
|
49
|
+
current = nil
|
50
|
+
each_node do |node|
|
51
|
+
node.previous = current
|
52
|
+
current = node
|
53
|
+
end
|
54
|
+
@doubly_linked = true
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
def doubly_linked?
|
59
|
+
@doubly_linked
|
60
|
+
end
|
61
|
+
|
62
|
+
def delete(value)
|
63
|
+
if value.is_a?(Node) && @head == value
|
64
|
+
@head = @head.next
|
65
|
+
value.next = value.previous = nil
|
66
|
+
elsif @head.value == value
|
67
|
+
node = @head.next
|
68
|
+
@head.next = @head.previous = nil
|
69
|
+
value = @head
|
70
|
+
@head = node
|
71
|
+
else
|
72
|
+
node = find_before(value)
|
73
|
+
node.next = node.next.next if node && node.next
|
74
|
+
node.next = node.previous = nil if node
|
75
|
+
end
|
76
|
+
@size -= 1
|
77
|
+
value
|
78
|
+
end
|
79
|
+
|
80
|
+
def each
|
81
|
+
return to_enum unless block_given?
|
82
|
+
node = @head
|
83
|
+
while node
|
84
|
+
yield node.value
|
85
|
+
node = node.next
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def find(value)
|
90
|
+
node = @head
|
91
|
+
is_node = value.is_a?(Node)
|
92
|
+
return false if !node.next
|
93
|
+
return node if (is_node && node == value) || node.value == value
|
94
|
+
while (node = node.next)
|
95
|
+
return node if (is_node && node == value) || node.value == value
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def find_before(value)
|
100
|
+
node = @head
|
101
|
+
return false if !node.next
|
102
|
+
|
103
|
+
is_node = value.is_a?(Node)
|
104
|
+
if (is_node && node.next == value) || node.next.value == value
|
105
|
+
return node
|
106
|
+
end
|
107
|
+
|
108
|
+
while (node = node.next)
|
109
|
+
if (is_node && node.next == value) || node.next.value == value
|
110
|
+
return node
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def find_tail
|
116
|
+
node = @head
|
117
|
+
return node if !node.next
|
118
|
+
return node if !node.next while (node = node.next)
|
119
|
+
end
|
120
|
+
alias :tail :find_tail
|
121
|
+
|
122
|
+
def peek
|
123
|
+
return nil unless @head
|
124
|
+
@head.value
|
125
|
+
end
|
126
|
+
|
127
|
+
def poll
|
128
|
+
return nil unless @head
|
129
|
+
previous_head = @head
|
130
|
+
@head = previous_head.next
|
131
|
+
@size -= 1
|
132
|
+
@head.previous = nil if @head
|
133
|
+
previous_head.next = nil
|
134
|
+
previous_head.value
|
135
|
+
end
|
136
|
+
|
137
|
+
def print(stream=STDOUT)
|
138
|
+
node = @head
|
139
|
+
prefix = ''
|
140
|
+
postfix = ''
|
141
|
+
stream.print "(size: #{size}) "
|
142
|
+
while node
|
143
|
+
postfix = "\n⨂" unless node.next
|
144
|
+
stream.print prefix
|
145
|
+
stream.print node
|
146
|
+
stream.print postfix
|
147
|
+
stream.print "\n"
|
148
|
+
node = node.next
|
149
|
+
prefix = ' ⤿ ' if node
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def singly_linked!
|
154
|
+
each_node do |node|
|
155
|
+
node.previous = nil
|
156
|
+
end
|
157
|
+
@doubly_linked = false
|
158
|
+
self
|
159
|
+
end
|
160
|
+
|
161
|
+
private
|
162
|
+
def each_node
|
163
|
+
raise 'requires a block' unless block_given?
|
164
|
+
node = @head
|
165
|
+
while node
|
166
|
+
yield node
|
167
|
+
node = node.next
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|