ddql 1.0.0 → 1.0.1

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: '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