diff --git a/CHANGELOG.md b/CHANGELOG.md index 83aa915c3..4854633b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ - Enh #282: Support `numeric` arrays, improve support of domain types and `int` and `varchar` array types (@Tigrov) - Enh #284: Add tests for `binary` type and fix casting of default value (@Tigrov) +- Bug #287: Fix `bit` type (@Tigrov) +- Enh #289: Array parser refactoring (@Tigrov) ## 1.0.0 April 12, 2023 diff --git a/src/ArrayParser.php b/src/ArrayParser.php index a34a9163e..4d5d5ab32 100644 --- a/src/ArrayParser.php +++ b/src/ArrayParser.php @@ -5,18 +5,12 @@ namespace Yiisoft\Db\Pgsql; use function in_array; -use function strlen; /** * Array representation to PHP array parser for PostgreSQL Server. */ final class ArrayParser { - /** - * @var string Character used in an array. - */ - private string $delimiter = ','; - /** * Convert an array from PostgresSQL to PHP. * @@ -24,15 +18,9 @@ final class ArrayParser */ public function parse(string|null $value): array|null { - if ($value === null) { - return null; - } - - if ($value === '{}') { - return []; - } - - return $this->parseArray($value); + return $value !== null + ? $this->parseArray($value) + : null; } /** @@ -43,33 +31,23 @@ public function parse(string|null $value): array|null */ private function parseArray(string $value, int &$i = 0): array { - $result = []; - $len = strlen($value); + if ($value[++$i] === '}') { + ++$i; + return []; + } - for (++$i; $i < $len; ++$i) { - switch ($value[$i]) { - case '{': - $result[] = $this->parseArray($value, $i); - break; - case '}': - break 2; - case $this->delimiter: - /** `{}` case */ - if (empty($result)) { - $result[] = null; - } + for ($result = [];; ++$i) { + $result[] = match ($value[$i]) { + '{' => $this->parseArray($value, $i), + ',', '}' => null, + default => $this->parseString($value, $i), + }; - /** `{,}` case */ - if (in_array($value[$i + 1], [$this->delimiter, '}'], true)) { - $result[] = null; - } - break; - default: - $result[] = $this->parseString($value, $i); + if ($value[$i] === '}') { + ++$i; + return $result; } } - - return $result; } /** @@ -80,27 +58,41 @@ private function parseArray(string $value, int &$i = 0): array */ private function parseString(string $value, int &$i): string|null { - $isQuoted = $value[$i] === '"'; - $stringEndChars = $isQuoted ? ['"'] : [$this->delimiter, '}']; - $result = ''; - $len = strlen($value); + return $value[$i] === '"' + ? $this->parseQuotedString($value, $i) + : $this->parseUnquotedString($value, $i); + } - for ($i += $isQuoted ? 1 : 0; $i < $len; ++$i) { - if (in_array($value[$i], ['\\', '"'], true) && in_array($value[$i + 1], [$value[$i], '"'], true)) { + /** + * Parses quoted string. + */ + private function parseQuotedString(string $value, int &$i): string + { + for ($result = '', ++$i;; ++$i) { + if ($value[$i] === '\\') { + ++$i; + } elseif ($value[$i] === '"') { ++$i; - } elseif (in_array($value[$i], $stringEndChars, true)) { - break; + return $result; } $result .= $value[$i]; } + } - $i -= $isQuoted ? 0 : 1; + /** + * Parses unquoted string. + */ + private function parseUnquotedString(string $value, int &$i): string|null + { + for ($result = '';; ++$i) { + if (in_array($value[$i], [',', '}'], true)) { + return $result !== 'NULL' + ? $result + : null; + } - if (!$isQuoted && $result === 'NULL') { - $result = null; + $result .= $value[$i]; } - - return $result; } } diff --git a/tests/ArrayParserTest.php b/tests/ArrayParserTest.php index 436e47754..a27273ea0 100644 --- a/tests/ArrayParserTest.php +++ b/tests/ArrayParserTest.php @@ -25,9 +25,10 @@ public function testParser(): void $this->assertSame([0 => '1', 1 => '2', 2 => '3'], $arrayParse->parse('{1,2,3}')); $this->assertSame([0 => '1', 1 => '-2', 2 => null, 3 => '42'], $arrayParse->parse('{1,-2,NULL,42}')); $this->assertSame([[0 => 'text'], [0 => null], [0 => '1']], $arrayParse->parse('{{text},{NULL},{1}}')); + $this->assertSame([0 => ''], $arrayParse->parse('{""}')); $this->assertSame( - [[0 => '[",","null",true,"false","f"]'], 1 => ''], - $arrayParse->parse('"{"[\",\",\"null\",true,\"false\",\"f\"]"}"') + [0 => '[",","null",true,"false","f"]'], + $arrayParse->parse('{"[\",\",\"null\",true,\"false\",\"f\"]"}') ); } } diff --git a/tests/ColumnSchemaTest.php b/tests/ColumnSchemaTest.php index 6536d1fbc..b19f283dd 100644 --- a/tests/ColumnSchemaTest.php +++ b/tests/ColumnSchemaTest.php @@ -50,8 +50,8 @@ public function testPhpTypeCast(): void 'bool_col' => false, 'bigint_col' => 9_223_372_036_854_775_806, 'intarray_col' => [1, -2, null, '42'], - 'numericarray_col' => [1.2, -2.2, null], - 'varchararray_col' => ['', 'some text', null], + 'numericarray_col' => [null, 1.2, -2.2, null, null], + 'varchararray_col' => ['', 'some text', '""', '\\\\', '[",","null",true,"false","f"]', null], 'textarray2_col' => new ArrayExpression(null), 'json_col' => [['a' => 1, 'b' => null, 'c' => [1, 3, 5]]], 'jsonb_col' => new JsonExpression(new ArrayExpression([1, 2, 3])), @@ -84,8 +84,8 @@ public function testPhpTypeCast(): void $this->assertFalse($boolColPhpTypeCast); $this->assertSame(33.22, $numericColPhpTypeCast); $this->assertSame([1, -2, null, 42], $intArrayColPhpType); - $this->assertSame([1.2, -2.2, null], $numericArrayColPhpTypeCast); - $this->assertSame(['', 'some text', null], $varcharArrayColPhpTypeCast); + $this->assertSame([null, 1.2, -2.2, null, null], $numericArrayColPhpTypeCast); + $this->assertSame(['', 'some text', '""', '\\\\', '[",","null",true,"false","f"]', null], $varcharArrayColPhpTypeCast); $this->assertNull($textArray2ColPhpType); $this->assertSame([['a' => 1, 'b' => null, 'c' => [1, 3, 5]]], $jsonColPhpType); $this->assertSame(['1', '2', '3'], $jsonBColPhpType);