ddql 0.1.0 → 1.0.0
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/.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
|