1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | namespace Yiisoft\Db\Sqlite; |
||
6 | |||
7 | use PDOException; |
||
8 | use Throwable; |
||
9 | use Yiisoft\Db\Driver\Pdo\AbstractPdoCommand; |
||
0 ignored issues
–
show
|
|||
10 | use Yiisoft\Db\Driver\Pdo\PdoConnectionInterface; |
||
0 ignored issues
–
show
The type
Yiisoft\Db\Driver\Pdo\PdoConnectionInterface was not found. Maybe you did not declare it correctly or list all dependencies?
The issue could also be caused by a filter entry in the build configuration.
If the path has been excluded in your configuration, e.g. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths
Loading history...
|
|||
11 | use Yiisoft\Db\Exception\ConvertException; |
||
12 | use Yiisoft\Db\Exception\Exception; |
||
13 | use Yiisoft\Db\Exception\InvalidArgumentException; |
||
14 | use Yiisoft\Db\QueryBuilder\QueryBuilderInterface; |
||
15 | |||
16 | use function array_pop; |
||
17 | use function count; |
||
18 | use function ltrim; |
||
19 | use function preg_match_all; |
||
20 | use function strpos; |
||
21 | |||
22 | /** |
||
23 | * Implements a database command that can be executed with a PDO (PHP Data Object) database connection for SQLite |
||
24 | * Server. |
||
25 | */ |
||
26 | final class Command extends AbstractPdoCommand |
||
27 | { |
||
28 | 4 | public function insertWithReturningPks(string $table, array $columns): bool|array |
|
29 | { |
||
30 | 4 | $params = []; |
|
31 | 4 | $sql = $this->db->getQueryBuilder()->insert($table, $columns, $params); |
|
32 | 4 | $this->setSql($sql)->bindValues($params); |
|
33 | |||
34 | 4 | if (!$this->execute()) { |
|
35 | return false; |
||
36 | } |
||
37 | |||
38 | 4 | $tableSchema = $this->db->getSchema()->getTableSchema($table); |
|
39 | 4 | $tablePrimaryKeys = $tableSchema?->getPrimaryKey() ?? []; |
|
40 | |||
41 | 4 | $result = []; |
|
42 | 4 | foreach ($tablePrimaryKeys as $name) { |
|
43 | 4 | if ($tableSchema?->getColumn($name)?->isAutoIncrement()) { |
|
44 | 3 | $result[$name] = $this->db->getLastInsertID((string) $tableSchema?->getSequenceName()); |
|
45 | 3 | continue; |
|
46 | } |
||
47 | |||
48 | /** @psalm-var mixed */ |
||
49 | 1 | $result[$name] = $columns[$name] ?? $tableSchema?->getColumn($name)?->getDefaultValue(); |
|
50 | } |
||
51 | |||
52 | 4 | return $result; |
|
53 | } |
||
54 | |||
55 | 1 | public function showDatabases(): array |
|
56 | { |
||
57 | 1 | $sql = <<<SQL |
|
58 | SELECT name FROM pragma_database_list; |
||
59 | 1 | SQL; |
|
60 | |||
61 | 1 | return $this->setSql($sql)->queryColumn(); |
|
62 | } |
||
63 | |||
64 | 281 | protected function getQueryBuilder(): QueryBuilderInterface |
|
65 | { |
||
66 | 281 | return $this->db->getQueryBuilder(); |
|
67 | } |
||
68 | |||
69 | /** |
||
70 | * Executes the SQL statement. |
||
71 | * |
||
72 | * This method should only be used for executing a non-query SQL statement, such as `INSERT`, `DELETE`, `UPDATE` SQLs. |
||
73 | * No result set will be returned. |
||
74 | * |
||
75 | * @throws Exception |
||
76 | * @throws Throwable The execution failed. |
||
77 | * |
||
78 | * @return int Number of rows affected by the execution. |
||
79 | */ |
||
80 | 109 | public function execute(): int |
|
81 | { |
||
82 | 109 | $sql = $this->getSql(); |
|
83 | |||
84 | /** @psalm-var array<string, string> $params */ |
||
85 | 109 | $params = $this->params; |
|
86 | |||
87 | 109 | $statements = $this->splitStatements($sql, $params); |
|
88 | |||
89 | 109 | if ($statements === false) { |
|
90 | 97 | return parent::execute(); |
|
91 | } |
||
92 | |||
93 | 12 | $result = 0; |
|
94 | |||
95 | /** @psalm-var array<array-key, array<array-key, string|array>> $statements */ |
||
96 | 12 | foreach ($statements as $statement) { |
|
97 | 12 | [$statementSql, $statementParams] = $statement; |
|
98 | 12 | $statementSql = is_string($statementSql) ? $statementSql : ''; |
|
99 | 12 | $statementParams = is_array($statementParams) ? $statementParams : []; |
|
100 | 12 | $this->setSql($statementSql)->bindValues($statementParams); |
|
101 | 12 | $result = parent::execute(); |
|
102 | } |
||
103 | |||
104 | 12 | $this->setSql($sql)->bindValues($params); |
|
105 | |||
106 | 12 | return $result; |
|
107 | } |
||
108 | |||
109 | /** |
||
110 | * @psalm-suppress UnusedClosureParam |
||
111 | * |
||
112 | * @throws Throwable |
||
113 | */ |
||
114 | 248 | protected function internalExecute(string|null $rawSql): void |
|
115 | { |
||
116 | 248 | $attempt = 0; |
|
117 | |||
118 | 248 | while (true) { |
|
119 | try { |
||
120 | if ( |
||
121 | 248 | ++$attempt === 1 |
|
122 | 248 | && $this->isolationLevel !== null |
|
123 | 248 | && $this->db->getTransaction() === null |
|
124 | ) { |
||
125 | 1 | $this->db->transaction( |
|
126 | 1 | fn (PdoConnectionInterface $db) => $this->internalExecute($rawSql), |
|
127 | 1 | $this->isolationLevel, |
|
128 | 1 | ); |
|
129 | } else { |
||
130 | 248 | $this->pdoStatement?->execute(); |
|
131 | } |
||
132 | 248 | break; |
|
133 | 2 | } catch (PDOException $e) { |
|
134 | 2 | $rawSql = $rawSql ?: $this->getRawSql(); |
|
135 | 2 | $e = (new ConvertException($e, $rawSql))->run(); |
|
136 | |||
137 | 2 | if ($this->retryHandler === null || !($this->retryHandler)($e, $attempt)) { |
|
138 | 2 | throw $e; |
|
139 | } |
||
140 | } |
||
141 | } |
||
142 | } |
||
143 | |||
144 | /** |
||
145 | * Performs the actual DB query of an SQL statement. |
||
146 | * |
||
147 | * @param int $queryMode Return results as DataReader |
||
148 | * |
||
149 | * @throws Exception |
||
150 | * @throws Throwable If the query causes any problem. |
||
151 | * |
||
152 | * @return mixed The method execution result. |
||
153 | */ |
||
154 | 248 | protected function queryInternal(int $queryMode): mixed |
|
155 | { |
||
156 | 248 | $sql = $this->getSql(); |
|
157 | |||
158 | /** @psalm-var array<string, string> $params */ |
||
159 | 248 | $params = $this->params; |
|
160 | |||
161 | 248 | $statements = $this->splitStatements($sql, $params); |
|
162 | |||
163 | 248 | if ($statements === false || $statements === []) { |
|
164 | 248 | return parent::queryInternal($queryMode); |
|
165 | } |
||
166 | |||
167 | 1 | [$lastStatementSql, $lastStatementParams] = array_pop($statements); |
|
168 | |||
169 | /** |
||
170 | * @psalm-var array<array-key, array> $statements |
||
171 | */ |
||
172 | 1 | foreach ($statements as $statement) { |
|
173 | /** |
||
174 | * @psalm-var string $statementSql |
||
175 | * @psalm-var array $statementParams |
||
176 | */ |
||
177 | 1 | [$statementSql, $statementParams] = $statement; |
|
178 | 1 | $this->setSql($statementSql)->bindValues($statementParams); |
|
179 | 1 | parent::execute(); |
|
180 | } |
||
181 | |||
182 | 1 | $this->setSql($lastStatementSql)->bindValues($lastStatementParams); |
|
183 | |||
184 | /** @psalm-var string $result */ |
||
185 | 1 | $result = parent::queryInternal($queryMode); |
|
186 | |||
187 | 1 | $this->setSql($sql)->bindValues($params); |
|
188 | |||
189 | 1 | return $result; |
|
190 | } |
||
191 | |||
192 | /** |
||
193 | * Splits the specified SQL into individual SQL statements and returns them or `false` if there's a single |
||
194 | * statement. |
||
195 | * |
||
196 | * @param string $sql SQL to split. |
||
197 | * |
||
198 | * @throws InvalidArgumentException |
||
199 | * |
||
200 | * @return array|bool List of SQL statements or `false` if there's a single statement. |
||
201 | * |
||
202 | * @psalm-param array<string, string> $params |
||
203 | * |
||
204 | * @psalm-return false|list<array{0: string, 1: array}> |
||
205 | */ |
||
206 | 249 | private function splitStatements(string $sql, array $params): bool|array |
|
207 | { |
||
208 | 249 | $semicolonIndex = strpos($sql, ';'); |
|
209 | |||
210 | 249 | if ($semicolonIndex === false || $semicolonIndex === mb_strlen($sql, '8bit') - 1) { |
|
211 | 249 | return false; |
|
212 | } |
||
213 | |||
214 | 13 | $tokenizer = new SqlTokenizer($sql); |
|
215 | |||
216 | 13 | $codeToken = $tokenizer->tokenize(); |
|
217 | |||
218 | 13 | if (count($codeToken->getChildren()) === 1) { |
|
219 | 1 | return false; |
|
220 | } |
||
221 | |||
222 | 12 | $statements = []; |
|
223 | |||
224 | 12 | foreach ($codeToken->getChildren() as $statement) { |
|
225 | 12 | $statements[] = [$statement->getSql(), $this->extractUsedParams($statement, $params)]; |
|
226 | } |
||
227 | |||
228 | 12 | return $statements; |
|
229 | } |
||
230 | |||
231 | /** |
||
232 | * Returns named bindings used in the specified statement token. |
||
233 | * |
||
234 | * @psalm-param array<string, string> $params |
||
235 | */ |
||
236 | 12 | private function extractUsedParams(SqlToken $statement, array $params): array |
|
237 | { |
||
238 | 12 | preg_match_all('/(?P<placeholder>:\w+)/', $statement->getSql(), $matches, PREG_SET_ORDER); |
|
239 | |||
240 | 12 | $result = []; |
|
241 | |||
242 | 12 | foreach ($matches as $match) { |
|
243 | 12 | $phName = ltrim($match['placeholder'], ':'); |
|
244 | 12 | if (isset($params[$phName])) { |
|
245 | 1 | $result[$phName] = $params[$phName]; |
|
246 | 11 | } elseif (isset($params[':' . $phName])) { |
|
247 | 11 | $result[':' . $phName] = $params[':' . $phName]; |
|
248 | } |
||
249 | } |
||
250 | |||
251 | 12 | return $result; |
|
252 | } |
||
253 | } |
||
254 |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"]
, you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths