From 03bfabae016abf8cb7ebf5fbb475ea0d0d74fa6b Mon Sep 17 00:00:00 2001 From: marliotto Date: Fri, 26 Apr 2019 16:07:04 +0300 Subject: [PATCH 1/2] Fix rewriting binded values --- src/ClickHouseStatement.php | 55 ++++++++++++++++++++++++------------- tests/InsertTest.php | 25 +++++++++++++++++ 2 files changed, 61 insertions(+), 19 deletions(-) diff --git a/src/ClickHouseStatement.php b/src/ClickHouseStatement.php index 9587257..bd71d24 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, '/'); + }, $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..3a939a8 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,34 @@ 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")); + } } From 9d82b239ece020aec5ddeb770e80c53b2d2f3ead Mon Sep 17 00:00:00 2001 From: marliotto Date: Fri, 26 Apr 2019 16:15:29 +0300 Subject: [PATCH 2/2] Fix binding keys with the same prefix --- src/ClickHouseStatement.php | 2 +- tests/InsertTest.php | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ClickHouseStatement.php b/src/ClickHouseStatement.php index bd71d24..9a9471e 100644 --- a/src/ClickHouseStatement.php +++ b/src/ClickHouseStatement.php @@ -291,7 +291,7 @@ public function execute($params = null) : bool $wordKeyPatterns = []; if (count($wordKeys)) { $wordKeyPatterns = array_map(function (string $key) : string { - return ':' . preg_quote($key, '/'); + return ':' . preg_quote($key, '/') . '\b'; }, $wordKeys); } diff --git a/tests/InsertTest.php b/tests/InsertTest.php index 3a939a8..92a3302 100644 --- a/tests/InsertTest.php +++ b/tests/InsertTest.php @@ -119,4 +119,11 @@ public function testStatementInsertWithKeyNameThroughBind() $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")); + } }