diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index 74c3c6c6a..aa310ba7d 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -1464,4 +1464,11 @@ public function enableAlterLocks(bool $enable): self return $this; } + + /** + * Handle non utf characters supported? + * + * @return bool + */ + abstract public function getSupportNonUtfCharacters(): bool; } diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 2876139f7..366ba8e90 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -7,6 +7,7 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Exception as DatabaseException; +use Utopia\Database\Exception\Character as CharacterException; use Utopia\Database\Exception\Duplicate as DuplicateException; use Utopia\Database\Exception\Limit as LimitException; use Utopia\Database\Exception\NotFound as NotFoundException; @@ -1838,6 +1839,10 @@ public function getInternalIndexesKeys(): array protected function processException(PDOException $e): \Exception { + if ($e->getCode() === '22007' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 1366) { + return new CharacterException('Invalid character', $e->getCode(), $e); + } + // Timeout if ($e->getCode() === '70100' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 1969) { return new TimeoutException('Query timed out', $e->getCode(), $e); @@ -2230,4 +2235,9 @@ public function getSupportForAlterLocks(): bool { return true; } + + public function getSupportNonUtfCharacters(): bool + { + return true; + } } diff --git a/src/Database/Adapter/Mongo.php b/src/Database/Adapter/Mongo.php index bd4aa70b4..734312b7e 100644 --- a/src/Database/Adapter/Mongo.php +++ b/src/Database/Adapter/Mongo.php @@ -3221,4 +3221,9 @@ public function getSupportForAlterLocks(): bool { return false; } + + public function getSupportNonUtfCharacters(): bool + { + return false; + } } diff --git a/src/Database/Adapter/MySQL.php b/src/Database/Adapter/MySQL.php index 2ff77e9a0..4305ee901 100644 --- a/src/Database/Adapter/MySQL.php +++ b/src/Database/Adapter/MySQL.php @@ -5,6 +5,7 @@ use PDOException; use Utopia\Database\Database; use Utopia\Database\Exception as DatabaseException; +use Utopia\Database\Exception\Character as CharacterException; use Utopia\Database\Exception\Dependency as DependencyException; use Utopia\Database\Exception\Structure as StructureException; use Utopia\Database\Exception\Timeout as TimeoutException; @@ -147,6 +148,10 @@ public function getSupportForCastIndexArray(): bool protected function processException(PDOException $e): \Exception { + if ($e->getCode() === 'HY000' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 1366) { + return new CharacterException('Invalid character', $e->getCode(), $e); + } + // Timeout if ($e->getCode() === 'HY000' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 3024) { return new TimeoutException('Query timed out', $e->getCode(), $e); diff --git a/src/Database/Adapter/Pool.php b/src/Database/Adapter/Pool.php index d41111150..a1c0d969f 100644 --- a/src/Database/Adapter/Pool.php +++ b/src/Database/Adapter/Pool.php @@ -642,4 +642,9 @@ public function getSupportForAlterLocks(): bool { return $this->delegate(__FUNCTION__, \func_get_args()); } + + public function getSupportNonUtfCharacters(): bool + { + return $this->delegate(__FUNCTION__, \func_get_args()); + } } diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php index 72e49cc07..44d028d99 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -2738,4 +2738,9 @@ protected function bindOperatorParams(\PDOStatement|PDOStatementProxy $stmt, Ope break; } } + + public function getSupportNonUtfCharacters(): bool + { + return false; + } } diff --git a/src/Database/Adapter/SQLite.php b/src/Database/Adapter/SQLite.php index a3d31db68..cca5cc927 100644 --- a/src/Database/Adapter/SQLite.php +++ b/src/Database/Adapter/SQLite.php @@ -1876,4 +1876,9 @@ public function getSupportForAlterLocks(): bool { return false; } + + public function getSupportNonUtfCharacters(): bool + { + return false; + } } diff --git a/src/Database/Database.php b/src/Database/Database.php index 68705f538..5fe03cc54 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -9358,5 +9358,4 @@ private function rollbackAttributeMetadata(Document $collection, array $attribut ); $collection->setAttribute('attributes', \array_values($filteredAttributes)); } - } diff --git a/src/Database/Exception/Character.php b/src/Database/Exception/Character.php new file mode 100644 index 000000000..bf184803a --- /dev/null +++ b/src/Database/Exception/Character.php @@ -0,0 +1,9 @@ +getDatabase(); + + if (!$database->getAdapter()->getSupportNonUtfCharacters()) { + $this->expectNotToPerformAssertions(); + return; + } + + $database->createCollection(__FUNCTION__); + $this->assertEquals(true, $database->createAttribute(__FUNCTION__, 'title', Database::VAR_STRING, 128, true)); + + $nonUtfString = "Hello\x00World\xC3\x28\xFF\xFE\xA0Test\x00End"; + + try { + $database->createDocument(__FUNCTION__, new Document([ + 'title' => $nonUtfString, + ])); + $this->fail('Failed to throw exception'); + } catch (Throwable $e) { + $this->assertTrue($e instanceof CharacterException); + } + + /** + * Convert to UTF-8 and replace invalid bytes with empty string + */ + $nonUtfString = mb_convert_encoding($nonUtfString, 'UTF-8', 'UTF-8'); + + /** + * Remove null bytes + */ + $nonUtfString = str_replace("\0", '', $nonUtfString); + + $document = $database->createDocument(__FUNCTION__, new Document([ + 'title' => $nonUtfString, + ])); + + $this->assertFalse($document->isEmpty()); + $this->assertEquals('HelloWorld?(???TestEnd', $document->getAttribute('title')); + } + public function testBigintSequence(): void { /** @var Database $database */