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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 82f78b8b46dd03069598a3945141114c781eef9da44a0f409d8a8ab3c6bae4b0
4
- data.tar.gz: ec23e584baad9e0359c9471cf3002371863f888cf5cf718ef7cdeb6eb8291686
3
+ metadata.gz: '038be470281a3216e90884f9b14e2f30c087494906a79089d7dff5ea32bdd29a'
4
+ data.tar.gz: 812b0f99b1b1f2c84bddf6ebbe0abfeecdbdbfc47d036c3d65d5a0234b961f8d
5
5
  SHA512:
6
- metadata.gz: 14f90543034d8fdd8813aad9db740efb95f11b9feb9ba17b0f23fd64b603cc7b804cde2fee516c95d11d593b0d57f219edee1d736246d5670e41afbd15c5ae1d
7
- data.tar.gz: 62e76c1c990a1168a4510b56636a6ca9105d1720794e4535a07bd53c5742cd5f5710fcd21ace66ff3e39623d800a07bcf8ee6ac71a87a38650ecf9ce4ac57441
6
+ metadata.gz: c313fd79cb88f08c98af30190e24d52a66e05365c02e29a6c1900d77bef056443f3db18bf3e204b81bdcaa0cefb4e00b778cad159c53c3cee6b9d4ddc775d76b
7
+ data.tar.gz: ced003fd8bcd3fa2b7a9e828fa76d01e101109b9eca210778b9283e226004cc4803a17f15b763ab8659a3d07506add93d28be0078fd11c15e5c04f290dc10345
data/.gitignore CHANGED
@@ -9,3 +9,5 @@
9
9
 
10
10
  # rspec failure tracking
11
11
  .rspec_status
12
+ vendor
13
+ spec/examples.txt
@@ -2,6 +2,8 @@
2
2
  sudo: false
3
3
  language: ruby
4
4
  cache: bundler
5
+ env:
6
+ - JRUBY_OPTS="-J-Xss8m"
5
7
  rvm:
6
8
  - 2.5
7
9
  - 2.6
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
@@ -1,45 +1,57 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ddql (0.1.0)
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.3)
13
- method_source (1.0.0)
14
- parslet (2.0.0)
15
- pry (0.13.1)
16
- coderay (~> 1.1)
17
- method_source (~> 1.0)
18
- pry-byebug (3.9.0)
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.13.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.8.0)
23
- rspec-core (~> 3.8.0)
24
- rspec-expectations (~> 3.8.0)
25
- rspec-mocks (~> 3.8.0)
26
- rspec-core (3.8.0)
27
- rspec-support (~> 3.8.0)
28
- rspec-expectations (3.8.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.8.0)
31
- rspec-mocks (3.8.0)
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.8.0)
34
- rspec-support (3.8.0)
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-byebug (~> 3.9)
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
@@ -1,5 +1,7 @@
1
1
  # DDQL
2
2
 
3
+ [![Build Status](https://travis-ci.com/iss-lab/DDQL.svg?branch=master)](https://travis-ci.com/iss-lab/DDQL)
4
+
3
5
  A (Ruby) DataDesk query language parser written in Parslet.
4
6
 
5
7
  ## Installation
@@ -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
- require 'pry'
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
- # require "irb"
16
- # IRB.start(__FILE__)
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
@@ -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
@@ -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
@@ -0,0 +1,7 @@
1
+ module DDQL
2
+ class InfixStringMapOperator < Operator
3
+ def initialize(symbol, name, ordinal)
4
+ super(symbol, name, :infix, 4, false, :boolean, ordinal)
5
+ end
6
+ end
7
+ end
@@ -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