1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
|
module Groonga
# TODO: Move me
class ExpressionCode
module Flags
RELATIONAL_EXPRESSION = 0x01
end
end
class ScanInfoBuilder
module Status
START = 0
VAR = 1
COL1 = 2
COL2 = 3
CONST = 4
end
def initialize(expression, operator, size)
@data_list = []
@expression = expression
@operator = operator
@size = size
end
RELATION_OPERATORS = [
Operator::MATCH,
Operator::NEAR,
Operator::NEAR2,
Operator::SIMILAR,
Operator::PREFIX,
Operator::SUFFIX,
Operator::EQUAL,
Operator::NOT_EQUAL,
Operator::LESS,
Operator::GREATER,
Operator::LESS_EQUAL,
Operator::GREATER_EQUAL,
Operator::GEO_WITHINP5,
Operator::GEO_WITHINP6,
Operator::GEO_WITHINP8,
Operator::TERM_EXTRACT,
]
LOGICAL_OPERATORS = [
Operator::AND,
Operator::OR,
Operator::AND_NOT,
Operator::ADJUST,
]
def build
return nil unless valid?
status = Status::START
variable = @expression.get_var_by_offset(0)
data = nil
codes = @expression.codes
n_codes = codes.size
codes.each_with_index do |code, i|
case code.op
when *RELATION_OPERATORS
status = Status::START
data.op = code.op
data.end = i
data.match_resolve_index
@data_list << data
data = nil
when *LOGICAL_OPERATORS
put_logical_op(code.op, i)
# TODO: rescue and return nil
status = Status::START
when Operator::PUSH
data ||= ScanInfoData.new(i)
if code.value == variable
status = Status::VAR
else
data.args << code.value
if status == Status::START
data.flags |= ScanInfo::Flags::PRE_CONST
end
status = Status::CONST
end
when Operator::GET_VALUE
case status
when Status::START
data ||= ScanInfoData.new(i)
status = Status::COL1
data.args << code.value
when Status::CONST, Status::VAR
status = Status::COL1
data.args << code.value
when Status::COL1
raise "invalid expression: can't use column as a value: <#{code.value.name}>: <#{@expression.grn_inspect}>"
status = Status::COL2
when Status::COL2
# Do nothing
end
when Operator::CALL
data ||= ScanInfoData.new(i)
if (code.flags & ExpressionCode::Flags::RELATIONAL_EXPRESSION) != 0 or
(i + 1) == n_codes
status = Status::START
data.op = code.op
data.end = i
data.call_relational_resolve_indexes
@data_list << data
data = nil
else
status = Status::COL2
end
end
end
if @operator == Operator::OR and @size == 0
first_data = @data_list.first
if (first_data.flags & ScanInfo::Flags::PUSH) == 0 or
first_data.logical_op != @operator
raise "invalid expr"
else
first_data.flags &= ~ScanInfo::Flags::PUSH
first_data.logical_op = @operator
end
else
put_logical_op(@operator, n_codes)
end
optimize
end
private
def valid?
n_relation_expressions = 0
n_logical_expressions = 0
status = Status::START
variable = @expression.get_var_by_offset(0)
codes = @expression.codes
codes.each do |code|
case code.op
when *RELATION_OPERATORS
return false if status < Status::COL1
return false if status > Status::CONST
status = Status::START
n_relation_expressions += 1
when *LOGICAL_OPERATORS
return false if status != Status::START
n_logical_expressions += 1
return false if n_logical_expressions >= n_relation_expressions
when Operator::PUSH
if code.value == variable
status = Status::VAR
else
status = Status::CONST
end
when Operator::GET_VALUE
case status
when Status::START, Status::CONST, Status::VAR
status = Status::COL1
when Status::COL1
status = Status::COL2
when Status::COL2
# Do nothing
else
return false
end
when Operator::CALL
if (code.flags & ExpressionCode::Flags::RELATIONAL_EXPRESSION) != 0 or
code == codes.last
status = Status::START
n_relation_expressions += 1
else
status = Status::COL2
end
else
return false
end
end
return false if status != Status::START
return false if n_relation_expressions != (n_logical_expressions + 1)
true
end
def put_logical_op(operator, start)
n_parens = 1
n_dif_ops = 0
r = 0
j = @data_list.size
while j > 0
j -= 1
data = @data_list[j]
if (data.flags & ScanInfo::Flags::POP) != 0
n_dif_ops += 1
n_parens += 1
else
if (data.flags & ScanInfo::Flags::PUSH) != 0
n_parens -= 1
if n_parens == 0
if r == 0
if n_dif_ops > 0
if j > 0 and operator != Operator::AND_NOT
n_parens = 1
n_dif_ops = 0
r = j
else
new_data = ScanInfoData.new(start)
new_data.flags = ScanInfo::Flags::POP
new_data.logical_op = operator
@data_list << new_data
end
else
data.flags &= ~ScanInfo::Flags::PUSH
data.logical_op = operator
end
else
if n_dif_ops > 0
new_data = ScanInfoData.new(start)
new_data.flags = ScanInfo::Flags::POP
new_data.logical_op = operator
@data_list << new_data
else
data.flags &= ~ScanInfo::Flags::PUSH
data.logical_op = operator
@data_list =
@data_list[0...j] +
@data_list[r..-1] +
@data_list[j...r]
end
end
end
else
if operator == Operator::AND_NOT or operator != data.logical_op
n_dif_ops += 1
end
end
end
if j < 0
raise GRN_INVALID_ARGUMENT.new("unmatched nesting level")
end
end
end
def optimize
optimized_data_list = []
i = 0
n = @data_list.size
while i < n
data = @data_list[i]
next_data = @data_list[i + 1]
i += 1
if next_data.nil?
optimized_data_list << data
next
end
if range_operations?(data, next_data)
between_data = create_between_data(data, next_data)
optimized_data_list << between_data
i += 1
next
end
optimized_data_list << data
end
optimized_data_list
end
def range_operations?(data, next_data)
return false unless next_data.logical_op == Operator::AND
op, next_op = data.op, next_data.op
return false if !(lower_condition?(op) or lower_condition?(next_op))
return false if !(upper_condition?(op) or upper_condition?(next_op))
return false if data.args[0] != next_data.args[0]
data_indexes = data.indexes
return false if data_indexes.empty?
data_indexes == next_data.indexes
end
def lower_condition?(operator)
case operator
when Operator::GREATER, Operator::GREATER_EQUAL
true
else
false
end
end
def upper_condition?(operator)
case operator
when Operator::LESS, Operator::LESS_EQUAL
true
else
false
end
end
def create_between_data(data, next_data)
between_data = ScanInfoData.new(data.start)
between_data.end = next_data.end + 1
between_data.flags = data.flags
between_data.op = Operator::CALL
between_data.logical_op = data.logical_op
between_data.args = create_between_data_args(data, next_data)
between_data.indexes = data.indexes
between_data
end
def create_between_data_args(data, next_data)
between = Context.instance["between"]
@expression.take_object(between)
column = data.args[0]
op, next_op = data.op, next_data.op
if lower_condition?(op)
min = data.args[1]
min_operator = op
max = next_data.args[1]
max_operator = next_op
else
min = next_data.args[1]
min_operator = next_op
max = data.args[1]
max_operator = op
end
if min_operator == Operator::GREATER
min_border = "exclude"
else
min_border = "include"
end
if max_operator == Operator::LESS
max_border = "exclude"
else
max_border = "include"
end
[
between,
column,
min,
@expression.allocate_constant(min_border),
max,
@expression.allocate_constant(max_border),
]
end
end
end
|