From e70b389f16f4e35f521fc011762dc564a3eb4e42 Mon Sep 17 00:00:00 2001 From: "Mark A. Connelly" <16519297+markconnellypro@users.noreply.github.com> Date: Sat, 2 Mar 2024 01:47:04 -0500 Subject: [PATCH 1/8] fix: isWriteType() to recognize CTE; [Postgre] allow beginning whitespace at query start, RETURNING with DELETE --- system/Database/BaseConnection.php | 2 +- system/Database/Postgre/Connection.php | 2 +- .../Database/Live/WriteTypeQueryTest.php | 141 +++++++++++++++++- 3 files changed, 137 insertions(+), 8 deletions(-) diff --git a/system/Database/BaseConnection.php b/system/Database/BaseConnection.php index 9b131152210b..00f39efb1be3 100644 --- a/system/Database/BaseConnection.php +++ b/system/Database/BaseConnection.php @@ -1657,7 +1657,7 @@ public function resetDataCache() */ public function isWriteType($sql): bool { - return (bool) preg_match('/^\s*"?(SET|INSERT|UPDATE|DELETE|REPLACE|CREATE|DROP|TRUNCATE|LOAD|COPY|ALTER|RENAME|GRANT|REVOKE|LOCK|UNLOCK|REINDEX|MERGE)\s/i', $sql); + return (bool) preg_match('/^\s*(WITH\s(\s|.)+(\s|[)]))?"?(SET|INSERT|UPDATE|DELETE|REPLACE|CREATE|DROP|TRUNCATE|LOAD|COPY|ALTER|RENAME|GRANT|REVOKE|LOCK|UNLOCK|REINDEX|MERGE)\s/i', $sql); } /** diff --git a/system/Database/Postgre/Connection.php b/system/Database/Postgre/Connection.php index 0f761db197b9..1a30e993abfb 100644 --- a/system/Database/Postgre/Connection.php +++ b/system/Database/Postgre/Connection.php @@ -577,7 +577,7 @@ protected function _transRollback(): bool */ public function isWriteType($sql): bool { - if (preg_match('#^(INSERT|UPDATE).*RETURNING\s.+(\,\s?.+)*$#is', $sql)) { + if (preg_match('#^\s*(WITH\s(\s|.)+(\s|[)]))?(INSERT|UPDATE|DELETE).*RETURNING\s.+(\,\s?.+)*$#is', $sql)) { return false; } diff --git a/tests/system/Database/Live/WriteTypeQueryTest.php b/tests/system/Database/Live/WriteTypeQueryTest.php index 9228288ac22d..bf524981563e 100644 --- a/tests/system/Database/Live/WriteTypeQueryTest.php +++ b/tests/system/Database/Live/WriteTypeQueryTest.php @@ -48,11 +48,49 @@ public function testInsert(): void $this->assertTrue($this->db->isWriteType($sql)); - if ($this->db->DBDriver === 'Postgre') { - $sql = "INSERT INTO my_table (col1, col2) VALUES ('Joe', 'Cool') RETURNING id;"; + $sql = "WITH seqvals AS (SELECT '3' AS seqval)INSERT INTO my_table (col1, col2) SELECT 'Joe', seqval FROM seqvals;"; + + $this->assertTrue($this->db->isWriteType($sql)); + + $sql = <<assertFalse($this->db->isWriteType($sql)); + $this->assertTrue($this->db->isWriteType($sql)); + + $assertionType = 'assertTrue'; + if ($this->db->DBDriver === 'Postgre') { + $assertionType = 'assertFalse'; } + + $sql = "INSERT INTO my_table (col1, col2) VALUES ('Joe', 'Cool') RETURNING id;"; + + $this->$assertionType($this->db->isWriteType($sql)); + + $sql = "WITH seqvals AS (SELECT '3' AS seqval)INSERT INTO my_table (col1, col2) SELECT 'Joe', seqval FROM seqvals RETURNING id;"; + + $this->$assertionType($this->db->isWriteType($sql)); + + $sql = <<$assertionType($this->db->isWriteType($sql)); + + $sql = <<$assertionType($this->db->isWriteType($sql)); } public function testUpdate(): void @@ -63,11 +101,52 @@ public function testUpdate(): void $this->assertTrue($this->db->isWriteType($sql)); - if ($this->db->DBDriver === 'Postgre') { - $sql = "UPDATE my_table SET col1 = 'foo' WHERE id = 2 RETURNING *;"; + $sql = "WITH seqvals AS (SELECT '3' AS seqval)UPDATE my_table SET col1 = seqval FROM seqvals WHERE id = 2;"; + + $this->assertTrue($this->db->isWriteType($sql)); + + $sql = <<assertFalse($this->db->isWriteType($sql)); + $this->assertTrue($this->db->isWriteType($sql)); + + $assertionType = 'assertTrue'; + if ($this->db->DBDriver === 'Postgre') { + $assertionType = 'assertFalse'; } + + $sql = "UPDATE my_table SET col1 = 'foo' WHERE id = 2 RETURNING *;"; + + $this->$assertionType($this->db->isWriteType($sql)); + + $sql = "WITH seqvals AS (SELECT '3' AS seqval)UPDATE my_table SET col1 = seqval FROM seqvals WHERE id = 2 RETURNING *;"; + + $this->$assertionType($this->db->isWriteType($sql)); + + $sql = <<$assertionType($this->db->isWriteType($sql)); + + $sql = <<$assertionType($this->db->isWriteType($sql)); } public function testDelete(): void @@ -76,6 +155,56 @@ public function testDelete(): void $sql = $builder->testMode()->delete(['id' => 1], null, true); $this->assertTrue($this->db->isWriteType($sql)); + + $sql = "DELETE FROM my_table WHERE id = 2;"; + + $this->assertTrue($this->db->isWriteType($sql)); + + $sql = "WITH seqvals AS (SELECT '3' AS seqval)DELETE FROM my_table JOIN seqvals ON col1 = seqval;"; + + $this->assertTrue($this->db->isWriteType($sql)); + + $sql = <<assertTrue($this->db->isWriteType($sql)); + + $assertionType = 'assertTrue'; + if ($this->db->DBDriver === 'Postgre') { + $assertionType = 'assertFalse'; + } + + $sql = "DELETE FROM my_table WHERE id = 2 RETURNING *;"; + + $this->$assertionType($this->db->isWriteType($sql)); + + $sql = "WITH seqvals AS (SELECT '3' AS seqval)DELETE FROM my_table JOIN seqvals ON col1 = seqval RETURNING *;"; + + $this->$assertionType($this->db->isWriteType($sql)); + + $sql = <<$assertionType($this->db->isWriteType($sql)); + + $sql = <<$assertionType($this->db->isWriteType($sql)); } public function testReplace(): void From ad4d54d2027856997d9360bc133744ad864d9f66 Mon Sep 17 00:00:00 2001 From: "Mark A. Connelly" <16519297+markconnellypro@users.noreply.github.com> Date: Sat, 2 Mar 2024 03:28:21 -0500 Subject: [PATCH 2/8] Fix linting errors in tests --- .../Database/Live/WriteTypeQueryTest.php | 146 +++++++++--------- 1 file changed, 73 insertions(+), 73 deletions(-) diff --git a/tests/system/Database/Live/WriteTypeQueryTest.php b/tests/system/Database/Live/WriteTypeQueryTest.php index bf524981563e..264528edb7f7 100644 --- a/tests/system/Database/Live/WriteTypeQueryTest.php +++ b/tests/system/Database/Live/WriteTypeQueryTest.php @@ -52,12 +52,12 @@ public function testInsert(): void $this->assertTrue($this->db->isWriteType($sql)); - $sql = <<assertTrue($this->db->isWriteType($sql)); @@ -68,29 +68,29 @@ public function testInsert(): void $sql = "INSERT INTO my_table (col1, col2) VALUES ('Joe', 'Cool') RETURNING id;"; - $this->$assertionType($this->db->isWriteType($sql)); + $this->{$assertionType}($this->db->isWriteType($sql)); $sql = "WITH seqvals AS (SELECT '3' AS seqval)INSERT INTO my_table (col1, col2) SELECT 'Joe', seqval FROM seqvals RETURNING id;"; - $this->$assertionType($this->db->isWriteType($sql)); + $this->{$assertionType}($this->db->isWriteType($sql)); - $sql = <<$assertionType($this->db->isWriteType($sql)); + $this->{$assertionType}($this->db->isWriteType($sql)); - $sql = <<$assertionType($this->db->isWriteType($sql)); + $this->{$assertionType}($this->db->isWriteType($sql)); } public function testUpdate(): void @@ -105,13 +105,13 @@ public function testUpdate(): void $this->assertTrue($this->db->isWriteType($sql)); - $sql = <<assertTrue($this->db->isWriteType($sql)); @@ -122,31 +122,31 @@ public function testUpdate(): void $sql = "UPDATE my_table SET col1 = 'foo' WHERE id = 2 RETURNING *;"; - $this->$assertionType($this->db->isWriteType($sql)); + $this->{$assertionType}($this->db->isWriteType($sql)); $sql = "WITH seqvals AS (SELECT '3' AS seqval)UPDATE my_table SET col1 = seqval FROM seqvals WHERE id = 2 RETURNING *;"; - $this->$assertionType($this->db->isWriteType($sql)); + $this->{$assertionType}($this->db->isWriteType($sql)); - $sql = <<$assertionType($this->db->isWriteType($sql)); + $this->{$assertionType}($this->db->isWriteType($sql)); - $sql = <<$assertionType($this->db->isWriteType($sql)); + $this->{$assertionType}($this->db->isWriteType($sql)); } public function testDelete(): void @@ -156,7 +156,7 @@ public function testDelete(): void $this->assertTrue($this->db->isWriteType($sql)); - $sql = "DELETE FROM my_table WHERE id = 2;"; + $sql = 'DELETE FROM my_table WHERE id = 2;'; $this->assertTrue($this->db->isWriteType($sql)); @@ -164,13 +164,13 @@ public function testDelete(): void $this->assertTrue($this->db->isWriteType($sql)); - $sql = <<assertTrue($this->db->isWriteType($sql)); @@ -179,32 +179,32 @@ public function testDelete(): void $assertionType = 'assertFalse'; } - $sql = "DELETE FROM my_table WHERE id = 2 RETURNING *;"; + $sql = 'DELETE FROM my_table WHERE id = 2 RETURNING *;'; - $this->$assertionType($this->db->isWriteType($sql)); + $this->{$assertionType}($this->db->isWriteType($sql)); $sql = "WITH seqvals AS (SELECT '3' AS seqval)DELETE FROM my_table JOIN seqvals ON col1 = seqval RETURNING *;"; - $this->$assertionType($this->db->isWriteType($sql)); + $this->{$assertionType}($this->db->isWriteType($sql)); - $sql = <<$assertionType($this->db->isWriteType($sql)); + $this->{$assertionType}($this->db->isWriteType($sql)); - $sql = <<$assertionType($this->db->isWriteType($sql)); + $this->{$assertionType}($this->db->isWriteType($sql)); } public function testReplace(): void From 8b05cde6870236a9d10653161281de0c1c73ab40 Mon Sep 17 00:00:00 2001 From: "Mark A. Connelly" <16519297+markconnellypro@users.noreply.github.com> Date: Sat, 2 Mar 2024 03:55:14 -0500 Subject: [PATCH 3/8] Refine isWriteType regex between WITH and other operation keyword --- system/Database/BaseConnection.php | 2 +- system/Database/Postgre/Connection.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/system/Database/BaseConnection.php b/system/Database/BaseConnection.php index 00f39efb1be3..b289c1a06a5d 100644 --- a/system/Database/BaseConnection.php +++ b/system/Database/BaseConnection.php @@ -1657,7 +1657,7 @@ public function resetDataCache() */ public function isWriteType($sql): bool { - return (bool) preg_match('/^\s*(WITH\s(\s|.)+(\s|[)]))?"?(SET|INSERT|UPDATE|DELETE|REPLACE|CREATE|DROP|TRUNCATE|LOAD|COPY|ALTER|RENAME|GRANT|REVOKE|LOCK|UNLOCK|REINDEX|MERGE)\s/i', $sql); + return (bool) preg_match('/^\s*(WITH\s.+(\s|[)]))?"?(SET|INSERT|UPDATE|DELETE|REPLACE|CREATE|DROP|TRUNCATE|LOAD|COPY|ALTER|RENAME|GRANT|REVOKE|LOCK|UNLOCK|REINDEX|MERGE)\s/is', $sql); } /** diff --git a/system/Database/Postgre/Connection.php b/system/Database/Postgre/Connection.php index 1a30e993abfb..3c2f28825388 100644 --- a/system/Database/Postgre/Connection.php +++ b/system/Database/Postgre/Connection.php @@ -577,7 +577,7 @@ protected function _transRollback(): bool */ public function isWriteType($sql): bool { - if (preg_match('#^\s*(WITH\s(\s|.)+(\s|[)]))?(INSERT|UPDATE|DELETE).*RETURNING\s.+(\,\s?.+)*$#is', $sql)) { + if (preg_match('#^\s*(WITH\s.+(\s|[)]))?(INSERT|UPDATE|DELETE).*RETURNING\s.+(\,\s?.+)*$#is', $sql)) { return false; } From 123d987ca6a3f77ac2faa8eab87b7927536a9781 Mon Sep 17 00:00:00 2001 From: "Mark A. Connelly" <16519297+markconnellypro@users.noreply.github.com> Date: Sun, 3 Mar 2024 22:21:22 -0500 Subject: [PATCH 4/8] Separate each test into its own function; add testAssertTypeReturning function for which databases support RETURNING --- .../Database/Live/WriteTypeQueryTest.php | 258 +++++++++++++++--- 1 file changed, 220 insertions(+), 38 deletions(-) diff --git a/tests/system/Database/Live/WriteTypeQueryTest.php b/tests/system/Database/Live/WriteTypeQueryTest.php index 264528edb7f7..befb0327b89b 100644 --- a/tests/system/Database/Live/WriteTypeQueryTest.php +++ b/tests/system/Database/Live/WriteTypeQueryTest.php @@ -28,6 +28,25 @@ final class WriteTypeQueryTest extends CIUnitTestCase protected $refresh = true; protected $seed = CITestSeeder::class; + /** + * Whether CodeIgniter ignores RETURNING for isWriteType. + * + * Currently, only Postgre is supported by CodeIgniter. + * This method should be updated if support for RETURNING + * is expanded to other databases. + * + * @param string $dbDriver + */ + private function testAssertTypeReturning($dbDriver): string + { + $assertType = 'assertTrue'; + if ($dbDriver === 'Postgre') { + $assertType = 'assertFalse'; + } + + return $assertType; + } + public function testSet(): void { $sql = 'SET FOREIGN_KEY_CHECKS=0'; @@ -35,7 +54,7 @@ public function testSet(): void $this->assertTrue($this->db->isWriteType($sql)); } - public function testInsert(): void + public function testInsertBuilder(): void { $builder = $this->db->table('jobs'); @@ -47,11 +66,42 @@ public function testInsert(): void $sql = $builder->getCompiledInsert(); $this->assertTrue($this->db->isWriteType($sql)); + } + + public function testInsertOne(): void + { + $sql = "INSERT INTO my_table (col1, col2) VALUES ('Joe', 'Cool');"; + + $this->assertTrue($this->db->isWriteType($sql)); + } + + public function testInsertMulti(): void + { + $sql = <<<'SQL' + INSERT INTO my_table (col1, col2) + VALUES ('Joe', 'Cool') + RETURNING id; + SQL; + + $this->assertTrue($this->db->isWriteType($sql)); + } + + public function testInsertWithOne(): void + { + $sql = "WITH seqvals AS (SELECT '3' AS seqval) INSERT INTO my_table (col1, col2) SELECT 'Joe', seqval FROM seqvals;"; + $this->assertTrue($this->db->isWriteType($sql)); + } + + public function testInsertWithOneNoSpace(): void + { $sql = "WITH seqvals AS (SELECT '3' AS seqval)INSERT INTO my_table (col1, col2) SELECT 'Joe', seqval FROM seqvals;"; $this->assertTrue($this->db->isWriteType($sql)); + } + public function testInsertWithMulti(): void + { $sql = <<<'SQL' WITH seqvals AS (SELECT '3' AS seqval) INSERT INTO my_table (col1, col2) @@ -60,28 +110,50 @@ public function testInsert(): void SQL; $this->assertTrue($this->db->isWriteType($sql)); + } - $assertionType = 'assertTrue'; - if ($this->db->DBDriver === 'Postgre') { - $assertionType = 'assertFalse'; - } - + public function testInsertOneReturning(): void + { $sql = "INSERT INTO my_table (col1, col2) VALUES ('Joe', 'Cool') RETURNING id;"; - $this->{$assertionType}($this->db->isWriteType($sql)); - - $sql = "WITH seqvals AS (SELECT '3' AS seqval)INSERT INTO my_table (col1, col2) SELECT 'Joe', seqval FROM seqvals RETURNING id;"; + $assertType = $this->testAssertTypeReturning($this->db->DBDriver); - $this->{$assertionType}($this->db->isWriteType($sql)); + $this->{$assertType}($this->db->isWriteType($sql)); + } + public function testInsertMultiReturning(): void + { $sql = <<<'SQL' INSERT INTO my_table (col1, col2) VALUES ('Joe', 'Cool') RETURNING id; SQL; - $this->{$assertionType}($this->db->isWriteType($sql)); + $assertType = $this->testAssertTypeReturning($this->db->DBDriver); + + $this->{$assertType}($this->db->isWriteType($sql)); + } + + public function testInsertWithOneReturning(): void + { + $sql = "WITH seqvals AS (SELECT '3' AS seqval) INSERT INTO my_table (col1, col2) SELECT 'Joe', seqval FROM seqvals RETURNING id;"; + + $assertType = $this->testAssertTypeReturning($this->db->DBDriver); + + $this->{$assertType}($this->db->isWriteType($sql)); + } + + public function testInsertWithOneReturningNoSpace(): void + { + $sql = "WITH seqvals AS (SELECT '3' AS seqval)INSERT INTO my_table (col1, col2) SELECT 'Joe', seqval FROM seqvals RETURNING id;"; + + $assertType = $this->testAssertTypeReturning($this->db->DBDriver); + + $this->{$assertType}($this->db->isWriteType($sql)); + } + public function testInsertWithMultiReturning(): void + { $sql = <<<'SQL' WITH seqvals AS (SELECT '3' AS seqval) INSERT INTO my_table (col1, col2) @@ -90,21 +162,54 @@ public function testInsert(): void RETURNING id; SQL; - $this->{$assertionType}($this->db->isWriteType($sql)); + $assertType = $this->testAssertTypeReturning($this->db->DBDriver); + + $this->{$assertType}($this->db->isWriteType($sql)); } - public function testUpdate(): void + public function testUpdateBuilder(): void { $builder = new BaseBuilder('jobs', $this->db); $builder->testMode()->where('id', 1)->update(['name' => 'Programmer'], null, null); $sql = $builder->getCompiledInsert(); $this->assertTrue($this->db->isWriteType($sql)); + } + + public function testUpdateOne(): void + { + $sql = "UPDATE my_table SET col1 = 'foo' WHERE id = 2;"; + + $this->assertTrue($this->db->isWriteType($sql)); + } + + public function testUpdateMulti(): void + { + $sql = <<<'SQL' + UPDATE my_table + SET col1 = 'foo' + WHERE id = 2; + SQL; + + $this->assertTrue($this->db->isWriteType($sql)); + } + + public function testUpdateWithOne(): void + { + $sql = "WITH seqvals AS (SELECT '3' AS seqval) UPDATE my_table SET col1 = seqval FROM seqvals WHERE id = 2;"; + + $this->assertTrue($this->db->isWriteType($sql)); + } + public function testUpdateWithOneNoSpace(): void + { $sql = "WITH seqvals AS (SELECT '3' AS seqval)UPDATE my_table SET col1 = seqval FROM seqvals WHERE id = 2;"; $this->assertTrue($this->db->isWriteType($sql)); + } + public function testUpdateWithMulti(): void + { $sql = <<<'SQL' WITH seqvals AS (SELECT '3' AS seqval) UPDATE my_table @@ -114,20 +219,19 @@ public function testUpdate(): void SQL; $this->assertTrue($this->db->isWriteType($sql)); + } - $assertionType = 'assertTrue'; - if ($this->db->DBDriver === 'Postgre') { - $assertionType = 'assertFalse'; - } - + public function testUpdateOneReturning(): void + { $sql = "UPDATE my_table SET col1 = 'foo' WHERE id = 2 RETURNING *;"; - $this->{$assertionType}($this->db->isWriteType($sql)); - - $sql = "WITH seqvals AS (SELECT '3' AS seqval)UPDATE my_table SET col1 = seqval FROM seqvals WHERE id = 2 RETURNING *;"; + $assertType = $this->testAssertTypeReturning($this->db->DBDriver); - $this->{$assertionType}($this->db->isWriteType($sql)); + $this->{$assertType}($this->db->isWriteType($sql)); + } + public function testUpdateMultiReturning(): void + { $sql = <<<'SQL' UPDATE my_table SET col1 = 'foo' @@ -135,8 +239,31 @@ public function testUpdate(): void RETURNING *; SQL; - $this->{$assertionType}($this->db->isWriteType($sql)); + $assertType = $this->testAssertTypeReturning($this->db->DBDriver); + + $this->{$assertType}($this->db->isWriteType($sql)); + } + + public function testUpdateWithOneReturning(): void + { + $sql = "WITH seqvals AS (SELECT '3' AS seqval) UPDATE my_table SET col1 = seqval FROM seqvals WHERE id = 2 RETURNING *;"; + + $assertType = $this->testAssertTypeReturning($this->db->DBDriver); + + $this->{$assertType}($this->db->isWriteType($sql)); + } + + public function testUpdateWithOneReturningNoSpace(): void + { + $sql = "WITH seqvals AS (SELECT '3' AS seqval)UPDATE my_table SET col1 = seqval FROM seqvals WHERE id = 2 RETURNING *;"; + + $assertType = $this->testAssertTypeReturning($this->db->DBDriver); + $this->{$assertType}($this->db->isWriteType($sql)); + } + + public function testUpdateWithMultiReturning(): void + { $sql = <<<'SQL' WITH seqvals AS (SELECT '3' AS seqval) UPDATE my_table @@ -146,24 +273,52 @@ public function testUpdate(): void RETURNING *; SQL; - $this->{$assertionType}($this->db->isWriteType($sql)); + $assertType = $this->testAssertTypeReturning($this->db->DBDriver); + + $this->{$assertType}($this->db->isWriteType($sql)); } - public function testDelete(): void + public function testDeleteBuilder(): void { $builder = $this->db->table('jobs'); $sql = $builder->testMode()->delete(['id' => 1], null, true); $this->assertTrue($this->db->isWriteType($sql)); + } + public function testDeleteOne(): void + { $sql = 'DELETE FROM my_table WHERE id = 2;'; $this->assertTrue($this->db->isWriteType($sql)); + } + + public function testDeleteMulti(): void + { + $sql = <<<'SQL' + DELETE FROM my_table + WHERE id = 2; + SQL; + + $this->assertTrue($this->db->isWriteType($sql)); + } + + public function testDeleteWithOne(): void + { + $sql = "WITH seqvals AS (SELECT '3' AS seqval) DELETE FROM my_table JOIN seqvals ON col1 = seqval;"; + $this->assertTrue($this->db->isWriteType($sql)); + } + + public function testDeleteWithOneNoSpace(): void + { $sql = "WITH seqvals AS (SELECT '3' AS seqval)DELETE FROM my_table JOIN seqvals ON col1 = seqval;"; $this->assertTrue($this->db->isWriteType($sql)); + } + public function testDeleteWithMulti(): void + { $sql = <<<'SQL' WITH seqvals AS (SELECT '3' AS seqval) @@ -173,28 +328,50 @@ public function testDelete(): void SQL; $this->assertTrue($this->db->isWriteType($sql)); + } - $assertionType = 'assertTrue'; - if ($this->db->DBDriver === 'Postgre') { - $assertionType = 'assertFalse'; - } - + public function testDeleteOneReturning(): void + { $sql = 'DELETE FROM my_table WHERE id = 2 RETURNING *;'; - $this->{$assertionType}($this->db->isWriteType($sql)); - - $sql = "WITH seqvals AS (SELECT '3' AS seqval)DELETE FROM my_table JOIN seqvals ON col1 = seqval RETURNING *;"; + $assertType = $this->testAssertTypeReturning($this->db->DBDriver); - $this->{$assertionType}($this->db->isWriteType($sql)); + $this->{$assertType}($this->db->isWriteType($sql)); + } + public function testDeleteMultiReturning(): void + { $sql = <<<'SQL' DELETE FROM my_table WHERE id = 2 RETURNING *; SQL; - $this->{$assertionType}($this->db->isWriteType($sql)); + $assertType = $this->testAssertTypeReturning($this->db->DBDriver); + + $this->{$assertType}($this->db->isWriteType($sql)); + } + + public function testDeleteWithOneReturning(): void + { + $sql = "WITH seqvals AS (SELECT '3' AS seqval) DELETE FROM my_table JOIN seqvals ON col1 = seqval RETURNING *;"; + + $assertType = $this->testAssertTypeReturning($this->db->DBDriver); + + $this->{$assertType}($this->db->isWriteType($sql)); + } + + public function testDeleteWithOneReturningNoSpace(): void + { + $sql = "WITH seqvals AS (SELECT '3' AS seqval)DELETE FROM my_table JOIN seqvals ON col1 = seqval RETURNING *;"; + + $assertType = $this->testAssertTypeReturning($this->db->DBDriver); + + $this->{$assertType}($this->db->isWriteType($sql)); + } + public function testDeleteWithMultiReturning(): void + { $sql = <<<'SQL' WITH seqvals AS (SELECT '3' AS seqval) @@ -204,7 +381,9 @@ public function testDelete(): void RETURNING *; SQL; - $this->{$assertionType}($this->db->isWriteType($sql)); + $assertType = $this->testAssertTypeReturning($this->db->DBDriver); + + $this->{$assertType}($this->db->isWriteType($sql)); } public function testReplace(): void @@ -225,19 +404,22 @@ public function testReplace(): void $this->assertTrue($this->db->isWriteType($sql)); } - public function testCreate(): void + public function testCreateDatabase(): void { $sql = 'CREATE DATABASE foo'; $this->assertTrue($this->db->isWriteType($sql)); } - public function testDrop(): void + public function testDropDatabase(): void { $sql = 'DROP DATABASE foo'; $this->assertTrue($this->db->isWriteType($sql)); + } + public function testDropTable(): void + { $sql = 'DROP TABLE foo'; $this->assertTrue($this->db->isWriteType($sql)); From 8e1f62fc2670a0db2e7a4241d7ad846fab17f3fb Mon Sep 17 00:00:00 2001 From: "Mark A. Connelly" <16519297+markconnellypro@users.noreply.github.com> Date: Sun, 3 Mar 2024 22:25:18 -0500 Subject: [PATCH 5/8] Remove RETURNING from testInsertMulti() --- tests/system/Database/Live/WriteTypeQueryTest.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/system/Database/Live/WriteTypeQueryTest.php b/tests/system/Database/Live/WriteTypeQueryTest.php index befb0327b89b..2ab956781d7f 100644 --- a/tests/system/Database/Live/WriteTypeQueryTest.php +++ b/tests/system/Database/Live/WriteTypeQueryTest.php @@ -79,8 +79,7 @@ public function testInsertMulti(): void { $sql = <<<'SQL' INSERT INTO my_table (col1, col2) - VALUES ('Joe', 'Cool') - RETURNING id; + VALUES ('Joe', 'Cool'); SQL; $this->assertTrue($this->db->isWriteType($sql)); From d2c6162ed3e4ccdfda0873323116addb4f62d41d Mon Sep 17 00:00:00 2001 From: "Mark A. Connelly" <16519297+markconnellypro@users.noreply.github.com> Date: Sun, 3 Mar 2024 23:12:12 -0500 Subject: [PATCH 6/8] Fix rector errors in tests --- tests/system/Database/Live/WriteTypeQueryTest.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/system/Database/Live/WriteTypeQueryTest.php b/tests/system/Database/Live/WriteTypeQueryTest.php index 2ab956781d7f..3a4e5a4ba50c 100644 --- a/tests/system/Database/Live/WriteTypeQueryTest.php +++ b/tests/system/Database/Live/WriteTypeQueryTest.php @@ -39,12 +39,11 @@ final class WriteTypeQueryTest extends CIUnitTestCase */ private function testAssertTypeReturning($dbDriver): string { - $assertType = 'assertTrue'; if ($dbDriver === 'Postgre') { - $assertType = 'assertFalse'; + return 'assertFalse'; } - return $assertType; + return 'assertTrue'; } public function testSet(): void From b0dd905a68e83b6ccfad362ba488bd632ed07eab Mon Sep 17 00:00:00 2001 From: "Mark A. Connelly" <16519297+markconnellypro@users.noreply.github.com> Date: Sun, 3 Mar 2024 23:30:17 -0500 Subject: [PATCH 7/8] Change testAssertTypeReturning to testReturning; remove dynamic $assertType and instead use if statement --- .../Database/Live/WriteTypeQueryTest.php | 132 +++++++++++------- 1 file changed, 79 insertions(+), 53 deletions(-) diff --git a/tests/system/Database/Live/WriteTypeQueryTest.php b/tests/system/Database/Live/WriteTypeQueryTest.php index 3a4e5a4ba50c..df3ea16d02c6 100644 --- a/tests/system/Database/Live/WriteTypeQueryTest.php +++ b/tests/system/Database/Live/WriteTypeQueryTest.php @@ -29,21 +29,17 @@ final class WriteTypeQueryTest extends CIUnitTestCase protected $seed = CITestSeeder::class; /** - * Whether CodeIgniter ignores RETURNING for isWriteType. + * Whether CodeIgniter considers RETURNING in isWriteType. * * Currently, only Postgre is supported by CodeIgniter. * This method should be updated if support for RETURNING - * is expanded to other databases. + * is expanded for other databases. * * @param string $dbDriver */ - private function testAssertTypeReturning($dbDriver): string + private function testReturning($dbDriver): bool { - if ($dbDriver === 'Postgre') { - return 'assertFalse'; - } - - return 'assertTrue'; + return $dbDriver === 'Postgre'; } public function testSet(): void @@ -114,9 +110,11 @@ public function testInsertOneReturning(): void { $sql = "INSERT INTO my_table (col1, col2) VALUES ('Joe', 'Cool') RETURNING id;"; - $assertType = $this->testAssertTypeReturning($this->db->DBDriver); - - $this->{$assertType}($this->db->isWriteType($sql)); + if ($this->testReturning($this->db->DBDriver)) { + $this->assertFalse($this->db->isWriteType($sql)); + } else { + $this->assertTrue($this->db->isWriteType($sql)); + } } public function testInsertMultiReturning(): void @@ -127,27 +125,33 @@ public function testInsertMultiReturning(): void RETURNING id; SQL; - $assertType = $this->testAssertTypeReturning($this->db->DBDriver); - - $this->{$assertType}($this->db->isWriteType($sql)); + if ($this->testReturning($this->db->DBDriver)) { + $this->assertFalse($this->db->isWriteType($sql)); + } else { + $this->assertTrue($this->db->isWriteType($sql)); + } } public function testInsertWithOneReturning(): void { $sql = "WITH seqvals AS (SELECT '3' AS seqval) INSERT INTO my_table (col1, col2) SELECT 'Joe', seqval FROM seqvals RETURNING id;"; - $assertType = $this->testAssertTypeReturning($this->db->DBDriver); - - $this->{$assertType}($this->db->isWriteType($sql)); + if ($this->testReturning($this->db->DBDriver)) { + $this->assertFalse($this->db->isWriteType($sql)); + } else { + $this->assertTrue($this->db->isWriteType($sql)); + } } public function testInsertWithOneReturningNoSpace(): void { $sql = "WITH seqvals AS (SELECT '3' AS seqval)INSERT INTO my_table (col1, col2) SELECT 'Joe', seqval FROM seqvals RETURNING id;"; - $assertType = $this->testAssertTypeReturning($this->db->DBDriver); - - $this->{$assertType}($this->db->isWriteType($sql)); + if ($this->testReturning($this->db->DBDriver)) { + $this->assertFalse($this->db->isWriteType($sql)); + } else { + $this->assertTrue($this->db->isWriteType($sql)); + } } public function testInsertWithMultiReturning(): void @@ -160,9 +164,11 @@ public function testInsertWithMultiReturning(): void RETURNING id; SQL; - $assertType = $this->testAssertTypeReturning($this->db->DBDriver); - - $this->{$assertType}($this->db->isWriteType($sql)); + if ($this->testReturning($this->db->DBDriver)) { + $this->assertFalse($this->db->isWriteType($sql)); + } else { + $this->assertTrue($this->db->isWriteType($sql)); + } } public function testUpdateBuilder(): void @@ -223,9 +229,11 @@ public function testUpdateOneReturning(): void { $sql = "UPDATE my_table SET col1 = 'foo' WHERE id = 2 RETURNING *;"; - $assertType = $this->testAssertTypeReturning($this->db->DBDriver); - - $this->{$assertType}($this->db->isWriteType($sql)); + if ($this->testReturning($this->db->DBDriver)) { + $this->assertFalse($this->db->isWriteType($sql)); + } else { + $this->assertTrue($this->db->isWriteType($sql)); + } } public function testUpdateMultiReturning(): void @@ -237,27 +245,33 @@ public function testUpdateMultiReturning(): void RETURNING *; SQL; - $assertType = $this->testAssertTypeReturning($this->db->DBDriver); - - $this->{$assertType}($this->db->isWriteType($sql)); + if ($this->testReturning($this->db->DBDriver)) { + $this->assertFalse($this->db->isWriteType($sql)); + } else { + $this->assertTrue($this->db->isWriteType($sql)); + } } public function testUpdateWithOneReturning(): void { $sql = "WITH seqvals AS (SELECT '3' AS seqval) UPDATE my_table SET col1 = seqval FROM seqvals WHERE id = 2 RETURNING *;"; - $assertType = $this->testAssertTypeReturning($this->db->DBDriver); - - $this->{$assertType}($this->db->isWriteType($sql)); + if ($this->testReturning($this->db->DBDriver)) { + $this->assertFalse($this->db->isWriteType($sql)); + } else { + $this->assertTrue($this->db->isWriteType($sql)); + } } public function testUpdateWithOneReturningNoSpace(): void { $sql = "WITH seqvals AS (SELECT '3' AS seqval)UPDATE my_table SET col1 = seqval FROM seqvals WHERE id = 2 RETURNING *;"; - $assertType = $this->testAssertTypeReturning($this->db->DBDriver); - - $this->{$assertType}($this->db->isWriteType($sql)); + if ($this->testReturning($this->db->DBDriver)) { + $this->assertFalse($this->db->isWriteType($sql)); + } else { + $this->assertTrue($this->db->isWriteType($sql)); + } } public function testUpdateWithMultiReturning(): void @@ -271,9 +285,11 @@ public function testUpdateWithMultiReturning(): void RETURNING *; SQL; - $assertType = $this->testAssertTypeReturning($this->db->DBDriver); - - $this->{$assertType}($this->db->isWriteType($sql)); + if ($this->testReturning($this->db->DBDriver)) { + $this->assertFalse($this->db->isWriteType($sql)); + } else { + $this->assertTrue($this->db->isWriteType($sql)); + } } public function testDeleteBuilder(): void @@ -332,9 +348,11 @@ public function testDeleteOneReturning(): void { $sql = 'DELETE FROM my_table WHERE id = 2 RETURNING *;'; - $assertType = $this->testAssertTypeReturning($this->db->DBDriver); - - $this->{$assertType}($this->db->isWriteType($sql)); + if ($this->testReturning($this->db->DBDriver)) { + $this->assertFalse($this->db->isWriteType($sql)); + } else { + $this->assertTrue($this->db->isWriteType($sql)); + } } public function testDeleteMultiReturning(): void @@ -345,27 +363,33 @@ public function testDeleteMultiReturning(): void RETURNING *; SQL; - $assertType = $this->testAssertTypeReturning($this->db->DBDriver); - - $this->{$assertType}($this->db->isWriteType($sql)); + if ($this->testReturning($this->db->DBDriver)) { + $this->assertFalse($this->db->isWriteType($sql)); + } else { + $this->assertTrue($this->db->isWriteType($sql)); + } } public function testDeleteWithOneReturning(): void { $sql = "WITH seqvals AS (SELECT '3' AS seqval) DELETE FROM my_table JOIN seqvals ON col1 = seqval RETURNING *;"; - $assertType = $this->testAssertTypeReturning($this->db->DBDriver); - - $this->{$assertType}($this->db->isWriteType($sql)); + if ($this->testReturning($this->db->DBDriver)) { + $this->assertFalse($this->db->isWriteType($sql)); + } else { + $this->assertTrue($this->db->isWriteType($sql)); + } } public function testDeleteWithOneReturningNoSpace(): void { $sql = "WITH seqvals AS (SELECT '3' AS seqval)DELETE FROM my_table JOIN seqvals ON col1 = seqval RETURNING *;"; - $assertType = $this->testAssertTypeReturning($this->db->DBDriver); - - $this->{$assertType}($this->db->isWriteType($sql)); + if ($this->testReturning($this->db->DBDriver)) { + $this->assertFalse($this->db->isWriteType($sql)); + } else { + $this->assertTrue($this->db->isWriteType($sql)); + } } public function testDeleteWithMultiReturning(): void @@ -379,9 +403,11 @@ public function testDeleteWithMultiReturning(): void RETURNING *; SQL; - $assertType = $this->testAssertTypeReturning($this->db->DBDriver); - - $this->{$assertType}($this->db->isWriteType($sql)); + if ($this->testReturning($this->db->DBDriver)) { + $this->assertFalse($this->db->isWriteType($sql)); + } else { + $this->assertTrue($this->db->isWriteType($sql)); + } } public function testReplace(): void From feb7f81ec0ff4de6da3e66cc8ad8aebb4efb5a9d Mon Sep 17 00:00:00 2001 From: "Mark A. Connelly" <16519297+markconnellypro@users.noreply.github.com> Date: Mon, 4 Mar 2024 00:20:00 -0500 Subject: [PATCH 8/8] Remove isWriteType override for Postgre; Omit RETURNING from isWriteType for all database types; Modify tests for uniform handling for all database types --- system/Database/BaseConnection.php | 2 +- system/Database/Postgre/Connection.php | 16 --- .../Database/Live/WriteTypeQueryTest.php | 104 +++--------------- 3 files changed, 16 insertions(+), 106 deletions(-) diff --git a/system/Database/BaseConnection.php b/system/Database/BaseConnection.php index b289c1a06a5d..ae5c4b3805f1 100644 --- a/system/Database/BaseConnection.php +++ b/system/Database/BaseConnection.php @@ -1657,7 +1657,7 @@ public function resetDataCache() */ public function isWriteType($sql): bool { - return (bool) preg_match('/^\s*(WITH\s.+(\s|[)]))?"?(SET|INSERT|UPDATE|DELETE|REPLACE|CREATE|DROP|TRUNCATE|LOAD|COPY|ALTER|RENAME|GRANT|REVOKE|LOCK|UNLOCK|REINDEX|MERGE)\s/is', $sql); + return (bool) preg_match('/^\s*(WITH\s.+(\s|[)]))?"?(SET|INSERT|UPDATE|DELETE|REPLACE|CREATE|DROP|TRUNCATE|LOAD|COPY|ALTER|RENAME|GRANT|REVOKE|LOCK|UNLOCK|REINDEX|MERGE)\s(?!.*\sRETURNING\s)/is', $sql); } /** diff --git a/system/Database/Postgre/Connection.php b/system/Database/Postgre/Connection.php index 3c2f28825388..d488c96ec1d4 100644 --- a/system/Database/Postgre/Connection.php +++ b/system/Database/Postgre/Connection.php @@ -567,20 +567,4 @@ protected function _transRollback(): bool { return (bool) pg_query($this->connID, 'ROLLBACK'); } - - /** - * Determines if a query is a "write" type. - * - * Overrides BaseConnection::isWriteType, adding additional read query types. - * - * @param string $sql - */ - public function isWriteType($sql): bool - { - if (preg_match('#^\s*(WITH\s.+(\s|[)]))?(INSERT|UPDATE|DELETE).*RETURNING\s.+(\,\s?.+)*$#is', $sql)) { - return false; - } - - return parent::isWriteType($sql); - } } diff --git a/tests/system/Database/Live/WriteTypeQueryTest.php b/tests/system/Database/Live/WriteTypeQueryTest.php index df3ea16d02c6..fe59f19ffd2e 100644 --- a/tests/system/Database/Live/WriteTypeQueryTest.php +++ b/tests/system/Database/Live/WriteTypeQueryTest.php @@ -28,20 +28,6 @@ final class WriteTypeQueryTest extends CIUnitTestCase protected $refresh = true; protected $seed = CITestSeeder::class; - /** - * Whether CodeIgniter considers RETURNING in isWriteType. - * - * Currently, only Postgre is supported by CodeIgniter. - * This method should be updated if support for RETURNING - * is expanded for other databases. - * - * @param string $dbDriver - */ - private function testReturning($dbDriver): bool - { - return $dbDriver === 'Postgre'; - } - public function testSet(): void { $sql = 'SET FOREIGN_KEY_CHECKS=0'; @@ -110,11 +96,7 @@ public function testInsertOneReturning(): void { $sql = "INSERT INTO my_table (col1, col2) VALUES ('Joe', 'Cool') RETURNING id;"; - if ($this->testReturning($this->db->DBDriver)) { - $this->assertFalse($this->db->isWriteType($sql)); - } else { - $this->assertTrue($this->db->isWriteType($sql)); - } + $this->assertFalse($this->db->isWriteType($sql)); } public function testInsertMultiReturning(): void @@ -125,33 +107,21 @@ public function testInsertMultiReturning(): void RETURNING id; SQL; - if ($this->testReturning($this->db->DBDriver)) { - $this->assertFalse($this->db->isWriteType($sql)); - } else { - $this->assertTrue($this->db->isWriteType($sql)); - } + $this->assertFalse($this->db->isWriteType($sql)); } public function testInsertWithOneReturning(): void { $sql = "WITH seqvals AS (SELECT '3' AS seqval) INSERT INTO my_table (col1, col2) SELECT 'Joe', seqval FROM seqvals RETURNING id;"; - if ($this->testReturning($this->db->DBDriver)) { - $this->assertFalse($this->db->isWriteType($sql)); - } else { - $this->assertTrue($this->db->isWriteType($sql)); - } + $this->assertFalse($this->db->isWriteType($sql)); } public function testInsertWithOneReturningNoSpace(): void { $sql = "WITH seqvals AS (SELECT '3' AS seqval)INSERT INTO my_table (col1, col2) SELECT 'Joe', seqval FROM seqvals RETURNING id;"; - if ($this->testReturning($this->db->DBDriver)) { - $this->assertFalse($this->db->isWriteType($sql)); - } else { - $this->assertTrue($this->db->isWriteType($sql)); - } + $this->assertFalse($this->db->isWriteType($sql)); } public function testInsertWithMultiReturning(): void @@ -164,11 +134,7 @@ public function testInsertWithMultiReturning(): void RETURNING id; SQL; - if ($this->testReturning($this->db->DBDriver)) { - $this->assertFalse($this->db->isWriteType($sql)); - } else { - $this->assertTrue($this->db->isWriteType($sql)); - } + $this->assertFalse($this->db->isWriteType($sql)); } public function testUpdateBuilder(): void @@ -229,11 +195,7 @@ public function testUpdateOneReturning(): void { $sql = "UPDATE my_table SET col1 = 'foo' WHERE id = 2 RETURNING *;"; - if ($this->testReturning($this->db->DBDriver)) { - $this->assertFalse($this->db->isWriteType($sql)); - } else { - $this->assertTrue($this->db->isWriteType($sql)); - } + $this->assertFalse($this->db->isWriteType($sql)); } public function testUpdateMultiReturning(): void @@ -245,33 +207,21 @@ public function testUpdateMultiReturning(): void RETURNING *; SQL; - if ($this->testReturning($this->db->DBDriver)) { - $this->assertFalse($this->db->isWriteType($sql)); - } else { - $this->assertTrue($this->db->isWriteType($sql)); - } + $this->assertFalse($this->db->isWriteType($sql)); } public function testUpdateWithOneReturning(): void { $sql = "WITH seqvals AS (SELECT '3' AS seqval) UPDATE my_table SET col1 = seqval FROM seqvals WHERE id = 2 RETURNING *;"; - if ($this->testReturning($this->db->DBDriver)) { - $this->assertFalse($this->db->isWriteType($sql)); - } else { - $this->assertTrue($this->db->isWriteType($sql)); - } + $this->assertFalse($this->db->isWriteType($sql)); } public function testUpdateWithOneReturningNoSpace(): void { $sql = "WITH seqvals AS (SELECT '3' AS seqval)UPDATE my_table SET col1 = seqval FROM seqvals WHERE id = 2 RETURNING *;"; - if ($this->testReturning($this->db->DBDriver)) { - $this->assertFalse($this->db->isWriteType($sql)); - } else { - $this->assertTrue($this->db->isWriteType($sql)); - } + $this->assertFalse($this->db->isWriteType($sql)); } public function testUpdateWithMultiReturning(): void @@ -285,11 +235,7 @@ public function testUpdateWithMultiReturning(): void RETURNING *; SQL; - if ($this->testReturning($this->db->DBDriver)) { - $this->assertFalse($this->db->isWriteType($sql)); - } else { - $this->assertTrue($this->db->isWriteType($sql)); - } + $this->assertFalse($this->db->isWriteType($sql)); } public function testDeleteBuilder(): void @@ -348,11 +294,7 @@ public function testDeleteOneReturning(): void { $sql = 'DELETE FROM my_table WHERE id = 2 RETURNING *;'; - if ($this->testReturning($this->db->DBDriver)) { - $this->assertFalse($this->db->isWriteType($sql)); - } else { - $this->assertTrue($this->db->isWriteType($sql)); - } + $this->assertFalse($this->db->isWriteType($sql)); } public function testDeleteMultiReturning(): void @@ -363,33 +305,21 @@ public function testDeleteMultiReturning(): void RETURNING *; SQL; - if ($this->testReturning($this->db->DBDriver)) { - $this->assertFalse($this->db->isWriteType($sql)); - } else { - $this->assertTrue($this->db->isWriteType($sql)); - } + $this->assertFalse($this->db->isWriteType($sql)); } public function testDeleteWithOneReturning(): void { $sql = "WITH seqvals AS (SELECT '3' AS seqval) DELETE FROM my_table JOIN seqvals ON col1 = seqval RETURNING *;"; - if ($this->testReturning($this->db->DBDriver)) { - $this->assertFalse($this->db->isWriteType($sql)); - } else { - $this->assertTrue($this->db->isWriteType($sql)); - } + $this->assertFalse($this->db->isWriteType($sql)); } public function testDeleteWithOneReturningNoSpace(): void { $sql = "WITH seqvals AS (SELECT '3' AS seqval)DELETE FROM my_table JOIN seqvals ON col1 = seqval RETURNING *;"; - if ($this->testReturning($this->db->DBDriver)) { - $this->assertFalse($this->db->isWriteType($sql)); - } else { - $this->assertTrue($this->db->isWriteType($sql)); - } + $this->assertFalse($this->db->isWriteType($sql)); } public function testDeleteWithMultiReturning(): void @@ -403,11 +333,7 @@ public function testDeleteWithMultiReturning(): void RETURNING *; SQL; - if ($this->testReturning($this->db->DBDriver)) { - $this->assertFalse($this->db->isWriteType($sql)); - } else { - $this->assertTrue($this->db->isWriteType($sql)); - } + $this->assertFalse($this->db->isWriteType($sql)); } public function testReplace(): void