Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
85.98% covered (success)
85.98%
92 / 107
82.61% covered (success)
82.61%
19 / 23
CRAP
0.00% covered (danger)
0.00%
0 / 1
AbstractSql
85.98% covered (success)
85.98%
92 / 107
82.61% covered (success)
82.61%
19 / 23
89.09
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 isMysql
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isPgsql
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isSqlsrv
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isSqlite
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 db
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDb
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setIdQuoteType
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 setPlaceholder
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getDbType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPlaceholder
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getIdQuoteType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getOpenQuote
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCloseQuote
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getParameterCount
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 incrementParameterCount
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 decrementParameterCount
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 isParameter
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
5
 getParameter
85.71% covered (success)
85.71%
18 / 21
0.00% covered (danger)
0.00%
0 / 1
13.49
 quoteId
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
9
 quote
60.00% covered (warning)
60.00%
6 / 10
0.00% covered (danger)
0.00%
0 / 1
26.54
 init
79.17% covered (success)
79.17%
19 / 24
0.00% covered (danger)
0.00%
0 / 1
10.90
 initQuoteType
76.92% covered (success)
76.92%
10 / 13
0.00% covered (danger)
0.00%
0 / 1
5.31
1<?php
2/**
3 * Pop PHP Framework (http://www.popphp.org/)
4 *
5 * @link       https://github.com/popphp/popphp-framework
6 * @author     Nick Sagona, III <dev@nolainteractive.com>
7 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
8 * @license    http://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace Pop\Db\Sql;
15
16use Pop\Db\Adapter;
17
18/**
19 * Abstract SQL class
20 *
21 * @category   Pop
22 * @package    Pop\Db
23 * @author     Nick Sagona, III <dev@nolainteractive.com>
24 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
25 * @license    http://www.popphp.org/license     New BSD License
26 * @version    6.5.0
27 */
28abstract class AbstractSql
29{
30
31    /**
32     * Constants for database types
33     */
34    const MYSQL  = 'MYSQL';
35    const PGSQL  = 'PGSQL';
36    const SQLITE = 'SQLITE';
37    const SQLSRV = 'SQLSRV';
38
39    /**
40     * Constants for id quote types
41     */
42    const BACKTICK     = 'BACKTICK';
43    const BRACKET      = 'BRACKET';
44    const DOUBLE_QUOTE = 'DOUBLE_QUOTE';
45    const NO_QUOTE     = 'NO_QUOTE';
46
47    /**
48     * Database object
49     * @var ?Adapter\AbstractAdapter
50     */
51    protected ?Adapter\AbstractAdapter $db = null;
52
53    /**
54     * Database type
55     * @var ?string
56     */
57    protected ?string $dbType = null;
58
59    /**
60     * SQL placeholder
61     * @var ?string
62     */
63    protected ?string $placeholder = null;
64
65    /**
66     * ID quote type
67     * @var string
68     */
69    protected string $idQuoteType = 'NO_QUOTE';
70
71    /**
72     * ID open quote
73     * @var ?string
74     */
75    protected ?string $openQuote = null;
76
77    /**
78     * ID close quote
79     * @var ?string
80     */
81    protected ?string $closeQuote = null;
82
83    /**
84     * Parameter count
85     * @var int
86     */
87    protected int $parameterCount = 0;
88
89    /**
90     * Constructor
91     *
92     * Instantiate the SQL object
93     *
94     * @param  Adapter\AbstractAdapter $db
95     */
96    public function __construct(Adapter\AbstractAdapter $db)
97    {
98        $this->db = $db;
99        $this->init(strtolower(get_class($db)));
100    }
101
102    /**
103     * Determine if the DB type is MySQL
104     *
105     * @return bool
106     */
107    public function isMysql(): bool
108    {
109        return ($this->dbType == self::MYSQL);
110    }
111
112    /**
113     * Determine if the DB type is PostgreSQL
114     *
115     * @return bool
116     */
117    public function isPgsql(): bool
118    {
119        return ($this->dbType == self::PGSQL);
120    }
121
122    /**
123     * Determine if the DB type is SQL Server
124     *
125     * @return bool
126     */
127    public function isSqlsrv(): bool
128    {
129        return ($this->dbType == self::SQLSRV);
130    }
131
132    /**
133     * Determine if the DB type is SQLite
134     *
135     * @return bool
136     */
137    public function isSqlite(): bool
138    {
139        return ($this->dbType == self::SQLITE);
140    }
141
142    /**
143     * Get the current database adapter object (alias method)
144     *
145     * @return ?Adapter\AbstractAdapter
146     */
147    public function db(): ?Adapter\AbstractAdapter
148    {
149        return $this->db;
150    }
151
152    /**
153     * Get the current database adapter object
154     *
155     * @return ?Adapter\AbstractAdapter
156     */
157    public function getDb(): ?Adapter\AbstractAdapter
158    {
159        return $this->db;
160    }
161
162    /**
163     * Set the quote ID type
164     *
165     * @param  string $type
166     * @return AbstractSql
167     */
168    public function setIdQuoteType(string $type = self::NO_QUOTE): AbstractSql
169    {
170        if (defined('Pop\Db\Sql::' . $type)) {
171            $this->idQuoteType = $type;
172            $this->initQuoteType();
173        }
174        return $this;
175    }
176
177    /**
178     * Set the placeholder
179     *
180     * @param  string $placeholder
181     * @return AbstractSql
182     */
183    public function setPlaceholder(string $placeholder): AbstractSql
184    {
185        $this->placeholder = $placeholder;
186        return $this;
187    }
188
189    /**
190     * Get the current database type
191     *
192     * @return ?string
193     */
194    public function getDbType(): ?string
195    {
196        return $this->dbType;
197    }
198
199    /**
200     * Get the SQL placeholder
201     *
202     * @return ?string
203     */
204    public function getPlaceholder(): ?string
205    {
206        return $this->placeholder;
207    }
208
209    /**
210     * Get the quote ID type
211     *
212     * @return string
213     */
214    public function getIdQuoteType(): string
215    {
216        return $this->idQuoteType;
217    }
218
219    /**
220     * Get open quote
221     *
222     * @return ?string
223     */
224    public function getOpenQuote(): ?string
225    {
226        return $this->openQuote;
227    }
228
229    /**
230     * Get close quote
231     *
232     * @return ?string
233     */
234    public function getCloseQuote(): ?string
235    {
236        return $this->closeQuote;
237    }
238
239    /**
240     * Get parameter count
241     *
242     * @return int
243     */
244    public function getParameterCount(): int
245    {
246        return $this->parameterCount;
247    }
248
249    /**
250     * Increment parameter count
251     *
252     * @return AbstractSql
253     */
254    public function incrementParameterCount(): AbstractSql
255    {
256        $this->parameterCount++;
257        return $this;
258    }
259
260    /**
261     * Decrement parameter count
262     *
263     * @return AbstractSql
264     */
265    public function decrementParameterCount(): AbstractSql
266    {
267        $this->parameterCount--;
268        return $this;
269    }
270
271    /**
272     * Check if value is parameter placeholder
273     *
274     * @param  mixed   $value
275     * @param  ?string $column
276     * @return bool
277     */
278    public function isParameter(mixed $value, ?string $column = null): bool
279    {
280        return ((!empty($value) && ($column !== null) && ((':' . $column) == $value)) ||
281                ((preg_match('/^\$\d*\d$/', (string)$value) == 1)) ||
282                (($value == '?')));
283    }
284
285    /**
286     * Get parameter placeholder value
287     *
288     * @param  mixed   $value
289     * @param  ?string $column
290     * @return string
291     */
292    public function getParameter(mixed $value, ?string $column = null): string
293    {
294        $detectedDbType = null;
295        $parameter      = $value;
296
297        // SQLITE
298        if (($column !== null) && ((':' . $column) == $value)) {
299            $detectedDbType = self::SQLITE;
300        // PGSQL
301        } else if (preg_match('/^\$\d*\d$/', $value) == 1) {
302            $detectedDbType = self::PGSQL;
303        // MYSQL/SQLSRV
304        } else if ($value == '?') {
305            $detectedDbType = self::MYSQL;
306        }
307
308        $this->incrementParameterCount();
309
310        // If the parameter is given in a different format than what the db expects, translate it
311        $realDbType = $this->dbType;
312        if ($this->placeholder == ':') {
313            // Either native SQLITE or PDO, in which case also use :param syntax
314            $realDbType = self::SQLITE;
315        }
316
317        if (($detectedDbType !== null) && ($realDbType != $detectedDbType)) {
318            switch ($realDbType) {
319                case self::MYSQL:
320                case self::SQLSRV:
321                    $parameter = '?';
322                    break;
323                case self::PGSQL:
324                    $parameter = '$' . $this->parameterCount;
325                    break;
326                case self::SQLITE:
327                    if ($column !== null) {
328                        $parameter = ':' . $column;
329                    }
330                    break;
331            }
332        }
333
334        return $parameter;
335    }
336
337    /**
338     * Quote the identifier
339     *
340     * @param  string $identifier
341     * @return string
342     */
343    public function quoteId(string $identifier): string
344    {
345        $quotedId = null;
346
347        if (str_contains($identifier, '.')) {
348            $identifierAry = explode('.', $identifier);
349            foreach ($identifierAry as $key => $val) {
350                $identifierAry[$key] = ($val != '*') ? $this->openQuote . $val . $this->closeQuote : $val;
351            }
352            $quotedId = implode('.', $identifierAry);
353        } else if (($identifier != '*') &&
354            ((preg_match('/^\$\d*\d$/', $identifier) == 0) && !is_int($identifier) &&
355                !is_float($identifier) && (preg_match('/^\d*$/', $identifier) == 0))) {
356            $quotedId = $this->openQuote . $identifier . $this->closeQuote;
357        } else {
358            $quotedId = $identifier;
359        }
360
361        return $quotedId;
362    }
363
364    /**
365     * Quote the value (if it is not a numeric value)
366     *
367     * @param  ?string $value
368     * @param  bool    $force
369     * @return float|int|string
370     */
371    public function quote(?string $value = null, bool $force = false): float|int|string
372    {
373        if ($force) {
374            if (($value == '') ||
375                ((preg_match('/^\$\d*\d$/', $value) == 0) &&
376                    !is_int($value) && !is_float($value) && (preg_match('/^\d*$/', $value) == 0))) {
377                $value = "'" . $this->db->escape($value) . "'";
378            }
379        } else {
380            if (($value == '') ||
381                (($value != '?') && (!str_starts_with($value, ':')) && (preg_match('/^\$\d*\d$/', $value) == 0) &&
382                    !is_int($value) && !is_float($value) && (preg_match('/^\d*$/', $value) == 0))) {
383                $value = "'" . $this->db->escape($value) . "'";
384            }
385        }
386
387        return $value;
388    }
389
390    /**
391     * Initialize SQL object
392     *
393     * @param  string $adapter
394     * @return void
395     */
396    protected function init(string $adapter): void
397    {
398        if (stripos($adapter, 'pdo') !== false) {
399            $adapter           = $this->db->getType();
400            $this->placeholder = ':';
401        }
402
403        if (stripos($adapter, 'mysql') !== false) {
404            $this->dbType      = self::MYSQL;
405            $this->idQuoteType = self::BACKTICK;
406            if ($this->placeholder === null) {
407                $this->placeholder = '?';
408            }
409        } else if (stripos($adapter, 'pgsql') !== false) {
410            $this->dbType      = self::PGSQL;
411            $this->idQuoteType = self::DOUBLE_QUOTE;
412            if ($this->placeholder === null) {
413                $this->placeholder = '$';
414            }
415        } else if (stripos($adapter, 'sqlite') !== false) {
416            $this->dbType      = self::SQLITE;
417            $this->idQuoteType = self::DOUBLE_QUOTE;
418            if ($this->placeholder === null) {
419                $this->placeholder = ':';
420            }
421        } else if (stripos($adapter, 'sqlsrv') !== false) {
422            $this->dbType      = self::SQLSRV;
423            $this->idQuoteType = self::BRACKET;
424            if ($this->placeholder === null) {
425                $this->placeholder = '?';
426            }
427        }
428
429        $this->initQuoteType();
430    }
431
432    /**
433     * Initialize quite type
434     *
435     * @return void
436     */
437    protected function initQuoteType(): void
438    {
439        switch ($this->idQuoteType) {
440            case (self::BACKTICK):
441                $this->openQuote   = '`';
442                $this->closeQuote  = '`';
443                break;
444            case (self::DOUBLE_QUOTE):
445                $this->openQuote   = '"';
446                $this->closeQuote  = '"';
447                break;
448            case (self::BRACKET):
449                $this->openQuote   = '[';
450                $this->closeQuote  = ']';
451                break;
452            case (self::NO_QUOTE):
453                $this->openQuote   = null;
454                $this->closeQuote  = null;
455                break;
456        }
457    }
458
459}