packages/ztd-query-pdo-adapter/tests/Integration/SqliteErrorHandlingTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Integration;
6
7use PDO;
8use PHPUnit\Framework\Attributes\CoversNothing;
9use PHPUnit\Framework\Attributes\Large;
10use PHPUnit\Framework\TestCase;
11use ZtdQuery\Adapter\Pdo\ZtdPdo;
12use ZtdQuery\Adapter\Pdo\ZtdPdoException;
13use ZtdQuery\Config\UnknownSchemaBehavior;
14use ZtdQuery\Config\UnsupportedSqlBehavior;
15use ZtdQuery\Config\ZtdConfig;
16
17/**
18 * @requires extension pdo_sqlite
19 * @phpstan-type Row array<string, mixed>
20 */
21#[CoversNothing]
22#[Large]
23final class SqliteErrorHandlingTest extends TestCase
24{
25    public function testLateTableUpdateAndDeletePassThroughByDefault(): void
26    {
27        $rawPdo = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]);
28        $ztd = ZtdPdo::fromPdo($rawPdo);
29
30        $rawPdo->exec('CREATE TABLE late_table (id INTEGER PRIMARY KEY, value TEXT NOT NULL)');
31        $rawPdo->exec("INSERT INTO late_table VALUES (1, 'physical')");
32
33        self::assertSame(1, $ztd->exec("UPDATE late_table SET value = 'updated' WHERE id = 1"));
34        $valueStatement = $rawPdo->query('SELECT value FROM late_table WHERE id = 1');
35        self::assertNotFalse($valueStatement);
36        self::assertSame('updated', $valueStatement->fetchColumn());
37        self::assertSame(1, $ztd->exec('DELETE FROM late_table WHERE id = 1'));
38        $countStatement = $rawPdo->query('SELECT COUNT(*) FROM late_table');
39        self::assertNotFalse($countStatement);
40        self::assertSame(0, $countStatement->fetchColumn());
41    }
42
43    public function testLateTableMutationRespectsExceptionBehavior(): void
44    {
45        $rawPdo = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]);
46        $config = new ZtdConfig(
47            UnsupportedSqlBehavior::Ignore,
48            UnknownSchemaBehavior::Exception,
49        );
50        $ztd = ZtdPdo::fromPdo($rawPdo, $config);
51
52        $rawPdo->exec('CREATE TABLE late_table (id INTEGER PRIMARY KEY, value TEXT NOT NULL)');
53        $rawPdo->exec("INSERT INTO late_table VALUES (1, 'physical')");
54
55        $this->expectException(ZtdPdoException::class);
56        $this->expectExceptionMessage('Unknown table: late_table');
57        $ztd->exec("UPDATE late_table SET value = 'updated' WHERE id = 1");
58    }
59
60    public function testRowsFromLateTableInsertDoNotBypassPassthroughBehavior(): void
61    {
62        $rawPdo = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]);
63        $ztd = ZtdPdo::fromPdo($rawPdo);
64
65        $rawPdo->exec('CREATE TABLE late_table (id INTEGER PRIMARY KEY, value TEXT NOT NULL)');
66        $rawPdo->exec("INSERT INTO late_table VALUES (1, 'physical')");
67        self::assertSame(1, $ztd->exec("INSERT INTO late_table (id, value) VALUES (2, 'shadow')"));
68
69        self::assertSame(1, $ztd->exec("UPDATE late_table SET value = 'updated' WHERE id = 1"));
70        $statement = $rawPdo->query('SELECT value FROM late_table WHERE id = 1');
71        self::assertNotFalse($statement);
72        self::assertSame('updated', $statement->fetchColumn());
73    }
74
75    public function testUnsupportedSqlWithExceptionBehavior(): void
76    {
77        $rawPdo = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]);
78        $rawPdo->exec('CREATE TABLE items (id INTEGER PRIMARY KEY AUTOINCREMENT, value TEXT NOT NULL)');
79
80        $config = new ZtdConfig(UnsupportedSqlBehavior::Exception);
81        $ztd = ZtdPdo::fromPdo($rawPdo, $config);
82
83        $this->expectException(ZtdPdoException::class);
84        $ztd->exec('PRAGMA table_info(sqlite_master)');
85    }
86
87    public function testUnsupportedSqlWithIgnoreBehavior(): void
88    {
89        $rawPdo = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]);
90        $rawPdo->exec('CREATE TABLE items (id INTEGER PRIMARY KEY AUTOINCREMENT, value TEXT NOT NULL)');
91
92        $config = new ZtdConfig(UnsupportedSqlBehavior::Ignore);
93        $ztd = ZtdPdo::fromPdo($rawPdo, $config);
94
95        $result = $ztd->exec('PRAGMA table_info(sqlite_master)');
96        self::assertSame(0, $result);
97    }
98
99    public function testUnsupportedSqlWithNoticeBehavior(): void
100    {
101        $rawPdo = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]);
102        $rawPdo->exec('CREATE TABLE items (id INTEGER PRIMARY KEY AUTOINCREMENT, value TEXT NOT NULL)');
103
104        $config = new ZtdConfig(UnsupportedSqlBehavior::Notice);
105        $ztd = ZtdPdo::fromPdo($rawPdo, $config);
106
107        $noticeEmitted = false;
108        set_error_handler(function (int $errno) use (&$noticeEmitted): bool {
109            if ($errno === E_USER_NOTICE) {
110                $noticeEmitted = true;
111            }
112            return true;
113        });
114
115        try {
116            $result = $ztd->exec('PRAGMA table_info(sqlite_master)');
117            self::assertSame(0, $result);
118            self::assertTrue($noticeEmitted, 'Expected E_USER_NOTICE to be emitted');
119        } finally {
120            restore_error_handler();
121        }
122    }
123
124    public function testPreparedStatementInsertAndSelect(): void
125    {
126        $rawPdo = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]);
127        $rawPdo->exec('CREATE TABLE items (id INTEGER PRIMARY KEY AUTOINCREMENT, value TEXT NOT NULL)');
128
129        $ztdPdo = ZtdPdo::fromPdo($rawPdo, null);
130
131        $ztdPdo->exec("INSERT INTO items (value) VALUES ('test_value')");
132
133        $stmt = $ztdPdo->prepare('SELECT * FROM items WHERE value = ?');
134        self::assertNotFalse($stmt);
135
136        $stmt->execute(['test_value']);
137        /** @var list<Row> $rows */
138        $rows = $stmt->fetchAll();
139
140        self::assertCount(1, $rows);
141        self::assertSame('test_value', $rows[0]['value']);
142    }
143
144    public function testPreparedStatementWithBindValue(): void
145    {
146        $rawPdo = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]);
147        $rawPdo->exec('CREATE TABLE items (id INTEGER PRIMARY KEY AUTOINCREMENT, value TEXT NOT NULL)');
148
149        $ztdPdo = ZtdPdo::fromPdo($rawPdo, null);
150
151        $ztdPdo->exec("INSERT INTO items (value) VALUES ('bound_value')");
152
153        $stmt = $ztdPdo->prepare('SELECT * FROM items WHERE value = :val');
154        self::assertNotFalse($stmt);
155
156        $stmt->bindValue(':val', 'bound_value');
157        $stmt->execute();
158        /** @var list<Row> $rows */
159        $rows = $stmt->fetchAll();
160
161        self::assertCount(1, $rows);
162        self::assertSame('bound_value', $rows[0]['value']);
163    }
164}
165