From 839cda3e5281d1fc2cdb034bded5d3555c332d91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 20 Aug 2021 11:21:33 +0200 Subject: [PATCH] Provide limited support for `NO_BACKSLASH_ESCAPES` SQL mode --- src/Io/Query.php | 17 ++++++++----- tests/Io/QueryTest.php | 9 +++---- tests/ResultQueryTest.php | 52 ++++++++++++++++++++++++++++++--------- 3 files changed, 55 insertions(+), 23 deletions(-) diff --git a/src/Io/Query.php b/src/Io/Query.php index 417408f..71ab90d 100644 --- a/src/Io/Query.php +++ b/src/Io/Query.php @@ -19,18 +19,23 @@ class Query * 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. + * * @var array * @see \React\MySQL\Commands\AuthenticateCommand::$charsetMap */ private $escapeChars = [ - "\x00" => "\\0", - "\r" => "\\r", - "\n" => "\\n", - "\t" => "\\t", + //"\x00" => "\\0", + //"\r" => "\\r", + //"\n" => "\\n", + //"\t" => "\\t", //"\b" => "\\b", //"\x1a" => "\\Z", - "'" => "\'", - '"' => '\"', + "'" => "''", + //'"' => '\"', "\\" => "\\\\", //"%" => "\\%", //"_" => "\\_", diff --git a/tests/Io/QueryTest.php b/tests/Io/QueryTest.php index f25df14..3a5831f 100644 --- a/tests/Io/QueryTest.php +++ b/tests/Io/QueryTest.php @@ -66,12 +66,9 @@ public function testEscapeChars() { $query = new Query(''); $this->assertEquals('\\\\', $query->escape('\\')); - $this->assertEquals('\"', $query->escape('"')); - $this->assertEquals("\'", $query->escape("'")); - $this->assertEquals("\\n", $query->escape("\n")); - $this->assertEquals("\\r", $query->escape("\r")); - $this->assertEquals("foo\\0bar", $query->escape("foo" . chr(0) . "bar")); + $this->assertEquals("''", $query->escape("'")); + $this->assertEquals("foo\0bar", $query->escape("foo" . chr(0) . "bar")); $this->assertEquals("n%3A", $query->escape("n%3A")); - //$this->assertEquals('§ä¨ì¥H¤U¤º®e\\\\§ä¨ì¥H¤U¤º®e', $query->escape('§ä¨ì¥H¤U¤º®e\\§ä¨ì¥H¤U¤º®e')); + $this->assertEquals('§ä¨ì¥H¤U¤º®e\\\\§ä¨ì¥H¤U¤º®e', $query->escape('§ä¨ì¥H¤U¤º®e\\§ä¨ì¥H¤U¤º®e')); } } diff --git a/tests/ResultQueryTest.php b/tests/ResultQueryTest.php index c267f74..3e2953a 100644 --- a/tests/ResultQueryTest.php +++ b/tests/ResultQueryTest.php @@ -32,42 +32,72 @@ public function provideValuesThatWillBeReturnedAsIs() 'hello?', 'FööBär', 'pile of 💩', - '<>&--\\\'";', - "\0\1\2\3\4\5\6\7\8\xff", + 'Dave\'s Diner', + 'Robert "Bobby"', + "first\r\nsecond", + 'C:\\\\Users\\', + '<>&--\'";', + "\0\1\2\3\4\5\6\7\10\xff", + implode('', range("\x00", "\x2F")) . implode('', range("\x7f", "\xFF")), '', null ]); } - public function provideValuesThatWillBeConvertedToString() + /** + * @dataProvider provideValuesThatWillBeReturnedAsIs + */ + public function testSelectStaticValueWillBeReturnedAsIs($value) { - return [ - [1, '1'], - [1.5, '1.5'], - [true, '1'], - [false, '0'] - ]; + $connection = $this->createConnection(Loop::get()); + + $expected = $value; + + $connection->query('select ?', [$value])->then(function (QueryResult $command) use ($expected) { + $this->assertCount(1, $command->resultRows); + $this->assertCount(1, $command->resultRows[0]); + $this->assertSame($expected, reset($command->resultRows[0])); + })->then(null, 'printf'); + + $connection->quit(); + Loop::run(); } /** * @dataProvider provideValuesThatWillBeReturnedAsIs */ - public function testSelectStaticValueWillBeReturnedAsIs($value) + public function testSelectStaticValueWillBeReturnedAsIsWithNoBackslashEscapesSqlMode($value) { + if (strpos($value, '\\') !== false) { + // TODO: strings such as '%\\' work as-is when string contains percent?! + $this->markTestIncomplete('Escaping backslash not supported when using NO_BACKSLASH_ESCAPES SQL mode'); + } + $connection = $this->createConnection(Loop::get()); $expected = $value; + $connection->query('SET SQL_MODE="NO_BACKSLASH_ESCAPES"'); $connection->query('select ?', [$value])->then(function (QueryResult $command) use ($expected) { $this->assertCount(1, $command->resultRows); $this->assertCount(1, $command->resultRows[0]); $this->assertSame($expected, reset($command->resultRows[0])); - }); + })->then(null, 'printf'); $connection->quit(); Loop::run(); } + public function provideValuesThatWillBeConvertedToString() + { + return [ + [1, '1'], + [1.5, '1.5'], + [true, '1'], + [false, '0'] + ]; + } + /** * @dataProvider provideValuesThatWillBeConvertedToString */