Passed
Push — master ( 0fda62...cc7033 )
by Ondřej
04:04
created

StatementException::getStatementPosition()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
namespace Ivory\Exception;
4
5
use Ivory\Lang\Sql\SqlState;
6
7
/**
8
 * Exception thrown upon errors on querying the database.
9
 *
10
 * The exception message contains the primary error message. To get the SQL State code, use {@link getSqlStateCode()}
11
 * method. To recognize SQLSTATE codes, {@link \Ivory\Result\SqlState} constants might be helpful. Alternatively,
12
 * {@link StatementException::getSqlState()} may be used to get an {@link SqlState} object.
13
 *
14
 * Besides the primary message and SQLSTATE code, the exception holds the error message detail, error hint, and all the
15
 * other error diagnostic fields except those referring to PostgreSQL source code (those referred to by the PHP
16
 * `PGSQL_DIAG_SOURCE_*` constants).
17
 *
18
 * This class is expected to be sub-classed by custom exception classes. *Any such subclasses are required to have the
19
 * same constructor arguments as this class, and are required to pass these arguments to the constructor of this class.*
20
 * See {@link StatementExceptionFactory} and its usages - either the global
21
 * {@link \Ivory\Ivory::getStatementExceptionFactory()} or the local
22
 * {@link \Ivory\Connection::getStatementExceptionFactory()} - for details on using custom statement exceptions.
23
 */
24
class StatementException extends \RuntimeException
25
{
26
    private $query;
27
    private $errorFields = [];
28
29
30
    /**
31
     * @param resource $resultHandler a PostgreSQL query result resource
32
     * @param string $query the statement, as sent by the client, which caused the error
33
     */
34
    public function __construct($resultHandler, string $query)
35
    {
36
        $message = self::inferMessage($resultHandler);
37
38
        parent::__construct($message); // NOTE: SQL state code (string) cannot be used as the exception code (int)
39
40
        $this->query = $query;
41
42
        $fields = [
43
            PGSQL_DIAG_SQLSTATE,
44
            PGSQL_DIAG_SEVERITY,
45
            PGSQL_DIAG_MESSAGE_DETAIL,
46
            PGSQL_DIAG_MESSAGE_HINT,
47
            PGSQL_DIAG_STATEMENT_POSITION,
48
            PGSQL_DIAG_INTERNAL_QUERY,
49
            PGSQL_DIAG_INTERNAL_POSITION,
50
            PGSQL_DIAG_CONTEXT,
51
        ];
52
        if (PHP_VERSION_ID >= 70300) {
53
            /** @noinspection PhpUndefinedConstantInspection */
54
            $fields[] = PGSQL_DIAG_SEVERITY_NONLOCALIZED;
0 ignored issues
show
Bug introduced by
The constant Ivory\Exception\PGSQL_DIAG_SEVERITY_NONLOCALIZED was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
55
        }
56
        foreach ($fields as $f) {
57
            $this->errorFields[$f] = pg_result_error_field($resultHandler, $f);
58
        }
59
    }
60
61
    private static function inferMessage($resultHandler): string
62
    {
63
        $message = pg_result_error_field($resultHandler, PGSQL_DIAG_MESSAGE_PRIMARY);
64
        if (strlen($message) > 0) {
65
            return $message;
66
        }
67
68
        switch (pg_result_status($resultHandler)) {
69
            case PGSQL_EMPTY_QUERY:
70
                return 'Empty query';
71
            case PGSQL_BAD_RESPONSE:
72
                return "The server's response was not understood";
73
            case PGSQL_NONFATAL_ERROR:
74
                return 'Non-fatal error';
75
            case PGSQL_FATAL_ERROR:
76
                return 'Fatal error';
77
            default:
78
                return 'Unknown error';
79
        }
80
    }
81
82
    final public function getSqlStateCode(): string
83
    {
84
        return $this->errorFields[PGSQL_DIAG_SQLSTATE];
85
    }
86
87
    final public function getSqlState(): SqlState
88
    {
89
        return SqlState::fromCode($this->getSqlStateCode());
90
    }
91
92
    /**
93
     * @return string the severity of the error;
94
     *                one of <tt>ERROR</tt>, <tt>FATAL</tt>, or <tt>PANIC</tt> (for errors), or <tt>WARNING</tt>,
95
     *                  <tt>NOTICE</tt>, <tt>DEBUG</tt>, <tt>INFO</tt>, or <tt>LOG</tt> (in a notice message), or a
96
     *                  localized translation of one of these
97
     */
98
    final public function getSeverity(): string
99
    {
100
        return $this->errorFields[PGSQL_DIAG_SEVERITY];
101
    }
102
103
    /**
104
     * @return string the nonlocalized severity of the error;
105
     *                one of <tt>ERROR</tt>, <tt>FATAL</tt>, or <tt>PANIC</tt> (for errors), or <tt>WARNING</tt>,
106
     *                  <tt>NOTICE</tt>, <tt>DEBUG</tt>, <tt>INFO</tt>, or <tt>LOG</tt> (in a notice message)
107
     */
108
    final public function getNonlocalizedSeverity(): string
109
    {
110
        if (PHP_VERSION_ID >= 70300) {
111
            /** @noinspection PhpUndefinedConstantInspection */
112
            return $this->errorFields[PGSQL_DIAG_SEVERITY_NONLOCALIZED];
0 ignored issues
show
Bug introduced by
The constant Ivory\Exception\PGSQL_DIAG_SEVERITY_NONLOCALIZED was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
113
        } else {
114
            throw new UnsupportedException('Nonlocalized severity is only supported on PHP >= 7.3');
115
        }
116
    }
117
118
    /**
119
     * @return string|null the detailed message, if any
120
     */
121
    final public function getMessageDetail(): ?string
122
    {
123
        return $this->errorFields[PGSQL_DIAG_MESSAGE_DETAIL];
124
    }
125
126
    /**
127
     * @return string|null the message hint, if any
128
     */
129
    final public function getMessageHint(): ?string
130
    {
131
        return $this->errorFields[PGSQL_DIAG_MESSAGE_HINT];
132
    }
133
134
    /**
135
     * @return string the original statement, as sent by the client, which caused the error
136
     */
137
    final public function getQuery(): string
138
    {
139
        return $this->query;
140
    }
141
142
    /**
143
     * @return int|null position of the error within the original statement, returned by {@link getQuery()};
144
     *                  one-based, measured in characters (not bytes)
145
     */
146
    final public function getStatementPosition(): ?int
147
    {
148
        return $this->errorFields[PGSQL_DIAG_STATEMENT_POSITION];
149
    }
150
151
    /**
152
     * @return string|null the text of the internally-generated query which actually caused the error (if any);
153
     *                     e.g., a query issued from within a PostgreSQL function
154
     */
155
    final public function getInternalQuery(): ?string
156
    {
157
        return $this->errorFields[PGSQL_DIAG_INTERNAL_QUERY];
158
    }
159
160
    /**
161
     * @return int|null position of the error within the {@link getInternalQuery() internally-generated query} which
162
     *                    actually caused the error (if any);
163
     *                  one-based, measured in characters (not bytes)
164
     */
165
    final public function getInternalPosition(): ?int
166
    {
167
        return $this->errorFields[PGSQL_DIAG_INTERNAL_POSITION];
168
    }
169
170
    /**
171
     * @return string|null the PostgreSQL context of the error; e.g., the stack trace of functions and internal queries
172
     */
173
    final public function getContext(): ?string
174
    {
175
        return $this->errorFields[PGSQL_DIAG_CONTEXT];
176
    }
177
}
178