Skip to content

Commit 386ed87

Browse files
authored
Added concat_ws (#1385)
1 parent 9f4bb17 commit 386ed87

File tree

7 files changed

+193
-7
lines changed

7 files changed

+193
-7
lines changed

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
CollectUnique,
3636
Combine,
3737
Concat,
38+
ConcatWithSeparator,
3839
Count,
3940
DateTimeFormat,
4041
DenseRank,
@@ -832,12 +833,24 @@ function combine(ScalarFunction|array $keys, ScalarFunction|array $values) : Com
832833
return new Combine($keys, $values);
833834
}
834835

836+
/**
837+
* Concat all values. If you want to concatenate values with separator use concat_ws function.
838+
*/
835839
#[DocumentationDSL(module: Module::CORE, type: DSLType::SCALAR_FUNCTION)]
836840
function concat(ScalarFunction|string ...$functions) : Concat
837841
{
838842
return new Concat(...$functions);
839843
}
840844

845+
/**
846+
* Concat all values with separator.
847+
*/
848+
#[DocumentationDSL(module: Module::CORE, type: DSLType::SCALAR_FUNCTION)]
849+
function concat_ws(ScalarFunction|string ...$functions) : ConcatWithSeparator
850+
{
851+
return new ConcatWithSeparator(...$functions);
852+
}
853+
841854
#[DocumentationDSL(module: Module::CORE, type: DSLType::SCALAR_FUNCTION)]
842855
function hash(mixed $value, Algorithm $algorithm = new NativePHPHash()) : Hash
843856
{

src/core/etl/src/Flow/ETL/Function/Concat.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,19 @@ public function eval(Row $row) : mixed
2525
{
2626
$values = \array_map(fn (ScalarFunction|string $string) : mixed => \is_string($string) ? $string : Caster::default()->to(type_string(true))->value($string->eval($row)), $this->refs);
2727

28+
$concatValues = [];
29+
2830
foreach ($values as $value) {
29-
if (!\is_string($value)) {
30-
return null;
31+
if (\is_string($value)) {
32+
$concatValues[] = $value;
3133
}
3234
}
3335

36+
if (\count($concatValues) === 0) {
37+
return '';
38+
}
39+
3440
/** @var array<string> $values */
35-
return \implode('', $values);
41+
return \implode('', $concatValues);
3642
}
3743
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Flow\ETL\Function;
6+
7+
use function Flow\ETL\DSL\type_string;
8+
use Flow\ETL\PHP\Type\Caster;
9+
use Flow\ETL\Row;
10+
11+
final class ConcatWithSeparator extends ScalarFunctionChain
12+
{
13+
/**
14+
* @var array<ScalarFunction|string>
15+
*/
16+
private readonly array $refs;
17+
18+
public function __construct(
19+
private readonly ScalarFunction|string $separator,
20+
ScalarFunction|string ...$refs,
21+
) {
22+
$this->refs = $refs;
23+
}
24+
25+
public function eval(Row $row) : mixed
26+
{
27+
$separator = (new Parameter($this->separator))->asString($row);
28+
29+
if (!\is_string($separator)) {
30+
return '';
31+
}
32+
33+
$values = \array_map(fn (ScalarFunction|string $string) : mixed => \is_string($string) ? $string : Caster::default()->to(type_string(true))->value($string->eval($row)), $this->refs);
34+
35+
$concatValues = [];
36+
37+
foreach ($values as $value) {
38+
if (\is_string($value)) {
39+
$concatValues[] = $value;
40+
}
41+
}
42+
43+
if (\count($concatValues) === 0) {
44+
return '';
45+
}
46+
47+
/** @var array<string> $values */
48+
return \implode($separator, $concatValues);
49+
}
50+
}

src/core/etl/src/Flow/ETL/Function/ScalarFunctionChain.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,11 @@ public function concat(ScalarFunction|string ...$params) : self
103103
return new Concat($this, ...$params);
104104
}
105105

106+
public function concatWithSeparator(ScalarFunction|string $separator, ScalarFunction|string ...$params) : self
107+
{
108+
return new ConcatWithSeparator($separator, ...$params);
109+
}
110+
106111
public function contains(ScalarFunction|string $needle) : self
107112
{
108113
return new Contains($this, $needle);

src/core/etl/tests/Flow/ETL/Tests/Integration/Function/ConcatTest.php

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,33 @@ public function test_concat_on_non_string_value() : void
2828

2929
self::assertSame(
3030
[
31-
['id' => 1, 'concat' => null],
32-
['id' => 2, 'concat' => null],
31+
['id' => 1, 'concat' => '1'],
32+
['id' => 2, 'concat' => '2'],
33+
],
34+
$memory->dump()
35+
);
36+
}
37+
38+
public function test_concat_on_nulls() : void
39+
{
40+
(data_frame())
41+
->read(
42+
from_array(
43+
[
44+
['id' => 1, 'array' => ['field' => 'value']],
45+
['id' => 2],
46+
]
47+
)
48+
)
49+
->withEntry('concat', concat(lit(null), lit(null)))
50+
->drop('array')
51+
->write(to_memory($memory = new ArrayMemory()))
52+
->run();
53+
54+
self::assertSame(
55+
[
56+
['id' => 1, 'concat' => ''],
57+
['id' => 2, 'concat' => ''],
3358
],
3459
$memory->dump()
3560
);
@@ -54,7 +79,7 @@ public function test_concat_on_stringable_value() : void
5479
self::assertSame(
5580
[
5681
['id' => 1, 'concat' => '1-value'],
57-
['id' => 2, 'concat' => null],
82+
['id' => 2, 'concat' => '2-'],
5883
],
5984
$memory->dump()
6085
);
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Flow\ETL\Tests\Integration\Function;
6+
7+
use function Flow\ETL\DSL\data_frame;
8+
use function Flow\ETL\DSL\{concat_ws, from_array, lit, ref, to_memory};
9+
use Flow\ETL\Memory\ArrayMemory;
10+
use Flow\ETL\Tests\FlowTestCase;
11+
12+
final class ConcatWithSeparatorTest extends FlowTestCase
13+
{
14+
public function test_concat_on_non_string_value() : void
15+
{
16+
(data_frame())
17+
->read(
18+
from_array(
19+
[
20+
['id' => 1],
21+
['id' => 2],
22+
]
23+
)
24+
)
25+
->withEntry('concat', concat_ws(lit(','), ref('id'), lit(null)))
26+
->write(to_memory($memory = new ArrayMemory()))
27+
->run();
28+
29+
self::assertSame(
30+
[
31+
['id' => 1, 'concat' => '1'],
32+
['id' => 2, 'concat' => '2'],
33+
],
34+
$memory->dump()
35+
);
36+
}
37+
38+
public function test_concat_on_nulls() : void
39+
{
40+
(data_frame())
41+
->read(
42+
from_array(
43+
[
44+
['id' => 1, 'array' => ['field' => 'value']],
45+
['id' => 2],
46+
]
47+
)
48+
)
49+
->withEntry('concat', concat_ws(lit(null), lit(null)))
50+
->drop('array')
51+
->write(to_memory($memory = new ArrayMemory()))
52+
->run();
53+
54+
self::assertSame(
55+
[
56+
['id' => 1, 'concat' => ''],
57+
['id' => 2, 'concat' => ''],
58+
],
59+
$memory->dump()
60+
);
61+
}
62+
63+
public function test_concat_with_separator() : void
64+
{
65+
(data_frame())
66+
->read(
67+
from_array(
68+
[
69+
['id' => 1],
70+
['id' => 2],
71+
]
72+
)
73+
)
74+
->withEntry('concat', concat_ws(lit('->'), lit('id'), ref('id')))
75+
->drop('array')
76+
->write(to_memory($memory = new ArrayMemory()))
77+
->run();
78+
79+
self::assertSame(
80+
[
81+
['id' => 1, 'concat' => 'id->1'],
82+
['id' => 2, 'concat' => 'id->2'],
83+
],
84+
$memory->dump()
85+
);
86+
}
87+
}

web/landing/resources/dsl.json

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

0 commit comments

Comments
 (0)