diff --git a/src/ClickHouseStatement.php b/src/ClickHouseStatement.php index 9587257..9a9471e 100644 --- a/src/ClickHouseStatement.php +++ b/src/ClickHouseStatement.php @@ -271,35 +271,52 @@ public function errorInfo() : void */ public function execute($params = null) : bool { - $hasZeroIndex = false; if (is_array($params)) { $this->values = array_replace($this->values, $params);//TODO array keys must be all strings or all integers? - $hasZeroIndex = array_key_exists(0, $params); } $sql = $this->statement; - if ($hasZeroIndex) { - $statementParts = explode('?', $sql); - array_walk($statementParts, function (&$part, $key) : void { - if (! array_key_exists($key, $this->values)) { - return; - } + $numericKeys = []; + $wordKeys = []; - $part .= $this->getTypedParam($key); - }); - $sql = implode('', $statementParts); - } else { - foreach (array_keys($this->values) as $key) { - $sql = preg_replace( - '/(' . (is_int($key) ? '\?' : ':' . $key) . ')/i', - $this->getTypedParam($key), - $sql, - 1 - ); + foreach (array_keys($this->values) as $key) { + if (is_int($key)) { + $numericKeys[] = $key; + } else { + $wordKeys[] = $key; } } + $wordKeyPatterns = []; + if (count($wordKeys)) { + $wordKeyPatterns = array_map(function (string $key) : string { + return ':' . preg_quote($key, '/') . '\b'; + }, $wordKeys); + } + + $keyPattern = implode('|', array_merge(['\?'], $wordKeyPatterns)); + + $keyIndex = 0; + $sql = preg_replace_callback( + '/(' . $keyPattern . ')/i', + function (array $matches) use ($numericKeys, &$keyIndex) : string { + $key = $matches[0]; + if ($key === '?') { + if (!array_key_exists($keyIndex, $numericKeys)) { + return '?'; // maybe ternary operator, clickhouse supports it + } + $key = $numericKeys[$keyIndex]; + $keyIndex++; + } else { + $key = ltrim($key, ':'); + } + + return $this->getTypedParam($key); + }, + $sql + ); + $this->processViaSMI2($sql); return true; diff --git a/tests/InsertTest.php b/tests/InsertTest.php index 59be336..92a3302 100644 --- a/tests/InsertTest.php +++ b/tests/InsertTest.php @@ -11,6 +11,7 @@ namespace FOD\DBALClickHouse\Tests; +use Doctrine\DBAL\ParameterType; use FOD\DBALClickHouse\Connection; use PHPUnit\Framework\TestCase; @@ -88,10 +89,41 @@ public function testStatementInsertWithoutKeyName() $this->assertEquals([['payload' => 'v?7'], ['payload' => 'v8']], $this->connection->fetchAll("SELECT payload from test_insert_table WHERE id IN (7, 8) ORDER BY id")); } + public function testStatementInsertWithoutKeyNameThroughBind() + { + $statement = $this->connection->prepare('INSERT INTO test_insert_table(id, payload) VALUES (?, ?), (?, ?)'); + $statement->bindValue(0, 7, ParameterType::INTEGER); + $statement->bindValue(1, 'v?7'); + $statement->bindValue(2, 8, ParameterType::INTEGER); + $statement->bindValue(3, 'v8'); + $statement->execute(); + + $this->assertEquals([['payload' => 'v?7'], ['payload' => 'v8']], $this->connection->fetchAll("SELECT payload from test_insert_table WHERE id IN (7, 8) ORDER BY id")); + } + public function testStatementInsertWithKeyName() { $statement = $this->connection->prepare('INSERT INTO test_insert_table(id, payload) VALUES (:v0, :v1), (:v2, :v3)'); $statement->execute(['v0' => 9, 'v1' => 'v?9', 'v2' => 10, 'v3' => 'v10']); $this->assertEquals([['payload' => 'v?9'], ['payload' => 'v10']], $this->connection->fetchAll("SELECT payload from test_insert_table WHERE id IN (9, 10) ORDER BY id")); } + + public function testStatementInsertWithKeyNameThroughBind() + { + $statement = $this->connection->prepare('INSERT INTO test_insert_table(id, payload) VALUES (:v0, :v1), (:v2, :v3)'); + $statement->bindValue('v0', 9, ParameterType::INTEGER); + $statement->bindValue('v1', 'v?9'); + $statement->bindValue('v2', 10, ParameterType::INTEGER); + $statement->bindValue('v3', 'v10'); + $statement->execute(); + + $this->assertEquals([['payload' => 'v?9'], ['payload' => 'v10']], $this->connection->fetchAll("SELECT payload from test_insert_table WHERE id IN (9, 10) ORDER BY id")); + } + + public function testStatementInsertWithKeysHaveSamePrefix() + { + $statement = $this->connection->prepare('INSERT INTO test_insert_table(id, payload) VALUES (:v1, :v10)'); + $statement->execute(['v1' => 9, 'v10' => 'v9']); + $this->assertEquals([['payload' => 'v9']], $this->connection->fetchAll("SELECT payload from test_insert_table WHERE id IN (9) ORDER BY id")); + } }