packages/ztd-query-pdo-adapter/tests/Integration/PostgreSql/DomainTypeTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Integration\PostgreSql;
6
7use Container\PostgreSql16Container;
8use PDO;
9use PDOException;
10use PDOStatement;
11use PHPUnit\Framework\Attributes\CoversNothing;
12use PHPUnit\Framework\Attributes\Large;
13use PHPUnit\Framework\TestCase;
14use ZtdQuery\Adapter\Pdo\ZtdPdo;
15
16/**
17 * @requires extension pdo_pgsql
18 * @group integration
19 * @group postgres
20 */
21#[CoversNothing]
22#[Large]
23final class DomainTypeTest extends TestCase
24{
25 public function testDomainConstraintsAndDmlRemainNativeInTheShadowStore(): void
26 {
27 $containerInstance = \Testcontainers\Testcontainers::run(PostgreSql16Container::class);
28 /** @var PDO $rawPdo */
29 $rawPdo = new PDO(
30 sprintf('pgsql:host=%s;port=%d;dbname=test', str_replace('localhost', '127.0.0.1', $containerInstance->getHost()), $containerInstance->getMappedPort(5432)),
31 'test',
32 'test',
33 [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC],
34 );
35
36 $schemaName = 'ztd_' . bin2hex(random_bytes(8));
37 $rawPdo->exec(sprintf('CREATE SCHEMA "%s"', $schemaName));
38 $rawPdo->exec(sprintf('SET search_path TO "%s"', $schemaName));
39
40 $suffix = bin2hex(random_bytes(8));
41 $positiveDomain = 'positive_int_' . $suffix;
42 $percentageDomain = 'percentage_' . $suffix;
43 $table = 'contacts_' . $suffix;
44
45 try {
46 $rawPdo->exec("CREATE DOMAIN {$positiveDomain} AS INTEGER CHECK (VALUE > 0)");
47 $rawPdo->exec(
48 "CREATE DOMAIN {$percentageDomain} AS NUMERIC(5,2) CHECK (VALUE >= 0 AND VALUE <= 100)",
49 );
50 $rawPdo->exec(
51 "CREATE TABLE {$table} (id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, "
52 . "age {$positiveDomain} NOT NULL, satisfaction {$percentageDomain} NOT NULL)",
53 );
54 $ztdPdo = ZtdPdo::fromPdo($rawPdo);
55
56 $insert = $ztdPdo->prepare(
57 "INSERT INTO {$table} (age, satisfaction) VALUES (\$1, \$2) RETURNING id, age, satisfaction",
58 );
59 self::assertInstanceOf(PDOStatement::class, $insert);
60 self::assertTrue($insert->execute([30, '85.50']));
61 self::assertSame(
62 ['id' => 1, 'age' => 30, 'satisfaction' => '85.50'],
63 $insert->fetch(PDO::FETCH_ASSOC),
64 );
65
66 self::assertSame(1, $ztdPdo->exec("UPDATE {$table} SET satisfaction = 99.99 WHERE id = 1"));
67 $updated = $ztdPdo->query("SELECT satisfaction::text FROM {$table} WHERE id = 1");
68 self::assertNotFalse($updated);
69 self::assertSame('99.99', $updated->fetchColumn());
70
71 self::assertSame(1, $ztdPdo->exec("DELETE FROM {$table} WHERE satisfaction > 90"));
72 $remaining = $ztdPdo->query("SELECT COUNT(*) FROM {$table}");
73 self::assertNotFalse($remaining);
74 self::assertSame(0, (int) $remaining->fetchColumn());
75
76 try {
77 $ztdPdo->exec("INSERT INTO {$table} (age, satisfaction) VALUES (-5, 101)");
78 self::fail('The domain CHECK constraint must reject invalid shadow data.');
79 } catch (PDOException $exception) {
80 self::assertStringContainsString('violates check constraint', $exception->getMessage());
81 }
82
83 $physical = $rawPdo->query("SELECT COUNT(*) FROM {$table}");
84 self::assertNotFalse($physical);
85 self::assertSame(0, (int) $physical->fetchColumn());
86 } finally {
87 $rawPdo->exec(sprintf('DROP SCHEMA IF EXISTS "%s" CASCADE', $schemaName));
88 }
89 }
90}
91