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