2022-08-05 15:41:38 +00:00
|
|
|
<?php
|
2022-08-15 20:28:25 +00:00
|
|
|
|
2024-03-30 16:44:38 +01:00
|
|
|
declare( strict_types=1 );
|
2022-08-05 15:41:38 +00:00
|
|
|
|
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;
|
2024-03-30 16:44:38 +01:00
|
|
|
use Hosterra\FluxBuilder\Expression\KeyFilter;
|
2022-09-01 15:17:25 +00:00
|
|
|
|
2024-03-30 16:44:38 +01:00
|
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
|
use Hosterra\FluxBuilder\QueryBuilder;
|
|
|
|
|
use Hosterra\FluxBuilder\Type\MathType;
|
|
|
|
|
|
|
|
|
|
final class QueryBuilderTest extends TestCase {
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider newTestsProvider
|
|
|
|
|
*/
|
|
|
|
|
public function testBasicQuery( string $methodName, array $params = [], string $expected = '' ) {
|
|
|
|
|
$queryBuilder = new QueryBuilder();
|
|
|
|
|
$queryBuilder->fromBucket( 'test_bucket' )
|
|
|
|
|
->addRangeStart( new DateTime( '2022-08-12 17:31:00' ) )
|
|
|
|
|
->fromMeasurement( 'test_measurement' );
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
$this->assertEquals( $expectedQuery, $queryBuilder->build() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function newTestsProvider(): array {
|
|
|
|
|
return [
|
|
|
|
|
'addAggregateWindow' => [
|
|
|
|
|
'addAggregateWindow',
|
|
|
|
|
[ '20s', 'mean', [ 'timeDst' => '_time' ] ],
|
|
|
|
|
'|> aggregateWindow(every: 20s, fn: mean, timeDst: "_time") '
|
|
|
|
|
],
|
|
|
|
|
'addBottom' => [
|
|
|
|
|
'addBottom',
|
|
|
|
|
[ 2, [ '_value' ] ],
|
|
|
|
|
'|> bottom(n: 2, columns: ["_value"]) '
|
|
|
|
|
],
|
|
|
|
|
'addCount' => [
|
|
|
|
|
'addCount',
|
|
|
|
|
[ '_value' ],
|
|
|
|
|
'|> count(column: "_value") '
|
|
|
|
|
],
|
|
|
|
|
'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") '
|
|
|
|
|
],
|
|
|
|
|
'addFirst' => [
|
|
|
|
|
'addFirst',
|
|
|
|
|
[ 'something' ],
|
|
|
|
|
'|> first(column: "something") '
|
|
|
|
|
],
|
|
|
|
|
'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 })) '
|
|
|
|
|
],
|
|
|
|
|
'addMax' => [
|
|
|
|
|
'addMax',
|
|
|
|
|
[],
|
|
|
|
|
'|> max() '
|
|
|
|
|
],
|
|
|
|
|
'addMean' => [
|
|
|
|
|
'addMean',
|
|
|
|
|
[ 'something' ],
|
|
|
|
|
'|> mean(column: "something") '
|
|
|
|
|
],
|
|
|
|
|
'addMin' => [
|
|
|
|
|
'addMin',
|
|
|
|
|
[ 'something' ],
|
|
|
|
|
'|> min(column: "something") '
|
|
|
|
|
],
|
|
|
|
|
'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") '
|
|
|
|
|
],
|
|
|
|
|
'addUnique' => [
|
|
|
|
|
'addUnique',
|
|
|
|
|
[ 'something' ],
|
|
|
|
|
'|> unique(column: "something") '
|
|
|
|
|
],
|
|
|
|
|
'addUnwindow' => [
|
|
|
|
|
'addUnwindow',
|
|
|
|
|
[],
|
|
|
|
|
'|> window(every: inf) '
|
|
|
|
|
],
|
|
|
|
|
'addWindow' => [
|
|
|
|
|
'addWindow',
|
|
|
|
|
[ '20s', [ 'timeColumn' => '_time' ] ],
|
|
|
|
|
'|> window(every: 20s, timeColumn: "_time") '
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider throwsExceptionWithoutRequiredDataProvider
|
|
|
|
|
*/
|
|
|
|
|
public function testThrowsExceptionWithoutRequiredData( $from, $measurement, $range ) {
|
|
|
|
|
$this->expectException( Exception::class );
|
|
|
|
|
|
|
|
|
|
$queryBuilder = new QueryBuilder();
|
|
|
|
|
|
|
|
|
|
if ( $from ) {
|
|
|
|
|
$queryBuilder->from( $from );
|
|
|
|
|
}
|
|
|
|
|
if ( $range ) {
|
|
|
|
|
$queryBuilder->addRangeStart( $range['start'] );
|
|
|
|
|
}
|
|
|
|
|
if ( $measurement ) {
|
|
|
|
|
$queryBuilder->fromMeasurement( $measurement );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$queryBuilder->build();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function throwsExceptionWithoutRequiredDataProvider(): array {
|
|
|
|
|
return [
|
|
|
|
|
'without from data' => [
|
|
|
|
|
null,
|
|
|
|
|
'test_measurement',
|
|
|
|
|
[ 'start' => new DateTime( '2022-08-12 20:05:00' ) ],
|
|
|
|
|
],
|
|
|
|
|
'without measurement data' => [
|
|
|
|
|
[ 'from' => 'test_bucket' ],
|
|
|
|
|
null,
|
|
|
|
|
[ 'start' => new DateTime( '2022-08-12 20:05:00' ) ],
|
|
|
|
|
],
|
|
|
|
|
'without range data' => [
|
|
|
|
|
[ 'from' => 'test_bucket' ],
|
|
|
|
|
'test_measurement',
|
|
|
|
|
null,
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testQueryWithWindow() {
|
|
|
|
|
$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 ] )
|
|
|
|
|
->addWindow( '20s' )
|
|
|
|
|
->addMean()
|
|
|
|
|
->addDuplicate( 'tag', 'tag_dup' )
|
|
|
|
|
->fromMeasurement( 'test_measurement' )
|
|
|
|
|
->addFilter( KeyValue::setGreaterOrEqualTo( 'count', 2 )->andGreaterOrEqualTo( 'count2', 3 ) )
|
|
|
|
|
->addKeyFilter( KeyFilter::setGreaterOrEqualTo( 'count', 1 )->andGreaterOrEqualTo( 'count2', 2 ) )
|
|
|
|
|
->addUnWindow();
|
|
|
|
|
|
|
|
|
|
$expectedQuery = 'from(bucket: "test_bucket") |> range(start: time(v: 2022-08-12T17:31:00Z)) ' .
|
|
|
|
|
'|> 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") ' .
|
|
|
|
|
'|> filter(fn: (r) => r.count >= 2 and r.count2 >= 3) ' .
|
|
|
|
|
'|> filter(fn: (r) => r.count >= 1 and r.count2 >= 2) |> window(every: inf) ';
|
|
|
|
|
|
|
|
|
|
$this->assertEquals( $expectedQuery, $queryBuilder->build() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testCorrectInstancesAreCreated() {
|
|
|
|
|
$queryBuilder = new QueryBuilder();
|
|
|
|
|
$queryBuilder->fromBucket( 'test_bucket' )
|
|
|
|
|
->addRangeStart( new DateTime( '2022-08-12 17:31:00' ) );
|
|
|
|
|
|
|
|
|
|
$instances = [
|
|
|
|
|
\Hosterra\FluxBuilder\Functions\From::class,
|
|
|
|
|
\Hosterra\FluxBuilder\Functions\Range::class,
|
|
|
|
|
\Hosterra\FluxBuilder\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
|
|
|
}
|