Skip to content

Commit

Permalink
Fixed search for single enum value in PostgreSQL (#181)
Browse files Browse the repository at this point in the history
  • Loading branch information
msmakouz authored Mar 29, 2024
1 parent 63acb67 commit 2081495
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 12 deletions.
40 changes: 28 additions & 12 deletions src/Driver/Postgres/Schema/PostgresColumn.php
Original file line number Diff line number Diff line change
Expand Up @@ -692,19 +692,10 @@ private static function resolveConstrains(
);

foreach ($constraints as $constraint) {
if (preg_match('/ARRAY\[([^\]]+)\]/', $constraint['consrc'], $matches)) {
$enumValues = explode(',', $matches[1]);
foreach ($enumValues as &$value) {
if (preg_match("/^'?(.*?)'?::(.+)/", trim($value, ' ()'), $matches)) {
//In database: 'value'::TYPE
$value = $matches[1];
}

unset($value);
}
unset($value);
$values = static::parseEnumValues($constraint['consrc']);

$column->enumValues = $enumValues;
if ($values !== []) {
$column->enumValues = $values;
$column->constrainName = $constraint['conname'];
$column->constrained = true;
}
Expand All @@ -729,4 +720,29 @@ private static function resolveEnum(DriverInterface $driver, self $column): void
);
}
}

private static function parseEnumValues(string $constraint): array
{
if (\preg_match('/ARRAY\[([^\]]+)\]/', $constraint, $matches)) {
$enumValues = \explode(',', $matches[1]);
foreach ($enumValues as &$value) {
if (\preg_match("/^'?([a-zA-Z0-9_]++)'?::([a-zA-Z0-9_]++)/", \trim($value, ' ()'), $matches)) {
//In database: 'value'::TYPE
$value = $matches[1];
}

unset($value);
}
unset($value);

return $enumValues;
}

$pattern = '/CHECK \\(\\(\\([a-zA-Z0-9_]++\\)::([a-z]++) = \'([a-zA-Z0-9_]++)\'::([a-z]++)\\)\\)/i';
if (\preg_match($pattern, $constraint, $matches) && !empty($matches[2])) {
return [$matches[2]];
}

return [];
}
}
14 changes: 14 additions & 0 deletions tests/Database/Functional/Driver/Common/Schema/ConsistencyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,20 @@ public function testEnum(): void
$this->assertTrue($schema->column('target')->compare($column));
}

public function testEnumWithSingleValue(): void
{
$schema = $this->schema('table');
$this->assertFalse($schema->exists());

$column = $schema->enum('target', ['catalog']);

$schema->save();
$schema = $this->schema('table');
$this->assertTrue($schema->exists());
$this->assertTrue($schema->column('target')->compare($column));
$this->assertSame(['catalog'], $schema->column('target')->getEnumValues());
}

public function testJson(): void
{
$schema = $this->schema('table');
Expand Down
74 changes: 74 additions & 0 deletions tests/Database/Unit/Driver/Postgres/Schema/PostgresColumnTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

declare(strict_types=1);

namespace Cycle\Database\Tests\Unit\Driver\Postgres\Schema;

use Cycle\Database\Driver\Postgres\Schema\PostgresColumn;
use PHPUnit\Framework\TestCase;

final class PostgresColumnTest extends TestCase
{
/**
* @dataProvider enumConstrainsDataProvider
*/
public function testParseEnumValues(string $constrain, array $expected): void
{
$column = new PostgresColumn('', '');

$ref = new \ReflectionMethod($column, 'parseEnumValues');
$ref->setAccessible(true);

$this->assertSame($expected, $ref->invoke($column, $constrain));
}

public static function enumConstrainsDataProvider(): \Traversable
{
yield ['', []];
yield ['foo', []];

// simple single value
yield ["CHECK (((target)::text = 'catalog'::text))", ['catalog']];

// single value with underscore
yield ["CHECK (((type)::text = 'user_profile'::text))", ['user_profile']];

// single value with number
yield ["CHECK (((type)::text = 'user_profile_2015'::text))", ['user_profile_2015']];
yield ["CHECK (((type)::text = 'user_profile2015'::text))", ['user_profile2015']];

// single value in table with underscore
yield ["CHECK (((log_type)::text = 'order'::text))", ['order']];

// single value in table with number
yield ["CHECK (((type_2013)::text = 'user_profile'::text))", ['user_profile']];
yield ["CHECK (((type2)::text = 'catalog'::text))", ['catalog']];

// simple multiple values
yield ["CHECK (((target)::text = ANY (ARRAY['catalog'::text, 'view'::text])))", ['catalog', 'view']];

// multiple values in table with underscore
yield ["CHECK (((log_type)::text = ANY (ARRAY['catalog'::text, 'view'::text])))", ['catalog', 'view']];

// multiple values in table with number
yield ["CHECK (((log_type2)::text = ANY (ARRAY['catalog'::text, 'view'::text])))", ['catalog', 'view']];
yield ["CHECK (((log_type_2)::text = ANY (ARRAY['catalog'::text, 'view'::text])))", ['catalog', 'view']];

// multiple values with underscore
yield ["CHECK (((type)::text = ANY (ARRAY['user_profile'::text, 'view'::text])))", ['user_profile', 'view']];

// multiple values with number
yield [
"CHECK (((type)::text = ANY (ARRAY['user_profile_2'::text, 'view'::text])))",
['user_profile_2', 'view'],
];
yield [
"CHECK (((type)::text = ANY (ARRAY['user_profile2'::text, 'view'::text])))",
['user_profile2', 'view'],
];

// different type casting TODO: it can be unnecessary
yield ["CHECK (((target)::foo = 'catalog'::bar))", ['catalog']];
yield ["CHECK (((log_type)::foo = ANY (ARRAY['catalog'::bar, 'view'::baz])))", ['catalog', 'view']];
}
}

0 comments on commit 2081495

Please sign in to comment.