packages/ztd-query-postgres/tests/Unit/Schema/Partition/PgSqlPartitionReflectorTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Schema\Partition;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use ZtdQuery\Connection\ConnectionInterface;
11use ZtdQuery\Platform\Postgres\Schema\Partition\PgSqlPartitionReflector;
12use ZtdQuery\Platform\Postgres\Sql\Partition\PgSqlPartitionParser;
13use ZtdQuery\Schema\Partition\TablePartitionStrategy;
14
15#[CoversClass(PgSqlPartitionReflector::class)]
16#[UsesClass(PgSqlPartitionParser::class)]
17#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\PgSqlLexerProfile::class)]
18#[UsesClass(\ZtdQuery\Platform\Postgres\Schema\Reflection\Catalog\PartitionKeys::class)]
19#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Partition\BoundPredicate::class)]
20#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Partition\ClauseTokens::class)]
21final class PgSqlPartitionReflectorTest extends TestCase
22{
23 public function testReflectsKeysAndParentRelationsFromCatalog(): void
24 {
25 $keyStatement = self::createStub(\ZtdQuery\Connection\StatementInterface::class);
26 $keyStatement->method('fetchAll')->willReturn([['table_name' => null, 'partition_key' => 'RANGE (id)'], ['table_name' => '', 'partition_key' => 'RANGE (id)'], ['table_name' => 'broken', 'partition_key' => null], ['table_name' => 'invalid', 'partition_key' => 'invalid'], ['table_name' => 'logs', 'partition_key' => 'RANGE (log_date)']]);
27 $relationStatement = self::createStub(\ZtdQuery\Connection\StatementInterface::class);
28 $relationStatement->method('fetchAll')->willReturn([['child_table' => null, 'parent_table' => 'logs', 'predicate' => 'TRUE'], ['child_table' => '', 'parent_table' => 'logs', 'predicate' => 'TRUE'], ['child_table' => 'missing_parent', 'parent_table' => null, 'predicate' => 'TRUE'], ['child_table' => 'empty_parent', 'parent_table' => '', 'predicate' => 'TRUE'], ['child_table' => 'missing_predicate', 'parent_table' => 'logs', 'predicate' => null], ['child_table' => 'blank_predicate', 'parent_table' => 'logs', 'predicate' => ' '], ['child_table' => 'logs_2024', 'parent_table' => 'logs', 'predicate' => "((log_date >= '2024-01-01'::date) AND (log_date < '2025-01-01'::date))"]]);
29 $queries = [];
30 $connection = self::createStub(ConnectionInterface::class);
31 $connection->method('query')->willReturnCallback(static function (string $sql) use (&$queries, $keyStatement, $relationStatement) {
32 $queries[] = $sql;
33 return count($queries) === 1 ? $keyStatement : $relationStatement;
34 });
35 $reflector = new PgSqlPartitionReflector($connection);
36 $metadata = $reflector->reflect();
37 self::assertSame(['logs'], array_keys($metadata['keys']));
38 self::assertSame(['logs_2024'], array_keys($metadata['relations']));
39 self::assertSame(TablePartitionStrategy::Range, $metadata['keys']['logs']->strategy);
40 self::assertSame(['log_date'], $metadata['keys']['logs']->expressions);
41 self::assertSame('logs', $metadata['relations']['logs_2024']->parentTable);
42 $predicate = $metadata['relations']['logs_2024']->predicate;
43 self::assertNotNull($predicate);
44 self::assertStringContainsString('log_date >=', $predicate);
45 self::assertSame(['SELECT c.relname AS table_name, pg_get_partkeydef(c.oid) AS partition_key ' . 'FROM pg_partitioned_table pt ' . 'JOIN pg_class c ON c.oid = pt.partrelid ' . 'JOIN pg_namespace n ON n.oid = c.relnamespace ' . 'WHERE n.nspname = current_schema() ORDER BY c.relname', 'SELECT child.relname AS child_table, parent.relname AS parent_table, ' . 'pg_get_partition_constraintdef(child.oid) AS predicate ' . 'FROM pg_inherits i ' . 'JOIN pg_class child ON child.oid = i.inhrelid ' . 'JOIN pg_namespace child_ns ON child_ns.oid = child.relnamespace ' . 'JOIN pg_class parent ON parent.oid = i.inhparent ' . 'JOIN pg_partitioned_table pt ON pt.partrelid = parent.oid ' . 'WHERE child_ns.nspname = current_schema() ORDER BY child.relname'], $queries);
46 }
47 public function testReturnsEmptyMetadataWhenCatalogQueriesFail(): void
48 {
49 $connection = self::createStub(ConnectionInterface::class);
50 $connection->method('query')->willReturn(false);
51 $metadata = (new PgSqlPartitionReflector($connection))->reflect();
52 self::assertSame(['keys' => [], 'relations' => []], $metadata);
53 }
54}
55