2022-08-05 15:41:38 +00:00
|
|
|
<?php
|
2022-08-15 20:28:25 +00:00
|
|
|
|
2022-08-05 15:41:38 +00:00
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
2022-10-21 20:03:52 +00:00
|
|
|
namespace Tests;
|
2022-08-15 20:28:25 +00:00
|
|
|
|
2023-02-10 15:32:25 +01:00
|
|
|
use Closure;
|
2022-08-15 20:35:51 +00:00
|
|
|
use DateTime;
|
|
|
|
|
use Exception;
|
2022-10-21 20:03:52 +00:00
|
|
|
use Arendsen\FluxQueryBuilder\Expression\KeyFilter;
|
2022-08-08 15:07:30 +00:00
|
|
|
use Arendsen\FluxQueryBuilder\Expression\KeyValue;
|
2022-08-05 15:41:38 +00:00
|
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
|
use Arendsen\FluxQueryBuilder\QueryBuilder;
|
2022-08-16 15:49:06 +00:00
|
|
|
use Arendsen\FluxQueryBuilder\Type\MathType;
|
2022-08-05 15:41:38 +00:00
|
|
|
|
2022-08-15 20:28:25 +00:00
|
|
|
final class QueryBuilderTest extends TestCase
|
|
|
|
|
{
|
2022-08-05 15:41:38 +00:00
|
|
|
/**
|
2023-02-10 14:23:19 +01:00
|
|
|
* @dataProvider newTestsProvider
|
2022-08-05 15:41:38 +00:00
|
|
|
*/
|
2023-02-10 14:23:19 +01:00
|
|
|
public function testBasicQuery(string $methodName, array $params = [], string $expected = '')
|
2022-08-05 15:41:38 +00:00
|
|
|
{
|
|
|
|
|
$queryBuilder = new QueryBuilder();
|
2023-02-10 14:23:19 +01:00
|
|
|
$queryBuilder->fromBucket('test_bucket')
|
|
|
|
|
->addRangeStart(new DateTime('2022-08-12 17:31:00'))
|
|
|
|
|
->fromMeasurement('test_measurement');
|
2022-08-05 15:41:38 +00:00
|
|
|
|
2023-02-10 14:23:19 +01:00
|
|
|
call_user_func_array([$queryBuilder, $methodName], $params);
|
|
|
|
|
|
|
|
|
|
$expectedQuery = 'from(bucket: "test_bucket") |> range(start: time(v: 2022-08-12T17:31:00Z)) ' .
|
|
|
|
|
'|> filter(fn: (r) => r._measurement == "test_measurement") ' . $expected;
|
2022-08-08 15:07:30 +00:00
|
|
|
|
2022-08-08 14:42:10 +00:00
|
|
|
$this->assertEquals($expectedQuery, $queryBuilder->build());
|
2022-08-05 15:41:38 +00:00
|
|
|
}
|
|
|
|
|
|
2023-02-10 14:23:19 +01:00
|
|
|
public function newTestsProvider(): array
|
2022-08-05 15:41:38 +00:00
|
|
|
{
|
|
|
|
|
return [
|
2023-02-10 14:23:19 +01:00
|
|
|
'addAggregateWindow' => [
|
|
|
|
|
'addAggregateWindow',
|
|
|
|
|
['20s', 'mean', ['timeDst' => '_time']],
|
|
|
|
|
'|> aggregateWindow(every: 20s, fn: mean, timeDst: "_time") '
|
|
|
|
|
],
|
|
|
|
|
'addDuplicate' => [
|
|
|
|
|
'addDuplicate',
|
|
|
|
|
['old_name', 'new_name'],
|
|
|
|
|
'|> duplicate(column: "old_name", as: "new_name") '
|
|
|
|
|
],
|
|
|
|
|
'addFilter' => [
|
|
|
|
|
'addFilter',
|
|
|
|
|
[KeyValue::setGreaterOrEqualTo('count', 2)->andGreaterOrEqualTo('count2', 3)],
|
|
|
|
|
'|> filter(fn: (r) => r.count >= 2 and r.count2 >= 3) '
|
|
|
|
|
],
|
|
|
|
|
'addKeyFilter' => [
|
|
|
|
|
'addKeyFilter',
|
|
|
|
|
[KeyFilter::setEqualTo('user', 'username')],
|
|
|
|
|
'|> filter(fn: (r) => r.user == "username") '
|
|
|
|
|
],
|
|
|
|
|
'addFieldFilter' => [
|
|
|
|
|
'addFieldFilter',
|
|
|
|
|
[['email', 'username']],
|
|
|
|
|
'|> filter(fn: (r) => r._field == "email" or r._field == "username") '
|
|
|
|
|
],
|
|
|
|
|
'addGroup' => [
|
|
|
|
|
'addGroup',
|
|
|
|
|
[['_field', 'ip']],
|
|
|
|
|
'|> group(columns: ["_field", "ip"], mode: "by") '
|
|
|
|
|
],
|
|
|
|
|
'addLast' => [
|
|
|
|
|
'addLast',
|
|
|
|
|
['something'],
|
|
|
|
|
'|> last(column: "something") '
|
|
|
|
|
],
|
|
|
|
|
'addLimit' => [
|
|
|
|
|
'addLimit',
|
|
|
|
|
[20, 40],
|
|
|
|
|
'|> limit(n: 20, offset: 40) '
|
|
|
|
|
],
|
|
|
|
|
'addMap' => [
|
|
|
|
|
'addMap',
|
|
|
|
|
['r with name: r.user'],
|
|
|
|
|
'|> map(fn: (r) => ({ r with name: r.user })) '
|
|
|
|
|
],
|
|
|
|
|
'addMean' => [
|
|
|
|
|
'addMean',
|
|
|
|
|
[],
|
|
|
|
|
'|> mean() '
|
|
|
|
|
],
|
|
|
|
|
'addRawFunction' => [
|
|
|
|
|
'addRawFunction',
|
|
|
|
|
['|> aggregateWindow(every: 20s, fn: mean, timeDst: "_time")'],
|
|
|
|
|
'|> aggregateWindow(every: 20s, fn: mean, timeDst: "_time") '
|
|
|
|
|
],
|
|
|
|
|
'addReduce' => [
|
|
|
|
|
'addReduce',
|
|
|
|
|
[['count' => new MathType('accumulator.count + 1')], ['count' => 0]],
|
|
|
|
|
'|> reduce(fn: (r, accumulator) => ({count: accumulator.count + 1}), identity: {count: 0}) '
|
|
|
|
|
],
|
|
|
|
|
'addSort' => [
|
|
|
|
|
'addSort',
|
|
|
|
|
[['column1', 'column2'], true],
|
|
|
|
|
'|> sort(columns: ["column1", "column2"], desc: true) '
|
|
|
|
|
],
|
|
|
|
|
'addSum' => [
|
|
|
|
|
'addSum',
|
|
|
|
|
['something'],
|
|
|
|
|
'|> sum(column: "something") '
|
|
|
|
|
],
|
|
|
|
|
'addUnwindow' => [
|
|
|
|
|
'addUnwindow',
|
|
|
|
|
[],
|
|
|
|
|
'|> window(every: inf) '
|
|
|
|
|
],
|
|
|
|
|
'addWindow' => [
|
|
|
|
|
'addWindow',
|
2023-02-11 17:33:35 +01:00
|
|
|
['20s', ['timeColumn' => '_time']],
|
|
|
|
|
'|> window(every: 20s, timeColumn: "_time") '
|
2022-08-08 15:07:30 +00:00
|
|
|
],
|
2022-08-08 14:42:10 +00:00
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider throwsExceptionWithoutRequiredDataProvider
|
|
|
|
|
*/
|
|
|
|
|
public function testThrowsExceptionWithoutRequiredData($from, $measurement, $range)
|
|
|
|
|
{
|
|
|
|
|
$this->expectException(Exception::class);
|
|
|
|
|
|
|
|
|
|
$queryBuilder = new QueryBuilder();
|
|
|
|
|
|
2022-08-15 20:28:25 +00:00
|
|
|
if ($from) {
|
2022-08-08 14:42:10 +00:00
|
|
|
$queryBuilder->from($from);
|
|
|
|
|
}
|
2022-08-15 20:28:25 +00:00
|
|
|
if ($range) {
|
2022-08-12 21:25:03 +00:00
|
|
|
$queryBuilder->addRangeStart($range['start']);
|
2022-08-08 14:42:10 +00:00
|
|
|
}
|
2022-09-01 15:17:25 +00:00
|
|
|
if ($measurement) {
|
|
|
|
|
$queryBuilder->fromMeasurement($measurement);
|
|
|
|
|
}
|
2022-08-08 14:42:10 +00:00
|
|
|
|
|
|
|
|
$queryBuilder->build();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function throwsExceptionWithoutRequiredDataProvider(): array
|
|
|
|
|
{
|
|
|
|
|
return [
|
|
|
|
|
'without from data' => [
|
2022-08-12 21:25:03 +00:00
|
|
|
null, 'test_measurement', ['start' => new DateTime('2022-08-12 20:05:00')],
|
2022-08-08 14:42:10 +00:00
|
|
|
],
|
|
|
|
|
'without measurement data' => [
|
2022-08-12 21:25:03 +00:00
|
|
|
['from' => 'test_bucket'], null, ['start' => new DateTime('2022-08-12 20:05:00')],
|
2022-08-08 14:42:10 +00:00
|
|
|
],
|
|
|
|
|
'without range data' => [
|
|
|
|
|
['from' => 'test_bucket'], 'test_measurement', null,
|
2022-08-05 15:41:38 +00:00
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-01 15:17:25 +00:00
|
|
|
public function testThrowsExceptionWhenIncorrectOrder()
|
|
|
|
|
{
|
|
|
|
|
$this->expectException(Exception::class);
|
|
|
|
|
|
|
|
|
|
$queryBuilder = new QueryBuilder();
|
|
|
|
|
$queryBuilder->from(['bucket' => 'test_bucket'])
|
|
|
|
|
->fromMeasurement('test_measurement')
|
|
|
|
|
->addRangeStart(new DateTime('2022-08-12 20:05:00'));
|
|
|
|
|
|
|
|
|
|
$queryBuilder->build();
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-01 12:57:36 +00:00
|
|
|
public function testQueryWithWindow()
|
2022-09-01 09:16:38 +00:00
|
|
|
{
|
|
|
|
|
$queryBuilder = new QueryBuilder();
|
|
|
|
|
$queryBuilder->fromBucket('test_bucket')
|
|
|
|
|
->addRangeStart(new DateTime('2022-08-12 17:31:00'))
|
|
|
|
|
->addReduce(['count' => new MathType('accumulator.count + 1')], ['count' => 0])
|
2022-09-01 15:17:25 +00:00
|
|
|
->addWindow('20s')
|
2022-09-01 13:29:27 +00:00
|
|
|
->addMean()
|
2022-09-01 13:47:34 +00:00
|
|
|
->addDuplicate('tag', 'tag_dup')
|
2022-09-01 15:17:25 +00:00
|
|
|
->fromMeasurement('test_measurement')
|
2022-10-21 20:03:52 +00:00
|
|
|
->addFilter(KeyValue::setGreaterOrEqualTo('count', 2)->andGreaterOrEqualTo('count2', 3))
|
|
|
|
|
->addKeyFilter(KeyFilter::setGreaterOrEqualTo('count', 1)->andGreaterOrEqualTo('count2', 2))
|
2022-09-01 09:16:38 +00:00
|
|
|
->addUnWindow();
|
|
|
|
|
|
|
|
|
|
$expectedQuery = 'from(bucket: "test_bucket") |> range(start: time(v: 2022-08-12T17:31:00Z)) ' .
|
2022-09-01 15:17:25 +00:00
|
|
|
'|> reduce(fn: (r, accumulator) => ({count: accumulator.count + 1}), identity: {count: 0}) ' .
|
|
|
|
|
'|> window(every: 20s) |> mean() |> duplicate(column: "tag", as: "tag_dup") ' .
|
|
|
|
|
'|> filter(fn: (r) => r._measurement == "test_measurement") ' .
|
2022-10-21 20:03:52 +00:00
|
|
|
'|> filter(fn: (r) => r.count >= 2 and r.count2 >= 3) ' .
|
2022-09-01 15:17:25 +00:00
|
|
|
'|> filter(fn: (r) => r.count >= 1 and r.count2 >= 2) |> window(every: inf) ';
|
2022-08-10 16:09:29 +00:00
|
|
|
|
|
|
|
|
$this->assertEquals($expectedQuery, $queryBuilder->build());
|
|
|
|
|
}
|
2023-02-10 15:32:25 +01:00
|
|
|
|
|
|
|
|
public function testCorrectInstancesAreCreated()
|
|
|
|
|
{
|
|
|
|
|
$queryBuilder = new QueryBuilder();
|
|
|
|
|
$queryBuilder->fromBucket('test_bucket')
|
|
|
|
|
->addRangeStart(new DateTime('2022-08-12 17:31:00'));
|
|
|
|
|
|
|
|
|
|
$instances = [
|
|
|
|
|
\Arendsen\FluxQueryBuilder\Functions\From::class,
|
|
|
|
|
\Arendsen\FluxQueryBuilder\Functions\Range::class,
|
|
|
|
|
\Arendsen\FluxQueryBuilder\Functions\Measurement::class,
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
$requiredFluxQueryParts = $this->getClassProperty($queryBuilder, 'requiredFluxQueryParts');
|
|
|
|
|
|
|
|
|
|
foreach ($requiredFluxQueryParts as $keyPart => $part) {
|
|
|
|
|
$this->assertEquals($instances[$keyPart], $part);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function getClassProperty($object, string $property): array
|
|
|
|
|
{
|
|
|
|
|
$propertyReader = function & ($object, $property) {
|
|
|
|
|
$value = & Closure::bind(function & () use ($property) {
|
|
|
|
|
return $this->$property;
|
|
|
|
|
}, $object, $object)->__invoke();
|
|
|
|
|
|
|
|
|
|
return $value;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return $propertyReader($object, $property);
|
|
|
|
|
}
|
2022-08-15 20:28:25 +00:00
|
|
|
}
|