packages/ztd-query-sqlite/src/Sql/Dml/Update/UpdateClauseParser.php

1<?php
2
3declare(strict_types=1);
4
5namespace ZtdQuery\Platform\Sqlite\Sql\Dml\Update;
6
7use ZtdQuery\Sql\SqlTokenKind;
8use ZtdQuery\Sql\SqlTokenStream;
9
10/**
11 * Extracts UPDATE aliases and top-level filtering clauses.
12 *
13 * @visibility ZtdQuery\Platform\Sqlite
14 */
15final class UpdateClauseParser
16{
17    /**
18     * Extracts UPDATE aliases and top-level filtering clauses.
19     */
20    public function extractUpdateAlias(string $sql): ?string
21    {
22        $tokens = SqlTokenStream::tokenize($sql, \ZtdQuery\Platform\Sqlite\Sql\SqliteLexerProfile::create())->significantTokens();
23        foreach ($tokens as $index => $token) {
24            if (!$token->isTopLevel() || !$token->isKeyword('UPDATE')) {
25                continue;
26            }
27
28            $index++;
29            if (($tokens[$index] ?? null)?->isKeyword('OR') === true) {
30                $index += 2;
31            }
32            $index = (new \ZtdQuery\Platform\Sqlite\Sql\Lexing\IdentifierDecoder())->identifierEndIndex($tokens, $index);
33            if (($tokens[$index] ?? null)?->kind === SqlTokenKind::Symbol
34                && $tokens[$index]->text === '.'
35            ) {
36                $index = (new \ZtdQuery\Platform\Sqlite\Sql\Lexing\IdentifierDecoder())->identifierEndIndex($tokens, $index + 1);
37            }
38            if (($tokens[$index] ?? null)?->isKeyword('AS') === true) {
39                $index++;
40            }
41
42            $alias = $tokens[$index] ?? null;
43            $set = $tokens[$index + 1] ?? null;
44            if ($alias === null
45                || $set === null
46                || !$set->isKeyword('SET')
47                || !in_array($alias->kind, [SqlTokenKind::Word, SqlTokenKind::QuotedIdentifier], true)
48            ) {
49                return null;
50            }
51
52            return (new \ZtdQuery\Platform\Sqlite\Sql\Lexing\IdentifierDecoder())->unquoteIdentifier($alias->text);
53        }
54
55        return null;
56    }
57
58    /**
59     * Extracts UPDATE aliases and top-level filtering clauses.
60     */
61    public function extractUpdateFromClause(string $sql): ?string
62    {
63        return SqlTokenStream::tokenize($sql, \ZtdQuery\Platform\Sqlite\Sql\SqliteLexerProfile::create())->topLevelClause(
64            ['FROM'],
65            [['WHERE'], ['ORDER', 'BY'], ['LIMIT'], ['RETURNING']],
66        );
67    }
68
69    /**
70     * Extract WHERE clause from a DML statement.
71     */
72    public function extractWhereClause(string $sql): ?string
73    {
74        return SqlTokenStream::tokenize($sql, \ZtdQuery\Platform\Sqlite\Sql\SqliteLexerProfile::create())->topLevelClause(
75            ['WHERE'],
76            [['ORDER', 'BY'], ['LIMIT'], ['GROUP', 'BY'], ['HAVING'], ['RETURNING']],
77        );
78    }
79
80    /**
81     * Extract ORDER BY clause from a statement.
82     */
83    public function extractOrderByClause(string $sql): ?string
84    {
85        return SqlTokenStream::tokenize($sql, \ZtdQuery\Platform\Sqlite\Sql\SqliteLexerProfile::create())->topLevelClause(
86            ['ORDER', 'BY'],
87            [['LIMIT'], ['RETURNING']],
88        );
89    }
90
91    /**
92     * Extract LIMIT clause from a statement.
93     */
94    public function extractLimitClause(string $sql): ?string
95    {
96        return SqlTokenStream::tokenize($sql, \ZtdQuery\Platform\Sqlite\Sql\SqliteLexerProfile::create())->topLevelClause(['LIMIT'], [['RETURNING']]);
97    }
98
99    /**
100     * Extracts UPDATE aliases and top-level filtering clauses.
101     */
102    public function extractOnConflictUpdateWhere(string $sql): ?string
103    {
104        return SqlTokenStream::tokenize($sql, \ZtdQuery\Platform\Sqlite\Sql\SqliteLexerProfile::create())->topLevelClauseAfter(
105            ['DO', 'UPDATE', 'SET'],
106            ['WHERE'],
107            [['RETURNING']],
108        );
109    }
110}
111