Completed
Push — master ( 77c668...454664 )
by Teye
21:32 queued 09:24
created

HasFilter::whereNot()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 4
c 0
b 0
f 0
dl 0
loc 12
ccs 5
cts 5
cp 1
rs 10
cc 1
nc 1
nop 2
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 49
    public function where(
61
        $filterOrDimensionOrClosure,
62
        $operator = null,
63
        $value = null,
64
        Closure $extraction = null,
65
        string $boolean = 'and'
66
    ) {
67 49
        $filter = null;
68 49
        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 16
        } elseif ($filterOrDimensionOrClosure instanceof FilterInterface) {
130 12
            $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 47
        if ($filter === null) {
144 5
            throw new InvalidArgumentException('The arguments which you have supplied cannot be parsed.');
145
        }
146
147 42
        strtolower($boolean) == 'and' ? $this->addAndFilter($filter) : $this->addOrFilter($filter);
148
149 42
        return $this;
150
    }
151
152
    /**
153
     * Build a where selection which is inverted
154
     *
155
     * @param \Closure $filterBuilder A closure which will receive a FilterBuilder instance.
156
     * @param string   $boolean       This influences how this filter will be joined with previous added filters.
157
     *                                Should both filters apply ("and") or one or the other ("or") ? Default is "and".
158
     *
159
     * @return $this
160
     */
161 1
    public function whereNot(Closure $filterBuilder, $boolean = 'and')
162
    {
163
        // lets create a bew builder object where the user can mess around with
164 1
        $builder = new FilterBuilder();
165
166
        // call the user function
167 1
        call_user_func($filterBuilder, $builder);
168
169
        // Now retrieve the filter which was created and add it to our current filter set.
170 1
        $filter = $builder->getFilter();
171
172 1
        return $this->where(new NotFilter($filter), null, null, null, $boolean);
0 ignored issues
show
Bug introduced by
It seems like $filter can also be of type null; however, parameter $filter of Level23\Druid\Filters\NotFilter::__construct() does only seem to accept Level23\Druid\Filters\FilterInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

172
        return $this->where(new NotFilter(/** @scrutinizer ignore-type */ $filter), null, null, null, $boolean);
Loading history...
173
    }
174
175
    /**
176
     * Build a where selection which is inverted
177
     *
178
     * @param \Closure $filterBuilder A closure which will receive a FilterBuilder instance.
179
     *
180
     * @return $this
181
     */
182 1
    public function orWhereNot(Closure $filterBuilder)
183
    {
184 1
        return $this->whereNot($filterBuilder, 'or');
185
    }
186
187
    /**
188
     * This applies a filter, only it will join previous added filters with an "or" instead of an "and".
189
     * See the documentation of the "where" method for more information
190
     *
191
     * @param string|FilterInterface $filterOrDimension
192
     * @param string|null            $operator
193
     * @param mixed|null             $value
194
     * @param \Closure|null          $extraction
195
     *
196
     * @return $this
197
     * @see \Level23\Druid\Concerns\HasFilter::where()
198
     */
199 1
    public function orWhere($filterOrDimension, $operator = null, $value = null, Closure $extraction = null)
200
    {
201 1
        return $this->where($filterOrDimension, $operator, $value, $extraction, 'or');
202
    }
203
204
    /**
205
     * Filter records where the given dimension exists in the given list of items
206
     *
207
     * @param string        $dimension  The dimension which you want to filte
208
     * @param array         $items      A list of values. We will return records where the dimension is in this list.
209
     * @param \Closure|null $extraction An extraction function to extract a different value from the dimension.
210
     * @param string        $boolean    This influences how this filter will be joined with previous added filters.
211
     *                                  Should both filters apply ("and") or one or the other ("or") ? Default is
212
     *                                  "and".
213
     *
214
     * @return $this
215
     */
216 3
    public function whereIn(string $dimension, array $items, Closure $extraction = null, $boolean = 'and')
217
    {
218 3
        $filter = new InFilter($dimension, $items, $this->getExtraction($extraction));
219
220 3
        return $this->where($filter, null, null, null, $boolean);
221
    }
222
223
    /**
224
     * Filter records where the given dimension exists in the given list of items.
225
     *
226
     * If there are previously defined filters, this filter will be joined with an "or".
227
     *
228
     * @param string        $dimension  The dimension which you want to filte
229
     * @param array         $items      A list of values. We will return records where the dimension is in this list.
230
     * @param \Closure|null $extraction An extraction function to extract a different value from the dimension.
231
     *
232
     * @return $this
233
     */
234 1
    public function orWhereIn(string $dimension, array $items, Closure $extraction = null)
235
    {
236 1
        return $this->whereIn($dimension, $items, $extraction, 'or');
237
    }
238
239
    /**
240
     * Filter records where dimensionA is equal to dimensionB.
241
     * You can either supply a string or a Closure. The Closure will receive a DimensionBuilder object, which allows
242
     * you to select a dimension and apply extraction functions if needed.
243
     *
244
     * Example:
245
     * ```php
246
     * $builder->whereColumn('initials', function(DimensionBuilder $dimensionBuilder) {
247
     *   $dimensionBuilder->select('first_name', function(ExtractionBuilder $extractionBuilder) {
248
     *     $extractionBuilder->substring(0, 1);
249
     *   });
250
     * });
251
     * ```
252
     *
253
     * @param string|Closure $dimensionA The dimension which you want to compare, or a Closure which will receive a
254
     *                                   DimensionBuilder which allows you to select a dimension in a more advance way.
255
     * @param string|Closure $dimensionB The dimension which you want to compare, or a Closure which will receive a
256
     *                                   DimensionBuilder which allows you to select a dimension in a more advance way.
257
     * @param string         $boolean    This influences how this filter will be joined with previous added filters.
258
     *                                   Should both filters apply ("and") or one or the other ("or") ? Default is
259
     *                                   "and".
260
     *
261
     * @return $this
262
     */
263 1
    public function whereColumn($dimensionA, $dimensionB, string $boolean = 'and')
264
    {
265 1
        $filter = new ColumnComparisonFilter(
266 1
            $this->columnCompareDimension($dimensionA),
267 1
            $this->columnCompareDimension($dimensionB)
268
        );
269
270 1
        return $this->where($filter, null, null, null, $boolean);
271
    }
272
273
    /**
274
     * Filter records where dimensionA is equal to dimensionB.
275
     * You can either supply a string or a Closure. The Closure will receive a DimensionBuilder object, which allows
276
     * you to select a dimension and apply extraction functions if needed.
277
     *
278
     * Example:
279
     * ```php
280
     * $builder->orWhereColumn('initials', function(DimensionBuilder $dimensionBuilder) {
281
     *   $dimensionBuilder->select('first_name', function(ExtractionBuilder $extractionBuilder) {
282
     *     $extractionBuilder->substring(0, 1);
283
     *   });
284
     * });
285
     * ```
286
     *
287
     * @param string|Closure $dimensionA The dimension which you want to compare, or a Closure which will receive a
288
     *                                   DimensionBuilder which allows you to select a dimension in a more advance way.
289
     * @param string|Closure $dimensionB The dimension which you want to compare, or a Closure which will receive a
290
     *                                   DimensionBuilder which allows you to select a dimension in a more advance way.
291
     *
292
     * @return $this
293
     */
294 1
    public function orWhereColumn($dimensionA, $dimensionB)
295
    {
296 1
        return $this->whereColumn($dimensionA, $dimensionB, 'or');
297
    }
298
299
    /**
300
     * Filter on records which match using a bitwise AND comparison.
301
     *
302
     * Due to the lack of support of a bitwise comparison by Druid, we have build our own variant.
303
     * A feature request has been opened already. See: https://github.com/apache/incubator-druid/issues/8560
304
     *
305
     * Only records will match where the dimension contains ALL bits which are also enabled in the given $flags
306
     * argument.
307
     *
308
     * Support for 64 bit integers are supported.
309
     *
310
     * NOTE:
311
     * Please note that javascript support is required for this method.
312
     * JavaScript-based functionality is disabled by default. Please refer to the Druid JavaScript programming guide
313
     * for guidelines about using Druid's JavaScript functionality, including instructions on how to enable it:
314
     * https://druid.apache.org/docs/latest/development/javascript.html
315
     *
316
     * @param string $dimension The dimension which contains int values where you want to do a bitwise AND check
317
     *                          against.
318
     * @param int    $flags     The bit's which you want to check if they are enabled in the given dimension.
319
     * @param string $boolean   This influences how this filter will be joined with previous added filters. Should both
320
     *                          filters apply ("and") or one or the other ("or") ? Default is "and".
321
     *
322
     * @return $this
323
     */
324 1
    public function whereFlags(string $dimension, int $flags, string $boolean = 'and')
325
    {
326
        return $this->where($dimension, '=', $flags, function (ExtractionBuilder $extraction) use ($flags) {
327
            // Do a binary "AND" flag comparison on a 64 bit int. The result will either be the
328
            // $flags, or 0 when it's bit is not set.
329 1
            $extraction->javascript('
330
                function(dimensionValue) { 
331 1
                    var givenValue = ' . $flags . '; 
332
                    var hi = 0x80000000; 
333
                    var low = 0x7fffffff; 
334
                    var hi1 = ~~(dimensionValue / hi); 
335
                    var hi2 = ~~(givenValue / hi); 
336
                    var low1 = dimensionValue & low; 
337
                    var low2 = givenValue & low; 
338
                    var h = hi1 & hi2; 
339
                    var l = low1 & low2; 
340
                    return (h*hi + l); 
341
                }
342
            ');
343 1
        }, $boolean);
344
    }
345
346
    /**
347
     * Filter on records which match using a bitwise AND comparison.
348
     *
349
     * Due to the lack of support of a bitwise comparison by Druid, we have build our own variant.
350
     * A feature request has been opened already. See: https://github.com/apache/incubator-druid/issues/8560
351
     *
352
     * Only records will match where the dimension contains ALL bits which are also enabled in the given $flags
353
     * argument.
354
     *
355
     * Support for 64 bit integers are supported.
356
     *
357
     * NOTE:
358
     * Please note that javascript support is required for this method.
359
     * JavaScript-based functionality is disabled by default. Please refer to the Druid JavaScript programming guide
360
     * for guidelines about using Druid's JavaScript functionality, including instructions on how to enable it:
361
     * https://druid.apache.org/docs/latest/development/javascript.html
362
     *
363
     * @param string $dimension The dimension which contains int values where you want to do a bitwise AND check
364
     *                          against.
365
     * @param int    $flags     The bit's which you want to check if they are enabled in the given dimension.
366
     *
367
     * @return $this
368
     */
369 1
    public function orWhereFlags(string $dimension, int $flags)
370
    {
371 1
        return $this->whereFlags($dimension, $flags, 'or');
372
    }
373
374
    /**
375
     * Filter records where dimensionA is NOT equal to dimensionB.
376
     * You can either supply a string or a Closure. The Closure will receive a DimensionBuilder object, which allows
377
     * you to select a dimension and apply extraction functions if needed.
378
     *
379
     * Example:
380
     * ```php
381
     * $builder->whereNotColumn('initials', function(DimensionBuilder $dimensionBuilder) {
382
     *   $dimensionBuilder->select('first_name', function(ExtractionBuilder $extractionBuilder) {
383
     *     $extractionBuilder->substring(0, 1);
384
     *   });
385
     * });
386
     * ```
387
     *
388
     * @param string|Closure $dimensionA The dimension which you want to compare, or a Closure which will receive a
389
     *                                   DimensionBuilder which allows you to select a dimension in a more advance way.
390
     * @param string|Closure $dimensionB The dimension which you want to compare, or a Closure which will receive a
391
     *                                   DimensionBuilder which allows you to select a dimension in a more advance way.
392
     * @param string         $boolean    This influences how this filter will be joined with previous added filters.
393
     *                                   Should both filters apply ("and") or one or the other ("or") ? Default is
394
     *                                   "and".
395
     *
396
     * @return $this
397
     * @deprecated Use the whereNot() method combined with a whereColumn instead.
398
     */
399 1
    public function whereNotColumn($dimensionA, $dimensionB, string $boolean = 'and')
400
    {
401 1
        $filter = new ColumnComparisonFilter(
402 1
            $this->columnCompareDimension($dimensionA),
403 1
            $this->columnCompareDimension($dimensionB)
404
        );
405
406 1
        return $this->where(new NotFilter($filter), null, null, null, $boolean);
407
    }
408
409
    /**
410
     * Filter records where dimensionA is NOT equal to dimensionB.
411
     * You can either supply a string or a Closure. The Closure will receive a DimensionBuilder object, which allows
412
     * you to select a dimension and apply extraction functions if needed.
413
     *
414
     * Example:
415
     * ```php
416
     * $builder->orWhereNotColumn('initials', function(DimensionBuilder $dimensionBuilder) {
417
     *   $dimensionBuilder->select('first_name', function(ExtractionBuilder $extractionBuilder) {
418
     *     $extractionBuilder->substring(0, 1);
419
     *   });
420
     * });
421
     * ```
422
     *
423
     * @param string|Closure $dimensionA The dimension which you want to compare, or a Closure which will receive a
424
     *                                   DimensionBuilder which allows you to select a dimension in a more advance way.
425
     * @param string|Closure $dimensionB The dimension which you want to compare, or a Closure which will receive a
426
     *                                   DimensionBuilder which allows you to select a dimension in a more advance way.
427
     *
428
     * @return $this
429
     */
430 1
    public function orWhereNotColumn($dimensionA, $dimensionB)
431
    {
432 1
        return $this->whereNotColumn($dimensionA, $dimensionB, 'or');
0 ignored issues
show
Deprecated Code introduced by
The function Level23\Druid\Concerns\HasFilter::whereNotColumn() has been deprecated: Use the whereNot() method combined with a whereColumn instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

432
        return /** @scrutinizer ignore-deprecated */ $this->whereNotColumn($dimensionA, $dimensionB, 'or');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
433
    }
434
435
    /**
436
     * This filter will select records where the given dimension is greater than or equal to the given minValue, and
437
     * less than or equal to the given $maxValue.
438
     *
439
     * So in SQL syntax, this would be:
440
     * ```
441
     * WHERE dimension => $minValue AND dimension <= $maxValue
442
     * ```
443
     *
444
     * @param string        $dimension   The dimension which you want to filter
445
     * @param string|int    $minValue    The minimum value where the dimension should match. It should be equal or
446
     *                                   greater than this value.
447
     * @param string|int    $maxValue    The maximum value where the dimension should match. It should be less than
448
     *                                   this value.
449
     * @param \Closure|null $extraction  Extraction function to extract a different value from the dimension.
450
     * @param null|string   $ordering    Specifies the sorting order to use when comparing values against the
451
     *                                   between filter. Can be one of the following values: "lexicographic",
452
     *                                   "alphanumeric", "numeric", "strlen", "version". See Sorting Orders for
453
     *                                   more details. By default it will be "numeric" if the values are
454
     *                                   numeric, otherwise it will be "lexicographic"
455
     * @param string        $boolean     This influences how this filter will be joined with previous added filters.
456
     *                                   Should both filters apply ("and") or one or the other ("or") ? Default is
457
     *                                   "and".
458
     *
459
     * @return $this
460
     */
461 1
    public function whereBetween(
462
        string $dimension,
463
        $minValue,
464
        $maxValue,
465
        Closure $extraction = null,
466
        string $ordering = null,
467
        string $boolean = 'and'
468
    ) {
469 1
        $filter = new BetweenFilter($dimension, $minValue, $maxValue, $ordering, $this->getExtraction($extraction));
470
471 1
        return $this->where($filter, null, null, null, $boolean);
472
    }
473
474
    /**
475
     * This filter will select records where the given dimension is greater than or equal to the given minValue, and
476
     * less than or equal to the given $maxValue.
477
     *
478
     * This method will join previous added filters with an "or" instead of an "and".
479
     *
480
     * So in SQL syntax, this would be:
481
     * ```
482
     * WHERE (dimension => $minValue AND dimension <= $maxValue) or .... (other filters here)
483
     * ```
484
     *
485
     * @param string        $dimension   The dimension which you want to filter
486
     * @param string|int    $minValue    The minimum value where the dimension should match. It should be equal or
487
     *                                   greater than this value.
488
     * @param string|int    $maxValue    The maximum value where the dimension should match. It should be less than
489
     *                                   this value.
490
     * @param \Closure|null $extraction  Extraction function to extract a different value from the dimension.
491
     * @param null|string   $ordering    Specifies the sorting order to use when comparing values against the
492
     *                                   between filter. Can be one of the following values: "lexicographic",
493
     *                                   "alphanumeric", "numeric", "strlen", "version". See Sorting Orders for
494
     *                                   more details. By default it will be "numeric" if the values are
495
     *                                   numeric, otherwise it will be "lexicographic"
496
     *
497
     * @return $this
498
     */
499 1
    public function orWhereBetween(
500
        string $dimension,
501
        $minValue,
502
        $maxValue,
503
        Closure $extraction = null,
504
        string $ordering = null
505
    ) {
506 1
        return $this->whereBetween($dimension, $minValue, $maxValue, $extraction, $ordering, 'or');
507
    }
508
509
    /**
510
     * This filter will select records where the given dimension is NOT between the given min and max value.
511
     *
512
     * So in SQL syntax, this would be:
513
     * ```
514
     * WHERE dimension < $minValue AND dimension > $maxValue
515
     * ```
516
     *
517
     * @param string        $dimension   The dimension which you want to filter
518
     * @param string|int    $minValue    The minimum value where the dimension should NOT match. It should be equal or
519
     *                                   greater than this value.
520
     * @param string|int    $maxValue    The maximum value where the dimension should NOT match. It should be less than
521
     *                                   this value.
522
     * @param \Closure|null $extraction  Extraction function to extract a different value from the dimension.
523
     * @param null|string   $ordering    Specifies the sorting order to use when comparing values against the
524
     *                                   between filter. Can be one of the following values: "lexicographic",
525
     *                                   "alphanumeric", "numeric", "strlen", "version". See Sorting Orders for
526
     *                                   more details. By default it will be "numeric" if the values are
527
     *                                   numeric, otherwise it will be "lexicographic"
528
     * @param string        $boolean     This influences how this filter will be joined with previous added filters.
529
     *                                   Should both filters apply ("and") or one or the other ("or") ? Default is
530
     *                                   "and".
531
     *
532
     * @return $this
533
     * @deprecated Use the whereNot() method combined with a whereBetween instead.
534
     */
535 1
    public function whereNotBetween(
536
        string $dimension,
537
        $minValue,
538
        $maxValue,
539
        Closure $extraction = null,
540
        string $ordering = null,
541
        string $boolean = 'and'
542
    ) {
543 1
        $filter = new BetweenFilter($dimension, $minValue, $maxValue, $ordering, $this->getExtraction($extraction));
544
545 1
        return $this->where(new NotFilter($filter), null, null, null, $boolean);
546
    }
547
548
    /**
549
     * This filter will select records where the given dimension is NOT between the given min and max value.
550
     *
551
     * So in SQL syntax, this would be:
552
     * ```
553
     * WHERE (dimension < $minValue AND dimension > $maxValue) or  .... (other filters here)
554
     * ```
555
     *
556
     * @param string        $dimension   The dimension which you want to filter
557
     * @param string|int    $minValue    The minimum value where the dimension should NOT match. It should be equal or
558
     *                                   greater than this value.
559
     * @param string|int    $maxValue    The maximum value where the dimension should NOT match. It should be less than
560
     *                                   this value.
561
     * @param \Closure|null $extraction  Extraction function to extract a different value from the dimension.
562
     * @param null|string   $ordering    Specifies the sorting order to use when comparing values against the
563
     *                                   between filter. Can be one of the following values: "lexicographic",
564
     *                                   "alphanumeric", "numeric", "strlen", "version". See Sorting Orders for
565
     *                                   more details. By default it will be "numeric" if the values are
566
     *                                   numeric, otherwise it will be "lexicographic"
567
     *
568
     * @return $this
569
     * @deprecated Use the whereNot() method combined with a whereBetween instead.
570
     */
571 1
    public function orWhereNotBetween(
572
        string $dimension,
573
        $minValue,
574
        $maxValue,
575
        Closure $extraction = null,
576
        string $ordering = null
577
    ) {
578 1
        return $this->whereNotBetween($dimension, $minValue, $maxValue, $extraction, $ordering, 'or');
0 ignored issues
show
Deprecated Code introduced by
The function Level23\Druid\Concerns\H...lter::whereNotBetween() has been deprecated: Use the whereNot() method combined with a whereBetween instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

578
        return /** @scrutinizer ignore-deprecated */ $this->whereNotBetween($dimension, $minValue, $maxValue, $extraction, $ordering, 'or');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
579
    }
580
581
    /**
582
     * Filter records where the given dimension NOT exists in the given list of items
583
     *
584
     * @param string        $dimension  The dimension which you want to filter
585
     * @param array         $items      A list of values. We will return records where the dimension is NOT in this
586
     *                                  list.
587
     * @param \Closure|null $extraction An extraction function to extract a different value from the dimension.
588
     * @param string        $boolean    This influences how this filter will be joined with previous added filters.
589
     *                                  Should both filters apply ("and") or one or the other ("or") ? Default is
590
     *                                  "and".
591
     *
592
     * @return $this
593
     * @deprecated Use the whereNot() method combined with a whereIn instead.
594
     */
595 1
    public function whereNotIn(string $dimension, array $items, Closure $extraction = null, string $boolean = 'and')
596
    {
597 1
        $filter = new NotFilter(new InFilter($dimension, $items, $this->getExtraction($extraction)));
598
599 1
        return $this->where($filter, null, null, null, $boolean);
600
    }
601
602
    /**
603
     * Filter records where the given dimension NOT exists in the given list of items
604
     *
605
     * @param string        $dimension  The dimension which you want to filter
606
     * @param array         $items      A list of values. We will return records where the dimension is NOT in this
607
     *                                  list.
608
     * @param \Closure|null $extraction An extraction function to extract a different value from the dimension.
609
     *
610
     * @return $this
611
     * @deprecated Use the orWhereNot() method combined with a whereIn instead.
612
     */
613 1
    public function orWhereNotIn(string $dimension, array $items, Closure $extraction = null)
614
    {
615 1
        return $this->whereNotIn($dimension, $items, $extraction, 'or');
0 ignored issues
show
Deprecated Code introduced by
The function Level23\Druid\Concerns\HasFilter::whereNotIn() has been deprecated: Use the whereNot() method combined with a whereIn instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

615
        return /** @scrutinizer ignore-deprecated */ $this->whereNotIn($dimension, $items, $extraction, 'or');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
616
    }
617
618
    /**
619
     * Filter on an dimension where the value does NOT exists in the given intervals array.
620
     *
621
     * The intervals array can contain the following:
622
     * - Only 2 elements, start and stop.
623
     * - an Interval object
624
     * - an raw interval string as used in druid. For example: 2019-04-15T08:00:00.000Z/2019-04-15T09:00:00.000Z
625
     * - an array which each contain 2 elements, a start and stop date. These can be an DateTime object, a unix
626
     * timestamp or anything which can be parsed by DateTime::__construct
627
     *
628
     * So valid are:
629
     * ['now', 'tomorrow']
630
     * [['now', 'now + 1 hour'], ['tomorrow', 'tomorrow + 1 hour']]
631
     * ['2019-04-15T08:00:00.000Z/2019-04-15T09:00:00.000Z']
632
     *
633
     * @param string        $dimension  The dimension which you want to filter
634
     * @param array         $intervals  The interval which you do not want to match. See above for more info.
635
     * @param \Closure|null $extraction Extraction function to extract a different value from the dimension.
636
     * @param string        $boolean    This influences how this filter will be joined with previous added filters.
637
     *                                  Should both filters apply ("and") or one or the other ("or") ? Default is
638
     *                                  "and".
639
     *
640
     * @return $this
641
     * @deprecated Use the whereNot() method combined with a whereInterval instead.
642
     */
643 1
    public function whereNotInterval(
644
        string $dimension,
645
        array $intervals,
646
        Closure $extraction = null,
647
        string $boolean = 'and'
648
    ) {
649 1
        $filter = new IntervalFilter(
650 1
            $dimension,
651 1
            $this->normalizeIntervals($intervals),
652 1
            $this->getExtraction($extraction)
653
        );
654
655 1
        return $this->where(new NotFilter($filter), null, null, null, $boolean);
656
    }
657
658
    /**
659
     * Filter on an dimension where the value does NOT exists in the given intervals array.
660
     *
661
     * The intervals array can contain the following:
662
     * - Only 2 elements, start and stop.
663
     * - an Interval object
664
     * - an raw interval string as used in druid. For example: 2019-04-15T08:00:00.000Z/2019-04-15T09:00:00.000Z
665
     * - an array which each contain 2 elements, a start and stop date. These can be an DateTime object, a unix
666
     * timestamp or anything which can be parsed by DateTime::__construct
667
     *
668
     * So valid are:
669
     * ['now', 'tomorrow']
670
     * [['now', 'now + 1 hour'], ['tomorrow', 'tomorrow + 1 hour']]
671
     * ['2019-04-15T08:00:00.000Z/2019-04-15T09:00:00.000Z']
672
     *
673
     * @param string        $dimension  The dimension which you want to filter
674
     * @param array         $intervals  The interval which you do not want to match. See above for more info.
675
     * @param \Closure|null $extraction Extraction function to extract a different value from the dimension.
676
     *
677
     * @return $this
678
     * @deprecated Use the orWhereNot() method combined with a whereInterval instead.
679
     */
680 1
    public function orWhereNotInterval(string $dimension, array $intervals, Closure $extraction = null)
681
    {
682 1
        return $this->whereNotInterval($dimension, $intervals, $extraction, 'or');
0 ignored issues
show
Deprecated Code introduced by
The function Level23\Druid\Concerns\H...ter::whereNotInterval() has been deprecated: Use the whereNot() method combined with a whereInterval instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

682
        return /** @scrutinizer ignore-deprecated */ $this->whereNotInterval($dimension, $intervals, $extraction, 'or');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
683
    }
684
685
    /**
686
     * Filter on an dimension where the value exists in the given intervals array.
687
     *
688
     * The intervals array can contain the following:
689
     * - an Interval object
690
     * - an raw interval string as used in druid. For example: 2019-04-15T08:00:00.000Z/2019-04-15T09:00:00.000Z
691
     * - an array which contains 2 elements, a start and stop date. These can be an DateTime object, a unix timestamp
692
     *   or anything which can be parsed by DateTime::__construct
693
     *
694
     * @param string        $dimension  The dimension which you want to filter
695
     * @param array         $intervals  The interval which you want to match. See above for more info.
696
     * @param \Closure|null $extraction Extraction function to extract a different value from the dimension.
697
     * @param string        $boolean    This influences how this filter will be joined with previous added filters.
698
     *                                  Should both filters apply ("and") or one or the other ("or") ? Default is
699
     *                                  "and".
700
     *
701
     * @return $this
702
     */
703 1
    public function whereInterval(
704
        string $dimension,
705
        array $intervals,
706
        Closure $extraction = null,
707
        string $boolean = 'and'
708
    ) {
709 1
        $filter = new IntervalFilter(
710 1
            $dimension,
711 1
            $this->normalizeIntervals($intervals),
712 1
            $this->getExtraction($extraction)
713
        );
714
715 1
        return $this->where($filter, null, null, null, $boolean);
716
    }
717
718
    /**
719
     * Filter on an dimension where the value exists in the given intervals array.
720
     *
721
     * The intervals array can contain the following:
722
     * - an Interval object
723
     * - an raw interval string as used in druid. For example: 2019-04-15T08:00:00.000Z/2019-04-15T09:00:00.000Z
724
     * - an array which contains 2 elements, a start and stop date. These can be an DateTime object, a unix timestamp
725
     *   or anything which can be parsed by DateTime::__construct
726
     *
727
     * @param string        $dimension  The dimension which you want to filter
728
     * @param array         $intervals  The interval which you want to match. See above for more info.
729
     * @param \Closure|null $extraction Extraction function to extract a different value from the dimension.
730
     *
731
     * @return $this
732
     */
733 1
    public function orWhereInterval(string $dimension, array $intervals, Closure $extraction = null)
734
    {
735 1
        return $this->whereInterval($dimension, $intervals, $extraction, 'or');
736
    }
737
738
    /**
739
     * Normalize the given dimension to a DimensionInterface object.
740
     *
741
     * @param string|Closure $dimension
742
     *
743
     * @return \Level23\Druid\Dimensions\DimensionInterface
744
     * @throws InvalidArgumentException
745
     */
746 6
    protected function columnCompareDimension($dimension): DimensionInterface
747
    {
748 6
        if (is_string($dimension)) {
749 3
            return new Dimension($dimension);
750
        }
751 3
        if ($dimension instanceof Closure) {
752 2
            $builder = new DimensionBuilder();
753 2
            call_user_func($dimension, $builder);
754 2
            $dimensions = $builder->getDimensions();
755
756 2
            if (count($dimensions) != 1) {
757 1
                throw new InvalidArgumentException('Your dimension builder should select 1 dimension');
758
            }
759
760
            /** @var \Level23\Druid\Dimensions\DimensionInterface $dimensionA */
761 1
            return $dimensions[0];
762
        }
763
764 1
        throw new InvalidArgumentException(
765 1
            'You need to supply either a string (the dimension) or a Closure which will receive a DimensionBuilder.'
766
        );
767
    }
768
769
    /**
770
     * Normalize the given intervals into Interval objects.
771
     *
772
     * @param array $intervals
773
     *
774
     * @return array
775
     */
776 11
    protected function normalizeIntervals(array $intervals): array
777
    {
778 11
        $first = reset($intervals);
779
780
        // If first is an array or already a druid interval string or object we do not wrap it in an array
781 11
        if (!is_array($first) && !$this->isDruidInterval($first)) {
782 8
            $intervals = [$intervals];
783
        }
784
785
        return array_map(function ($interval) {
786
787 11
            if ($interval instanceof IntervalInterface) {
788 1
                return $interval;
789
            }
790
791
            // If it is a string we explode it into to elements
792 10
            if (is_string($interval)) {
793 1
                $interval = explode('/', $interval);
794
            }
795
796
            // If the value is an array and is not empty and has either one or 2 values its an interval array
797 10
            if (is_array($interval) && !empty(array_filter($interval)) && count($interval) < 3) {
798
                /** @scrutinizer ignore-type */
799 7
                return new Interval(...$interval);
800
            }
801
802 3
            throw new InvalidArgumentException(
803
                'Invalid type given in the interval array. We cannot process ' .
804 3
                var_export($interval, true)
805
            );
806 11
        }, $intervals);
807
    }
808
809
    /**
810
     * Returns true if the argument provided is a druid interval string or interface
811
     *
812
     * @param string|IntervalInterface $interval
813
     *
814
     * @return bool
815
     */
816 10
    protected function isDruidInterval($interval)
817
    {
818 10
        if ($interval instanceof IntervalInterface) {
819 1
            return true;
820
        }
821
822 9
        return is_string($interval) && strpos($interval, '/') !== false;
823
    }
824
825
    /**
826
     * Helper method to add an OR filter
827
     *
828
     * @param FilterInterface $filter
829
     */
830 4
    protected function addOrFilter(FilterInterface $filter): void
831
    {
832 4
        if (!$this->filter instanceof FilterInterface) {
833 4
            $this->filter = $filter;
834
835 4
            return;
836
        }
837
838 4
        if ($this->filter instanceof OrFilter) {
839 1
            $this->filter->addFilter($filter);
840
841 1
            return;
842
        }
843
844 4
        $this->filter = new OrFilter([$this->filter, $filter]);
845 4
    }
846
847
    /**
848
     * Helper method to add an AND filter
849
     *
850
     * @param FilterInterface $filter
851
     */
852 38
    protected function addAndFilter(FilterInterface $filter): void
853
    {
854 38
        if (!$this->filter instanceof FilterInterface) {
855 38
            $this->filter = $filter;
856
857 38
            return;
858
        }
859
860 20
        if ($this->filter instanceof AndFilter) {
861 2
            $this->filter->addFilter($filter);
862
863 2
            return;
864
        }
865
866 20
        $this->filter = new AndFilter([$this->filter, $filter]);
867 20
    }
868
869
    /**
870
     * @return \Level23\Druid\Filters\FilterInterface|null
871
     */
872 29
    public function getFilter()
873
    {
874 29
        return $this->filter;
875
    }
876
877
    /**
878
     * @param \Closure|null $extraction
879
     *
880
     * @return \Level23\Druid\Extractions\ExtractionInterface|null
881
     */
882 38
    private function getExtraction(?Closure $extraction): ?ExtractionInterface
883
    {
884 38
        if (empty($extraction)) {
885 37
            return null;
886
        }
887
888 1
        $builder = new ExtractionBuilder();
889 1
        call_user_func($extraction, $builder);
890
891 1
        return $builder->getExtraction();
892
    }
893
}