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

1<?php
2
3declare(strict_types=1);
4
5namespace ZtdQuery\Platform\Postgres\Sql\Dml\Update;
6
7use ZtdQuery\Sql\SqlTokenStream;
8
9/**
10 * Extracts PostgreSQL UPDATE aliases and assignments.
11 *
12 * @visibility root
13 */
14final class UpdateClauseParser
15{
16    /**
17     * Extract table alias from UPDATE statement.
18     */
19    public function extractUpdateAlias(string $sql): ?string
20    {
21        $sql = \ZtdQuery\Platform\Postgres\Sql\PostgreSqlLexicalMasker::maskComments($sql);
22        if (preg_match('/UPDATE\s+(?:ONLY\s+)?(?:"[^"]+"|[a-zA-Z_]\w*(?:\."[^"]+"|\.(?:[a-zA-Z_]\w*))?)\s+(?:AS\s+)?("[^"]+"|[a-zA-Z_]\w*)\s+SET\b/i', $sql, $m) === 1) {
23            return (new \ZtdQuery\Platform\Postgres\Sql\Lexing\IdentifierDecoder())->unquoteIdentifier($m[1]);
24        }
25
26        return null;
27    }
28
29    /**
30     * Extract SET assignments from UPDATE statement.
31     *
32     * @return array<string, string> column => value expression
33     */
34    public function extractUpdateSets(string $sql): array
35    {
36        $setClause = SqlTokenStream::tokenize($sql, \ZtdQuery\Platform\Postgres\Sql\PgSqlLexerProfile::create())->topLevelClause(
37            ['SET'],
38            [['FROM'], ['WHERE'], ['RETURNING']],
39        );
40        if ($setClause === null) {
41            return [];
42        }
43
44        $assignments = SqlTokenStream::tokenize($setClause, \ZtdQuery\Platform\Postgres\Sql\PgSqlLexerProfile::create())->splitTopLevel();
45        $result = [];
46
47        foreach ($assignments as $assignment) {
48            $assignment = trim($assignment);
49            if (preg_match('/^("[^"]+"|[a-zA-Z_]\w*)\s*=\s*(.+)$/s', $assignment, $parts) === 1) {
50                $colName = (new \ZtdQuery\Platform\Postgres\Sql\Lexing\IdentifierDecoder())->unquoteIdentifier($parts[1]);
51                $result[$colName] = trim($parts[2]);
52            }
53        }
54
55        return $result;
56    }
57}
58