Issues (52)

src/Concerns/HasAggregations.php (1 issue)

Severity
1
<?php
2
declare(strict_types=1);
3
4
namespace Level23\Druid\Concerns;
5
6
use Closure;
7
use Level23\Druid\Types\DataType;
8
use Level23\Druid\Dimensions\Dimension;
9
use Level23\Druid\Queries\QueryBuilder;
10
use Level23\Druid\Filters\FilterBuilder;
11
use Level23\Druid\Filters\FilterInterface;
12
use Level23\Druid\Aggregations\MaxAggregator;
13
use Level23\Druid\Aggregations\MinAggregator;
14
use Level23\Druid\Aggregations\SumAggregator;
15
use Level23\Druid\Aggregations\AnyAggregator;
16
use Level23\Druid\Aggregations\LastAggregator;
17
use Level23\Druid\Dimensions\DimensionBuilder;
18
use Level23\Druid\Aggregations\CountAggregator;
19
use Level23\Druid\Aggregations\FirstAggregator;
20
use Level23\Druid\Aggregations\FilteredAggregator;
21
use Level23\Druid\Collections\DimensionCollection;
22
use Level23\Druid\Aggregations\AggregatorInterface;
23
use Level23\Druid\Aggregations\JavascriptAggregator;
24
use Level23\Druid\Aggregations\HyperUniqueAggregator;
25
use Level23\Druid\Aggregations\CardinalityAggregator;
26
use Level23\Druid\Aggregations\DoublesSketchAggregator;
27
use Level23\Druid\Aggregations\DistinctCountAggregator;
28
29
trait HasAggregations
30
{
31
    protected ?QueryBuilder $query = null;
32
33
    /**
34
     * @var array|\Level23\Druid\Aggregations\AggregatorInterface[]
35
     */
36
    protected array $aggregations = [];
37
38
    /**
39
     * @return array|\Level23\Druid\Aggregations\AggregatorInterface[]
40
     */
41 1
    public function getAggregations(): array
42
    {
43 1
        return $this->aggregations;
44
    }
45
46
    /**
47
     * Sum the given metric
48
     *
49
     * @param string        $metric
50
     * @param string        $as
51
     * @param string|DataType        $type
52
     * @param \Closure|null $filterBuilder   A closure which receives a FilterBuilder. When given, we will only apply
53
     *                                       the "sum" function to the records which match with the given filter.
54
     *
55
     * @return $this
56
     */
57 15
    public function sum(
58
        string $metric,
59
        string $as = '',
60
        string|DataType $type = DataType::LONG,
61
        ?Closure $filterBuilder = null
62
    ): self {
63 15
        $this->aggregations[] = $this->buildFilteredAggregation(
64 15
            new SumAggregator($metric, $as, $type),
65 15
            $filterBuilder
66 15
        );
67
68 15
        return $this;
69
    }
70
71
    /**
72
     * DoublesSketch is a mergeable streaming algorithm to estimate the distribution of values, and approximately
73
     * answer queries about the rank of a value, probability mass function of the distribution (PMF) or histogram,
74
     * cumulative distribution function (CDF), and quantiles (median, min, max, 95th percentile and such). See
75
     * Quantiles Sketch Overview.
76
     *s
77
     * To make use of this aggregator you have to have the datasketches module enabled in your druid server:
78
     * druid.extensions.loadList=["druid-datasketches"]
79
     *
80
     *
81
     * @param string   $metric          A String for the name of the input field (can contain sketches or raw numeric
82
     *                                  values).
83
     * @param string   $as              A String for the output (result) name of the calculation.
84
     * @param int|null $sizeAndAccuracy Parameter that determines the accuracy and size of the sketch. Higher k means
85
     *                                  higher accuracy but more space to store sketches. Must be a power of 2 from 2
86
     *                                  to 32768. See accuracy information in the DataSketches documentation for
87
     *                                  details.
88
     * @param int|null $maxStreamLength This parameter is a temporary solution to avoid a known issue. It may be
89
     *                                  removed in a future release after the bug is fixed. This parameter defines the
90
     *                                  maximum number of items to store in each sketch. If a sketch reaches the limit,
91
     *                                  the query can throw IllegalStateException. To work around this issue, increase
92
     *                                  the maximum stream length. See accuracy information in the DataSketches
93
     *                                  documentation for how many bytes are required per stream length.
94
     *
95
     * @return $this
96
     */
97 5
    public function doublesSketch(
98
        string $metric,
99
        string $as = '',
100
        ?int $sizeAndAccuracy = null,
101
        ?int $maxStreamLength = null
102
    ): self {
103 5
        $this->aggregations[] = new DoublesSketchAggregator(
104 5
            $metric,
105 5
            $as ?: $metric,
106 5
            $sizeAndAccuracy,
107 5
            $maxStreamLength
108 5
        );
109
110 5
        return $this;
111
    }
112
113
    /**
114
     * Shorthand for summing long's
115
     *
116
     * @param string        $metric
117
     * @param string        $as
118
     * @param \Closure|null $filterBuilder   A closure which receives a FilterBuilder. When given, we will only apply
119
     *                                       the "sum" function to the records which match with the given filter.
120
     *
121
     * @return $this
122
     */
123 7
    public function longSum(string $metric, string $as = '', ?Closure $filterBuilder = null): self
124
    {
125 7
        return $this->sum($metric, $as, DataType::LONG, $filterBuilder);
126
    }
127
128
    /**
129
     * Shorthand for summing doubles
130
     *
131
     * @param string        $metric
132
     * @param string        $as
133
     * @param \Closure|null $filterBuilder   A closure which receives a FilterBuilder. When given, we will only apply
134
     *                                       the "sum" function to the records which match with the given filter.
135
     *
136
     * @return $this
137
     */
138 1
    public function doubleSum(string $metric, string $as = '', ?Closure $filterBuilder = null): self
139
    {
140 1
        return $this->sum($metric, $as, DataType::DOUBLE, $filterBuilder);
141
    }
142
143
    /**
144
     * Shorthand for summing floats
145
     *
146
     * @param string        $metric
147
     * @param string        $as
148
     * @param \Closure|null $filterBuilder   A closure which receives a FilterBuilder. When given, we will only apply
149
     *                                       the "sum" function to the records which match with the given filter.
150
     *
151
     * @return $this
152
     */
153 1
    public function floatSum(string $metric, string $as = '', ?Closure $filterBuilder = null): self
154
    {
155 1
        return $this->sum($metric, $as, DataType::FLOAT, $filterBuilder);
156
    }
157
158
    /**
159
     * Uses HyperLogLog to compute the estimated cardinality of a dimension that has been aggregated as a "hyperUnique"
160
     * metric at indexing time.
161
     *
162
     * @see http://algo.inria.fr/flajolet/Publications/FlFuGaMe07.pdf
163
     *
164
     * @param string $metric
165
     * @param string $as
166
     * @param bool   $round              Only affects query-time behavior, and is ignored at ingestion-time. The
167
     *                                   HyperLogLog algorithm generates decimal estimates with some error. "round" can
168
     *                                   be set to true to round off estimated values to whole numbers. Note that even
169
     *                                   with rounding, the cardinality is still an estimate.
170
     * @param bool   $isInputHyperUnique Only affects ingestion-time behavior, and is ignored at query-time. Set to
171
     *                                   true to index pre-computed HLL (Base64 encoded output from druid-hll is
172
     *                                   expected).
173
     *
174
     * @return $this
175
     */
176 5
    public function hyperUnique(string $metric, string $as, bool $round = false, bool $isInputHyperUnique = false): self
177
    {
178 5
        $this->aggregations[] = new HyperUniqueAggregator(
179 5
            $as, $metric, $isInputHyperUnique, $round
180 5
        );
181
182 5
        return $this;
183
    }
184
185
    /**
186
     * Computes the cardinality of a set of Apache Druid (incubating) dimensions, using HyperLogLog to estimate the
187
     * cardinality. Please note that this aggregator will be much slower than indexing a column with the hyperUnique
188
     * aggregator. This aggregator also runs over a dimension column, which means the string dimension cannot be
189
     * removed from the dataset to improve rollup. In general, we strongly recommend using the hyperUnique aggregator
190
     * instead of the cardinality aggregator if you do not care about the individual values of a dimension.
191
     *
192
     * The HyperLogLog algorithm generates decimal estimates with some error. "round" can be set to true to round off
193
     * estimated values to whole numbers. Note that even with rounding, the cardinality is still an estimate. The
194
     * "round" field only affects query-time behavior, and is ignored at ingestion-time.
195
     *
196
     * When setting byRow to false (the default) it computes the cardinality of the set composed of the union of all
197
     * dimension values for all the given dimensions. For a single dimension, this is equivalent to:
198
     * ```
199
     * SELECT COUNT(DISTINCT(dimension)) FROM <datasource>
200
     * ```
201
     *
202
     * For multiple dimensions, this is equivalent to something akin to
203
     * ```
204
     * SELECT COUNT(DISTINCT(value)) FROM (
205
     * SELECT dim_1 as value FROM <datasource>
206
     * UNION
207
     * SELECT dim_2 as value FROM <datasource>
208
     * UNION
209
     * SELECT dim_3 as value FROM <datasource>
210
     * )
211
     * ```
212
     *
213
     * When setting byRow to true it computes the cardinality by row, i.e. the cardinality of distinct dimension
214
     * combinations. This is equivalent to something akin to
215
     *
216
     * ```
217
     * SELECT COUNT(*) FROM ( SELECT DIM1, DIM2, DIM3 FROM <datasource> GROUP BY DIM1, DIM2, DIM3 )
218
     * ```
219
     *
220
     * @see https://druid.apache.org/docs/latest/querying/hll-old.html
221
     *
222
     * @param string            $as                           The output name which is used for the result.
223
     * @param \Closure|string[] $dimensionsOrDimensionBuilder An array with the dimensions which you want to calculate
224
     *                                                        the cardinality over, or a closure which will receive a
225
     *                                                        DimensionBuilder. You should build the dimensions which
226
     *                                                        are used to calculate the cardinality over.
227
     * @param bool              $byRow                        For more details see method description.
228
     * @param bool              $round                        Only affects query-time behavior, and is ignored at
229
     *                                                        ingestion-time. The HyperLogLog algorithm generates
230
     *                                                        decimal estimates with some error. "round" can be set to
231
     *                                                        true to round off estimated values to whole numbers. Note
232
     *                                                        that even with rounding, the cardinality is still an
233
     *                                                        estimate.
234
     *
235
     * @return $this
236
     */
237 6
    public function cardinality(
238
        string $as,
239
        array|Closure $dimensionsOrDimensionBuilder,
240
        bool $byRow = false,
241
        bool $round = false
242
    ): self {
243 6
        if ($dimensionsOrDimensionBuilder instanceof Closure) {
0 ignored issues
show
$dimensionsOrDimensionBuilder is never a sub-type of Closure.
Loading history...
244 5
            $builder = new DimensionBuilder();
245 5
            call_user_func($dimensionsOrDimensionBuilder, $builder);
246 5
            $dimensions = $builder->getDimensions();
247
        } else {
248 1
            $dimensions = [];
249
250 1
            foreach ($dimensionsOrDimensionBuilder as $dimension) {
251 1
                $dimensions[] = new Dimension($dimension);
252
            }
253
        }
254
255 6
        $this->aggregations[] = new CardinalityAggregator(
256 6
            $as,
257 6
            new DimensionCollection(...$dimensions),
258 6
            $byRow,
259 6
            $round
260 6
        );
261
262 6
        return $this;
263
    }
264
265
    /**
266
     * When a closure is given, we will call the given function which is responsible for building a filter.
267
     * We will then only apply the given aggregator for the records where the filter matches.
268
     *
269
     * @param \Level23\Druid\Aggregations\AggregatorInterface $aggregator
270
     * @param \Closure|null                                   $filterBuilder
271
     *
272
     * @return \Level23\Druid\Aggregations\AggregatorInterface
273
     */
274 16
    protected function buildFilteredAggregation(
275
        AggregatorInterface $aggregator,
276
        ?Closure $filterBuilder = null
277
    ): AggregatorInterface {
278 16
        if (!$filterBuilder) {
279 13
            return $aggregator;
280
        }
281
282 3
        $builder = new FilterBuilder($this->query);
283 3
        call_user_func($filterBuilder, $builder);
284 3
        $filter = $builder->getFilter();
285
286 3
        if ($filter instanceof FilterInterface) {
287 2
            return new FilteredAggregator($filter, $aggregator);
288
        }
289
290 1
        return $aggregator;
291
    }
292
293
    /**
294
     * Count the number of results and put it in a dimension with the given name.
295
     *
296
     * @param string        $as
297
     * @param \Closure|null $filterBuilder A closure which receives a FilterBuilder. When given, we will only count the
298
     *                                     records which match with the given filter.
299
     *
300
     * @return $this
301
     */
302 1
    public function count(string $as, ?Closure $filterBuilder = null): self
303
    {
304 1
        $this->aggregations[] = $this->buildFilteredAggregation(
305 1
            new CountAggregator($as),
306 1
            $filterBuilder
307 1
        );
308
309 1
        return $this;
310
    }
311
312
    /**
313
     * Count the number of distinct values of a specific dimension.
314
     * NOTE: The DataSketches Theta Sketch extension is required to run this aggregation.
315
     *
316
     * @param string        $dimension
317
     * @param string        $as
318
     * @param int           $size          Must be a power of 2. Internally, size refers to the maximum number of
319
     *                                     entries sketch object will retain. Higher size means higher accuracy but
320
     *                                     more space to store sketches. Note that after you index with a particular
321
     *                                     size, druid will persist sketch in segments, and you will use size greater or
322
     *                                     equal to that at query time. See the DataSketches site for details. In
323
     *                                     general, We recommend just sticking to default size.
324
     * @param \Closure|null $filterBuilder A closure which receives a FilterBuilder. When given, we will only count the
325
     *                                     records which match with the given filter.
326
     *
327
     * @return $this
328
     */
329 2
    public function distinctCount(
330
        string $dimension,
331
        string $as = '',
332
        int $size = 16384,
333
        ?Closure $filterBuilder = null
334
    ): self {
335 2
        $this->aggregations[] = $this->buildFilteredAggregation(
336 2
            new DistinctCountAggregator($dimension, ($as ?: $dimension), $size),
337 2
            $filterBuilder
338 2
        );
339
340 2
        return $this;
341
    }
342
343
    /**
344
     * Get the minimum value for the given metric
345
     *
346
     * @param string        $metric
347
     * @param string        $as
348
     * @param string|DataType        $type
349
     * @param \Closure|null $filterBuilder A closure which receives a FilterBuilder. When given, we will only apply the
350
     *                                     min function to the records which match with the given filter.
351
     *
352
     * @return $this
353
     */
354 2
    public function min(
355
        string $metric,
356
        string $as = '',
357
        string|DataType $type = DataType::LONG,
358
        ?Closure $filterBuilder = null
359
    ): self {
360 2
        $this->aggregations[] = $this->buildFilteredAggregation(
361 2
            new MinAggregator($metric, $as, $type),
362 2
            $filterBuilder
363 2
        );
364
365 2
        return $this;
366
    }
367
368
    /**
369
     * Get the minimum value for the given metric using long as type
370
     *
371
     * @param string        $metric
372
     * @param string        $as
373
     * @param \Closure|null $filterBuilder A closure which receives a FilterBuilder. When given, we will only apply the
374
     *                                     min function to the records which match with the given filter.
375
     *
376
     * @return $this
377
     */
378 1
    public function longMin(string $metric, string $as = '', ?Closure $filterBuilder = null): self
379
    {
380 1
        return $this->min($metric, $as, DataType::LONG, $filterBuilder);
381
    }
382
383
    /**
384
     * Get the minimum value for the given metric using double as type
385
     *
386
     * @param string        $metric
387
     * @param string        $as
388
     * @param \Closure|null $filterBuilder A closure which receives a FilterBuilder. When given, we will only apply the
389
     *                                     min function to the records which match with the given filter.
390
     *
391
     * @return $this
392
     */
393 1
    public function doubleMin(string $metric, string $as = '', ?Closure $filterBuilder = null): self
394
    {
395 1
        return $this->min($metric, $as, DataType::DOUBLE, $filterBuilder);
396
    }
397
398
    /**
399
     * Get the minimum value for the given metric using float as type
400
     *
401
     * @param string        $metric
402
     * @param string        $as
403
     * @param \Closure|null $filterBuilder A closure which receives a FilterBuilder. When given, we will only apply the
404
     *                                     min function to the records which match with the given filter.
405
     *
406
     * @return $this
407
     */
408 1
    public function floatMin(string $metric, string $as = '', ?Closure $filterBuilder = null): self
409
    {
410 1
        return $this->min($metric, $as, DataType::FLOAT, $filterBuilder);
411
    }
412
413
    /**
414
     * Get the maximum value for the given metric
415
     *
416
     * @param string        $metric
417
     * @param string        $as
418
     * @param string|DataType        $type
419
     * @param \Closure|null $filterBuilder A closure which receives a FilterBuilder. When given, we will only apply the
420
     *                                     max function to the records which match with the given filter.
421
     *
422
     * @return $this
423
     */
424 2
    public function max(
425
        string $metric,
426
        string $as = '',
427
        string|DataType $type = DataType::LONG,
428
        ?Closure $filterBuilder = null
429
    ): self {
430 2
        $this->aggregations[] = $this->buildFilteredAggregation(
431 2
            new MaxAggregator($metric, $as, $type),
432 2
            $filterBuilder
433 2
        );
434
435 2
        return $this;
436
    }
437
438
    /**
439
     * Get the maximum value for the given metric using long as type
440
     *
441
     * @param string        $metric
442
     * @param string        $as
443
     * @param \Closure|null $filterBuilder A closure which receives a FilterBuilder. When given, we will only apply the
444
     *                                     max function to the records which match with the given filter.
445
     *
446
     * @return $this
447
     */
448 1
    public function longMax(string $metric, string $as = '', ?Closure $filterBuilder = null): self
449
    {
450 1
        return $this->max($metric, $as, DataType::LONG, $filterBuilder);
451
    }
452
453
    /**
454
     * Get the maximum value for the given metric using float as type
455
     *
456
     * @param string        $metric
457
     * @param string        $as
458
     * @param \Closure|null $filterBuilder A closure which receives a FilterBuilder. When given, we will only apply the
459
     *                                     max function to the records which match with the given filter.
460
     *
461
     * @return $this
462
     */
463 1
    public function floatMax(string $metric, string $as = '', ?Closure $filterBuilder = null): self
464
    {
465 1
        return $this->max($metric, $as, DataType::FLOAT, $filterBuilder);
466
    }
467
468
    /**
469
     * Get the maximum value for the given metric using double as type
470
     *
471
     * @param string        $metric
472
     * @param string        $as
473
     * @param \Closure|null $filterBuilder A closure which receives a FilterBuilder. When given, we will only apply the
474
     *                                     max function to the records which match with the given filter.
475
     *
476
     * @return $this
477
     */
478 1
    public function doubleMax(string $metric, string $as = '', ?Closure $filterBuilder = null): self
479
    {
480 1
        return $this->max($metric, $as, DataType::DOUBLE, $filterBuilder);
481
    }
482
483
    /**
484
     * Get the any-metric found based on the applied group-by filters.
485
     * Returns any value including null. This aggregator can simplify and
486
     * optimize the performance by returning the first encountered value (including null)
487
     *
488
     * ANY aggregator cannot be used in ingestion spec, and should only be specified as part of queries.
489
     *
490
     * @param string        $metric
491
     * @param string        $as
492
     * @param string|DataType        $type
493
     * @param int|null      $maxStringBytes For string types only. Optional, defaults to 1024.
494
     * @param \Closure|null $filterBuilder  A closure which receives a FilterBuilder. When given, we will only apply the
495
     *                                      "any" function to the records which match with the given filter.
496
     *
497
     * @return $this
498
     */
499 2
    public function any(
500
        string $metric,
501
        string $as = '',
502
        string|DataType $type = DataType::LONG,
503
        ?int $maxStringBytes = null,
504
        ?Closure $filterBuilder = null
505
    ): self {
506 2
        $this->aggregations[] = $this->buildFilteredAggregation(
507 2
            new AnyAggregator($metric, $as, $type, $maxStringBytes),
508 2
            $filterBuilder
509 2
        );
510
511 2
        return $this;
512
    }
513
514
    /**
515
     * Get the any-metric found based on the applied group-by filters.
516
     * Returns any value including null. This aggregator can simplify and
517
     * optimize the performance by returning the first encountered value (including null)
518
     *
519
     * ANY aggregator cannot be used in ingestion spec, and should only be specified as part of queries.
520
     *
521
     * @param string        $metric
522
     * @param string        $as
523
     * @param \Closure|null $filterBuilder A closure which receives a FilterBuilder. When given, we will only apply the
524
     *                                     "any" function to the records which match with the given filter.
525
     *
526
     * @return $this
527
     */
528 1
    public function doubleAny(string $metric, string $as = '', ?Closure $filterBuilder = null): self
529
    {
530 1
        return $this->any($metric, $as, DataType::DOUBLE, null, $filterBuilder);
531
    }
532
533
    /**
534
     * Get the any-metric found based on the applied group-by filters.
535
     * Returns any value including null. This aggregator can simplify and
536
     * optimize the performance by returning the first encountered value (including null)
537
     *
538
     * ANY aggregator cannot be used in ingestion spec, and should only be specified as part of queries.
539
     *
540
     * @param string        $metric
541
     * @param string        $as
542
     * @param \Closure|null $filterBuilder A closure which receives a FilterBuilder. When given, we will only apply the
543
     *                                     "any" function to the records which match with the given filter.
544
     *
545
     * @return $this
546
     */
547 1
    public function floatAny(string $metric, string $as = '', ?Closure $filterBuilder = null): self
548
    {
549 1
        return $this->any($metric, $as, DataType::FLOAT, null, $filterBuilder);
550
    }
551
552
    /**
553
     * Get the any-metric found based on the applied group-by filters.
554
     * Returns any value including null. This aggregator can simplify and
555
     * optimize the performance by returning the first encountered value (including null)
556
     *
557
     * ANY aggregator cannot be used in ingestion spec, and should only be specified as part of queries.
558
     *
559
     * @param string        $metric
560
     * @param string        $as
561
     * @param \Closure|null $filterBuilder A closure which receives a FilterBuilder. When given, we will only apply the
562
     *                                     "any" function to the records which match with the given filter.
563
     *
564
     * @return $this
565
     */
566 1
    public function longAny(string $metric, string $as = '', ?Closure $filterBuilder = null): self
567
    {
568 1
        return $this->any($metric, $as, DataType::LONG, null, $filterBuilder);
569
    }
570
571
    /**
572
     * Get the any-metric found based on the applied group-by filters.
573
     * Returns any value including null. This aggregator can simplify and
574
     * optimize the performance by returning the first encountered value (including null)
575
     *
576
     * ANY aggregator cannot be used in ingestion spec, and should only be specified as part of queries.
577
     *
578
     * @param string        $metric
579
     * @param string        $as
580
     * @param int|null      $maxStringBytes Optional, defaults to 1024
581
     * @param \Closure|null $filterBuilder  A closure which receives a FilterBuilder. When given, we will only apply the
582
     *                                      "any" function to the records which match with the given filter.
583
     *
584
     * @return $this
585
     */
586 1
    public function stringAny(
587
        string $metric,
588
        string $as = '',
589
        ?int $maxStringBytes = null,
590
        ?Closure $filterBuilder = null
591
    ): self {
592 1
        return $this->any($metric, $as, DataType::STRING, $maxStringBytes, $filterBuilder);
593
    }
594
595
    /**
596
     * Get the first metric found based on the applied group-by filters.
597
     * So if you group by the dimension "countries", you can get the first "metric" per country.
598
     *
599
     * NOTE: This is different from the Laravel ELOQUENT first() method!
600
     *
601
     * @param string        $metric
602
     * @param string        $as
603
     * @param string|DataType        $type
604
     * @param \Closure|null $filterBuilder A closure which receives a FilterBuilder. When given, we will only apply the
605
     *                                     "first" function to the records which match with the given filter.
606
     *
607
     * @return $this
608
     */
609 3
    public function first(
610
        string $metric,
611
        string $as = '',
612
        string|DataType $type = DataType::LONG,
613
        ?Closure $filterBuilder = null
614
    ): self {
615 3
        $this->aggregations[] = $this->buildFilteredAggregation(
616 3
            new FirstAggregator($metric, $as, $type),
617 3
            $filterBuilder
618 3
        );
619
620 3
        return $this;
621
    }
622
623
    /**
624
     * Get the first metric found based on the applied group-by filters.
625
     * So if you group by the dimension "countries", you can get the first "metric" per country.
626
     *
627
     * NOTE: This is different from the Laravel ELOQUENT first() method!
628
     *
629
     * @param string        $metric
630
     * @param string        $as
631
     * @param \Closure|null $filterBuilder A closure which receives a FilterBuilder. When given, we will only apply the
632
     *                                     "first" function to the records which match with the given filter.
633
     *
634
     * @return $this
635
     */
636 1
    public function longFirst(string $metric, string $as = '', ?Closure $filterBuilder = null): self
637
    {
638 1
        return $this->first($metric, $as, DataType::LONG, $filterBuilder);
639
    }
640
641
    /**
642
     * Get the first metric found based on the applied group-by filters.
643
     * So if you group by the dimension "countries", you can get the first "metric" per country.
644
     *
645
     * NOTE: This is different from the Laravel ELOQUENT first() method!
646
     *
647
     * @param string        $metric
648
     * @param string        $as
649
     * @param \Closure|null $filterBuilder A closure which receives a FilterBuilder. When given, we will only apply the
650
     *                                     "first" function to the records which match with the given filter.
651
     *
652
     * @return $this
653
     */
654 1
    public function floatFirst(string $metric, string $as = '', ?Closure $filterBuilder = null): self
655
    {
656 1
        return $this->first($metric, $as, DataType::FLOAT, $filterBuilder);
657
    }
658
659
    /**
660
     * Get the first metric found based on the applied group-by filters.
661
     * So if you group by the dimension "countries", you can get the first "metric" per country.
662
     *
663
     * NOTE: This is different from the Laravel ELOQUENT first() method!
664
     *
665
     * @param string        $metric
666
     * @param string        $as
667
     * @param \Closure|null $filterBuilder A closure which receives a FilterBuilder. When given, we will only apply the
668
     *                                     "first" function to the records which match with the given filter.
669
     *
670
     * @return $this
671
     */
672 1
    public function doubleFirst(string $metric, string $as = '', ?Closure $filterBuilder = null): self
673
    {
674 1
        return $this->first($metric, $as, DataType::DOUBLE, $filterBuilder);
675
    }
676
677
    /**
678
     * Get the first metric found based on the applied group-by filters.
679
     * So if you group by the dimension "countries", you can get the first "metric" per country.
680
     *
681
     * NOTE: This is different from the Laravel ELOQUENT first() method!
682
     *
683
     * @param string        $metric
684
     * @param string        $as
685
     * @param \Closure|null $filterBuilder A closure which receives a FilterBuilder. When given, we will only apply the
686
     *                                     "first" function to the records which match with the given filter.
687
     *
688
     * @return $this
689
     */
690 1
    public function stringFirst(string $metric, string $as = '', ?Closure $filterBuilder = null): self
691
    {
692 1
        return $this->first($metric, $as, DataType::STRING, $filterBuilder);
693
    }
694
695
    /**
696
     * Get the last metric found
697
     *
698
     * @param string        $metric
699
     * @param string        $as
700
     * @param string|DataType        $type
701
     * @param \Closure|null $filterBuilder A closure which receives a FilterBuilder. When given, we will only apply the
702
     *                                     "last" function to the records which match with the given filter.
703
     *
704
     * @return $this
705
     */
706 2
    public function last(
707
        string $metric,
708
        string $as = '',
709
        string|DataType $type = DataType::LONG,
710
        ?Closure $filterBuilder = null
711
    ): self {
712 2
        $this->aggregations[] = $this->buildFilteredAggregation(
713 2
            new LastAggregator($metric, $as, $type),
714 2
            $filterBuilder
715 2
        );
716
717 2
        return $this;
718
    }
719
720
    /**
721
     * Get the last metric found based on the applied group-by filters.
722
     * So if you group by the dimension "countries", you can get the last "metric" per country.
723
     *
724
     * NOTE: This is different from the ELOQUENT last() method!
725
     *
726
     * @param string        $metric
727
     * @param string        $as
728
     * @param \Closure|null $filterBuilder A closure which receives a FilterBuilder. When given, we will only apply the
729
     *                                     "last" function to the records which match with the given filter.
730
     *
731
     * @return $this
732
     */
733 1
    public function longLast(string $metric, string $as = '', ?Closure $filterBuilder = null): self
734
    {
735 1
        return $this->last($metric, $as, DataType::LONG, $filterBuilder);
736
    }
737
738
    /**
739
     * Get the last metric found based on the applied group-by filters.
740
     * So if you group by the dimension "countries", you can get the last "metric" per country.
741
     *
742
     * NOTE: This is different from the ELOQUENT last() method!
743
     *
744
     * @param string        $metric
745
     * @param string        $as
746
     * @param \Closure|null $filterBuilder A closure which receives a FilterBuilder. When given, we will only apply the
747
     *                                     "last" function to the records which match with the given filter.
748
     *
749
     * @return $this
750
     */
751 1
    public function floatLast(string $metric, string $as = '', ?Closure $filterBuilder = null): self
752
    {
753 1
        return $this->last($metric, $as, DataType::FLOAT, $filterBuilder);
754
    }
755
756
    /**
757
     * Get the last metric found based on the applied group-by filters.
758
     * So if you group by the dimension "countries", you can get the last "metric" per country.
759
     *
760
     * NOTE: This is different from the ELOQUENT last() method!
761
     *
762
     * @param string        $metric
763
     * @param string        $as
764
     * @param \Closure|null $filterBuilder A closure which receives a FilterBuilder. When given, we will only apply the
765
     *                                     "last" function to the records which match with the given filter.
766
     *
767
     * @return $this
768
     */
769 1
    public function doubleLast(string $metric, string $as = '', ?Closure $filterBuilder = null): self
770
    {
771 1
        return $this->last($metric, $as, DataType::DOUBLE, $filterBuilder);
772
    }
773
774
    /**
775
     * Get the last metric found based on the applied group-by filters.
776
     * So if you group by the dimension "countries", you can get the last "metric" per country.
777
     *
778
     * NOTE: This is different from the ELOQUENT last() method!
779
     *
780
     * @param string        $metric
781
     * @param string        $as
782
     * @param \Closure|null $filterBuilder A closure which receives a FilterBuilder. When given, we will only apply the
783
     *                                     "last" function to the records which match with the given filter.
784
     *
785
     * @return $this
786
     */
787 1
    public function stringLast(string $metric, string $as = '', ?Closure $filterBuilder = null): self
788
    {
789 1
        return $this->last($metric, $as, DataType::STRING, $filterBuilder);
790
    }
791
792
    /**
793
     * Computes an arbitrary JavaScript function over a set of columns (both metrics and dimensions are allowed). Your
794
     * JavaScript functions are expected to return floating-point values.
795
     *
796
     * Note: JavaScript-based functionality is disabled by default. Please refer to the Druid JavaScript programming
797
     * guide for guidelines about using Druid's JavaScript functionality, including instructions on how to enable it.
798
     *
799
     * @param string        $as            The output name as the result will be available
800
     * @param string[]      $fieldNames    The columns which will be given to the fnAggregate function. Both metrics
801
     *                                     and dimensions are allowed.
802
     * @param string        $fnAggregate   A javascript function which does the aggregation. This function will receive
803
     *                                     the "current" value as first parameter. The other parameters will be the
804
     *                                     values of the columns as given in the $fieldNames parameter.
805
     * @param string        $fnCombine     A function which can combine two aggregation results.
806
     * @param string        $fnReset       A function which will reset a value.
807
     * @param \Closure|null $filterBuilder A closure which receives a FilterBuilder. When given, we will only apply the
808
     *                                     javascript function to the records which match with the given filter.
809
     *
810
     * @return $this
811
     */
812 1
    public function javascript(
813
        string $as,
814
        array $fieldNames,
815
        string $fnAggregate,
816
        string $fnCombine,
817
        string $fnReset,
818
        ?Closure $filterBuilder = null
819
    ): self {
820 1
        $this->aggregations[] = $this->buildFilteredAggregation(
821 1
            new JavascriptAggregator($fieldNames, $as, $fnAggregate, $fnCombine, $fnReset),
822 1
            $filterBuilder
823 1
        );
824
825 1
        return $this;
826
    }
827
}