Skip to content

Commit 11f7a5f

Browse files
authored
Fixed handling comparison between entries that are unknonw type (created from null) (#1778)
1 parent 6f68fd8 commit 11f7a5f

File tree

2 files changed

+324
-2
lines changed

2 files changed

+324
-2
lines changed

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@
55
namespace Flow\ETL\Function;
66

77
use function Flow\ETL\DSL\lit;
8-
use function Flow\Types\DSL\get_type;
8+
use function Flow\Types\DSL\{get_type, type_null};
99
use Flow\ETL\Function\ScalarFunction\ScalarResult;
1010
use Flow\ETL\Row;
1111
use Flow\ETL\Row\{Entry, Reference};
12+
use Flow\ETL\Schema\Metadata;
1213
use Flow\Types\Type;
1314
use UnitEnum;
1415

@@ -178,6 +179,10 @@ public function asString(Row $row, ?string $default = null) : ?string
178179
public function asType(Row $row) : Type
179180
{
180181
if ($this->function instanceof Reference) {
182+
if ($row->get($this->function)->definition()->metadata()->has(Metadata::FROM_NULL)) {
183+
return type_null();
184+
}
185+
181186
return $row->get($this->function)->type();
182187
}
183188

src/core/etl/tests/Flow/ETL/Tests/Unit/Function/ParameterTest.php

Lines changed: 318 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,274 @@
55
namespace Flow\ETL\Tests\Unit\Function;
66

77
use function Flow\ETL\DSL\{int_entry, lit, ref, row, str_entry};
8-
use function Flow\Types\DSL\{type_boolean, type_integer, type_string};
8+
use function Flow\Types\DSL\{type_boolean, type_integer, type_null, type_string};
9+
use Flow\Doctrine\Bulk\SQLParametersStyle;
910
use Flow\ETL\Function\Parameter;
1011
use Flow\ETL\Function\ScalarFunction\ScalarResult;
12+
use Flow\ETL\Row\Entry\StringEntry;
13+
use Flow\ETL\String\StringStyles;
1114
use Flow\ETL\Tests\FlowTestCase;
1215
use Flow\Types\Type\Native\{BooleanType, IntegerType, StringType};
16+
use PHPUnit\Framework\Attributes\DataProvider;
1317

1418
final class ParameterTest extends FlowTestCase
1519
{
20+
public static function boolean_data_provider() : \Generator
21+
{
22+
yield 'string true' => ['true', true];
23+
yield 'string false' => ['false', true];
24+
yield 'string empty' => ['', false];
25+
yield 'string zero' => ['0', false];
26+
yield 'string one' => ['1', true];
27+
yield 'integer zero' => [0, false];
28+
yield 'integer one' => [1, true];
29+
yield 'integer negative' => [-1, true];
30+
yield 'float zero' => [0.0, false];
31+
yield 'float positive' => [1.5, true];
32+
yield 'boolean true' => [true, true];
33+
yield 'boolean false' => [false, false];
34+
yield 'array non-scalar' => [['value'], false];
35+
yield 'object non-scalar' => [new \stdClass(), false];
36+
yield 'null non-scalar' => [null, false];
37+
}
38+
39+
public static function float_data_provider() : \Generator
40+
{
41+
yield 'valid float' => [3.14, 3.14];
42+
yield 'zero float' => [0.0, 0.0];
43+
yield 'negative float' => [-2.5, -2.5];
44+
yield 'integer not float' => [42, null];
45+
yield 'string not float' => ['3.14', null];
46+
yield 'boolean not float' => [true, null];
47+
yield 'array not float' => [[], null];
48+
yield 'null not float' => [null, null];
49+
}
50+
51+
public static function int_data_provider() : \Generator
52+
{
53+
yield 'valid integer' => [42, null, 42];
54+
yield 'zero integer' => [0, null, 0];
55+
yield 'negative integer' => [-123, null, -123];
56+
yield 'float not integer with default' => [3.14, 99, 99];
57+
yield 'string not integer with default' => ['42', 99, 99];
58+
yield 'boolean not integer with default' => [true, 99, 99];
59+
yield 'null not integer with default' => [null, 99, 99];
60+
yield 'float not integer without default' => [3.14, null, null];
61+
}
62+
63+
public static function number_data_provider() : \Generator
64+
{
65+
yield 'integer' => [42, null, 42];
66+
yield 'float' => [3.14, null, 3.14];
67+
yield 'zero' => [0, null, 0];
68+
yield 'negative integer' => [-42, null, -42];
69+
yield 'negative float' => [-3.14, null, -3.14];
70+
yield 'string with default' => ['not numeric', 99, 99];
71+
yield 'string with float default' => ['not numeric', 99.5, 99.5];
72+
yield 'boolean with default' => [true, 99, 99];
73+
yield 'array with default' => [[], 99, 99];
74+
yield 'null with default' => [null, 99, 99];
75+
yield 'string without default' => ['not numeric', null, null];
76+
}
77+
78+
public static function string_data_provider() : \Generator
79+
{
80+
yield 'valid string' => ['hello', null, 'hello'];
81+
yield 'empty string' => ['', null, ''];
82+
yield 'numeric string' => ['123', null, '123'];
83+
yield 'integer with default' => [42, 'default', 'default'];
84+
yield 'float with default' => [3.14, 'default', 'default'];
85+
yield 'boolean with default' => [true, 'default', 'default'];
86+
yield 'array with default' => [[], 'default', 'default'];
87+
yield 'null with default' => [null, 'default', 'default'];
88+
yield 'integer without default' => [42, null, null];
89+
}
90+
91+
public function test_as_array_with_empty_array() : void
92+
{
93+
$parameter = new Parameter(lit([]));
94+
self::assertSame([], $parameter->asArray(row()));
95+
}
96+
97+
public function test_as_array_with_non_array() : void
98+
{
99+
$parameter = new Parameter(lit('not an array'));
100+
self::assertNull($parameter->asArray(row()));
101+
102+
$parameter = new Parameter(lit(42));
103+
self::assertNull($parameter->asArray(row()));
104+
105+
$parameter = new Parameter(lit(true));
106+
self::assertNull($parameter->asArray(row()));
107+
}
108+
109+
public function test_as_array_with_valid_array() : void
110+
{
111+
$parameter = new Parameter(lit(['key' => 'value', 'number' => 42]));
112+
$result = $parameter->asArray(row());
113+
114+
self::assertSame(['key' => 'value', 'number' => 42], $result);
115+
}
116+
117+
#[DataProvider('boolean_data_provider')]
118+
public function test_as_boolean(mixed $input, bool $expected) : void
119+
{
120+
$parameter = new Parameter(lit($input));
121+
self::assertSame($expected, $parameter->asBoolean(row()));
122+
}
123+
124+
public function test_as_entry_with_literal() : void
125+
{
126+
$parameter = new Parameter(lit('literal_value'));
127+
self::assertNull($parameter->asEntry(row()));
128+
}
129+
130+
public function test_as_entry_with_missing_reference() : void
131+
{
132+
$parameter = new Parameter(ref('missing_column'));
133+
$row = row(str_entry('other_column', 'test_value'));
134+
135+
self::assertNull($parameter->asEntry($row));
136+
}
137+
138+
public function test_as_entry_with_reference() : void
139+
{
140+
$parameter = new Parameter(ref('test_column'));
141+
$row = row(str_entry('test_column', 'test_value'));
142+
143+
$entry = $parameter->asEntry($row);
144+
self::assertNotNull($entry);
145+
self::assertSame('test_value', $entry->value());
146+
}
147+
148+
public function test_as_enum_with_invalid_type() : void
149+
{
150+
$parameter = new Parameter(lit('not an enum'));
151+
self::assertNull($parameter->asEnum(row(), SQLParametersStyle::class));
152+
153+
$parameter = new Parameter(lit(42));
154+
self::assertNull($parameter->asEnum(row(), SQLParametersStyle::class));
155+
}
156+
157+
public function test_as_enum_with_valid_enum() : void
158+
{
159+
$parameter = new Parameter(lit(SQLParametersStyle::NAMED));
160+
$result = $parameter->asEnum(row(), SQLParametersStyle::class);
161+
162+
self::assertSame(SQLParametersStyle::NAMED, $result);
163+
}
164+
165+
public function test_as_enum_with_wrong_enum_class() : void
166+
{
167+
$parameter = new Parameter(lit(SQLParametersStyle::NAMED));
168+
self::assertNull($parameter->asEnum(row(), StringStyles::class));
169+
}
170+
171+
#[DataProvider('float_data_provider')]
172+
public function test_as_float(mixed $input, ?float $expected) : void
173+
{
174+
$parameter = new Parameter(lit($input));
175+
self::assertSame($expected, $parameter->asFloat(row()));
176+
}
177+
178+
public function test_as_instance_of_with_invalid_type() : void
179+
{
180+
$parameter = new Parameter(lit('not an object'));
181+
self::assertNull($parameter->asInstanceOf(row(), \DateTimeImmutable::class));
182+
183+
$dateTime = new \DateTimeImmutable('2023-01-01');
184+
$parameter = new Parameter(lit($dateTime));
185+
self::assertNull($parameter->asInstanceOf(row(), \stdClass::class));
186+
}
187+
188+
public function test_as_instance_of_with_valid_object() : void
189+
{
190+
$dateTime = new \DateTimeImmutable('2023-01-01');
191+
$parameter = new Parameter(lit($dateTime));
192+
193+
$result = $parameter->asInstanceOf(row(), \DateTimeImmutable::class);
194+
self::assertSame($dateTime, $result);
195+
196+
$result = $parameter->asInstanceOf(row(), \DateTimeInterface::class);
197+
self::assertSame($dateTime, $result);
198+
}
199+
200+
#[DataProvider('int_data_provider')]
201+
public function test_as_int(mixed $input, ?int $default, ?int $expected) : void
202+
{
203+
$parameter = new Parameter(lit($input));
204+
self::assertSame($expected, $parameter->asInt(row(), $default));
205+
}
206+
207+
public function test_as_list_of_objects_with_empty_array() : void
208+
{
209+
$parameter = new Parameter(lit([]));
210+
$result = $parameter->asListOfObjects(row(), \DateTimeImmutable::class);
211+
212+
self::assertSame([], $result);
213+
}
214+
215+
public function test_as_list_of_objects_with_mixed_types() : void
216+
{
217+
$date = new \DateTimeImmutable('2023-01-01');
218+
$parameter = new Parameter(lit([$date, 'not an object']));
219+
220+
self::assertNull($parameter->asListOfObjects(row(), \DateTimeImmutable::class));
221+
}
222+
223+
public function test_as_list_of_objects_with_non_array() : void
224+
{
225+
$parameter = new Parameter(lit('not an array'));
226+
self::assertNull($parameter->asListOfObjects(row(), \DateTimeImmutable::class));
227+
}
228+
229+
public function test_as_list_of_objects_with_valid_array() : void
230+
{
231+
$date1 = new \DateTimeImmutable('2023-01-01');
232+
$date2 = new \DateTimeImmutable('2023-01-02');
233+
$parameter = new Parameter(lit([$date1, $date2]));
234+
235+
$result = $parameter->asListOfObjects(row(), \DateTimeImmutable::class);
236+
self::assertSame([$date1, $date2], $result);
237+
}
238+
239+
public function test_as_list_of_objects_with_wrong_object_type() : void
240+
{
241+
$date = new \DateTimeImmutable('2023-01-01');
242+
$std = new \stdClass();
243+
$parameter = new Parameter(lit([$date, $std]));
244+
245+
self::assertNull($parameter->asListOfObjects(row(), \DateTimeImmutable::class));
246+
}
247+
248+
#[DataProvider('number_data_provider')]
249+
public function test_as_number(mixed $input, int|float|null $default, int|float|null $expected) : void
250+
{
251+
$parameter = new Parameter(lit($input));
252+
self::assertSame($expected, $parameter->asNumber(row(), $default));
253+
}
254+
255+
public function test_as_object_with_non_object() : void
256+
{
257+
$parameter = new Parameter(lit('not an object'));
258+
self::assertNull($parameter->asObject(row()));
259+
260+
$parameter = new Parameter(lit(42));
261+
self::assertNull($parameter->asObject(row()));
262+
263+
$parameter = new Parameter(lit([]));
264+
self::assertNull($parameter->asObject(row()));
265+
}
266+
267+
public function test_as_object_with_valid_object() : void
268+
{
269+
$object = new \stdClass();
270+
$object->property = 'value';
271+
$parameter = new Parameter(lit($object));
272+
273+
self::assertSame($object, $parameter->asObject(row()));
274+
}
275+
16276
public function test_as_one_of() : void
17277
{
18278
$parameter = new Parameter(ref('value'));
@@ -45,6 +305,20 @@ public function test_as_scalar_on_scalar_result() : void
45305
self::assertSame('test', $parameter->as(row(), type_string()));
46306
}
47307

308+
#[DataProvider('string_data_provider')]
309+
public function test_as_string(mixed $input, ?string $default, ?string $expected) : void
310+
{
311+
$parameter = new Parameter(lit($input));
312+
self::assertSame($expected, $parameter->asString(row(), $default));
313+
}
314+
315+
public function test_as_type_when_handling_string_entry_created_from_null() : void
316+
{
317+
$parameter = new Parameter(ref('value'));
318+
319+
self::assertEquals(type_null(), $parameter->asType(row(StringEntry::fromNull('value'))));
320+
}
321+
48322
public function test_as_type_with_literal_value() : void
49323
{
50324
$parameter = new Parameter(lit('string_value'));
@@ -72,4 +346,47 @@ public function test_as_type_with_scalar_result() : void
72346
$parameter = new Parameter(lit(ScalarResult::from(123)));
73347
self::assertInstanceOf(IntegerType::class, $parameter->asType(row()));
74348
}
349+
350+
public function test_constructor_with_mixed_value() : void
351+
{
352+
$parameter = new Parameter('direct_string');
353+
self::assertSame('direct_string', $parameter->eval(row()));
354+
355+
$parameter = new Parameter(123);
356+
self::assertSame(123, $parameter->eval(row()));
357+
358+
$parameter = new Parameter(true);
359+
self::assertTrue($parameter->eval(row()));
360+
}
361+
362+
public function test_constructor_with_scalar_function() : void
363+
{
364+
$scalarFunction = lit('test');
365+
$parameter = new Parameter($scalarFunction);
366+
367+
self::assertSame('test', $parameter->eval(row()));
368+
}
369+
370+
public function test_eval_with_direct_value() : void
371+
{
372+
$parameter = new Parameter(lit('direct_value'));
373+
self::assertSame('direct_value', $parameter->eval(row()));
374+
375+
$parameter = new Parameter(lit(42));
376+
self::assertSame(42, $parameter->eval(row()));
377+
}
378+
379+
public function test_eval_with_reference() : void
380+
{
381+
$parameter = new Parameter(ref('column'));
382+
$result = $parameter->eval(row(str_entry('column', 'ref_value')));
383+
384+
self::assertSame('ref_value', $result);
385+
}
386+
387+
public function test_eval_with_scalar_result() : void
388+
{
389+
$parameter = new Parameter(lit(ScalarResult::from('test_value')));
390+
self::assertSame('test_value', $parameter->eval(row()));
391+
}
75392
}

0 commit comments

Comments
 (0)