1
|
|
|
<?php |
2
|
|
|
declare(strict_types=1); |
3
|
|
|
|
4
|
|
|
namespace Level23\Druid\Queries; |
5
|
|
|
|
6
|
|
|
use InvalidArgumentException; |
7
|
|
|
use Level23\Druid\DruidClient; |
8
|
|
|
use Level23\Druid\Types\DataType; |
9
|
|
|
use Level23\Druid\Concerns\HasLimit; |
10
|
|
|
use Level23\Druid\Types\Granularity; |
11
|
|
|
use Level23\Druid\Concerns\HasFilter; |
12
|
|
|
use Level23\Druid\Concerns\HasHaving; |
13
|
|
|
use Level23\Druid\Types\SortingOrder; |
14
|
|
|
use Level23\Druid\Dimensions\Dimension; |
15
|
|
|
use Level23\Druid\Context\QueryContext; |
16
|
|
|
use Level23\Druid\Concerns\HasIntervals; |
17
|
|
|
use Level23\Druid\Limits\LimitInterface; |
18
|
|
|
use Level23\Druid\Concerns\HasDimensions; |
19
|
|
|
use Level23\Druid\Types\OrderByDirection; |
20
|
|
|
use Level23\Druid\Responses\QueryResponse; |
21
|
|
|
use Level23\Druid\Concerns\HasAggregations; |
22
|
|
|
use Level23\Druid\Context\TopNQueryContext; |
23
|
|
|
use Level23\Druid\Context\ScanQueryContext; |
24
|
|
|
use Level23\Druid\Concerns\HasSearchFilters; |
25
|
|
|
use Level23\Druid\Concerns\HasVirtualColumns; |
26
|
|
|
use Level23\Druid\Types\ScanQueryResultFormat; |
27
|
|
|
use Level23\Druid\Responses\ScanQueryResponse; |
28
|
|
|
use Level23\Druid\Responses\TopNQueryResponse; |
29
|
|
|
use Level23\Druid\VirtualColumns\VirtualColumn; |
30
|
|
|
use Level23\Druid\Concerns\HasPostAggregations; |
31
|
|
|
use Level23\Druid\Context\GroupByV1QueryContext; |
32
|
|
|
use Level23\Druid\Context\GroupByV2QueryContext; |
33
|
|
|
use Level23\Druid\Responses\SelectQueryResponse; |
34
|
|
|
use Level23\Druid\Responses\SearchQueryResponse; |
35
|
|
|
use Level23\Druid\Extractions\ExtractionBuilder; |
36
|
|
|
use Level23\Druid\Collections\IntervalCollection; |
37
|
|
|
use Level23\Druid\Context\TimeSeriesQueryContext; |
38
|
|
|
use Level23\Druid\Responses\GroupByQueryResponse; |
39
|
|
|
use Level23\Druid\Collections\DimensionCollection; |
40
|
|
|
use Level23\Druid\Collections\AggregationCollection; |
41
|
|
|
use Level23\Druid\Responses\TimeSeriesQueryResponse; |
42
|
|
|
use Level23\Druid\Collections\VirtualColumnCollection; |
43
|
|
|
use Level23\Druid\Collections\PostAggregationCollection; |
44
|
|
|
use Level23\Druid\Responses\SegmentMetadataQueryResponse; |
45
|
|
|
|
46
|
|
|
class QueryBuilder |
47
|
|
|
{ |
48
|
|
|
use HasFilter, HasHaving, HasDimensions, HasAggregations, HasIntervals, HasLimit, HasVirtualColumns, HasPostAggregations, HasSearchFilters; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* @var \Level23\Druid\DruidClient |
52
|
|
|
*/ |
53
|
|
|
protected $client; |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* @var string |
57
|
|
|
*/ |
58
|
|
|
protected $dataSource; |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* @var string |
62
|
|
|
*/ |
63
|
|
|
protected $granularity; |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* @var array|\Level23\Druid\PostAggregations\PostAggregatorInterface[] |
67
|
|
|
*/ |
68
|
|
|
protected $postAggregations = []; |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* Set a paging identifier for a Select query. |
72
|
|
|
* |
73
|
|
|
* @var array|null |
74
|
|
|
*/ |
75
|
|
|
protected $pagingIdentifier; |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* The subtotal spec (only applies for groupBy queries) |
79
|
|
|
* |
80
|
|
|
* @var array |
81
|
|
|
*/ |
82
|
|
|
protected $subtotals = []; |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* The metrics to select when using a Select Query. |
86
|
|
|
* When empty, all metrics are returned. |
87
|
|
|
* |
88
|
|
|
* @var array |
89
|
|
|
*/ |
90
|
|
|
protected $metrics = []; |
91
|
|
|
|
92
|
|
|
/** |
93
|
|
|
* This contains a list of "temporary" field names which we will use to store our result of |
94
|
|
|
* a virtual column when the whereFlag() method is used. |
95
|
|
|
* |
96
|
|
|
* @var array |
97
|
|
|
*/ |
98
|
|
|
protected $placeholders = []; |
99
|
|
|
|
100
|
|
|
/** |
101
|
|
|
* QueryBuilder constructor. |
102
|
|
|
* |
103
|
|
|
* @param \Level23\Druid\DruidClient $client |
104
|
|
|
* @param string $dataSource |
105
|
|
|
* @param string $granularity |
106
|
|
|
*/ |
107
|
271 |
|
public function __construct(DruidClient $client, string $dataSource, string $granularity = Granularity::ALL) |
108
|
|
|
{ |
109
|
271 |
|
$this->client = $client; |
110
|
271 |
|
$this->dataSource = $dataSource; |
111
|
271 |
|
$this->granularity = Granularity::validate($granularity); |
112
|
270 |
|
} |
113
|
|
|
|
114
|
|
|
/** |
115
|
|
|
* Create a virtual column and select the result. |
116
|
|
|
* |
117
|
|
|
* Virtual columns are queryable column "views" created from a set of columns during a query. |
118
|
|
|
* |
119
|
|
|
* A virtual column can potentially draw from multiple underlying columns, although a virtual column always |
120
|
|
|
* presents itself as a single column. |
121
|
|
|
* |
122
|
|
|
* @param string $expression |
123
|
|
|
* @param string $as |
124
|
|
|
* @param string $outputType |
125
|
|
|
* |
126
|
|
|
* @return $this |
127
|
|
|
* @see https://druid.apache.org/docs/latest/misc/math-expr.html |
128
|
|
|
*/ |
129
|
1 |
|
public function selectVirtual(string $expression, string $as, $outputType = DataType::STRING) |
130
|
|
|
{ |
131
|
1 |
|
$this->virtualColumns[] = new VirtualColumn($expression, $as, $outputType); |
132
|
|
|
|
133
|
1 |
|
$this->select($as); |
134
|
|
|
|
135
|
1 |
|
return $this; |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
/** |
139
|
|
|
* Execute a druid query. We will try to detect the best possible query type possible. |
140
|
|
|
* |
141
|
|
|
* @param array|QueryContext $context |
142
|
|
|
* |
143
|
|
|
* @return QueryResponse |
144
|
|
|
* @throws \Level23\Druid\Exceptions\QueryResponseException |
145
|
|
|
*/ |
146
|
1 |
|
public function execute($context = []): QueryResponse |
147
|
|
|
{ |
148
|
1 |
|
$query = $this->buildQuery($context); |
149
|
|
|
|
150
|
1 |
|
$rawResponse = $this->client->executeQuery($query); |
151
|
|
|
|
152
|
1 |
|
return $query->parseResponse($rawResponse); |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
/** |
156
|
|
|
* Update/set the dataSource |
157
|
|
|
* |
158
|
|
|
* @param string $dataSource |
159
|
|
|
* |
160
|
|
|
* @return $this |
161
|
|
|
*/ |
162
|
28 |
|
public function dataSource(string $dataSource): QueryBuilder |
163
|
|
|
{ |
164
|
28 |
|
$this->dataSource = $dataSource; |
165
|
|
|
|
166
|
28 |
|
return $this; |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
/** |
170
|
|
|
* Update/set the granularity |
171
|
|
|
* |
172
|
|
|
* @param string $granularity |
173
|
|
|
* |
174
|
|
|
* @return $this |
175
|
|
|
*/ |
176
|
17 |
|
public function granularity(string $granularity): QueryBuilder |
177
|
|
|
{ |
178
|
17 |
|
$this->granularity = Granularity::validate($granularity); |
179
|
|
|
|
180
|
17 |
|
return $this; |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
/** |
184
|
|
|
* Filter on records which match using a bitwise AND comparison. |
185
|
|
|
* |
186
|
|
|
* Only records will match where the dimension contains ALL bits which are also enabled in the given $flags |
187
|
|
|
* argument. Support for 64 bit integers are supported. |
188
|
|
|
* |
189
|
|
|
* Druid has support for bitwise flags since version 0.20.2. |
190
|
|
|
* Before that, we have build our own variant, but then javascript support is required. |
191
|
|
|
* |
192
|
|
|
* JavaScript-based functionality is disabled by default. Please refer to the Druid JavaScript programming guide |
193
|
|
|
* for guidelines about using Druid's JavaScript functionality, including instructions on how to enable it: |
194
|
|
|
* https://druid.apache.org/docs/latest/development/javascript.html |
195
|
|
|
* |
196
|
|
|
* @param string $dimension The dimension which contains int values where you want to do a bitwise AND check |
197
|
|
|
* against. |
198
|
|
|
* @param int $flags The bit's which you want to check if they are enabled in the given dimension. |
199
|
|
|
* |
200
|
|
|
* @return $this |
201
|
|
|
*/ |
202
|
1 |
|
public function orWhereFlags(string $dimension, int $flags) |
203
|
|
|
{ |
204
|
1 |
|
return $this->whereFlags($dimension, $flags, 'or'); |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
/** |
208
|
|
|
* Filter on records which match using a bitwise AND comparison. |
209
|
|
|
* |
210
|
|
|
* Only records will match where the dimension contains ALL bits which are also enabled in the given $flags |
211
|
|
|
* argument. Support for 64 bit integers are supported. |
212
|
|
|
* |
213
|
|
|
* Druid has support for bitwise flags since version 0.20.2. |
214
|
|
|
* Before that, we have build our own variant, but then javascript support is required. |
215
|
|
|
* |
216
|
|
|
* JavaScript-based functionality is disabled by default. Please refer to the Druid JavaScript programming guide |
217
|
|
|
* for guidelines about using Druid's JavaScript functionality, including instructions on how to enable it: |
218
|
|
|
* https://druid.apache.org/docs/latest/development/javascript.html |
219
|
|
|
* |
220
|
|
|
* @param string $dimension The dimension which contains int values where you want to do a bitwise AND check |
221
|
|
|
* against. |
222
|
|
|
* @param int $flags The bit's which you want to check if they are enabled in the given dimension. |
223
|
|
|
* @param string $boolean This influences how this filter will be joined with previous added filters. Should both |
224
|
|
|
* filters apply ("and") or one or the other ("or") ? Default is "and". |
225
|
|
|
* |
226
|
|
|
* @return $this |
227
|
|
|
*/ |
228
|
3 |
|
public function whereFlags(string $dimension, int $flags, string $boolean = 'and') |
229
|
|
|
{ |
230
|
|
|
/** |
231
|
|
|
* If our version supports this, let's use the new and improved bitwiseAnd function! |
232
|
|
|
*/ |
233
|
3 |
|
$version = (string)$this->client->config('version'); |
234
|
3 |
|
if (!empty($version) && version_compare($version, '0.20.2', '>=')) { |
235
|
|
|
|
236
|
1 |
|
$placeholder = 'v' . count($this->placeholders); |
237
|
1 |
|
$this->placeholders[] = $placeholder; |
238
|
|
|
|
239
|
1 |
|
$this->virtualColumn('bitwiseAnd("' . $dimension . '", ' . $flags . ')', $placeholder, DataType::LONG); |
240
|
|
|
|
241
|
1 |
|
return $this->where($placeholder, '=', $flags, null, $boolean); |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
return $this->where($dimension, '=', $flags, function (ExtractionBuilder $extraction) use ($flags) { |
245
|
|
|
// Do a binary "AND" flag comparison on a 64 bit int. The result will either be the |
246
|
|
|
// $flags, or 0 when it's bit is not set. |
247
|
2 |
|
$extraction->javascript(' |
248
|
|
|
function(dimensionValue) { |
249
|
2 |
|
var givenValue = ' . $flags . '; |
250
|
|
|
var hi = 0x80000000; |
251
|
|
|
var low = 0x7fffffff; |
252
|
|
|
var hi1 = ~~(dimensionValue / hi); |
253
|
|
|
var hi2 = ~~(givenValue / hi); |
254
|
|
|
var low1 = dimensionValue & low; |
255
|
|
|
var low2 = givenValue & low; |
256
|
|
|
var h = hi1 & hi2; |
257
|
|
|
var l = low1 & low2; |
258
|
|
|
return (h*hi + l); |
259
|
|
|
} |
260
|
|
|
'); |
261
|
2 |
|
}, $boolean); |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
/** |
265
|
|
|
* Define an array which should contain arrays with dimensions where you want to retrieve the subtotals for. |
266
|
|
|
* NOTE: This only applies for groupBy queries. |
267
|
|
|
* |
268
|
|
|
* This is like doing a WITH ROLLUP in an SQL query. |
269
|
|
|
* |
270
|
|
|
* Example: Imagine that you count the number of people. You want to do this for per city, but also |
271
|
|
|
* per province, per country and per continent. This method allows you to do that all at once. Druid will |
272
|
|
|
* do the "sum(people)" per subtotal row. |
273
|
|
|
* |
274
|
|
|
* Example: |
275
|
|
|
* Array( |
276
|
|
|
* Array('continent', 'country', 'province', 'city'), |
277
|
|
|
* Array('continent', 'country', 'province'), |
278
|
|
|
* Array('continent', 'country'), |
279
|
|
|
* Array('continent'), |
280
|
|
|
* ) |
281
|
|
|
* |
282
|
|
|
* @param array $subtotals |
283
|
|
|
* |
284
|
|
|
* @return $this |
285
|
|
|
*/ |
286
|
3 |
|
public function subtotals(array $subtotals) |
287
|
|
|
{ |
288
|
3 |
|
$this->subtotals = $subtotals; |
289
|
|
|
|
290
|
3 |
|
return $this; |
291
|
|
|
} |
292
|
|
|
|
293
|
|
|
/** |
294
|
|
|
* Select the metrics which should be returned when using a selectQuery. |
295
|
|
|
* If this is not specified, all metrics are returned (which is default). |
296
|
|
|
* |
297
|
|
|
* NOTE: This only applies to select queries! |
298
|
|
|
* |
299
|
|
|
* @param array $metrics |
300
|
|
|
* |
301
|
|
|
* @return $this |
302
|
|
|
*/ |
303
|
1 |
|
public function metrics(array $metrics) |
304
|
|
|
{ |
305
|
1 |
|
$this->metrics = $metrics; |
306
|
|
|
|
307
|
1 |
|
return $this; |
308
|
|
|
} |
309
|
|
|
|
310
|
|
|
/** |
311
|
|
|
* Set a paging identifier. This is only applied for a SELECT query! |
312
|
|
|
* |
313
|
|
|
* @param array $pagingIdentifier |
314
|
|
|
* |
315
|
|
|
* @return \Level23\Druid\Queries\QueryBuilder |
316
|
|
|
*/ |
317
|
5 |
|
public function pagingIdentifier(array $pagingIdentifier): QueryBuilder |
318
|
|
|
{ |
319
|
5 |
|
$this->pagingIdentifier = $pagingIdentifier; |
320
|
|
|
|
321
|
5 |
|
return $this; |
322
|
|
|
} |
323
|
|
|
|
324
|
|
|
/** |
325
|
|
|
* Do a segment metadata query and return the response |
326
|
|
|
* |
327
|
|
|
* @return SegmentMetadataQueryResponse |
328
|
|
|
* @throws \Level23\Druid\Exceptions\QueryResponseException |
329
|
|
|
*/ |
330
|
1 |
|
public function segmentMetadata(): SegmentMetadataQueryResponse |
331
|
|
|
{ |
332
|
1 |
|
$query = new SegmentMetadataQuery($this->dataSource, new IntervalCollection(...$this->intervals)); |
333
|
|
|
|
334
|
1 |
|
$rawResponse = $this->client->executeQuery($query); |
335
|
|
|
|
336
|
1 |
|
return $query->parseResponse($rawResponse); |
337
|
|
|
} |
338
|
|
|
|
339
|
|
|
/** |
340
|
|
|
* Return the query as a JSON string |
341
|
|
|
* |
342
|
|
|
* @param array|QueryContext $context |
343
|
|
|
* |
344
|
|
|
* @return string |
345
|
|
|
* @throws \InvalidArgumentException if the JSON cannot be encoded. |
346
|
|
|
*/ |
347
|
1 |
|
public function toJson($context = []): string |
348
|
|
|
{ |
349
|
1 |
|
$query = $this->buildQuery($context); |
350
|
|
|
|
351
|
1 |
|
$json = \json_encode($query->toArray(), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); |
352
|
1 |
|
if (\JSON_ERROR_NONE !== \json_last_error()) { |
353
|
|
|
throw new InvalidArgumentException( |
354
|
|
|
'json_encode error: ' . \json_last_error_msg() |
355
|
|
|
); |
356
|
|
|
} |
357
|
|
|
|
358
|
1 |
|
return (string)$json; |
359
|
|
|
} |
360
|
|
|
|
361
|
|
|
/** |
362
|
|
|
* Return the query as an array |
363
|
|
|
* |
364
|
|
|
* @param array|QueryContext $context |
365
|
|
|
* |
366
|
|
|
* @return array |
367
|
|
|
*/ |
368
|
2 |
|
public function toArray($context = []): array |
369
|
|
|
{ |
370
|
2 |
|
return $this->buildQuery($context)->toArray(); |
371
|
|
|
} |
372
|
|
|
|
373
|
|
|
/** |
374
|
|
|
* Execute a TimeSeries query. |
375
|
|
|
* |
376
|
|
|
* @param array|TimeSeriesQueryContext $context |
377
|
|
|
* |
378
|
|
|
* @return TimeSeriesQueryResponse |
379
|
|
|
* @throws \Level23\Druid\Exceptions\QueryResponseException |
380
|
|
|
*/ |
381
|
1 |
|
public function timeseries($context = []): TimeSeriesQueryResponse |
382
|
|
|
{ |
383
|
1 |
|
$query = $this->buildTimeSeriesQuery($context); |
384
|
|
|
|
385
|
1 |
|
$rawResponse = $this->client->executeQuery($query); |
386
|
|
|
|
387
|
1 |
|
return $query->parseResponse($rawResponse); |
388
|
|
|
} |
389
|
|
|
|
390
|
|
|
/** |
391
|
|
|
* Execute a Scan Query. |
392
|
|
|
* |
393
|
|
|
* @param array|ScanQueryContext $context Query context parameters |
394
|
|
|
* @param int|null $rowBatchSize How many rows buffered before return to client. Default is 20480 |
395
|
|
|
* @param bool $legacy Return results consistent with the legacy "scan-query" contrib |
396
|
|
|
* extension. Defaults to the value set by druid.query.scan.legacy, |
397
|
|
|
* which in turn defaults to false. See Legacy mode for details. |
398
|
|
|
* @param string $resultFormat Result Format. Use one of the ScanQueryResultFormat::* constants. |
399
|
|
|
* |
400
|
|
|
* @return ScanQueryResponse |
401
|
|
|
* @throws \Level23\Druid\Exceptions\QueryResponseException |
402
|
|
|
*/ |
403
|
5 |
|
public function scan( |
404
|
|
|
$context = [], |
405
|
|
|
int $rowBatchSize = null, |
406
|
|
|
bool $legacy = false, |
407
|
|
|
string $resultFormat = ScanQueryResultFormat::NORMAL_LIST |
408
|
|
|
): ScanQueryResponse { |
409
|
5 |
|
$query = $this->buildScanQuery($context, $rowBatchSize, $legacy, $resultFormat); |
410
|
|
|
|
411
|
2 |
|
$rawResponse = $this->client->executeQuery($query); |
412
|
|
|
|
413
|
2 |
|
return $query->parseResponse($rawResponse); |
414
|
|
|
} |
415
|
|
|
|
416
|
|
|
/** |
417
|
|
|
* Execute a select query. |
418
|
|
|
* |
419
|
|
|
* @param array|QueryContext $context |
420
|
|
|
* |
421
|
|
|
* @return SelectQueryResponse |
422
|
|
|
* @throws \Level23\Druid\Exceptions\QueryResponseException |
423
|
|
|
*/ |
424
|
3 |
|
public function selectQuery($context = []): SelectQueryResponse |
425
|
|
|
{ |
426
|
3 |
|
$query = $this->buildSelectQuery($context); |
427
|
|
|
|
428
|
1 |
|
$rawResponse = $this->client->executeQuery($query); |
429
|
|
|
|
430
|
1 |
|
return $query->parseResponse($rawResponse); |
431
|
|
|
} |
432
|
|
|
|
433
|
|
|
/** |
434
|
|
|
* Execute a topN query. |
435
|
|
|
* |
436
|
|
|
* @param array|TopNQueryContext $context |
437
|
|
|
* |
438
|
|
|
* @return TopNQueryResponse |
439
|
|
|
* @throws \Level23\Druid\Exceptions\QueryResponseException |
440
|
|
|
*/ |
441
|
4 |
|
public function topN($context = []): TopNQueryResponse |
442
|
|
|
{ |
443
|
4 |
|
$query = $this->buildTopNQuery($context); |
444
|
|
|
|
445
|
1 |
|
$rawResponse = $this->client->executeQuery($query); |
446
|
|
|
|
447
|
1 |
|
return $query->parseResponse($rawResponse); |
448
|
|
|
} |
449
|
|
|
|
450
|
|
|
/** |
451
|
|
|
* Return the group by query |
452
|
|
|
* |
453
|
|
|
* @param array|GroupByV2QueryContext|GroupByV1QueryContext $context |
454
|
|
|
* |
455
|
|
|
* @return GroupByQueryResponse |
456
|
|
|
* @throws \Level23\Druid\Exceptions\QueryResponseException |
457
|
|
|
*/ |
458
|
1 |
|
public function groupBy($context = []): GroupByQueryResponse |
459
|
|
|
{ |
460
|
1 |
|
$query = $this->buildGroupByQuery($context, 'v2'); |
461
|
|
|
|
462
|
1 |
|
$rawResponse = $this->client->executeQuery($query); |
463
|
|
|
|
464
|
1 |
|
return $query->parseResponse($rawResponse); |
465
|
|
|
} |
466
|
|
|
|
467
|
|
|
/** |
468
|
|
|
* Return the group by query |
469
|
|
|
* |
470
|
|
|
* @param array|GroupByV2QueryContext|GroupByV1QueryContext $context |
471
|
|
|
* |
472
|
|
|
* @return GroupByQueryResponse |
473
|
|
|
* @throws \Level23\Druid\Exceptions\QueryResponseException |
474
|
|
|
*/ |
475
|
1 |
|
public function groupByV1($context = []): GroupByQueryResponse |
476
|
|
|
{ |
477
|
1 |
|
$query = $this->buildGroupByQuery($context, 'v1'); |
478
|
|
|
|
479
|
1 |
|
$rawResponse = $this->client->executeQuery($query); |
480
|
|
|
|
481
|
1 |
|
return $query->parseResponse($rawResponse); |
482
|
|
|
} |
483
|
|
|
|
484
|
|
|
/** |
485
|
|
|
* Execute a search query and return the response |
486
|
|
|
* |
487
|
|
|
* @param array|QueryContext $context |
488
|
|
|
* @param string $sortingOrder |
489
|
|
|
* |
490
|
|
|
* @return \Level23\Druid\Responses\SearchQueryResponse |
491
|
|
|
* @throws \Level23\Druid\Exceptions\QueryResponseException |
492
|
|
|
*/ |
493
|
3 |
|
public function search($context = [], string $sortingOrder = SortingOrder::LEXICOGRAPHIC): SearchQueryResponse |
494
|
|
|
{ |
495
|
3 |
|
$query = $this->buildSearchQuery($context, $sortingOrder); |
496
|
|
|
|
497
|
1 |
|
$rawResponse = $this->client->executeQuery($query); |
498
|
|
|
|
499
|
1 |
|
return $query->parseResponse($rawResponse); |
500
|
|
|
} |
501
|
|
|
|
502
|
|
|
//<editor-fold desc="Protected methods"> |
503
|
|
|
|
504
|
|
|
/** |
505
|
|
|
* In our previous version we required an `order()` with "__time" for TimeSeries, Scan and Select Queries. |
506
|
|
|
* This method makes sure that we are backwards compatible. |
507
|
|
|
* |
508
|
|
|
* @param string|null $dimension If given, we will also check if this dimension was ordered by. |
509
|
|
|
* |
510
|
|
|
* @return bool|null |
511
|
|
|
*/ |
512
|
9 |
|
protected function legacyIsOrderByDirectionDescending(string $dimension = null): ?bool |
513
|
|
|
{ |
514
|
9 |
|
if ($this->limit) { |
515
|
7 |
|
$orderBy = $this->limit->getOrderByCollection(); |
516
|
|
|
|
517
|
7 |
|
if ($orderBy->count() > 0) { |
518
|
5 |
|
$orderByItems = $orderBy->toArray(); |
519
|
5 |
|
$first = reset($orderByItems); |
520
|
|
|
|
521
|
5 |
|
if ($first['dimension'] == '__time' || ($dimension && $dimension == $first['dimension'])) { |
522
|
4 |
|
return $first['direction'] == OrderByDirection::DESC; |
523
|
|
|
} |
524
|
|
|
} |
525
|
|
|
} |
526
|
|
|
|
527
|
5 |
|
return null; |
528
|
|
|
} |
529
|
|
|
|
530
|
|
|
/** |
531
|
|
|
* Build a search query. |
532
|
|
|
* |
533
|
|
|
* @param array|QueryContext $context |
534
|
|
|
* @param string $sortingOrder |
535
|
|
|
* |
536
|
|
|
* @return \Level23\Druid\Queries\SearchQuery |
537
|
|
|
*/ |
538
|
5 |
|
protected function buildSearchQuery($context = [], string $sortingOrder = SortingOrder::LEXICOGRAPHIC): SearchQuery |
539
|
|
|
{ |
540
|
5 |
|
if (count($this->intervals) == 0) { |
541
|
1 |
|
throw new InvalidArgumentException('You have to specify at least one interval'); |
542
|
|
|
} |
543
|
|
|
|
544
|
4 |
|
if (!$this->searchFilter) { |
545
|
1 |
|
throw new InvalidArgumentException('You have to specify a search filter!'); |
546
|
|
|
} |
547
|
|
|
|
548
|
3 |
|
$query = new SearchQuery( |
549
|
3 |
|
$this->dataSource, |
550
|
3 |
|
$this->granularity, |
551
|
3 |
|
new IntervalCollection(...$this->intervals), |
552
|
3 |
|
$this->searchFilter |
553
|
|
|
); |
554
|
|
|
|
555
|
3 |
|
if (count($this->searchDimensions) > 0) { |
556
|
1 |
|
$query->setDimensions($this->searchDimensions); |
557
|
|
|
} |
558
|
|
|
|
559
|
3 |
|
if (is_array($context) && count($context) > 0) { |
560
|
1 |
|
$query->setContext(new QueryContext($context)); |
561
|
2 |
|
} elseif ($context instanceof QueryContext) { |
562
|
1 |
|
$query->setContext($context); |
563
|
|
|
} |
564
|
|
|
|
565
|
3 |
|
if ($this->filter) { |
566
|
2 |
|
$query->setFilter($this->filter); |
567
|
|
|
} |
568
|
|
|
|
569
|
3 |
|
if ($sortingOrder) { |
570
|
3 |
|
$query->setSort($sortingOrder); |
571
|
|
|
} |
572
|
|
|
|
573
|
3 |
|
if ($this->limit && $this->limit->getLimit() !== null) { |
574
|
2 |
|
$query->setLimit($this->limit->getLimit()); |
575
|
|
|
} |
576
|
|
|
|
577
|
3 |
|
return $query; |
578
|
|
|
} |
579
|
|
|
|
580
|
|
|
/** |
581
|
|
|
* Build a select query. |
582
|
|
|
* |
583
|
|
|
* @param array|QueryContext $context |
584
|
|
|
* |
585
|
|
|
* @return \Level23\Druid\Queries\SelectQuery |
586
|
|
|
*/ |
587
|
6 |
|
protected function buildSelectQuery($context = []): SelectQuery |
588
|
|
|
{ |
589
|
6 |
|
if (count($this->intervals) == 0) { |
590
|
1 |
|
throw new InvalidArgumentException('You have to specify at least one interval'); |
591
|
|
|
} |
592
|
|
|
|
593
|
5 |
|
if (!$this->limit || $this->limit->getLimit() === null) { |
594
|
1 |
|
throw new InvalidArgumentException('You have to supply a limit'); |
595
|
|
|
} |
596
|
|
|
|
597
|
4 |
|
$limit = $this->limit->getLimit(); |
598
|
|
|
|
599
|
4 |
|
$descending = false; |
600
|
4 |
|
if ($this->direction) { |
601
|
1 |
|
$descending = ($this->direction === OrderByDirection::DESC); |
602
|
3 |
|
} elseif ($this->legacyIsOrderByDirectionDescending() === true) { |
603
|
1 |
|
$descending = true; |
604
|
|
|
} |
605
|
|
|
|
606
|
4 |
|
$query = new SelectQuery( |
607
|
4 |
|
$this->dataSource, |
608
|
4 |
|
new IntervalCollection(...$this->intervals), |
609
|
4 |
|
$limit, |
610
|
4 |
|
count($this->dimensions) > 0 ? new DimensionCollection(...$this->dimensions) : null, |
611
|
4 |
|
$this->metrics, |
612
|
4 |
|
$descending |
613
|
|
|
); |
614
|
|
|
|
615
|
4 |
|
if ($this->pagingIdentifier) { |
616
|
2 |
|
$query->setPagingIdentifier($this->pagingIdentifier); |
617
|
|
|
} |
618
|
|
|
|
619
|
4 |
|
if (is_array($context) && count($context) > 0) { |
620
|
1 |
|
$query->setContext(new QueryContext($context)); |
621
|
3 |
|
} elseif ($context instanceof QueryContext) { |
622
|
2 |
|
$query->setContext($context); |
623
|
|
|
} |
624
|
|
|
|
625
|
4 |
|
return $query; |
626
|
|
|
} |
627
|
|
|
|
628
|
|
|
/** |
629
|
|
|
* Build a scan query. |
630
|
|
|
* |
631
|
|
|
* @param array|QueryContext $context |
632
|
|
|
* @param int|null $rowBatchSize |
633
|
|
|
* @param bool $legacy |
634
|
|
|
* @param string $resultFormat |
635
|
|
|
* |
636
|
|
|
* @return \Level23\Druid\Queries\ScanQuery |
637
|
|
|
*/ |
638
|
10 |
|
protected function buildScanQuery( |
639
|
|
|
$context = [], |
640
|
|
|
int $rowBatchSize = null, |
641
|
|
|
bool $legacy = false, |
642
|
|
|
string $resultFormat = ScanQueryResultFormat::NORMAL_LIST |
643
|
|
|
) { |
644
|
10 |
|
if (count($this->intervals) == 0) { |
645
|
1 |
|
throw new InvalidArgumentException('You have to specify at least one interval'); |
646
|
|
|
} |
647
|
|
|
|
648
|
9 |
|
if (!$this->isDimensionsListScanCompliant()) { |
649
|
1 |
|
throw new InvalidArgumentException( |
650
|
|
|
'Only simple dimension or metric selects are available in a scan query. ' . |
651
|
1 |
|
'Aliases, extractions or lookups are not available.' |
652
|
|
|
); |
653
|
|
|
} |
654
|
|
|
|
655
|
8 |
|
$query = new ScanQuery( |
656
|
8 |
|
$this->dataSource, |
657
|
8 |
|
new IntervalCollection(...$this->intervals) |
658
|
|
|
); |
659
|
|
|
|
660
|
8 |
|
$columns = []; |
661
|
8 |
|
foreach ($this->dimensions as $dimension) { |
662
|
3 |
|
$columns[] = $dimension->getDimension(); |
663
|
|
|
} |
664
|
|
|
|
665
|
8 |
|
if ($this->direction) { |
666
|
2 |
|
$query->setOrder($this->direction); |
667
|
|
|
} else { |
668
|
6 |
|
$isDescending = $this->legacyIsOrderByDirectionDescending(); |
669
|
6 |
|
if ($isDescending !== null) { |
670
|
2 |
|
$query->setOrder($isDescending ? OrderByDirection::DESC : OrderByDirection::ASC); |
671
|
|
|
} |
672
|
|
|
} |
673
|
|
|
|
674
|
8 |
|
if (count($columns) > 0) { |
675
|
3 |
|
$query->setColumns($columns); |
676
|
|
|
} |
677
|
|
|
|
678
|
8 |
|
if ($this->filter) { |
679
|
4 |
|
$query->setFilter($this->filter); |
680
|
|
|
} |
681
|
|
|
|
682
|
8 |
|
if ($this->limit && $this->limit->getLimit() !== null) { |
683
|
5 |
|
$query->setLimit($this->limit->getLimit()); |
684
|
|
|
} |
685
|
|
|
|
686
|
8 |
|
if ($this->limit && $this->limit->getOffset() !== null) { |
687
|
3 |
|
$query->setOffset($this->limit->getOffset()); |
688
|
|
|
} |
689
|
|
|
|
690
|
8 |
|
if (is_array($context) && count($context) > 0) { |
691
|
1 |
|
$query->setContext(new ScanQueryContext($context)); |
692
|
7 |
|
} elseif ($context instanceof QueryContext) { |
693
|
2 |
|
$query->setContext($context); |
694
|
|
|
} |
695
|
|
|
|
696
|
8 |
|
if ($resultFormat) { |
697
|
8 |
|
$query->setResultFormat($resultFormat); |
698
|
|
|
} |
699
|
|
|
|
700
|
7 |
|
if ($rowBatchSize !== null && $rowBatchSize > 0) { |
701
|
3 |
|
$query->setBatchSize($rowBatchSize); |
702
|
|
|
} |
703
|
|
|
|
704
|
7 |
|
$query->setLegacy($legacy); |
705
|
|
|
|
706
|
7 |
|
return $query; |
707
|
|
|
} |
708
|
|
|
|
709
|
|
|
/** |
710
|
|
|
* Build a TimeSeries query. |
711
|
|
|
* |
712
|
|
|
* @param array|QueryContext $context |
713
|
|
|
* |
714
|
|
|
* @return TimeSeriesQuery |
715
|
|
|
*/ |
716
|
8 |
|
protected function buildTimeSeriesQuery($context = []): TimeSeriesQuery |
717
|
|
|
{ |
718
|
8 |
|
if (count($this->intervals) == 0) { |
719
|
1 |
|
throw new InvalidArgumentException('You have to specify at least one interval'); |
720
|
|
|
} |
721
|
|
|
|
722
|
7 |
|
$query = new TimeSeriesQuery( |
723
|
7 |
|
$this->dataSource, |
724
|
7 |
|
new IntervalCollection(...$this->intervals), |
725
|
7 |
|
$this->granularity |
726
|
|
|
); |
727
|
|
|
|
728
|
|
|
// check if we want to use a different output name for the __time column |
729
|
7 |
|
$dimension = null; |
730
|
7 |
|
if (count($this->dimensions) == 1) { |
731
|
7 |
|
$dimension = $this->dimensions[0]; |
732
|
|
|
// did we only retrieve the time dimension? |
733
|
7 |
|
if ($dimension->getDimension() == '__time' && $dimension->getOutputName() != '__time') { |
734
|
6 |
|
$query->setTimeOutputName($dimension->getOutputName()); |
735
|
|
|
} |
736
|
|
|
} |
737
|
|
|
|
738
|
7 |
|
if (is_array($context) && count($context) > 0) { |
739
|
3 |
|
$query->setContext(new TimeSeriesQueryContext($context)); |
740
|
4 |
|
} elseif ($context instanceof QueryContext) { |
741
|
4 |
|
$query->setContext($context); |
742
|
|
|
} |
743
|
|
|
|
744
|
7 |
|
if ($this->filter) { |
745
|
1 |
|
$query->setFilter($this->filter); |
746
|
|
|
} |
747
|
|
|
|
748
|
7 |
|
if (count($this->aggregations) > 0) { |
749
|
1 |
|
$query->setAggregations(new AggregationCollection(...$this->aggregations)); |
750
|
|
|
} |
751
|
|
|
|
752
|
7 |
|
if (count($this->postAggregations) > 0) { |
753
|
2 |
|
$query->setPostAggregations(new PostAggregationCollection(...$this->postAggregations)); |
754
|
|
|
} |
755
|
|
|
|
756
|
7 |
|
if (count($this->virtualColumns) > 0) { |
757
|
2 |
|
$query->setVirtualColumns(new VirtualColumnCollection(...$this->virtualColumns)); |
758
|
|
|
} |
759
|
|
|
|
760
|
|
|
// If there is a limit set, then apply this on the time series query. |
761
|
7 |
|
if ($this->limit && $this->limit->getLimit() !== null) { |
762
|
6 |
|
$query->setLimit($this->limit->getLimit()); |
763
|
|
|
} |
764
|
|
|
|
765
|
7 |
|
$descending = false; |
766
|
7 |
|
if ($this->direction) { |
767
|
5 |
|
$descending = ($this->direction === OrderByDirection::DESC); |
768
|
2 |
|
} elseif ($this->legacyIsOrderByDirectionDescending($dimension ? $dimension->getOutputName() : null) === true) { |
769
|
1 |
|
$descending = true; |
770
|
|
|
} |
771
|
|
|
|
772
|
7 |
|
if ($descending) { |
773
|
2 |
|
$query->setDescending($descending); |
774
|
|
|
} |
775
|
|
|
|
776
|
7 |
|
return $query; |
777
|
|
|
} |
778
|
|
|
|
779
|
|
|
/** |
780
|
|
|
* Build a topN query. |
781
|
|
|
* |
782
|
|
|
* @param array|QueryContext $context |
783
|
|
|
* |
784
|
|
|
* @return TopNQuery |
785
|
|
|
*/ |
786
|
6 |
|
protected function buildTopNQuery($context = []): TopNQuery |
787
|
|
|
{ |
788
|
6 |
|
if (count($this->intervals) == 0) { |
789
|
1 |
|
throw new InvalidArgumentException('You have to specify at least one interval'); |
790
|
|
|
} |
791
|
|
|
|
792
|
5 |
|
if (!$this->limit instanceof LimitInterface || $this->limit->getLimit() === null) { |
793
|
1 |
|
throw new InvalidArgumentException( |
794
|
1 |
|
'You should specify a limit to make use of a top query' |
795
|
|
|
); |
796
|
|
|
} |
797
|
|
|
|
798
|
4 |
|
$orderByCollection = $this->limit->getOrderByCollection(); |
799
|
4 |
|
if (count($orderByCollection) == 0) { |
800
|
1 |
|
throw new InvalidArgumentException( |
801
|
1 |
|
'You should specify a an order by direction to make use of a top query' |
802
|
|
|
); |
803
|
|
|
} |
804
|
|
|
|
805
|
|
|
/** |
806
|
|
|
* @var \Level23\Druid\OrderBy\OrderBy $orderBy |
807
|
|
|
*/ |
808
|
3 |
|
$orderBy = $orderByCollection[0]; |
809
|
|
|
|
810
|
3 |
|
$metric = $orderBy->getDimension(); |
811
|
|
|
|
812
|
|
|
/** @var \Level23\Druid\OrderBy\OrderByInterface $orderBy */ |
813
|
3 |
|
$query = new TopNQuery( |
814
|
3 |
|
$this->dataSource, |
815
|
3 |
|
new IntervalCollection(...$this->intervals), |
816
|
3 |
|
$this->dimensions[0], |
817
|
3 |
|
$this->limit->getLimit(), |
818
|
3 |
|
$metric, |
819
|
3 |
|
$this->granularity |
820
|
|
|
); |
821
|
|
|
|
822
|
3 |
|
$query->setDescending( |
823
|
3 |
|
($orderBy->getDirection() == OrderByDirection::DESC) |
824
|
|
|
); |
825
|
|
|
|
826
|
3 |
|
if (count($this->aggregations) > 0) { |
827
|
2 |
|
$query->setAggregations(new AggregationCollection(...$this->aggregations)); |
828
|
|
|
} |
829
|
|
|
|
830
|
3 |
|
if (count($this->postAggregations) > 0) { |
831
|
2 |
|
$query->setPostAggregations(new PostAggregationCollection(...$this->postAggregations)); |
832
|
|
|
} |
833
|
|
|
|
834
|
3 |
|
if (count($this->virtualColumns) > 0) { |
835
|
2 |
|
$query->setVirtualColumns(new VirtualColumnCollection(...$this->virtualColumns)); |
836
|
|
|
} |
837
|
|
|
|
838
|
3 |
|
if (is_array($context) && count($context) > 0) { |
839
|
1 |
|
$query->setContext(new TopNQueryContext($context)); |
840
|
2 |
|
} elseif ($context instanceof QueryContext) { |
841
|
1 |
|
$query->setContext($context); |
842
|
|
|
} |
843
|
|
|
|
844
|
3 |
|
if ($this->filter) { |
845
|
2 |
|
$query->setFilter($this->filter); |
846
|
|
|
} |
847
|
|
|
|
848
|
3 |
|
return $query; |
849
|
|
|
} |
850
|
|
|
|
851
|
|
|
/** |
852
|
|
|
* Build the group by query |
853
|
|
|
* |
854
|
|
|
* @param array|QueryContext $context |
855
|
|
|
* @param string $type |
856
|
|
|
* |
857
|
|
|
* @return GroupByQuery |
858
|
|
|
*/ |
859
|
5 |
|
protected function buildGroupByQuery($context = [], string $type = 'v2'): GroupByQuery |
860
|
|
|
{ |
861
|
5 |
|
if (count($this->intervals) == 0) { |
862
|
1 |
|
throw new InvalidArgumentException('You have to specify at least one interval'); |
863
|
|
|
} |
864
|
|
|
|
865
|
4 |
|
$query = new GroupByQuery( |
866
|
4 |
|
$this->dataSource, |
867
|
4 |
|
new DimensionCollection(...$this->dimensions), |
868
|
4 |
|
new IntervalCollection(...$this->intervals), |
869
|
4 |
|
new AggregationCollection(...$this->aggregations), |
870
|
4 |
|
$this->granularity |
871
|
|
|
); |
872
|
|
|
|
873
|
4 |
|
if (is_array($context)) { |
874
|
2 |
|
switch ($type) { |
875
|
2 |
|
case 'v1': |
876
|
1 |
|
$context = new GroupByV1QueryContext($context); |
877
|
1 |
|
break; |
878
|
|
|
|
879
|
|
|
default: |
880
|
1 |
|
case 'v2': |
881
|
1 |
|
$context = new GroupByV2QueryContext($context); |
882
|
1 |
|
break; |
883
|
|
|
} |
884
|
|
|
} |
885
|
|
|
|
886
|
4 |
|
$query->setContext($context); |
887
|
|
|
|
888
|
4 |
|
if (count($this->postAggregations) > 0) { |
889
|
2 |
|
$query->setPostAggregations(new PostAggregationCollection(...$this->postAggregations)); |
890
|
|
|
} |
891
|
|
|
|
892
|
4 |
|
if ($this->filter) { |
893
|
2 |
|
$query->setFilter($this->filter); |
894
|
|
|
} |
895
|
|
|
|
896
|
4 |
|
if ($this->limit) { |
897
|
2 |
|
$query->setLimit($this->limit); |
898
|
|
|
} |
899
|
|
|
|
900
|
4 |
|
if (count($this->virtualColumns) > 0) { |
901
|
1 |
|
$query->setVirtualColumns(new VirtualColumnCollection(...$this->virtualColumns)); |
902
|
|
|
} |
903
|
|
|
|
904
|
4 |
|
if (count($this->subtotals) > 0) { |
905
|
2 |
|
$query->setSubtotals($this->subtotals); |
906
|
|
|
} |
907
|
|
|
|
908
|
4 |
|
if ($this->having) { |
909
|
2 |
|
$query->setHaving($this->having); |
910
|
|
|
} |
911
|
|
|
|
912
|
4 |
|
return $query; |
913
|
|
|
} |
914
|
|
|
|
915
|
|
|
/** |
916
|
|
|
* Return the query automatically detected based on the requested data. |
917
|
|
|
* |
918
|
|
|
* @param array|QueryContext $context |
919
|
|
|
* |
920
|
|
|
* @return \Level23\Druid\Queries\QueryInterface |
921
|
|
|
*/ |
922
|
7 |
|
protected function buildQuery($context = []): QueryInterface |
923
|
|
|
{ |
924
|
|
|
// Check if this is a scan query. This is the preferred way to query when there are |
925
|
|
|
// no aggregations done. |
926
|
7 |
|
if ($this->isScanQuery()) { |
927
|
2 |
|
return $this->buildScanQuery($context); |
928
|
|
|
} |
929
|
|
|
|
930
|
|
|
// If we only have "grouped" by __time, then we can use a time series query. |
931
|
|
|
// This is preferred, because it's a lot faster then doing a group by query. |
932
|
5 |
|
if ($this->isTimeSeriesQuery()) { |
933
|
1 |
|
return $this->buildTimeSeriesQuery($context); |
934
|
|
|
} |
935
|
|
|
|
936
|
|
|
// Check if we can use a topN query. |
937
|
4 |
|
if ($this->isTopNQuery()) { |
938
|
1 |
|
return $this->buildTopNQuery($context); |
939
|
|
|
} |
940
|
|
|
|
941
|
|
|
// Check if we can use a select query. |
942
|
3 |
|
if ($this->isSelectQuery()) { |
943
|
1 |
|
return $this->buildSelectQuery($context); |
944
|
|
|
} |
945
|
|
|
|
946
|
|
|
// Check if we can use a search query. |
947
|
2 |
|
if ($this->isSearchQuery()) { |
948
|
1 |
|
return $this->buildSearchQuery($context); |
949
|
|
|
} |
950
|
|
|
|
951
|
1 |
|
return $this->buildGroupByQuery($context, 'v2'); |
952
|
|
|
} |
953
|
|
|
|
954
|
|
|
/** |
955
|
|
|
* Determine if the current query is a TimeSeries query |
956
|
|
|
* |
957
|
|
|
* @return bool |
958
|
|
|
*/ |
959
|
4 |
|
protected function isTimeSeriesQuery(): bool |
960
|
|
|
{ |
961
|
4 |
|
if (count($this->dimensions) != 1) { |
962
|
1 |
|
return false; |
963
|
|
|
} |
964
|
|
|
|
965
|
3 |
|
return $this->dimensions[0]->getDimension() == '__time' |
966
|
3 |
|
&& $this->dimensions[0] instanceof Dimension |
967
|
3 |
|
&& $this->dimensions[0]->getExtractionFunction() === null; |
968
|
|
|
} |
969
|
|
|
|
970
|
|
|
/** |
971
|
|
|
* Determine if the current query is topN query |
972
|
|
|
* |
973
|
|
|
* @return bool |
974
|
|
|
*/ |
975
|
4 |
|
protected function isTopNQuery(): bool |
976
|
|
|
{ |
977
|
4 |
|
if (count($this->dimensions) != 1) { |
978
|
1 |
|
return false; |
979
|
|
|
} |
980
|
|
|
|
981
|
3 |
|
return $this->limit |
982
|
3 |
|
&& $this->limit->getLimit() !== null |
983
|
3 |
|
&& $this->limit->getOffset() === null |
984
|
3 |
|
&& count($this->limit->getOrderByCollection()) == 1; |
985
|
|
|
} |
986
|
|
|
|
987
|
|
|
/** |
988
|
|
|
* Check if we should use a select query. |
989
|
|
|
* |
990
|
|
|
* @return bool |
991
|
|
|
*/ |
992
|
4 |
|
protected function isSelectQuery(): bool |
993
|
|
|
{ |
994
|
4 |
|
return $this->pagingIdentifier !== null && count($this->aggregations) == 0; |
995
|
|
|
} |
996
|
|
|
|
997
|
|
|
/** |
998
|
|
|
* Check if we should use a search query. |
999
|
|
|
* |
1000
|
|
|
* @return bool |
1001
|
|
|
*/ |
1002
|
2 |
|
protected function isSearchQuery(): bool |
1003
|
|
|
{ |
1004
|
2 |
|
return !empty($this->searchFilter); |
1005
|
|
|
} |
1006
|
|
|
|
1007
|
|
|
/** |
1008
|
|
|
* Check if we should use a scan query. |
1009
|
|
|
* |
1010
|
|
|
* @return bool |
1011
|
|
|
*/ |
1012
|
5 |
|
protected function isScanQuery(): bool |
1013
|
|
|
{ |
1014
|
5 |
|
return count($this->aggregations) == 0 && $this->isDimensionsListScanCompliant(); |
1015
|
|
|
} |
1016
|
|
|
|
1017
|
|
|
/** |
1018
|
|
|
* Return true if the dimensions which are selected can be used as "columns" in a scan query. |
1019
|
|
|
* |
1020
|
|
|
* @return bool |
1021
|
|
|
*/ |
1022
|
19 |
|
protected function isDimensionsListScanCompliant(): bool |
1023
|
|
|
{ |
1024
|
19 |
|
foreach ($this->dimensions as $dimension) { |
1025
|
13 |
|
if (!$dimension instanceof Dimension) { |
1026
|
5 |
|
return false; |
1027
|
|
|
} |
1028
|
|
|
|
1029
|
8 |
|
if ($dimension->getExtractionFunction()) { |
1030
|
1 |
|
return false; |
1031
|
|
|
} |
1032
|
|
|
|
1033
|
7 |
|
if ($dimension->getDimension() != $dimension->getOutputName()) { |
1034
|
7 |
|
return false; |
1035
|
|
|
} |
1036
|
|
|
} |
1037
|
|
|
|
1038
|
10 |
|
return true; |
1039
|
|
|
} |
1040
|
|
|
//</editor-fold> |
1041
|
|
|
} |
1042
|
|
|
|
1043
|
|
|
|