• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dg / dibi / 23992579605

05 Apr 2026 02:31AM UTC coverage: 77.838%. Remained the same
23992579605

push

github

dg
drivers: escape*() methods moved to Engine [WIP]

101 of 113 new or added lines in 8 files covered. (89.38%)

162 existing lines in 9 files now uncovered.

1721 of 2211 relevant lines covered (77.84%)

0.78 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.57
/src/Dibi/Translator.php
1
<?php declare(strict_types=1);
2

3
/**
4
 * This file is part of the Dibi, smart database abstraction layer (https://dibi.nette.org)
5
 * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
6
 */
7

8
namespace Dibi;
9

10
use function array_filter, array_keys, array_splice, array_values, count, explode, get_debug_type, gettype, implode, is_array, is_bool, is_float, is_int, is_numeric, is_object, is_scalar, is_string, iterator_to_array, key, ltrim, number_format, preg_last_error, preg_match, preg_replace_callback, reset, rtrim, str_contains, str_replace, strcspn, strlen, strncasecmp, strtoupper, substr, trim;
11

12

13
/**
14
 * SQL translator.
15
 */
16
final class Translator
17
{
18
        private readonly Connection $connection;
19
        private readonly Drivers\Connection $driver;
20
        private readonly Drivers\Engine $engine;
21
        private int $cursor = 0;
22

23
        /** @var list<mixed>  SQL arguments */
24
        private array $args;
25

26
        /** @var string[] */
27
        private array $errors;
28
        private bool $comment = false;
29
        private int $ifLevel = 0;
30
        private int $ifLevelStart = 0;
31
        private ?int $limit = null;
32
        private ?int $offset = null;
33
        private HashMap $identifiers;
34

35

36
        public function __construct(Connection $connection)
1✔
37
        {
38
                $this->connection = $connection;
1✔
39
                $this->driver = $connection->getDriver();
1✔
40
                $this->engine = $connection->getDatabaseEngine();
1✔
41
                $this->identifiers = new HashMap($this->delimite(...));
1✔
42
        }
1✔
43

44

45
        /**
46
         * Generates SQL. Can be called only once.
47
         * @param  mixed[]  $args
48
         * @throws Exception
49
         */
50
        public function translate(array $args): string
1✔
51
        {
52
                $args = array_values($args);
1✔
53
                while (count($args) === 1 && is_array($args[0])) { // implicit array expansion
1✔
54
                        $args = array_values($args[0]);
1✔
55
                }
56

57
                $this->args = $args;
1✔
58
                $this->errors = [];
1✔
59

60
                $commandIns = null;
1✔
61
                $lastArr = null;
1✔
62
                $cursor = &$this->cursor;
1✔
63
                $comment = &$this->comment;
1✔
64

65
                // iterate
66
                $sql = [];
1✔
67
                while ($cursor < count($this->args)) {
1✔
68
                        $arg = $this->args[$cursor];
1✔
69
                        $cursor++;
1✔
70

71
                        // simple string means SQL
72
                        if (is_string($arg)) {
1✔
73
                                // speed-up - is regexp required?
74
                                $toSkip = strcspn($arg, '`[\'":%?');
1✔
75

76
                                if (strlen($arg) === $toSkip) { // needn't be translated
1✔
77
                                        $sql[] = $arg;
1✔
78
                                } else {
79
                                        $sql[] = substr($arg, 0, $toSkip)
1✔
80
                                                // note: this can change $this->args & $this->cursor & ...
81
                                                . preg_replace_callback(
1✔
82
                                                        <<<'XX'
83
                                                                /
1✔
84
                                                                (?=[`['":%?])                       ## speed-up
85
                                                                (?:
86
                                                                        `(.+?)`|                        ## 1) `identifier`
87
                                                                        \[(.+?)\]|                      ## 2) [identifier]
88
                                                                        (')((?:''|[^'])*)'|             ## 3,4) string
89
                                                                        (")((?:""|[^"])*)"|             ## 5,6) "string"
90
                                                                        ('|")|                          ## 7) lone quote
91
                                                                        :(\S*?:)([a-zA-Z0-9._]?)|       ## 8,9) :substitution:
92
                                                                        %([a-zA-Z~][a-zA-Z0-9~]{0,5})|  ## 10) modifier
93
                                                                        (\?)                            ## 11) placeholder
94
                                                                )/xs
95
                                                                XX,
96
                                                        $this->cb(...),
1✔
97
                                                        substr($arg, $toSkip),
1✔
98
                                                );
99
                                        if (preg_last_error()) {
1✔
UNCOV
100
                                                throw new PcreException;
×
101
                                        }
102
                                }
103

104
                                continue;
1✔
105
                        }
106

107
                        if ($comment) {
1✔
UNCOV
108
                                $sql[] = '...';
×
UNCOV
109
                                continue;
×
110
                        }
111

112
                        if ($arg instanceof \Traversable) {
1✔
UNCOV
113
                                $arg = iterator_to_array($arg);
×
114
                        }
115

116
                        if (is_array($arg) && is_string(key($arg))) {
1✔
117
                                // associative array -> autoselect between SET or VALUES & LIST
118
                                if ($commandIns === null) {
1✔
119
                                        $commandIns = strtoupper(substr(ltrim($this->args[0]), 0, 6));
1✔
120
                                        $commandIns = $commandIns === 'INSERT' || $commandIns === 'REPLAC';
1✔
121
                                        $sql[] = $this->formatValue($arg, $commandIns ? 'v' : 'a');
1✔
122
                                } else {
123
                                        if ($lastArr === $cursor - 1) {
1✔
124
                                                $sql[] = ',';
1✔
125
                                        }
126

127
                                        $sql[] = $this->formatValue($arg, $commandIns ? 'l' : 'a');
1✔
128
                                }
129

130
                                $lastArr = $cursor;
1✔
131
                                continue;
1✔
132
                        }
133

134
                        // default processing
135
                        $sql[] = $this->formatValue($arg, null);
1✔
136
                } // while
137

138
                if ($comment) {
1✔
139
                        $sql[] = '*/';
1✔
140
                }
141

142
                $sql = trim(implode(' ', $sql), ' ');
1✔
143

144
                if ($this->errors) {
1✔
145
                        throw new Exception('SQL translate error: ' . trim(reset($this->errors), '*'), 0, $sql);
1✔
146
                }
147

148
                // apply limit
149
                if ($this->limit !== null || $this->offset !== null) {
1✔
150
                        $this->engine->applyLimit($sql, $this->limit, $this->offset);
1✔
151
                }
152

153
                return $sql;
1✔
154
        }
155

156

157
        /**
158
         * Apply modifier to single value.
159
         */
160
        public function formatValue(mixed $value, ?string $modifier): string
1✔
161
        {
162
                if ($this->comment) {
1✔
163
                        return '...';
1✔
164
                }
165

166
                // array processing (with or without modifier)
167
                if ($value instanceof \Traversable) {
1✔
UNCOV
168
                        $value = iterator_to_array($value);
×
169
                }
170

171
                if (is_array($value)) {
1✔
172
                        $vx = $kx = [];
1✔
173
                        switch ($modifier) {
1✔
174
                                case 'and':
1✔
175
                                case 'or':  // key=val AND key IS NULL AND ...
1✔
176
                                        if (empty($value)) {
1✔
177
                                                return '1=1';
1✔
178
                                        }
179

180
                                        foreach ($value as $k => $v) {
1✔
181
                                                if (is_string($k)) {
1✔
182
                                                        $pair = explode('%', $k, 2); // split into identifier & modifier
1✔
183
                                                        $k = $this->identifiers->{$pair[0]} . ' ';
1✔
184
                                                        if (!isset($pair[1])) {
1✔
185
                                                                $v = $this->formatValue($v, null);
1✔
186
                                                                $vx[] = $k . ($v === 'NULL' ? 'IS ' : '= ') . $v;
1✔
187

188
                                                        } elseif ($pair[1] === 'ex') {
1✔
189
                                                                $vx[] = $k . $this->formatValue($v, 'ex');
1✔
190

191
                                                        } else {
192
                                                                $v = $this->formatValue($v, $pair[1]);
1✔
193
                                                                if ($pair[1] === 'l' || $pair[1] === 'in') {
1✔
194
                                                                        $op = 'IN ';
1✔
195
                                                                } elseif (str_contains($pair[1], 'like')) {
1✔
UNCOV
196
                                                                        $op = 'LIKE ';
×
197
                                                                } elseif ($v === 'NULL') {
1✔
198
                                                                        $op = 'IS ';
×
199
                                                                } else {
200
                                                                        $op = '= ';
1✔
201
                                                                }
202

203
                                                                $vx[] = $k . $op . $v;
1✔
204
                                                        }
205
                                                } else {
206
                                                        $vx[] = $this->formatValue($v, 'ex');
1✔
207
                                                }
208
                                        }
209

210
                                        return '(' . implode(') ' . strtoupper($modifier) . ' (', $vx) . ')';
1✔
211

212
                                case 'n':  // key, key, ... identifier names
1✔
213
                                        foreach ($value as $k => $v) {
1✔
214
                                                if (is_string($k)) {
1✔
215
                                                        $vx[] = $this->identifiers->$k . (empty($v) ? '' : ' AS ' . $this->engine->escapeIdentifier($v));
1✔
216
                                                } else {
217
                                                        $pair = explode('%', $v, 2); // split into identifier & modifier
1✔
218
                                                        $vx[] = $this->identifiers->{$pair[0]};
1✔
219
                                                }
220
                                        }
221

222
                                        return implode(', ', $vx);
1✔
223

224

225
                                case 'a': // key=val, key=val, ...
1✔
226
                                        foreach ($value as $k => $v) {
1✔
227
                                                $pair = explode('%', (string) $k, 2); // split into identifier & modifier
1✔
228
                                                $vx[] = $this->identifiers->{$pair[0]} . '='
1✔
229
                                                        . $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null));
1✔
230
                                        }
231

232
                                        return implode(', ', $vx);
1✔
233

234

235
                                case 'in':// replaces scalar %in modifier!
1✔
236
                                case 'l': // (val, val, ...)
1✔
237
                                        foreach ($value as $k => $v) {
1✔
238
                                                $pair = explode('%', (string) $k, 2); // split into identifier & modifier
1✔
239
                                                $vx[] = $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null));
1✔
240
                                        }
241

242
                                        return '(' . (($vx || $modifier === 'l') ? implode(', ', $vx) : 'NULL') . ')';
1✔
243

244

245
                                case 'v': // (key, key, ...) VALUES (val, val, ...)
1✔
246
                                        foreach ($value as $k => $v) {
1✔
247
                                                $pair = explode('%', $k, 2); // split into identifier & modifier
1✔
248
                                                $kx[] = $this->identifiers->{$pair[0]};
1✔
249
                                                $vx[] = $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null));
1✔
250
                                        }
251

252
                                        return '(' . implode(', ', $kx) . ') VALUES (' . implode(', ', $vx) . ')';
1✔
253

254
                                case 'm': // (key, key, ...) VALUES (val, val, ...), (val, val, ...), ...
1✔
255
                                        foreach ($value as $k => $v) {
1✔
256
                                                if (is_array($v)) {
1✔
257
                                                        if (isset($proto)) {
1✔
258
                                                                if ($proto !== array_keys($v)) {
1✔
259
                                                                        return $this->errors[] = '**Multi-insert array "' . $k . '" is different**';
1✔
260
                                                                }
261
                                                        } else {
262
                                                                $proto = array_keys($v);
1✔
263
                                                        }
264
                                                } else {
UNCOV
265
                                                        return $this->errors[] = '**Unexpected type ' . get_debug_type($v) . '**';
×
266
                                                }
267

268
                                                $pair = explode('%', $k, 2); // split into identifier & modifier
1✔
269
                                                $kx[] = $this->identifiers->{$pair[0]};
1✔
270
                                                foreach ($v as $k2 => $v2) {
1✔
271
                                                        $vx[$k2][] = $this->formatValue($v2, $pair[1] ?? (is_array($v2) ? 'ex!' : null));
1✔
272
                                                }
273
                                        }
274

275
                                        foreach ($vx as $k => $v) {
1✔
276
                                                $vx[$k] = '(' . implode(', ', $v) . ')';
1✔
277
                                        }
278

279
                                        return '(' . implode(', ', $kx) . ') VALUES ' . implode(', ', $vx);
1✔
280

281
                                case 'by': // key ASC, key DESC
1✔
282
                                        foreach ($value as $k => $v) {
1✔
283
                                                if (is_array($v)) {
1✔
284
                                                        $vx[] = $this->formatValue($v, 'ex');
1✔
285
                                                } elseif (is_string($k)) {
1✔
286
                                                        $v = (is_string($v) ? strncasecmp($v, 'd', 1) : $v > 0) ? 'ASC' : 'DESC';
1✔
287
                                                        $vx[] = $this->identifiers->$k . ' ' . $v;
1✔
288
                                                } else {
UNCOV
289
                                                        $vx[] = $this->identifiers->$v;
×
290
                                                }
291
                                        }
292

293
                                        return implode(', ', $vx);
1✔
294

295
                                case 'ex!':
1✔
UNCOV
296
                                        trigger_error('Use Dibi\Expression instead of array: ' . implode(', ', array_filter($value, is_scalar(...))), E_USER_WARNING);
×
297
                                        // break omitted
298
                                case 'ex':
1✔
299
                                case 'sql':
1✔
300
                                        return $this->connection->translate(...$value);
1✔
301

302
                                default:  // value, value, value - all with the same modifier
303
                                        foreach ($value as $v) {
1✔
304
                                                $vx[] = $this->formatValue($v, $modifier);
1✔
305
                                        }
306

307
                                        return implode(', ', $vx);
1✔
308
                        }
309
                }
310

311
                if (is_object($value)
1✔
312
                        && $modifier === null
1✔
313
                        && !$value instanceof Literal
1✔
314
                        && !$value instanceof Expression
1✔
315
                        && $result = $this->connection->translateObject($value)
1✔
316
                ) {
317
                        return $this->connection->translate(...$result->getValues());
1✔
318
                }
319

320
                // object-to-scalar procession
321
                if ($value instanceof \BackedEnum && is_scalar($value->value)) {
1✔
322
                        $value = $value->value;
1✔
323
                }
324

325
                // with modifier procession
326
                if ($modifier) {
1✔
327
                        if ($value !== null && !is_scalar($value)) {  // array is already processed
1✔
328
                                if ($value instanceof Literal && ($modifier === 'sql' || $modifier === 'SQL')) {
1✔
329
                                        return (string) $value;
1✔
330

331
                                } elseif ($value instanceof Expression && $modifier === 'ex') {
1✔
332
                                        return $this->connection->translate(...$value->getValues());
1✔
333

334
                                } elseif (
335
                                        $value instanceof \DateTimeInterface
1✔
336
                                        && ($modifier === 'd'
1✔
337
                                                || $modifier === 't'
1✔
338
                                                || $modifier === 'dt'
1✔
339
                                        )
340
                                ) {
341
                                        // continue
342
                                } else {
343
                                        $type = get_debug_type($value);
1✔
344
                                        return $this->errors[] = "**Invalid combination of type $type and modifier %$modifier**";
1✔
345
                                }
346
                        }
347

348
                        switch ($modifier) {
1✔
UNCOV
349
                                case 's':  // string
×
350
                                        return $value === null
1✔
351
                                                ? 'NULL'
×
352
                                                : $this->driver->escapeText((string) $value);
1✔
353

UNCOV
354
                                case 'bin':// binary
×
UNCOV
355
                                        return $value === null
×
356
                                                ? 'NULL'
×
357
                                                : $this->driver->escapeBinary($value);
×
358

359
                                case 'b':  // boolean
×
UNCOV
360
                                        return $value === null
×
361
                                                ? 'NULL'
×
NEW
362
                                                : $this->engine->escapeBool((bool) $value);
×
363

364
                                case 'sN': // string or null
×
UNCOV
365
                                case 'sn':
×
366
                                        return $value === '' || $value === 0 || $value === null
1✔
367
                                                ? 'NULL'
1✔
368
                                                : $this->driver->escapeText((string) $value);
1✔
369

UNCOV
370
                                case 'iN': // signed int or null
×
UNCOV
371
                                        if ($value === '' || $value === 0 || $value === null) {
×
372
                                                return 'NULL';
×
373
                                        }
374
                                        // break omitted
UNCOV
375
                                case 'i':  // signed int
×
UNCOV
376
                                case 'u':  // unsigned int, ignored
×
377
                                        if ($value === null) {
1✔
378
                                                return 'NULL';
1✔
379
                                        } elseif (is_string($value)) {
1✔
380
                                                if (preg_match('#[+-]?\d++(?:e\d+)?\z#A', $value)) {
1✔
381
                                                        return $value; // support for long numbers - keep them unchanged
1✔
382
                                                } else {
383
                                                        throw new Exception("Expected number, '$value' given.");
1✔
384
                                                }
385
                                        } else {
386
                                                return (string) (int) $value;
1✔
387
                                        }
388
                                        // break omitted
UNCOV
389
                                case 'f':  // float
×
390
                                        if ($value === null) {
1✔
391
                                                return 'NULL';
×
392
                                        } elseif (is_string($value)) {
1✔
393
                                                if (is_numeric($value) && substr($value, 1, 1) !== 'x') {
1✔
394
                                                        return $value; // support for long numbers - keep them unchanged
1✔
395
                                                } else {
396
                                                        throw new Exception("Expected number, '$value' given.");
1✔
397
                                                }
398
                                        } else {
399
                                                return rtrim(rtrim(number_format($value + 0, 10, '.', ''), '0'), '.');
1✔
400
                                        }
401
                                        // break omitted
UNCOV
402
                                case 'd':  // date
×
UNCOV
403
                                case 't':  // datetime
×
404
                                case 'dt': // datetime
×
405
                                        if ($value === null) {
1✔
406
                                                return 'NULL';
1✔
407
                                        } elseif (!$value instanceof \DateTimeInterface) {
1✔
408
                                                $value = new DateTime($value);
1✔
409
                                        }
410

411
                                        return $modifier === 'd'
1✔
412
                                                ? $this->engine->escapeDate($value)
1✔
413
                                                : $this->engine->escapeDateTime($value);
1✔
414

UNCOV
415
                                case 'by':
×
UNCOV
416
                                case 'n':  // composed identifier name
×
417
                                        return $this->identifiers->$value;
1✔
418

UNCOV
419
                                case 'N':  // identifier name
×
420
                                        return $this->engine->escapeIdentifier($value);
1✔
421

UNCOV
422
                                case 'ex':
×
UNCOV
423
                                case 'sql': // preserve as dibi-SQL  (TODO: leave only %ex)
×
424
                                        $value = (string) $value;
1✔
425
                                        // speed-up - is regexp required?
426
                                        $toSkip = strcspn($value, '`[\'":');
1✔
427
                                        if (strlen($value) !== $toSkip) {
1✔
428
                                                $value = substr($value, 0, $toSkip)
1✔
429
                                                        . preg_replace_callback(
1✔
430
                                                                <<<'XX'
431
                                                                        /
1✔
432
                                                                        (?=[`['":])
433
                                                                        (?:
434
                                                                                `(.+?)`|
435
                                                                                \[(.+?)]|
436
                                                                                (')((?:''|[^'])*)'|
437
                                                                                (")((?:""|[^"])*)"|
438
                                                                                (['"])|
439
                                                                                :(\S*?:)([a-zA-Z0-9._]?)
440
                                                                        )/sx
441
                                                                        XX,
442
                                                                $this->cb(...),
1✔
443
                                                                substr($value, $toSkip),
1✔
444
                                                        );
445
                                                if (preg_last_error()) {
1✔
UNCOV
446
                                                        throw new PcreException;
×
447
                                                }
448
                                        }
449

450
                                        return $value;
1✔
451

UNCOV
452
                                case 'SQL': // preserve as real SQL (TODO: rename to %sql)
×
453
                                        return (string) $value;
1✔
454

UNCOV
455
                                case 'like~':  // LIKE string%
×
456
                                        return $this->engine->escapeLike($value, 2);
1✔
457

UNCOV
458
                                case '~like':  // LIKE %string
×
459
                                        return $this->engine->escapeLike($value, 1);
1✔
460

UNCOV
461
                                case '~like~': // LIKE %string%
×
462
                                        return $this->engine->escapeLike($value, 3);
1✔
463

UNCOV
464
                                case 'like': // LIKE string
×
465
                                        return $this->engine->escapeLike($value, 0);
1✔
466

UNCOV
467
                                case 'and':
×
UNCOV
468
                                case 'or':
×
469
                                case 'a':
×
470
                                case 'l':
×
471
                                case 'v':
×
472
                                        $type = gettype($value);
×
473
                                        return $this->errors[] = "**Invalid combination of type $type and modifier %$modifier**";
×
474

475
                                default:
476
                                        return $this->errors[] = "**Unknown or unexpected modifier %$modifier**";
1✔
477
                        }
478
                }
479

480
                // without modifier procession
481
                if (is_string($value)) {
1✔
482
                        return $this->driver->escapeText($value);
1✔
483

484
                } elseif (is_int($value)) {
1✔
485
                        return (string) $value;
1✔
486

487
                } elseif (is_float($value)) {
1✔
488
                        return rtrim(rtrim(number_format($value, 10, '.', ''), '0'), '.');
1✔
489

490
                } elseif (is_bool($value)) {
1✔
491
                        return $this->engine->escapeBool($value);
1✔
492

493
                } elseif ($value === null) {
1✔
494
                        return 'NULL';
1✔
495

496
                } elseif ($value instanceof \DateTimeInterface) {
1✔
497
                        return $this->engine->escapeDateTime($value);
1✔
498

499
                } elseif ($value instanceof \DateInterval) {
1✔
500
                        return $this->engine->escapeDateInterval($value);
1✔
501

502
                } elseif ($value instanceof Literal) {
1✔
503
                        return (string) $value;
1✔
504

505
                } elseif ($value instanceof Expression) {
1✔
506
                        return $this->connection->translate(...$value->getValues());
1✔
507

508
                } else {
509
                        $type = get_debug_type($value);
1✔
510
                        return $this->errors[] = "**Unexpected $type**";
1✔
511
                }
512
        }
513

514

515
        /**
516
         * PREG callback from translate() or formatValue().
517
         * @param  array<int, string>  $matches
518
         */
519
        private function cb(array $matches): string
1✔
520
        {
521
                //    [1] => `ident`
522
                //    [2] => [ident]
523
                //    [3] => '
524
                //    [4] => string
525
                //    [5] => "
526
                //    [6] => string
527
                //    [7] => lone-quote
528
                //    [8] => substitution
529
                //    [9] => substitution flag
530
                //    [10] => modifier (when called from self::translate())
531
                //    [11] => placeholder (when called from self::translate())
532

533

534
                if (!empty($matches[11])) { // placeholder
1✔
535
                        $cursor = &$this->cursor;
1✔
536

537
                        if ($cursor >= count($this->args)) {
1✔
UNCOV
538
                                return $this->errors[] = '**Extra placeholder**';
×
539
                        }
540

541
                        $cursor++;
1✔
542
                        return $this->formatValue($this->args[$cursor - 1], null);
1✔
543
                }
544

545
                if (!empty($matches[10])) { // modifier
1✔
546
                        $mod = $matches[10];
1✔
547
                        $cursor = &$this->cursor;
1✔
548

549
                        if ($cursor >= count($this->args) && $mod !== 'else' && $mod !== 'end') {
1✔
UNCOV
550
                                return $this->errors[] = "**Extra modifier %$mod**";
×
551
                        }
552

553
                        if ($mod === 'if') {
1✔
554
                                $this->ifLevel++;
1✔
555
                                $cursor++;
1✔
556
                                if (!$this->comment && !$this->args[$cursor - 1]) {
1✔
557
                                        // open comment
558
                                        $this->ifLevelStart = $this->ifLevel;
1✔
559
                                        $this->comment = true;
1✔
560
                                        return '/*';
1✔
561
                                }
562

563
                                return '';
1✔
564

565
                        } elseif ($mod === 'else') {
1✔
566
                                if ($this->ifLevelStart === $this->ifLevel) {
1✔
567
                                        $this->ifLevelStart = 0;
1✔
568
                                        $this->comment = false;
1✔
569
                                        return '*/';
1✔
570
                                } elseif (!$this->comment) {
1✔
571
                                        $this->ifLevelStart = $this->ifLevel;
1✔
572
                                        $this->comment = true;
1✔
573
                                        return '/*';
1✔
574
                                }
575
                        } elseif ($mod === 'end') {
1✔
576
                                $this->ifLevel--;
1✔
577
                                if ($this->ifLevelStart === $this->ifLevel + 1) {
1✔
578
                                        // close comment
579
                                        $this->ifLevelStart = 0;
1✔
580
                                        $this->comment = false;
1✔
581
                                        return '*/';
1✔
582
                                }
583

UNCOV
584
                                return '';
×
585

586
                        } elseif ($mod === 'ex') { // array expansion
1✔
587
                                array_splice($this->args, $cursor, 1, $this->args[$cursor]);
1✔
588
                                return '';
1✔
589

590
                        } elseif ($mod === 'lmt') { // apply limit
1✔
591
                                $arg = $this->args[$cursor++];
1✔
592
                                if ($arg === null) {
1✔
593
                                } elseif ($this->comment) {
1✔
594
                                        return "(limit $arg)";
1✔
595
                                } else {
596
                                        $this->limit = Helpers::intVal($arg);
1✔
597
                                }
598

599
                                return '';
1✔
600

601
                        } elseif ($mod === 'ofs') { // apply offset
1✔
602
                                $arg = $this->args[$cursor++];
1✔
603
                                if ($arg === null) {
1✔
604
                                } elseif ($this->comment) {
1✔
605
                                        return "(offset $arg)";
1✔
606
                                } else {
607
                                        $this->offset = Helpers::intVal($arg);
1✔
608
                                }
609

610
                                return '';
1✔
611

612
                        } else { // default processing
613
                                $cursor++;
1✔
614
                                return $this->formatValue($this->args[$cursor - 1], $mod);
1✔
615
                        }
616
                }
617

618
                if ($this->comment) {
1✔
619
                        return '...';
1✔
620
                }
621

622
                if ($matches[1]) { // SQL identifiers: `ident`
1✔
623
                        return $this->identifiers->{$matches[1]};
1✔
624

625
                } elseif ($matches[2]) { // SQL identifiers: [ident]
1✔
626
                        return $this->identifiers->{$matches[2]};
1✔
627

628
                } elseif ($matches[3]) { // SQL strings: '...'
1✔
629
                        return $this->driver->escapeText(str_replace("''", "'", $matches[4]));
1✔
630

631
                } elseif ($matches[5]) { // SQL strings: "..."
1✔
632
                        return $this->driver->escapeText(str_replace('""', '"', $matches[6]));
1✔
633

634
                } elseif ($matches[7]) { // string quote
1✔
635
                        return $this->errors[] = '**Alone quote**';
1✔
636
                }
637

638
                if ($matches[8]) { // SQL identifier substitution
1✔
639
                        $m = substr($matches[8], 0, -1);
1✔
640
                        $m = $this->connection->getSubstitutes()->$m;
1✔
641
                        return $matches[9] === ''
1✔
642
                                ? $this->formatValue($m, null)
1✔
643
                                : $m . $matches[9]; // value or identifier
1✔
644
                }
645

UNCOV
646
                throw new \Exception('this should be never executed');
×
647
        }
648

649

650
        /**
651
         * Apply substitutions to identifier and delimites it.
652
         * @internal
653
         */
654
        public function delimite(string $value): string
1✔
655
        {
656
                $value = $this->connection->substitute($value);
1✔
657
                $parts = explode('.', $value);
1✔
658
                foreach ($parts as &$v) {
1✔
659
                        if ($v !== '*') {
1✔
660
                                $v = $this->engine->escapeIdentifier($v);
1✔
661
                        }
662
                }
663

664
                return implode('.', $parts);
1✔
665
        }
666
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc