packages/ztd-query-sqlite/tests/Unit/Sql/Transaction/SqliteTransactionStatementParserTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Sql\Transaction;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\DataProvider;
9use PHPUnit\Framework\Attributes\UsesClass;
10use PHPUnit\Framework\TestCase;
11use ZtdQuery\Platform\Sqlite\Sql\SqliteLexerProfile;
12use ZtdQuery\Platform\Sqlite\Sql\Transaction\SqliteTransactionStatementParser;
13use ZtdQuery\Shadow\ShadowStore;
14use ZtdQuery\Shadow\ShadowTransactions;
15
16#[CoversClass(SqliteTransactionStatementParser::class)]
17#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Transaction\TransactionTokens::class)]
18#[UsesClass(SqliteLexerProfile::class)]
19final class SqliteTransactionStatementParserTest extends TestCase
20{
21    #[DataProvider('providerSqliteTransactionForms')]
22    public function testParsesSqliteTransactionForms(string $sql): void
23    {
24        self::assertNotNull((new SqliteTransactionStatementParser())->parse($sql));
25    }
26
27    /**
28     * @return array<string, array{string}>
29     */
30    public static function providerSqliteTransactionForms(): array
31    {
32        return [
33            'begin' => ['BEGIN;'],
34            'begin transaction' => ['BEGIN TRANSACTION'],
35            'begin deferred' => ['BEGIN DEFERRED'],
36            'begin deferred transaction' => ['BEGIN DEFERRED TRANSACTION'],
37            'begin immediate' => ['BEGIN IMMEDIATE'],
38            'begin immediate transaction' => ['BEGIN IMMEDIATE TRANSACTION'],
39            'begin exclusive' => ['BEGIN EXCLUSIVE'],
40            'begin exclusive transaction' => ['BEGIN EXCLUSIVE TRANSACTION'],
41            'commit' => ['COMMIT'],
42            'commit transaction' => ['COMMIT TRANSACTION'],
43            'end' => ['END'],
44            'end transaction' => ['END TRANSACTION'],
45            'rollback' => ['ROLLBACK'],
46            'rollback transaction' => ['ROLLBACK TRANSACTION'],
47            'savepoint' => ['SAVEPOINT `a``b`'],
48            'rollback to' => ['ROLLBACK TO point'],
49            'rollback to savepoint' => ['ROLLBACK TO SAVEPOINT point'],
50            'release' => ['RELEASE point'],
51            'release savepoint' => ['RELEASE SAVEPOINT point'],
52        ];
53    }
54
55    public function testRejectsNonSqliteAndMalformedForms(): void
56    {
57        $parser = new SqliteTransactionStatementParser();
58
59        self::assertNull($parser->parse(''));
60        self::assertNull($parser->parse('START TRANSACTION'));
61        self::assertNull($parser->parse('COMMIT WORK'));
62        self::assertNull($parser->parse('SAVEPOINT point extra'));
63        self::assertNull($parser->parse('SAVEPOINT `broken'));
64    }
65
66    public function testAppliesUnescapedSqliteSavepointName(): void
67    {
68        $store = new ShadowStore();
69        $store->set('items', [['id' => 1]]);
70        $transactions = new ShadowTransactions($store);
71        $transactions->begin();
72        $statement = (new SqliteTransactionStatementParser())->parse('SAVEPOINT `a``b`');
73        self::assertNotNull($statement);
74        $statement->apply($transactions);
75        $store->insert('items', [['id' => 2]]);
76
77        $transactions->rollBackTo('a`b');
78
79        self::assertSame([['id' => 1]], $store->get('items'));
80    }
81}
82