Skip to content

Commit

Permalink
Add greatest and least scalar functions (#1320)
Browse files Browse the repository at this point in the history
* Added greatest and least scalar functions

* Updated dsl json

* Fixed failing tests
  • Loading branch information
norberttech authored Jan 3, 2025
1 parent 4ed793f commit 9f418b6
Show file tree
Hide file tree
Showing 7 changed files with 211 additions and 1 deletion.
14 changes: 14 additions & 0 deletions src/core/etl/src/Flow/ETL/DSL/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@
DenseRank,
Exists,
First,
Greatest,
Hash,
Last,
Least,
ListFunctions,
Literal,
Max,
Expand Down Expand Up @@ -1165,6 +1167,18 @@ function average(EntryReference|string $ref) : Average
return new Average(is_string($ref) ? ref($ref) : $ref);
}

#[DocumentationDSL(module: Module::CORE, type: DSLType::SCALAR_FUNCTION)]
function greatest(mixed ...$values) : Greatest
{
return new Greatest($values);
}

#[DocumentationDSL(module: Module::CORE, type: DSLType::SCALAR_FUNCTION)]
function least(mixed ...$values) : Least
{
return new Least($values);
}

#[DocumentationDSL(module: Module::CORE, type: DSLType::AGGREGATING_FUNCTION)]
function collect(EntryReference|string $ref) : Collect
{
Expand Down
25 changes: 25 additions & 0 deletions src/core/etl/src/Flow/ETL/Function/Comparison/Comparable.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,31 @@

trait Comparable
{
public function assertAllComparable(array $values, string $symbol) : void
{
$detector = new TypeDetector();

$types = [];

foreach ($values as $value) {
$type = $detector->detectType($value);

if (!in_array($type, $types, true)) {
$types[] = $type;
}
}

if (count($types) > 1) {
foreach ($types as $nextType) {
foreach ($types as $baseType) {
if (!$baseType->isComparableWith($nextType)) {
throw new InvalidArgumentException(\sprintf("Can't compare '(%s %s %s)' due to data type mismatch.", $baseType->toString(), $symbol, $nextType->toString()));
}
}
}
}
}

public function assertComparable(mixed $left, mixed $right, string $symbol) : void
{
$detector = new TypeDetector();
Expand Down
31 changes: 31 additions & 0 deletions src/core/etl/src/Flow/ETL/Function/Greatest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace Flow\ETL\Function;

use Flow\ETL\Function\Comparison\Comparable;
use Flow\ETL\Row;

final class Greatest extends ScalarFunctionChain
{
use Comparable;

public function __construct(
private readonly array $values,
) {
}

public function eval(Row $row) : mixed
{
$extractedValues = [];

foreach ($this->values as $value) {
$extractedValues[] = (new Parameter($value))->eval($row);
}

$this->assertAllComparable($extractedValues, '>');

return max($extractedValues);
}
}
31 changes: 31 additions & 0 deletions src/core/etl/src/Flow/ETL/Function/Least.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace Flow\ETL\Function;

use Flow\ETL\Function\Comparison\Comparable;
use Flow\ETL\Row;

final class Least extends ScalarFunctionChain
{
use Comparable;

public function __construct(
private readonly array $values,
) {
}

public function eval(Row $row) : mixed
{
$extractedValues = [];

foreach ($this->values as $value) {
$extractedValues[] = (new Parameter($value))->eval($row);
}

$this->assertAllComparable($extractedValues, '<');

return \min($extractedValues);
}
}
55 changes: 55 additions & 0 deletions src/core/etl/tests/Flow/ETL/Tests/Unit/Function/GreatestTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

declare(strict_types=1);

namespace Flow\ETL\Tests\Unit\Function;

use function Flow\ETL\DSL\{greatest, int_entry, ref, row};
use Flow\ETL\Tests\FlowTestCase;

final class GreatestTest extends FlowTestCase
{
public function test_greatest_value() : void
{
$greatest = greatest(
10,
20,
ref('int'),
40
);

self::assertSame(
55,
$greatest->eval(row(int_entry('int', 55)))
);
}

public function test_greatest_with_non_comparable_values() : void
{
$greatest = greatest(
null,
20,
ref('int'),
new \DateTimeImmutable('now')
);

$this->expectExceptionMessage("Can't compare '(datetime > integer)' due to data type mismatch.");

$greatest->eval(row(int_entry('int', 55)));
}

public function test_greatest_with_null() : void
{
$greatest = greatest(
null,
20,
ref('int'),
1257
);

self::assertSame(
1257,
$greatest->eval(row(int_entry('int', 55)))
);
}
}
54 changes: 54 additions & 0 deletions src/core/etl/tests/Flow/ETL/Tests/Unit/Function/LeastTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

declare(strict_types=1);

namespace Flow\ETL\Tests\Unit\Function;

use function Flow\ETL\DSL\{int_entry, least, ref, row};
use Flow\ETL\Tests\FlowTestCase;

final class LeastTest extends FlowTestCase
{
public function test_greatest_with_non_comparable_values() : void
{
$lest = least(
null,
20,
ref('int'),
new \DateTimeImmutable('now'),
);

$this->expectExceptionMessage("Can't compare '(datetime < integer)' due to data type mismatch.");

$lest->eval(row(int_entry('int', 55)));
}

public function test_least_value() : void
{
$lest = least(
10,
20,
ref('int'),
40,
);

self::assertSame(
10,
$lest->eval(row(int_entry('int', 55)))
);
}

public function test_least_with_null() : void
{
$ltest = least(
null,
20,
ref('int'),
1257
);

self::assertNull(
$ltest->eval(row(int_entry('int', 4)))
);
}
}
2 changes: 1 addition & 1 deletion web/landing/resources/dsl.json

Large diffs are not rendered by default.

0 comments on commit 9f418b6

Please sign in to comment.