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