Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
74.89% covered (success)
74.89%
164 / 219
60.53% covered (warning)
60.53%
23 / 38
CRAP
0.00% covered (danger)
0.00%
0 / 1
Pdo
74.89% covered (success)
74.89%
164 / 219
60.53% covered (warning)
60.53%
23 / 38
306.16
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
2
 connect
76.92% covered (success)
76.92%
10 / 13
0.00% covered (danger)
0.00%
0 / 1
10.00
 setOptions
92.86% covered (success)
92.86%
13 / 14
0.00% covered (danger)
0.00%
0 / 1
6.01
 hasOptions
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
6
 dbFileExists
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 getDsn
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 beginTransaction
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 commit
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 rollback
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 inTransaction
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isSuccess
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
3
 setAttribute
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAttribute
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 query
66.67% covered (warning)
66.67%
14 / 21
0.00% covered (danger)
0.00%
0 / 1
8.81
 prepare
92.31% covered (success)
92.31%
12 / 13
0.00% covered (danger)
0.00%
0 / 1
6.02
 bindParams
68.18% covered (warning)
68.18%
15 / 22
0.00% covered (danger)
0.00%
0 / 1
13.22
 bindParam
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 bindValue
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 bindColumn
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 execute
46.67% covered (warning)
46.67%
7 / 15
0.00% covered (danger)
0.00%
0 / 1
11.46
 fetch
80.00% covered (success)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
4.13
 fetchAll
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 escape
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getLastId
75.00% covered (success)
75.00%
9 / 12
0.00% covered (danger)
0.00%
0 / 1
5.39
 getNumberOfRows
85.71% covered (success)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
3.03
 getNumberOfAffectedRows
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getVersion
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getTables
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
7.01
 getErrorMessage
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 buildError
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 getNumberOfFields
71.43% covered (success)
71.43%
5 / 7
0.00% covered (danger)
0.00%
0 / 1
3.21
 closeCursor
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCountOfFields
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 fetchColumn
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCountOfRows
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 debugDumpParams
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 exec
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2declare(strict_types=1);
3/**
4 * Pop PHP Framework (https://www.popphp.org/)
5 *
6 * @link       https://github.com/popphp/popphp-framework
7 * @author     Nick Sagona, III <nick@popphp.org>
8 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
9 * @license    https://www.popphp.org/license     New BSD License
10 */
11
12/**
13 * @namespace
14 */
15namespace Pop\Db\Adapter;
16
17/**
18 * PDO database adapter class
19 *
20 * @category   Pop
21 * @package    Pop\Db
22 * @author     Nick Sagona, III <nick@popphp.org>
23 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
24 * @license    https://www.popphp.org/license     New BSD License
25 * @version    7.0.0
26 */
27class Pdo extends AbstractAdapter
28{
29
30    /**
31     * PDO DSN
32     * @var ?string
33     */
34    protected ?string $dsn = null;
35
36    /**
37     * PDO type
38     * @var ?string
39     */
40    protected ?string $type = null;
41
42    /**
43     * Statement placeholder
44     * @var ?string
45     */
46    protected ?string $placeholder = null;
47
48    /**
49     * Statement result
50     * @var bool
51     */
52    protected bool $statementResult = false;
53
54    /**
55     * Constructor
56     *
57     * Instantiate the database connection object using PDO
58     *
59     * @param  array $options
60     */
61    public function __construct(array $options = [])
62    {
63        if (!empty($options)) {
64            $this->connect($options);
65        }
66    }
67
68    /**
69     * Connect to the database
70     *
71     * @param  array $options
72     * @return Pdo
73     */
74    public function connect(array $options = []): Pdo
75    {
76        if (!empty($options)) {
77            $this->setOptions($options);
78        } else if (!$this->hasOptions()) {
79            $this->throwError('Error: The proper database credentials were not passed.');
80        }
81
82        try {
83            if ($this->type == 'sqlite') {
84                $this->connection = (isset($this->options['options']) && is_array($this->options['options'])) ?
85                    new \PDO($this->dsn, null, null, $this->options['options']) : new \PDO($this->dsn);
86            } else {
87                $this->connection = (isset($this->options['options']) && is_array($this->options['options'])) ?
88                    new \PDO($this->dsn, $this->options['username'], $this->options['password'], $this->options['options']) :
89                    new \PDO($this->dsn, $this->options['username'], $this->options['password']);
90            }
91        } catch (\PDOException $e) {
92            $this->throwError('PDO Connection Error: ' . $e->getMessage() . ' (#' . $e->getCode() . ')');
93        }
94
95        return $this;
96    }
97
98    /**
99     * Set database connection options
100     *
101     * @param  array $options
102     * @return Pdo
103     */
104    public function setOptions(array $options): Pdo
105    {
106        if (!isset($options['host'])) {
107            $options['host'] = 'localhost';
108        }
109
110        $this->options = $options;
111
112        if (!$this->hasOptions()) {
113            $this->throwError('Error: The proper database credentials were not passed.');
114        }
115
116        $this->type = strtolower($this->options['type']);
117
118        if ($this->type == 'sqlite') {
119            if (!$this->dbFileExists()) {
120                $this->throwError("Error: The database file '" . $this->options['database'] . "' does not exist.");
121            }
122            $this->dsn = $this->type . ':' . $this->options['database'];
123        } else {
124            $this->dsn = ($this->type == 'sqlsrv') ?
125                $this->type . ':Server=' . $this->options['host'] . ';Database=' . $this->options['database'] :
126                $this->type . ':host=' . $this->options['host'] . ';dbname=' . $this->options['database'];
127        }
128
129        return $this;
130    }
131
132    /**
133     * Has database connection options
134     *
135     * @return bool
136     */
137    public function hasOptions(): bool
138    {
139        if (!isset($this->options['type'])) {
140            return false;
141        } else {
142            return (strtolower($this->options['type']) == 'sqlite') ?
143                (isset($this->options['database'])) :
144                (isset($this->options['database']) && isset($this->options['host']) &&
145                    isset($this->options['username']) && isset($this->options['password']));
146        }
147    }
148
149    /**
150     * Does the database file exist
151     *
152     * @return bool
153     */
154    public function dbFileExists(): bool
155    {
156        return (isset($this->options['database']) && file_exists($this->options['database']));
157    }
158
159    /**
160     * Return the DSN
161     *
162     * @return ?string
163     */
164    public function getDsn(): ?string
165    {
166        return $this->dsn;
167    }
168
169    /**
170     * Return the type
171     *
172     * @return ?string
173     */
174    public function getType(): ?string
175    {
176        return $this->type;
177    }
178
179    /**
180     * Begin a transaction
181     *
182     * @return Pdo
183     */
184    public function beginTransaction(): Pdo
185    {
186        $this->getTransactionManager()->enter(
187            beginFunc: function () { $this->connection->beginTransaction(); },
188            savepointFunc: function (string $sp) { $this->query('SAVEPOINT ' . $sp); },
189        );
190
191        return $this;
192    }
193
194    /**
195     * Commit a transaction
196     *
197     * @return Pdo
198     */
199    public function commit(): Pdo
200    {
201        $this->getTransactionManager()->leave(true,
202            commitFunc: function () { $this->connection->commit(); },
203            rollbackFunc: function () { $this->connection->rollBack(); },
204            savepointReleaseFunc: function (string $sp) { $this->query('RELEASE SAVEPOINT ' . $sp); },
205        );
206        return $this;
207    }
208
209    /**
210     * Rollback a transaction
211     *
212     * @return Pdo
213     */
214    public function rollback(): Pdo
215    {
216        $this->getTransactionManager()->leave(false,
217            rollbackFunc: function () { $this->connection->rollBack(); },
218            savepointRollbackFunc: function (string $sp) { $this->query('ROLLBACK TO SAVEPOINT ' . $sp); },
219        );
220
221        return $this;
222    }
223
224    /**
225     * Method checks, whether the transaction is initiated.
226     *
227     * @return bool
228     */
229    public function inTransaction(): bool
230    {
231        return $this->connection->inTransaction();
232    }
233
234    /**
235     * Check if transaction is success
236     *
237     * @return bool
238     */
239    public function isSuccess(): bool
240    {
241        return ((($this->result) || ($this->statementResult)) && (!$this->hasError()));
242    }
243
244    /**
245     * Method sets the value of the request attribute PDO.
246     *
247     * @param  int    $attribute A request attribute
248     * @param  mixed  $value     The value of the attribute request
249     * @return bool
250     */
251    public function setAttribute(int $attribute, mixed $value): bool
252    {
253        return $this->connection->setAttribute($attribute, $value);
254    }
255
256    /**
257     * The method of obtaining the value of the request attribute PDO.
258     *
259     * @param  int $attribute A request attribute
260     * @return mixed
261     */
262    public function getAttribute(int $attribute): mixed
263    {
264        return $this->connection->getAttribute($attribute);
265    }
266
267    /**
268     * Execute a SQL query directly
269     *
270     * @param  mixed $sql
271     * @return Pdo
272     */
273    public function query(mixed $sql): Pdo
274    {
275        if ($sql instanceof \Pop\Db\Sql\AbstractSql) {
276            $sql = (string)$sql;
277        }
278
279        $this->statement       = null;
280        $this->statementResult = false;
281
282        $sth = $this->connection->prepare($sql);
283
284        if (!($sth->execute())) {
285            if ($this->profiler !== null) {
286                $this->profiler->addStep();
287                $this->profiler->current->setQuery($sql);
288                $this->profiler->current->addError($this->getErrorMessage($sth->errorInfo()), $sth->errorCode());
289            }
290            $this->buildError($sth->errorCode(), $sth->errorInfo())
291                 ->throwError();
292        } else {
293            if ($this->profiler !== null) {
294                $this->profiler->addStep();
295                $this->profiler->current->setQuery($sql);
296            }
297            $this->result = $sth;
298        }
299
300        if ($this->profiler !== null) {
301            $this->profiler->current->finish();
302            if ($this->profiler->hasDebugger()) {
303                $this->profiler->debugger()->save();
304            }
305        }
306
307        return $this;
308    }
309
310    /**
311     * Prepare a SQL query
312     *
313     * @param  mixed  $sql
314     * @param  ?array $attribs
315     * @return Pdo
316     */
317    public function prepare(mixed $sql, ?array $attribs = null): Pdo
318    {
319        if ($sql instanceof \Pop\Db\Sql\AbstractSql) {
320            $sql = (string)$sql;
321        }
322
323        if (str_contains($sql, '?')) {
324            $this->placeholder = '?';
325        } else if (str_contains($sql, ':')) {
326            $this->placeholder = ':';
327        }
328
329        if ($this->profiler !== null) {
330            $this->profiler->addStep();
331            $this->profiler->current->setQuery($sql);
332        }
333
334        if ($attribs !== null) {
335            $this->statement = $this->connection->prepare($sql, $attribs);
336        } else {
337            $this->statement = $this->connection->prepare($sql);
338        }
339
340        return $this;
341    }
342
343    /**
344     * Bind parameters to a prepared SQL query
345     *
346     * @param  array $params
347     * @return Pdo
348     */
349    public function bindParams(array $params): Pdo
350    {
351        if ($this->profiler !== null) {
352            $this->profiler->current->addParams($params);
353        }
354
355        if ($this->placeholder == '?') {
356            $i = 1;
357            foreach ($params as $dbColumnName => $dbColumnValue) {
358                if (is_array($dbColumnValue)) {
359                    foreach ($dbColumnValue as $k => $dbColumnVal) {
360                        ${$dbColumnName . ($k + 1)} = $dbColumnVal;
361                        $this->statement->bindParam($i, ${$dbColumnName . ($k + 1)});
362                        $i++;
363
364                    }
365                } else {
366                    ${$dbColumnName} = $dbColumnValue;
367                    $this->statement->bindParam($i, ${$dbColumnName});
368                    $i++;
369                }
370            }
371        } else if ($this->placeholder == ':') {
372            foreach ($params as $dbColumnName => $dbColumnValue) {
373                if (is_array($dbColumnValue)) {
374                    foreach ($dbColumnValue as $k => $dbColumnVal) {
375                        ${$dbColumnName} = $dbColumnVal;
376                        $this->statement->bindParam(':' . $dbColumnName . ($k + 1), ${$dbColumnName});
377                    }
378                } else {
379                    ${$dbColumnName} = $dbColumnValue;
380                    $this->statement->bindParam(':' . $dbColumnName, ${$dbColumnName});
381                }
382            }
383        }
384
385        return $this;
386    }
387
388    /**
389     * Bind a parameter for a prepared SQL query
390     *
391     * @param  mixed $param
392     * @param  mixed $value
393     * @param  int   $dataType
394     * @param  ?int  $length
395     * @param  mixed $options
396     * @return Pdo
397     */
398    public function bindParam(mixed $param, mixed &$value, int $dataType = \PDO::PARAM_STR, ?int $length = null, mixed $options = null): Pdo
399    {
400        if ($this->profiler !== null) {
401            $this->profiler->current->addParam($param, $value);
402        }
403        $this->statement->bindParam($param, $value, $dataType, (int)$length, $options);
404        return $this;
405    }
406
407    /**
408     * Bind a value for a prepared SQL query
409     *
410     * @param  mixed $param
411     * @param  mixed $value
412     * @param  int   $dataType
413     * @return Pdo
414     */
415    public function bindValue(mixed $param, mixed $value, int $dataType = \PDO::PARAM_STR): Pdo
416    {
417        if ($this->profiler !== null) {
418            $this->profiler->current->addParam($param, $value);
419        }
420        $this->statement->bindValue($param, $value, $dataType);
421        return $this;
422    }
423
424    /**
425     *  Bind a column to a PHP variable.
426     *
427     * @param  mixed $column    Number of the column (1-indexed) or name of the column in the result set.
428     * @param  mixed $param     Name of the PHP variable to which the column will be bound.
429     * @param  int   $dataType  Data type of the parameter, specified by the PDO::PARAM_* constants.
430     * @return Pdo
431     */
432    public function bindColumn(mixed $column, mixed $param, int $dataType = \PDO::PARAM_STR): Pdo
433    {
434        $this->statement->bindColumn($column, $param, $dataType);
435        return $this;
436    }
437
438    /**
439     * Execute a prepared SQL query
440     *
441     * @return Pdo
442     */
443    public function execute(): Pdo
444    {
445        if ($this->statement === null) {
446            $this->throwError('Error: The database statement resource is not currently set.');
447        }
448
449        $this->statementResult = $this->statement->execute();
450
451        if ($this->statement->errorCode() != 0) {
452            if ($this->profiler !== null) {
453                $this->profiler->current->addError(
454                    $this->getErrorMessage($this->statement->errorInfo()), $this->statement->errorCode()
455                );
456            }
457            $this->buildError($this->statement->errorCode(), $this->statement->errorInfo())
458                 ->throwError();
459        }
460
461        if ($this->profiler !== null) {
462            $this->profiler->current->finish();
463            if ($this->profiler->hasDebugger()) {
464                $this->profiler->debugger()->save();
465            }
466        }
467
468        return $this;
469    }
470
471    /**
472     * Fetch and return a row from the result
473     *
474     * @param  int $dataType  Data type of the parameter, specified by the PDO::PARAM_* constants.
475     * @return mixed
476     */
477    public function fetch(int $dataType = \PDO::FETCH_ASSOC): mixed
478    {
479        if (($this->statement !== null) && ($this->statementResult !== false)) {
480            return $this->statement->fetch($dataType);
481        } else {
482            if ($this->result === null) {
483                $this->throwError('Error: The database statement resource is not currently set.');
484            }
485            return $this->result->fetch($dataType);
486        }
487    }
488
489    /**
490     * Fetch and return all rows from the result
491     *
492     * @param  int $dataType  Data type of the parameter, specified by the PDO::PARAM_* constants.
493     * @return array
494     */
495    public function fetchAll(int $dataType = \PDO::FETCH_ASSOC): array
496    {
497        return $this->statement->fetchAll($dataType);
498    }
499
500    /**
501     * Escape the value
502     *
503     * @param  ?string $value
504     * @return string
505     */
506    public function escape(?string $value = null): string
507    {
508        return substr($this->connection->quote($value), 1, -1);
509    }
510
511    /**
512     * Return the last ID of the last query
513     *
514     * @return int
515     */
516    public function getLastId(): int
517    {
518        $id = 0;
519
520        // If pgsql
521        if ($this->type == 'pgsql') {
522            $this->query("SELECT lastval();");
523            if ($this->result !== null) {
524                $insertRow = $this->result->fetch();
525                $id        = $insertRow[0];
526            }
527        // Else, if sqlsrv
528        } else if ($this->type == 'sqlsrv') {
529            $this->query('SELECT SCOPE_IDENTITY() as Current_Identity');
530            $row = $this->fetch();
531            $id  = (isset($row['Current_Identity'])) ? $row['Current_Identity'] : 0;
532        // Else, just get the last insert ID
533        } else {
534            $id = $this->connection->lastInsertId();
535        }
536
537        return (int)$id;
538    }
539
540    /**
541     * Return the number of rows from the last query
542     *
543     * @throws Exception
544     * @return int
545     */
546    public function getNumberOfRows(): int
547    {
548        $count = 0;
549
550        if ($this->result !== null) {
551            $count = $this->result->rowCount();
552        } else if ($this->statement !== null) {
553            $count = $this->statement->rowCount();
554        } else {
555            $this->throwError('Error: The database statement resource is not currently set.');
556        }
557
558        return $count;
559    }
560
561    /**
562     * Return the number of affected rows from the last query
563     *
564     * @throws Exception
565     * @return int
566     */
567    public function getNumberOfAffectedRows(): int
568    {
569        return $this->getNumberOfRows();
570    }
571
572    /**
573     * Return the database version
574     *
575     * @return string
576     */
577    public function getVersion(): string
578    {
579        return 'PDO ' . substr($this->dsn, 0, strpos($this->dsn, ':')) . ' ' .
580        $this->connection->getAttribute(\PDO::ATTR_SERVER_VERSION);
581    }
582
583    /**
584     * Return the tables in the database
585     *
586     * @return array
587     */
588    public function getTables(): array
589    {
590        $tables = [];
591
592        if (stripos($this->dsn, 'sqlite') !== false) {
593            $sql = "SELECT name FROM sqlite_master WHERE type IN ('table', 'view') AND name NOT LIKE 'sqlite_%' " .
594                "UNION ALL SELECT name FROM sqlite_temp_master WHERE type IN ('table', 'view') ORDER BY 1";
595
596            $this->query($sql);
597            while (($row = $this->fetch())) {
598                $tables[] = $row['name'];
599            }
600        } else {
601            if (stripos($this->dsn, 'pgsql') !== false) {
602                $sql = "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'";
603            } else if (stripos($this->dsn, 'sqlsrv') !== false) {
604                $sql = "SELECT name FROM " . $this->options['database'] . ".sysobjects WHERE xtype = 'U'";
605            } else {
606                $sql = 'SHOW TABLES';
607            }
608            $this->query($sql);
609            while (($row = $this->fetch())) {
610                foreach($row as $value) {
611                    $tables[] = $value;
612                }
613            }
614        }
615
616        return $tables;
617    }
618
619    /**
620     * Get the error message
621     *
622     * @param  mixed $errorInfo
623     * @return ?string
624     */
625    protected function getErrorMessage(mixed $errorInfo): ?string
626    {
627        if (is_array($errorInfo)) {
628            $errorMessage = null;
629            if (isset($errorInfo[1])) {
630                $errorMessage .= $errorInfo[1];
631            }
632            if (isset($errorInfo[2])) {
633                $errorMessage .= ' : ' . $errorInfo[2];
634            }
635        } else {
636            $errorMessage = $errorInfo;
637        }
638
639        return $errorMessage;
640    }
641
642    /**
643     * Build the error
644     *
645     * @param  ?string $code
646     * @param  ?array  $info
647     * @return Pdo
648     */
649    protected function buildError(?string $code = null, ?array $info = null): Pdo
650    {
651        if (($code === null) && ($info === null)) {
652            $errorCode = $this->connection->errorCode();
653            $errorInfo = $this->connection->errorInfo();
654        } else {
655            $errorCode = $code;
656            $errorInfo = $info;
657        }
658
659        $this->setError('Error: ' . $errorCode . ' => ' . $this->getErrorMessage($errorInfo));
660        return $this;
661    }
662
663    /**
664     * Return the number of fields in the result.
665     *
666     * @throws Exception
667     * @return int
668     */
669    public function getNumberOfFields(): int
670    {
671        $count = 0;
672
673        if ($this->result !== null) {
674            $count = $this->result->columnCount();
675        } else if ($this->statement !== null) {
676            $count = $this->statement->columnCount();
677        } else {
678            $this->throwError('Error: The database statement resource is not currently set.');
679        }
680
681        return $count;
682    }
683
684    /**
685     * Method closes the cursor, translating the request in the ready state.
686     *
687     * @return bool
688     */
689    public function closeCursor(): bool
690    {
691        return $this->statement->closeCursor();
692    }
693
694    /**
695     * The method returns the number of columns in the result set.
696     *
697     * @return int
698     */
699    public function getCountOfFields(): int
700    {
701        return $this->statement->columnCount();
702    }
703
704    /**
705     * The method receives data of one column from the next row of the result set.
706     *
707     * @param  ?int $num The number of the table column
708     * @return mixed
709     */
710    public function fetchColumn(?int $num = null): mixed
711    {
712        return $this->statement->fetchColumn($num);
713    }
714
715    /**
716     * The method returns the number of rows modified by the last SQL query.
717     *
718     * @return int
719     */
720    public function getCountOfRows(): int
721    {
722        return $this->statement->rowCount();
723    }
724
725    /**
726     * The method displays information about the prepared SQL command for debugging purposes.
727     *
728     * @param  bool $debug
729     * @return string
730     */
731    public function debugDumpParams(bool $debug = false): bool|string
732    {
733        ob_start();
734        $this->statement->debugDumpParams();
735        $result = ob_get_contents();
736        ob_end_clean();
737        return (!$debug) ?: $result;
738    }
739
740    /**
741     * The method runs an SQL query for execution and returns the number of rows affected during execution.
742     *
743     * @param  mixed $sql The SQL statement to be prepared and run
744     * @return Pdo
745     */
746    public function exec(mixed $sql): Pdo
747    {
748        if (!($this->connection->exec($sql))) {
749            $this->throwError('Error: The database statement resource is not currently set.');
750        }
751
752        return $this;
753    }
754
755}