packages/ztd-query-pdo-adapter/tests/Unit/ZtdPdoStatementTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit;
6
7use PDO;
8use PDOException;
9use PHPUnit\Framework\Attributes\CoversClass;
10use PHPUnit\Framework\TestCase;
11use ReflectionException;
12use ZtdQuery\Adapter\Pdo\Driver\PdoStatement;
13use ZtdQuery\Adapter\Pdo\Session\BufferedRow;
14use ZtdQuery\Adapter\Pdo\Session\ParameterBinder;
15use ZtdQuery\Adapter\Pdo\Session\ParameterKind;
16use ZtdQuery\Adapter\Pdo\Session\PreparedQuery;
17use ZtdQuery\Adapter\Pdo\ZtdPdo;
18use ZtdQuery\Adapter\Pdo\ZtdPdoException;
19use ZtdQuery\Adapter\Pdo\ZtdPdoStatement;
20use ZtdQuery\Config\ZtdConfig;
21use ZtdQuery\Connection\ConnectionInterface;
22use ZtdQuery\Connection\Exception\DatabaseException;
23use ZtdQuery\Exception\MissingPrimaryKeyException;
24use ZtdQuery\Platform\ResultColumnTypeResolver;
25use ZtdQuery\ResultSelectRunner;
26use ZtdQuery\Rewrite\QueryKind;
27use ZtdQuery\Rewrite\RewritePlan;
28use ZtdQuery\Rewrite\SqlRewriter;
29use ZtdQuery\Schema\ColumnType;
30use ZtdQuery\Schema\ColumnTypeFamily;
31use ZtdQuery\Session;
32use ZtdQuery\Shadow\Mutation\UpdateMutation;
33use ZtdQuery\Shadow\ShadowStore;
34
35#[CoversClass(ZtdPdoStatement::class)]
36#[\PHPUnit\Framework\Attributes\UsesClass(ZtdPdoException::class)]
37#[\PHPUnit\Framework\Attributes\UsesClass(ZtdPdo::class)]
38#[\PHPUnit\Framework\Attributes\UsesClass(\ZtdQuery\Adapter\Pdo\Driver\PdoConnection::class)]
39#[\PHPUnit\Framework\Attributes\UsesClass(PdoStatement::class)]
40#[\PHPUnit\Framework\Attributes\UsesClass(\ZtdQuery\Adapter\Pdo\Session\StatementExecution::class)]
41#[\PHPUnit\Framework\Attributes\UsesClass(\ZtdQuery\Adapter\Pdo\Session\Bindings::class)]
42#[CoversClass(BufferedRow::class)]
43#[\PHPUnit\Framework\Attributes\UsesClass(\ZtdQuery\Adapter\Pdo\Session\DriverSessionFactory::class)]
44#[\PHPUnit\Framework\Attributes\UsesClass(ParameterKind::class)]
45#[\PHPUnit\Framework\Attributes\UsesClass(ParameterBinder::class)]
46#[\PHPUnit\Framework\Attributes\UsesClass(PreparedQuery::class)]
47#[\PHPUnit\Framework\Attributes\Medium]
48#[\PHPUnit\Framework\Attributes\UsesClass(\ZtdQuery\Adapter\Pdo\Session\ConnectionExecution::class)]
49final class ZtdPdoStatementTest extends TestCase
50{
51 /**
52 * @throws ReflectionException
53 */
54 public function testBufferedObjectHydrationUsesDeclaredProperties(): void
55 {
56 $pdo = new PDO('sqlite::memory:');
57 $pdo->exec('CREATE TABLE errors (id INTEGER PRIMARY KEY, message TEXT)');
58 $statement = ZtdPdo::fromPdo($pdo)->prepare("INSERT INTO errors VALUES (1, 'row message') RETURNING message");
59 self::assertInstanceOf(ZtdPdoStatement::class, $statement);
60 self::assertTrue($statement->execute());
61 $object = $statement->fetchObject(PDOException::class, ['constructor message', 7]);
62 self::assertInstanceOf(PDOException::class, $object);
63 self::assertSame('row message', $object->getMessage());
64 self::assertSame(7, $object->getCode());
65 }
66
67 public function testPassthroughFetchAllForwardsConstructorArguments(): void
68 {
69 $pdo = ZtdPdo::fromPdo(new PDO('sqlite::memory:'));
70 $statement = $pdo->prepare("SELECT 'row message' AS message");
71 self::assertInstanceOf(ZtdPdoStatement::class, $statement);
72 self::assertTrue($statement->execute());
73 $objects = $statement->fetchAll(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, PDOException::class, ['constructor message', 7]);
74 self::assertCount(1, $objects);
75 self::assertInstanceOf(PDOException::class, $objects[0]);
76 self::assertSame('row message', $objects[0]->getMessage());
77 self::assertSame(7, $objects[0]->getCode());
78 }
79
80 public function testExecuteDelegatesWhenNoPlan(): void
81 {
82 $pdo = new PDO('sqlite::memory:');
83 $pdo->exec('CREATE TABLE t (id INTEGER)');
84 $inner = $pdo->prepare('INSERT INTO t VALUES (1)');
85 self::assertNotFalse($inner);
86
87 $session = new Session(static::createStub(SqlRewriter::class), new ShadowStore(), new ResultSelectRunner(), ZtdConfig::default(), static::createStub(ConnectionInterface::class));
88
89 $stmt = new ZtdPdoStatement($inner, $session, null);
90 self::assertTrue($stmt->execute());
91 }
92
93 public function testExecuteReturnsFalseWhenShouldExecuteIsFalse(): void
94 {
95 $pdo = new PDO('sqlite::memory:');
96 $pdo->exec('CREATE TABLE t (id INTEGER)');
97 $inner = $pdo->prepare('SELECT * FROM t');
98 self::assertNotFalse($inner);
99
100 $plan = new RewritePlan('SELECT 1', QueryKind::SKIPPED);
101
102 $session = new Session(static::createStub(SqlRewriter::class), new ShadowStore(), new ResultSelectRunner(), ZtdConfig::default(), static::createStub(ConnectionInterface::class));
103 $stmt = new ZtdPdoStatement($inner, $session, $plan);
104 self::assertFalse($stmt->execute());
105 }
106
107 public function testExecuteDelegatesWhenNoPostProcessingNeeded(): void
108 {
109 $pdo = new PDO('sqlite::memory:');
110 $pdo->exec('CREATE TABLE t (id INTEGER)');
111 $inner = $pdo->prepare('SELECT * FROM t');
112 self::assertNotFalse($inner);
113
114 $plan = new RewritePlan('SELECT * FROM t', QueryKind::READ);
115
116 $session = new Session(static::createStub(SqlRewriter::class), new ShadowStore(), new ResultSelectRunner(), ZtdConfig::default(), static::createStub(ConnectionInterface::class));
117 $stmt = new ZtdPdoStatement($inner, $session, $plan);
118 self::assertTrue($stmt->execute());
119 }
120
121 public function testExecuteWithoutPostProcessingRunsNativeStatementOnce(): void
122 {
123 $pdo = new PDO('sqlite::memory:');
124 $pdo->exec('CREATE TABLE t (id INTEGER)');
125 $inner = $pdo->prepare('INSERT INTO t VALUES (1)');
126 self::assertNotFalse($inner);
127
128 $plan = new RewritePlan('INSERT INTO t VALUES (1)', QueryKind::READ);
129 $session = new Session(static::createStub(SqlRewriter::class), new ShadowStore(), new ResultSelectRunner(), ZtdConfig::default(), static::createStub(ConnectionInterface::class));
130 $stmt = new ZtdPdoStatement($inner, $session, $plan);
131
132 self::assertTrue($stmt->execute());
133 $count = $pdo->query('SELECT COUNT(*) FROM t');
134 self::assertNotFalse($count);
135 self::assertSame(1, $count->fetchColumn());
136 }
137
138 public function testBindValueSurvivesPreparedStatementRecompilation(): void
139 {
140 $pdo = new PDO('sqlite::memory:');
141 $stmt = ZtdPdo::fromPdo($pdo)->prepare('SELECT ? AS value');
142 self::assertInstanceOf(ZtdPdoStatement::class, $stmt);
143
144 self::assertTrue($stmt->bindValue(1, 42, PDO::PARAM_INT));
145 self::assertTrue($stmt->execute());
146 self::assertSame(42, $stmt->fetchColumn());
147 }
148
149 public function testBindParamSurvivesPreparedStatementRecompilation(): void
150 {
151 $pdo = new PDO('sqlite::memory:');
152 $stmt = ZtdPdo::fromPdo($pdo)->prepare('SELECT ? AS value');
153 self::assertInstanceOf(ZtdPdoStatement::class, $stmt);
154
155 $value = 41;
156
157 self::assertTrue($stmt->bindParam(1, $value, PDO::PARAM_INT));
158 $value = 43;
159 self::assertTrue($stmt->execute());
160 self::assertSame(43, $stmt->fetchColumn());
161 }
162
163 public function testExecuteWrapsSimulationFailureAsAdapterException(): void
164 {
165 $pdo = new PDO('sqlite::memory:');
166 $inner = $pdo->prepare("SELECT 1 AS id, 'Bob' AS name");
167 self::assertNotFalse($inner);
168
169 $shadowStore = new ShadowStore();
170 $shadowStore->set('users', [['id' => 1, 'name' => 'Alice']]);
171 $typeResolver = static::createStub(ResultColumnTypeResolver::class);
172 $typeResolver->method('resolve')->willReturn(new ColumnType(ColumnTypeFamily::INTEGER, 'INTEGER'));
173 $session = new Session(
174 static::createStub(SqlRewriter::class),
175 $shadowStore,
176 new ResultSelectRunner(),
177 ZtdConfig::default(),
178 static::createStub(ConnectionInterface::class),
179 resultColumnTypeResolver: $typeResolver,
180 );
181 $plan = new RewritePlan(
182 "SELECT 1 AS id, 'Bob' AS name",
183 QueryKind::WRITE_SIMULATED,
184 new UpdateMutation('users', []),
185 );
186 $stmt = new ZtdPdoStatement($inner, $session, $plan);
187
188 try {
189 $stmt->execute();
190 self::fail('Expected a ZTD PDO exception.');
191 } catch (ZtdPdoException $exception) {
192 self::assertSame(0, $exception->getCode());
193 $databaseException = $exception->getPrevious();
194 self::assertInstanceOf(DatabaseException::class, $databaseException);
195 self::assertInstanceOf(MissingPrimaryKeyException::class, $databaseException->getPrevious());
196 }
197 }
198
199 public function testBindValueDelegatesToInner(): void
200 {
201 $pdo = new PDO('sqlite::memory:');
202 $pdo->exec('CREATE TABLE t (id INTEGER, name TEXT)');
203 $inner = $pdo->prepare('INSERT INTO t VALUES (:id, :name)');
204 self::assertNotFalse($inner);
205
206 $session = new Session(static::createStub(SqlRewriter::class), new ShadowStore(), new ResultSelectRunner(), ZtdConfig::default(), static::createStub(ConnectionInterface::class));
207
208 $stmt = new ZtdPdoStatement($inner, $session, null);
209 self::assertTrue($stmt->bindValue(1, 'test'));
210 }
211
212 public function testRowCountDelegatesToInnerWhenNoResult(): void
213 {
214 $pdo = new PDO('sqlite::memory:');
215 $pdo->exec('CREATE TABLE t (id INTEGER)');
216 $pdo->exec('INSERT INTO t VALUES (1)');
217 $pdo->exec('INSERT INTO t VALUES (2)');
218
219 $inner = $pdo->prepare('SELECT * FROM t');
220 self::assertNotFalse($inner);
221 $inner->execute();
222
223 $session = new Session(static::createStub(SqlRewriter::class), new ShadowStore(), new ResultSelectRunner(), ZtdConfig::default(), static::createStub(ConnectionInterface::class));
224
225 $stmt = new ZtdPdoStatement($inner, $session, null);
226 self::assertSame(0, $stmt->rowCount());
227 }
228
229 public function testCloseCursorDelegatesToInner(): void
230 {
231 $pdo = new PDO('sqlite::memory:');
232 $pdo->exec('CREATE TABLE t (id INTEGER)');
233 $inner = $pdo->prepare('SELECT * FROM t');
234 self::assertNotFalse($inner);
235 $inner->execute();
236
237 $session = new Session(static::createStub(SqlRewriter::class), new ShadowStore(), new ResultSelectRunner(), ZtdConfig::default(), static::createStub(ConnectionInterface::class));
238
239 $stmt = new ZtdPdoStatement($inner, $session, null);
240 self::assertTrue($stmt->closeCursor());
241 }
242
243 public function testColumnCountDelegatesToInner(): void
244 {
245 $pdo = new PDO('sqlite::memory:');
246 $pdo->exec('CREATE TABLE t (id INTEGER, name TEXT, value REAL)');
247 $inner = $pdo->prepare('SELECT * FROM t');
248 self::assertNotFalse($inner);
249 $inner->execute();
250
251 $session = new Session(static::createStub(SqlRewriter::class), new ShadowStore(), new ResultSelectRunner(), ZtdConfig::default(), static::createStub(ConnectionInterface::class));
252
253 $stmt = new ZtdPdoStatement($inner, $session, null);
254 self::assertSame(3, $stmt->columnCount());
255 }
256 public function testBindColumnFillsTheVariableTheColumnIsReadInto(): void
257 {
258 $native = new PDO('sqlite::memory:');
259 $inner = $native->prepare('SELECT 7 AS id');
260 self::assertNotFalse($inner);
261 $session = (new \ZtdQuery\Platform\Sqlite\SqliteSessionFactory())->create(new \ZtdQuery\Adapter\Pdo\Driver\PdoConnection($native), ZtdConfig::default());
262 $statement = new ZtdPdoStatement($inner, $session, null);
263 $id = null;
264
265 $statement->bindColumn('id', $id);
266 $statement->execute();
267 $statement->fetch(PDO::FETCH_BOUND);
268
269 self::assertSame('7', $id);
270 }
271
272
273
274
275
276 public function testExecuteAndPostProcessLetsZtdReadWhatTheStatementAnswered(): void
277 {
278 $native = new PDO('sqlite::memory:');
279 $native->exec('CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL)');
280 $statement = ZtdPdo::fromPdo($native)->prepare('INSERT INTO users (name) VALUES (?) RETURNING id, name');
281 self::assertInstanceOf(ZtdPdoStatement::class, $statement);
282 $statement->execute(['linus']);
283
284 self::assertSame([['id' => 1, 'name' => 'linus']], $statement->fetchAll(PDO::FETCH_ASSOC));
285 }
286
287
288
289 public function testFetchAnswersOneBufferedRowAtATime(): void
290 {
291 $native = new PDO('sqlite::memory:');
292 $native->exec('CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL)');
293 $statement = ZtdPdo::fromPdo($native)->prepare('INSERT INTO users (name) VALUES (?) RETURNING id, name');
294 self::assertInstanceOf(ZtdPdoStatement::class, $statement);
295 $statement->execute(['linus']);
296
297 self::assertSame(['id' => 1, 'name' => 'linus'], $statement->fetch(PDO::FETCH_ASSOC));
298 }
299
300 public function testFetchAnswersFalseOnceTheBufferedRowsRunOut(): void
301 {
302 $native = new PDO('sqlite::memory:');
303 $native->exec('CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL)');
304 $statement = ZtdPdo::fromPdo($native)->prepare('INSERT INTO users (name) VALUES (?) RETURNING id, name');
305 self::assertInstanceOf(ZtdPdoStatement::class, $statement);
306 $statement->execute(['linus']);
307 $statement->fetch(PDO::FETCH_ASSOC);
308
309 self::assertFalse($statement->fetch(PDO::FETCH_ASSOC));
310 }
311
312 public function testFetchAllAnswersEveryBufferedRowInTheModeItIsAsked(): void
313 {
314 $native = new PDO('sqlite::memory:');
315 $native->exec('CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL)');
316 $statement = ZtdPdo::fromPdo($native)->prepare('INSERT INTO users (name) VALUES (?) RETURNING id, name');
317 self::assertInstanceOf(ZtdPdoStatement::class, $statement);
318 $statement->execute(['linus']);
319
320 self::assertSame([[1, 'linus']], $statement->fetchAll(PDO::FETCH_NUM));
321 }
322
323 public function testFetchAllAnswersOneColumnWhereOnlyOneIsAsked(): void
324 {
325 $native = new PDO('sqlite::memory:');
326 $native->exec('CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL)');
327 $statement = ZtdPdo::fromPdo($native)->prepare('INSERT INTO users (name) VALUES (?) RETURNING id, name');
328 self::assertInstanceOf(ZtdPdoStatement::class, $statement);
329 $statement->execute(['linus']);
330
331 self::assertSame(['linus'], $statement->fetchAll(PDO::FETCH_COLUMN, 1));
332 }
333
334 public function testFetchColumnAnswersTheColumnAskedForInTheBufferedRow(): void
335 {
336 $native = new PDO('sqlite::memory:');
337 $native->exec('CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL)');
338 $statement = ZtdPdo::fromPdo($native)->prepare('INSERT INTO users (name) VALUES (?) RETURNING id, name');
339 self::assertInstanceOf(ZtdPdoStatement::class, $statement);
340 $statement->execute(['linus']);
341
342 self::assertSame('linus', $statement->fetchColumn(1));
343 }
344
345 /**
346 * @throws ReflectionException
347 */
348 public function testFetchObjectBuildsAnObjectOutOfTheBufferedRow(): void
349 {
350 $native = new PDO('sqlite::memory:');
351 $native->exec('CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL)');
352 $statement = ZtdPdo::fromPdo($native)->prepare('INSERT INTO users (name) VALUES (?) RETURNING id, name');
353 self::assertInstanceOf(ZtdPdoStatement::class, $statement);
354 $statement->execute(['linus']);
355
356 $object = $statement->fetchObject();
357
358 self::assertSame(['id' => 1, 'name' => 'linus'], $object === false ? [] : get_object_vars($object));
359 }
360
361 /**
362 * @throws ReflectionException
363 */
364 public function testFetchObjectAnswersFalseWhereThereIsNoBufferedRowLeft(): void
365 {
366 $native = new PDO('sqlite::memory:');
367 $native->exec('CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL)');
368 $statement = ZtdPdo::fromPdo($native)->prepare('INSERT INTO users (name) VALUES (?) RETURNING id, name');
369 self::assertInstanceOf(ZtdPdoStatement::class, $statement);
370 $statement->execute(['linus']);
371 $statement->fetchObject();
372
373 self::assertFalse($statement->fetchObject());
374 }
375
376 public function testSetFetchModeIsRememberedAcrossAReprepare(): void
377 {
378 $native = new PDO('sqlite::memory:');
379 $statement = ZtdPdo::fromPdo($native)->prepare('SELECT ? AS value');
380 self::assertInstanceOf(ZtdPdoStatement::class, $statement);
381 $statement->setFetchMode(PDO::FETCH_NUM);
382
383 $statement->execute([42]);
384
385 self::assertSame([[42]], $statement->fetchAll());
386 }
387
388
389
390
391
392
393
394 public function testErrorCodeAnswersWhatTheDriverSaysWentWrongLast(): void
395 {
396 $native = new PDO('sqlite::memory:');
397 $inner = $native->prepare('SELECT 1 AS id');
398 self::assertNotFalse($inner);
399 $session = (new \ZtdQuery\Platform\Sqlite\SqliteSessionFactory())->create(new \ZtdQuery\Adapter\Pdo\Driver\PdoConnection($native), ZtdConfig::default());
400 $statement = new ZtdPdoStatement($inner, $session, null);
401 $statement->execute();
402 self::assertSame('00000', $statement->errorCode());
403 }
404
405 public function testErrorCodeAnswersNothingWhereTheDriverSaysNothing(): void
406 {
407 $native = new PDO('sqlite::memory:');
408 $inner = $native->prepare('SELECT 1 AS id');
409 self::assertNotFalse($inner);
410 $session = (new \ZtdQuery\Platform\Sqlite\SqliteSessionFactory())->create(new \ZtdQuery\Adapter\Pdo\Driver\PdoConnection($native), ZtdConfig::default());
411 $statement = new ZtdPdoStatement($inner, $session, null);
412 self::assertSame('', $statement->errorCode());
413 }
414
415 public function testErrorInfoAnswersWhatTheDriverSaysAboutTheLastFailure(): void
416 {
417 $native = new PDO('sqlite::memory:');
418 $inner = $native->prepare('SELECT 1 AS id');
419 self::assertNotFalse($inner);
420 $session = (new \ZtdQuery\Platform\Sqlite\SqliteSessionFactory())->create(new \ZtdQuery\Adapter\Pdo\Driver\PdoConnection($native), ZtdConfig::default());
421 $statement = new ZtdPdoStatement($inner, $session, null);
422 $statement->execute();
423 self::assertSame('00000', $statement->errorInfo()[0]);
424 }
425
426 public function testGetAttributeReadsTheAttributeOffTheStatementItWraps(): void
427 {
428 $native = new PDO('sqlite::memory:');
429 $inner = $native->prepare('SELECT 1 AS id');
430 self::assertNotFalse($inner);
431 $session = (new \ZtdQuery\Platform\Sqlite\SqliteSessionFactory())->create(new \ZtdQuery\Adapter\Pdo\Driver\PdoConnection($native), ZtdConfig::default());
432 $statement = new ZtdPdoStatement($inner, $session, null);
433 $statement->execute();
434 $this->expectException(PDOException::class);
435 $statement->getAttribute(PDO::ATTR_CURSOR);
436 }
437
438 public function testSetAttributeSetsTheAttributeOnTheStatementItWraps(): void
439 {
440 $native = new PDO('sqlite::memory:');
441 $inner = $native->prepare('SELECT 1 AS id');
442 self::assertNotFalse($inner);
443 $session = (new \ZtdQuery\Platform\Sqlite\SqliteSessionFactory())->create(new \ZtdQuery\Adapter\Pdo\Driver\PdoConnection($native), ZtdConfig::default());
444 $statement = new ZtdPdoStatement($inner, $session, null);
445 $statement->execute();
446 try {
447 $expected = $inner->setAttribute(PDO::ATTR_CURSOR, PDO::CURSOR_FWDONLY);
448 } catch (PDOException $failure) {
449 $this->expectExceptionObject($failure);
450 $statement->setAttribute(PDO::ATTR_CURSOR, PDO::CURSOR_FWDONLY);
451 return;
452 }
453 self::assertSame($expected, $statement->setAttribute(PDO::ATTR_CURSOR, PDO::CURSOR_FWDONLY));
454 }
455
456 public function testGetColumnMetaAnswersWhatTheDriverSaysAboutAColumn(): void
457 {
458 $native = new PDO('sqlite::memory:');
459 $inner = $native->prepare('SELECT 1 AS id');
460 self::assertNotFalse($inner);
461 $session = (new \ZtdQuery\Platform\Sqlite\SqliteSessionFactory())->create(new \ZtdQuery\Adapter\Pdo\Driver\PdoConnection($native), ZtdConfig::default());
462 $statement = new ZtdPdoStatement($inner, $session, null);
463 $statement->execute();
464 $meta = $statement->getColumnMeta(0);
465
466 self::assertSame('id', $meta === false ? null : $meta['name']);
467 }
468
469 public function testNextRowsetMovesTheStatementOnToTheDriversNextResult(): void
470 {
471 $native = new PDO('sqlite::memory:');
472 $inner = $native->prepare('SELECT 1 AS id');
473 self::assertNotFalse($inner);
474 $session = (new \ZtdQuery\Platform\Sqlite\SqliteSessionFactory())->create(new \ZtdQuery\Adapter\Pdo\Driver\PdoConnection($native), ZtdConfig::default());
475 $statement = new ZtdPdoStatement($inner, $session, null);
476 $statement->execute();
477 $this->expectException(PDOException::class);
478 $statement->nextRowset();
479 }
480
481 public function testDebugDumpParamsWritesTheDumpRatherThanAnsweringIt(): void
482 {
483 $native = new PDO('sqlite::memory:');
484 $inner = $native->prepare('SELECT 1 AS id');
485 self::assertNotFalse($inner);
486 $session = (new \ZtdQuery\Platform\Sqlite\SqliteSessionFactory())->create(new \ZtdQuery\Adapter\Pdo\Driver\PdoConnection($native), ZtdConfig::default());
487 $statement = new ZtdPdoStatement($inner, $session, null);
488
489 ob_start();
490 $dumped = $statement->debugDumpParams();
491 $output = ob_get_clean();
492
493 self::assertTrue($dumped);
494 self::assertIsString($output);
495 self::assertStringContainsString('SELECT 1 AS id', $output);
496 self::assertStringContainsString('Params: 0', $output);
497 }
498
499 public function testGetIteratorWalksTheRowsZtdBuffered(): void
500 {
501 $native = new PDO('sqlite::memory:');
502 $native->exec('CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL)');
503 $statement = ZtdPdo::fromPdo($native)->prepare('INSERT INTO users (name) VALUES (?) RETURNING id, name');
504 self::assertInstanceOf(ZtdPdoStatement::class, $statement);
505 $statement->execute(['linus']);
506
507 self::assertSame([['id' => 1, 0 => 1, 'name' => 'linus', 1 => 'linus']], iterator_to_array($statement->getIterator()));
508 }
509
510 public function testGetIteratorWalksTheDriversOwnCursorWhereNothingWasBuffered(): void
511 {
512 $native = new PDO('sqlite::memory:');
513 $inner = $native->prepare('SELECT 1 AS id');
514 self::assertNotFalse($inner);
515 $session = (new \ZtdQuery\Platform\Sqlite\SqliteSessionFactory())->create(new \ZtdQuery\Adapter\Pdo\Driver\PdoConnection($native), ZtdConfig::default());
516 $statement = new ZtdPdoStatement($inner, $session, null);
517 $statement->execute();
518
519 self::assertCount(1, iterator_to_array($statement->getIterator()));
520 }
521
522
523
524
525
526
527
528
529 public function testFetchAllAnswersFalseForAColumnTheBufferedRowDoesNotHold(): void
530 {
531 $native = new PDO('sqlite::memory:');
532 $native->exec('CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL)');
533 $statement = ZtdPdo::fromPdo($native)->prepare('INSERT INTO users (name) VALUES (?) RETURNING id, name');
534 self::assertInstanceOf(ZtdPdoStatement::class, $statement);
535 $statement->execute(['linus']);
536
537 self::assertSame([false], $statement->fetchAll(PDO::FETCH_COLUMN, 5));
538 }
539
540 public function testFetchAllAnswersEveryBufferedRowAndNotJustTheFirst(): void
541 {
542 $pdo = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
543 $pdo->exec('CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL)');
544 $ztdPdo = ZtdPdo::fromPdo($pdo);
545 $statement = $ztdPdo->prepare("INSERT INTO users (name) VALUES ('ada'), ('grace') RETURNING id, name");
546 self::assertInstanceOf(ZtdPdoStatement::class, $statement);
547 $statement->execute();
548
549 self::assertSame(
550 [['id' => 1, 'name' => 'ada'], ['id' => 2, 'name' => 'grace']],
551 $statement->fetchAll(PDO::FETCH_ASSOC),
552 );
553 }
554 /**
555 * @throws ReflectionException
556 */
557 public function testSimulatedWritesWithoutReturningHaveNoFetchableRows(): void
558 {
559 $pdo = new PDO('sqlite::memory:');
560 $pdo->exec('CREATE TABLE users (id INTEGER PRIMARY KEY)');
561 $statement = ZtdPdo::fromPdo($pdo)->prepare('INSERT INTO users VALUES (1)');
562 self::assertInstanceOf(ZtdPdoStatement::class, $statement);
563 self::assertTrue($statement->execute());
564 self::assertSame(1, $statement->rowCount());
565 self::assertFalse($statement->fetch());
566 self::assertSame([], $statement->fetchAll());
567 self::assertFalse($statement->fetchColumn());
568 self::assertFalse($statement->fetchObject());
569 }
570
571 public function testBufferedColumnFetchDefaultsToTheFirstColumnAndThenExhausts(): void
572 {
573 $native = new PDO('sqlite::memory:');
574 $native->exec('CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL)');
575 $statement = ZtdPdo::fromPdo($native)->prepare('INSERT INTO users (name) VALUES (?) RETURNING id, name');
576 self::assertInstanceOf(ZtdPdoStatement::class, $statement);
577 $statement->execute(['linus']);
578 self::assertSame(1, $statement->fetchColumn());
579 self::assertFalse($statement->fetchColumn());
580 $native = new PDO('sqlite::memory:');
581 $native->exec('CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL)');
582 $statement = ZtdPdo::fromPdo($native)->prepare('INSERT INTO users (name) VALUES (?) RETURNING id, name');
583 self::assertInstanceOf(ZtdPdoStatement::class, $statement);
584 $statement->execute(['linus']);
585 self::assertSame([1], $statement->fetchAll(PDO::FETCH_COLUMN));
586 }
587
588 public function testPassthroughFetchAllPreservesKeyPairKeys(): void
589 {
590 $native = new PDO('sqlite::memory:');
591 $inner = $native->prepare("SELECT 'alice' AS name, 7 AS score UNION ALL SELECT 'bob', 9");
592 self::assertNotFalse($inner);
593 $session = (new \ZtdQuery\Platform\Sqlite\SqliteSessionFactory())->create(new \ZtdQuery\Adapter\Pdo\Driver\PdoConnection($native), ZtdConfig::default());
594 $statement = new ZtdPdoStatement($inner, $session, null);
595 self::assertTrue($statement->execute());
596 self::assertSame(['alice' => 7, 'bob' => 9], $statement->fetchAll(PDO::FETCH_KEY_PAIR));
597 }
598
599 public function testPassthroughFetchAllForwardsColumnAndCallbackArguments(): void
600 {
601 $native = new PDO('sqlite::memory:');
602 $inner = $native->prepare("SELECT 1 AS id, 'alice' AS name");
603 self::assertNotFalse($inner);
604 $session = (new \ZtdQuery\Platform\Sqlite\SqliteSessionFactory())->create(new \ZtdQuery\Adapter\Pdo\Driver\PdoConnection($native), ZtdConfig::default());
605 $statement = new ZtdPdoStatement($inner, $session, null);
606 self::assertTrue($statement->execute());
607 self::assertSame(['alice'], $statement->fetchAll(PDO::FETCH_COLUMN, 1));
608 self::assertTrue($statement->execute());
609 self::assertSame(['1:alice'], $statement->fetchAll(PDO::FETCH_FUNC, static fn (int $id, string $name): string => $id . ':' . $name));
610 }
611
612
613
614
615
616 public function testBufferedColumnFetchAllReturnsEveryRow(): void
617 {
618 $native = new PDO('sqlite::memory:');
619 $native->exec('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)');
620 $statement = ZtdPdo::fromPdo($native)->query("INSERT INTO users VALUES (1, 'Ada'), (2, 'Grace') RETURNING id, name");
621 self::assertInstanceOf(ZtdPdoStatement::class, $statement);
622 self::assertSame(['Ada', 'Grace'], $statement->fetchAll(PDO::FETCH_COLUMN, 1));
623 self::assertSame([], $statement->fetchAll(PDO::FETCH_COLUMN, 1));
624 }
625
626 /**
627 * @throws ReflectionException When object hydration fails.
628 */
629 public function testFetchObjectDelegatesWithoutABufferedResult(): void
630 {
631 $native = new PDO('sqlite::memory:');
632 $inner = $native->query("SELECT 7 AS id, 'Ada' AS name");
633 self::assertNotFalse($inner);
634 $session = (new \ZtdQuery\Platform\Sqlite\SqliteSessionFactory())->create(new \ZtdQuery\Adapter\Pdo\Driver\PdoConnection($native), ZtdConfig::default());
635 $statement = new ZtdPdoStatement($inner, $session, null);
636 $row = $statement->fetchObject();
637 self::assertIsObject($row);
638 self::assertSame(['id' => 7, 'name' => 'Ada'], get_object_vars($row));
639 self::assertFalse($statement->fetchObject());
640 }
641}
642