packages/ztd-query-core/src/RewriteRefusal.php

1<?php
2
3declare(strict_types=1);
4
5namespace ZtdQuery;
6
7use ZtdQuery\Config\UnknownSchemaBehavior;
8use ZtdQuery\Config\UnsupportedSqlBehavior;
9use ZtdQuery\Config\ZtdConfig;
10use ZtdQuery\Connection\Exception\DatabaseException;
11use ZtdQuery\Exception\UnknownSchemaException;
12use ZtdQuery\Exception\UnsupportedSqlException;
13use ZtdQuery\Rewrite\QueryKind;
14use ZtdQuery\Rewrite\RewritePlan;
15
16/**
17 * What a session makes of a statement the rewriter would not take.
18 *
19 * A rewriter refuses for two reasons: the statement is written in a way ZTD
20 * cannot simulate, or it names a table nothing declared. What should happen
21 * then is the caller's to say, and they say it in the configuration — raise
22 * it, say something and carry on, or let the statement through untouched —
23 * so turning a refusal into a plan is a question of configuration and not of
24 * rewriting.
25 */
26final class RewriteRefusal
27{
28    /**
29     * @param ZtdConfig $config What the caller said should happen
30     */
31    public function __construct(private readonly ZtdConfig $config)
32    {
33    }
34
35    /**
36     * Answers the plan for a statement ZTD cannot simulate.
37     *
38     * @param UnsupportedSqlException $refusal Why the rewriter would not take it
39     * @param string $sql Statement as it was written
40     *
41     * @return RewritePlan A plan that does nothing, where the caller allows that
42     *
43     * @throws DatabaseException When the caller asked to be told by being refused
44     */
45    public function forUnsupported(UnsupportedSqlException $refusal, string $sql): RewritePlan
46    {
47        $behavior = $this->config->resolveUnsupportedBehavior($sql);
48        if ($behavior === UnsupportedSqlBehavior::Exception) {
49            throw new DatabaseException($refusal->getMessage(), null, 0, $refusal);
50        }
51        if ($behavior === UnsupportedSqlBehavior::Notice) {
52            trigger_error(sprintf('[ZTD Notice] Unsupported SQL ignored: %s', $sql), E_USER_NOTICE);
53        }
54
55        return new RewritePlan($sql, QueryKind::SKIPPED);
56    }
57
58    /**
59     * Answers the plan for a statement naming a table nothing declared.
60     *
61     * @param UnknownSchemaException $refusal Why the rewriter would not take it
62     * @param string $sql Statement as it was written
63     * @param string $emptyResultSelect Statement that reads nothing back, in the dialect at hand
64     *
65     * @return RewritePlan A plan that lets the statement through, or one that reads nothing back
66     *
67     * @throws DatabaseException When the caller asked to be told by being refused
68     */
69    public function forUnknownSchema(
70        UnknownSchemaException $refusal,
71        string $sql,
72        string $emptyResultSelect,
73    ): RewritePlan {
74        $behavior = $this->config->unknownSchemaBehavior();
75        if ($behavior === UnknownSchemaBehavior::Exception) {
76            throw new DatabaseException($refusal->getMessage(), null, 0, $refusal);
77        }
78        if ($behavior === UnknownSchemaBehavior::Passthrough) {
79            return new RewritePlan($sql, QueryKind::READ);
80        }
81        if ($behavior === UnknownSchemaBehavior::Notice) {
82            trigger_error(
83                sprintf('[ZTD Notice] Unknown table referenced: %s', $refusal->getIdentifier()),
84                E_USER_NOTICE,
85            );
86        }
87
88        return new RewritePlan($emptyResultSelect, QueryKind::READ);
89    }
90}
91