From d9cb8b4832c3888f5a020e35ad3b73914f5032db Mon Sep 17 00:00:00 2001 From: Tomasz Narloch Date: Mon, 10 Dec 2018 16:55:05 +0100 Subject: [PATCH 1/4] Add a new method quoteBinary with unit tests --- Tests/DatabaseSqliteCase.php | 112 +++++++++++++++++++ Tests/DatabaseSqlsrvCase.php | 12 +- Tests/DriverMysqlTest.php | 161 ++++++++++++++++++++++++++- Tests/DriverMysqliTest.php | 161 ++++++++++++++++++++++++++- Tests/DriverPgsqlTest.php | 167 +++++++++++++++++++++++++++- Tests/DriverSqliteTest.php | 161 +++++++++++++++++++++++++-- Tests/DriverSqlsrvTest.php | 137 ++++++++++++++++++++++- Tests/DriverTest.php | 22 ++++ Tests/Mock/Driver.php | 1 + Tests/Stubs/ddl.sql | 3 +- Tests/Stubs/mysql.sql | 1 + Tests/Stubs/postgresql.sql | 1 + src/DatabaseDriver.php | 29 +++++ src/Pgsql/PgsqlDriver.php | 33 ++++++ src/Postgresql/PostgresqlDriver.php | 1 + src/Sqlsrv/SqlsrvDriver.php | 15 +++ 16 files changed, 990 insertions(+), 27 deletions(-) create mode 100644 Tests/DatabaseSqliteCase.php diff --git a/Tests/DatabaseSqliteCase.php b/Tests/DatabaseSqliteCase.php new file mode 100644 index 000000000..bbb031203 --- /dev/null +++ b/Tests/DatabaseSqliteCase.php @@ -0,0 +1,112 @@ + 'sqlite'); + + /** + * This method is called before the first test of this test class is run. + * + * @return void + * + * @since 1.0 + */ + public static function setUpBeforeClass() + { + if (!class_exists('\\Joomla\\Database\\DatabaseDriver')) + { + static::markTestSkipped('The joomla/database package is not installed, cannot use this test case.'); + } + + // Make sure the driver is supported + if (!SqliteDriver::isSupported()) + { + static::markTestSkipped('The SQLite driver is not supported on this platform.'); + } + + // We always want the default database test case to use an SQLite memory database. + $options = array( + 'driver' => 'sqlite', + 'database' => ':memory:', + 'prefix' => 'jos_', + ); + + try + { + // Attempt to instantiate the driver. + static::$driver = DatabaseDriver::getInstance($options); + static::$driver->connect(); + + // Get the PDO instance for an SQLite memory database and load the test schema into it. + static::$driver->getConnection()->exec(file_get_contents(__DIR__ . '/Stubs/ddl.sql')); + } + catch (\RuntimeException $e) + { + static::$driver = null; + } + } + + /** + * This method is called after the last test of this test class is run. + * + * @return void + * + * @since 1.0 + */ + public static function tearDownAfterClass() + { + if (static::$driver !== null) + { + static::$driver->disconnect(); + static::$driver = null; + } + } + + /** + * Gets the data set to be loaded into the database during setup + * + * @return \PHPUnit_Extensions_Database_DataSet_XmlDataSet + * + * @since 1.0 + */ + protected function getDataSet() + { + return $this->createXMLDataSet(__DIR__ . '/Stubs/database.xml'); + } + + /** + * Returns the default database connection for running the tests. + * + * @return \PHPUnit_Extensions_Database_DB_IDatabaseConnection + * + * @since 1.0 + */ + protected function getConnection() + { + if (!is_null(static::$driver)) + { + return $this->createDefaultDBConnection(static::$driver->getConnection(), ':memory:'); + } + + return null; + } +} diff --git a/Tests/DatabaseSqlsrvCase.php b/Tests/DatabaseSqlsrvCase.php index f8fa6a5b1..9063b19b3 100644 --- a/Tests/DatabaseSqlsrvCase.php +++ b/Tests/DatabaseSqlsrvCase.php @@ -140,7 +140,17 @@ protected function getConnection() // Create the PDO object from the DSN and options. $pdo = new \PDO($dsn, self::$options['user'], self::$options['password']); - $pdo->exec('create table [jos_dbtest]([id] [int] IDENTITY(1,1) NOT NULL, [title] [nvarchar](50) NOT NULL, [start_date] [datetime] NOT NULL, [description] [nvarchar](max) NOT NULL, CONSTRAINT [PK_jos_dbtest_id] PRIMARY KEY CLUSTERED ([id] ASC) WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF))'); + $pdo->exec(" + IF OBJECT_ID('jos_dbtest', 'U') IS NOT NULL DROP TABLE [jos_dbtest]; + CREATE TABLE [jos_dbtest] ( + [id] [int] IDENTITY(1,1) NOT NULL, + [title] nvarchar(50) NOT NULL, + [start_date] datetime NOT NULL, + [description] nvarchar(max) NOT NULL, + [data] varbinary(max), + CONSTRAINT [PK_jos_dbtest_id] PRIMARY KEY CLUSTERED ([id] ASC) WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF) + );" + ); return $this->createDefaultDBConnection($pdo, self::$options['database']); } diff --git a/Tests/DriverMysqlTest.php b/Tests/DriverMysqlTest.php index 6fd0274f5..713f6d865 100644 --- a/Tests/DriverMysqlTest.php +++ b/Tests/DriverMysqlTest.php @@ -28,6 +28,22 @@ public function dataTestEscape() ); } + /** + * Data for the testQuoteBinary test. + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public function dataTestQuoteBinary() + { + return array( + array('DATA', "X'" . bin2hex('DATA') . "'"), + array("\x00\x01\x02\xff", "X'000102ff'"), + array("\x01\x01\x02\xff", "X'010102ff'"), + ); + } + /** * Data for the testQuoteName test. * @@ -128,6 +144,25 @@ public function testEscape($text, $extra, $expected) ); } + /** + * Test the quoteBinary method. + * + * @param string $data The binary quoted input string. + * + * @return void + * + * @dataProvider dataTestQuoteBinary + * @since __DEPLOY_VERSION__ + */ + public function testQuoteBinary($data, $expected) + { + $this->assertThat( + self::$driver->quoteBinary($data), + $this->equalTo($expected), + 'The binary data was not quoted properly' + ); + } + /** * Test the quoteName method. * @@ -261,7 +296,13 @@ public function testGetTableCreate() */ public function testGetTableColumns() { - $tableCol = array('id' => 'int unsigned', 'title' => 'varchar', 'start_date' => 'datetime', 'description' => 'text'); + $tableCol = array( + 'id' => 'int unsigned', + 'title' => 'varchar', + 'start_date' => 'datetime', + 'description' => 'text', + 'data' => 'blob', + ); $this->assertThat( self::$driver->getTableColumns('jos_dbtest'), @@ -314,6 +355,17 @@ public function testGetTableColumns() $description->Privileges = 'select,insert,update,references'; $description->Comment = ''; + $data = new \stdClass; + $data->Default = null; + $data->Field = 'data'; + $data->Type = 'blob'; + $data->Null = 'YES'; + $data->Key = ''; + $data->Collation = null; + $data->Extra = ''; + $data->Privileges = 'select,insert,update,references'; + $data->Comment = ''; + $this->assertThat( self::$driver->getTableColumns('jos_dbtest', false), $this->equalTo( @@ -321,7 +373,8 @@ public function testGetTableColumns() 'id' => $id, 'title' => $title, 'start_date' => $start_date, - 'description' => $description + 'description' => $description, + 'data' => $data, ) ), __LINE__ @@ -474,6 +527,7 @@ public function testLoadObject() $objCompare->title = 'Testing3'; $objCompare->start_date = '1980-04-18 00:00:00'; $objCompare->description = 'three'; + $objCompare->data = null; $this->assertThat($result, $this->equalTo($objCompare), __LINE__); } @@ -501,6 +555,7 @@ public function testLoadObjectList() $objCompare->title = 'Testing'; $objCompare->start_date = '1980-04-18 00:00:00'; $objCompare->description = 'one'; + $objCompare->data = null; $expected[] = clone $objCompare; @@ -509,6 +564,7 @@ public function testLoadObjectList() $objCompare->title = 'Testing2'; $objCompare->start_date = '1980-04-18 00:00:00'; $objCompare->description = 'one'; + $objCompare->data = null; $expected[] = clone $objCompare; @@ -517,6 +573,7 @@ public function testLoadObjectList() $objCompare->title = 'Testing3'; $objCompare->start_date = '1980-04-18 00:00:00'; $objCompare->description = 'three'; + $objCompare->data = null; $expected[] = clone $objCompare; @@ -525,6 +582,7 @@ public function testLoadObjectList() $objCompare->title = 'Testing4'; $objCompare->start_date = '1980-04-18 00:00:00'; $objCompare->description = 'four'; + $objCompare->data = null; $expected[] = clone $objCompare; @@ -567,7 +625,7 @@ public function testLoadRow() self::$driver->setQuery($query); $result = self::$driver->loadRow(); - $expected = array(3, 'Testing3', '1980-04-18 00:00:00', 'three'); + $expected = array(3, 'Testing3', '1980-04-18 00:00:00', 'three', null); $this->assertThat($result, $this->equalTo($expected), __LINE__); } @@ -588,8 +646,101 @@ public function testLoadRowList() self::$driver->setQuery($query); $result = self::$driver->loadRowList(); - $expected = array(array(1, 'Testing', '1980-04-18 00:00:00', 'one'), array(2, 'Testing2', '1980-04-18 00:00:00', 'one')); + $expected = array( + array(1, 'Testing', '1980-04-18 00:00:00', 'one', null), + array(2, 'Testing2', '1980-04-18 00:00:00', 'one', null) + ); + + $this->assertThat($result, $this->equalTo($expected), __LINE__); + } + + /** + * Test quoteBinary and decodeBinary methods + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testLoadBinary() + { + // Add binary data with null byte + $query = self::$driver->getQuery(true) + ->update('jos_dbtest') + ->set('data = ' . self::$driver->quoteBinary("\x00\x01\x02\xff")) + ->where('id = 3'); + + self::$driver->setQuery($query)->execute(); + + // Add binary data with invalid UTF-8 + $query = self::$driver->getQuery(true) + ->update('jos_dbtest') + ->set('data = ' . self::$driver->quoteBinary("\x01\x01\x02\xff")) + ->where('id = 4'); + + self::$driver->setQuery($query)->execute(); + + $selectRow3 = self::$driver->getQuery(true) + ->select('id') + ->from('jos_dbtest') + ->where('data = ' . self::$driver->quoteBinary("\x00\x01\x02\xff")); + + $selectRow4 = self::$driver->getQuery(true) + ->select('id') + ->from('jos_dbtest') + ->where('data = '. self::$driver->quoteBinary("\x01\x01\x02\xff")); + + $result = self::$driver->setQuery($selectRow3)->loadResult(); + $this->assertThat($result, $this->equalTo(3), __LINE__); + + $result = self::$driver->setQuery($selectRow4)->loadResult(); + $this->assertThat($result, $this->equalTo(4), __LINE__); + + $selectRows = self::$driver->getQuery(true) + ->select('data') + ->from('jos_dbtest') + ->order('id'); + + // Test loadColumn + $result = self::$driver->setQuery($selectRows)->loadColumn(); + + foreach ($result as $i => $v) + { + $result[$i] = self::$driver->decodeBinary($v); + } + + $expected = array(null, null, "\x00\x01\x02\xff", "\x01\x01\x02\xff"); + $this->assertThat($result, $this->equalTo($expected), __LINE__); + + // Test loadAssocList + $result = self::$driver->setQuery($selectRows)->loadAssocList(); + + foreach ($result as $i => $v) + { + $result[$i]['data'] = self::$driver->decodeBinary($v['data']); + } + $expected = array( + array('data' => null), + array('data' => null), + array('data' => "\x00\x01\x02\xff"), + array('data' => "\x01\x01\x02\xff"), + ); + $this->assertThat($result, $this->equalTo($expected), __LINE__); + + // Test loadObjectList + $result = self::$driver->setQuery($selectRows)->loadObjectList(); + + foreach ($result as $i => $v) + { + $result[$i]->data = self::$driver->decodeBinary($v->data); + } + + $expected = array( + (object) array('data' => null), + (object) array('data' => null), + (object) array('data' => "\x00\x01\x02\xff"), + (object) array('data' => "\x01\x01\x02\xff"), + ); $this->assertThat($result, $this->equalTo($expected), __LINE__); } @@ -683,7 +834,7 @@ public function testTransactionCommit() self::$driver->setQuery($queryCheck); $result = self::$driver->loadRow(); - $expected = array('6', 'testTitle', '1970-01-01 00:00:00', 'testDescription'); + $expected = array('6', 'testTitle', '1970-01-01 00:00:00', 'testDescription', null); $this->assertThat($result, $this->equalTo($expected), __LINE__); } diff --git a/Tests/DriverMysqliTest.php b/Tests/DriverMysqliTest.php index 05f5cdb81..2d9936489 100644 --- a/Tests/DriverMysqliTest.php +++ b/Tests/DriverMysqliTest.php @@ -28,6 +28,22 @@ public function dataTestEscape() ); } + /** + * Data for the testQuoteBinary test. + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public function dataTestQuoteBinary() + { + return array( + array('DATA', "X'" . bin2hex('DATA') . "'"), + array("\x00\x01\x02\xff", "X'000102ff'"), + array("\x01\x01\x02\xff", "X'010102ff'"), + ); + } + /** * Data for the testQuoteName test. * @@ -117,6 +133,25 @@ public function testEscape($text, $extra, $expected) ); } + /** + * Test the quoteBinary method. + * + * @param string $data The binary quoted input string. + * + * @return void + * + * @dataProvider dataTestQuoteBinary + * @since __DEPLOY_VERSION__ + */ + public function testQuoteBinary($data, $expected) + { + $this->assertThat( + self::$driver->quoteBinary($data), + $this->equalTo($expected), + 'The binary data was not quoted properly' + ); + } + /** * Test the quoteName method. * @@ -250,7 +285,13 @@ public function testGetTableCreate() */ public function testGetTableColumns() { - $tableCol = array('id' => 'int unsigned', 'title' => 'varchar', 'start_date' => 'datetime', 'description' => 'text'); + $tableCol = array( + 'id' => 'int unsigned', + 'title' => 'varchar', + 'start_date' => 'datetime', + 'description' => 'text', + 'data' => 'blob', + ); $this->assertThat( self::$driver->getTableColumns('jos_dbtest'), @@ -303,6 +344,17 @@ public function testGetTableColumns() $description->Privileges = 'select,insert,update,references'; $description->Comment = ''; + $data = new \stdClass; + $data->Default = null; + $data->Field = 'data'; + $data->Type = 'blob'; + $data->Null = 'YES'; + $data->Key = ''; + $data->Collation = null; + $data->Extra = ''; + $data->Privileges = 'select,insert,update,references'; + $data->Comment = ''; + $this->assertThat( self::$driver->getTableColumns('jos_dbtest', false), $this->equalTo( @@ -310,7 +362,8 @@ public function testGetTableColumns() 'id' => $id, 'title' => $title, 'start_date' => $start_date, - 'description' => $description + 'description' => $description, + 'data' => $data, ) ), __LINE__ @@ -475,6 +528,7 @@ public function testLoadObject() $objCompare->title = 'Testing3'; $objCompare->start_date = '1980-04-18 00:00:00'; $objCompare->description = 'three'; + $objCompare->data = null; $this->assertThat($result, $this->equalTo($objCompare), __LINE__); } @@ -502,6 +556,7 @@ public function testLoadObjectList() $objCompare->title = 'Testing'; $objCompare->start_date = '1980-04-18 00:00:00'; $objCompare->description = 'one'; + $objCompare->data = null; $expected[] = clone $objCompare; @@ -510,6 +565,7 @@ public function testLoadObjectList() $objCompare->title = 'Testing2'; $objCompare->start_date = '1980-04-18 00:00:00'; $objCompare->description = 'one'; + $objCompare->data = null; $expected[] = clone $objCompare; @@ -518,6 +574,7 @@ public function testLoadObjectList() $objCompare->title = 'Testing3'; $objCompare->start_date = '1980-04-18 00:00:00'; $objCompare->description = 'three'; + $objCompare->data = null; $expected[] = clone $objCompare; @@ -526,6 +583,7 @@ public function testLoadObjectList() $objCompare->title = 'Testing4'; $objCompare->start_date = '1980-04-18 00:00:00'; $objCompare->description = 'four'; + $objCompare->data = null; $expected[] = clone $objCompare; @@ -568,7 +626,7 @@ public function testLoadRow() self::$driver->setQuery($query); $result = self::$driver->loadRow(); - $expected = array(3, 'Testing3', '1980-04-18 00:00:00', 'three'); + $expected = array(3, 'Testing3', '1980-04-18 00:00:00', 'three', null); $this->assertThat($result, $this->equalTo($expected), __LINE__); } @@ -589,8 +647,101 @@ public function testLoadRowList() self::$driver->setQuery($query); $result = self::$driver->loadRowList(); - $expected = array(array(1, 'Testing', '1980-04-18 00:00:00', 'one'), array(2, 'Testing2', '1980-04-18 00:00:00', 'one')); + $expected = array( + array(1, 'Testing', '1980-04-18 00:00:00', 'one', null), + array(2, 'Testing2', '1980-04-18 00:00:00', 'one', null) + ); + + $this->assertThat($result, $this->equalTo($expected), __LINE__); + } + + /** + * Test quoteBinary and decodeBinary methods + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testLoadBinary() + { + // Add binary data with null byte + $query = self::$driver->getQuery(true) + ->update('jos_dbtest') + ->set('data = ' . self::$driver->quoteBinary("\x00\x01\x02\xff")) + ->where('id = 3'); + + self::$driver->setQuery($query)->execute(); + + // Add binary data with invalid UTF-8 + $query = self::$driver->getQuery(true) + ->update('jos_dbtest') + ->set('data = ' . self::$driver->quoteBinary("\x01\x01\x02\xff")) + ->where('id = 4'); + + self::$driver->setQuery($query)->execute(); + + $selectRow3 = self::$driver->getQuery(true) + ->select('id') + ->from('jos_dbtest') + ->where('data = ' . self::$driver->quoteBinary("\x00\x01\x02\xff")); + + $selectRow4 = self::$driver->getQuery(true) + ->select('id') + ->from('jos_dbtest') + ->where('data = '. self::$driver->quoteBinary("\x01\x01\x02\xff")); + + $result = self::$driver->setQuery($selectRow3)->loadResult(); + $this->assertThat($result, $this->equalTo(3), __LINE__); + + $result = self::$driver->setQuery($selectRow4)->loadResult(); + $this->assertThat($result, $this->equalTo(4), __LINE__); + + $selectRows = self::$driver->getQuery(true) + ->select('data') + ->from('jos_dbtest') + ->order('id'); + + // Test loadColumn + $result = self::$driver->setQuery($selectRows)->loadColumn(); + + foreach ($result as $i => $v) + { + $result[$i] = self::$driver->decodeBinary($v); + } + + $expected = array(null, null, "\x00\x01\x02\xff", "\x01\x01\x02\xff"); + $this->assertThat($result, $this->equalTo($expected), __LINE__); + + // Test loadAssocList + $result = self::$driver->setQuery($selectRows)->loadAssocList(); + + foreach ($result as $i => $v) + { + $result[$i]['data'] = self::$driver->decodeBinary($v['data']); + } + $expected = array( + array('data' => null), + array('data' => null), + array('data' => "\x00\x01\x02\xff"), + array('data' => "\x01\x01\x02\xff"), + ); + $this->assertThat($result, $this->equalTo($expected), __LINE__); + + // Test loadObjectList + $result = self::$driver->setQuery($selectRows)->loadObjectList(); + + foreach ($result as $i => $v) + { + $result[$i]->data = self::$driver->decodeBinary($v->data); + } + + $expected = array( + (object) array('data' => null), + (object) array('data' => null), + (object) array('data' => "\x00\x01\x02\xff"), + (object) array('data' => "\x01\x01\x02\xff"), + ); $this->assertThat($result, $this->equalTo($expected), __LINE__); } @@ -715,7 +866,7 @@ public function testTransactionCommit() self::$driver->setQuery($queryCheck); $result = self::$driver->loadRow(); - $expected = array('6', 'testTitle', '1970-01-01 00:00:00', 'testDescription'); + $expected = array('6', 'testTitle', '1970-01-01 00:00:00', 'testDescription', null); $this->assertThat($result, $this->equalTo($expected), __LINE__); } diff --git a/Tests/DriverPgsqlTest.php b/Tests/DriverPgsqlTest.php index 5ec7a57a1..a72f15834 100644 --- a/Tests/DriverPgsqlTest.php +++ b/Tests/DriverPgsqlTest.php @@ -31,6 +31,22 @@ public function dataTestEscape() array("\'%_abc123", true, '\\\\\'\'%_abc123')); } + /** + * Data for the testQuoteBinary test. + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public function dataTestQuoteBinary() + { + return array( + array('DATA', "decode('" . bin2hex('DATA') . "', 'hex')"), + array("\x00\x01\x02\xff", "decode('000102ff', 'hex')"), + array("\x01\x01\x02\xff", "decode('010102ff', 'hex')"), + ); + } + /** * Data for the testGetEscaped test, proxies of escape, so same data test. * @@ -197,6 +213,25 @@ public function testEscape($text, $extra, $result) ); } + /** + * Test the quoteBinary method. + * + * @param string $data The binary quoted input string. + * + * @return void + * + * @dataProvider dataTestQuoteBinary + * @since __DEPLOY_VERSION__ + */ + public function testQuoteBinary($data, $expected) + { + $this->assertThat( + self::$driver->quoteBinary($data), + $this->equalTo($expected), + 'The binary data was not quoted properly' + ); + } + /** * Test getAffectedRows method. * @@ -273,7 +308,13 @@ public function testGetTableCreate() */ public function testGetTableColumns() { - $tableCol = array('id' => 'integer', 'title' => 'character varying', 'start_date' => 'timestamp without time zone', 'description' => 'text'); + $tableCol = array( + 'id' => 'integer', + 'title' => 'character varying', + 'start_date' => 'timestamp without time zone', + 'description' => 'text', + 'data' => 'bytea', + ); $this->assertThat(self::$driver->getTableColumns('jos_dbtest'), $this->equalTo($tableCol), __LINE__); @@ -318,9 +359,27 @@ public function testGetTableColumns() $description->Default = null; $description->comments = ''; + $data = new \stdClass; + $data->column_name = 'data'; + $data->Field = 'data'; + $data->type = 'bytea'; + $data->Type = 'bytea'; + $data->null = 'YES'; + $data->Null = 'YES'; + $data->Default = null; + $data->comments = ''; + $this->assertThat( self::$driver->getTableColumns('jos_dbtest', false), - $this->equalTo(array('id' => $id, 'title' => $title, 'start_date' => $start_date, 'description' => $description)), + $this->equalTo( + array( + 'id' => $id, + 'title' => $title, + 'start_date' => $start_date, + 'description' => $description, + 'data' => $data, + ) + ), __LINE__ ); } @@ -629,6 +688,7 @@ public function testLoadObject() $objCompare->title = 'Testing3'; $objCompare->start_date = '1980-04-18 00:00:00'; $objCompare->description = 'three'; + $objCompare->data = null; $this->assertThat($result, $this->equalTo($objCompare), __LINE__); } @@ -656,6 +716,7 @@ public function testLoadObjectList() $objCompare->title = 'Testing'; $objCompare->start_date = '1980-04-18 00:00:00'; $objCompare->description = 'one'; + $objCompare->data = null; $expected[] = clone $objCompare; @@ -664,6 +725,7 @@ public function testLoadObjectList() $objCompare->title = 'Testing2'; $objCompare->start_date = '1980-04-18 00:00:00'; $objCompare->description = 'one'; + $objCompare->data = null; $expected[] = clone $objCompare; @@ -672,6 +734,7 @@ public function testLoadObjectList() $objCompare->title = 'Testing3'; $objCompare->start_date = '1980-04-18 00:00:00'; $objCompare->description = 'three'; + $objCompare->data = null; $expected[] = clone $objCompare; @@ -680,6 +743,7 @@ public function testLoadObjectList() $objCompare->title = 'Testing4'; $objCompare->start_date = '1980-04-18 00:00:00'; $objCompare->description = 'four'; + $objCompare->data = null; $expected[] = clone $objCompare; @@ -722,7 +786,7 @@ public function testLoadRow() self::$driver->setQuery($query); $result = self::$driver->loadRow(); - $expected = array(3, 'Testing3', '1980-04-18 00:00:00', 'three'); + $expected = array(3, 'Testing3', '1980-04-18 00:00:00', 'three', null); $this->assertThat($result, $this->equalTo($expected), __LINE__); } @@ -743,8 +807,101 @@ public function testLoadRowList() self::$driver->setQuery($query); $result = self::$driver->loadRowList(); - $expected = array(array(1, 'Testing', '1980-04-18 00:00:00', 'one'), array(2, 'Testing2', '1980-04-18 00:00:00', 'one')); + $expected = array( + array(1, 'Testing', '1980-04-18 00:00:00', 'one', null), + array(2, 'Testing2', '1980-04-18 00:00:00', 'one', null) + ); + + $this->assertThat($result, $this->equalTo($expected), __LINE__); + } + + /** + * Test quoteBinary and decodeBinary methods + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testLoadBinary() + { + // Add binary data with null byte + $query = self::$driver->getQuery(true) + ->update('jos_dbtest') + ->set('data = ' . self::$driver->quoteBinary("\x00\x01\x02\xff")) + ->where('id = 3'); + + self::$driver->setQuery($query)->execute(); + + // Add binary data with invalid UTF-8 + $query = self::$driver->getQuery(true) + ->update('jos_dbtest') + ->set('data = ' . self::$driver->quoteBinary("\x01\x01\x02\xff")) + ->where('id = 4'); + + self::$driver->setQuery($query)->execute(); + + $selectRow3 = self::$driver->getQuery(true) + ->select('id') + ->from('jos_dbtest') + ->where('data = ' . self::$driver->quoteBinary("\x00\x01\x02\xff")); + + $selectRow4 = self::$driver->getQuery(true) + ->select('id') + ->from('jos_dbtest') + ->where('data = '. self::$driver->quoteBinary("\x01\x01\x02\xff")); + + $result = self::$driver->setQuery($selectRow3)->loadResult(); + $this->assertThat($result, $this->equalTo(3), __LINE__); + + $result = self::$driver->setQuery($selectRow4)->loadResult(); + $this->assertThat($result, $this->equalTo(4), __LINE__); + $selectRows = self::$driver->getQuery(true) + ->select('data') + ->from('jos_dbtest') + ->order('id'); + + // Test loadColumn + $result = self::$driver->setQuery($selectRows)->loadColumn(); + + foreach ($result as $i => $v) + { + $result[$i] = self::$driver->decodeBinary($v); + } + + $expected = array(null, null, "\x00\x01\x02\xff", "\x01\x01\x02\xff"); + $this->assertThat($result, $this->equalTo($expected), __LINE__); + + // Test loadAssocList + $result = self::$driver->setQuery($selectRows)->loadAssocList(); + + foreach ($result as $i => $v) + { + $result[$i]['data'] = self::$driver->decodeBinary($v['data']); + } + + $expected = array( + array('data' => null), + array('data' => null), + array('data' => "\x00\x01\x02\xff"), + array('data' => "\x01\x01\x02\xff"), + ); + $this->assertThat($result, $this->equalTo($expected), __LINE__); + + // Test loadObjectList + $result = self::$driver->setQuery($selectRows)->loadObjectList(); + + foreach ($result as $i => $v) + { + $result[$i]->data = self::$driver->decodeBinary($v->data); + } + + $expected = array( + (object) array('data' => null), + (object) array('data' => null), + (object) array('data' => "\x00\x01\x02\xff"), + (object) array('data' => "\x01\x01\x02\xff"), + ); $this->assertThat($result, $this->equalTo($expected), __LINE__); } @@ -920,7 +1077,7 @@ public function testTransactionCommit() self::$driver->setQuery($queryCheck); $result = self::$driver->loadRow(); - $expected = array(6, 'testTitle', '1970-01-01 00:00:00', 'testDescription'); + $expected = array(6, 'testTitle', '1970-01-01 00:00:00', 'testDescription', null); $this->assertThat($result, $this->equalTo($expected), __LINE__); } diff --git a/Tests/DriverSqliteTest.php b/Tests/DriverSqliteTest.php index 6685ba3e6..9ae9d3b81 100644 --- a/Tests/DriverSqliteTest.php +++ b/Tests/DriverSqliteTest.php @@ -6,14 +6,12 @@ namespace Joomla\Database\Tests; -use Joomla\Test\TestDatabase; - /** * Test class for Joomla\Database\Sqlite\SqliteDriver. * * @since 1.0 */ -class DriverSqliteTest extends TestDatabase +class DriverSqliteTest extends DatabaseSqliteCase { /** * Data for the testEscape test. @@ -31,6 +29,22 @@ public function dataTestEscape() ); } + /** + * Data for the testQuoteBinary test. + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public function dataTestQuoteBinary() + { + return array( + array('DATA', "X'" . bin2hex('DATA') . "'"), + array("\x00\x01\x02\xff", "X'000102ff'"), + array("\x01\x01\x02\xff", "X'010102ff'"), + ); + } + /** * Data for the testQuoteName test. * @@ -120,6 +134,25 @@ public function testEscape($text, $extra, $expected) ); } + /** + * Test the quoteBinary method. + * + * @param string $data The binary quoted input string. + * + * @return void + * + * @dataProvider dataTestQuoteBinary + * @since __DEPLOY_VERSION__ + */ + public function testQuoteBinary($data, $expected) + { + $this->assertThat( + self::$driver->quoteBinary($data), + $this->equalTo($expected), + 'The binary data was not quoted properly' + ); + } + /** * Test the quoteName method. * @@ -249,7 +282,13 @@ public function testGetTableCreate() */ public function testGetTableColumns() { - $tableCol = array('id' => 'INTEGER', 'title' => 'TEXT', 'start_date' => 'TEXT', 'description' => 'TEXT'); + $tableCol = array( + 'id' => 'INTEGER', + 'title' => 'TEXT', + 'start_date' => 'TEXT', + 'description' => 'TEXT', + 'data' => 'BLOB', + ); $this->assertThat( self::$driver->getTableColumns('jos_dbtest'), @@ -286,6 +325,13 @@ public function testGetTableColumns() $description->Null = 'NO'; $description->Key = ''; + $data = new \stdClass; + $data->Default = null; + $data->Field = 'data'; + $data->Type = 'BLOB'; + $data->Null = 'YES'; + $data->Key = ''; + $this->assertThat( self::$driver->getTableColumns('jos_dbtest', false), $this->equalTo( @@ -293,7 +339,8 @@ public function testGetTableColumns() 'id' => $id, 'title' => $title, 'start_date' => $start_date, - 'description' => $description + 'description' => $description, + 'data' => $data, ) ), __LINE__ @@ -470,6 +517,7 @@ public function testLoadObject() $objCompare->title = 'Testing3'; $objCompare->start_date = '1980-04-18 00:00:00'; $objCompare->description = 'three'; + $objCompare->data = null; $this->assertThat($result, $this->equalTo($objCompare), __LINE__); } @@ -497,6 +545,7 @@ public function testLoadObjectList() $objCompare->title = 'Testing'; $objCompare->start_date = '1980-04-18 00:00:00'; $objCompare->description = 'one'; + $objCompare->data = null; $expected[] = clone $objCompare; @@ -505,6 +554,7 @@ public function testLoadObjectList() $objCompare->title = 'Testing2'; $objCompare->start_date = '1980-04-18 00:00:00'; $objCompare->description = 'one'; + $objCompare->data = null; $expected[] = clone $objCompare; @@ -513,6 +563,7 @@ public function testLoadObjectList() $objCompare->title = 'Testing3'; $objCompare->start_date = '1980-04-18 00:00:00'; $objCompare->description = 'three'; + $objCompare->data = null; $expected[] = clone $objCompare; @@ -521,6 +572,7 @@ public function testLoadObjectList() $objCompare->title = 'Testing4'; $objCompare->start_date = '1980-04-18 00:00:00'; $objCompare->description = 'four'; + $objCompare->data = null; $expected[] = clone $objCompare; @@ -563,7 +615,7 @@ public function testLoadRow() self::$driver->setQuery($query); $result = self::$driver->loadRow(); - $expected = array(3, 'Testing3', '1980-04-18 00:00:00', 'three'); + $expected = array(3, 'Testing3', '1980-04-18 00:00:00', 'three', null); $this->assertThat($result, $this->equalTo($expected), __LINE__); } @@ -584,8 +636,101 @@ public function testLoadRowList() self::$driver->setQuery($query); $result = self::$driver->loadRowList(); - $expected = array(array(1, 'Testing', '1980-04-18 00:00:00', 'one'), array(2, 'Testing2', '1980-04-18 00:00:00', 'one')); + $expected = array( + array(1, 'Testing', '1980-04-18 00:00:00', 'one', null), + array(2, 'Testing2', '1980-04-18 00:00:00', 'one', null) + ); + + $this->assertThat($result, $this->equalTo($expected), __LINE__); + } + /** + * Test quoteBinary and decodeBinary methods + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testLoadBinary() + { + // Add binary data with null byte + $query = self::$driver->getQuery(true) + ->update('jos_dbtest') + ->set('data = ' . self::$driver->quoteBinary("\x00\x01\x02\xff")) + ->where('id = 3'); + + self::$driver->setQuery($query)->execute(); + + // Add binary data with invalid UTF-8 + $query = self::$driver->getQuery(true) + ->update('jos_dbtest') + ->set('data = ' . self::$driver->quoteBinary("\x01\x01\x02\xff")) + ->where('id = 4'); + + self::$driver->setQuery($query)->execute(); + + $selectRow3 = self::$driver->getQuery(true) + ->select('id') + ->from('jos_dbtest') + ->where('data = ' . self::$driver->quoteBinary("\x00\x01\x02\xff")); + + $selectRow4 = self::$driver->getQuery(true) + ->select('id') + ->from('jos_dbtest') + ->where('data = '. self::$driver->quoteBinary("\x01\x01\x02\xff")); + + $result = self::$driver->setQuery($selectRow3)->loadResult(); + $this->assertThat($result, $this->equalTo(3), __LINE__); + + $result = self::$driver->setQuery($selectRow4)->loadResult(); + $this->assertThat($result, $this->equalTo(4), __LINE__); + + $selectRows = self::$driver->getQuery(true) + ->select('data') + ->from('jos_dbtest') + ->order('id'); + + // Test loadColumn + $result = self::$driver->setQuery($selectRows)->loadColumn(); + + foreach ($result as $i => $v) + { + $result[$i] = self::$driver->decodeBinary($v); + } + + $expected = array(null, null, "\x00\x01\x02\xff", "\x01\x01\x02\xff"); + $this->assertThat($result, $this->equalTo($expected), __LINE__); + + // Test loadAssocList + $result = self::$driver->setQuery($selectRows)->loadAssocList(); + + foreach ($result as $i => $v) + { + $result[$i]['data'] = self::$driver->decodeBinary($v['data']); + } + + $expected = array( + array('data' => null), + array('data' => null), + array('data' => "\x00\x01\x02\xff"), + array('data' => "\x01\x01\x02\xff"), + ); + $this->assertThat($result, $this->equalTo($expected), __LINE__); + + // Test loadObjectList + $result = self::$driver->setQuery($selectRows)->loadObjectList(); + + foreach ($result as $i => $v) + { + $result[$i]->data = self::$driver->decodeBinary($v->data); + } + + $expected = array( + (object) array('data' => null), + (object) array('data' => null), + (object) array('data' => "\x00\x01\x02\xff"), + (object) array('data' => "\x01\x01\x02\xff"), + ); $this->assertThat($result, $this->equalTo($expected), __LINE__); } @@ -677,7 +822,7 @@ public function testTransactionCommit() self::$driver->setQuery($queryCheck); $result = self::$driver->loadRow(); - $expected = array('6', 'testTitle', '1970-01-01', 'testDescription'); + $expected = array('6', 'testTitle', '1970-01-01', 'testDescription', null); $this->assertThat($result, $this->equalTo($expected), __LINE__); } diff --git a/Tests/DriverSqlsrvTest.php b/Tests/DriverSqlsrvTest.php index 9d196b084..6ab64b39a 100644 --- a/Tests/DriverSqlsrvTest.php +++ b/Tests/DriverSqlsrvTest.php @@ -30,6 +30,22 @@ public function dataTestEscape() ); } + /** + * Data for the testQuoteBinary test. + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public function dataTestQuoteBinary() + { + return array( + array('DATA', "0x" . bin2hex('DATA')), + array("\x00\x01\x02\xff", "0x000102ff"), + array("\x01\x01\x02\xff", "0x010102ff"), + ); + } + /** * Data for the testQuoteName test. * @@ -111,6 +127,25 @@ public function testEscape($text, $extra, $expected) ); } + /** + * Test the quoteBinary method. + * + * @param string $data The binary quoted input string. + * + * @return void + * + * @dataProvider dataTestQuoteBinary + * @since __DEPLOY_VERSION__ + */ + public function testQuoteBinary($data, $expected) + { + $this->assertThat( + self::$driver->quoteBinary($data), + $this->equalTo($expected), + 'The binary data was not quoted properly' + ); + } + /** * Test the quoteName method. * @@ -386,6 +421,7 @@ public function testLoadObject() $objCompare->title = 'Testing3'; $objCompare->start_date = '1980-04-18 00:00:00.000'; $objCompare->description = 'three'; + $objCompare->data = null; $this->assertThat($result, $this->equalTo($objCompare), __LINE__); } @@ -413,6 +449,7 @@ public function testLoadObjectList() $objCompare->title = 'Testing'; $objCompare->start_date = '1980-04-18 00:00:00.000'; $objCompare->description = 'one'; + $objCompare->data = null; $expected[] = clone $objCompare; @@ -421,6 +458,7 @@ public function testLoadObjectList() $objCompare->title = 'Testing2'; $objCompare->start_date = '1980-04-18 00:00:00.000'; $objCompare->description = 'one'; + $objCompare->data = null; $expected[] = clone $objCompare; @@ -429,6 +467,7 @@ public function testLoadObjectList() $objCompare->title = 'Testing3'; $objCompare->start_date = '1980-04-18 00:00:00.000'; $objCompare->description = 'three'; + $objCompare->data = null; $expected[] = clone $objCompare; @@ -437,6 +476,7 @@ public function testLoadObjectList() $objCompare->title = 'Testing4'; $objCompare->start_date = '1980-04-18 00:00:00.000'; $objCompare->description = 'four'; + $objCompare->data = null; $expected[] = clone $objCompare; @@ -479,7 +519,7 @@ public function testLoadRow() self::$driver->setQuery($query); $result = self::$driver->loadRow(); - $expected = array(3, 'Testing3', '1980-04-18 00:00:00.000', 'three'); + $expected = array(3, 'Testing3', '1980-04-18 00:00:00.000', 'three', null); $this->assertThat($result, $this->equalTo($expected), __LINE__); } @@ -500,8 +540,101 @@ public function testLoadRowList() self::$driver->setQuery($query); $result = self::$driver->loadRowList(); - $expected = array(array(1, 'Testing', '1980-04-18 00:00:00.000', 'one'), array(2, 'Testing2', '1980-04-18 00:00:00.000', 'one')); + $expected = array( + array(1, 'Testing', '1980-04-18 00:00:00.000', 'one', null), + array(2, 'Testing2', '1980-04-18 00:00:00.000', 'one', null) + ); + + $this->assertThat($result, $this->equalTo($expected), __LINE__); + } + + /** + * Test quoteBinary and decodeBinary methods + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testLoadBinary() + { + // Add binary data with null byte + $query = self::$driver->getQuery(true) + ->update('jos_dbtest') + ->set('data = ' . self::$driver->quoteBinary("\x00\x01\x02\xff")) + ->where('id = 3'); + + self::$driver->setQuery($query)->execute(); + + // Add binary data with invalid UTF-8 + $query = self::$driver->getQuery(true) + ->update('jos_dbtest') + ->set('data = ' . self::$driver->quoteBinary("\x01\x01\x02\xff")) + ->where('id = 4'); + + self::$driver->setQuery($query)->execute(); + + $selectRow3 = self::$driver->getQuery(true) + ->select('id') + ->from('jos_dbtest') + ->where('data = ' . self::$driver->quoteBinary("\x00\x01\x02\xff")); + + $selectRow4 = self::$driver->getQuery(true) + ->select('id') + ->from('jos_dbtest') + ->where('data = '. self::$driver->quoteBinary("\x01\x01\x02\xff")); + + $result = self::$driver->setQuery($selectRow3)->loadResult(); + $this->assertThat($result, $this->equalTo(3), __LINE__); + $result = self::$driver->setQuery($selectRow4)->loadResult(); + $this->assertThat($result, $this->equalTo(4), __LINE__); + + $selectRows = self::$driver->getQuery(true) + ->select('data') + ->from('jos_dbtest') + ->order('id'); + + // Test loadColumn + $result = self::$driver->setQuery($selectRows)->loadColumn(); + + foreach ($result as $i => $v) + { + $result[$i] = self::$driver->decodeBinary($v); + } + + $expected = array(null, null, "\x00\x01\x02\xff", "\x01\x01\x02\xff"); + $this->assertThat($result, $this->equalTo($expected), __LINE__); + + // Test loadAssocList + $result = self::$driver->setQuery($selectRows)->loadAssocList(); + + foreach ($result as $i => $v) + { + $result[$i]['data'] = self::$driver->decodeBinary($v['data']); + } + + $expected = array( + array('data' => null), + array('data' => null), + array('data' => "\x00\x01\x02\xff"), + array('data' => "\x01\x01\x02\xff"), + ); + $this->assertThat($result, $this->equalTo($expected), __LINE__); + + // Test loadObjectList + $result = self::$driver->setQuery($selectRows)->loadObjectList(); + + foreach ($result as $i => $v) + { + $result[$i]->data = self::$driver->decodeBinary($v->data); + } + + $expected = array( + (object) array('data' => null), + (object) array('data' => null), + (object) array('data' => "\x00\x01\x02\xff"), + (object) array('data' => "\x01\x01\x02\xff"), + ); $this->assertThat($result, $this->equalTo($expected), __LINE__); } diff --git a/Tests/DriverTest.php b/Tests/DriverTest.php index cff5f61e8..20fb1ef1a 100644 --- a/Tests/DriverTest.php +++ b/Tests/DriverTest.php @@ -580,6 +580,28 @@ public function testQuoteFloat() ); } + /** + * Tests the Joomla\Database\DatabaseDriver::quoteBinary method. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testQuoteBinary() + { + $this->assertThat( + $this->instance->quoteBinary('DATA'), + $this->equalTo("X'" . bin2hex('DATA') . "'"), + 'Tests the binary data 1.' + ); + + $this->assertThat( + $this->instance->quoteBinary("\x00\x01\x02\xff"), + $this->equalTo("X'000102ff'"), + 'Tests the binary data 2.' + ); + } + /** * Tests the Joomla\Database\DatabaseDriver::quoteName method. * diff --git a/Tests/Mock/Driver.php b/Tests/Mock/Driver.php index 955f66358..0c9b3807f 100644 --- a/Tests/Mock/Driver.php +++ b/Tests/Mock/Driver.php @@ -77,6 +77,7 @@ public static function create(\PHPUnit_Framework_TestCase $test, $nullDate = '00 'lockTable', 'query', 'quote', + 'quoteBinary', 'quoteName', 'renameTable', 'replacePrefix', diff --git a/Tests/Stubs/ddl.sql b/Tests/Stubs/ddl.sql index 1f03cfd36..6e077ca85 100644 --- a/Tests/Stubs/ddl.sql +++ b/Tests/Stubs/ddl.sql @@ -484,7 +484,8 @@ CREATE TABLE `jos_dbtest` ( `id` INTEGER PRIMARY KEY AUTOINCREMENT, `title` TEXT NOT NULL DEFAULT '', `start_date` TEXT NOT NULL DEFAULT '', - `description` TEXT NOT NULL DEFAULT '' + `description` TEXT NOT NULL DEFAULT '', + `data` BLOB ); diff --git a/Tests/Stubs/mysql.sql b/Tests/Stubs/mysql.sql index 313f2b8fc..7f031cf10 100644 --- a/Tests/Stubs/mysql.sql +++ b/Tests/Stubs/mysql.sql @@ -20,6 +20,7 @@ CREATE TABLE `jos_dbtest` ( `title` varchar(50) NOT NULL, `start_date` datetime NOT NULL, `description` text NOT NULL, + `data` blob, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/Tests/Stubs/postgresql.sql b/Tests/Stubs/postgresql.sql index 25c660aa7..dc591ffae 100644 --- a/Tests/Stubs/postgresql.sql +++ b/Tests/Stubs/postgresql.sql @@ -559,6 +559,7 @@ CREATE TABLE "jos_dbtest" ( "title" character varying(50) NOT NULL, "start_date" timestamp without time zone NOT NULL, "description" text NOT NULL, + "data" bytea, PRIMARY KEY ("id") ); diff --git a/src/DatabaseDriver.php b/src/DatabaseDriver.php index c39f98a15..e6e3bffdb 100644 --- a/src/DatabaseDriver.php +++ b/src/DatabaseDriver.php @@ -1457,6 +1457,35 @@ public function quote($text, $escape = true) return '\'' . ($escape ? $this->escape($text) : $text) . '\''; } + /** + * Quotes a binary string to database requirements for use in database queries. + * + * @param string $data A binary string to quote. + * + * @return string The binary quoted input string. + * + * @since __DEPLOY_VERSION__ + */ + public function quoteBinary($data) + { + // SQL standard syntax for hexadecimal literals + return "X'" . bin2hex($data) . "'"; + } + + /** + * Replace special placeholder representing binary field with the original string. + * + * @param string|resource $data Encoded string or resource. + * + * @return string The original string. + * + * @since __DEPLOY_VERSION__ + */ + public function decodeBinary($data) + { + return $data; + } + /** * Wrap an SQL statement identifier name such as column, table or database names in quotes to prevent injection * risks and reserved word conflicts. diff --git a/src/Pgsql/PgsqlDriver.php b/src/Pgsql/PgsqlDriver.php index b3654b501..f1e02875f 100644 --- a/src/Pgsql/PgsqlDriver.php +++ b/src/Pgsql/PgsqlDriver.php @@ -981,4 +981,37 @@ public function updateObject($table, &$object, $key, $nulls = false) return $this->execute(); } + + /** + * Quotes a binary string to database requirements for use in database queries. + * + * @param string $data A binary string to quote. + * + * @return string The binary quoted input string. + * + * @since __DEPLOY_VERSION__ + */ + public function quoteBinary($data) + { + return "decode('" . bin2hex($data) . "', 'hex')"; + } + + /** + * Replace special placeholder representing binary field with the original string. + * + * @param string|resource $data Encoded string or resource. + * + * @return string The original string. + * + * @since __DEPLOY_VERSION__ + */ + public function decodeBinary($data) + { + if (is_resource($data)) + { + return stream_get_contents($data); + } + + return $data; + } } diff --git a/src/Postgresql/PostgresqlDriver.php b/src/Postgresql/PostgresqlDriver.php index 9d163a16d..9787349a5 100644 --- a/src/Postgresql/PostgresqlDriver.php +++ b/src/Postgresql/PostgresqlDriver.php @@ -202,6 +202,7 @@ public function connect() pg_set_error_verbosity($this->connection, PGSQL_ERRORS_DEFAULT); pg_query($this->connection, 'SET standard_conforming_strings=off'); pg_query($this->connection, 'SET escape_string_warning=off'); + pg_query($this->connection, 'SET bytea_output=escape'); } /** diff --git a/src/Sqlsrv/SqlsrvDriver.php b/src/Sqlsrv/SqlsrvDriver.php index 0a93a78dd..7a86f3ce1 100644 --- a/src/Sqlsrv/SqlsrvDriver.php +++ b/src/Sqlsrv/SqlsrvDriver.php @@ -272,6 +272,21 @@ public function quote($text, $escape = true) return 'N\'' . ($escape ? $this->escape($text) : $text) . '\''; } + /** + * Quotes a binary string to database requirements for use in database queries. + * + * @param string $data A binary string to quote. + * + * @return string The binary quoted input string. + * + * @since __DEPLOY_VERSION__ + */ + public function quoteBinary($data) + { + // ODBC syntax for hexadecimal literals + return '0x' . bin2hex($data); + } + /** * Determines if the connection to the server is active. * From 432d139a7f777394a984345f2c7dd5cc982df978 Mon Sep 17 00:00:00 2001 From: Tomasz Narloch Date: Tue, 18 Dec 2018 22:25:28 +0100 Subject: [PATCH 2/4] Changes for old PostgreSQL driver and unit tests --- Tests/DriverPostgresqlTest.php | 167 +++++++++++++++++++++++++++- src/Postgresql/PostgresqlDriver.php | 33 ++++++ 2 files changed, 195 insertions(+), 5 deletions(-) diff --git a/Tests/DriverPostgresqlTest.php b/Tests/DriverPostgresqlTest.php index 2f871e209..d9e9fd109 100644 --- a/Tests/DriverPostgresqlTest.php +++ b/Tests/DriverPostgresqlTest.php @@ -99,6 +99,22 @@ public function dataTestReplacePrefix() array('SELECT * FROM "#!-_table"', '#!-_', 'SELECT * FROM "jos_table"')); } + /** + * Data for the testQuoteBinary test. + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public function dataTestQuoteBinary() + { + return array( + array('DATA', "decode('" . bin2hex('DATA') . "', 'hex')"), + array("\x00\x01\x02\xff", "decode('000102ff', 'hex')"), + array("\x01\x01\x02\xff", "decode('010102ff', 'hex')"), + ); + } + /** * Data for testQuoteName test. * @@ -193,6 +209,25 @@ public function testEscape($text, $extra, $result) ); } + /** + * Test the quoteBinary method. + * + * @param string $data The binary quoted input string. + * + * @return void + * + * @dataProvider dataTestQuoteBinary + * @since __DEPLOY_VERSION__ + */ + public function testQuoteBinary($data, $expected) + { + $this->assertThat( + self::$driver->quoteBinary($data), + $this->equalTo($expected), + 'The binary data was not quoted properly' + ); + } + /** * Test getAffectedRows method. * @@ -269,7 +304,13 @@ public function testGetTableCreate() */ public function testGetTableColumns() { - $tableCol = array('id' => 'integer', 'title' => 'character varying', 'start_date' => 'timestamp without time zone', 'description' => 'text'); + $tableCol = array( + 'id' => 'integer', + 'title' => 'character varying', + 'start_date' => 'timestamp without time zone', + 'description' => 'text', + 'data' => 'bytea', + ); $this->assertThat(self::$driver->getTableColumns('jos_dbtest'), $this->equalTo($tableCol), __LINE__); @@ -314,9 +355,27 @@ public function testGetTableColumns() $description->Default = null; $description->comments = ''; + $data = new \stdClass; + $data->column_name = 'data'; + $data->Field = 'data'; + $data->type = 'bytea'; + $data->Type = 'bytea'; + $data->null = 'YES'; + $data->Null = 'YES'; + $data->Default = null; + $data->comments = ''; + $this->assertThat( self::$driver->getTableColumns('jos_dbtest', false), - $this->equalTo(array('id' => $id, 'title' => $title, 'start_date' => $start_date, 'description' => $description)), + $this->equalTo( + array( + 'id' => $id, + 'title' => $title, + 'start_date' => $start_date, + 'description' => $description, + 'data' => $data, + ) + ), __LINE__ ); } @@ -655,6 +714,7 @@ public function testLoadObject() $objCompare->title = 'Testing3'; $objCompare->start_date = '1980-04-18 00:00:00'; $objCompare->description = 'three'; + $objCompare->data = null; $this->assertThat($result, $this->equalTo($objCompare), __LINE__); } @@ -682,6 +742,7 @@ public function testLoadObjectList() $objCompare->title = 'Testing'; $objCompare->start_date = '1980-04-18 00:00:00'; $objCompare->description = 'one'; + $objCompare->data = null; $expected[] = clone $objCompare; @@ -690,6 +751,7 @@ public function testLoadObjectList() $objCompare->title = 'Testing2'; $objCompare->start_date = '1980-04-18 00:00:00'; $objCompare->description = 'one'; + $objCompare->data = null; $expected[] = clone $objCompare; @@ -698,6 +760,7 @@ public function testLoadObjectList() $objCompare->title = 'Testing3'; $objCompare->start_date = '1980-04-18 00:00:00'; $objCompare->description = 'three'; + $objCompare->data = null; $expected[] = clone $objCompare; @@ -706,6 +769,7 @@ public function testLoadObjectList() $objCompare->title = 'Testing4'; $objCompare->start_date = '1980-04-18 00:00:00'; $objCompare->description = 'four'; + $objCompare->data = null; $expected[] = clone $objCompare; @@ -748,7 +812,7 @@ public function testLoadRow() self::$driver->setQuery($query); $result = self::$driver->loadRow(); - $expected = array(3, 'Testing3', '1980-04-18 00:00:00', 'three'); + $expected = array(3, 'Testing3', '1980-04-18 00:00:00', 'three', null); $this->assertThat($result, $this->equalTo($expected), __LINE__); } @@ -769,8 +833,101 @@ public function testLoadRowList() self::$driver->setQuery($query); $result = self::$driver->loadRowList(); - $expected = array(array(1, 'Testing', '1980-04-18 00:00:00', 'one'), array(2, 'Testing2', '1980-04-18 00:00:00', 'one')); + $expected = array( + array(1, 'Testing', '1980-04-18 00:00:00', 'one', null), + array(2, 'Testing2', '1980-04-18 00:00:00', 'one', null) + ); + + $this->assertThat($result, $this->equalTo($expected), __LINE__); + } + + /** + * Test quoteBinary and decodeBinary methods + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testLoadBinary() + { + // Add binary data with null byte + $query = self::$driver->getQuery(true) + ->update('jos_dbtest') + ->set('data = ' . self::$driver->quoteBinary("\x00\x01\x02\xff")) + ->where('id = 3'); + + self::$driver->setQuery($query)->execute(); + + // Add binary data with invalid UTF-8 + $query = self::$driver->getQuery(true) + ->update('jos_dbtest') + ->set('data = ' . self::$driver->quoteBinary("\x01\x01\x02\xff")) + ->where('id = 4'); + + self::$driver->setQuery($query)->execute(); + + $selectRow3 = self::$driver->getQuery(true) + ->select('id') + ->from('jos_dbtest') + ->where('data = ' . self::$driver->quoteBinary("\x00\x01\x02\xff")); + + $selectRow4 = self::$driver->getQuery(true) + ->select('id') + ->from('jos_dbtest') + ->where('data = '. self::$driver->quoteBinary("\x01\x01\x02\xff")); + + $result = self::$driver->setQuery($selectRow3)->loadResult(); + $this->assertThat($result, $this->equalTo(3), __LINE__); + + $result = self::$driver->setQuery($selectRow4)->loadResult(); + $this->assertThat($result, $this->equalTo(4), __LINE__); + $selectRows = self::$driver->getQuery(true) + ->select('data') + ->from('jos_dbtest') + ->order('id'); + + // Test loadColumn + $result = self::$driver->setQuery($selectRows)->loadColumn(); + + foreach ($result as $i => $v) + { + $result[$i] = self::$driver->decodeBinary($v); + } + + $expected = array(null, null, "\x00\x01\x02\xff", "\x01\x01\x02\xff"); + $this->assertThat($result, $this->equalTo($expected), __LINE__); + + // Test loadAssocList + $result = self::$driver->setQuery($selectRows)->loadAssocList(); + + foreach ($result as $i => $v) + { + $result[$i]['data'] = self::$driver->decodeBinary($v['data']); + } + + $expected = array( + array('data' => null), + array('data' => null), + array('data' => "\x00\x01\x02\xff"), + array('data' => "\x01\x01\x02\xff"), + ); + $this->assertThat($result, $this->equalTo($expected), __LINE__); + + // Test loadObjectList + $result = self::$driver->setQuery($selectRows)->loadObjectList(); + + foreach ($result as $i => $v) + { + $result[$i]->data = self::$driver->decodeBinary($v->data); + } + + $expected = array( + (object) array('data' => null), + (object) array('data' => null), + (object) array('data' => "\x00\x01\x02\xff"), + (object) array('data' => "\x01\x01\x02\xff"), + ); $this->assertThat($result, $this->equalTo($expected), __LINE__); } @@ -953,7 +1110,7 @@ public function testTransactionCommit() self::$driver->setQuery($queryCheck); $result = self::$driver->loadRow(); - $expected = array(6, 'testTitle', '1970-01-01 00:00:00', 'testDescription'); + $expected = array(6, 'testTitle', '1970-01-01 00:00:00', 'testDescription', null); $this->assertThat($result, $this->equalTo($expected), __LINE__); } diff --git a/src/Postgresql/PostgresqlDriver.php b/src/Postgresql/PostgresqlDriver.php index 9787349a5..54c931c77 100644 --- a/src/Postgresql/PostgresqlDriver.php +++ b/src/Postgresql/PostgresqlDriver.php @@ -1603,4 +1603,37 @@ public function updateObject($table, &$object, $key, $nulls = false) return $this->execute(); } + + /** + * Quotes a binary string to database requirements for use in database queries. + * + * @param string $data A binary string to quote. + * + * @return string The binary quoted input string. + * + * @since __DEPLOY_VERSION__ + */ + public function quoteBinary($data) + { + return "decode('" . bin2hex($data) . "', 'hex')"; + } + + /** + * Replace special placeholder representing binary field with the original string. + * + * @param string|resource $data Encoded string or resource. + * + * @return string The original string. + * + * @since __DEPLOY_VERSION__ + */ + public function decodeBinary($data) + { + if (is_string($data)) + { + return pg_unescape_bytea($data); + } + + return $data; + } } From 889c2f2f8248ccfc96142c5baa96b6da4968e81c Mon Sep 17 00:00:00 2001 From: Tomasz Narloch Date: Thu, 27 Dec 2018 19:58:01 +0100 Subject: [PATCH 3/4] Add methods to interface --- src/DatabaseInterface.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/DatabaseInterface.php b/src/DatabaseInterface.php index b32b560c7..d3a20b002 100644 --- a/src/DatabaseInterface.php +++ b/src/DatabaseInterface.php @@ -23,4 +23,26 @@ interface DatabaseInterface * @since 1.0 */ public static function isSupported(); + + /** + * Quotes a binary string to database requirements for use in database queries. + * + * @param string $data A binary string to quote. + * + * @return string The binary quoted input string. + * + * @since __DEPLOY_VERSION__ + */ + public function quoteBinary($data); + + /** + * Replace special placeholder representing binary field with the original string. + * + * @param string|resource $data Encoded string or resource. + * + * @return string The original string. + * + * @since __DEPLOY_VERSION__ + */ + public function decodeBinary($data); } From c88c8ff060a5006ac2bdcce3b5c066e3495eb0e5 Mon Sep 17 00:00:00 2001 From: Tomasz Narloch Date: Thu, 27 Dec 2018 21:23:47 +0100 Subject: [PATCH 4/4] Revert "Add methods to interface" This reverts commit 889c2f2f8248ccfc96142c5baa96b6da4968e81c. --- src/DatabaseInterface.php | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/src/DatabaseInterface.php b/src/DatabaseInterface.php index d3a20b002..b32b560c7 100644 --- a/src/DatabaseInterface.php +++ b/src/DatabaseInterface.php @@ -23,26 +23,4 @@ interface DatabaseInterface * @since 1.0 */ public static function isSupported(); - - /** - * Quotes a binary string to database requirements for use in database queries. - * - * @param string $data A binary string to quote. - * - * @return string The binary quoted input string. - * - * @since __DEPLOY_VERSION__ - */ - public function quoteBinary($data); - - /** - * Replace special placeholder representing binary field with the original string. - * - * @param string|resource $data Encoded string or resource. - * - * @return string The original string. - * - * @since __DEPLOY_VERSION__ - */ - public function decodeBinary($data); }