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
introduced
by
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
|
|||
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 |