Issues (18)

src/Command.php (2 issues)

Severity
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Sqlite;
6
7
use Throwable;
8
use Yiisoft\Db\Driver\Pdo\AbstractPdoCommand;
9
use Yiisoft\Db\Exception\Exception;
10
use Yiisoft\Db\Exception\InvalidArgumentException;
11
12
use function array_pop;
13
use function count;
14
use function ltrim;
15
use function preg_match_all;
16
use function strpos;
17
18
/**
19
 * Implements a database command that can be executed with a PDO (PHP Data Object) database connection for SQLite
20
 * Server.
21
 */
22
final class Command extends AbstractPdoCommand
23
{
24 6
    public function insertWithReturningPks(string $table, array $columns): bool|array
25
    {
26 6
        $params = [];
27 6
        $sql = $this->db->getQueryBuilder()->insert($table, $columns, $params);
28 6
        $this->setSql($sql)->bindValues($params);
29
30 6
        if (!$this->execute()) {
31
            return false;
32
        }
33
34 6
        $tableSchema = $this->db->getSchema()->getTableSchema($table);
35 6
        $tablePrimaryKeys = $tableSchema?->getPrimaryKey() ?? [];
36
37 6
        $result = [];
38 6
        foreach ($tablePrimaryKeys as $name) {
39 5
            if ($tableSchema?->getColumn($name)?->isAutoIncrement()) {
40 4
                $result[$name] = $this->db->getLastInsertID((string) $tableSchema?->getSequenceName());
41 4
                continue;
42
            }
43
44 1
            $result[$name] = $columns[$name] ?? $tableSchema?->getColumn($name)?->getDefaultValue();
45
        }
46
47 6
        return $result;
48
    }
49
50 1
    public function showDatabases(): array
51
    {
52 1
        $sql = <<<SQL
53
        SELECT name FROM pragma_database_list;
54 1
        SQL;
55
56 1
        return $this->setSql($sql)->queryColumn();
57
    }
58
59
    /**
60
     * Executes the SQL statement.
61
     *
62
     * This method should only be used for executing a non-query SQL statement, such as `INSERT`, `DELETE`, `UPDATE` SQLs.
63
     * No result set will be returned.
64
     *
65
     * @throws Exception
66
     * @throws Throwable The execution failed.
67
     *
68
     * @return int Number of rows affected by the execution.
69
     */
70 123
    public function execute(): int
71
    {
72 123
        $sql = $this->getSql();
73
74 123
        $params = $this->params;
75
76 123
        $statements = $this->splitStatements($sql, $params);
77
78 123
        if ($statements === false) {
0 ignored issues
show
The condition $statements === false is always true.
Loading history...
79 110
            return parent::execute();
80
        }
81
82 13
        $result = 0;
83
84 13
        foreach ($statements as $statement) {
85 13
            [$statementSql, $statementParams] = $statement;
86 13
            $this->setSql($statementSql)->bindValues($statementParams);
87 13
            $result = parent::execute();
88
        }
89
90 13
        $this->setSql($sql)->bindValues($params);
91
92 13
        return $result;
93
    }
94
95
    /**
96
     * Performs the actual DB query of an SQL statement.
97
     *
98
     * @param int $queryMode Return results as DataReader
99
     *
100
     * @throws Exception
101
     * @throws Throwable If the query causes any problem.
102
     *
103
     * @return mixed The method execution result.
104
     */
105 268
    protected function queryInternal(int $queryMode): mixed
106
    {
107 268
        $sql = $this->getSql();
108
109 268
        $params = $this->params;
110
111 268
        $statements = $this->splitStatements($sql, $params);
112
113 268
        if ($statements === false || $statements === []) {
0 ignored issues
show
The condition $statements === false is always true.
Loading history...
114 268
            return parent::queryInternal($queryMode);
115
        }
116
117 1
        [$lastStatementSql, $lastStatementParams] = array_pop($statements);
118
119 1
        foreach ($statements as $statement) {
120 1
            [$statementSql, $statementParams] = $statement;
121 1
            $this->setSql($statementSql)->bindValues($statementParams);
122 1
            parent::execute();
123
        }
124
125 1
        $this->setSql($lastStatementSql)->bindValues($lastStatementParams);
126
127 1
        $result = parent::queryInternal($queryMode);
128
129 1
        $this->setSql($sql)->bindValues($params);
130
131 1
        return $result;
132
    }
133
134
    /**
135
     * Splits the specified SQL into individual SQL statements and returns them or `false` if there's a single
136
     * statement.
137
     *
138
     * @param string $sql SQL to split.
139
     *
140
     * @throws InvalidArgumentException
141
     *
142
     * @return array|bool List of SQL statements or `false` if there's a single statement.
143
     *
144
     * @psalm-return false|list<array{0: string, 1: array}>
145
     */
146 269
    private function splitStatements(string $sql, array $params): bool|array
147
    {
148 269
        $semicolonIndex = strpos($sql, ';');
149
150 269
        if ($semicolonIndex === false || $semicolonIndex === mb_strlen($sql, '8bit') - 1) {
151 269
            return false;
152
        }
153
154 14
        $tokenizer = new SqlTokenizer($sql);
155
156 14
        $codeToken = $tokenizer->tokenize();
157
158 14
        if (count($codeToken->getChildren()) === 1) {
159 1
            return false;
160
        }
161
162 13
        $statements = [];
163
164 13
        foreach ($codeToken->getChildren() as $statement) {
165 13
            $statements[] = [$statement->getSql(), $this->extractUsedParams($statement, $params)];
166
        }
167
168 13
        return $statements;
169
    }
170
171
    /**
172
     * Returns named bindings used in the specified statement token.
173
     */
174 13
    private function extractUsedParams(SqlToken $statement, array $params): array
175
    {
176 13
        preg_match_all('/(?P<placeholder>:\w+)/', $statement->getSql(), $matches, PREG_SET_ORDER);
177
178 13
        $result = [];
179
180 13
        foreach ($matches as $match) {
181 13
            $phName = ltrim($match['placeholder'], ':');
182 13
            if (isset($params[$phName])) {
183 1
                $result[$phName] = $params[$phName];
184 12
            } elseif (isset($params[':' . $phName])) {
185 12
                $result[':' . $phName] = $params[':' . $phName];
186
            }
187
        }
188
189 13
        return $result;
190
    }
191
}
192