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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '038be470281a3216e90884f9b14e2f30c087494906a79089d7dff5ea32bdd29a'
4
- data.tar.gz: 812b0f99b1b1f2c84bddf6ebbe0abfeecdbdbfc47d036c3d65d5a0234b961f8d
3
+ metadata.gz: a257ea91453ff55380b71ef1a98e0a4b32d9688ee87b9f6d74e5141d58e73424
4
+ data.tar.gz: e6eae8733865abfb13efc7d2e252cb0a1c7b3ee1afbe2a7445248a1adef66c98
5
5
  SHA512:
6
- metadata.gz: c313fd79cb88f08c98af30190e24d52a66e05365c02e29a6c1900d77bef056443f3db18bf3e204b81bdcaa0cefb4e00b778cad159c53c3cee6b9d4ddc775d76b
7
- data.tar.gz: ced003fd8bcd3fa2b7a9e828fa76d01e101109b9eca210778b9283e226004cc4803a17f15b763ab8659a3d07506add93d28be0078fd11c15e5c04f290dc10345
6
+ metadata.gz: d539c78fd753b66f5303d5d1941b75e52e8a3a77c3c506823c73aa15b8a79804d23e5e54472c28888038d5073e3a4f0dc493b934b0f6dba6e9b660077eab5474
7
+ data.tar.gz: b94754405933f0607ca4412b02219df659c2068b52cac3f4d2c2db18fc7b5d246d84131bc0af665fd8b81eb2ac74c022ef0474bc5b1f9cd717e9bdda5d64d41c
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- ruby-2.6.3
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.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.13.1-java)
12
+ ffi (1.15.3-java)
13
13
  method_source (0.9.2)
14
- pry (0.11.3)
14
+ pry (0.12.2)
15
15
  coderay (~> 1.1.0)
16
16
  method_source (~> 0.9.0)
17
- pry (0.11.3-java)
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.1-java)
25
- pry (>= 0.10, < 0.12)
26
- ruby-debug-base (~> 0.10.4)
27
- rake (13.0.1)
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)
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.9.0)
37
- rspec-mocks (3.9.1)
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.9.0)
40
- rspec-support (3.9.3)
41
- ruby-debug-base (0.10.6-java)
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
 
@@ -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, expression: [SomeFactor]} / CNT{type:Issuer, expression: [OtherFactor]}+
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, expression: [SomeFactor]} > 5+
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.key?(:right)
58
- if expression[:left]&.empty?
59
- expression.delete :left
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
- op = expression.delete :op
62
- right = expression.delete :right
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?(:yes_no_op)
69
- yes_no = expression.delete :yes_no_op
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
- left: expression,
72
- yes_no_op: yes_no,
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
- factors: {
19
- left_factor: {factor: left_factor},
20
- right_factor: {factor: right_factor},
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, ordinal, op_type, comparison)
6
- super(symbol, name, :infix, 4, false, :boolean, ordinal)
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}"
@@ -1,7 +1,7 @@
1
1
  module DDQL
2
2
  class InfixStringMapOperator < Operator
3
- def initialize(symbol, name, ordinal)
4
- super(symbol, name, :infix, 4, false, :boolean, ordinal)
3
+ def initialize(symbol, name)
4
+ super(symbol, name, :infix, 4, false, :boolean)
5
5
  end
6
6
  end
7
7
  end
@@ -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 = nil
11
- @previous = nil
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
- tail.next = Node.new(value)
32
- tail.next.previous = tail if @doubly_linked
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 = find(target)
54
+ node = find(target)
42
55
  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
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
- @doubly_linked = true
55
- self
71
+ @tail = node.next if old_next.nil?
72
+ node.next
56
73
  end
74
+ alias :insert :append_after
57
75
 
58
- def doubly_linked?
59
- @doubly_linked
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 = node.previous = nil if node
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 find(value)
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
- node = @head
117
- return node if !node.next
118
- return node if !node.next while (node = node.next)
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