packages/ztd-query-sqlite/tests/Unit/Schema/Key/ForeignKeyEntryParserTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Schema\Key;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use ZtdQuery\Platform\Sqlite\Schema\Key\ForeignKeyEntryParser;
11use ZtdQuery\Platform\Sqlite\Sql\SqliteLexerProfile;
12use ZtdQuery\Sql\SqlTokenStream;
13
14#[CoversClass(ForeignKeyEntryParser::class)]
15#[UsesClass(\ZtdQuery\Platform\Sqlite\Schema\Key\ForeignKeyTokens::class)]
16#[UsesClass(SqliteLexerProfile::class)]
17final class ForeignKeyEntryParserTest extends TestCase
18{
19    public function testParseEntryReadsNamedConstraintAndActions(): void
20    {
21        $stream = SqlTokenStream::tokenize('CONSTRAINT fk FOREIGN KEY(user_id) REFERENCES main.users(id) ON DELETE CASCADE ON UPDATE SET NULL', SqliteLexerProfile::create());
22        $entry = (new ForeignKeyEntryParser())->parseEntry($stream, 'fallback', null);
23        self::assertNotNull($entry);
24        self::assertSame('fk', $entry['name']);
25        self::assertSame(['user_id'], $entry['foreignKey']->columns);
26        self::assertSame('users', $entry['foreignKey']->referencedTable);
27        self::assertSame(['id'], $entry['foreignKey']->referencedColumns);
28        self::assertSame(\ZtdQuery\Schema\Key\ReferentialAction::Cascade, $entry['foreignKey']->onDelete);
29        self::assertSame(\ZtdQuery\Schema\Key\ReferentialAction::SetNull, $entry['foreignKey']->onUpdate);
30    }
31
32    public function testParseEntryAcceptsInlineReferencesAndRejectsMissingRelations(): void
33    {
34        $parser = new ForeignKeyEntryParser();
35        $inline = $parser->parseEntry(SqlTokenStream::tokenize('user_id INTEGER REFERENCES users', SqliteLexerProfile::create()), 'inline', 'user_id');
36        self::assertNotNull($inline);
37        self::assertSame(['user_id'], $inline['foreignKey']->columns);
38        self::assertNull($parser->parseEntry(SqlTokenStream::tokenize('user_id INTEGER', SqliteLexerProfile::create()), 'inline', 'user_id'));
39        self::assertNull($parser->parseEntry(SqlTokenStream::tokenize('user_id REFERENCES', SqliteLexerProfile::create()), 'inline', 'user_id'));
40    }
41
42    public function testForeignKeyColumnsExtractsLocalCompositeKey(): void
43    {
44        $stream = SqlTokenStream::tokenize('FOREIGN KEY(a, b) REFERENCES parent(a, b)', SqliteLexerProfile::create());
45        $parser = new ForeignKeyEntryParser();
46        self::assertSame(['a', 'b'], $parser->foreignKeyColumns($stream, $stream->significantTokens(), 7));
47        self::assertSame([], $parser->foreignKeyColumns($stream, [], 0));
48    }
49
50    public function testReferencedRelationUnqualifiesTableAndReadsColumns(): void
51    {
52        $stream = SqlTokenStream::tokenize('main.parent(a, b)', SqliteLexerProfile::create());
53        self::assertSame(['table' => 'parent', 'columns' => ['a', 'b']], (new ForeignKeyEntryParser())->referencedRelation($stream, $stream->significantTokens(), 0));
54    }
55
56    public function testIdentifierListRequiresAClosingGroup(): void
57    {
58        $parser = new ForeignKeyEntryParser();
59        $stream = SqlTokenStream::tokenize('(a, "b name")', SqliteLexerProfile::create());
60        self::assertSame(['a', 'b name'], $parser->identifierList($stream, $stream->significantTokens(), 0));
61        $incomplete = SqlTokenStream::tokenize('(a, b', SqliteLexerProfile::create());
62        self::assertSame([], $parser->identifierList($incomplete, $incomplete->significantTokens(), 0));
63    }
64
65    public function testActionDistinguishesSupportedReferentialActions(): void
66    {
67        $parser = new ForeignKeyEntryParser();
68        self::assertSame(\ZtdQuery\Schema\Key\ReferentialAction::Restrict, $parser->action(SqlTokenStream::tokenize('ON DELETE RESTRICT', SqliteLexerProfile::create())->significantTokens(), 'DELETE'));
69        self::assertSame(\ZtdQuery\Schema\Key\ReferentialAction::SetDefault, $parser->action(SqlTokenStream::tokenize('ON UPDATE SET DEFAULT', SqliteLexerProfile::create())->significantTokens(), 'UPDATE'));
70        self::assertSame(\ZtdQuery\Schema\Key\ReferentialAction::NoAction, $parser->action(SqlTokenStream::tokenize('ON UPDATE SET', SqliteLexerProfile::create())->significantTokens(), 'UPDATE'));
71        self::assertSame(\ZtdQuery\Schema\Key\ReferentialAction::NoAction, $parser->action([], 'DELETE'));
72    }
73
74}
75