Passed
Push — main ( 5c7dd3...ccb13a )
by Thierry
01:47
created

Grammar::sqlForRowCount()   A

Complexity

Conditions 6
Paths 24

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 7
c 0
b 0
f 0
nc 24
nop 4
dl 0
loc 10
rs 9.2222
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 sqlForRowCount(string $table, array $where, bool $isGroup, array $groups)
164
    {
165
        $query = ' FROM ' . $this->table($table);
166
        if (!empty($where)) {
167
            $query .= ' WHERE ' . implode(' AND ', $where);
168
        }
169
        return ($isGroup && ($this->driver->jush() == 'sql' || count($groups) == 1) ?
170
            'SELECT COUNT(DISTINCT ' . implode(', ', $groups) . ")$query" :
171
            'SELECT COUNT(*)' . ($isGroup ? " FROM (SELECT 1$query GROUP BY " .
172
            implode(', ', $groups) . ') x' : $query)
173
        );
174
    }
175
176
    /**
177
     * @param QueryEntity $queryEntity
178
     *
179
     * @return bool
180
     */
181
    private function setDelimiter(QueryEntity $queryEntity)
182
    {
183
        $space = "(?:\\s|/\\*[\s\S]*?\\*/|(?:#|-- )[^\n]*\n?|--\r?\n)";
184
        if ($queryEntity->offset !== 0 ||
185
            !preg_match("~^$space*+DELIMITER\\s+(\\S+)~i", $queryEntity->queries, $match)) {
186
            return false;
187
        }
188
        $queryEntity->delimiter = $match[1];
189
        $queryEntity->queries = substr($queryEntity->queries, strlen($match[0]));
190
        return true;
191
    }
192
193
    /**
194
     * @param QueryEntity $queryEntity
195
     * @param string $found
196
     * @param array $match
197
     *
198
     * @return bool
199
     */
200
    private function notQuery(QueryEntity $queryEntity, string $found, array &$match)
201
    {
202
        return preg_match('(' . ($found == '/*' ? '\*/' : ($found == '[' ? ']' :
203
            (preg_match('~^-- |^#~', $found) ? "\n" : preg_quote($found) . "|\\\\."))) . '|$)s',
204
            $queryEntity->queries, $match, PREG_OFFSET_CAPTURE, $queryEntity->offset) > 0;
205
    }
206
207
    /**
208
     * Return the regular expression for queries
209
     *
210
     * @return string
211
     */
212
    abstract protected function queryRegex();
213
    // Original code from Adminer
214
    // {
215
    //     $parse = '[\'"' .
216
    //         ($this->driver->jush() == "sql" ? '`#' :
217
    //         ($this->driver->jush() == "sqlite" ? '`[' :
218
    //         ($this->driver->jush() == "mssql" ? '[' : ''))) . ']|/\*|-- |$' .
219
    //         ($this->driver->jush() == "pgsql" ? '|\$[^$]*\$' : '');
220
    //     return "\\s*|$parse";
221
    // }
222
223
    /**
224
     * @param QueryEntity $queryEntity
225
     *
226
     * @return int
227
     */
228
    private function nextQueryPos(QueryEntity $queryEntity)
229
    {
230
        // TODO: Move this to driver implementations
231
        $parse = $this->queryRegex();
232
        $delimiter = preg_quote($queryEntity->delimiter);
233
        // Should always match
234
        preg_match("($delimiter$parse)", $queryEntity->queries, $match,
235
            PREG_OFFSET_CAPTURE, $queryEntity->offset);
236
        [$found, $pos] = $match[0];
237
        if (!is_string($found) && $queryEntity->queries == '') {
238
            return -1;
239
        }
240
        $queryEntity->offset = $pos + strlen($found);
241
        if (empty($found) || rtrim($found) == $queryEntity->delimiter) {
242
            return intval($pos);
243
        }
244
        // Find matching quote or comment end
245
        $match = [];
246
        while ($this->notQuery($queryEntity, $found, $match)) {
247
            //! Respect sql_mode NO_BACKSLASH_ESCAPES
248
            $s = $match[0][0];
249
            $queryEntity->offset = $match[0][1] + strlen($s);
250
            if ($s[0] != "\\") {
251
                break;
252
            }
253
        }
254
        return 0;
255
    }
256
257
    /**
258
     * @inheritDoc
259
     */
260
    public function parseQueries(QueryEntity $queryEntity)
261
    {
262
        $queryEntity->queries = trim($queryEntity->queries);
263
        while ($queryEntity->queries !== '') {
264
            if ($this->setDelimiter($queryEntity)) {
265
                continue;
266
            }
267
            $pos = $this->nextQueryPos($queryEntity);
268
            if ($pos < 0) {
269
                return false;
270
            }
271
            if ($pos === 0) {
272
                continue;
273
            }
274
            // End of a query
275
            $queryEntity->query = substr($queryEntity->queries, 0, $pos);
276
            $queryEntity->queries = substr($queryEntity->queries, $queryEntity->offset);
277
            $queryEntity->offset = 0;
278
            return true;
279
        }
280
        return false;
281
    }
282
}
283