Passed
Push — main ( 317226...e95517 )
by Thierry
07:32
created

Driver::removeDefiner()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Lagdo\DbAdmin\Driver;
4
5
use Exception;
6
use Lagdo\DbAdmin\Driver\Db\ConnectionInterface;
7
use Lagdo\DbAdmin\Driver\Db\ServerInterface;
8
use Lagdo\DbAdmin\Driver\Db\DatabaseInterface;
9
use Lagdo\DbAdmin\Driver\Db\TableInterface;
10
use Lagdo\DbAdmin\Driver\Db\QueryInterface;
11
use Lagdo\DbAdmin\Driver\Db\GrammarInterface;
12
use Lagdo\DbAdmin\Driver\Entity\ConfigEntity;
13
use Lagdo\DbAdmin\Driver\Exception\AuthException;
14
15
use function is_object;
16
use function preg_match;
17
use function preg_replace;
18
use function substr;
19
use function strlen;
20
use function version_compare;
21
22
abstract class Driver implements DriverInterface
23
{
24
    use ErrorTrait;
25
    use ConfigTrait;
0 ignored issues
show
introduced by
The trait Lagdo\DbAdmin\Driver\ConfigTrait requires some properties which are not provided by Lagdo\DbAdmin\Driver\Driver: $onActions, $jush, $numberRegex, $grouping, $editFunctions, $inout, $version, $unsigned, $enumLength, $functions, $types, $operators
Loading history...
26
    use ConnectionTrait;
27
    use ServerTrait;
28
    use TableTrait;
29
    use DatabaseTrait;
30
    use QueryTrait;
31
    use GrammarTrait;
32
33
    /**
34
     * @var UtilInterface
35
     */
36
    protected $util;
37
38
    /**
39
     * @var TranslatorInterface
40
     */
41
    protected $trans;
42
43
    /**
44
     * @var ServerInterface
45
     */
46
    protected $server;
47
48
    /**
49
     * @var DatabaseInterface
50
     */
51
    protected $database;
52
53
    /**
54
     * @var TableInterface
55
     */
56
    protected $table;
57
58
    /**
59
     * @var QueryInterface
60
     */
61
    protected $query;
62
63
    /**
64
     * @var GrammarInterface
65
     */
66
    protected $grammar;
67
68
    /**
69
     * @var ConnectionInterface
70
     */
71
    protected $connection;
72
73
    /**
74
     * @var ConnectionInterface
75
     */
76
    protected $mainConnection;
77
78
    /**
79
     * @var ConfigEntity
80
     */
81
    protected $config;
82
83
    /**
84
     * @var History
85
     */
86
    protected $history;
87
88
    /**
89
     * The constructor
90
     *
91
     * @param UtilInterface $util
92
     * @param TranslatorInterface $trans
93
     * @param array $options
94
     */
95
    public function __construct(UtilInterface $util, TranslatorInterface $trans, array $options)
96
    {
97
        $this->util = $util;
98
        $this->util->setDriver($this);
99
        $this->trans = $trans;
100
        $this->config = new ConfigEntity($options);
101
        $this->history = new History($trans);
102
        $this->initConfig();
103
        $this->createConnection();
104
        // Set the current connection as the main connection.
105
        $this->mainConnection = $this->connection;
106
    }
107
108
    /**
109
     * @param ConnectionInterface $connection
110
     *
111
     * @return Driver
112
     */
113
    public function useConnection(ConnectionInterface $connection)
114
    {
115
        $this->connection = $connection;
116
        return $this;
117
    }
118
119
    /**
120
     * @return Driver
121
     */
122
    public function useMainConnection()
123
    {
124
        $this->connection = $this->mainConnection;
125
        return $this;
126
    }
127
128
    /**
129
     * Set driver config
130
     *
131
     * @return void
132
     */
133
    abstract protected function initConfig();
134
135
    /**
136
     * @inheritDoc
137
     * @throws AuthException
138
     */
139
    public function connect(string $database, string $schema)
140
    {
141
        if (!$this->connection->open($database, $schema)) {
142
            throw new AuthException($this->error());
143
        }
144
        $this->config->database = $database;
145
        $this->config->schema = $schema;
146
    }
147
148
    /**
149
     * @inheritDoc
150
     */
151
    public function minVersion(string $version, string $mariaDb = '')
152
    {
153
        $info = $this->connection->serverInfo();
154
        if ($mariaDb && preg_match('~([\d.]+)-MariaDB~', $info, $match)) {
155
            $info = $match[1];
156
            $version = $mariaDb;
157
        }
158
        return (version_compare($info, $version) >= 0);
159
    }
160
161
    /**
162
     * @inheritDoc
163
     */
164
    public function charset()
165
    {
166
        // SHOW CHARSET would require an extra query
167
        return ($this->minVersion('5.5.3', 0) ? 'utf8mb4' : 'utf8');
168
    }
169
170
    /**
171
     * @inheritDoc
172
     */
173
    public function begin()
174
    {
175
        $result = $this->connection->query("BEGIN");
176
        return $result !== false;
177
    }
178
179
    /**
180
     * @inheritDoc
181
     */
182
    public function commit()
183
    {
184
        $result = $this->connection->query("COMMIT");
185
        return $result !== false;
186
    }
187
188
    /**
189
     * @inheritDoc
190
     */
191
    public function rollback()
192
    {
193
        $result = $this->connection->query("ROLLBACK");
194
        return $result !== false;
195
    }
196
197
    /**
198
     * @inheritDoc
199
     */
200
    public function setUtf8mb4(string $create)
201
    {
202
        static $set = false;
203
        // possible false positive
204
        if (!$set && preg_match('~\butf8mb4~i', $create)) {
205
            $set = true;
206
            return 'SET NAMES ' . $this->charset() . ";\n\n";
207
        }
208
        return '';
209
    }
210
211
    /**
212
     * @inheritDoc
213
     */
214
    public function execute(string $query)
215
    {
216
        $this->history->save($query);
217
        return $this->connection->query($query);
218
    }
219
220
    /**
221
     * @inheritDoc
222
     */
223
    public function queries()
224
    {
225
        return $this->history->queries();
226
    }
227
228
    /**
229
     * @inheritDoc
230
     */
231
    public function applyQueries(string $query, array $tables, $escape = null)
232
    {
233
        if (!$escape) {
234
            $escape = function ($table) {
235
                return $this->table($table);
236
            };
237
        }
238
        foreach ($tables as $table) {
239
            if (!$this->execute("$query " . $escape($table))) {
240
                return false;
241
            }
242
        }
243
        return true;
244
    }
245
246
    /**
247
     * @inheritDoc
248
     */
249
    public function values(string $query, int $column = 0)
250
    {
251
        $statement = $this->execute($query);
252
        if (!is_object($statement)) {
253
            return [];
254
        }
255
        $values = [];
256
        while ($row = $statement->fetchRow()) {
257
            $values[] = $row[$column];
258
        }
259
        return $values;
260
    }
261
262
    /**
263
     * @inheritDoc
264
     */
265
    public function colValues(string $query, string $column)
266
    {
267
        $statement = $this->execute($query);
268
        if (!is_object($statement)) {
269
            return [];
270
        }
271
        $values = [];
272
        while ($row = $statement->fetchAssoc()) {
273
            $values[] = $row[$column];
274
        }
275
        return $values;
276
    }
277
278
    /**
279
     * @inheritDoc
280
     */
281
    public function keyValues(string $query, int $keyColumn = 0, int $valueColumn = 1)
282
    {
283
        $statement = $this->execute($query);
284
        if (!is_object($statement)) {
285
            return [];
286
        }
287
        $values = [];
288
        while ($row = $statement->fetchRow()) {
289
            $values[$row[$keyColumn]] = $row[$valueColumn];
290
        }
291
        return $values;
292
    }
293
294
    /**
295
     * @inheritDoc
296
     */
297
    public function rows(string $query)
298
    {
299
        $statement = $this->execute($query);
300
        if (!is_object($statement)) { // can return true
301
            return [];
302
        }
303
        $rows = [];
304
        while ($row = $statement->fetchAssoc()) {
305
            $rows[] = $row;
306
        }
307
        return $rows;
308
    }
309
310
    /**
311
     * Remove current user definer from SQL command
312
     *
313
     * @param string $query
314
     *
315
     * @return string
316
     */
317
    public function removeDefiner(string $query): string
318
    {
319
        return preg_replace('~^([A-Z =]+) DEFINER=`' .
320
            preg_replace('~@(.*)~', '`@`(%|\1)', $this->user()) .
321
            '`~', '\1', $query); //! proper escaping of user
322
    }
323
324
    /**
325
     * Query printed after execution in the message
326
     *
327
     * @param string $query Executed query
328
     *
329
     * @return string
330
     */
331
    private function queryToLog(string $query/*, string $time*/): string
332
    {
333
        if (strlen($query) > 1e6) {
334
            // [\x80-\xFF] - valid UTF-8, \n - can end by one-line comment
335
            $query = preg_replace('~[\x80-\xFF]+$~', '', substr($query, 0, 1e6)) . "\n…";
336
        }
337
        return $query;
338
    }
339
340
    /**
341
     * Execute query
342
     *
343
     * @param string $query
344
     * @param bool $execute
345
     * @param bool $failed
346
     *
347
     * @return bool
348
     * @throws Exception
349
     */
350
    public function executeQuery(string $query, bool $execute = true,
351
        bool $failed = false/*, string $time = ''*/): bool
352
    {
353
        if ($execute) {
354
            // $start = microtime(true);
355
            $failed = !$this->execute($query);
356
            // $time = $this->trans->formatTime($start);
357
        }
358
        if ($failed) {
359
            $sql = '';
360
            if ($query) {
361
                $sql = $this->queryToLog($query/*, $time*/);
362
            }
363
            throw new Exception($this->error() . $sql);
364
        }
365
        return true;
366
    }
367
368
    /**
369
     * Create SQL condition from parsed query string
370
     *
371
     * @param array $where Parsed query string
372
     * @param array $fields
373
     *
374
     * @return string
375
     */
376
    public function where(array $where, array $fields = []): string
377
    {
378
        $clauses = [];
379
        $wheres = $where["where"] ?? [];
380
        foreach ((array) $wheres as $key => $value) {
381
            $key = $this->util->bracketEscape($key, 1); // 1 - back
382
            $column = $this->util->escapeKey($key);
383
            $clauses[] = $column .
384
                // LIKE because of floats but slow with ints
385
                ($this->jush() == "sql" && is_numeric($value) && preg_match('~\.~', $value) ? " LIKE " .
386
                $this->quote($value) : ($this->jush() == "mssql" ? " LIKE " .
387
                $this->quote(preg_replace('~[_%[]~', '[\0]', $value)) : " = " . // LIKE because of text
388
                $this->unconvertField($fields[$key], $this->quote($value)))); //! enum and set
389
            if ($this->jush() == "sql" &&
390
                preg_match('~char|text~', $fields[$key]->type) && preg_match("~[^ -@]~", $value)) {
391
                // not just [a-z] to catch non-ASCII characters
392
                $clauses[] = "$column = " . $this->quote($value) . " COLLATE " . $this->charset() . "_bin";
393
            }
394
        }
395
        $nulls = $where["null"] ?? [];
396
        foreach ((array) $nulls as $key) {
397
            $clauses[] = $this->util->escapeKey($key) . " IS NULL";
398
        }
399
        return implode(" AND ", $clauses);
400
    }
401
}
402