Passed
Push — main ( 3626c9...5c7dd3 )
by Thierry
01:38
created

Grammar::sqlForUseDatabase()   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 1
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\TableSelectEntity;
6
use Lagdo\DbAdmin\Driver\Entity\ForeignKeyEntity;
7
use Lagdo\DbAdmin\Driver\Entity\QueryEntity;
8
9
use Lagdo\DbAdmin\Driver\DriverInterface;
10
use Lagdo\DbAdmin\Driver\UtilInterface;
11
use Lagdo\DbAdmin\Driver\TranslatorInterface;
12
13
use function preg_match;
14
use function preg_quote;
15
use function substr;
16
use function strlen;
17
use function is_string;
18
use function trim;
19
use function rtrim;
20
use function intval;
21
22
abstract class Grammar implements GrammarInterface
23
{
24
    use GrammarTrait;
25
26
    /**
27
     * @var DriverInterface
28
     */
29
    protected $driver;
30
31
    /**
32
     * @var UtilInterface
33
     */
34
    protected $util;
35
36
    /**
37
     * @var TranslatorInterface
38
     */
39
    protected $trans;
40
41
    /**
42
     * @var ConnectionInterface
43
     */
44
    protected $connection;
45
46
    /**
47
     * The constructor
48
     *
49
     * @param DriverInterface $driver
50
     * @param UtilInterface $util
51
     * @param TranslatorInterface $trans
52
     * @param ConnectionInterface $connection
53
     */
54
    public function __construct(DriverInterface $driver, UtilInterface $util,
55
        TranslatorInterface $trans, ConnectionInterface $connection)
56
    {
57
        $this->driver = $driver;
58
        $this->util = $util;
59
        $this->trans = $trans;
60
        $this->connection = $connection;
61
    }
62
63
    /**
64
     * @inheritDoc
65
     */
66
    public function table(string $idf)
67
    {
68
        return $this->escapeId($idf);
69
    }
70
71
    /**
72
     * @inheritDoc
73
     */
74
    public function limit(string $query, string $where, int $limit, int $offset = 0)
75
    {
76
        $sql = " $query$where";
77
        if ($limit > 0) {
78
            $sql .= " LIMIT $limit";
79
            if ($offset > 0) {
80
                $sql .= " OFFSET $offset";
81
            }
82
        }
83
        return $sql;
84
    }
85
86
    /**
87
     * @inheritDoc
88
     */
89
    public function formatForeignKey(ForeignKeyEntity $foreignKey)
90
    {
91
        $database = $foreignKey->database;
92
        $schema = $foreignKey->schema;
93
        $onActions = $this->driver->actions();
94
        $sources = implode(', ', array_map(function ($idf) {
95
            return $this->escapeId($idf);
96
        }, $foreignKey->source));
97
        $targets = implode(', ', array_map(function ($idf) {
98
            return $this->escapeId($idf);
99
        }, $foreignKey->target));
100
101
        $query = " FOREIGN KEY ($sources) REFERENCES ";
102
        if ($database != '' && $database != $this->driver->database()) {
103
            $query .= $this->escapeId($database) . '.';
104
        }
105
        if ($schema != '' && $schema != $this->driver->schema()) {
106
            $query .= $this->escapeId($schema) . '.';
107
        }
108
        $query .= $this->table($foreignKey->table) . " ($targets)";
109
        if (preg_match("~^($onActions)\$~", $foreignKey->onDelete)) {
110
            $query .= " ON DELETE {$foreignKey->onDelete}";
111
        }
112
        if (preg_match("~^($onActions)\$~", $foreignKey->onUpdate)) {
113
            $query .= " ON UPDATE {$foreignKey->onUpdate}";
114
        }
115
116
        return $query;
117
    }
118
119
    /**
120
     * @inheritDoc
121
     */
122
    public function buildSelectQuery(TableSelectEntity $select)
123
    {
124
        $query = \implode(', ', $select->fields) . ' FROM ' . $this->table($select->table);
125
        $limit = +$select->limit;
126
        $offset = $select->page ? $limit * $select->page : 0;
127
128
        return 'SELECT' . $this->limit($query, $select->clauses, $limit, $offset);
129
    }
130
131
    /**
132
     * @inheritDoc
133
     */
134
    public function defaultValue($field)
135
    {
136
        $default = $field->default;
137
        return ($default === null ? '' : ' DEFAULT ' .
138
            (preg_match('~char|binary|text|enum|set~', $field->type) ||
139
            preg_match('~^(?![a-z])~i', $default) ? $this->connection->quote($default) : $default));
140
    }
141
142
    /**
143
     * @inheritDoc
144
     */
145
    public function convertFields(array $columns, array $fields, array $select = [])
146
    {
147
        $clause = '';
148
        foreach ($columns as $key => $val) {
149
            if (!empty($select) && !in_array($this->escapeId($key), $select)) {
150
                continue;
151
            }
152
            $as = $this->convertField($fields[$key]);
153
            if ($as) {
154
                $clause .= ', $as AS ' . $this->escapeId($key);
155
            }
156
        }
157
        return $clause;
158
    }
159
160
    /**
161
     * @inheritDoc
162
     */
163
    public function countRowsSql(string $table, array $where, bool $isGroup, array $groups)
164
    {
165
        $query = ' FROM ' . $this->table($table) . ($where ? ' WHERE ' . implode(' AND ', $where) : '');
166
        return ($isGroup && ($this->driver->jush() == 'sql' || count($groups) == 1)
167
            ? 'SELECT COUNT(DISTINCT ' . implode(', ', $groups) . ")$query"
168
            : 'SELECT COUNT(*)' . ($isGroup ? " FROM (SELECT 1$query GROUP BY " . implode(', ', $groups) . ') x' : $query)
169
        );
170
    }
171
172
    /**
173
     * @param QueryEntity $queryEntity
174
     *
175
     * @return bool
176
     */
177
    private function setDelimiter(QueryEntity $queryEntity)
178
    {
179
        $space = "(?:\\s|/\\*[\s\S]*?\\*/|(?:#|-- )[^\n]*\n?|--\r?\n)";
180
        if ($queryEntity->offset !== 0 ||
181
            !preg_match("~^$space*+DELIMITER\\s+(\\S+)~i", $queryEntity->queries, $match)) {
182
            return false;
183
        }
184
        $queryEntity->delimiter = $match[1];
185
        $queryEntity->queries = substr($queryEntity->queries, strlen($match[0]));
186
        return true;
187
    }
188
189
    /**
190
     * @param QueryEntity $queryEntity
191
     * @param string $found
192
     * @param array $match
193
     *
194
     * @return bool
195
     */
196
    private function notQuery(QueryEntity $queryEntity, string $found, array &$match)
197
    {
198
        return preg_match('(' . ($found == '/*' ? '\*/' : ($found == '[' ? ']' :
199
            (preg_match('~^-- |^#~', $found) ? "\n" : preg_quote($found) . "|\\\\."))) . '|$)s',
200
            $queryEntity->queries, $match, PREG_OFFSET_CAPTURE, $queryEntity->offset) > 0;
201
    }
202
203
    /**
204
     * Return the regular expression for queries
205
     *
206
     * @return string
207
     */
208
    abstract protected function queryRegex();
209
    // Original code from Adminer
210
    // {
211
    //     $parse = '[\'"' .
212
    //         ($this->driver->jush() == "sql" ? '`#' :
213
    //         ($this->driver->jush() == "sqlite" ? '`[' :
214
    //         ($this->driver->jush() == "mssql" ? '[' : ''))) . ']|/\*|-- |$' .
215
    //         ($this->driver->jush() == "pgsql" ? '|\$[^$]*\$' : '');
216
    //     return "\\s*|$parse";
217
    // }
218
219
    /**
220
     * @param QueryEntity $queryEntity
221
     *
222
     * @return int
223
     */
224
    private function nextQueryPos(QueryEntity $queryEntity)
225
    {
226
        // TODO: Move this to driver implementations
227
        $parse = $this->queryRegex();
228
        $delimiter = preg_quote($queryEntity->delimiter);
229
        // Should always match
230
        preg_match("($delimiter$parse)", $queryEntity->queries, $match,
231
            PREG_OFFSET_CAPTURE, $queryEntity->offset);
232
        [$found, $pos] = $match[0];
233
        if (!is_string($found) && $queryEntity->queries == '') {
234
            return -1;
235
        }
236
        $queryEntity->offset = $pos + strlen($found);
237
        if (empty($found) || rtrim($found) == $queryEntity->delimiter) {
238
            return intval($pos);
239
        }
240
        // Find matching quote or comment end
241
        $match = [];
242
        while ($this->notQuery($queryEntity, $found, $match)) {
243
            //! Respect sql_mode NO_BACKSLASH_ESCAPES
244
            $s = $match[0][0];
245
            $queryEntity->offset = $match[0][1] + strlen($s);
246
            if ($s[0] != "\\") {
247
                break;
248
            }
249
        }
250
        return 0;
251
    }
252
253
    /**
254
     * @inheritDoc
255
     */
256
    public function parseQueries(QueryEntity $queryEntity)
257
    {
258
        $queryEntity->queries = trim($queryEntity->queries);
259
        while ($queryEntity->queries !== '') {
260
            if ($this->setDelimiter($queryEntity)) {
261
                continue;
262
            }
263
            $pos = $this->nextQueryPos($queryEntity);
264
            if ($pos < 0) {
265
                return false;
266
            }
267
            if ($pos === 0) {
268
                continue;
269
            }
270
            // End of a query
271
            $queryEntity->query = substr($queryEntity->queries, 0, $pos);
272
            $queryEntity->queries = substr($queryEntity->queries, $queryEntity->offset);
273
            $queryEntity->offset = 0;
274
            return true;
275
        }
276
        return false;
277
    }
278
}
279