Passed
Push — dbal ( 18ae5a...530d2d )
by Greg
05:47
created

AbstractDriver::quoteIdentifier()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2022 webtrees development team
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
 */
17
18
declare(strict_types=1);
19
20
namespace Fisharebest\Webtrees\DB\Drivers;
21
22
use Fisharebest\Webtrees\DB\Exceptions\SchemaException;
23
use Fisharebest\Webtrees\DB\Expression;
24
use Fisharebest\Webtrees\DB\Schema\ColumnInterface;
25
use Fisharebest\Webtrees\DB\Schema\ForeignKey;
26
use Fisharebest\Webtrees\DB\Schema\Index;
27
use Fisharebest\Webtrees\DB\Schema\PrimaryKey;
28
use Fisharebest\Webtrees\DB\Schema\Schema;
29
use Fisharebest\Webtrees\DB\Schema\Table;
30
use Fisharebest\Webtrees\DB\Schema\UniqueIndex;
31
use PDO;
32
use PDOException;
33
use RuntimeException;
34
35
use function array_map;
36
use function is_bool;
37
use function is_int;
38
39
/**
40
 * Common functionality for all drivers.
41
 */
42
abstract class AbstractDriver
43
{
44
    protected const IDENTIFIER_OPEN_QUOTE  = '"';
45
    protected const IDENTIFIER_CLOSE_QUOTE = '"';
46
47
    protected readonly string $server_version;
48
49
    /**
50
     * @param PDO    $pdo
51
     * @param string $prefix
52
     */
53
    public function __construct(protected readonly PDO $pdo, protected readonly string $prefix)
54
    {
55
        $this->server_version = $pdo->getAttribute(PDO::ATTR_SERVER_VERSION);
56
    }
57
58
    /**
59
     * @return array<string>
60
     */
61
    abstract public function listTables(): array;
62
63
    /**
64
     * @param string $table_name
65
     *
66
     * @return array<string>
67
     */
68
    abstract public function listColumns(string $table_name): array;
69
70
    /**
71
     * @param string $table_name
72
     *
73
     * @return array<string>
74
     */
75
    abstract public function listPrimaryKeys(string $table_name): array;
76
77
    /**
78
     * @param string $table_name
79
     *
80
     * @return array<string>
81
     */
82
    abstract public function listUniqueIndexes(string $table_name): array;
83
84
    /**
85
     * @param string $table_name
86
     *
87
     * @return array<string>
88
     */
89
    abstract public function listIndexes(string $table_name): array;
90
91
    /**
92
     * @param string $table_name
93
     *
94
     * @return array<string>
95
     */
96
    abstract public function listForeignKeys(string $table_name): array;
97
98
    /**
99
     * @return Schema
100
     */
101
    public function introspectSchema(): Schema
102
    {
103
        return new Schema(array_map($this->introspectTable(...), $this->listTables()));
0 ignored issues
show
Bug introduced by
A parse error occurred: Syntax error, unexpected ')' on line 103 at column 62
Loading history...
104
    }
105
106
    /**
107
     * @param string $table_name
108
     *
109
     * @return Table
110
     * @throws SchemaException
111
     */
112
    public function introspectTable(string $table_name): Table
113
    {
114
        $components = [];
115
116
        foreach ($this->listColumns($table_name) as $column_name) {
117
            $components[] = $this->introspectColumn($table_name, $column_name);
118
        }
119
120
        foreach ($this->listPrimaryKeys($table_name) as $key_name) {
121
            $components[] = $this->introspectPrimaryKey($table_name, $key_name);
122
        }
123
124
        foreach ($this->listUniqueIndexes($table_name) as $key_name) {
125
            $components[] = $this->introspectUniqueIndex($table_name, $key_name);
126
        }
127
128
        foreach ($this->listIndexes($table_name) as $key_name) {
129
            $components[] = $this->introspectIndex($table_name, $key_name);
130
        }
131
132
        foreach ($this->listForeignKeys($table_name) as $key_name) {
133
            $components[] = $this->introspectForeignKey($table_name, $key_name);
134
        }
135
136
        return new Table($table_name, $components);
137
    }
138
139
    /**
140
     * @param string $table_name
141
     * @param string $column_name
142
     *
143
     * @return ColumnInterface
144
     */
145
    abstract public function introspectColumn(string $table_name, string $column_name): ColumnInterface;
146
147
    /**
148
     * @param string $table_name
149
     * @param string $key_name
150
     *
151
     * @return PrimaryKey
152
     */
153
    abstract public function introspectPrimaryKey(string $table_name, string $key_name): PrimaryKey;
154
155
    /**
156
     * @param string $table_name
157
     * @param string $key_name
158
     *
159
     * @return UniqueIndex
160
     */
161
    abstract public function introspectUniqueIndex(string $table_name, string $key_name): UniqueIndex;
162
163
    /**
164
     * @param string $table_name
165
     * @param string $key_name
166
     *
167
     * @return Index
168
     */
169
    abstract public function introspectIndex(string $table_name, string $key_name): Index;
170
171
    /**
172
     * @param string $table_name
173
     * @param string $key_name
174
     *
175
     * @return ForeignKey
176
     */
177
    abstract public function introspectForeignKey(string $table_name, string $key_name): ForeignKey;
178
179
    /**
180
     * @param string $identifier
181
     *
182
     * @return Expression
183
     */
184
    public function quoteIdentifier(string $identifier): Expression
185
    {
186
        $escaped = strtr($identifier, [static::IDENTIFIER_CLOSE_QUOTE => static::IDENTIFIER_CLOSE_QUOTE . static::IDENTIFIER_CLOSE_QUOTE]);
187
188
        return new Expression(static::IDENTIFIER_OPEN_QUOTE . $escaped . static::IDENTIFIER_CLOSE_QUOTE);
189
    }
190
191
    /**
192
     * For quoting strings in DDL statements which cannot use placeholders. e.g. COMMENT 'foo' and DEFAULT 'bar'.
193
     *
194
     * @param string $value
195
     *
196
     * @return Expression
197
     */
198
    public function quoteValue(string $value): Expression
199
    {
200
        return new Expression($this->pdo->quote($value));
201
    }
202
    
203
    /**
204
     * Prepare, bind and execute a select query.
205
     *
206
     * @param string                            $sql
207
     * @param array<bool|int|float|string|null> $bindings
208
     *
209
     * @return array<object>
210
     */
211
    public function query(string $sql, array $bindings = []): array
212
    {
213
        try {
214
            $statement = $this->pdo->prepare($sql);
215
        } catch (PDOException) {
216
            $statement = false;
217
        }
218
219
        if ($statement === false) {
220
            throw new RuntimeException('Failed to prepare statement: ' . $sql);
221
        }
222
223
        foreach ($bindings as $param => $value) {
224
            $type = match (true) {
225
                $value === null => PDO::PARAM_NULL,
226
                is_bool($value) => PDO::PARAM_BOOL,
227
                is_int($value)  => PDO::PARAM_INT,
228
                default         => PDO::PARAM_STR,
229
            };
230
231
            if (is_int($param)) {
232
                // Positional parameters are numeric, starting at 1.
233
                $statement->bindValue($param + 1, $value, $type);
234
            } else {
235
                // Named parameters are (optionally) prefixed with a colon.
236
                $statement->bindValue(':' . $param, $value, $type);
237
            }
238
        }
239
240
        if ($statement->execute()) {
241
            return $statement->fetchAll(PDO::FETCH_OBJ);
242
        }
243
244
        throw new RuntimeException('Failed to execute statement: ' . $sql);
245
    }
246
}
247