1
|
|
|
# frozen_string_literal: true |
2
|
|
|
|
3
|
1 |
|
module NoSE |
4
|
|
|
# Communication with backends for index creation and statement execution |
5
|
1 |
|
module Backend |
6
|
|
|
# Superclass of all database backends |
7
|
1 |
|
class BackendBase |
8
|
1 |
|
def initialize(model, indexes, plans, update_plans, _config) |
9
|
23 |
|
@model = model |
10
|
23 |
|
@indexes = indexes |
11
|
23 |
|
@plans = plans |
12
|
23 |
|
@update_plans = update_plans |
13
|
|
|
end |
14
|
|
|
|
15
|
|
|
# By default, do not use ID graphs |
16
|
|
|
# @return [Boolean] |
17
|
1 |
|
def by_id_graph |
18
|
4 |
|
false |
19
|
|
|
end |
20
|
|
|
|
21
|
|
|
# @abstract Subclasses implement to check if an index is empty |
22
|
|
|
# @return [Boolean] |
23
|
1 |
|
def index_empty?(_index) |
24
|
|
|
true |
25
|
|
|
end |
26
|
|
|
|
27
|
|
|
# @abstract Subclasses implement to check if an index already exists |
28
|
|
|
# @return [Boolean] |
29
|
1 |
|
def index_exists?(_index) |
30
|
|
|
false |
31
|
|
|
end |
32
|
|
|
|
33
|
|
|
# @abstract Subclasses implement to remove existing indexes |
34
|
|
|
# @return [void] |
35
|
1 |
|
def drop_index |
36
|
|
|
end |
37
|
|
|
|
38
|
|
|
# @abstract Subclasses implement to allow inserting |
39
|
|
|
# data into the backend database |
40
|
|
|
# :nocov: |
41
|
|
|
# @return [void] |
42
|
1 |
|
def index_insert_chunk(_index, _chunk) |
43
|
|
|
fail NotImplementedError |
44
|
|
|
end |
45
|
|
|
# :nocov: |
46
|
|
|
|
47
|
|
|
# @abstract Subclasses implement to generate a new random ID |
48
|
|
|
# :nocov: |
49
|
|
|
# @return [Object] |
50
|
1 |
|
def generate_id |
51
|
|
|
fail NotImplementedError |
52
|
|
|
end |
53
|
|
|
# :nocov: |
54
|
|
|
|
55
|
|
|
# @abstract Subclasses should create indexes |
56
|
|
|
# :nocov: |
57
|
|
|
# @return [Enumerable] |
58
|
1 |
|
def indexes_ddl(_execute = false, _skip_existing = false, |
59
|
|
|
_drop_existing = false) |
60
|
|
|
fail NotImplementedError |
61
|
|
|
end |
62
|
|
|
# :nocov: |
63
|
|
|
|
64
|
|
|
# @abstract Subclasses should return sample values from the index |
65
|
|
|
# :nocov: |
66
|
|
|
# @return [Array<Hash>] |
67
|
1 |
|
def indexes_sample(_index, _count) |
68
|
|
|
fail NotImplementedError |
69
|
|
|
end |
70
|
|
|
# :nocov: |
71
|
|
|
|
72
|
|
|
# Prepare a query to be executed with the given plans |
73
|
|
|
# @return [PreparedQuery] |
74
|
1 |
|
def prepare_query(query, fields, conditions, plans = []) |
75
|
11 |
|
plan = plans.empty? ? find_query_plan(query) : plans.first |
76
|
|
|
|
77
|
11 |
|
state = Plans::QueryState.new(query, @model) unless query.nil? |
78
|
11 |
|
first_step = Plans::RootPlanStep.new state |
79
|
11 |
|
steps = [first_step] + plan.to_a + [nil] |
80
|
11 |
|
PreparedQuery.new query, prepare_query_steps(steps, fields, conditions) |
81
|
|
|
end |
82
|
|
|
|
83
|
|
|
# Prepare a statement to be executed with the given plans |
84
|
1 |
|
def prepare(statement, plans = []) |
85
|
7 |
|
if statement.is_a? Query |
86
|
1 |
|
prepare_query statement, statement.all_fields, |
87
|
|
|
statement.conditions, plans |
88
|
6 |
|
elsif statement.is_a? Delete |
89
|
3 |
|
prepare_update statement, plans |
90
|
3 |
|
elsif statement.is_a? Disconnect |
91
|
1 |
|
prepare_update statement, plans |
92
|
2 |
|
elsif statement.is_a? Connection |
93
|
1 |
|
prepare_update statement, plans |
94
|
|
|
else |
95
|
1 |
|
prepare_update statement, plans |
96
|
|
|
end |
97
|
|
|
end |
98
|
|
|
|
99
|
|
|
# Execute a query with the stored plans |
100
|
|
|
# @return [Array<Hash>] |
101
|
1 |
|
def query(query, plans = []) |
102
|
|
|
prepared = prepare query, plans |
103
|
|
|
prepared.execute query.conditions |
104
|
|
|
end |
105
|
|
|
|
106
|
|
|
# Prepare an update for execution |
107
|
|
|
# @return [PreparedUpdate] |
108
|
1 |
|
def prepare_update(update, plans) |
109
|
|
|
# Search for plans if they were not given |
110
|
10 |
|
plans = find_update_plans(update) if plans.empty? |
111
|
10 |
|
fail PlanNotFound if plans.empty? |
112
|
|
|
|
113
|
|
|
# Prepare each plan |
114
|
10 |
|
plans.map do |plan| |
115
|
10 |
|
delete = false |
116
|
10 |
|
insert = false |
117
|
10 |
|
plan.update_steps.each do |step| |
118
|
10 |
|
delete = true if step.is_a?(Plans::DeletePlanStep) |
119
|
10 |
|
insert = true if step.is_a?(Plans::InsertPlanStep) |
120
|
|
|
end |
121
|
|
|
|
122
|
10 |
|
steps = [] |
123
|
10 |
|
add_delete_step(plan, steps) if delete |
124
|
10 |
|
add_insert_step(plan, steps, plan.update_fields) if insert |
125
|
|
|
|
126
|
10 |
|
PreparedUpdate.new update, prepare_support_plans(plan), steps |
127
|
|
|
end |
128
|
|
|
end |
129
|
|
|
|
130
|
|
|
# Execute an update with the stored plans |
131
|
|
|
# @return [void] |
132
|
1 |
|
def update(update, plans = []) |
133
|
|
|
prepared = prepare_update update, plans |
134
|
|
|
prepared.each { |p| p.execute update.settings, update.conditions } |
135
|
|
|
end |
136
|
|
|
|
137
|
|
|
# Superclass for all statement execution steps |
138
|
1 |
|
class StatementStep |
139
|
1 |
|
include Supertype |
140
|
1 |
|
attr_reader :index |
141
|
|
|
end |
142
|
|
|
|
143
|
|
|
# Look up data on an index in the backend |
144
|
1 |
|
class IndexLookupStatementStep < StatementStep |
145
|
1 |
|
def initialize(client, _select, _conditions, |
|
|
|
|
146
|
|
|
step, next_step, prev_step) |
147
|
13 |
|
@client = client |
148
|
13 |
|
@step = step |
149
|
13 |
|
@index = step.index |
150
|
13 |
|
@prev_step = prev_step |
151
|
13 |
|
@next_step = next_step |
152
|
|
|
|
153
|
13 |
|
@eq_fields = step.eq_filter |
154
|
13 |
|
@range_field = step.range_filter |
155
|
|
|
end |
156
|
|
|
|
157
|
1 |
|
protected |
158
|
|
|
|
159
|
|
|
# Get lookup values from the query for the first step |
160
|
1 |
|
def initial_results(conditions) |
161
|
|
|
[Hash[conditions.map do |field_id, condition| |
162
|
15 |
|
fail if condition.value.nil? |
163
|
15 |
|
[field_id, condition.value] |
164
|
15 |
|
end]] |
165
|
|
|
end |
166
|
|
|
|
167
|
|
|
# Construct a list of conditions from the results |
168
|
1 |
|
def result_conditions(conditions, results) |
169
|
15 |
|
results.map do |result| |
170
|
15 |
|
result_condition = @eq_fields.map do |field| |
171
|
15 |
|
Condition.new field, :'=', result[field.id] |
172
|
|
|
end |
173
|
|
|
|
174
|
15 |
|
unless @range_field.nil? |
175
|
|
|
operator = conditions.values.find(&:range?).operator |
176
|
|
|
result_condition << Condition.new(@range_field, operator, |
177
|
|
|
result[@range_field.id]) |
178
|
|
|
end |
179
|
|
|
|
180
|
15 |
|
result_condition |
181
|
|
|
end |
182
|
|
|
end |
183
|
|
|
|
184
|
|
|
# Decide which fields should be selected |
185
|
1 |
|
def expand_selected_fields(select) |
186
|
|
|
# We just pick whatever is contained in the index that is either |
187
|
|
|
# mentioned in the query or required for the next lookup |
188
|
|
|
# TODO: Potentially try query.all_fields for those not required |
189
|
|
|
# It should be sufficient to check what is needed for future |
190
|
|
|
# filtering and sorting and use only those + query.select |
191
|
|
|
select += @next_step.index.hash_fields \ |
192
|
|
|
unless @next_step.nil? || |
193
|
2 |
|
!@next_step.is_a?(Plans::IndexLookupPlanStep) |
194
|
2 |
|
select &= @step.index.all_fields |
195
|
|
|
|
196
|
2 |
|
select |
197
|
|
|
end |
198
|
|
|
end |
199
|
|
|
|
200
|
|
|
# Insert data into an index on the backend |
201
|
1 |
|
class InsertStatementStep < StatementStep |
202
|
1 |
|
def initialize(client, index, _fields) |
203
|
8 |
|
@client = client |
204
|
8 |
|
@index = index |
205
|
|
|
end |
206
|
|
|
end |
207
|
|
|
|
208
|
|
|
# Delete data from an index on the backend |
209
|
1 |
|
class DeleteStatementStep < StatementStep |
210
|
1 |
|
def initialize(client, index) |
211
|
5 |
|
@client = client |
212
|
5 |
|
@index = index |
213
|
|
|
end |
214
|
|
|
end |
215
|
|
|
|
216
|
|
|
# Perform filtering external to the backend |
217
|
1 |
|
class FilterStatementStep < StatementStep |
218
|
1 |
|
def initialize(_client, _fields, _conditions, |
|
|
|
|
219
|
|
|
step, _next_step, _prev_step) |
220
|
2 |
|
@step = step |
221
|
|
|
end |
222
|
|
|
|
223
|
|
|
# Filter results by a list of fields given in the step |
224
|
|
|
# @return [Array<Hash>] |
225
|
1 |
|
def process(conditions, results) |
226
|
|
|
# Extract the equality conditions |
227
|
2 |
|
eq_conditions = conditions.values.select do |condition| |
228
|
3 |
|
!condition.range? && @step.eq.include?(condition.field) |
229
|
|
|
end |
230
|
|
|
|
231
|
|
|
# XXX: This assumes that the range filter step is the same as |
232
|
|
|
# the one in the query, which is always true for now |
233
|
2 |
|
range = @step.range && conditions.values.find(&:range?) |
234
|
|
|
|
235
|
6 |
|
results.select! { |row| include_row?(row, eq_conditions, range) } |
236
|
|
|
|
237
|
2 |
|
results |
238
|
|
|
end |
239
|
|
|
|
240
|
1 |
|
private |
241
|
|
|
|
242
|
|
|
# Check if the row should be included in the result |
243
|
|
|
# @return [Boolean] |
244
|
1 |
|
def include_row?(row, eq_conditions, range) |
245
|
4 |
|
select = eq_conditions.all? do |condition| |
246
|
2 |
|
row[condition.field.id] == condition.value |
247
|
|
|
end |
248
|
|
|
|
249
|
4 |
|
if range |
250
|
2 |
|
range_check = row[range.field.id].method(range.operator) |
251
|
2 |
|
select &&= range_check.call range.value |
252
|
|
|
end |
253
|
|
|
|
254
|
4 |
|
select |
255
|
|
|
end |
256
|
|
|
end |
257
|
|
|
|
258
|
|
|
# Perform sorting external to the backend |
259
|
1 |
|
class SortStatementStep < StatementStep |
260
|
1 |
|
def initialize(_client, _fields, _conditions, |
|
|
|
|
261
|
|
|
step, _next_step, _prev_step) |
262
|
1 |
|
@step = step |
263
|
|
|
end |
264
|
|
|
|
265
|
|
|
# Sort results by a list of fields given in the step |
266
|
|
|
# @return [Array<Hash>] |
267
|
1 |
|
def process(_conditions, results) |
268
|
1 |
|
results.sort_by! do |row| |
269
|
2 |
|
@step.sort_fields.map do |field| |
270
|
2 |
|
row[field.id] |
271
|
|
|
end |
272
|
|
|
end |
273
|
|
|
end |
274
|
|
|
end |
275
|
|
|
|
276
|
|
|
# Perform a client-side limit of the result set size |
277
|
1 |
|
class LimitStatementStep < StatementStep |
278
|
1 |
|
def initialize(_client, _fields, _conditions, |
|
|
|
|
279
|
|
|
step, _next_step, _prev_step) |
280
|
1 |
|
@limit = step.limit |
281
|
|
|
end |
282
|
|
|
|
283
|
|
|
# Remove results past the limit |
284
|
|
|
# @return [Array<Hash>] |
285
|
1 |
|
def process(_conditions, results) |
286
|
1 |
|
results[0..@limit - 1] |
287
|
|
|
end |
288
|
|
|
end |
289
|
|
|
|
290
|
1 |
|
private |
291
|
|
|
|
292
|
|
|
# Find plans for a given query |
293
|
|
|
# @return [Plans::QueryPlan] |
294
|
1 |
|
def find_query_plan(query) |
295
|
|
|
plan = @plans.find do |possible_plan| |
296
|
|
|
possible_plan.query == query |
297
|
|
|
end unless query.nil? |
298
|
|
|
fail PlanNotFound if plan.nil? |
299
|
|
|
|
300
|
|
|
plan |
301
|
|
|
end |
302
|
|
|
|
303
|
|
|
# Prepare all the steps for executing a given query |
304
|
|
|
# @return [Array<StatementStep>] |
305
|
1 |
|
def prepare_query_steps(steps, fields, conditions) |
306
|
11 |
|
steps.each_cons(3).map do |prev_step, step, next_step| |
307
|
11 |
|
step_class = StatementStep.subtype_class step.subtype_name |
308
|
|
|
|
309
|
|
|
# Check if the subclass has overridden this step |
310
|
11 |
|
subclass_step_name = step_class.name.sub \ |
311
|
|
|
'NoSE::Backend::BackendBase', self.class.name |
312
|
11 |
|
step_class = Object.const_get subclass_step_name |
313
|
11 |
|
step_class.new client, fields, conditions, |
314
|
|
|
step, next_step, prev_step |
315
|
|
|
end |
316
|
|
|
end |
317
|
|
|
|
318
|
|
|
# Find plans for a given update |
319
|
|
|
# @return [Array<Plans::UpdatePlan>] |
320
|
1 |
|
def find_update_plans(update) |
321
|
|
|
@update_plans.select do |possible_plan| |
322
|
|
|
possible_plan.statement == update |
323
|
|
|
end |
324
|
|
|
end |
325
|
|
|
|
326
|
|
|
# Add a delete step to a prepared update plan |
327
|
|
|
# @return [void] |
328
|
1 |
View Code Duplication |
def add_delete_step(plan, steps) |
|
|
|
|
329
|
4 |
|
step_class = DeleteStatementStep |
330
|
4 |
|
subclass_step_name = step_class.name.sub \ |
331
|
|
|
'NoSE::Backend::BackendBase', self.class.name |
332
|
4 |
|
step_class = Object.const_get subclass_step_name |
333
|
4 |
|
steps << step_class.new(client, plan.index) |
334
|
|
|
end |
335
|
|
|
|
336
|
|
|
# Add an insert step to a prepared update plan |
337
|
|
|
# @return [void] |
338
|
1 |
View Code Duplication |
def add_insert_step(plan, steps, fields) |
|
|
|
|
339
|
6 |
|
step_class = InsertStatementStep |
340
|
6 |
|
subclass_step_name = step_class.name.sub \ |
341
|
|
|
'NoSE::Backend::BackendBase', self.class.name |
342
|
6 |
|
step_class = Object.const_get subclass_step_name |
343
|
6 |
|
steps << step_class.new(client, plan.index, fields) |
344
|
|
|
end |
345
|
|
|
|
346
|
|
|
# Prepare plans for each support query |
347
|
|
|
# @return [Array<PreparedQuery>] |
348
|
1 |
|
def prepare_support_plans(plan) |
349
|
10 |
|
plan.query_plans.map do |query_plan| |
350
|
8 |
|
query = query_plan.instance_variable_get(:@query) |
351
|
8 |
|
prepare_query query, query_plan.select_fields, query_plan.params, |
352
|
|
|
[query_plan.steps] |
353
|
|
|
end |
354
|
|
|
end |
355
|
|
|
end |
356
|
|
|
|
357
|
|
|
# A prepared query which can be executed against the backend |
358
|
1 |
|
class PreparedQuery |
359
|
1 |
|
attr_reader :query, :steps |
360
|
|
|
|
361
|
1 |
|
def initialize(query, steps) |
362
|
11 |
|
@query = query |
363
|
11 |
|
@steps = steps |
364
|
|
|
end |
365
|
|
|
|
366
|
|
|
# Execute the query for the given set of conditions |
367
|
|
|
# @return [Array<Hash>] |
368
|
1 |
|
def execute(conditions) |
369
|
13 |
|
results = nil |
370
|
|
|
|
371
|
13 |
|
@steps.each do |step| |
372
|
13 |
|
if step.is_a?(BackendBase::IndexLookupStatementStep) |
373
|
13 |
|
field_ids = step.index.all_fields.map(&:id) |
374
|
28 |
|
field_conds = conditions.select { |key| field_ids.include? key } |
375
|
|
|
else |
376
|
|
|
field_conds = conditions |
377
|
|
|
end |
378
|
13 |
|
results = step.process field_conds, results |
379
|
|
|
|
380
|
|
|
# The query can't return any results at this point, so we're done |
381
|
13 |
|
break if results.empty? |
382
|
|
|
end |
383
|
|
|
|
384
|
|
|
# Only return fields selected by the query if one is given |
385
|
|
|
# (we have no query to refer to for manually-defined plans) |
386
|
13 |
|
unless @query.nil? |
387
|
11 |
|
select_ids = @query.select.map(&:id).to_set |
388
|
40 |
|
results.map { |row| row.select! { |k, _| select_ids.include? k } } |
389
|
|
|
end |
390
|
|
|
|
391
|
13 |
|
results |
392
|
|
|
end |
393
|
|
|
end |
394
|
|
|
|
395
|
|
|
# An update prepared with a backend which is ready to execute |
396
|
1 |
|
class PreparedUpdate |
397
|
1 |
|
attr_reader :statement, :steps |
398
|
|
|
|
399
|
1 |
|
def initialize(statement, support_plans, steps) |
400
|
10 |
|
@statement = statement |
401
|
10 |
|
@support_plans = support_plans |
402
|
10 |
|
@delete_step = steps.find do |step| |
403
|
10 |
|
step.is_a? BackendBase::DeleteStatementStep |
404
|
|
|
end |
405
|
10 |
|
@insert_step = steps.find do |step| |
406
|
10 |
|
step.is_a? BackendBase::InsertStatementStep |
407
|
|
|
end |
408
|
|
|
end |
409
|
|
|
|
410
|
|
|
# Execute the statement for the given set of conditions |
411
|
|
|
# @return [void] |
412
|
1 |
|
def execute(update_settings, update_conditions) |
|
|
|
|
413
|
|
|
# Execute all the support queries |
414
|
10 |
|
settings = initial_update_settings update_settings, update_conditions |
415
|
|
|
|
416
|
|
|
# Execute the support queries for this update |
417
|
10 |
|
support = support_results update_conditions |
418
|
|
|
|
419
|
|
|
# Perform the deletion |
420
|
10 |
|
@delete_step.process support unless support.empty? || @delete_step.nil? |
421
|
10 |
|
return if @insert_step.nil? |
422
|
|
|
|
423
|
|
|
# Get the fields which should be used from the original statement |
424
|
|
|
# If we didn't delete old entries, then we just need the primary key |
425
|
|
|
# attributes of the index, otherwise we need everything |
426
|
6 |
|
index = @insert_step.index |
427
|
6 |
|
include_fields = if @delete_step.nil? |
428
|
6 |
|
index.hash_fields + index.order_fields |
429
|
|
|
else |
430
|
|
|
index.all_fields |
431
|
|
|
end |
432
|
|
|
|
433
|
|
|
# Add fields from the original statement |
434
|
6 |
|
update_conditions.each_value do |condition| |
435
|
5 |
|
next unless include_fields.include? condition.field |
436
|
5 |
|
settings.merge! condition.field.id => condition.value |
437
|
|
|
end |
438
|
|
|
|
439
|
6 |
|
if support.empty? |
440
|
5 |
|
support = [settings] |
441
|
|
|
else |
442
|
1 |
|
support.each do |row| |
443
|
3 |
|
row.merge!(settings) { |_, value, _| value } |
444
|
|
|
end |
445
|
|
|
end |
446
|
|
|
|
447
|
|
|
# Stop if we have nothing to insert, otherwise insert |
448
|
6 |
|
return if support.empty? |
449
|
6 |
|
@insert_step.process support |
450
|
|
|
end |
451
|
|
|
|
452
|
1 |
|
private |
453
|
|
|
|
454
|
|
|
# Get the initial values which will be used in the first plan step |
455
|
|
|
# @return [Hash] |
456
|
1 |
|
def initial_update_settings(update_settings, update_conditions) |
457
|
10 |
|
if !@insert_step.nil? && @delete_step.nil? |
458
|
|
|
# Populate the data to insert for Insert statements |
459
|
6 |
|
settings = Hash[update_settings.map do |setting| |
460
|
11 |
|
[setting.field.id, setting.value] |
461
|
|
|
end] |
462
|
|
|
else |
463
|
|
|
# Get values for updates and deletes |
464
|
4 |
|
settings = Hash[update_conditions.map do |field_id, condition| |
465
|
5 |
|
[field_id, condition.value] |
466
|
|
|
end] |
467
|
|
|
end |
468
|
|
|
|
469
|
10 |
|
settings |
470
|
|
|
end |
471
|
|
|
|
472
|
|
|
# Execute all the support queries |
473
|
|
|
# @return [Array<Hash>] |
474
|
1 |
|
def support_results(settings) |
|
|
|
|
475
|
10 |
|
return [] if @support_plans.empty? |
476
|
|
|
|
477
|
|
|
# Get a hash of values used in settings, first |
478
|
|
|
# resolving any settings which specify foreign keys |
479
|
5 |
|
settings = Hash[settings.map do |k, v| |
|
|
|
|
480
|
7 |
|
new_condition = v.resolve_foreign_key |
481
|
7 |
|
[new_condition.field.id, new_condition] |
482
|
|
|
end] |
483
|
12 |
|
setting_values = Hash[settings.map { |k, v| [k, v.value] }] |
484
|
|
|
|
485
|
|
|
# If we have no query for IDs on the first entity, we must |
486
|
|
|
# have the fields we need to execute the other support queries |
487
|
|
|
if [email protected]? && |
488
|
5 |
|
@support_plans.first.query.entity != @statement.entity |
489
|
2 |
|
support = @support_plans.map do |plan| |
490
|
2 |
|
plan.execute settings |
491
|
|
|
end |
492
|
|
|
|
493
|
|
|
# Combine the results from multiple support queries |
494
|
2 |
|
unless support.empty? |
495
|
2 |
|
support = support.first.product(*support[1..-1]) |
496
|
2 |
|
support.map! do |results| |
497
|
2 |
|
results.reduce(&:merge!).merge!(setting_values) |
498
|
|
|
end |
499
|
|
|
end |
500
|
|
|
else |
501
|
|
|
# Execute the first support query to get a list of IDs |
502
|
3 |
|
first_query = @support_plans.first.query |
503
|
|
|
|
504
|
|
|
# We may not have a statement if this is manually defined |
505
|
3 |
|
if @statement.nil? |
506
|
|
|
select_key = false |
507
|
|
|
entity_fields = nil |
508
|
|
|
else |
509
|
3 |
|
id = @statement.entity.id_field |
510
|
3 |
|
select_key = first_query.select.include? id |
511
|
|
|
|
512
|
|
|
# Select any fields from the entity being modified if required |
513
|
|
|
entity_fields = @support_plans.first.execute settings \ |
514
|
|
|
if first_query.graph.size == 1 && \ |
515
|
3 |
|
first_query.graph.entities.first == @statement.entity |
516
|
|
|
end |
517
|
|
|
|
518
|
3 |
|
if select_key |
519
|
|
|
# Pull the IDs from the first support query |
520
|
1 |
|
conditions = entity_fields.map do |row| |
521
|
1 |
|
{ id.id => Condition.new(id, :'=', row[id.id]) } |
522
|
|
|
end |
523
|
|
|
else |
524
|
|
|
# Use the ID specified in the statement conditions |
525
|
2 |
|
conditions = [settings] |
526
|
|
|
end |
527
|
|
|
|
528
|
|
|
# Execute the support queries for each ID |
529
|
3 |
|
support = conditions.each_with_index.flat_map do |condition, i| |
530
|
3 |
|
results = @support_plans[(select_key ? 1 : 0)..-1].map do |plan| |
531
|
5 |
|
plan.execute condition |
532
|
|
|
end |
533
|
|
|
|
534
|
|
|
# Combine the results of the different support queries |
535
|
3 |
|
results[0].product(*results[1..-1]).map do |result| |
536
|
2 |
|
row = result.reduce(&:merge!) |
537
|
2 |
|
row.merge!(entity_fields[i]) unless entity_fields.nil? |
538
|
2 |
|
row.merge!(setting_values) |
539
|
|
|
|
540
|
2 |
|
row |
541
|
|
|
end |
542
|
|
|
end |
543
|
|
|
end |
544
|
|
|
|
545
|
5 |
|
support |
546
|
|
|
end |
547
|
|
|
end |
548
|
|
|
|
549
|
|
|
# Raised when a statement is executed that we have no plan for |
550
|
1 |
|
class PlanNotFound < StandardError |
551
|
|
|
end |
552
|
|
|
|
553
|
|
|
# Raised when a backend attempts to create an index that already exists |
554
|
1 |
|
class IndexAlreadyExists < StandardError |
555
|
|
|
end |
556
|
|
|
end |
557
|
|
|
end |
558
|
|
|
|