From 8bd9ae90183948e7730b858f32848579c2fbedc7 Mon Sep 17 00:00:00 2001 From: Peter Placzek Date: Mon, 25 Mar 2019 15:54:13 +0100 Subject: [PATCH 1/9] added ignore flag --- system/Database/BaseBuilder.php | 32 +++++++++++++++++++++++++ system/Database/MySQLi/Builder.php | 36 +++++++++++++++++++++++++++++ system/Database/SQLite3/Builder.php | 36 ++++++++++++++++++++++++++++- 3 files changed, 103 insertions(+), 1 deletion(-) diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php index f1a0dc286089..302aa05e44c5 100644 --- a/system/Database/BaseBuilder.php +++ b/system/Database/BaseBuilder.php @@ -208,6 +208,14 @@ class BaseBuilder */ protected $canLimitWhereUpdates = true; + /** + * Ignore errors on insert statements + * for example for duplicate keys. + * + * @var bool + */ + protected $insertIgnore = false; + //-------------------------------------------------------------------- /** @@ -254,6 +262,22 @@ public function getBinds(): array return $this->binds; } + //-------------------------------------------------------------------- + + /** + * Ignore + * + * Set ignore Flag for next insert query. + * + * @return BaseBuilder + */ + public function ignore() + { + $this->insertIgnore = true; + + return $this; + } + //-------------------------------------------------------------------- /** @@ -1666,6 +1690,8 @@ public function insertBatch($set = null, $escape = null, $batchSize = 100, $test } } + $this->insertIgnore = false; + if (! $testing) { $this->resetWrite(); @@ -1758,6 +1784,8 @@ public function setInsertBatch($key, $value = '', $escape = null) * * @param boolean $reset TRUE: reset QB values; FALSE: leave QB values alone * + * @throws DatabaseException + * * @return string */ public function getCompiledInsert($reset = true) @@ -1791,6 +1819,8 @@ public function getCompiledInsert($reset = true) * @param array $set An associative array of insert values * @param boolean $escape Whether to escape values and identifiers * @param boolean $test Used when running tests + * + * @throws DatabaseException * * @return BaseResult|Query|false */ @@ -1812,6 +1842,8 @@ public function insert($set = null, $escape = null, $test = false) ), array_keys($this->QBSet), array_values($this->QBSet) ); + $this->insertIgnore = false; + if ($test === false) { $this->resetWrite(); diff --git a/system/Database/MySQLi/Builder.php b/system/Database/MySQLi/Builder.php index 07cd6c4cea94..c49b4364c4c0 100644 --- a/system/Database/MySQLi/Builder.php +++ b/system/Database/MySQLi/Builder.php @@ -51,4 +51,40 @@ class Builder extends BaseBuilder */ protected $escapeChar = '`'; + //-------------------------------------------------------------------- + + /** + * Insert batch statement + * + * Generates a platform-specific insert string from the supplied data. + * + * @param string $table Table name + * @param array $keys INSERT keys + * @param array $values INSERT values + * + * @return string + */ + protected function _insertBatch($table, $keys, $values) + { + return 'INSERT ' . ($this->insertIgnore ? 'IGNORE' : '') . ' INTO ' . $table . ' (' . implode(', ', $keys) . ') VALUES ' . implode(', ', $values); + } + + //-------------------------------------------------------------------- + + /** + * Insert statement + * + * Generates a platform-specific insert string from the supplied data + * + * @param string $table The table name + * @param array $keys The insert keys + * @param array $unescapedKeys The insert values + * + * @return string + */ + protected function _insert($table, array $keys, array $unescapedKeys) + { + return 'INSERT ' . ($this->insertIgnore ? 'IGNORE' : '') . ' INTO ' . $table . ' (' . implode(', ', $keys) . ') VALUES (' . implode(', ', $unescapedKeys) . ')'; + } + } diff --git a/system/Database/SQLite3/Builder.php b/system/Database/SQLite3/Builder.php index 757adbeef49b..2a4c1d9ac831 100644 --- a/system/Database/SQLite3/Builder.php +++ b/system/Database/SQLite3/Builder.php @@ -103,6 +103,40 @@ protected function _truncate($table) return 'DELETE FROM ' . $table; } - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- + + /** + * Insert batch statement + * + * Generates a platform-specific insert string from the supplied data. + * + * @param string $table Table name + * @param array $keys INSERT keys + * @param array $values INSERT values + * + * @return string + */ + protected function _insertBatch($table, $keys, $values) + { + return 'INSERT ' . ($this->insertIgnore ? 'OR IGNORE' : '') . ' INTO ' . $table . ' (' . implode(', ', $keys) . ') VALUES ' . implode(', ', $values); + } + + //-------------------------------------------------------------------- + + /** + * Insert statement + * + * Generates a platform-specific insert string from the supplied data + * + * @param string $table The table name + * @param array $keys The insert keys + * @param array $unescapedKeys The insert values + * + * @return string + */ + protected function _insert($table, array $keys, array $unescapedKeys) + { + return 'INSERT ' . ($this->insertIgnore ? 'OR IGNORE' : '') . ' INTO ' . $table . ' (' . implode(', ', $keys) . ') VALUES (' . implode(', ', $unescapedKeys) . ')'; + } } From a5bd591a51c1d67ed041afea05ba991b7b4d22ef Mon Sep 17 00:00:00 2001 From: Peter Placzek Date: Mon, 25 Mar 2019 17:00:05 +0100 Subject: [PATCH 2/9] fixed for unit tests --- system/Database/MySQLi/Builder.php | 4 ++-- system/Database/SQLite3/Builder.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/system/Database/MySQLi/Builder.php b/system/Database/MySQLi/Builder.php index c49b4364c4c0..39d12ef2f595 100644 --- a/system/Database/MySQLi/Builder.php +++ b/system/Database/MySQLi/Builder.php @@ -66,7 +66,7 @@ class Builder extends BaseBuilder */ protected function _insertBatch($table, $keys, $values) { - return 'INSERT ' . ($this->insertIgnore ? 'IGNORE' : '') . ' INTO ' . $table . ' (' . implode(', ', $keys) . ') VALUES ' . implode(', ', $values); + return 'INSERT ' . ($this->insertIgnore ? 'IGNORE ' : '') . 'INTO ' . $table . ' (' . implode(', ', $keys) . ') VALUES ' . implode(', ', $values); } //-------------------------------------------------------------------- @@ -84,7 +84,7 @@ protected function _insertBatch($table, $keys, $values) */ protected function _insert($table, array $keys, array $unescapedKeys) { - return 'INSERT ' . ($this->insertIgnore ? 'IGNORE' : '') . ' INTO ' . $table . ' (' . implode(', ', $keys) . ') VALUES (' . implode(', ', $unescapedKeys) . ')'; + return 'INSERT ' . ($this->insertIgnore ? 'IGNORE ' : '') . 'INTO ' . $table . ' (' . implode(', ', $keys) . ') VALUES (' . implode(', ', $unescapedKeys) . ')'; } } diff --git a/system/Database/SQLite3/Builder.php b/system/Database/SQLite3/Builder.php index 2a4c1d9ac831..5d246196c4d1 100644 --- a/system/Database/SQLite3/Builder.php +++ b/system/Database/SQLite3/Builder.php @@ -118,7 +118,7 @@ protected function _truncate($table) */ protected function _insertBatch($table, $keys, $values) { - return 'INSERT ' . ($this->insertIgnore ? 'OR IGNORE' : '') . ' INTO ' . $table . ' (' . implode(', ', $keys) . ') VALUES ' . implode(', ', $values); + return 'INSERT ' . ($this->insertIgnore ? 'OR IGNORE ' : '') . 'INTO ' . $table . ' (' . implode(', ', $keys) . ') VALUES ' . implode(', ', $values); } //-------------------------------------------------------------------- @@ -136,7 +136,7 @@ protected function _insertBatch($table, $keys, $values) */ protected function _insert($table, array $keys, array $unescapedKeys) { - return 'INSERT ' . ($this->insertIgnore ? 'OR IGNORE' : '') . ' INTO ' . $table . ' (' . implode(', ', $keys) . ') VALUES (' . implode(', ', $unescapedKeys) . ')'; + return 'INSERT ' . ($this->insertIgnore ? 'OR IGNORE ' : '') . 'INTO ' . $table . ' (' . implode(', ', $keys) . ') VALUES (' . implode(', ', $unescapedKeys) . ')'; } } From 2dbe64345194a90f399d3b2fadd0f9d6c2d23ea5 Mon Sep 17 00:00:00 2001 From: Peter Placzek Date: Mon, 25 Mar 2019 17:03:40 +0100 Subject: [PATCH 3/9] added bool parameter for better method call --- system/Database/BaseBuilder.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php index 302aa05e44c5..a59813f944f1 100644 --- a/system/Database/BaseBuilder.php +++ b/system/Database/BaseBuilder.php @@ -269,11 +269,13 @@ public function getBinds(): array * * Set ignore Flag for next insert query. * + * @param bool $ignore + * * @return BaseBuilder */ - public function ignore() + public function ignore(bool $ignore = true) { - $this->insertIgnore = true; + $this->insertIgnore = $ignore; return $this; } From 6581002f8c58f2e6ca607dd5749e517114ce2d38 Mon Sep 17 00:00:00 2001 From: Peter Placzek Date: Wed, 27 Mar 2019 12:24:35 +0100 Subject: [PATCH 4/9] added compileIgnore + tests --- system/Database/BaseBuilder.php | 76 ++++++++++++++++---- system/Database/MySQLi/Builder.php | 40 +++-------- system/Database/Postgre/Builder.php | 74 ++++++++++++++++++- system/Database/SQLite3/Builder.php | 43 ++--------- tests/system/Database/Builder/DeleteTest.php | 23 ++++++ tests/system/Database/Builder/InsertTest.php | 40 +++++++++++ tests/system/Database/Builder/UpdateTest.php | 29 ++++++++ 7 files changed, 243 insertions(+), 82 deletions(-) diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php index a59813f944f1..dc7489b6fd40 100644 --- a/system/Database/BaseBuilder.php +++ b/system/Database/BaseBuilder.php @@ -209,12 +209,21 @@ class BaseBuilder protected $canLimitWhereUpdates = true; /** - * Ignore errors on insert statements - * for example for duplicate keys. + * Ignore data that cause certain + * exceptions, for example in case of + * duplicate keys. * * @var bool */ - protected $insertIgnore = false; + protected $ignore = false; + + /** + * Specifies which sql statements + * support the ignore option. + * + * @var array + */ + protected $supportedIgnoreStatements = []; //-------------------------------------------------------------------- @@ -267,7 +276,8 @@ public function getBinds(): array /** * Ignore * - * Set ignore Flag for next insert query. + * Set ignore Flag for next insert, + * update or delete query. * * @param bool $ignore * @@ -275,7 +285,7 @@ public function getBinds(): array */ public function ignore(bool $ignore = true) { - $this->insertIgnore = $ignore; + $this->ignore = $ignore; return $this; } @@ -1692,7 +1702,7 @@ public function insertBatch($set = null, $escape = null, $batchSize = 100, $test } } - $this->insertIgnore = false; + $this->ignore = false; if (! $testing) { @@ -1717,7 +1727,7 @@ public function insertBatch($set = null, $escape = null, $batchSize = 100, $test */ protected function _insertBatch($table, $keys, $values) { - return 'INSERT INTO ' . $table . ' (' . implode(', ', $keys) . ') VALUES ' . implode(', ', $values); + return 'INSERT ' . $this->compileIgnore('insert') . 'INTO ' . $table . ' (' . implode(', ', $keys) . ') VALUES ' . implode(', ', $values); } //-------------------------------------------------------------------- @@ -1844,7 +1854,7 @@ public function insert($set = null, $escape = null, $test = false) ), array_keys($this->QBSet), array_values($this->QBSet) ); - $this->insertIgnore = false; + $this->ignore = false; if ($test === false) { @@ -1901,7 +1911,7 @@ protected function validateInsert() */ protected function _insert($table, array $keys, array $unescapedKeys) { - return 'INSERT INTO ' . $table . ' (' . implode(', ', $keys) . ') VALUES (' . implode(', ', $unescapedKeys) . ')'; + return 'INSERT ' . $this->compileIgnore('insert') . 'INTO ' . $table . ' (' . implode(', ', $keys) . ') VALUES (' . implode(', ', $unescapedKeys) . ')'; } //-------------------------------------------------------------------- @@ -2016,6 +2026,8 @@ public function getCompiledUpdate($reset = true) * @param mixed $where * @param integer $limit * @param boolean $test Are we testing the code? + * + * @throws DatabaseException * * @return boolean TRUE on success, FALSE on failure */ @@ -2048,6 +2060,8 @@ public function update($set = null, $where = null, int $limit = null, $test = fa $sql = $this->_update($this->QBFrom[0], $this->QBSet); + $this->ignore = false; + if (! $test) { $this->resetWrite(); @@ -2080,12 +2094,14 @@ public function update($set = null, $where = null, int $limit = null, $test = fa */ protected function _update($table, $values) { + $valStr = []; + foreach ($values as $key => $val) { - $valstr[] = $key . ' = ' . $val; + $valStr[] = $key . ' = ' . $val; } - return 'UPDATE ' . $table . ' SET ' . implode(', ', $valstr) + return 'UPDATE ' . $this->compileIgnore('update') . $table . ' SET ' . implode(', ', $valStr) . $this->compileWhereHaving('QBWhere') . $this->compileOrderBy() . ($this->QBLimit ? $this->_limit(' ') : ''); @@ -2193,6 +2209,8 @@ public function updateBatch($set = null, $index = null, $batchSize = 100, $retur $this->QBWhere = []; } + $this->ignore = false; + $this->resetWrite(); return $returnSQL ? $savedSQL : $affected_rows; @@ -2214,6 +2232,8 @@ public function updateBatch($set = null, $index = null, $batchSize = 100, $retur protected function _updateBatch($table, $values, $index) { $ids = []; + $final = []; + foreach ($values as $key => $val) { $ids[] = $val[$index]; @@ -2237,7 +2257,7 @@ protected function _updateBatch($table, $values, $index) $this->where($index . ' IN(' . implode(',', $ids) . ')', null, false); - return 'UPDATE ' . $table . ' SET ' . substr($cases, 0, -2) . $this->compileWhereHaving('QBWhere'); + return 'UPDATE ' . $this->compileIgnore('update') . $table . ' SET ' . substr($cases, 0, -2) . $this->compileWhereHaving('QBWhere'); } //-------------------------------------------------------------------- @@ -2306,6 +2326,8 @@ public function emptyTable($test = false) $sql = $this->_delete($table); + $this->ignore = false; + if ($test) { return $sql; @@ -2422,6 +2444,8 @@ public function delete($where = '', $limit = null, $reset_data = true, $returnSQ $sql = $this->_delete($table); + $this->ignore = false; + if (! empty($limit)) { $this->QBLimit = $limit; @@ -2496,7 +2520,7 @@ public function decrement(string $column, int $value = 1) */ protected function _delete($table) { - return 'DELETE FROM ' . $table . $this->compileWhereHaving('QBWhere') + return 'DELETE ' . $this->compileIgnore('delete') . 'FROM ' . $table . $this->compileWhereHaving('QBWhere') . ($this->QBLimit ? ' LIMIT ' . $this->QBLimit : ''); } @@ -2611,6 +2635,32 @@ protected function compileSelect($select_override = false) return $sql; } + //-------------------------------------------------------------------- + + /** + * Compile Ignore Statement + * + * Checks if the ignore option is supported by + * the Database Driver for the specific statement. + * + * @param string $statement + * + * @return string + */ + protected function compileIgnore(string $statement) + { + $sql = ''; + + if( + $this->ignore && + isset($this->supportedIgnoreStatements[$statement]) + ) { + $sql = trim($this->supportedIgnoreStatements[$statement]).' '; + } + + return $sql; + } + //-------------------------------------------------------------------- /** diff --git a/system/Database/MySQLi/Builder.php b/system/Database/MySQLi/Builder.php index 39d12ef2f595..9a9a3ab86243 100644 --- a/system/Database/MySQLi/Builder.php +++ b/system/Database/MySQLi/Builder.php @@ -51,40 +51,16 @@ class Builder extends BaseBuilder */ protected $escapeChar = '`'; - //-------------------------------------------------------------------- - - /** - * Insert batch statement - * - * Generates a platform-specific insert string from the supplied data. - * - * @param string $table Table name - * @param array $keys INSERT keys - * @param array $values INSERT values - * - * @return string - */ - protected function _insertBatch($table, $keys, $values) - { - return 'INSERT ' . ($this->insertIgnore ? 'IGNORE ' : '') . 'INTO ' . $table . ' (' . implode(', ', $keys) . ') VALUES ' . implode(', ', $values); - } - - //-------------------------------------------------------------------- - /** - * Insert statement - * - * Generates a platform-specific insert string from the supplied data - * - * @param string $table The table name - * @param array $keys The insert keys - * @param array $unescapedKeys The insert values + * Specifies which sql statements + * support the ignore option. * - * @return string + * @var array */ - protected function _insert($table, array $keys, array $unescapedKeys) - { - return 'INSERT ' . ($this->insertIgnore ? 'IGNORE ' : '') . 'INTO ' . $table . ' (' . implode(', ', $keys) . ') VALUES (' . implode(', ', $unescapedKeys) . ')'; - } + protected $supportedIgnoreStatements = [ + 'update' => 'IGNORE', + 'insert' => 'IGNORE', + 'delete' => 'IGNORE' + ]; } diff --git a/system/Database/Postgre/Builder.php b/system/Database/Postgre/Builder.php index 478df55703cf..095a6d3af7ca 100644 --- a/system/Database/Postgre/Builder.php +++ b/system/Database/Postgre/Builder.php @@ -55,9 +55,41 @@ class Builder extends BaseBuilder 'RANDOM()', ]; + /** + * Specifies which sql statements + * support the ignore option. + * + * @var array + */ + protected $supportedIgnoreStatements = [ + 'insert' => 'ON CONFLICT DO NOTHING' + ]; + //-------------------------------------------------------------------- - /** + /** + * Compile Ignore Statement + * + * Checks if the ignore option is supported by + * the Database Driver for the specific statement. + * + * @param string $statement + * + * @return string + */ + protected function compileIgnore(string $statement) { + $sql = parent::compileIgnore($statement); + + if(!empty($sql)) { + $sql = ' '.trim($sql); + } + + return $sql; + } + + //-------------------------------------------------------------------- + + /** * ORDER BY * * @param string $orderby @@ -96,6 +128,8 @@ public function orderBy($orderby, $direction = '', $escape = null) * * @param string $column * @param integer $value + * + * @throws DatabaseException * * @return boolean */ @@ -115,6 +149,8 @@ public function increment(string $column, int $value = 1) * * @param string $column * @param integer $value + * + * @throws DatabaseException * * @return boolean */ @@ -127,6 +163,42 @@ public function decrement(string $column, int $value = 1) return $this->db->query($sql, $this->binds, false); } + //-------------------------------------------------------------------- + + /** + * Insert batch statement + * + * Generates a platform-specific insert string from the supplied data. + * + * @param string $table Table name + * @param array $keys INSERT keys + * @param array $values INSERT values + * + * @return string + */ + protected function _insertBatch($table, $keys, $values) + { + return 'INSERT INTO ' . $table . ' (' . implode(', ', $keys) . ') VALUES ' . implode(', ', $values) . $this->compileIgnore('insert'); + } + + //-------------------------------------------------------------------- + + /** + * Insert statement + * + * Generates a platform-specific insert string from the supplied data + * + * @param string $table The table name + * @param array $keys The insert keys + * @param array $unescapedKeys The insert values + * + * @return string + */ + protected function _insert($table, array $keys, array $unescapedKeys) + { + return 'INSERT INTO ' . $table . ' (' . implode(', ', $keys) . ') VALUES (' . implode(', ', $unescapedKeys) . ')' . $this->compileIgnore('insert'); + } + //-------------------------------------------------------------------- /** diff --git a/system/Database/SQLite3/Builder.php b/system/Database/SQLite3/Builder.php index 5d246196c4d1..492f51080425 100644 --- a/system/Database/SQLite3/Builder.php +++ b/system/Database/SQLite3/Builder.php @@ -67,6 +67,13 @@ class Builder extends BaseBuilder */ protected $canLimitWhereUpdates = false; + /** + * @var array + */ + protected $supportedIgnoreStatements = [ + 'insert' => 'OR IGNORE' + ]; + //-------------------------------------------------------------------- /** @@ -103,40 +110,4 @@ protected function _truncate($table) return 'DELETE FROM ' . $table; } - //-------------------------------------------------------------------- - - /** - * Insert batch statement - * - * Generates a platform-specific insert string from the supplied data. - * - * @param string $table Table name - * @param array $keys INSERT keys - * @param array $values INSERT values - * - * @return string - */ - protected function _insertBatch($table, $keys, $values) - { - return 'INSERT ' . ($this->insertIgnore ? 'OR IGNORE ' : '') . 'INTO ' . $table . ' (' . implode(', ', $keys) . ') VALUES ' . implode(', ', $values); - } - - //-------------------------------------------------------------------- - - /** - * Insert statement - * - * Generates a platform-specific insert string from the supplied data - * - * @param string $table The table name - * @param array $keys The insert keys - * @param array $unescapedKeys The insert values - * - * @return string - */ - protected function _insert($table, array $keys, array $unescapedKeys) - { - return 'INSERT ' . ($this->insertIgnore ? 'OR IGNORE ' : '') . 'INTO ' . $table . ' (' . implode(', ', $keys) . ') VALUES (' . implode(', ', $unescapedKeys) . ')'; - } - } diff --git a/tests/system/Database/Builder/DeleteTest.php b/tests/system/Database/Builder/DeleteTest.php index 2c616693735b..0fe4da54414d 100644 --- a/tests/system/Database/Builder/DeleteTest.php +++ b/tests/system/Database/Builder/DeleteTest.php @@ -36,4 +36,27 @@ public function testDelete() } //-------------------------------------------------------------------- + + public function testDeleteIgnore() + { + $builder = $this->db->table('jobs'); + + $answer = $builder->ignore()->delete(['id' => 1], null, true, true); + + if($this->db->getPlatform() == 'MySQLi') { + $expectedSQL = 'DELETE IGNORE FROM "jobs" WHERE "id" = :id:'; + } else { + $expectedSQL = 'DELETE FROM "jobs" WHERE "id" = :id:'; + } + + $expectedBinds = [ + 'id' => [ + 1, + true, + ], + ]; + + $this->assertEquals($expectedSQL, str_replace("\n", ' ', $answer)); + $this->assertEquals($expectedBinds, $builder->getBinds()); + } } diff --git a/tests/system/Database/Builder/InsertTest.php b/tests/system/Database/Builder/InsertTest.php index 6512a3628d0c..096f3b2ca744 100644 --- a/tests/system/Database/Builder/InsertTest.php +++ b/tests/system/Database/Builder/InsertTest.php @@ -44,6 +44,46 @@ public function testSimpleInsert() $this->assertEquals($expectedBinds, $builder->getBinds()); } + //-------------------------------------------------------------------- + + public function testInsertIgnore() + { + $builder = $this->db->table('jobs'); + + $insertData = [ + 'id' => 1, + 'name' => 'Grocery Sales', + ]; + + $builder->ignore()->insert($insertData, true, true); + + switch($this->db->getPlatform()) { + case 'MySQLi': + $expectedSQL = 'INSERT IGNORE INTO "jobs" ("id", "name") VALUES (1, \'Grocery Sales\')'; + break; + case 'postgre': + $expectedSQL = 'INSERT INTO "jobs" ("id", "name") VALUES (1, \'Grocery Sales\') ON CONFLICT DO NOTHING'; + break; + case 'SQLite3': + $expectedSQL = 'INSERT OR IGNORE INTO "jobs" ("id", "name") VALUES (1, \'Grocery Sales\') '; + breaK; + } + + $expectedBinds = [ + 'id' => [ + 1, + true, + ], + 'name' => [ + 'Grocery Sales', + true, + ], + ]; + + $this->assertEquals($expectedSQL, str_replace("\n", ' ', $builder->getCompiledInsert())); + $this->assertEquals($expectedBinds, $builder->getBinds()); + } + //-------------------------------------------------------------------- public function testThrowsExceptionOnNoValuesSet() diff --git a/tests/system/Database/Builder/UpdateTest.php b/tests/system/Database/Builder/UpdateTest.php index af3498e073c7..e594ac1e19d5 100644 --- a/tests/system/Database/Builder/UpdateTest.php +++ b/tests/system/Database/Builder/UpdateTest.php @@ -41,6 +41,35 @@ public function testUpdate() $this->assertEquals($expectedBinds, $builder->getBinds()); } + //-------------------------------------------------------------------- + + public function testUpdateIgnore() + { + $builder = new BaseBuilder('jobs', $this->db); + + $builder->where('id', 1)->ignore()->update(['name' => 'Programmer'], null, null, true); + + if($this->db->getPlatform() == 'MySQLi') { + $expectedSQL = 'UPDATE IGNORE "jobs" SET "name" = \'Programmer\' WHERE "id" = 1'; + } else { + $expectedSQL = 'UPDATE "jobs" SET "name" = \'Programmer\' WHERE "id" = 1'; + } + + $expectedBinds = [ + 'id' => [ + 1, + true, + ], + 'name' => [ + 'Programmer', + true, + ], + ]; + + $this->assertEquals($expectedSQL, str_replace("\n", ' ', $builder->getCompiledUpdate())); + $this->assertEquals($expectedBinds, $builder->getBinds()); + } + //-------------------------------------------------------------------- public function testUpdateInternalWhereAndLimit() From 6c0f7472e500506829d97a134c5569c1caa82890 Mon Sep 17 00:00:00 2001 From: Peter Placzek Date: Wed, 27 Mar 2019 13:28:11 +0100 Subject: [PATCH 5/9] fixed --- system/Database/BaseBuilder.php | 135 +++++++++---------- tests/system/Database/Builder/DeleteTest.php | 24 ---- tests/system/Database/Builder/InsertTest.php | 40 ------ tests/system/Database/Builder/UpdateTest.php | 29 ---- 4 files changed, 62 insertions(+), 166 deletions(-) diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php index dc7489b6fd40..6e4724322dfa 100644 --- a/system/Database/BaseBuilder.php +++ b/system/Database/BaseBuilder.php @@ -156,6 +156,15 @@ class BaseBuilder */ protected $QBWhereGroupCount = 0; + /** + * Ignore data that cause certain + * exceptions, for example in case of + * duplicate keys. + * + * @var boolean + */ + protected $QBIgnore = false; + /** * A reference to the database connection. * @@ -208,21 +217,12 @@ class BaseBuilder */ protected $canLimitWhereUpdates = true; - /** - * Ignore data that cause certain - * exceptions, for example in case of - * duplicate keys. - * - * @var bool - */ - protected $ignore = false; - - /** - * Specifies which sql statements - * support the ignore option. - * - * @var array - */ + /** + * Specifies which sql statements + * support the ignore option. + * + * @var array + */ protected $supportedIgnoreStatements = []; //-------------------------------------------------------------------- @@ -271,24 +271,24 @@ public function getBinds(): array return $this->binds; } - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- - /** - * Ignore - * - * Set ignore Flag for next insert, - * update or delete query. - * - * @param bool $ignore - * - * @return BaseBuilder - */ - public function ignore(bool $ignore = true) - { - $this->ignore = $ignore; + /** + * Ignore + * + * Set ignore Flag for next insert, + * update or delete query. + * + * @param boolean $ignore + * + * @return BaseBuilder + */ + public function ignore(bool $ignore = true) + { + $this->QBIgnore = $ignore; - return $this; - } + return $this; + } //-------------------------------------------------------------------- @@ -1702,8 +1702,6 @@ public function insertBatch($set = null, $escape = null, $batchSize = 100, $test } } - $this->ignore = false; - if (! $testing) { $this->resetWrite(); @@ -1796,8 +1794,8 @@ public function setInsertBatch($key, $value = '', $escape = null) * * @param boolean $reset TRUE: reset QB values; FALSE: leave QB values alone * - * @throws DatabaseException - * + * @throws DatabaseException + * * @return string */ public function getCompiledInsert($reset = true) @@ -1831,8 +1829,8 @@ public function getCompiledInsert($reset = true) * @param array $set An associative array of insert values * @param boolean $escape Whether to escape values and identifiers * @param boolean $test Used when running tests - * - * @throws DatabaseException + * + * @throws DatabaseException * * @return BaseResult|Query|false */ @@ -1854,8 +1852,6 @@ public function insert($set = null, $escape = null, $test = false) ), array_keys($this->QBSet), array_values($this->QBSet) ); - $this->ignore = false; - if ($test === false) { $this->resetWrite(); @@ -2026,8 +2022,8 @@ public function getCompiledUpdate($reset = true) * @param mixed $where * @param integer $limit * @param boolean $test Are we testing the code? - * - * @throws DatabaseException + * + * @throws DatabaseException * * @return boolean TRUE on success, FALSE on failure */ @@ -2060,8 +2056,6 @@ public function update($set = null, $where = null, int $limit = null, $test = fa $sql = $this->_update($this->QBFrom[0], $this->QBSet); - $this->ignore = false; - if (! $test) { $this->resetWrite(); @@ -2094,7 +2088,7 @@ public function update($set = null, $where = null, int $limit = null, $test = fa */ protected function _update($table, $values) { - $valStr = []; + $valStr = []; foreach ($values as $key => $val) { @@ -2209,8 +2203,6 @@ public function updateBatch($set = null, $index = null, $batchSize = 100, $retur $this->QBWhere = []; } - $this->ignore = false; - $this->resetWrite(); return $returnSQL ? $savedSQL : $affected_rows; @@ -2231,7 +2223,7 @@ public function updateBatch($set = null, $index = null, $batchSize = 100, $retur */ protected function _updateBatch($table, $values, $index) { - $ids = []; + $ids = []; $final = []; foreach ($values as $key => $val) @@ -2326,8 +2318,6 @@ public function emptyTable($test = false) $sql = $this->_delete($table); - $this->ignore = false; - if ($test) { return $sql; @@ -2444,8 +2434,6 @@ public function delete($where = '', $limit = null, $reset_data = true, $returnSQ $sql = $this->_delete($table); - $this->ignore = false; - if (! empty($limit)) { $this->QBLimit = $limit; @@ -2635,31 +2623,31 @@ protected function compileSelect($select_override = false) return $sql; } - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- - /** - * Compile Ignore Statement - * - * Checks if the ignore option is supported by - * the Database Driver for the specific statement. - * - * @param string $statement - * - * @return string - */ - protected function compileIgnore(string $statement) - { - $sql = ''; + /** + * Compile Ignore Statement + * + * Checks if the ignore option is supported by + * the Database Driver for the specific statement. + * + * @param string $statement + * + * @return string + */ + public function compileIgnore(string $statement) + { + $sql = ''; - if( - $this->ignore && - isset($this->supportedIgnoreStatements[$statement]) - ) { - $sql = trim($this->supportedIgnoreStatements[$statement]).' '; - } + if ($this->QBIgnore && + isset($this->supportedIgnoreStatements[$statement]) + ) + { + $sql = trim($this->supportedIgnoreStatements[$statement]) . ' '; + } - return $sql; - } + return $sql; + } //-------------------------------------------------------------------- @@ -2983,6 +2971,7 @@ protected function resetWrite() 'QBOrderBy' => [], 'QBKeys' => [], 'QBLimit' => false, + 'QBIgnore' => false, ]); } diff --git a/tests/system/Database/Builder/DeleteTest.php b/tests/system/Database/Builder/DeleteTest.php index 0fe4da54414d..88c3207b170d 100644 --- a/tests/system/Database/Builder/DeleteTest.php +++ b/tests/system/Database/Builder/DeleteTest.php @@ -35,28 +35,4 @@ public function testDelete() $this->assertEquals($expectedBinds, $builder->getBinds()); } - //-------------------------------------------------------------------- - - public function testDeleteIgnore() - { - $builder = $this->db->table('jobs'); - - $answer = $builder->ignore()->delete(['id' => 1], null, true, true); - - if($this->db->getPlatform() == 'MySQLi') { - $expectedSQL = 'DELETE IGNORE FROM "jobs" WHERE "id" = :id:'; - } else { - $expectedSQL = 'DELETE FROM "jobs" WHERE "id" = :id:'; - } - - $expectedBinds = [ - 'id' => [ - 1, - true, - ], - ]; - - $this->assertEquals($expectedSQL, str_replace("\n", ' ', $answer)); - $this->assertEquals($expectedBinds, $builder->getBinds()); - } } diff --git a/tests/system/Database/Builder/InsertTest.php b/tests/system/Database/Builder/InsertTest.php index 096f3b2ca744..6512a3628d0c 100644 --- a/tests/system/Database/Builder/InsertTest.php +++ b/tests/system/Database/Builder/InsertTest.php @@ -44,46 +44,6 @@ public function testSimpleInsert() $this->assertEquals($expectedBinds, $builder->getBinds()); } - //-------------------------------------------------------------------- - - public function testInsertIgnore() - { - $builder = $this->db->table('jobs'); - - $insertData = [ - 'id' => 1, - 'name' => 'Grocery Sales', - ]; - - $builder->ignore()->insert($insertData, true, true); - - switch($this->db->getPlatform()) { - case 'MySQLi': - $expectedSQL = 'INSERT IGNORE INTO "jobs" ("id", "name") VALUES (1, \'Grocery Sales\')'; - break; - case 'postgre': - $expectedSQL = 'INSERT INTO "jobs" ("id", "name") VALUES (1, \'Grocery Sales\') ON CONFLICT DO NOTHING'; - break; - case 'SQLite3': - $expectedSQL = 'INSERT OR IGNORE INTO "jobs" ("id", "name") VALUES (1, \'Grocery Sales\') '; - breaK; - } - - $expectedBinds = [ - 'id' => [ - 1, - true, - ], - 'name' => [ - 'Grocery Sales', - true, - ], - ]; - - $this->assertEquals($expectedSQL, str_replace("\n", ' ', $builder->getCompiledInsert())); - $this->assertEquals($expectedBinds, $builder->getBinds()); - } - //-------------------------------------------------------------------- public function testThrowsExceptionOnNoValuesSet() diff --git a/tests/system/Database/Builder/UpdateTest.php b/tests/system/Database/Builder/UpdateTest.php index e594ac1e19d5..af3498e073c7 100644 --- a/tests/system/Database/Builder/UpdateTest.php +++ b/tests/system/Database/Builder/UpdateTest.php @@ -41,35 +41,6 @@ public function testUpdate() $this->assertEquals($expectedBinds, $builder->getBinds()); } - //-------------------------------------------------------------------- - - public function testUpdateIgnore() - { - $builder = new BaseBuilder('jobs', $this->db); - - $builder->where('id', 1)->ignore()->update(['name' => 'Programmer'], null, null, true); - - if($this->db->getPlatform() == 'MySQLi') { - $expectedSQL = 'UPDATE IGNORE "jobs" SET "name" = \'Programmer\' WHERE "id" = 1'; - } else { - $expectedSQL = 'UPDATE "jobs" SET "name" = \'Programmer\' WHERE "id" = 1'; - } - - $expectedBinds = [ - 'id' => [ - 1, - true, - ], - 'name' => [ - 'Programmer', - true, - ], - ]; - - $this->assertEquals($expectedSQL, str_replace("\n", ' ', $builder->getCompiledUpdate())); - $this->assertEquals($expectedBinds, $builder->getBinds()); - } - //-------------------------------------------------------------------- public function testUpdateInternalWhereAndLimit() From 51948c258b356e508390a3e5293f563f39ed151a Mon Sep 17 00:00:00 2001 From: Peter Placzek Date: Wed, 27 Mar 2019 13:54:06 +0100 Subject: [PATCH 6/9] fixed public method for testing proposes --- system/Database/BaseBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php index 6e4724322dfa..04e4d4f4a24c 100644 --- a/system/Database/BaseBuilder.php +++ b/system/Database/BaseBuilder.php @@ -2635,7 +2635,7 @@ protected function compileSelect($select_override = false) * * @return string */ - public function compileIgnore(string $statement) + protected function compileIgnore(string $statement) { $sql = ''; From 5f811bade4cbf97706ef63f2fd0778f9acc239c7 Mon Sep 17 00:00:00 2001 From: Peter Placzek Date: Fri, 23 Aug 2019 14:58:49 +0200 Subject: [PATCH 7/9] updated docs --- user_guide_src/source/database/query_builder.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/user_guide_src/source/database/query_builder.rst b/user_guide_src/source/database/query_builder.rst index 6bf1efcc6739..fa8e32b3ccfa 100755 --- a/user_guide_src/source/database/query_builder.rst +++ b/user_guide_src/source/database/query_builder.rst @@ -622,6 +622,22 @@ The first parameter is an object. .. note:: All values are escaped automatically producing safer queries. +**$builder->ignore()** + +Generates an insert ignore string based on the data you supply, and runs the +query. So if an entry with the same primary key already exists, the query won't be inserted. +You can optionally pass an **boolean** to the function. Here is an example using the array of the above example:: + + $data = [ + 'title' => 'My title', + 'name' => 'My Name', + 'date' => 'My date' + ]; + + $builder->ignore(true)->insert($data); + // Produces: INSERT OR IGNORE INTO mytable (title, name, date) VALUES ('My title', 'My name', 'My date') + + **$builder->getCompiledInsert()** Compiles the insertion query just like $builder->insert() but does not From fa0bf1d6ac3b9fcc674382a7eb17ffee28393b2b Mon Sep 17 00:00:00 2001 From: Peter Placzek Date: Fri, 23 Aug 2019 15:05:42 +0200 Subject: [PATCH 8/9] fixed postgre builder methods --- system/Database/Postgre/Builder.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/system/Database/Postgre/Builder.php b/system/Database/Postgre/Builder.php index 162522d26b19..92915ac77ecb 100644 --- a/system/Database/Postgre/Builder.php +++ b/system/Database/Postgre/Builder.php @@ -39,6 +39,7 @@ use CodeIgniter\Database\BaseBuilder; use CodeIgniter\Database\Exceptions\DatabaseException; +use http\Encoding\Stream\Inflate; /** * Builder for Postgre @@ -178,7 +179,7 @@ public function decrement(string $column, int $value = 1) * * @return string */ - protected function _insertBatch($table, $keys, $values) + protected function _insertBatch(string $table, array $keys, array $values) : string { return 'INSERT INTO ' . $table . ' (' . implode(', ', $keys) . ') VALUES ' . implode(', ', $values) . $this->compileIgnore('insert'); } @@ -196,7 +197,7 @@ protected function _insertBatch($table, $keys, $values) * * @return string */ - protected function _insert($table, array $keys, array $unescapedKeys) + protected function _insert(string $table, array $keys, array $unescapedKeys) : string { return 'INSERT INTO ' . $table . ' (' . implode(', ', $keys) . ') VALUES (' . implode(', ', $unescapedKeys) . ')' . $this->compileIgnore('insert'); } From 2dc03f21925c10c283756928abff1f9161aa7e5c Mon Sep 17 00:00:00 2001 From: MGatner Date: Mon, 9 Dec 2019 10:31:12 -0500 Subject: [PATCH 9/9] Remove unrelated Postrgres methods --- system/Database/Postgre/Builder.php | 36 ----------------------------- 1 file changed, 36 deletions(-) diff --git a/system/Database/Postgre/Builder.php b/system/Database/Postgre/Builder.php index 2982312d06ec..f375445add19 100644 --- a/system/Database/Postgre/Builder.php +++ b/system/Database/Postgre/Builder.php @@ -169,42 +169,6 @@ public function decrement(string $column, int $value = 1) //-------------------------------------------------------------------- - /** - * Insert batch statement - * - * Generates a platform-specific insert string from the supplied data. - * - * @param string $table Table name - * @param array $keys INSERT keys - * @param array $values INSERT values - * - * @return string - */ - protected function _insertBatch(string $table, array $keys, array $values) : string - { - return 'INSERT INTO ' . $table . ' (' . implode(', ', $keys) . ') VALUES ' . implode(', ', $values) . $this->compileIgnore('insert'); - } - - //-------------------------------------------------------------------- - - /** - * Insert statement - * - * Generates a platform-specific insert string from the supplied data - * - * @param string $table The table name - * @param array $keys The insert keys - * @param array $unescapedKeys The insert values - * - * @return string - */ - protected function _insert(string $table, array $keys, array $unescapedKeys) : string - { - return 'INSERT INTO ' . $table . ' (' . implode(', ', $keys) . ') VALUES (' . implode(', ', $unescapedKeys) . ')' . $this->compileIgnore('insert'); - } - - //-------------------------------------------------------------------- - /** * Replace *