Passed
Push — main ( efab48...3626c9 )
by Thierry
01:26
created

Grammar::sqlForCreateTable()   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 3
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Lagdo\DbAdmin\Driver\Db;
4
5
use Lagdo\DbAdmin\Driver\Entity\TableFieldEntity;
6
use Lagdo\DbAdmin\Driver\Entity\TableSelectEntity;
7
use Lagdo\DbAdmin\Driver\Entity\ForeignKeyEntity;
8
use Lagdo\DbAdmin\Driver\Entity\QueryEntity;
9
10
use Lagdo\DbAdmin\Driver\DriverInterface;
11
use Lagdo\DbAdmin\Driver\UtilInterface;
12
use Lagdo\DbAdmin\Driver\TranslatorInterface;
13
14
use function preg_match;
15
use function preg_quote;
16
use function substr;
17
use function strlen;
18
use function is_string;
19
use function rtrim;
20
use function intval;
21
22
abstract class Grammar implements GrammarInterface
23
{
24
    /**
25
     * @var DriverInterface
26
     */
27
    protected $driver;
28
29
    /**
30
     * @var UtilInterface
31
     */
32
    protected $util;
33
34
    /**
35
     * @var TranslatorInterface
36
     */
37
    protected $trans;
38
39
    /**
40
     * @var ConnectionInterface
41
     */
42
    protected $connection;
43
44
    /**
45
     * The constructor
46
     *
47
     * @param DriverInterface $driver
48
     * @param UtilInterface $util
49
     * @param TranslatorInterface $trans
50
     * @param ConnectionInterface $connection
51
     */
52
    public function __construct(DriverInterface $driver, UtilInterface $util,
53
        TranslatorInterface $trans, ConnectionInterface $connection)
54
    {
55
        $this->driver = $driver;
56
        $this->util = $util;
57
        $this->trans = $trans;
58
        $this->connection = $connection;
59
    }
60
61
    /**
62
     * @inheritDoc
63
     */
64
    public function escapeId(string $idf)
65
    {
66
        return $idf;
67
    }
68
69
    /**
70
     * @inheritDoc
71
     */
72
    public function unescapeId(string $idf)
73
    {
74
        $last = substr($idf, -1);
75
        return str_replace($last . $last, $last, substr($idf, 1, -1));
76
    }
77
78
    /**
79
     * @inheritDoc
80
     */
81
    public function table(string $idf)
82
    {
83
        return $this->escapeId($idf);
84
    }
85
86
    /**
87
     * @inheritDoc
88
     */
89
    public function limit(string $query, string $where, int $limit, int $offset = 0)
90
    {
91
        $sql = " $query$where";
92
        if ($limit > 0) {
93
            $sql .= " LIMIT $limit";
94
            if ($offset > 0) {
95
                $sql .= " OFFSET $offset";
96
            }
97
        }
98
        return $sql;
99
    }
100
101
    /**
102
     * @inheritDoc
103
     */
104
    public function formatForeignKey(ForeignKeyEntity $foreignKey)
105
    {
106
        $database = $foreignKey->database;
107
        $schema = $foreignKey->schema;
108
        $onActions = $this->driver->actions();
109
        $sources = implode(', ', array_map(function ($idf) {
110
            return $this->escapeId($idf);
111
        }, $foreignKey->source));
112
        $targets = implode(', ', array_map(function ($idf) {
113
            return $this->escapeId($idf);
114
        }, $foreignKey->target));
115
116
        $query = " FOREIGN KEY ($sources) REFERENCES ";
117
        if ($database != '' && $database != $this->driver->database()) {
118
            $query .= $this->escapeId($database) . '.';
119
        }
120
        if ($schema != '' && $schema != $this->driver->schema()) {
121
            $query .= $this->escapeId($schema) . '.';
122
        }
123
        $query .= $this->table($foreignKey->table) . " ($targets)";
124
        if (preg_match("~^($onActions)\$~", $foreignKey->onDelete)) {
125
            $query .= " ON DELETE {$foreignKey->onDelete}";
126
        }
127
        if (preg_match("~^($onActions)\$~", $foreignKey->onUpdate)) {
128
            $query .= " ON UPDATE {$foreignKey->onUpdate}";
129
        }
130
131
        return $query;
132
    }
133
134
    /**
135
     * @inheritDoc
136
     */
137
    public function buildSelectQuery(TableSelectEntity $select)
138
    {
139
        $query = \implode(', ', $select->fields) . ' FROM ' . $this->table($select->table);
140
        $limit = +$select->limit;
141
        $offset = $select->page ? $limit * $select->page : 0;
142
143
        return 'SELECT' . $this->limit($query, $select->clauses, $limit, $offset);
144
    }
145
146
    /**
147
     * @inheritDoc
148
     */
149
    public function defaultValue($field)
150
    {
151
        $default = $field->default;
152
        return ($default === null ? '' : ' DEFAULT ' .
153
            (preg_match('~char|binary|text|enum|set~', $field->type) ||
154
            preg_match('~^(?![a-z])~i', $default) ? $this->connection->quote($default) : $default));
155
    }
156
157
    /**
158
     * @inheritDoc
159
     */
160
    public function convertField(TableFieldEntity $field)
161
    {
162
        return '';
163
    }
164
165
    /**
166
     * @inheritDoc
167
     */
168
    public function unconvertField(TableFieldEntity $field, string $value)
169
    {
170
        return $value;
171
    }
172
173
    /**
174
     * @inheritDoc
175
     */
176
    public function convertFields(array $columns, array $fields, array $select = [])
177
    {
178
        $clause = '';
179
        foreach ($columns as $key => $val) {
180
            if (!empty($select) && !in_array($this->escapeId($key), $select)) {
181
                continue;
182
            }
183
            $as = $this->convertField($fields[$key]);
184
            if ($as) {
185
                $clause .= ', $as AS ' . $this->escapeId($key);
186
            }
187
        }
188
        return $clause;
189
    }
190
191
    /**
192
     * @inheritDoc
193
     */
194
    public function countRowsSql(string $table, array $where, bool $isGroup, array $groups)
195
    {
196
        $query = ' FROM ' . $this->table($table) . ($where ? ' WHERE ' . implode(' AND ', $where) : '');
197
        return ($isGroup && ($this->driver->jush() == 'sql' || count($groups) == 1)
198
            ? 'SELECT COUNT(DISTINCT ' . implode(', ', $groups) . ")$query"
199
            : 'SELECT COUNT(*)' . ($isGroup ? " FROM (SELECT 1$query GROUP BY " . implode(', ', $groups) . ') x' : $query)
200
        );
201
    }
202
203
    /**
204
     * @inheritDoc
205
     */
206
    public function sqlForCreateTable(string $table, bool $autoIncrement, string $style)
207
    {
208
        return '';
209
    }
210
211
    /**
212
     * @inheritDoc
213
     */
214
    public function sqlForCreateIndex(string $table, string $type, string $name, string $columns)
215
    {
216
        return '';
217
    }
218
219
    /**
220
     * @inheritDoc
221
     */
222
    public function sqlForUseDatabase(string $database)
223
    {
224
        return '';
225
    }
226
227
    /**
228
     * @inheritDoc
229
     */
230
    public function sqlForForeignKeys(string $table)
231
    {
232
        return '';
233
    }
234
235
    /**
236
     * @inheritDoc
237
     */
238
    public function sqlForTruncateTable(string $table)
239
    {
240
        return '';
241
    }
242
243
    /**
244
     * @inheritDoc
245
     */
246
    public function sqlForCreateTrigger(string $table)
247
    {
248
        return '';
249
    }
250
251
    /**
252
     * @inheritDoc
253
     */
254
    public function autoIncrement()
255
    {
256
        return '';
257
    }
258
259
    /**
260
     * @param QueryEntity $queryEntity
261
     *
262
     * @return bool
263
     */
264
    private function setDelimiter(QueryEntity $queryEntity)
265
    {
266
        $space = "(?:\\s|/\\*[\s\S]*?\\*/|(?:#|-- )[^\n]*\n?|--\r?\n)";
267
        if ($queryEntity->offset !== 0 ||
268
            !preg_match("~^$space*+DELIMITER\\s+(\\S+)~i", $queryEntity->queries, $match)) {
269
            return false;
270
        }
271
        $queryEntity->delimiter = $match[1];
272
        $queryEntity->queries = substr($queryEntity->queries, strlen($match[0]));
273
        return true;
274
    }
275
276
    /**
277
     * @param QueryEntity $queryEntity
278
     * @param string $found
279
     * @param array $match
280
     *
281
     * @return bool
282
     */
283
    private function notQuery(QueryEntity $queryEntity, string $found, array &$match)
284
    {
285
        return preg_match('(' . ($found == '/*' ? '\*/' : ($found == '[' ? ']' :
0 ignored issues
show
Bug Best Practice introduced by
The expression return preg_match('(' . ..., $queryEntity->offset) returns the type integer which is incompatible with the documented return type boolean.
Loading history...
286
            (preg_match('~^-- |^#~', $found) ? "\n" : preg_quote($found) . "|\\\\."))) . '|$)s',
287
            $queryEntity->queries, $match, PREG_OFFSET_CAPTURE, $queryEntity->offset);
288
    }
289
290
    /**
291
     * Return the regular expression for queries
292
     *
293
     * @return string
294
     */
295
    abstract protected function queryRegex();
296
    // Original code from Adminer
297
    // {
298
    //     $parse = '[\'"' .
299
    //         ($this->driver->jush() == "sql" ? '`#' :
300
    //         ($this->driver->jush() == "sqlite" ? '`[' :
301
    //         ($this->driver->jush() == "mssql" ? '[' : ''))) . ']|/\*|-- |$' .
302
    //         ($this->driver->jush() == "pgsql" ? '|\$[^$]*\$' : '');
303
    //     return "\\s*|$parse";
304
    // }
305
306
    /**
307
     * @param QueryEntity $queryEntity
308
     *
309
     * @return int
310
     */
311
    private function nextQueryPos(QueryEntity $queryEntity)
312
    {
313
        // TODO: Move this to driver implementations
314
        $parse = $this->queryRegex();
315
        $delimiter = preg_quote($queryEntity->delimiter);
316
        // Should always match
317
        preg_match("($delimiter$parse)", $queryEntity->queries, $match,
318
            PREG_OFFSET_CAPTURE, $queryEntity->offset);
319
        [$found, $pos] = $match[0];
320
        if (!is_string($found) && rtrim($queryEntity->queries) == '') {
321
            return -1;
322
        }
323
        $queryEntity->offset = $pos + strlen($found);
324
        if (empty($found) || rtrim($found) == $queryEntity->delimiter) {
325
            return intval($pos);
326
        }
327
        // Find matching quote or comment end
328
        $match = [];
329
        while ($this->notQuery($queryEntity, $found, $match)) {
330
            //! Respect sql_mode NO_BACKSLASH_ESCAPES
331
            $s = $match[0][0];
332
            $queryEntity->offset = $match[0][1] + strlen($s);
333
            if ($s[0] != "\\") {
334
                break;
335
            }
336
        }
337
        return 0;
338
    }
339
340
    /**
341
     * @inheritDoc
342
     */
343
    public function parseQueries(QueryEntity $queryEntity)
344
    {
345
        while ($queryEntity->queries !== '') {
346
            if ($this->setDelimiter($queryEntity)) {
347
                continue;
348
            }
349
            $pos = $this->nextQueryPos($queryEntity);
350
            if ($pos < 0) {
351
                return false;
352
            }
353
            if ($pos === 0) {
354
                continue;
355
            }
356
            // End of a query
357
            $queryEntity->query = substr($queryEntity->queries, 0, $pos);
358
            $queryEntity->queries = substr($queryEntity->queries, $queryEntity->offset);
359
            $queryEntity->offset = 0;
360
            return true;
361
        }
362
        return false;
363
    }
364
}
365