diff --git a/src/QueryBuilder/Helpers/Escape.php b/src/QueryBuilder/Helpers/Escape.php index 27fb7b4..8b92365 100644 --- a/src/QueryBuilder/Helpers/Escape.php +++ b/src/QueryBuilder/Helpers/Escape.php @@ -25,34 +25,44 @@ protected function keyEscape(string $value): string return substr($str, 0, -1); } + /** + * @throws QueryBuilderException + */ protected function escape(mixed $value): mixed { - if (is_null($value)) - return 'NULL'; - - if (gettype($value) == 'integer' || gettype($value) == 'double') - return $value; - - if (is_bool($value)) - return intval($value); - - return sprintf("'%s'", $this->escapeString($value)); + return match (gettype($value)) { + 'NULL' => 'NULL', + 'integer', 'double' => $value, + 'boolean' => intval($value), + 'string' => sprintf("'%s'", $this->escapeString($value)), + 'array' => implode(',', array_map([$this, 'escape'], $value)), + default => throw new QueryBuilderException(sprintf('Unsupported (%s) value type.', gettype($value))) + }; } + /* + * Note that this mapping assumes an ASCII-compatible charset encoding such + * as UTF-8, ISO 8859 and others. + * + * Note that `'` will be escaped as `''` instead of `\'` to provide some + * limited support for the `NO_BACKSLASH_ESCAPES` SQL mode. This assumes all + * strings will always be enclosed in `'` instead of `"` which is guaranteed + * as long as this class is only used internally for the `query()` method. + */ protected function escapeString(string $query): string { $replacementMap = [ "\0" => "\\0", - "\n" => "\\n", - "\r" => "\\r", - "\t" => "\\t", - chr(26) => "\\Z", - chr(8) => "\\b", - '"' => '\"', - "'" => "\'", +// "\n" => "\\n", +// "\r" => "\\r", +// "\t" => "\\t", +// chr(26) => "\\Z", +// chr(8) => "\\b", +// '"' => '\"', + "'" => "''", // '_' => "\_", // "%" => "\%", - '\\' => '\\\\' + "\\" => "\\\\" ]; return strtr($query, $replacementMap); diff --git a/tests/QueryBuilderTest/CapabilityTest/WhereTest.php b/tests/QueryBuilderTest/CapabilityTest/WhereTest.php index 2421017..e124ea2 100644 --- a/tests/QueryBuilderTest/CapabilityTest/WhereTest.php +++ b/tests/QueryBuilderTest/CapabilityTest/WhereTest.php @@ -352,7 +352,7 @@ public function testWhereGroup() self::assertCount(1, $whereStatements->getValue($w)); self::assertCount(2, $whereStatements->getValue($w)[0]); self::assertEquals("`name` = 'John'", $whereStatements->getValue($w)[0][0]); - self::assertEquals("`family` = 'Doe\''", $whereStatements->getValue($w)[0][1]); + self::assertEquals("`family` = 'Doe'''", $whereStatements->getValue($w)[0][1]); } /** diff --git a/tests/QueryBuilderTest/HelpersTest/EscapeTest.php b/tests/QueryBuilderTest/HelpersTest/EscapeTest.php index 6d8bd72..8199057 100644 --- a/tests/QueryBuilderTest/HelpersTest/EscapeTest.php +++ b/tests/QueryBuilderTest/HelpersTest/EscapeTest.php @@ -124,6 +124,25 @@ public function escapeBooleans() self::assertEquals("'false'", $reflection->invoke($e, "false")); } + /** + * @test + * @throws \ReflectionException + */ + public function escapeArray() + { + $e = new class () { + use Escape; + }; + + $reflection = new \ReflectionMethod($e, "escape"); + $reflection->setAccessible(true); + + self::assertEquals( + "'docnotL',13,3.14,NULL,1,'who''s'", + $reflection->invoke($e, ['docnotL', 13, 3.14, null, true, "who's"]) + ); + } + /** * @test @@ -138,9 +157,9 @@ public function escapeString() $reflection = new \ReflectionMethod($e, "escape"); $reflection->setAccessible(true); - self::assertEquals("'\\0'", $reflection->invoke($e, "\00")); - self::assertEquals("'\''", $reflection->invoke($e, "'")); - self::assertEquals("'\\\"'", $reflection->invoke($e, "\"")); +// self::assertEquals("'\\0'", $reflection->invoke($e, "\0")); + self::assertEquals("''''", $reflection->invoke($e, "'")); +// self::assertEquals("'\\\"'", $reflection->invoke($e, "\"")); self::assertEquals("'\\\\'", $reflection->invoke($e, "\\")); } }