Skip to content

Commit 2335513

Browse files
authored
Match cases scalar function (#1720)
* Match cases scalar function * Renamed DSL functions accordingly to community preferences
1 parent cae6ea6 commit 2335513

File tree

6 files changed

+192
-3
lines changed

6 files changed

+192
-3
lines changed

src/core/etl/src/Flow/ETL/DSL/functions.php

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@
8080
use Flow\ETL\Extractor\SequenceGenerator\{DatePeriodSequenceGenerator, NumberSequenceGenerator};
8181
use Flow\ETL\Filesystem\{SaveMode};
8282
use Flow\ETL\Formatter\AsciiTableFormatter;
83-
use Flow\ETL\Function\{All,
83+
use Flow\ETL\Function\{
84+
All,
8485
Any,
8586
ArrayGet,
8687
ArrayGetCollection,
@@ -114,6 +115,7 @@
114115
Least,
115116
ListFunctions,
116117
Literal,
118+
MatchCases,
117119
Max,
118120
Min,
119121
Not,
@@ -145,10 +147,12 @@
145147
ToUpper,
146148
Ulid,
147149
Uuid,
148-
When};
150+
When
151+
};
149152
use Flow\ETL\Function\ArrayExpand\ArrayExpand;
150153
use Flow\ETL\Function\ArraySort\Sort;
151154
use Flow\ETL\Function\Between\Boundary;
155+
use Flow\ETL\Function\MatchCases\MatchCase;
152156
use Flow\ETL\Loader\{ArrayLoader, CallbackLoader, MemoryLoader, StreamLoader, TransformerLoader};
153157
use Flow\ETL\Loader\BranchingLoader;
154158
use Flow\ETL\Loader\StreamLoader\Output;
@@ -2031,3 +2035,15 @@ function analyze() : Analyze
20312035
{
20322036
return new Analyze();
20332037
}
2038+
2039+
#[DocumentationDSL(module: Module::CORE, type: DSLType::SCALAR_FUNCTION)]
2040+
function match_cases(array $cases, mixed $default = null) : MatchCases
2041+
{
2042+
return new MatchCases($cases, $default);
2043+
}
2044+
2045+
#[DocumentationDSL(module: Module::CORE, type: DSLType::SCALAR_FUNCTION)]
2046+
function match_condition(mixed $condition, mixed $then) : MatchCase
2047+
{
2048+
return new MatchCase($condition, $then);
2049+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Flow\ETL\Function;
6+
7+
use Flow\ETL\Exception\RuntimeException;
8+
use Flow\ETL\Function\MatchCases\MatchCase;
9+
use Flow\ETL\Row;
10+
11+
final class MatchCases extends ScalarFunctionChain
12+
{
13+
/**
14+
* @param array<MatchCase> $cases
15+
* @param MatchCase $default
16+
*/
17+
public function __construct(private readonly array $cases, private readonly mixed $default = null)
18+
{
19+
20+
}
21+
22+
public function eval(Row $row) : mixed
23+
{
24+
foreach ($this->cases as $condition) {
25+
if ($condition->valid($row)) {
26+
return $condition->eval($row);
27+
}
28+
}
29+
30+
if ($this->default) {
31+
return $this->default->eval($row);
32+
}
33+
34+
throw new RuntimeException(
35+
'Not a single case matches row, consider using default parameter, row: '
36+
. \json_encode($row->toArray(), JSON_THROW_ON_ERROR)
37+
);
38+
}
39+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Flow\ETL\Function\MatchCases;
6+
7+
use Flow\ETL\Function\{Parameter, ScalarFunction};
8+
use Flow\ETL\Row;
9+
10+
final readonly class MatchCase implements ScalarFunction
11+
{
12+
public function __construct(
13+
private mixed $condition,
14+
private mixed $then,
15+
) {
16+
}
17+
18+
public function eval(Row $row) : mixed
19+
{
20+
return (new Parameter($this->then))->eval($row);
21+
}
22+
23+
public function valid(Row $row) : bool
24+
{
25+
return (new Parameter($this->condition))->asBoolean($row);
26+
}
27+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Flow\ETL\Tests\Integration\Function;
6+
7+
use function Flow\ETL\DSL\{df, from_rows, lit, match_cases, match_condition, ref, row, rows, string_entry};
8+
use function Flow\Types\DSL\type_integer;
9+
use Flow\ETL\Tests\FlowTestCase;
10+
11+
final class MatchCasesTest extends FlowTestCase
12+
{
13+
public function test_case_match() : void
14+
{
15+
$rows = rows(
16+
row(string_entry('string', 'string-with-dashes')),
17+
row(string_entry('string', '123')),
18+
row(string_entry('string', '14%')),
19+
row(string_entry('string', '+14')),
20+
row(string_entry('string', ''))
21+
);
22+
23+
$output = df()
24+
->read(from_rows($rows))
25+
->withEntry(
26+
'string',
27+
match_cases(
28+
[
29+
match_condition(ref('string')->contains('-'), ref('string')->strReplace('-', ' ')),
30+
match_condition(ref('string')->call('is_numeric'), ref('string')->cast(type_integer())),
31+
match_condition(ref('string')->endsWith('%'), ref('string')->strReplace('%', '')->cast(type_integer())),
32+
match_condition(ref('string')->startsWith('+'), ref('string')->strReplace('+', '')->cast(type_integer())),
33+
],
34+
default: lit('DEFAULT')
35+
)
36+
)
37+
->fetch()
38+
->toArray();
39+
40+
self::assertSame(
41+
[
42+
['string' => 'string with dashes'],
43+
['string' => 123],
44+
['string' => 14],
45+
['string' => 14],
46+
['string' => 'DEFAULT'],
47+
],
48+
$output
49+
);
50+
}
51+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Flow\ETL\Tests\Unit\Function;
6+
7+
use function Flow\ETL\DSL\{lit, match_cases, match_condition, ref, row, str_entry};
8+
use Flow\ETL\Tests\FlowTestCase;
9+
10+
final class MatchCasesTest extends FlowTestCase
11+
{
12+
public function test_case_match() : void
13+
{
14+
$match = match_cases([
15+
match_condition(ref('string')->contains('_'), ref('string')->strReplace('_', ' ')),
16+
match_condition(ref('string')->contains('-'), ref('string')->strReplace('-', ' ')),
17+
]);
18+
19+
self::assertSame(
20+
'this is slug',
21+
$match->eval(row(str_entry('string', 'this-is-slug')))
22+
);
23+
self::assertSame(
24+
'this is slug',
25+
$match->eval(row(str_entry('string', 'this_is_slug')))
26+
);
27+
}
28+
29+
public function test_not_matching_anything() : void
30+
{
31+
$match = match_cases([
32+
match_condition(ref('string')->contains('_'), ref('string')->strReplace('_', ' ')),
33+
match_condition(ref('string')->contains('-'), ref('string')->strReplace('-', ' ')),
34+
]);
35+
36+
$this->expectExceptionMessage('Not a single case matches row, consider using default parameter, row: {"string":"weirdstring"}');
37+
38+
$match->eval(row(str_entry('string', 'weirdstring')));
39+
}
40+
41+
public function test_not_matching_anything_with_default() : void
42+
{
43+
$match = match_cases(
44+
[
45+
match_condition(ref('string')->contains('_'), ref('string')->strReplace('_', ' ')),
46+
match_condition(ref('string')->contains('-'), ref('string')->strReplace('-', ' ')),
47+
],
48+
default: lit('normal string')
49+
);
50+
51+
self::assertEquals(
52+
'normal string',
53+
$match->eval(row(str_entry('string', 'weirdstring')))
54+
);
55+
}
56+
}

web/landing/resources/dsl.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)