Completed
Push — master ( f7f8c9...77c668 )
by Teye
37:01 queued 24:30
created

HasFilter::whereFlags()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 20
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 4
c 0
b 0
f 0
dl 0
loc 20
ccs 4
cts 4
cp 1
rs 10
cc 1
nc 1
nop 3
crap 1
1
<?php
2
declare(strict_types=1);
3
4
namespace Level23\Druid\Concerns;
5
6
use Closure;
7
use InvalidArgumentException;
8
use Level23\Druid\Filters\InFilter;
9
use Level23\Druid\Filters\OrFilter;
10
use Level23\Druid\Filters\AndFilter;
11
use Level23\Druid\Filters\NotFilter;
12
use Level23\Druid\Interval\Interval;
13
use Level23\Druid\Filters\LikeFilter;
14
use Level23\Druid\Filters\BoundFilter;
15
use Level23\Druid\Filters\RegexFilter;
16
use Level23\Druid\Filters\SearchFilter;
17
use Level23\Druid\Dimensions\Dimension;
18
use Level23\Druid\Filters\FilterBuilder;
19
use Level23\Druid\Filters\BetweenFilter;
20
use Level23\Druid\Filters\IntervalFilter;
21
use Level23\Druid\Filters\SelectorFilter;
22
use Level23\Druid\Filters\FilterInterface;
23
use Level23\Druid\Filters\JavascriptFilter;
24
use Level23\Druid\Interval\IntervalInterface;
25
use Level23\Druid\Dimensions\DimensionBuilder;
26
use Level23\Druid\Extractions\ExtractionBuilder;
27
use Level23\Druid\Dimensions\DimensionInterface;
28
use Level23\Druid\Filters\ColumnComparisonFilter;
29
use Level23\Druid\Extractions\ExtractionInterface;
30
31
trait HasFilter
32
{
33
    /**
34
     * @var \Level23\Druid\Filters\FilterInterface|null
35
     */
36
    protected $filter;
37
38
    /**
39
     * Filter our results where the given dimension matches the value based on the operator.
40
     * The operator can be '=', '>', '>=', '<', '<=', '<>', '!=', 'like', 'not like', 'regex', 'not regex',
41
     * 'javascript', 'not javascript', 'search' and 'not search'
42
     *
43
     * @param string|FilterInterface|\Closure $filterOrDimensionOrClosure The dimension which you want to filter.
44
     * @param string|null                     $operator                   The operator which you want to use to filter.
45
     *                                                                    See below for a complete list of supported
46
     *                                                                    operators.
47
     * @param mixed                           $value                      The value which you want to use in your
48
     *                                                                    filter comparison
49
     * @param \Closure|null                   $extraction                 A closure which builds one or more extraction
50
     *                                                                    function. These are applied before the filter
51
     *                                                                    will be applied. So the filter will use the
52
     *                                                                    value returned by the extraction function(s).
53
     * @param string                          $boolean                    This influences how this filter will be
54
     *                                                                    joined with previous added filters. Should
55
     *                                                                    both filters apply ("and") or one or the
56
     *                                                                    other ("or") ? Default is "and".
57
     *
58
     * @return $this
59
     */
60 48
    public function where(
61
        $filterOrDimensionOrClosure,
62
        $operator = null,
63
        $value = null,
64
        Closure $extraction = null,
65
        string $boolean = 'and'
66
    ) {
67 48
        $filter = null;
68 48
        if (is_string($filterOrDimensionOrClosure)) {
69 33
            if ($value === null && $operator !== null) {
70 2
                $value    = $operator;
71 2
                $operator = '=';
72
            }
73
74 33
            if ($operator === null || $value === null) {
75 2
                throw new InvalidArgumentException('You have to supply an operator and an compare value when you supply a dimension as string');
76
            }
77
78 31
            $operator = strtolower($operator);
79
80 31
            if ($operator == '=') {
81 11
                $filter = new SelectorFilter(
82 11
                    $filterOrDimensionOrClosure,
83 11
                    (string)$value,
84 11
                    $this->getExtraction($extraction)
85
                );
86 20
            } elseif ($operator == '<>' || $operator == '!=') {
87 5
                $filter = new NotFilter(
88 5
                    new SelectorFilter($filterOrDimensionOrClosure, (string)$value, $this->getExtraction($extraction))
89
                );
90 15
            } elseif (in_array($operator, ['>', '>=', '<', '<='])) {
91 4
                $filter = new BoundFilter(
92 4
                    $filterOrDimensionOrClosure,
93 4
                    $operator,
94 4
                    (string)$value,
95 4
                    null,
96 4
                    $this->getExtraction($extraction)
97
                );
98 11
            } elseif ($operator == 'like') {
99 1
                $filter = new LikeFilter(
100 1
                    $filterOrDimensionOrClosure, $value, '\\', $this->getExtraction($extraction)
101
                );
102 10
            } elseif ($operator == 'not like') {
103 1
                $filter = new NotFilter(
104 1
                    new LikeFilter($filterOrDimensionOrClosure, $value, '\\', $this->getExtraction($extraction))
105
                );
106 9
            } elseif ($operator == 'javascript') {
107 1
                $filter = new JavascriptFilter($filterOrDimensionOrClosure, $value, $this->getExtraction($extraction));
108 8
            } elseif ($operator == 'not javascript') {
109 1
                $filter = new NotFilter(
110 1
                    new JavascriptFilter($filterOrDimensionOrClosure, $value, $this->getExtraction($extraction))
111
                );
112 7
            } elseif ($operator == 'regex' || $operator == 'regexp') {
113 2
                $filter = new RegexFilter($filterOrDimensionOrClosure, $value, $this->getExtraction($extraction));
114 5
            } elseif ($operator == 'not regex' || $operator == 'not regexp') {
115 2
                $filter = new NotFilter(
116 2
                    new RegexFilter($filterOrDimensionOrClosure, $value, $this->getExtraction($extraction))
117
                );
118 3
            } elseif ($operator == 'search') {
119 1
                $filter = new SearchFilter(
120 1
                    $filterOrDimensionOrClosure, $value, false, $this->getExtraction($extraction)
121
                );
122 2
            } elseif ($operator == 'not search') {
123 1
                $filter = new NotFilter(new SearchFilter(
124 1
                    $filterOrDimensionOrClosure, $value, false, $this->getExtraction($extraction)
125
                ));
126
            } else {
127 31
                $filter = null;
128
            }
129 15
        } elseif ($filterOrDimensionOrClosure instanceof FilterInterface) {
130 11
            $filter = $filterOrDimensionOrClosure;
131 5
        } elseif ($filterOrDimensionOrClosure instanceof Closure) {
132
133
            // lets create a bew builder object where the user can mess around with
134 1
            $builder = new FilterBuilder();
135
136
            // call the user function
137 1
            call_user_func($filterOrDimensionOrClosure, $builder);
138
139
            // Now retrieve the filter which was created and add it to our current filter set.
140 1
            $filter = $builder->getFilter();
141
        }
142
143 46
        if ($filter === null) {
144 5
            throw new InvalidArgumentException('The arguments which you have supplied cannot be parsed.');
145
        }
146
147 41
        strtolower($boolean) == 'and' ? $this->addAndFilter($filter) : $this->addOrFilter($filter);
148
149 41
        return $this;
150
    }
151
152
    /**
153
     * This applies a filter, only it will join previous added filters with an "or" instead of an "and".
154
     * See the documentation of the "where" method for more information
155
     *
156
     * @param string|FilterInterface $filterOrDimension
157
     * @param string|null            $operator
158
     * @param mixed|null             $value
159
     * @param \Closure|null          $extraction
160
     *
161
     * @return $this
162
     * @see \Level23\Druid\Concerns\HasFilter::where()
163
     */
164 1
    public function orWhere($filterOrDimension, $operator = null, $value = null, Closure $extraction = null)
165
    {
166 1
        return $this->where($filterOrDimension, $operator, $value, $extraction, 'or');
167
    }
168
169
    /**
170
     * Filter records where the given dimension exists in the given list of items
171
     *
172
     * @param string        $dimension  The dimension which you want to filte
173
     * @param array         $items      A list of values. We will return records where the dimension is in this list.
174
     * @param \Closure|null $extraction An extraction function to extract a different value from the dimension.
175
     * @param string        $boolean    This influences how this filter will be joined with previous added filters.
176
     *                                  Should both filters apply ("and") or one or the other ("or") ? Default is
177
     *                                  "and".
178
     *
179
     * @return $this
180
     */
181 2
    public function whereIn(string $dimension, array $items, Closure $extraction = null, $boolean = 'and')
182
    {
183 2
        $filter = new InFilter($dimension, $items, $this->getExtraction($extraction));
184
185 2
        return $this->where($filter, null, null, null, $boolean);
186
    }
187
188
    /**
189
     * Filter records where the given dimension exists in the given list of items.
190
     *
191
     * If there are previously defined filters, this filter will be joined with an "or".
192
     *
193
     * @param string        $dimension  The dimension which you want to filte
194
     * @param array         $items      A list of values. We will return records where the dimension is in this list.
195
     * @param \Closure|null $extraction An extraction function to extract a different value from the dimension.
196
     *
197
     * @return $this
198
     */
199 1
    public function orWhereIn(string $dimension, array $items, Closure $extraction = null)
200
    {
201 1
        return $this->whereIn($dimension, $items, $extraction, 'or');
202
    }
203
204
    /**
205
     * Filter records where dimensionA is equal to dimensionB.
206
     * You can either supply a string or a Closure. The Closure will receive a DimensionBuilder object, which allows
207
     * you to select a dimension and apply extraction functions if needed.
208
     *
209
     * Example:
210
     * ```php
211
     * $builder->whereColumn('initials', function(DimensionBuilder $dimensionBuilder) {
212
     *   $dimensionBuilder->select('first_name', function(ExtractionBuilder $extractionBuilder) {
213
     *     $extractionBuilder->substring(0, 1);
214
     *   });
215
     * });
216
     * ```
217
     *
218
     * @param string|Closure $dimensionA The dimension which you want to compare, or a Closure which will receive a
219
     *                                   DimensionBuilder which allows you to select a dimension in a more advance way.
220
     * @param string|Closure $dimensionB The dimension which you want to compare, or a Closure which will receive a
221
     *                                   DimensionBuilder which allows you to select a dimension in a more advance way.
222
     * @param string         $boolean    This influences how this filter will be joined with previous added filters.
223
     *                                   Should both filters apply ("and") or one or the other ("or") ? Default is
224
     *                                   "and".
225
     *
226
     * @return $this
227
     */
228 1
    public function whereColumn($dimensionA, $dimensionB, string $boolean = 'and')
229
    {
230 1
        $filter = new ColumnComparisonFilter(
231 1
            $this->columnCompareDimension($dimensionA),
232 1
            $this->columnCompareDimension($dimensionB)
233
        );
234
235 1
        return $this->where($filter, null, null, null, $boolean);
236
    }
237
238
    /**
239
     * Filter records where dimensionA is equal to dimensionB.
240
     * You can either supply a string or a Closure. The Closure will receive a DimensionBuilder object, which allows
241
     * you to select a dimension and apply extraction functions if needed.
242
     *
243
     * Example:
244
     * ```php
245
     * $builder->orWhereColumn('initials', function(DimensionBuilder $dimensionBuilder) {
246
     *   $dimensionBuilder->select('first_name', function(ExtractionBuilder $extractionBuilder) {
247
     *     $extractionBuilder->substring(0, 1);
248
     *   });
249
     * });
250
     * ```
251
     *
252
     * @param string|Closure $dimensionA The dimension which you want to compare, or a Closure which will receive a
253
     *                                   DimensionBuilder which allows you to select a dimension in a more advance way.
254
     * @param string|Closure $dimensionB The dimension which you want to compare, or a Closure which will receive a
255
     *                                   DimensionBuilder which allows you to select a dimension in a more advance way.
256
     *
257
     * @return $this
258
     */
259 1
    public function orWhereColumn($dimensionA, $dimensionB)
260
    {
261 1
        return $this->whereColumn($dimensionA, $dimensionB, 'or');
262
    }
263
264
    /**
265
     * Filter on records which match using a bitwise AND comparison.
266
     *
267
     * Due to the lack of support of a bitwise comparison by Druid, we have build our own variant.
268
     * A feature request has been opened already. See: https://github.com/apache/incubator-druid/issues/8560
269
     *
270
     * Only records will match where the dimension contains ALL bits which are also enabled in the given $flags
271
     * argument.
272
     *
273
     * Support for 64 bit integers are supported.
274
     *
275
     * NOTE:
276
     * Please note that javascript support is required for this method.
277
     * JavaScript-based functionality is disabled by default. Please refer to the Druid JavaScript programming guide
278
     * for guidelines about using Druid's JavaScript functionality, including instructions on how to enable it:
279
     * https://druid.apache.org/docs/latest/development/javascript.html
280
     *
281
     * @param string $dimension The dimension which contains int values where you want to do a bitwise AND check
282
     *                          against.
283
     * @param int    $flags     The bit's which you want to check if they are enabled in the given dimension.
284
     * @param string $boolean   This influences how this filter will be joined with previous added filters. Should both
285
     *                          filters apply ("and") or one or the other ("or") ? Default is "and".
286
     *
287
     * @return $this
288
     */
289 1
    public function whereFlags(string $dimension, int $flags, string $boolean = 'and')
290
    {
291
        return $this->where($dimension, '=', $flags, function (ExtractionBuilder $extraction) use ($flags) {
292
            // Do a binary "AND" flag comparison on a 64 bit int. The result will either be the
293
            // $flags, or 0 when it's bit is not set.
294 1
            $extraction->javascript('
295
                function(dimensionValue) { 
296 1
                    var givenValue = ' . $flags . '; 
297
                    var hi = 0x80000000; 
298
                    var low = 0x7fffffff; 
299
                    var hi1 = ~~(dimensionValue / hi); 
300
                    var hi2 = ~~(givenValue / hi); 
301
                    var low1 = dimensionValue & low; 
302
                    var low2 = givenValue & low; 
303
                    var h = hi1 & hi2; 
304
                    var l = low1 & low2; 
305
                    return (h*hi + l); 
306
                }
307
            ');
308 1
        }, $boolean);
309
    }
310
311
    /**
312
     * Filter on records which match using a bitwise AND comparison.
313
     *
314
     * Due to the lack of support of a bitwise comparison by Druid, we have build our own variant.
315
     * A feature request has been opened already. See: https://github.com/apache/incubator-druid/issues/8560
316
     *
317
     * Only records will match where the dimension contains ALL bits which are also enabled in the given $flags
318
     * argument.
319
     *
320
     * Support for 64 bit integers are supported.
321
     *
322
     * NOTE:
323
     * Please note that javascript support is required for this method.
324
     * JavaScript-based functionality is disabled by default. Please refer to the Druid JavaScript programming guide
325
     * for guidelines about using Druid's JavaScript functionality, including instructions on how to enable it:
326
     * https://druid.apache.org/docs/latest/development/javascript.html
327
     *
328
     * @param string $dimension The dimension which contains int values where you want to do a bitwise AND check
329
     *                          against.
330
     * @param int    $flags     The bit's which you want to check if they are enabled in the given dimension.
331
     *
332
     * @return $this
333
     */
334 1
    public function orWhereFlags(string $dimension, int $flags)
335
    {
336 1
        return $this->whereFlags($dimension, $flags, 'or');
337
    }
338
339
    /**
340
     * Filter records where dimensionA is NOT equal to dimensionB.
341
     * You can either supply a string or a Closure. The Closure will receive a DimensionBuilder object, which allows
342
     * you to select a dimension and apply extraction functions if needed.
343
     *
344
     * Example:
345
     * ```php
346
     * $builder->whereNotColumn('initials', function(DimensionBuilder $dimensionBuilder) {
347
     *   $dimensionBuilder->select('first_name', function(ExtractionBuilder $extractionBuilder) {
348
     *     $extractionBuilder->substring(0, 1);
349
     *   });
350
     * });
351
     * ```
352
     *
353
     * @param string|Closure $dimensionA The dimension which you want to compare, or a Closure which will receive a
354
     *                                   DimensionBuilder which allows you to select a dimension in a more advance way.
355
     * @param string|Closure $dimensionB The dimension which you want to compare, or a Closure which will receive a
356
     *                                   DimensionBuilder which allows you to select a dimension in a more advance way.
357
     * @param string         $boolean    This influences how this filter will be joined with previous added filters.
358
     *                                   Should both filters apply ("and") or one or the other ("or") ? Default is
359
     *                                   "and".
360
     *
361
     * @return $this
362
     */
363 1
    public function whereNotColumn($dimensionA, $dimensionB, string $boolean = 'and')
364
    {
365 1
        $filter = new ColumnComparisonFilter(
366 1
            $this->columnCompareDimension($dimensionA),
367 1
            $this->columnCompareDimension($dimensionB)
368
        );
369
370 1
        return $this->where(new NotFilter($filter), null, null, null, $boolean);
371
    }
372
373
    /**
374
     * Filter records where dimensionA is NOT equal to dimensionB.
375
     * You can either supply a string or a Closure. The Closure will receive a DimensionBuilder object, which allows
376
     * you to select a dimension and apply extraction functions if needed.
377
     *
378
     * Example:
379
     * ```php
380
     * $builder->orWhereNotColumn('initials', function(DimensionBuilder $dimensionBuilder) {
381
     *   $dimensionBuilder->select('first_name', function(ExtractionBuilder $extractionBuilder) {
382
     *     $extractionBuilder->substring(0, 1);
383
     *   });
384
     * });
385
     * ```
386
     *
387
     * @param string|Closure $dimensionA The dimension which you want to compare, or a Closure which will receive a
388
     *                                   DimensionBuilder which allows you to select a dimension in a more advance way.
389
     * @param string|Closure $dimensionB The dimension which you want to compare, or a Closure which will receive a
390
     *                                   DimensionBuilder which allows you to select a dimension in a more advance way.
391
     *
392
     * @return $this
393
     */
394 1
    public function orWhereNotColumn($dimensionA, $dimensionB)
395
    {
396 1
        return $this->whereNotColumn($dimensionA, $dimensionB, 'or');
397
    }
398
399
    /**
400
     * This filter will select records where the given dimension is greater than or equal to the given minValue, and
401
     * less than or equal to the given $maxValue.
402
     *
403
     * So in SQL syntax, this would be:
404
     * ```
405
     * WHERE dimension => $minValue AND dimension <= $maxValue
406
     * ```
407
     *
408
     * @param string        $dimension   The dimension which you want to filter
409
     * @param string|int    $minValue    The minimum value where the dimension should match. It should be equal or
410
     *                                   greater than this value.
411
     * @param string|int    $maxValue    The maximum value where the dimension should match. It should be less than
412
     *                                   this value.
413
     * @param \Closure|null $extraction  Extraction function to extract a different value from the dimension.
414
     * @param null|string   $ordering    Specifies the sorting order to use when comparing values against the
415
     *                                   between filter. Can be one of the following values: "lexicographic",
416
     *                                   "alphanumeric", "numeric", "strlen", "version". See Sorting Orders for
417
     *                                   more details. By default it will be "numeric" if the values are
418
     *                                   numeric, otherwise it will be "lexicographic"
419
     * @param string        $boolean     This influences how this filter will be joined with previous added filters.
420
     *                                   Should both filters apply ("and") or one or the other ("or") ? Default is
421
     *                                   "and".
422
     *
423
     * @return $this
424
     */
425 1
    public function whereBetween(
426
        string $dimension,
427
        $minValue,
428
        $maxValue,
429
        Closure $extraction = null,
430
        string $ordering = null,
431
        string $boolean = 'and'
432
    ) {
433 1
        $filter = new BetweenFilter($dimension, $minValue, $maxValue, $ordering, $this->getExtraction($extraction));
434
435 1
        return $this->where($filter, null, null, null, $boolean);
436
    }
437
438
    /**
439
     * This filter will select records where the given dimension is greater than or equal to the given minValue, and
440
     * less than or equal to the given $maxValue.
441
     *
442
     * This method will join previous added filters with an "or" instead of an "and".
443
     *
444
     * So in SQL syntax, this would be:
445
     * ```
446
     * WHERE (dimension => $minValue AND dimension <= $maxValue) or .... (other filters here)
447
     * ```
448
     *
449
     * @param string        $dimension   The dimension which you want to filter
450
     * @param string|int    $minValue    The minimum value where the dimension should match. It should be equal or
451
     *                                   greater than this value.
452
     * @param string|int    $maxValue    The maximum value where the dimension should match. It should be less than
453
     *                                   this value.
454
     * @param \Closure|null $extraction  Extraction function to extract a different value from the dimension.
455
     * @param null|string   $ordering    Specifies the sorting order to use when comparing values against the
456
     *                                   between filter. Can be one of the following values: "lexicographic",
457
     *                                   "alphanumeric", "numeric", "strlen", "version". See Sorting Orders for
458
     *                                   more details. By default it will be "numeric" if the values are
459
     *                                   numeric, otherwise it will be "lexicographic"
460
     *
461
     * @return $this
462
     */
463 1
    public function orWhereBetween(
464
        string $dimension,
465
        $minValue,
466
        $maxValue,
467
        Closure $extraction = null,
468
        string $ordering = null
469
    ) {
470 1
        return $this->whereBetween($dimension, $minValue, $maxValue, $extraction, $ordering, 'or');
471
    }
472
473
    /**
474
     * This filter will select records where the given dimension is NOT between the given min and max value.
475
     *
476
     * So in SQL syntax, this would be:
477
     * ```
478
     * WHERE dimension < $minValue AND dimension > $maxValue
479
     * ```
480
     *
481
     * @param string        $dimension   The dimension which you want to filter
482
     * @param string|int    $minValue    The minimum value where the dimension should NOT match. It should be equal or
483
     *                                   greater than this value.
484
     * @param string|int    $maxValue    The maximum value where the dimension should NOT match. It should be less than
485
     *                                   this value.
486
     * @param \Closure|null $extraction  Extraction function to extract a different value from the dimension.
487
     * @param null|string   $ordering    Specifies the sorting order to use when comparing values against the
488
     *                                   between filter. Can be one of the following values: "lexicographic",
489
     *                                   "alphanumeric", "numeric", "strlen", "version". See Sorting Orders for
490
     *                                   more details. By default it will be "numeric" if the values are
491
     *                                   numeric, otherwise it will be "lexicographic"
492
     * @param string        $boolean     This influences how this filter will be joined with previous added filters.
493
     *                                   Should both filters apply ("and") or one or the other ("or") ? Default is
494
     *                                   "and".
495
     *
496
     * @return $this
497
     */
498 1
    public function whereNotBetween(
499
        string $dimension,
500
        $minValue,
501
        $maxValue,
502
        Closure $extraction = null,
503
        string $ordering = null,
504
        string $boolean = 'and'
505
    ) {
506 1
        $filter = new BetweenFilter($dimension, $minValue, $maxValue, $ordering, $this->getExtraction($extraction));
507
508 1
        return $this->where(new NotFilter($filter), null, null, null, $boolean);
509
    }
510
511
    /**
512
     * This filter will select records where the given dimension is NOT between the given min and max value.
513
     *
514
     * So in SQL syntax, this would be:
515
     * ```
516
     * WHERE (dimension < $minValue AND dimension > $maxValue) or  .... (other filters here)
517
     * ```
518
     *
519
     * @param string        $dimension   The dimension which you want to filter
520
     * @param string|int    $minValue    The minimum value where the dimension should NOT match. It should be equal or
521
     *                                   greater than this value.
522
     * @param string|int    $maxValue    The maximum value where the dimension should NOT match. It should be less than
523
     *                                   this value.
524
     * @param \Closure|null $extraction  Extraction function to extract a different value from the dimension.
525
     * @param null|string   $ordering    Specifies the sorting order to use when comparing values against the
526
     *                                   between filter. Can be one of the following values: "lexicographic",
527
     *                                   "alphanumeric", "numeric", "strlen", "version". See Sorting Orders for
528
     *                                   more details. By default it will be "numeric" if the values are
529
     *                                   numeric, otherwise it will be "lexicographic"
530
     *
531
     * @return $this
532
     */
533 1
    public function orWhereNotBetween(
534
        string $dimension,
535
        $minValue,
536
        $maxValue,
537
        Closure $extraction = null,
538
        string $ordering = null
539
    ) {
540 1
        return $this->whereNotBetween($dimension, $minValue, $maxValue, $extraction, $ordering, 'or');
541
    }
542
543
    /**
544
     * Filter records where the given dimension NOT exists in the given list of items
545
     *
546
     * @param string        $dimension  The dimension which you want to filter
547
     * @param array         $items      A list of values. We will return records where the dimension is NOT in this
548
     *                                  list.
549
     * @param \Closure|null $extraction An extraction function to extract a different value from the dimension.
550
     * @param string        $boolean    This influences how this filter will be joined with previous added filters.
551
     *                                  Should both filters apply ("and") or one or the other ("or") ? Default is
552
     *                                  "and".
553
     *
554
     * @return $this
555
     */
556 1
    public function whereNotIn(string $dimension, array $items, Closure $extraction = null, string $boolean = 'and')
557
    {
558 1
        $filter = new NotFilter(new InFilter($dimension, $items, $this->getExtraction($extraction)));
559
560 1
        return $this->where($filter, null, null, null, $boolean);
561
    }
562
563
    /**
564
     * Filter records where the given dimension NOT exists in the given list of items
565
     *
566
     * @param string        $dimension  The dimension which you want to filter
567
     * @param array         $items      A list of values. We will return records where the dimension is NOT in this
568
     *                                  list.
569
     * @param \Closure|null $extraction An extraction function to extract a different value from the dimension.
570
     *
571
     * @return $this
572
     */
573 1
    public function orWhereNotIn(string $dimension, array $items, Closure $extraction = null)
574
    {
575 1
        return $this->whereNotIn($dimension, $items, $extraction, 'or');
576
    }
577
578
    /**
579
     * Filter on an dimension where the value does NOT exists in the given intervals array.
580
     *
581
     * The intervals array can contain the following:
582
     * - Only 2 elements, start and stop.
583
     * - an Interval object
584
     * - an raw interval string as used in druid. For example: 2019-04-15T08:00:00.000Z/2019-04-15T09:00:00.000Z
585
     * - an array which each contain 2 elements, a start and stop date. These can be an DateTime object, a unix
586
     * timestamp or anything which can be parsed by DateTime::__construct
587
     *
588
     * So valid are:
589
     * ['now', 'tomorrow']
590
     * [['now', 'now + 1 hour'], ['tomorrow', 'tomorrow + 1 hour']]
591
     * ['2019-04-15T08:00:00.000Z/2019-04-15T09:00:00.000Z']
592
     *
593
     * @param string        $dimension  The dimension which you want to filter
594
     * @param array         $intervals  The interval which you do not want to match. See above for more info.
595
     * @param \Closure|null $extraction Extraction function to extract a different value from the dimension.
596
     * @param string        $boolean    This influences how this filter will be joined with previous added filters.
597
     *                                  Should both filters apply ("and") or one or the other ("or") ? Default is
598
     *                                  "and".
599
     *
600
     * @return $this
601
     */
602 1
    public function whereNotInterval(
603
        string $dimension,
604
        array $intervals,
605
        Closure $extraction = null,
606
        string $boolean = 'and'
607
    ) {
608 1
        $filter = new IntervalFilter(
609 1
            $dimension,
610 1
            $this->normalizeIntervals($intervals),
611 1
            $this->getExtraction($extraction)
612
        );
613
614 1
        return $this->where(new NotFilter($filter), null, null, null, $boolean);
615
    }
616
617
    /**
618
     * Filter on an dimension where the value does NOT exists in the given intervals array.
619
     *
620
     * The intervals array can contain the following:
621
     * - Only 2 elements, start and stop.
622
     * - an Interval object
623
     * - an raw interval string as used in druid. For example: 2019-04-15T08:00:00.000Z/2019-04-15T09:00:00.000Z
624
     * - an array which each contain 2 elements, a start and stop date. These can be an DateTime object, a unix
625
     * timestamp or anything which can be parsed by DateTime::__construct
626
     *
627
     * So valid are:
628
     * ['now', 'tomorrow']
629
     * [['now', 'now + 1 hour'], ['tomorrow', 'tomorrow + 1 hour']]
630
     * ['2019-04-15T08:00:00.000Z/2019-04-15T09:00:00.000Z']
631
     *
632
     * @param string        $dimension  The dimension which you want to filter
633
     * @param array         $intervals  The interval which you do not want to match. See above for more info.
634
     * @param \Closure|null $extraction Extraction function to extract a different value from the dimension.
635
     *
636
     * @return $this
637
     */
638 1
    public function orWhereNotInterval(string $dimension, array $intervals, Closure $extraction = null)
639
    {
640 1
        return $this->whereNotInterval($dimension, $intervals, $extraction, 'or');
641
    }
642
643
    /**
644
     * Filter on an dimension where the value exists in the given intervals array.
645
     *
646
     * The intervals array can contain the following:
647
     * - an Interval object
648
     * - an raw interval string as used in druid. For example: 2019-04-15T08:00:00.000Z/2019-04-15T09:00:00.000Z
649
     * - an array which contains 2 elements, a start and stop date. These can be an DateTime object, a unix timestamp
650
     *   or anything which can be parsed by DateTime::__construct
651
     *
652
     * @param string        $dimension  The dimension which you want to filter
653
     * @param array         $intervals  The interval which you want to match. See above for more info.
654
     * @param \Closure|null $extraction Extraction function to extract a different value from the dimension.
655
     * @param string        $boolean    This influences how this filter will be joined with previous added filters.
656
     *                                  Should both filters apply ("and") or one or the other ("or") ? Default is
657
     *                                  "and".
658
     *
659
     * @return $this
660
     */
661 1
    public function whereInterval(
662
        string $dimension,
663
        array $intervals,
664
        Closure $extraction = null,
665
        string $boolean = 'and'
666
    ) {
667 1
        $filter = new IntervalFilter(
668 1
            $dimension,
669 1
            $this->normalizeIntervals($intervals),
670 1
            $this->getExtraction($extraction)
671
        );
672
673 1
        return $this->where($filter, null, null, null, $boolean);
674
    }
675
676
    /**
677
     * Filter on an dimension where the value exists in the given intervals array.
678
     *
679
     * The intervals array can contain the following:
680
     * - an Interval object
681
     * - an raw interval string as used in druid. For example: 2019-04-15T08:00:00.000Z/2019-04-15T09:00:00.000Z
682
     * - an array which contains 2 elements, a start and stop date. These can be an DateTime object, a unix timestamp
683
     *   or anything which can be parsed by DateTime::__construct
684
     *
685
     * @param string        $dimension  The dimension which you want to filter
686
     * @param array         $intervals  The interval which you want to match. See above for more info.
687
     * @param \Closure|null $extraction Extraction function to extract a different value from the dimension.
688
     *
689
     * @return $this
690
     */
691 1
    public function orWhereInterval(string $dimension, array $intervals, Closure $extraction = null)
692
    {
693 1
        return $this->whereInterval($dimension, $intervals, $extraction, 'or');
694
    }
695
696
    /**
697
     * Normalize the given dimension to a DimensionInterface object.
698
     *
699
     * @param string|Closure $dimension
700
     *
701
     * @return \Level23\Druid\Dimensions\DimensionInterface
702
     * @throws InvalidArgumentException
703
     */
704 6
    protected function columnCompareDimension($dimension): DimensionInterface
705
    {
706 6
        if (is_string($dimension)) {
707 3
            return new Dimension($dimension);
708
        }
709 3
        if ($dimension instanceof Closure) {
710 2
            $builder = new DimensionBuilder();
711 2
            call_user_func($dimension, $builder);
712 2
            $dimensions = $builder->getDimensions();
713
714 2
            if (count($dimensions) != 1) {
715 1
                throw new InvalidArgumentException('Your dimension builder should select 1 dimension');
716
            }
717
718
            /** @var \Level23\Druid\Dimensions\DimensionInterface $dimensionA */
719 1
            return $dimensions[0];
720
        }
721
722 1
        throw new InvalidArgumentException(
723 1
            'You need to supply either a string (the dimension) or a Closure which will receive a DimensionBuilder.'
724
        );
725
    }
726
727
    /**
728
     * Normalize the given intervals into Interval objects.
729
     *
730
     * @param array $intervals
731
     *
732
     * @return array
733
     */
734 11
    protected function normalizeIntervals(array $intervals): array
735
    {
736 11
        $first = reset($intervals);
737
738
        // If first is an array or already a druid interval string or object we do not wrap it in an array
739 11
        if (!is_array($first) && !$this->isDruidInterval($first)) {
740 8
            $intervals = [$intervals];
741
        }
742
743
        return array_map(function ($interval) {
744
745 11
            if ($interval instanceof IntervalInterface) {
746 1
                return $interval;
747
            }
748
749
            // If it is a string we explode it into to elements
750 10
            if (is_string($interval)) {
751 1
                $interval = explode('/', $interval);
752
            }
753
754
            // If the value is an array and is not empty and has either one or 2 values its an interval array
755 10
            if (is_array($interval) && !empty(array_filter($interval)) && count($interval) < 3) {
756
                /** @scrutinizer ignore-type */
757 7
                return new Interval(...$interval);
758
            }
759
760 3
            throw new InvalidArgumentException(
761
                'Invalid type given in the interval array. We cannot process ' .
762 3
                var_export($interval, true)
763
            );
764 11
        }, $intervals);
765
    }
766
767
    /**
768
     * Returns true if the argument provided is a druid interval string or interface
769
     *
770
     * @param string|IntervalInterface $interval
771
     *
772
     * @return bool
773
     */
774 10
    protected function isDruidInterval($interval)
775
    {
776 10
        if ($interval instanceof IntervalInterface) {
777 1
            return true;
778
        }
779
780 9
        return is_string($interval) && strpos($interval, '/') !== false;
781
    }
782
783
    /**
784
     * Helper method to add an OR filter
785
     *
786
     * @param FilterInterface $filter
787
     */
788 4
    protected function addOrFilter(FilterInterface $filter): void
789
    {
790 4
        if (!$this->filter instanceof FilterInterface) {
791 4
            $this->filter = $filter;
792
793 4
            return;
794
        }
795
796 4
        if ($this->filter instanceof OrFilter) {
797 1
            $this->filter->addFilter($filter);
798
799 1
            return;
800
        }
801
802 4
        $this->filter = new OrFilter([$this->filter, $filter]);
803 4
    }
804
805
    /**
806
     * Helper method to add an AND filter
807
     *
808
     * @param FilterInterface $filter
809
     */
810 37
    protected function addAndFilter(FilterInterface $filter): void
811
    {
812 37
        if (!$this->filter instanceof FilterInterface) {
813 37
            $this->filter = $filter;
814
815 37
            return;
816
        }
817
818 20
        if ($this->filter instanceof AndFilter) {
819 2
            $this->filter->addFilter($filter);
820
821 2
            return;
822
        }
823
824 20
        $this->filter = new AndFilter([$this->filter, $filter]);
825 20
    }
826
827
    /**
828
     * @return \Level23\Druid\Filters\FilterInterface|null
829
     */
830 28
    public function getFilter()
831
    {
832 28
        return $this->filter;
833
    }
834
835
    /**
836
     * @param \Closure|null $extraction
837
     *
838
     * @return \Level23\Druid\Extractions\ExtractionInterface|null
839
     */
840 37
    private function getExtraction(?Closure $extraction): ?ExtractionInterface
841
    {
842 37
        if (empty($extraction)) {
843 36
            return null;
844
        }
845
846 1
        $builder = new ExtractionBuilder();
847 1
        call_user_func($extraction, $builder);
848
849 1
        return $builder->getExtraction();
850
    }
851
}