Throw Exception if the required functions are defined in incorrect order

Signed-off-by: davidarendsen <davidarendsen@hey.com>
This commit is contained in:
davidarendsen 2022-09-01 15:17:25 +00:00
commit 3a112f4e79
4 changed files with 103 additions and 68 deletions

View file

@ -19,8 +19,8 @@ use Arendsen\FluxQueryBuilder\Expression\KeyValue;
$queryBuilder = new QueryBuilder(); $queryBuilder = new QueryBuilder();
$queryBuilder->fromBucket('test_bucket') $queryBuilder->fromBucket('test_bucket')
->fromMeasurement('test_measurement')
->addRangeStart(new DateTime('3 hours ago')) ->addRangeStart(new DateTime('3 hours ago'))
->fromMeasurement('test_measurement')
->addFilter( ->addFilter(
KeyValue::setEqualTo('_field', 'username') KeyValue::setEqualTo('_field', 'username')
->orEqualTo('_field', 'email') ->orEqualTo('_field', 'email')
@ -39,7 +39,7 @@ composer test
## Coding style ## Coding style
Run the following commands to check the coding style. We're using the PSR12 standard. Run the following commands to check and fix the coding style. We're using the PSR12 standard.
``` ```
composer check composer check

View file

@ -0,0 +1,21 @@
<?php
namespace Arendsen\FluxQueryBuilder\Functions;
class RawFunction extends Base
{
/**
* @var string $input
*/
private $input;
public function __construct(string $input)
{
$this->input = $input;
}
public function __toString()
{
return $this->input . ' ';
}
}

View file

@ -16,6 +16,7 @@ use Arendsen\FluxQueryBuilder\Functions\Map;
use Arendsen\FluxQueryBuilder\Functions\Group; use Arendsen\FluxQueryBuilder\Functions\Group;
use Arendsen\FluxQueryBuilder\Functions\Limit; use Arendsen\FluxQueryBuilder\Functions\Limit;
use Arendsen\FluxQueryBuilder\Functions\Mean; use Arendsen\FluxQueryBuilder\Functions\Mean;
use Arendsen\FluxQueryBuilder\Functions\RawFunction;
use Arendsen\FluxQueryBuilder\Functions\Window; use Arendsen\FluxQueryBuilder\Functions\Window;
class QueryBuilder class QueryBuilder
@ -33,33 +34,23 @@ class QueryBuilder
public const FLUX_PART_DUPLICATE = 'duplicate'; public const FLUX_PART_DUPLICATE = 'duplicate';
public const FLUX_PART_UNWINDOW = 'unwindow'; public const FLUX_PART_UNWINDOW = 'unwindow';
public const FLUX_PART_AGGREGATEWINDOW = 'aggregateWindow'; public const FLUX_PART_AGGREGATEWINDOW = 'aggregateWindow';
public const FLUX_PART_RAWFUNCTION = 'raw';
public const PARTS = [
self::FLUX_PART_FROM,
self::FLUX_PART_RANGE,
self::FLUX_PART_REDUCE,
self::FLUX_PART_WINDOW,
self::FLUX_PART_MEAN,
self::FLUX_PART_DUPLICATE,
self::FLUX_PART_FILTERS,
self::FLUX_PART_MAP,
self::FLUX_PART_SORT,
self::FLUX_PART_GROUP,
self::FLUX_PART_LIMIT,
self::FLUX_PART_UNWINDOW,
self::FLUX_PART_AGGREGATEWINDOW,
];
public const REQUIRED_INPUT_FROM = 'from'; public const REQUIRED_INPUT_FROM = 'from';
public const REQUIRED_INPUT_MEASUREMENT = 'measurement';
public const REQUIRED_INPUT_RANGE = 'range'; public const REQUIRED_INPUT_RANGE = 'range';
public const REQUIRED_INPUT_MEASUREMENT = 'measurement';
public const REQUIRED_INPUT = [ public const REQUIRED_INPUT = [
self::REQUIRED_INPUT_FROM, self::REQUIRED_INPUT_FROM,
self::REQUIRED_INPUT_MEASUREMENT,
self::REQUIRED_INPUT_RANGE, self::REQUIRED_INPUT_RANGE,
self::REQUIRED_INPUT_MEASUREMENT,
]; ];
/**
* @var int $currentFluxQueryPart
*/
private $currentFluxQueryPart = 0;
/** /**
* @var array $fluxQuery * @var array $fluxQuery
*/ */
@ -89,16 +80,13 @@ class QueryBuilder
public function fromMeasurement(string $measurement): QueryBuilder public function fromMeasurement(string $measurement): QueryBuilder
{ {
$this->addRequiredData(self::REQUIRED_INPUT_MEASUREMENT, $measurement); $this->addRequiredData(self::REQUIRED_INPUT_MEASUREMENT, $measurement);
$this->addToQueryArray( $this->addFilter(KeyValue::setEqualTo('_measurement', $measurement));
self::FLUX_PART_FILTERS,
new Filter(KeyValue::setEqualTo('_measurement', $measurement))
);
return $this; return $this;
} }
public function addFilter(KeyValue $keyValue): QueryBuilder public function addFilter(KeyValue $keyValue): QueryBuilder
{ {
$this->addToQueryArray( $this->addToQuery(
self::FLUX_PART_FILTERS, self::FLUX_PART_FILTERS,
new Filter($keyValue) new Filter($keyValue)
); );
@ -107,7 +95,7 @@ class QueryBuilder
public function addFieldFilter(array $fields): QueryBuilder public function addFieldFilter(array $fields): QueryBuilder
{ {
$this->addToQueryArray( $this->addToQuery(
self::FLUX_PART_FILTERS, self::FLUX_PART_FILTERS,
new Filter($fields) new Filter($fields)
); );
@ -138,7 +126,7 @@ class QueryBuilder
public function addReduce(array $settings, array $identity): QueryBuilder public function addReduce(array $settings, array $identity): QueryBuilder
{ {
$this->addToQueryArray( $this->addToQuery(
self::FLUX_PART_REDUCE, self::FLUX_PART_REDUCE,
new Reduce($settings, $identity) new Reduce($settings, $identity)
); );
@ -147,7 +135,7 @@ class QueryBuilder
public function addSort(array $columns, $desc): QueryBuilder public function addSort(array $columns, $desc): QueryBuilder
{ {
$this->addToQueryArray( $this->addToQuery(
self::FLUX_PART_SORT, self::FLUX_PART_SORT,
new Sort($columns, $desc) new Sort($columns, $desc)
); );
@ -156,7 +144,7 @@ class QueryBuilder
public function addMap(string $query): QueryBuilder public function addMap(string $query): QueryBuilder
{ {
$this->addToQueryArray( $this->addToQuery(
self::FLUX_PART_MAP, self::FLUX_PART_MAP,
new Map($query) new Map($query)
); );
@ -165,7 +153,7 @@ class QueryBuilder
public function addGroup(array $columns, $mode = 'by'): QueryBuilder public function addGroup(array $columns, $mode = 'by'): QueryBuilder
{ {
$this->addToQueryArray( $this->addToQuery(
self::FLUX_PART_GROUP, self::FLUX_PART_GROUP,
new Group($columns, $mode) new Group($columns, $mode)
); );
@ -174,7 +162,7 @@ class QueryBuilder
public function addLimit(int $limit): QueryBuilder public function addLimit(int $limit): QueryBuilder
{ {
$this->addToQueryArray( $this->addToQuery(
self::FLUX_PART_LIMIT, self::FLUX_PART_LIMIT,
new Limit($limit) new Limit($limit)
); );
@ -226,14 +214,19 @@ class QueryBuilder
return $this; return $this;
} }
protected function addToQuery($key, $query) public function addRawFunction(string $input): QueryBuilder
{ {
$this->fluxQueryParts[$key] = $query; $this->addToQuery(
self::FLUX_PART_RAWFUNCTION,
new RawFunction($input)
);
return $this;
} }
protected function addToQueryArray($key, $query) protected function addToQuery($key, $query)
{ {
$this->fluxQueryParts[$key][] = $query; $this->fluxQueryParts[$this->currentFluxQueryPart] = $query;
$this->currentFluxQueryPart++;
} }
public function build(): string public function build(): string
@ -242,16 +235,8 @@ class QueryBuilder
$query = ''; $query = '';
foreach (self::PARTS as $part) { foreach ($this->fluxQueryParts as $part) {
if (isset($this->fluxQueryParts[$part])) { $query .= $part;
if (is_array($this->fluxQueryParts[$part])) {
foreach ($this->fluxQueryParts[$part] as $filter) {
$query .= $filter;
}
} else {
$query .= $this->fluxQueryParts[$part];
}
}
} }
return $query; return $query;
@ -259,13 +244,13 @@ class QueryBuilder
protected function addRequiredData(string $key, $value) protected function addRequiredData(string $key, $value)
{ {
$this->requiredData[$key] = $value; $this->requiredData[][$key] = $value;
} }
protected function checkRequired() protected function checkRequired()
{ {
foreach (self::REQUIRED_INPUT as $input) { foreach (self::REQUIRED_INPUT as $key => $input) {
if (!isset($this->requiredData[$input])) { if (!isset($this->requiredData[$key][$input])) {
throw new Exception('You need to define the "' . $input . '" part of the query!'); throw new Exception('You need to define the "' . $input . '" part of the query!');
} }
} }

View file

@ -20,8 +20,8 @@ final class QueryBuilderTest extends TestCase
{ {
$queryBuilder = new QueryBuilder(); $queryBuilder = new QueryBuilder();
$queryBuilder->from($bucket) $queryBuilder->from($bucket)
->fromMeasurement($measurement) ->addRangeStart($range)
->addRangeStart($range); ->fromMeasurement($measurement);
if ($keyValue) { if ($keyValue) {
$queryBuilder->addFilter($keyValue); $queryBuilder->addFilter($keyValue);
@ -69,12 +69,12 @@ final class QueryBuilderTest extends TestCase
if ($from) { if ($from) {
$queryBuilder->from($from); $queryBuilder->from($from);
} }
if ($measurement) {
$queryBuilder->fromMeasurement($measurement);
}
if ($range) { if ($range) {
$queryBuilder->addRangeStart($range['start']); $queryBuilder->addRangeStart($range['start']);
} }
if ($measurement) {
$queryBuilder->fromMeasurement($measurement);
}
$queryBuilder->build(); $queryBuilder->build();
} }
@ -94,17 +94,29 @@ final class QueryBuilderTest extends TestCase
]; ];
} }
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 testComplexQuery() public function testComplexQuery()
{ {
$queryBuilder = new QueryBuilder(); $queryBuilder = new QueryBuilder();
$queryBuilder->fromBucket('test_bucket') $queryBuilder->fromBucket('test_bucket')
->fromMeasurement('test_measurement')
->addRangeStart(new DateTime('2022-08-12 17:31:00')) ->addRangeStart(new DateTime('2022-08-12 17:31:00'))
->addFieldFilter(['username', 'ip'])
->addMap('r with name: r.user')
->addGroup(['_field', 'ip'])
->addReduce(['count' => new MathType('accumulator.count + 1')], ['count' => 0]) ->addReduce(['count' => new MathType('accumulator.count + 1')], ['count' => 0])
->addFilter(KeyValue::setGreaterOrEqualTo('count', 1)->andGreaterOrEqualTo('count2', 2)); ->fromMeasurement('test_measurement')
->addFieldFilter(['username', 'ip'])
->addFilter(KeyValue::setGreaterOrEqualTo('count', 1)->andGreaterOrEqualTo('count2', 2))
->addMap('r with name: r.user')
->addGroup(['_field', 'ip']);
$expectedQuery = 'from(bucket: "test_bucket") |> range(start: time(v: 2022-08-12T17:31:00Z)) ' . $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}) ' . '|> reduce(fn: (r, accumulator) => ({count: accumulator.count + 1}), identity: {count: 0}) ' .
@ -119,20 +131,20 @@ final class QueryBuilderTest extends TestCase
{ {
$queryBuilder = new QueryBuilder(); $queryBuilder = new QueryBuilder();
$queryBuilder->fromBucket('test_bucket') $queryBuilder->fromBucket('test_bucket')
->fromMeasurement('test_measurement')
->addRangeStart(new DateTime('2022-08-12 17:31:00')) ->addRangeStart(new DateTime('2022-08-12 17:31:00'))
->addWindow('20s')
->addReduce(['count' => new MathType('accumulator.count + 1')], ['count' => 0]) ->addReduce(['count' => new MathType('accumulator.count + 1')], ['count' => 0])
->addWindow('20s')
->addMean() ->addMean()
->addDuplicate('tag', 'tag_dup') ->addDuplicate('tag', 'tag_dup')
->fromMeasurement('test_measurement')
->addFilter(KeyValue::setGreaterOrEqualTo('count', 1)->andGreaterOrEqualTo('count2', 2)) ->addFilter(KeyValue::setGreaterOrEqualTo('count', 1)->andGreaterOrEqualTo('count2', 2))
->addUnWindow(); ->addUnWindow();
$expectedQuery = 'from(bucket: "test_bucket") |> range(start: time(v: 2022-08-12T17:31:00Z)) ' . $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}) ' . '|> reduce(fn: (r, accumulator) => ({count: accumulator.count + 1}), identity: {count: 0}) ' .
'|> window(every: 20s) |> mean() |> duplicate(column: "tag", as: "tag_dup") ' . '|> window(every: 20s) |> mean() |> duplicate(column: "tag", as: "tag_dup") ' .
'|> filter(fn: (r) => r._measurement == "test_measurement") ' . '|> filter(fn: (r) => r._measurement == "test_measurement") ' .
'|> filter(fn: (r) => r.count >= 1 and r.count2 >= 2) |> window(every: inf) '; '|> filter(fn: (r) => r.count >= 1 and r.count2 >= 2) |> window(every: inf) ';
$this->assertEquals($expectedQuery, $queryBuilder->build()); $this->assertEquals($expectedQuery, $queryBuilder->build());
} }
@ -141,15 +153,32 @@ final class QueryBuilderTest extends TestCase
{ {
$queryBuilder = new QueryBuilder(); $queryBuilder = new QueryBuilder();
$queryBuilder->fromBucket('test_bucket') $queryBuilder->fromBucket('test_bucket')
->fromMeasurement('test_measurement')
->addRangeStart(new DateTime('2022-08-12 17:31:00')) ->addRangeStart(new DateTime('2022-08-12 17:31:00'))
->addAggregateWindow('20s', 'mean', ['timeDst' => '_time']) ->addReduce(['count' => new MathType('accumulator.count + 1')], ['count' => 0])
->addReduce(['count' => new MathType('accumulator.count + 1')], ['count' => 0]); ->fromMeasurement('test_measurement')
->addAggregateWindow('20s', 'mean', ['timeDst' => '_time']);
$expectedQuery = 'from(bucket: "test_bucket") |> range(start: time(v: 2022-08-12T17:31:00Z)) ' . $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}) ' . '|> reduce(fn: (r, accumulator) => ({count: accumulator.count + 1}), identity: {count: 0}) ' .
'|> filter(fn: (r) => r._measurement == "test_measurement") ' . '|> filter(fn: (r) => r._measurement == "test_measurement") ' .
'|> aggregateWindow(every: 20s, fn: mean, timeDst: "_time") '; '|> aggregateWindow(every: 20s, fn: mean, timeDst: "_time") ';
$this->assertEquals($expectedQuery, $queryBuilder->build());
}
public function testRawQuery()
{
$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])
->fromMeasurement('test_measurement')
->addRawFunction('|> aggregateWindow(every: 20s, fn: mean, timeDst: "_time")');
$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}) ' .
'|> filter(fn: (r) => r._measurement == "test_measurement") ' .
'|> aggregateWindow(every: 20s, fn: mean, timeDst: "_time") ';
$this->assertEquals($expectedQuery, $queryBuilder->build()); $this->assertEquals($expectedQuery, $queryBuilder->build());
} }