From 981bfce833f40443910dcbd808b413a0503b14ed Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 21 Jan 2021 22:19:27 +0200 Subject: [PATCH 001/190] First Commit --- .gitignore | 3 + .travis.yml | 29 + README.md | 41 +- composer.json | 25 + docker-compose.yml | 28 + phpunit.xml | 16 + psalm.xml | 15 + src/Database/Adapter.php | 296 +++ src/Database/Adapter/MySQL.php | 999 +++++++++ src/Database/Adapter/Redis.php | 362 ++++ src/Database/Adapter/Relational.php | 1148 ++++++++++ src/Database/Database.php | 691 ++++++ src/Database/Document.php | 254 +++ src/Database/Exception/Authorization.php | 7 + src/Database/Exception/Duplicate.php | 7 + src/Database/Exception/Structure.php | 7 + src/Database/Validator/Authorization.php | 188 ++ src/Database/Validator/Collection.php | 62 + src/Database/Validator/DocumentId.php | 81 + src/Database/Validator/Key.php | 51 + src/Database/Validator/Permissions.php | 66 + src/Database/Validator/Structure.php | 294 +++ src/Database/Validator/UID.php | 46 + tests/Database/DatabaseTest.php | 1907 +++++++++++++++++ .../Database/Validator/AuthorizationTest.php | 90 + tests/Database/Validator/KeyTest.php | 36 + tests/Database/Validator/PermissionsTest.php | 78 + tests/Database/Validator/UIDTest.php | 30 + 28 files changed, 6856 insertions(+), 1 deletion(-) create mode 100755 .gitignore create mode 100644 .travis.yml create mode 100755 composer.json create mode 100644 docker-compose.yml create mode 100755 phpunit.xml create mode 100644 psalm.xml create mode 100644 src/Database/Adapter.php create mode 100644 src/Database/Adapter/MySQL.php create mode 100644 src/Database/Adapter/Redis.php create mode 100644 src/Database/Adapter/Relational.php create mode 100644 src/Database/Database.php create mode 100644 src/Database/Document.php create mode 100644 src/Database/Exception/Authorization.php create mode 100644 src/Database/Exception/Duplicate.php create mode 100644 src/Database/Exception/Structure.php create mode 100644 src/Database/Validator/Authorization.php create mode 100644 src/Database/Validator/Collection.php create mode 100644 src/Database/Validator/DocumentId.php create mode 100644 src/Database/Validator/Key.php create mode 100644 src/Database/Validator/Permissions.php create mode 100644 src/Database/Validator/Structure.php create mode 100644 src/Database/Validator/UID.php create mode 100644 tests/Database/DatabaseTest.php create mode 100644 tests/Database/Validator/AuthorizationTest.php create mode 100644 tests/Database/Validator/KeyTest.php create mode 100644 tests/Database/Validator/PermissionsTest.php create mode 100644 tests/Database/Validator/UIDTest.php diff --git a/.gitignore b/.gitignore new file mode 100755 index 000000000..8924c8079 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +composer.lock +/vendor/ +/.idea/ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..6f5e8afad --- /dev/null +++ b/.travis.yml @@ -0,0 +1,29 @@ +language: php + +php: +- 7.3 +- 7.4 +- nightly + +services: +- docker + +notifications: + email: + - team@appwrite.io + +before_install: +- curl -fsSL https://get.docker.com | sh +- echo '{"experimental":"enabled"}' | sudo tee /etc/docker/daemon.json +- mkdir -p $HOME/.docker +- echo '{"experimental":"enabled"}' | sudo tee $HOME/.docker/config.json +- service docker start + +install: +- docker-compose up -d +- sleep 10 + +script: +- docker ps +- vendor/bin/phpunit --configuration phpunit.xml +- vendor/bin/psalm --show-info=true diff --git a/README.md b/README.md index 3be9984d0..ce1491b4a 100644 --- a/README.md +++ b/README.md @@ -1 +1,40 @@ -# database +# Utopia Database + +[![Build Status](https://travis-ci.org/utopia-php/abuse.svg?branch=master)](https://travis-ci.com/utopia-php/abuse) +![Total Downloads](https://img.shields.io/packagist/dt/utopia-php/abuse.svg) +[![Discord](https://img.shields.io/discord/564160730845151244)](https://appwrite.io/discord) + +Utopia framework database library is simple and lite library for managing application persistency using multiple database adapters. This library is aiming to be as simple and easy to learn and use. This library is maintained by the [Appwrite team](https://appwrite.io). + +Although this library is part of the [Utopia Framework](https://github.com/utopia-php/framework) project it is dependency free, and can be used as standalone with any other PHP project or framework. + +## Getting Started + +Install using composer: +```bash +composer require utopia-php/database +``` + +Initialization: + +```php +=7.1", + "ext-pdo": "*" + }, + "require-dev": { + "phpunit/phpunit": "^9.4", + "vimeo/psalm": "4.0.1" + } +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..d83c7cbca --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,28 @@ +version: '3.1' + +services: + + postgres: + image: postgres:13 + container_name: postgres + ports: + - "8700:8080" + environment: + POSTGRES_PASSWORD: password + + mariadb: + image: mariadb:10.5 + container_name: mariadb + ports: + - "8701:3306" + environment: + - MYSQL_ROOT_PASSWORD=password + + mongo: + image: mongo:3.6 + container_name: mongo + ports: + - "8702:27017" + environment: + MONGO_INITDB_ROOT_USERNAME: root + MONGO_INITDB_ROOT_PASSWORD: example \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml new file mode 100755 index 000000000..ec39a7ede --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,16 @@ + + + + ./tests/ + + + \ No newline at end of file diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 000000000..7c0333df3 --- /dev/null +++ b/psalm.xml @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php new file mode 100644 index 000000000..34d0a591c --- /dev/null +++ b/src/Database/Adapter.php @@ -0,0 +1,296 @@ +debug[$key] = $value; + + return $this; + } + + /** + * @return array + */ + public function getDebug() + { + return $this->debug; + } + + /** + * return $this + */ + public function resetDebug() + { + $this->debug = []; + } + + /** + * Set Namespace. + * + * Set namespace to divide different scope of data sets + * + * @param $namespace + * + * @throws Exception + * + * @return bool + */ + public function setNamespace($namespace) + { + if (empty($namespace)) { + throw new Exception('Missing namespace'); + } + + $this->namespace = $namespace; + + return true; + } + + /** + * Get Namespace. + * + * Get namespace of current set scope + * + * @throws Exception + * + * @return string + */ + public function getNamespace() + { + if (empty($this->namespace)) { + throw new Exception('Missing namespace'); + } + + return $this->namespace; + } + + /** + * Create Collection + * + * @param Document $collection + * @param string $id + * + * @return bool + */ + abstract public function createCollection(Document $collection, string $id): bool; + + /** + * Delete Collection + * + * @param Document $collection + * + * @return bool + */ + abstract public function deleteCollection(Document $collection): bool; + + /** + * Create Attribute + * + * @param Document $collection + * @param string $id + * @param string $type + * @param bool $array + * + * @return bool + */ + abstract public function createAttribute(Document $collection, string $id, string $type, bool $array = false): bool; + + /** + * Delete Attribute + * + * @param Document $collection + * @param string $id + * @param bool $array + * + * @return bool + */ + abstract public function deleteAttribute(Document $collection, string $id, bool $array = false): bool; + + /** + * Create Index + * + * @param Document $collection + * @param string $id + * @param string $type + * @param array $attributes + * + * @return bool + */ + abstract public function createIndex(Document $collection, string $id, string $type, array $attributes): bool; + + /** + * Delete Index + * + * @param Document $collection + * @param string $id + * + * @return bool + */ + abstract public function deleteIndex(Document $collection, string $id): bool; + + /** + * Get Document. + * + * @param Document $collection + * @param string $id + * + * @return array + */ + abstract public function getDocument(Document $collection, $id); + + /** + * Create Document + * + * @param Document $collection + * @param array $data + * @param array $unique + * + * @return array + */ + abstract public function createDocument(Document $collection, array $data, array $unique = []); + + /** + * Update Document. + * + * @param Document $collection + * @param array $data + * + * @return array + */ + abstract public function updateDocument(Document $collection, string $id, array $data); + + /** + * Delete Node. + * + * @param Document $collection + * @param string $id + * + * @return array + */ + abstract public function deleteDocument(Document $collection, string $id); + + /** + * Delete Unique Key. + * + * @param int $key + * + * @return array + */ + abstract public function deleteUniqueKey($key); + + /** + * Create Namespace. + * + * @param string $namespace + * + * @return bool + */ + abstract public function createNamespace($namespace); + + /** + * Delete Namespace. + * + * @param string $namespace + * + * @return bool + */ + abstract public function deleteNamespace($namespace); + + /** + * Filter. + * + * Filter data sets using chosen queries + * + * @param Document $collection + * @param array $options + * + * @return array + */ + abstract public function find(Document $collection, array $options); + + /** + * @param array $options + * + * @return int + */ + abstract public function count(array $options); + + /** + * Get Unique Document ID. + */ + public function getId() + { + return \uniqid(); + } + + /** + * @param Database $database + * + * @return $this + */ + public function setDatabase(Database $database) + { + $this->database = $database; + + return $this; + } + + /** + * @return Database + */ + public function getDatabase() + { + return $this->database; + } + + /** + * @param string $mocks + * + * @return $this + */ + public function setMocks(array $mocks) + { + $this->mocks = $mocks; + + return $this; + } + + /** + * @return array + */ + public function getMocks() + { + return $this->mocks; + } +} diff --git a/src/Database/Adapter/MySQL.php b/src/Database/Adapter/MySQL.php new file mode 100644 index 000000000..a50dcf6c3 --- /dev/null +++ b/src/Database/Adapter/MySQL.php @@ -0,0 +1,999 @@ +register = $register; + } + + /** + * Create Collection + * + * @param Document $collection + * @param string $id + * + * @return bool + */ + public function createCollection(Document $collection, string $id): bool + { + return true; + } + + /** + * Delete Collection + * + * @param Document $collection + * + * @return bool + */ + public function deleteCollection(Document $collection): bool + { + return true; + } + + /** + * Create Attribute + * + * @param Document $collection + * @param string $id + * @param string $type + * @param bool $array + * + * @return bool + */ + public function createAttribute(Document $collection, string $id, string $type, bool $array = false): bool + { + return true; + } + + /** + * Delete Attribute + * + * @param Document $collection + * @param string $id + * @param bool $array + * + * @return bool + */ + public function deleteAttribute(Document $collection, string $id, bool $array = false): bool + { + return true; + } + + + /** + * Create Index + * + * @param Document $collection + * @param string $id + * @param string $type + * @param array $attributes + * + * @return bool + */ + public function createIndex(Document $collection, string $id, string $type, array $attributes): bool + { + return true; + } + + /** + * Delete Index + * + * @param Document $collection + * @param string $id + * + * @return bool + */ + public function deleteIndex(Document $collection, string $id): bool + { + return true; + } + + /** + * Get Document. + * + * @param Document $collection + * @param string $id + * + * @return array + * + * @throws Exception + */ + public function getDocument(Document $collection, $id) + { + // Get fields abstraction + $st = $this->getPDO()->prepare('SELECT * FROM `'.$this->getNamespace().'.database.documents` a + WHERE a.uid = :uid AND a.status = 0 + ORDER BY a.updatedAt DESC LIMIT 10; + '); + + $st->bindValue(':uid', $id, PDO::PARAM_STR); + + $st->execute(); + + $document = $st->fetch(); + + if (empty($document)) { // Not Found + return []; + } + + // Get fields abstraction + $st = $this->getPDO()->prepare('SELECT * FROM `'.$this->getNamespace().'.database.properties` a + WHERE a.documentUid = :documentUid AND a.documentRevision = :documentRevision + ORDER BY `order` + '); + + $st->bindParam(':documentUid', $document['uid'], PDO::PARAM_STR); + $st->bindParam(':documentRevision', $document['revision'], PDO::PARAM_STR); + + $st->execute(); + + $properties = $st->fetchAll(); + + $output = [ + '$id' => null, + '$collection' => null, + '$permissions' => (!empty($document['permissions'])) ? \json_decode($document['permissions'], true) : [], + ]; + + foreach ($properties as &$property) { + \settype($property['value'], $property['primitive']); + + if ($property['array']) { + $output[$property['key']][] = $property['value']; + } else { + $output[$property['key']] = $property['value']; + } + } + + // Get fields abstraction + $st = $this->getPDO()->prepare('SELECT * FROM `'.$this->getNamespace().'.database.relationships` a + WHERE a.start = :start AND revision = :revision + ORDER BY `order` + '); + + $st->bindParam(':start', $document['uid'], PDO::PARAM_STR); + $st->bindParam(':revision', $document['revision'], PDO::PARAM_STR); + + $st->execute(); + + $output['temp-relations'] = $st->fetchAll(); + + return $output; + } + + /** + * Create Document. + * + * @param Document $collection + * @param array $data + * + * @throws \Exception + * + * @return array + */ + public function createDocument(Document $collection, array $data, array $unique = []) + { + $order = 0; + $data = \array_merge(['$id' => null, '$permissions' => []], $data); // Merge data with default params + $signature = \md5(\json_encode($data)); + $revision = \uniqid('', true); + $data['$id'] = (empty($data['$id'])) ? null : $data['$id']; + + /* + * When updating node, check if there are any changes to update + * by comparing data md5 signatures + */ + if (null !== $data['$id']) { + $st = $this->getPDO()->prepare('SELECT signature FROM `'.$this->getNamespace().'.database.documents` a + WHERE a.uid = :uid AND a.status = 0 + ORDER BY a.updatedAt DESC LIMIT 1; + '); + + $st->bindValue(':uid', $data['$id'], PDO::PARAM_STR); + + $st->execute(); + + $result = $st->fetch(); + + if ($result && isset($result['signature'])) { + $oldSignature = $result['signature']; + + if ($signature === $oldSignature) { + return $data; + } + } + } + + /** + * Check Unique Keys + */ + foreach ($unique as $key => $value) { + $st = $this->getPDO()->prepare('INSERT INTO `'.$this->getNamespace().'.database.unique` + SET `key` = :key; + '); + + $st->bindValue(':key', \md5($data['$collection'].':'.$key.'='.$value), PDO::PARAM_STR); + + if (!$st->execute()) { + throw new Duplicate('Duplicated Property: '.$key.'='.$value); + } + } + + // Add or update fields abstraction level + $st1 = $this->getPDO()->prepare('INSERT INTO `'.$this->getNamespace().'.database.documents` + SET uid = :uid, createdAt = :createdAt, updatedAt = :updatedAt, signature = :signature, revision = :revision, permissions = :permissions, status = 0 + ON DUPLICATE KEY UPDATE uid = :uid, updatedAt = :updatedAt, signature = :signature, revision = :revision, permissions = :permissions; + '); + + // Adding fields properties + if (null === $data['$id'] || !isset($data['$id'])) { // Get new fields UID + $data['$id'] = $this->getId(); + } + + $st1->bindValue(':uid', $data['$id'], PDO::PARAM_STR); + $st1->bindValue(':revision', $revision, PDO::PARAM_STR); + $st1->bindValue(':signature', $signature, PDO::PARAM_STR); + $st1->bindValue(':createdAt', \date('Y-m-d H:i:s', \time()), PDO::PARAM_STR); + $st1->bindValue(':updatedAt', \date('Y-m-d H:i:s', \time()), PDO::PARAM_STR); + $st1->bindValue(':permissions', \json_encode($data['$permissions']), PDO::PARAM_STR); + + $st1->execute(); + + // Delete old properties + $rms1 = $this->getPDO()->prepare('DELETE FROM `'.$this->getNamespace().'.database.properties` WHERE documentUid = :documentUid AND documentRevision != :documentRevision'); + $rms1->bindValue(':documentUid', $data['$id'], PDO::PARAM_STR); + $rms1->bindValue(':documentRevision', $revision, PDO::PARAM_STR); + $rms1->execute(); + + // Delete old relationships + $rms2 = $this->getPDO()->prepare('DELETE FROM `'.$this->getNamespace().'.database.relationships` WHERE start = :start AND revision != :revision'); + $rms2->bindValue(':start', $data['$id'], PDO::PARAM_STR); + $rms2->bindValue(':revision', $revision, PDO::PARAM_STR); + $rms2->execute(); + + // Create new properties + $st2 = $this->getPDO()->prepare('INSERT INTO `'.$this->getNamespace().'.database.properties` + (`documentUid`, `documentRevision`, `key`, `value`, `primitive`, `array`, `order`) + VALUES (:documentUid, :documentRevision, :key, :value, :primitive, :array, :order)'); + + $props = []; + + foreach ($data as $key => $value) { // Prepare properties data + + if (\in_array($key, ['$permissions'])) { + continue; + } + + $type = $this->getDataType($value); + + // Handle array of relations + if (self::DATA_TYPE_ARRAY === $type) { + if (!is_array($value)) { // Property should be of type array, if not = skip + continue; + } + + foreach ($value as $i => $child) { + if (self::DATA_TYPE_DICTIONARY !== $this->getDataType($child)) { // not dictionary + + $props[] = [ + 'type' => $this->getDataType($child), + 'key' => $key, + 'value' => $child, + 'array' => true, + 'order' => $order++, + ]; + + continue; + } + + $data[$key][$i] = $this->createDocument(new Document([]), $child); + + $this->createRelationship($revision, $data['$id'], $data[$key][$i]['$id'], $key, true, $i); + } + + continue; + } + + // Handle relation + if (self::DATA_TYPE_DICTIONARY === $type) { + $value = $this->createDocument(new Document([]), $value); + $this->createRelationship($revision, $data['$id'], $value['$id'], $key); //xxx + continue; + } + + // Handle empty values + if (self::DATA_TYPE_NULL === $type) { + continue; + } + + $props[] = [ + 'type' => $type, + 'key' => $key, + 'value' => $value, + 'array' => false, + 'order' => $order++, + ]; + } + + foreach ($props as $prop) { + if (\is_array($prop['value'])) { + throw new Exception('Value can\'t be an array: '.\json_encode($prop['value'])); + } + $st2->bindValue(':documentUid', $data['$id'], PDO::PARAM_STR); + $st2->bindValue(':documentRevision', $revision, PDO::PARAM_STR); + + $st2->bindValue(':key', $prop['key'], PDO::PARAM_STR); + $st2->bindValue(':value', $prop['value'], PDO::PARAM_STR); + $st2->bindValue(':primitive', $prop['type'], PDO::PARAM_STR); + $st2->bindValue(':array', $prop['array'], PDO::PARAM_BOOL); + $st2->bindValue(':order', $prop['order'], PDO::PARAM_STR); + + $st2->execute(); + } + + //TODO remove this dependency (check if related to nested documents) + $this->getRedis()->expire($this->getNamespace().':document-'.$data['$id'], 0); + $this->getRedis()->expire($this->getNamespace().':document-'.$data['$id'], 0); + + return $data; + } + + /** + * Update Document. + * + * @param Document $collection + * @param string $id + * @param array $data + * + * @return array + * + * @throws Exception + */ + public function updateDocument(Document $collection, string $id, array $data) + { + return $this->createDocument($collection, $data); + } + + /** + * Delete Document. + * + * @param Document $collection + * @param string $id + * + * @return array + * + * @throws Exception + */ + public function deleteDocument(Document $collection, string $id) + { + $st1 = $this->getPDO()->prepare('DELETE FROM `'.$this->getNamespace().'.database.documents` + WHERE uid = :id + '); + + $st1->bindValue(':id', $id, PDO::PARAM_STR); + + $st1->execute(); + + $st2 = $this->getPDO()->prepare('DELETE FROM `'.$this->getNamespace().'.database.properties` + WHERE documentUid = :id + '); + + $st2->bindValue(':id', $id, PDO::PARAM_STR); + + $st2->execute(); + + $st3 = $this->getPDO()->prepare('DELETE FROM `'.$this->getNamespace().'.database.relationships` + WHERE start = :id OR end = :id + '); + + $st3->bindValue(':id', $id, PDO::PARAM_STR); + + $st3->execute(); + + return []; + } + + /** + * Delete Unique Key. + * + * @param int $key + * + * @return array + * + * @throws Exception + */ + public function deleteUniqueKey($key) + { + $st1 = $this->getPDO()->prepare('DELETE FROM `'.$this->getNamespace().'.database.unique` WHERE `key` = :key'); + + $st1->bindValue(':key', $key, PDO::PARAM_STR); + + $st1->execute(); + + return []; + } + + /** + * Create Relation. + * + * Adds a new relationship between different nodes + * + * @param string $revision + * @param int $start + * @param int $end + * @param string $key + * @param bool $isArray + * @param int $order + * + * @return array + * + * @throws Exception + */ + protected function createRelationship($revision, $start, $end, $key, $isArray = false, $order = 0) + { + $st2 = $this->getPDO()->prepare('INSERT INTO `'.$this->getNamespace().'.database.relationships` + (`revision`, `start`, `end`, `key`, `array`, `order`) + VALUES (:revision, :start, :end, :key, :array, :order)'); + + $st2->bindValue(':revision', $revision, PDO::PARAM_STR); + $st2->bindValue(':start', $start, PDO::PARAM_STR); + $st2->bindValue(':end', $end, PDO::PARAM_STR); + $st2->bindValue(':key', $key, PDO::PARAM_STR); + $st2->bindValue(':array', $isArray, PDO::PARAM_INT); + $st2->bindValue(':order', $order, PDO::PARAM_INT); + + $st2->execute(); + + return []; + } + + /** + * Create Namespace. + * + * @param $namespace + * + * @throws Exception + * + * @return bool + */ + public function createNamespace($namespace) + { + if (empty($namespace)) { + throw new Exception('Empty namespace'); + } + + $documents = 'app_'.$namespace.'.database.documents'; + $properties = 'app_'.$namespace.'.database.properties'; + $relationships = 'app_'.$namespace.'.database.relationships'; + $unique = 'app_'.$namespace.'.database.unique'; + $audit = 'app_'.$namespace.'.audit.audit'; + $abuse = 'app_'.$namespace.'.abuse.abuse'; + + try { + $this->getPDO()->prepare('CREATE TABLE `'.$documents.'` LIKE `template.database.documents`;')->execute(); + $this->getPDO()->prepare('CREATE TABLE `'.$properties.'` LIKE `template.database.properties`;')->execute(); + $this->getPDO()->prepare('CREATE TABLE `'.$relationships.'` LIKE `template.database.relationships`;')->execute(); + $this->getPDO()->prepare('CREATE TABLE `'.$unique.'` LIKE `template.database.unique`;')->execute(); + $this->getPDO()->prepare('CREATE TABLE `'.$audit.'` LIKE `template.audit.audit`;')->execute(); + $this->getPDO()->prepare('CREATE TABLE `'.$abuse.'` LIKE `template.abuse.abuse`;')->execute(); + } catch (Exception $e) { + throw $e; + } + + return true; + } + + /** + * Delete Namespace. + * + * @param $namespace + * + * @throws Exception + * + * @return bool + */ + public function deleteNamespace($namespace) + { + if (empty($namespace)) { + throw new Exception('Empty namespace'); + } + + $unique = 'app_'.$namespace.'.database.unique'; + $documents = 'app_'.$namespace.'.database.documents'; + $properties = 'app_'.$namespace.'.database.properties'; + $relationships = 'app_'.$namespace.'.database.relationships'; + $audit = 'app_'.$namespace.'.audit.audit'; + $abuse = 'app_'.$namespace.'.abuse.abuse'; + + try { + $this->getPDO()->prepare('DROP TABLE `'.$unique.'`;')->execute(); + $this->getPDO()->prepare('DROP TABLE `'.$documents.'`;')->execute(); + $this->getPDO()->prepare('DROP TABLE `'.$properties.'`;')->execute(); + $this->getPDO()->prepare('DROP TABLE `'.$relationships.'`;')->execute(); + $this->getPDO()->prepare('DROP TABLE `'.$audit.'`;')->execute(); + $this->getPDO()->prepare('DROP TABLE `'.$abuse.'`;')->execute(); + } catch (Exception $e) { + throw $e; + } + + return true; + } + + /** + * Get Collection. + * + * @param array $options + * + * @throws Exception + * + * @return array + */ + public function find(Document $collection, array $options) + { + $start = \microtime(true); + $orderCastMap = [ + 'int' => 'UNSIGNED', + 'string' => 'CHAR', + 'date' => 'DATE', + 'time' => 'TIME', + 'datetime' => 'DATETIME', + ]; + $orderTypeMap = ['DESC', 'ASC']; + + $options['orderField'] = (empty($options['orderField'])) ? '' : $options['orderField']; // Set default order field + $options['orderCast'] = (empty($options['orderCast'])) ? 'string' : $options['orderCast']; // Set default order field + + if (!\array_key_exists($options['orderCast'], $orderCastMap)) { + throw new Exception('Invalid order cast'); + } + + if (!\in_array($options['orderType'], $orderTypeMap)) { + throw new Exception('Invalid order type'); + } + + $where = []; + $join = []; + $sorts = []; + $search = ''; + + $options['filters'][] = '$collection='.$collection->getId(); + + // Filters + foreach ($options['filters'] as $i => $filter) { + $filter = $this->parseFilter($filter); + $key = $filter['key']; + $value = $filter['value']; + $operator = $filter['operator']; + + $path = \explode('.', $key); + $original = $path; + + if (1 < \count($path)) { + $key = \array_pop($path); + } else { + $path = []; + } + + //$path = implode('.', $path); + + $key = $this->getPDO()->quote($key, PDO::PARAM_STR); + $value = $this->getPDO()->quote($value, PDO::PARAM_STR); + //$path = $this->getPDO()->quote($path, PDO::PARAM_STR); + $options['offset'] = (int) $options['offset']; + $options['limit'] = (int) $options['limit']; + + if (empty($path)) { + //if($path == "''") { // Handle direct attributes queries + $where[] = 'JOIN `'.$this->getNamespace().".database.properties` b{$i} ON a.uid IS NOT NULL AND b{$i}.documentUid = a.uid AND (b{$i}.key = {$key} AND b{$i}.value {$operator} {$value})"; + } else { // Handle direct child attributes queries + $len = \count($original); + $prev = 'c'.$i; + + foreach ($original as $y => $part) { + $part = $this->getPDO()->quote($part, PDO::PARAM_STR); + + if (0 === $y) { // First key + $join[$i] = 'JOIN `'.$this->getNamespace().".database.relationships` c{$i} ON a.uid IS NOT NULL AND c{$i}.start = a.uid AND c{$i}.key = {$part}"; + } elseif ($y == $len - 1) { // Last key + $join[$i] .= 'JOIN `'.$this->getNamespace().".database.properties` e{$i} ON e{$i}.documentUid = {$prev}.end AND e{$i}.key = {$part} AND e{$i}.value {$operator} {$value}"; + } else { + $join[$i] .= 'JOIN `'.$this->getNamespace().".database.relationships` d{$i}{$y} ON d{$i}{$y}.start = {$prev}.end AND d{$i}{$y}.key = {$part}"; + $prev = 'd'.$i.$y; + } + } + + //$join[] = "JOIN `" . $this->getNamespace() . ".database.relationships` c{$i} ON a.uid IS NOT NULL AND c{$i}.start = a.uid AND c{$i}.key = {$path} + // JOIN `" . $this->getNamespace() . ".database.properties` d{$i} ON d{$i}.documentUid = c{$i}.end AND d{$i}.key = {$key} AND d{$i}.value {$operator} {$value}"; + } + } + + // Sorting + if(!empty($options['orderField'])) { + $orderPath = \explode('.', $options['orderField']); + $len = \count($orderPath); + $orderKey = 'order_b'; + $part = $this->getPDO()->quote(\implode('', $orderPath), PDO::PARAM_STR); + $orderSelect = "CASE WHEN {$orderKey}.key = {$part} THEN CAST({$orderKey}.value AS {$orderCastMap[$options['orderCast']]}) END AS sort_ff"; + + if (1 === $len) { + //if($path == "''") { // Handle direct attributes queries + $sorts[] = 'LEFT JOIN `'.$this->getNamespace().".database.properties` order_b ON a.uid IS NOT NULL AND order_b.documentUid = a.uid AND (order_b.key = {$part})"; + } else { // Handle direct child attributes queries + $prev = 'c'; + $orderKey = 'order_e'; + + foreach ($orderPath as $y => $part) { + $part = $this->getPDO()->quote($part, PDO::PARAM_STR); + $x = $y - 1; + + if (0 === $y) { // First key + $sorts[] = 'JOIN `'.$this->getNamespace().".database.relationships` order_c{$y} ON a.uid IS NOT NULL AND order_c{$y}.start = a.uid AND order_c{$y}.key = {$part}"; + } elseif ($y == $len - 1) { // Last key + $sorts[] .= 'JOIN `'.$this->getNamespace().".database.properties` order_e ON order_e.documentUid = order_{$prev}{$x}.end AND order_e.key = {$part}"; + } else { + $sorts[] .= 'JOIN `'.$this->getNamespace().".database.relationships` order_d{$y} ON order_d{$y}.start = order_{$prev}{$x}.end AND order_d{$y}.key = {$part}"; + $prev = 'd'; + } + } + } + } + else { + $orderSelect = 'a.uid AS sort_ff'; + } + + /* + * Workaround for a MySQL bug as reported here: + * https://bugs.mysql.com/bug.php?id=78485 + */ + $options['search'] = ($options['search'] === '*') ? '' : $options['search']; + + // Search + if (!empty($options['search'])) { // Handle free search + $where[] = 'LEFT JOIN `'.$this->getNamespace().".database.properties` b_search ON a.uid IS NOT NULL AND b_search.documentUid = a.uid AND b_search.primitive = 'string' + LEFT JOIN + `".$this->getNamespace().'.database.relationships` c_search ON c_search.start = b_search.documentUid + LEFT JOIN + `'.$this->getNamespace().".database.properties` d_search ON d_search.documentUid = c_search.end AND d_search.primitive = 'string' + \n"; + + $search = "AND (MATCH (b_search.value) AGAINST ({$this->getPDO()->quote($options['search'], PDO::PARAM_STR)} IN BOOLEAN MODE) + OR MATCH (d_search.value) AGAINST ({$this->getPDO()->quote($options['search'], PDO::PARAM_STR)} IN BOOLEAN MODE) + )"; + } + + $select = 'DISTINCT a.uid'; + $where = \implode("\n", $where); + $join = \implode("\n", $join); + $sorts = \implode("\n", $sorts); + $range = "LIMIT {$options['offset']}, {$options['limit']}"; + $roles = []; + + foreach (Authorization::getRoles() as $role) { + $roles[] = 'JSON_CONTAINS(REPLACE(a.permissions, \'{self}\', a.uid), \'"'.$role.'"\', \'$.read\')'; + } + + if (false === Authorization::$status) { // FIXME temporary solution (hopefully) + $roles = ['1=1']; + } + + $query = "SELECT %s, {$orderSelect} + FROM `".$this->getNamespace().".database.documents` a {$where}{$join}{$sorts} + WHERE status = 0 + {$search} + AND (".\implode('||', $roles).") + ORDER BY sort_ff {$options['orderType']} %s"; + + $st = $this->getPDO()->prepare(\sprintf($query, $select, $range)); + + $st->execute(); + + $results = ['data' => []]; + + // Get entire fields data for each id + foreach ($st->fetchAll() as $node) { + $results['data'][] = $node['uid']; + } + + $count = $this->getPDO()->prepare(\sprintf($query, 'count(DISTINCT a.uid) as sum', '')); + + $count->execute(); + + $count = $count->fetch(); + + $this->resetDebug(); + + $this + ->setDebug('query', \preg_replace('/\s+/', ' ', \sprintf($query, $select, $range))) + ->setDebug('time', \microtime(true) - $start) + ->setDebug('filters', \count($options['filters'])) + ->setDebug('joins', \substr_count($query, 'JOIN')) + ->setDebug('count', \count($results['data'])) + ->setDebug('sum', (int) $count['sum']) + ; + + return $results['data']; + } + + /** + * Get Collection. + * + * @param array $options + * + * @throws Exception + * + * @return int + */ + public function count(array $options) + { + $start = \microtime(true); + $where = []; + $join = []; + + $options = array_merge([ + 'attribute' => '', + 'filters' => [], + ], $options); + + // Filters + foreach ($options['filters'] as $i => $filter) { + $filter = $this->parseFilter($filter); + $key = $filter['key']; + $value = $filter['value']; + $operator = $filter['operator']; + $path = \explode('.', $key); + $original = $path; + + if (1 < \count($path)) { + $key = \array_pop($path); + } else { + $path = []; + } + + $key = $this->getPDO()->quote($key, PDO::PARAM_STR); + $value = $this->getPDO()->quote($value, PDO::PARAM_STR); + + if (empty($path)) { + //if($path == "''") { // Handle direct attributes queries + $where[] = 'JOIN `'.$this->getNamespace().".database.properties` b{$i} ON a.uid IS NOT NULL AND b{$i}.documentUid = a.uid AND (b{$i}.key = {$key} AND b{$i}.value {$operator} {$value})"; + } else { // Handle direct child attributes queries + $len = \count($original); + $prev = 'c'.$i; + + foreach ($original as $y => $part) { + $part = $this->getPDO()->quote($part, PDO::PARAM_STR); + + if (0 === $y) { // First key + $join[$i] = 'JOIN `'.$this->getNamespace().".database.relationships` c{$i} ON a.uid IS NOT NULL AND c{$i}.start = a.uid AND c{$i}.key = {$part}"; + } elseif ($y == $len - 1) { // Last key + $join[$i] .= 'JOIN `'.$this->getNamespace().".database.properties` e{$i} ON e{$i}.documentUid = {$prev}.end AND e{$i}.key = {$part} AND e{$i}.value {$operator} {$value}"; + } else { + $join[$i] .= 'JOIN `'.$this->getNamespace().".database.relationships` d{$i}{$y} ON d{$i}{$y}.start = {$prev}.end AND d{$i}{$y}.key = {$part}"; + $prev = 'd'.$i.$y; + } + } + } + } + + $where = \implode("\n", $where); + $join = \implode("\n", $join); + $attribute = $this->getPDO()->quote($options['attribute'], PDO::PARAM_STR); + $func = 'JOIN `'.$this->getNamespace().".database.properties` b_func ON a.uid IS NOT NULL + AND a.uid = b_func.documentUid + AND (b_func.key = {$attribute})"; + $roles = []; + + foreach (Authorization::getRoles() as $role) { + $roles[] = 'JSON_CONTAINS(REPLACE(a.permissions, \'{self}\', a.uid), \'"'.$role.'"\', \'$.read\')'; + } + + if (false === Authorization::$status) { // FIXME temporary solution (hopefully) + $roles = ['1=1']; + } + + $query = "SELECT SUM(b_func.value) as result + FROM `".$this->getNamespace().".database.documents` a {$where}{$join}{$func} + WHERE status = 0 + AND (".\implode('||', $roles).')'; + + $st = $this->getPDO()->prepare(\sprintf($query)); + + $st->execute(); + + $result = $st->fetch(); + + $this->resetDebug(); + + $this + ->setDebug('query', \preg_replace('/\s+/', ' ', \sprintf($query))) + ->setDebug('time', \microtime(true) - $start) + ->setDebug('filters', \count($options['filters'])) + ->setDebug('joins', \substr_count($query, 'JOIN')) + ; + + return (isset($result['result'])) ? (int)$result['result'] : 0; + } + + /** + * Get Unique Document ID. + * + * @return string + */ + public function getId(): string + { + return \uniqid(); + } + + /** + * Parse Filter. + * + * @param string $filter + * + * @return array + * + * @throws Exception + */ + protected function parseFilter($filter) + { + $operatorsMap = ['!=', '>=', '<=', '=', '>', '<']; // Do not edit order of this array + + //FIXME bug with >= <= operators + + $operator = null; + + foreach ($operatorsMap as $node) { + if (\strpos($filter, $node) !== false) { + $operator = $node; + break; + } + } + + if (empty($operator)) { + throw new Exception('Invalid operator'); + } + + $filter = \explode($operator, $filter); + + if (\count($filter) != 2) { + throw new Exception('Invalid filter expression'); + } + + return [ + 'key' => $filter[0], + 'value' => $filter[1], + 'operator' => $operator, + ]; + } + + /** + * Get Data Type. + * + * Check value data type. return value can be on of the following: + * string, integer, float, boolean, object, list or null + * + * @param $value + * + * @return string + * + * @throws \Exception + */ + protected function getDataType($value) + { + switch (\gettype($value)) { + + case 'string': + return self::DATA_TYPE_STRING; + break; + + case 'integer': + return self::DATA_TYPE_INTEGER; + break; + + case 'double': + return self::DATA_TYPE_FLOAT; + break; + + case 'boolean': + return self::DATA_TYPE_BOOLEAN; + break; + + case 'array': + if ((bool) \count(\array_filter(\array_keys($value), 'is_string'))) { + return self::DATA_TYPE_DICTIONARY; + } + + return self::DATA_TYPE_ARRAY; + break; + + case 'NULL': + return self::DATA_TYPE_NULL; + break; + } + + throw new Exception('Unknown data type: '.$value.' ('.\gettype($value).')'); + } + + /** + * @param string $key + * @param mixed $value + * + * @return $this + */ + public function setDebug(string $key, $value): self + { + $this->debug[$key] = $value; + + return $this; + } + + /** + * @return array + */ + public function getDebug(): array + { + return $this->debug; + } + + /** + * return $this;. + * + * @return void + */ + public function resetDebug(): void + { + $this->debug = []; + } + + /** + * @return PDO + * + * @throws Exception + */ + protected function getPDO(): PDO + { + return $this->register->get('db'); + } + + /** + * @throws Exception + * + * @return Client + */ + protected function getRedis(): Client + { + return $this->register->get('cache'); + } +} diff --git a/src/Database/Adapter/Redis.php b/src/Database/Adapter/Redis.php new file mode 100644 index 000000000..dc46e680d --- /dev/null +++ b/src/Database/Adapter/Redis.php @@ -0,0 +1,362 @@ +register = $register; + $this->adapter = $adapter; + } + + /** + * Create Collection + * + * @param Document $collection + * @param string $id + * + * @return bool + */ + public function createCollection(Document $collection, string $id): bool + { + return $this->adapter->createCollection($collection, $id); + } + + /** + * Delete Collection + * + * @param Document $collection + * + * @return bool + */ + public function deleteCollection(Document $collection): bool + { + return $this->adapter->deleteCollection($collection); + } + + /** + * Create Attribute + * + * @param Document $collection + * @param string $id + * @param string $type + * @param bool $array + * + * @return bool + */ + public function createAttribute(Document $collection, string $id, string $type, bool $array = false): bool + { + return $this->adapter->createAttribute($collection, $id, $type, $array); + } + + /** + * Delete Attribute + * + * @param Document $collection + * @param string $id + * @param bool $array + * + * @return bool + */ + public function deleteAttribute(Document $collection, string $id, bool $array = false): bool + { + return $this->adapter->deleteAttribute($collection, $id, $array); + } + + /** + * Create Index + * + * @param Document $collection + * @param string $id + * @param string $type + * @param array $attributes + * + * @return bool + */ + public function createIndex(Document $collection, string $id, string $type, array $attributes): bool + { + return $this->adapter->createIndex($collection, $id, $type, $attributes); + } + + /** + * Delete Index + * + * @param Document $collection + * @param string $id + * + * @return bool + */ + public function deleteIndex(Document $collection, string $id): bool + { + return $this->adapter->deleteIndex($collection, $id); + } + + /** + * Get Document. + * + * @param Document $collection + * @param string $id + * + * @return array + * + * @throws Exception + */ + public function getDocument(Document $collection, $id) + { + $output = \json_decode($this->getRedis()->get($this->getNamespace().':document-'.$id), true); + + if (!$output) { + $output = $this->adapter->getDocument($collection, $id); + $this->getRedis()->set($this->getNamespace().':document-'.$id, \json_encode($output, JSON_UNESCAPED_UNICODE)); + } + + $output = $this->parseRelations($output); + + return $output; + } + + /** + * @param $output + * + * @return mixed + * + * @throws Exception + */ + protected function parseRelations($output) + { + $keys = []; + + if (empty($output) || !isset($output['temp-relations'])) { + return $output; + } + + foreach ($output['temp-relations'] as $relationship) { + $keys[] = $this->getNamespace().':document-'.$relationship['end']; + } + + $nodes = (!empty($keys)) ? $this->getRedis()->mget($keys) : []; + + foreach ($output['temp-relations'] as $i => $relationship) { + $node = $relationship['end']; + + $node = (!empty($nodes[$i])) ? $this->parseRelations(\json_decode($nodes[$i], true)) : $this->getDocument(new Document([]), $node); + + if (empty($node)) { + continue; + } + + if ($relationship['array']) { + $output[$relationship['key']][] = $node; + } else { + $output[$relationship['key']] = $node; + } + } + + unset($output['temp-relations']); + + return $output; + } + + /** + * Create Document. + * + * @param Document $collection + * @param array $data + * @param array $unique + * + * @return array + * + * @throws Exception + */ + public function createDocument(Document $collection, array $data, array $unique = []) + { + $data = $this->adapter->createDocument($collection, $data, $unique); + + $this->getRedis()->expire($this->getNamespace().':document-'.$data['$id'], 0); + $this->getRedis()->expire($this->getNamespace().':document-'.$data['$id'], 0); + + return $data; + } + + /** + * Update Document. + * + * @param Document $collection + * @param string $id + * @param array $data + * + * @return array + * + * @throws Exception + */ + public function updateDocument(Document $collection, string $id, array $data) + { + $data = $this->adapter->updateDocument($collection, $id, $data); + + $this->getRedis()->expire($this->getNamespace().':document-'.$data['$id'], 0); + $this->getRedis()->expire($this->getNamespace().':document-'.$data['$id'], 0); + + return $data; + } + + /** + * Delete Document. + * + * @param Document $collection + * @param string $id + * + * @return array + * + * @throws Exception + */ + public function deleteDocument(Document $collection, string $id) + { + $data = $this->adapter->deleteDocument($collection, $id); + + $this->getRedis()->expire($this->getNamespace().':document-'.$id, 0); + $this->getRedis()->expire($this->getNamespace().':document-'.$id, 0); + + return $data; + } + + /** + * Delete Unique Key. + * + * @param $key + * + * @return array + * + * @throws Exception + */ + public function deleteUniqueKey($key) + { + $data = $this->adapter->deleteUniqueKey($key); + + return $data; + } + + /** + * Create Namespace. + * + * @param string $namespace + * + * @return bool + */ + public function createNamespace($namespace) + { + return $this->adapter->createNamespace($namespace); + } + + /** + * Delete Namespace. + * + * @param string $namespace + * + * @return bool + */ + public function deleteNamespace($namespace) + { + return $this->adapter->deleteNamespace($namespace); + } + + /** + * @param Document $collection + * @param array $options + * + * @return array + * + * @throws Exception + */ + public function find(Document $collection, array $options) + { + $data = $this->adapter->find($collection, $options); + $keys = []; + + foreach ($data as $node) { + $keys[] = $this->getNamespace().':document-'.$node; + } + + $nodes = (!empty($keys)) ? $this->getRedis()->mget($keys) : []; + + foreach ($data as $i => &$node) { + $temp = (!empty($nodes[$i])) ? $this->parseRelations(\json_decode($nodes[$i], true)) : $this->getDocument($collection, $node); + + if (!empty($temp)) { + $node = $temp; + } + } + + return $data; + } + + /** + * @param array $options + * + * @return int + * + * @throws Exception + */ + public function count(array $options) + { + return $this->adapter->count($options); + } + + /** + * @return array + */ + public function getDebug() + { + return $this->adapter->getDebug(); + } + + /** + * @throws Exception + * + * @return Client + */ + protected function getRedis(): Client + { + return $this->register->get('cache'); + } + + /** + * Set Namespace. + * + * Set namespace to divide different scope of data sets + * + * @param $namespace + * + * @return bool + * + * @throws Exception + */ + public function setNamespace($namespace) + { + $this->adapter->setNamespace($namespace); + + return parent::setNamespace($namespace); + } +} diff --git a/src/Database/Adapter/Relational.php b/src/Database/Adapter/Relational.php new file mode 100644 index 000000000..ae8908fb7 --- /dev/null +++ b/src/Database/Adapter/Relational.php @@ -0,0 +1,1148 @@ + true, + '$collection' => true, + '$permissions' => true + ]; + + /** + * @var bool + */ + protected $transaction = false; + + /** + * Constructor. + * + * Set connection and settings + * + * @param Registry $register + */ + public function __construct(PDO $pdo) + { + $this->pdo = $pdo; + } + + /** + * Create Collection + * + * @param Document $collection + * @param string $id + * + * @return bool + */ + public function createCollection(Document $collection, string $id): bool + { + if($collection->isEmpty()) { + throw new Exception('Missing Collection'); + } + + $rules = $collection->getAttribute('rules', []); + $indexes = $collection->getAttribute('indexes', []); + $PDOColumns = []; + $PDOIndexes = []; + + foreach ($rules as $rule) { /** @var Document $attribute */ + $key = $rule->getAttribute('key'); + $type = $rule->getAttribute('type'); + $array = $rule->getAttribute('array'); + + if($array) { + $this->createAttribute($collection, $key, $type, $array); + continue; + } + + $PDOColumns[] = $this->getAttributeType($key, $type); + } + + foreach ($indexes as $index) { /** @var Document $index */ + $type = $index->getAttribute('type', ''); + $attributes = $index->getAttribute('attributes', []); + + $PDOIndexes[] = $this->getIndexType($index->getId(), $type, $attributes); + } + + $PDOColumns = (!empty($PDOColumns)) ? implode(",\n", $PDOColumns) . ",\n" : ''; + $PDOIndexes = (!empty($PDOIndexes)) ? ",\n" . implode(",\n", $PDOIndexes) : ''; + + $query = $this->getPDO()->prepare('CREATE TABLE `app_'.$this->getNamespace().'.collection.'.$id.'` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `uid` varchar(45) DEFAULT NULL, + `createdAt` datetime DEFAULT NULL, + `updatedAt` datetime DEFAULT NULL, + `permissions` longtext DEFAULT NULL, + '.$PDOColumns.' + PRIMARY KEY (`id`), + UNIQUE KEY `index1` (`uid`) + '.$PDOIndexes.' + ) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4; + '); + + if (!$query->execute()) { + return false; + } + + return true; + } + + /** + * Delete Collection + * + * @param Document $collection + * + * @return bool + */ + public function deleteCollection(Document $collection): bool + { + $query = $this->getPDO()->prepare('DROP TABLE `app_'.$this->getNamespace().'.collection.'.$collection->getId().'`;'); + + if (!$query->execute()) { + return false; + } + + $rules = $collection->getAttribute('rules', []); + + foreach ($rules as $attribute) { /** @var Document $attribute */ + $key = $attribute->getAttribute('key'); + $array = $attribute->getAttribute('array'); + + if($array) { + $this->deleteAttribute($collection, $key, $array); + } + } + + return true; + } + + /** + * Create Attribute + * + * @param Document $collection + * @param string $id + * @param string $type + * @param bool $array + * + * @return bool + */ + public function createAttribute(Document $collection, string $id, string $type, bool $array = false): bool + { + if($array) { + $query = $this->getPDO()->prepare('CREATE TABLE `app_'.$this->getNamespace().'.collection.'.$collection->getId().'.'.$id.'` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `uid` varchar(45) DEFAULT NULL, + '.$this->getAttributeType($id, $type).', + PRIMARY KEY (`id`), + KEY `index1` (`uid`) + ) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4; + ;'); + } + else { + $query = $this->getPDO()->prepare('ALTER TABLE `app_'.$this->getNamespace().'.collection.'.$collection->getId().'` + ADD COLUMN '.$this->getAttributeType($id, $type).';'); + } + + if (!$query->execute()) { + return false; + } + + return true; + } + + /** + * Delete Attribute + * + * @param Document $collection + * @param string $id + * @param bool $array + * + * @return bool + */ + public function deleteAttribute(Document $collection, string $id, bool $array = false): bool + { + if($array) { + $query = $this->getPDO()->prepare('DROP TABLE `app_'.$this->getNamespace().'.collection.'.$collection->getId().'.'.$id.'`;'); + } + else { + $query = $this->getPDO()->prepare('ALTER TABLE `app_'.$this->getNamespace().'.collection.'.$collection->getId().'` + DROP COLUMN `col_'.$id.'`;'); + } + + if (!$query->execute()) { + return false; + } + + return true; + } + + /** + * Create Index + * + * @param Document $collection + * @param string $id + * @param string $type + * @param array $attributes + * + * @return bool + */ + public function createIndex(Document $collection, string $id, string $type, array $attributes): bool + { + $query = $this->getPDO()->prepare('ALTER TABLE `app_'.$this->getNamespace().'.collection.'.$collection->getId().'` + ADD '.$this->getIndexType($id, $type, $attributes) . ';'); + + if (!$query->execute()) { + return false; + } + + return true; + } + + /** + * Delete Index + * + * @param Document $collection + * @param string $id + * + * @return bool + */ + public function deleteIndex(Document $collection, string $id): bool + { + $query = $this->getPDO()->prepare('ALTER TABLE `app_'.$this->getNamespace().'.collection.'.$collection->getId().'` + DROP INDEX `index_'.$id.'`;'); + + if (!$query->execute()) { + return false; + } + + return true; + } + + /** + * Get Document. + * + * @param Document $collection + * @param string $id + * + * @return array + * + * @throws Exception + */ + public function getDocument(Document $collection, $id) + { + // Get fields abstraction + $st = $this->getPDO()->prepare('SELECT * FROM `app_'.$this->getNamespace().'.collection.'.$collection->getId().'` documents + WHERE documents.uid = :uid; + '); + + $st->bindValue(':uid', $id, PDO::PARAM_STR); + + $st->execute(); + + $document = $st->fetch(); + + if (empty($document)) { // Not Found + return []; + } + + $rules = $collection->getAttribute('rules', []); + $data = []; + + $data['$id'] = (isset($document['uid'])) ? $document['uid'] : null; + $data['$collection'] = $collection->getId(); + $data['$permissions'] = (isset($document['permissions'])) ? json_decode($document['permissions'], true) : new stdClass; + + foreach($rules as $i => $rule) { /** @var Document $rule */ + $key = $rule->getAttribute('key'); + $type = $rule->getAttribute('type'); + $array = $rule->getAttribute('array'); + $list = $rule->getAttribute('list', []); + $value = (isset($document['col_'.$key])) ? $document['col_'.$key] : null; + + if(array_key_exists($key, $this->protected)) { + continue; + } + + if($array) { + $st = $this->getPDO()->prepare('SELECT * FROM `app_'.$this->getNamespace().'.collection.'.$collection->getId().'.'.$key.'` documents + WHERE documents.uid = :uid; + '); + + $st->bindValue(':uid', $id, PDO::PARAM_STR); + + $st->execute(); + + $elements = $st->fetchAll(); + + $value = []; + + foreach ($elements as $element) { + $value[] = (isset($element['col_'.$key])) ? $element['col_'.$key] : null; + } + } + + $value = ($array) ? $value : [$value]; + + if($array && !\is_array($value)) { + continue; + } + + foreach($value as $i => $element) { + switch($type) { + case Database::VAR_INTEGER: + $value[$i] = (int)$element; + break; + case Database::VAR_FLOAT: + case Database::VAR_NUMERIC: + $value[$i] = (float)$element; + break; + case Database::VAR_BOOLEAN: + $value[$i] = ($element === '1'); + break; + case Database::VAR_DOCUMENT: + $value[$i] = $this->getDatabase()->getDocument(array_pop(array_reverse($list)), $element); + break; + } + } + + $data[$key] = ($array) ? $value : $value[0]; + } + + return $data; + } + + /** + * Create Document. + * + * @param Document $collection + * @param array $data + * @param array $unique + * + * @throws \Exception + * + * @return array + */ + public function createDocument(Document $collection, array $data, array $unique = []) + { + $data['$id'] = $this->getId(); + $data['$permissions'] = (!isset($data['$permissions'])) ? [] : $data['$permissions']; + $columns = []; + $rules = $collection->getAttribute('rules', []); + + foreach($rules as $i => $rule) { + $key = $rule->getAttribute('key'); + $type = $rule->getAttribute('type'); + $array = $rule->getAttribute('array'); + + if(array_key_exists($key, $this->protected) || $array) { + continue; + } + + $columns[] = '`col_'.$key.'` = :col_'.$i; + } + + $columns = (!empty($columns)) ? ', '.implode(', ', $columns) : ''; + + /** + * Check Unique Keys + */ + //throw new Duplicate('Duplicated Property'); + + $this->beginTransaction(); + + $st = $this->getPDO()->prepare('INSERT INTO `app_'.$this->getNamespace().'.collection.'.$collection->getId().'` + SET uid = :uid, createdAt = :createdAt, updatedAt = :updatedAt, permissions = :permissions'.$columns.'; + '); + + $st->bindValue(':uid', $data['$id'], PDO::PARAM_STR); + $st->bindValue(':createdAt', \date('Y-m-d H:i:s', \time()), PDO::PARAM_STR); + $st->bindValue(':updatedAt', \date('Y-m-d H:i:s', \time()), PDO::PARAM_STR); + $st->bindValue(':permissions', \json_encode($data['$permissions']), PDO::PARAM_STR); + + foreach($rules as $i => $rule) { /** @var Document $rule */ + $key = $rule->getAttribute('key'); + $type = $rule->getAttribute('type'); + $array = $rule->getAttribute('array'); + $list = $rule->getAttribute('list', []); + $value = (isset($data[$key])) ? $data[$key] : null; + + if(array_key_exists($key, $this->protected)) { + continue; + } + + $value = ($array) ? $value : [$value]; + $value = ($array && !\is_array($value)) ? [] : $value; + + foreach ($value as $x => $element) { + switch($type) { + case Database::VAR_DOCUMENT: + $id = (isset($element['$id'])) ? $element['$id'] : null; + + $value[$x] = (empty($id)) + ? $this->getDatabase()->createDocument(array_pop(array_reverse($list)), $element)->getId() + : $this->getDatabase()->updateDocument(array_pop(array_reverse($list)), $id, $element)->getId(); + break; + } + } + + $value = ($array) ? $value : $value[0]; + + if($array) { + if(!is_array($value)) { + continue; + } + + foreach ($value as $element) { + $stArray = $this->getPDO()->prepare('INSERT INTO `app_'.$this->getNamespace().'.collection.'.$collection->getId().'.'.$key.'` + SET uid = :uid, `col_'.$key.'` = :col_x; + '); + + $stArray->bindValue(':uid', $data['$id'], PDO::PARAM_STR); + $stArray->bindValue(':col_x', $element, $this->getDataType($type)); + $stArray->execute(); + } + + continue; + } + + if(!$array) { + $st->bindValue(':col_'.$i, $value, $this->getDataType($type)); + } + } + + try { + $st->execute(); + } catch (\Throwable $th) { + switch ($th->getCode()) { + case '23000': + throw new Duplicate('Duplicated documents'); + break; + } + + throw $th; + } + + $this->commit(); + + //TODO remove this dependency (check if related to nested documents) + // $this->getRedis()->expire($this->getNamespace().':document-'.$data['$id'], 0); + // $this->getRedis()->expire($this->getNamespace().':document-'.$data['$id'], 0); + + return $data; + } + + /** + * Update Document. + * + * @param Document $collection + * @param string $id + * @param array $data + * + * @return array + * + * @throws Exception + */ + public function updateDocument(Document $collection, string $id, array $data) + { + if(!isset($data['$id']) || empty($data['$id']) || empty($id)) { + throw new Exception('$id is missing'); + } + + $data['$permissions'] = (!isset($data['$permissions'])) ? [] : $data['$permissions']; + $columns = []; + $rules = $collection->getAttribute('rules', []); + + foreach($rules as $i => $rule) { + $key = $rule->getAttribute('key'); + $type = $rule->getAttribute('type'); + $array = $rule->getAttribute('array'); + + if(array_key_exists($key, $this->protected) || $array) { + continue; + } + + $columns[] = '`col_'.$key.'` = :col_'.$i; + } + + $columns = (!empty($columns)) ? ', '.implode(', ', $columns) : ''; + + /** + * Check Unique Keys + */ + //throw new Duplicate('Duplicated Property'); + + $this->beginTransaction(); + + $st = $this->getPDO()->prepare('UPDATE `app_'.$this->getNamespace().'.collection.'.$collection->getId().'` + SET updatedAt = :updatedAt, permissions = :permissions'.$columns.' + WHERE uid = :uid; + '); + + $st->bindValue(':uid', $data['$id'], PDO::PARAM_STR); + $st->bindValue(':updatedAt', \date('Y-m-d H:i:s', \time()), PDO::PARAM_STR); + $st->bindValue(':permissions', \json_encode($data['$permissions']), PDO::PARAM_STR); + + foreach($rules as $i => $rule) { /** @var Document $rule */ + $key = $rule->getAttribute('key'); + $type = $rule->getAttribute('type'); + $array = $rule->getAttribute('array'); + $list = $rule->getAttribute('list', []); + $value = (isset($data[$key])) ? $data[$key] : null; + + if(array_key_exists($key, $this->protected)) { + continue; + } + + $value = ($array) ? $value : [$value]; + $value = ($array && !\is_array($value)) ? [] : $value; + + foreach ($value as $x => $element) { + switch($type) { + case Database::VAR_DOCUMENT: + $id = (isset($element['$id'])) ? $element['$id'] : null; + $value[$x] = (empty($id)) + ? $this->getDatabase()->createDocument(array_pop(array_reverse($list)), $element)->getId() + : $this->getDatabase()->updateDocument(array_pop(array_reverse($list)), $id, $element)->getId(); + break; + } + } + + $value = ($array) ? $value : $value[0]; + + if($array) { + if(!is_array($value)) { + continue; + } + + $stArray = $this->getPDO()->prepare('DELETE FROM `app_'.$this->getNamespace().'.collection.'.$collection->getId().'.'.$key.'` + WHERE uid = :uid; + '); + + $stArray->bindValue(':uid', $data['$id'], PDO::PARAM_STR); + $stArray->execute(); + + foreach ($value as $element) { + $stArray = $this->getPDO()->prepare('INSERT INTO `app_'.$this->getNamespace().'.collection.'.$collection->getId().'.'.$key.'` + SET uid = :uid, `col_'.$key.'` = :col_x; + '); + + $stArray->bindValue(':uid', $data['$id'], PDO::PARAM_STR); + $stArray->bindValue(':col_x', $element, $this->getDataType($type)); + $stArray->execute(); + } + + continue; + } + + if(!$array) { + $st->bindValue(':col_'.$i, $value, $this->getDataType($type)); + } + } + + try { + $st->execute(); + } catch (\Throwable $th) { + switch ($th->getCode()) { + case '23000': + throw new Duplicate('Duplicated documents'); + break; + } + + throw $th; + } + + $this->commit(); + + //TODO remove this dependency (check if related to nested documents) + // $this->getRedis()->expire($this->getNamespace().':document-'.$data['$id'], 0); + // $this->getRedis()->expire($this->getNamespace().':document-'.$data['$id'], 0); + + return $data; + } + + /** + * Delete Document. + * + * @param Document $collection + * @param string $id + * + * @return array + * + * @throws Exception + */ + public function deleteDocument(Document $collection, string $id) + { + $rules = $collection->getAttribute('rules', []); + + $this->beginTransaction(); + + $st = $this->getPDO()->prepare('DELETE FROM `app_'.$this->getNamespace().'.collection.'.$collection->getId().'` + WHERE uid = :uid + '); + + $st->bindValue(':uid', $id, PDO::PARAM_STR); + + $st->execute(); + + foreach($rules as $i => $rule) { /** @var Document $rule */ + $key = $rule->getAttribute('key'); + $array = $rule->getAttribute('array'); + + if($array) { + $stArray = $this->getPDO()->prepare('DELETE FROM `app_'.$this->getNamespace().'.collection.'.$collection->getId().'.'.$key.'` + WHERE uid = :uid; + '); + + $stArray->bindValue(':uid', $id, PDO::PARAM_STR); + $stArray->execute(); + } + } + + $st->execute(); + + $this->commit(); + + return []; + } + + /** + * Create Namespace. + * + * @param $namespace + * + * @throws Exception + * + * @return bool + */ + public function createNamespace($namespace) + { + if (empty($namespace)) { + throw new Exception('Empty namespace'); + } + + $audit = 'app_'.$namespace.'.audit.audit'; + $abuse = 'app_'.$namespace.'.abuse.abuse'; + + /** + * 1. Itterate default collections + * 2. Create collection + * 3. Create all regular and array fields + * 4. Create all indexes + * 5. Create audit / abuse tables + */ + + foreach($this->getMocks() as $collection) { /** @var Document $collection */ + $this->createCollection($collection, $collection->getId()); + } + + try { + $this->getPDO()->prepare('CREATE TABLE `'.$audit.'` LIKE `template.audit.audit`;')->execute(); + $this->getPDO()->prepare('CREATE TABLE `'.$abuse.'` LIKE `template.abuse.abuse`;')->execute(); + } catch (Exception $e) { + throw $e; + } + + return true; + } + + /** + * Delete Namespace. + * + * @param $namespace + * + * @throws Exception + * + * @return bool + */ + public function deleteNamespace($namespace) + { + if (empty($namespace)) { + throw new Exception('Empty namespace'); + } + + $audit = 'app_'.$namespace.'.audit.audit'; + $abuse = 'app_'.$namespace.'.abuse.abuse'; + + foreach($this->getMocks() as $collection) { /** @var Document $collection */ + $this->deleteCollection($collection, $collection->getId()); + } + + // TODO Delete all custom collections + + try { + $this->getPDO()->prepare('DROP TABLE `'.$audit.'`;')->execute(); + $this->getPDO()->prepare('DROP TABLE `'.$abuse.'`;')->execute(); + } catch (Exception $e) { + throw $e; + } + + return true; + } + + /** + * Find + * + * @param Document $collection + * @param array $options + * + * @throws Exception + * + * @return array + */ + public function find(Document $collection, array $options) + { + $start = \microtime(true); + + $rules = $collection->getAttribute('rules', []); /** @var Document[] $rules */ + $where = []; + $join = []; + $sorts = []; + $columns = []; + $search = ''; + + foreach ($rules as $key => $rule) { + $columns[$rule->getAttribute('key', '')] = $rule; + } + + var_dump(array_keys($columns)); + + $options['orderField'] = (empty($options['orderField']) || $options['orderField'] === '$id') // Set default order field + ? '' + : $options['orderField']; + + + if (!\in_array($options['orderType'], ['DESC', 'ASC'])) { + throw new Exception('Invalid order type'); + } + + if(!array_key_exists($options['orderField'], $columns) && !empty($options['orderField'])) { + throw new Exception('Unknown oreder field: '.$options['orderField']); + } + + $options['orderField'] = (!empty($options['orderField'])) ? 'col_'.$options['orderField'] : 'a.uid'; + + foreach ($options['filters'] as $i => $filter) { // Filters + $filter = $this->parseFilter($filter); + $key = $filter['key']; + $value = $filter['value']; + $operator = $filter['operator']; + + $path = \explode('.', $key); + $original = $path; + + if (1 < \count($path)) { + $key = \array_pop($path); + } + else { + $path = []; + } + + $value = $this->getPDO()->quote($value, PDO::PARAM_STR); + + $options['offset'] = (int) $options['offset']; + $options['limit'] = (int) $options['limit']; + + if (empty($path)) { + if(!array_key_exists($key, $columns)) { + throw new Exception('Unknown key: '.$key); + } + + var_dump($columns[$key]); + var_dump($columns[$key]->getAttribute('array')); + if($columns[$key]->getAttribute('array') === true) { + $join[] = 'JOIN `app_'.$this->getNamespace().'.collection.'.$collection->getId().'.'.$key.'` a'.$i.' + ON a.uid IS NOT NULL AND a'.$i.'.uid = a.uid'; + $where[] = '(a'.$i.'.col_'.$key.' '.$operator.' '.$value.')'; + $where[] = '(a'.$i.'.col_'.$key.' '.$operator.' '.$value.')'; + //AND a.uid NOT IN (SELECT a2.uid FROM `app_'.$this->getNamespace().'.collection.'.$collection->getId().'.'.$key.'` a'.$i.' a'.$i.' WHERE (a'.$i.'.col_langauges = '.$value.')) + + } + else { + $where[] = "(col_{$key} {$operator} {$value})"; + } + } + else { // Handle relationships + $len = \count($original); + $prev = 'c'.$i; + + foreach ($original as $y => $part) { + $part = $this->getPDO()->quote($part, PDO::PARAM_STR); + + if (0 === $y) { // First key + $join[$i] = 'JOIN `'.$this->getNamespace().".database.relationships` c{$i} ON a.uid IS NOT NULL AND c{$i}.start = a.uid AND c{$i}.key = {$part}"; + } elseif ($y == $len - 1) { // Last key + $join[$i] .= 'JOIN `'.$this->getNamespace().".database.properties` e{$i} ON e{$i}.documentUid = {$prev}.end AND e{$i}.key = {$part} AND e{$i}.value {$operator} {$value}"; + } else { + $join[$i] .= 'JOIN `'.$this->getNamespace().".database.relationships` d{$i}{$y} ON d{$i}{$y}.start = {$prev}.end AND d{$i}{$y}.key = {$part}"; + $prev = 'd'.$i.$y; + } + } + } + } + + $select = 'DISTINCT a.uid'; + $where = \implode(" AND \n", $where); + $join = \implode("\n", $join); + $sorts = \implode("\n", $sorts); + $range = "LIMIT {$options['offset']}, {$options['limit']}"; + $roles = []; + + if (false === Authorization::$status) { // FIXME temporary solution (hopefully) + $roles = ['1=1']; + } + else { + foreach (Authorization::getRoles() as $role) { + $roles[] = 'JSON_CONTAINS(REPLACE(a.permissions, \'{self}\', a.uid), \'"'.$role.'"\', \'$.read\')'; + } + } + + $query = 'SELECT %s + FROM `app_'.$this->getNamespace().'.collection.'.$collection->getId().'` a + '.$join.' + '.$sorts.' + WHERE + '.$where.' + '.$search.' + AND ('.\implode('||', $roles).') + ORDER BY '.$options['orderField'].' '.$options['orderType'].' %s'; + + var_dump(\preg_replace('/\s+/', ' ', \sprintf($query, $select, $range))); + + return []; + + $st = $this->getPDO()->prepare(\sprintf($query, $select, $range)); + + $st->execute(); + + $results = ['data' => []]; + + // Get entire fields data for each id + foreach ($st->fetchAll() as $node) { + $results['data'][] = $node['uid']; + } + + $this->resetDebug(); + + $this + ->setDebug('query', \preg_replace('/\s+/', ' ', \sprintf($query, $select, $range))) + ->setDebug('time', \microtime(true) - $start) + ->setDebug('filters', \count($options['filters'])) + ->setDebug('joins', \substr_count($query, 'JOIN')) + ->setDebug('count', \count($results['data'])) + ->setDebug('sum', (int) 0) + ; + + return $results['data']; + } + + /** + * Count + * + * @param array $options + * + * @throws Exception + * + * @return int + */ + public function count(array $options) + { + return 0; + } + + /** + * Parse Filter. + * + * @param string $filter + * + * @return array + * + * @throws Exception + */ + protected function parseFilter($filter) + { + $operatorsMap = ['!=', '>=', '<=', '=', '>', '<']; // Do not edit order of this array + + //FIXME bug with >= <= operators + + $operator = null; + + foreach ($operatorsMap as $node) { + if (\strpos($filter, $node) !== false) { + $operator = $node; + break; + } + } + + if (empty($operator)) { + throw new Exception('Invalid operator'); + } + + $filter = \explode($operator, $filter); + + if (\count($filter) != 2) { + throw new Exception('Invalid filter expression'); + } + + return [ + 'key' => $filter[0], + 'value' => $filter[1], + 'operator' => $operator, + ]; + } + + /** + * Get PDO Data Type. + * + * @param $type + * + * @return string + * + * @throws \Exception + */ + protected function getDataType($type) + { + switch ($type) { + case Database::VAR_TEXT: + case Database::VAR_URL: + case Database::VAR_KEY: + case Database::VAR_IPV4: + case Database::VAR_IPV6: + case Database::VAR_EMAIL: + case Database::VAR_FLOAT: + case Database::VAR_NUMERIC: + return PDO::PARAM_STR; + break; + + case Database::VAR_DOCUMENT: + return PDO::PARAM_STR; + break; + + case Database::VAR_INTEGER: + return PDO::PARAM_INT; + break; + + case Database::VAR_BOOLEAN: + return PDO::PARAM_BOOL; + break; + + default: + throw new Exception('Unsupported attribute: '.$type); + break; + } + } + + /** + * Get Column + * + * @var string $key + * @var string $type + * + * @return string + */ + protected function getAttributeType(string $key, string $type): string + { + switch ($type) { + case Database::VAR_TEXT: + case Database::VAR_URL: + return '`col_'.$key.'` TEXT NULL'; + break; + + case Database::VAR_KEY: + case Database::VAR_DOCUMENT: + return '`col_'.$key.'` VARCHAR(36) NULL'; + break; + + case Database::VAR_IPV4: + //return '`col_'.$key.'` INT UNSIGNED NULL'; + return '`col_'.$key.'` VARCHAR(15) NULL'; + break; + + case Database::VAR_IPV6: + //return '`col_'.$key.'` BINARY(16) NULL'; + return '`col_'.$key.'` VARCHAR(39) NULL'; + break; + + case Database::VAR_EMAIL: + return '`col_'.$key.'` VARCHAR(255) NULL'; + break; + + case Database::VAR_INTEGER: + return '`col_'.$key.'` INT NULL'; + break; + + case Database::VAR_FLOAT: + case Database::VAR_NUMERIC: + return '`col_'.$key.'` FLOAT NULL'; + break; + + case Database::VAR_BOOLEAN: + return '`col_'.$key.'` BOOLEAN NULL'; + break; + + default: + throw new Exception('Unsupported attribute: '.$type); + break; + } + } + + /** + * @var string $type + * + * @throws Exceptions + * + * @return string + */ + protected function getIndexType(string $key, string $type, array $attributes): string + { + $index = ''; + $columns = []; + + foreach ($attributes as $attribute) { + $columns[] = '`col_'.$attribute.'`(32) ASC'; // TODO custom size limit per type + } + + switch ($type) { + case Database::INDEX_KEY: + $index = 'INDEX'; + break; + + case Database::INDEX_FULLTEXT: + $index = 'FULLTEXT INDEX'; + break; + + case Database::INDEX_UNIQUE: + $index = 'UNIQUE INDEX'; + break; + + case Database::INDEX_SPATIAL: + $index = 'SPATIAL INDEX'; + break; + + default: + throw new Exception('Unsupported indext type'); + break; + } + + return $index .' `index_'.$key.'` ('.implode(',', $columns).')'; + } + + /** + * @return false + */ + protected function beginTransaction(): bool + { + if($this->transaction) { + return false; + } + + $this->transaction = true; + + $this->getPDO()->beginTransaction(); + + return true; + } + + /** + * @return false + */ + protected function commit(): bool + { + if(!$this->transaction) { + return false; + } + + $this->getPDO()->commit(); + + $this->transaction = false; + + return true; + } + + /** + * @return PDO + * + * @throws Exception + */ + protected function getPDO() + { + return $this->pdo; + } + + public function deleteUniqueKey($key) + { + return []; + } +} + + +// // Sorting +// $orderPath = \explode('.', $options['orderField']); +// $len = \count($orderPath); +// $orderKey = 'order_b'; +// $part = $this->getPDO()->quote(\implode('', $orderPath), PDO::PARAM_STR); +// $orderSelect = "CASE WHEN {$orderKey}.key = {$part} THEN CAST({$orderKey}.value AS {$orderCastMap[$options['orderCast']]}) END AS sort_ff"; + +// if (1 === $len) { +// //if($path == "''") { // Handle direct attributes queries +// $sorts[] = 'LEFT JOIN `'.$this->getNamespace().".database.properties` order_b ON a.uid IS NOT NULL AND order_b.documentUid = a.uid AND (order_b.key = {$part})"; +// } else { // Handle direct child attributes queries +// $prev = 'c'; +// $orderKey = 'order_e'; + +// foreach ($orderPath as $y => $part) { +// $part = $this->getPDO()->quote($part, PDO::PARAM_STR); +// $x = $y - 1; + +// if (0 === $y) { // First key +// $sorts[] = 'JOIN `'.$this->getNamespace().".database.relationships` order_c{$y} ON a.uid IS NOT NULL AND order_c{$y}.start = a.uid AND order_c{$y}.key = {$part}"; +// } elseif ($y == $len - 1) { // Last key +// $sorts[] .= 'JOIN `'.$this->getNamespace().".database.properties` order_e ON order_e.documentUid = order_{$prev}{$x}.end AND order_e.key = {$part}"; +// } else { +// $sorts[] .= 'JOIN `'.$this->getNamespace().".database.relationships` order_d{$y} ON order_d{$y}.start = order_{$prev}{$x}.end AND order_d{$y}.key = {$part}"; +// $prev = 'd'; +// } +// } +// } + +/* + * Workaround for a MySQL bug as reported here: + * https://bugs.mysql.com/bug.php?id=78485 + */ +// $options['search'] = ($options['search'] === '*') ? '' : $options['search']; + +// Search +// if (!empty($options['search'])) { // Handle free search +// $where[] = 'LEFT JOIN `'.$this->getNamespace().".database.properties` b_search ON a.uid IS NOT NULL AND b_search.documentUid = a.uid AND b_search.primitive = 'string' +// LEFT JOIN +// `".$this->getNamespace().'.database.relationships` c_search ON c_search.start = b_search.documentUid +// LEFT JOIN +// `'.$this->getNamespace().".database.properties` d_search ON d_search.documentUid = c_search.end AND d_search.primitive = 'string' +// \n"; + +// $search = "AND (MATCH (b_search.value) AGAINST ({$this->getPDO()->quote($options['search'], PDO::PARAM_STR)} IN BOOLEAN MODE) +// OR MATCH (d_search.value) AGAINST ({$this->getPDO()->quote($options['search'], PDO::PARAM_STR)} IN BOOLEAN MODE) +// )"; +// } + +// $count = $this->getPDO()->prepare(\sprintf($query, 'count(DISTINCT a.uid) as sum', '')); +// $count->execute(); +// $count = $count->fetch(); \ No newline at end of file diff --git a/src/Database/Database.php b/src/Database/Database.php new file mode 100644 index 000000000..890242c7d --- /dev/null +++ b/src/Database/Database.php @@ -0,0 +1,691 @@ +adapter = $adapter; + + $this->adapter->setDatabase($this); + + return $this; + } + + /** + * Set Namespace. + * + * Set namespace to divide different scope of data sets + * + * @param $namespace + * + * @return $this + * + * @throws Exception + */ + public function setNamespace($namespace) + { + $this->adapter->setNamespace($namespace); + + return $this; + } + + /** + * Get Namespace. + * + * Get namespace of current set scope + * + * @return string + * + * @throws Exception + */ + public function getNamespace() + { + return $this->adapter->getNamespace(); + } + + /** + * Create Namespace. + * + * @param string $namespace + * + * @return bool + */ + public function createNamespace($namespace) + { + return $this->adapter->createNamespace($namespace); + } + + /** + * Delete Namespace. + * + * @param string $namespace + * + * @return bool + */ + public function deleteNamespace($namespace) + { + return $this->adapter->deleteNamespace($namespace); + } + + /** + * @param string $collection + * @param array $options + * + * @return Document[] + */ + public function find(string $collection, array $options) + { + $options = \array_merge([ + 'offset' => 0, + 'limit' => 15, + 'search' => '', + 'relations' => true, + 'orderField' => '', + 'orderType' => 'ASC', + 'orderCast' => 'int', + 'filters' => [], + ], $options); + + $results = $this->adapter->find($this->getDocument(self::COLLECTION_COLLECTIONS, $collection), $options); + + foreach ($results as &$node) { + $node = $this->decode(new Document($node)); + } + + return $results; + } + + /** + * @param string $collection + * @param array $options + * + * @return Document + */ + public function findFirst(string $collection, array $options) + { + $results = $this->find($collection, $options); + return \reset($results); + } + + /** + * @param string $collection + * @param array $options + * + * @return Document + */ + public function findLast(string $collection, array $options) + { + $results = $this->find($collection, $options); + return \end($results); + } + + /** + * @param array $options + * + * @return int + */ + public function count(array $options) + { + $options = \array_merge([ + 'filters' => [], + ], $options); + + $results = $this->adapter->count($options); + + return $results; + } + + /** + * Create Collection + * + * @param string $id + * + * @return bool + */ + public function createCollection(string $id): bool + { + return $this->adapter->createCollection($this->getDocument(self::COLLECTION_COLLECTIONS, $id), $id); + } + + /** + * Delete Collection + * + * @param string $id + * + * @return bool + */ + public function deleteCollection(string $id): bool + { + return $this->adapter->deleteCollection($this->getDocument(self::COLLECTION_COLLECTIONS, $id)); + } + + /** + * Create Attribute + * + * @param string $collection + * @param string $id + * @param string $type + * @param bool $array + * + * @return bool + */ + public function createAttribute(string $collection, string $id, string $type, bool $array = false): bool + { + $collection = $this->getDocument(self::COLLECTION_COLLECTIONS, $collection); + return $this->adapter->createAttribute($collection, $id, $type, $array); + } + + /** + * Delete Attribute + * + * @param string $collection + * @param string $id + * @param bool $array + * + * @return bool + */ + public function deleteAttribute(string $collection, string $id, bool $array): bool + { + return $this->adapter->deleteAttribute($this->getDocument(self::COLLECTION_COLLECTIONS, $collection), $id, $array); + } + + /** + * Create Index + * + * @param string $collection + * @param string $id + * @param string $type + * @param array $attributes + * + * @return bool + */ + public function createIndex(string $collection, string $id, string $type, array $attributes): bool + { + $collection = $this->getDocument(self::COLLECTION_COLLECTIONS, $collection); + return $this->adapter->createIndex($collection, $id, $type, $attributes); + } + + /** + * Delete Index + * + * @param string $collection + * @param string $id + * + * @return bool + */ + public function deleteIndex(string $collection, string $id): bool + { + $collection = $this->getDocument(self::COLLECTION_COLLECTIONS, $collection); + return $this->adapter->deleteIndex($collection, $id); + } + + /** + * @param string $collection + * @param string $id + * @param bool $mock is mocked data allowed? + * @param bool $decode + * + * @return Document + */ + public function getDocument($collection, $id, bool $mock = true, bool $decode = true) + { + if (\is_null($id)) { + return new Document([]); + } + + if(isset(self::$cache[$this->getNamespace().'/'.$collection.'/'.$id])) { + self::$cache[$this->getNamespace().'/'.$collection.'/'.$id]++; + } + else { + self::$cache[$this->getNamespace().'/'.$collection.'/'.$id] = 0; + } + + if($mock === true + && isset($this->mocks[$id])) { + $document = $this->mocks[$id]; + } + else { + $document = new Document($this->adapter->getDocument($this->getDocument(self::COLLECTION_COLLECTIONS, $collection), $id)); + } + + $validator = new Authorization($document, 'read'); + + if (!$validator->isValid($document->getPermissions())) { // Check if user has read access to this document + return new Document(); + } + + $document = ($decode) ? $this->decode($document) : $document; + + return $document; + } + + /** + * @param string $collection + * @param array $data + * @param array $unique + * + * @return Document|bool + * + * @throws AuthorizationException + * @throws StructureException + */ + public function createDocument(string $collection, array $data, array $unique = []) + { + if(isset($data['$id'])) { + throw new Exception('Use update method instead of create'); + } + + $document = new Document($data); + + $validator = new Authorization($document, 'write'); + + if (!$validator->isValid($document->getPermissions())) { // Check if user has write access to this document + throw new AuthorizationException($validator->getDescription()); + } + + $validator = new Structure($this); + $document = $this->encode($document); + + if (!$validator->isValid($document)) { + throw new StructureException($validator->getDescription()); // var_dump($validator->getDescription()); return false; + } + + $document = new Document($this->adapter->createDocument($this->getDocument(self::COLLECTION_COLLECTIONS, $collection), $document->getArrayCopy(), $unique)); + + $document = $this->decode($document); + + return $document; + } + + /** + * @param array $collection + * @param array $id + * @param array $data + * + * @return Document|false + * + * @throws Exception + */ + public function updateDocument(string $collection, string $id, array $data) + { + if (!isset($data['$id'])) { + throw new Exception('Must define $id attribute'); + } + + $document = $this->getDocument($collection, $id); // TODO make sure user don\'t need read permission for write operations + + // Make sure reserved keys stay constant + $data['$id'] = $document->getId(); + $data['$collection'] = $document->getCollection(); + + $validator = new Authorization($document, 'write'); + + if (!$validator->isValid($document->getPermissions())) { // Check if user has write access to this document + throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false; + } + + $new = new Document($data); + + if (!$validator->isValid($new->getPermissions())) { // Check if user has write access to this document + throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false; + } + + $new = $this->encode($new); + + $validator = new Structure($this); + + if (!$validator->isValid($new)) { // Make sure updated structure still apply collection rules (if any) + throw new StructureException($validator->getDescription()); // var_dump($validator->getDescription()); return false; + } + + $new = new Document($this->adapter->updateDocument($this->getDocument(self::COLLECTION_COLLECTIONS, $collection), $id, $new->getArrayCopy())); + + $new = $this->decode($new); + + return $new; + } + + /** + * @param array $data + * + * @return Document|false + * + * @throws Exception + */ + public function overwriteDocument(array $data) + { + if (!isset($data['$id'])) { + throw new Exception('Must define $id attribute'); + } + + $document = $this->getDocument($data['$collection'], $data['$id']); // TODO make sure user don\'t need read permission for write operations + + $validator = new Authorization($document, 'write'); + + if (!$validator->isValid($document->getPermissions())) { // Check if user has write access to this document + throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false; + } + + $new = new Document($data); + + if (!$validator->isValid($new->getPermissions())) { // Check if user has write access to this document + throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false; + } + + $new = $this->encode($new); + + $validator = new Structure($this); + + if (!$validator->isValid($new)) { // Make sure updated structure still apply collection rules (if any) + throw new StructureException($validator->getDescription()); // var_dump($validator->getDescription()); return false; + } + + $new = new Document($this->adapter->updateDocument($this->getDocument(self::COLLECTION_COLLECTIONS, $new->getCollection()), $new->getId(), $new->getArrayCopy())); + + $new = $this->decode($new); + + return $new; + } + + /** + * @param string $collection + * @param string $id + * + * @return Document|false + * + * @throws AuthorizationException + */ + public function deleteDocument(string $collection, string $id) + { + $document = $this->getDocument($collection, $id); + + $validator = new Authorization($document, 'write'); + + if (!$validator->isValid($document->getPermissions())) { // Check if user has write access to this document + throw new AuthorizationException($validator->getDescription()); + } + + return new Document($this->adapter->deleteDocument($this->getDocument(self::COLLECTION_COLLECTIONS, $collection), $id)); + } + + /** + * @param int $key + * + * @return Document|false + * + * @throws AuthorizationException + */ + public function deleteUniqueKey($key) + { + return new Document($this->adapter->deleteUniqueKey($key)); + } + + /** + * @return array + */ + public function getDebug() + { + return $this->adapter->getDebug(); + } + + /** + * @return int + */ + public function getSum() + { + $debug = $this->getDebug(); + + return (isset($debug['sum'])) ? $debug['sum'] : 0; + } + + /** + * @param string $key + * @param string $value + * + * @return self + */ + public function setMock($key, $value): self + { + $this->mocks[$key] = $value; + + return $this; + } + + /** + * @param array $mocks + * + * @return self + */ + public function setMocks(array $mocks): self + { + foreach ($mocks as $key => $mock) { + $this->mocks[$key] = new Document($mock); + } + + $this->adapter->setMocks($this->mocks); + + return $this; + } + + /** + * @return array + */ + public function getMocks() + { + return $this->mocks; + } + + /** + * Add Attribute Filter + * + * @param string $name + * @param callable $encode + * @param callable $decode + * + * @return void + */ + static public function addFilter(string $name, callable $encode, callable $decode): void + { + self::$filters[$name] = [ + 'encode' => $encode, + 'decode' => $decode, + ]; + } + + public function encode(Document $document):Document + { + if($document->getCollection() === null) { + return $document; + } + + $collection = $this->getDocument(self::COLLECTION_COLLECTIONS, $document->getCollection(), true , false); + $rules = $collection->getAttribute('rules', []); + + foreach ($rules as $key => $rule) { + $key = $rule->getAttribute('key', null); + $type = $rule->getAttribute('type', null); + $array = $rule->getAttribute('array', false); + $filters = $rule->getAttribute('filter', []); + $value = $document->getAttribute($key, null); + + if (($value !== null)) { + if ($type === self::VAR_DOCUMENT) { + if($array) { + $list = []; + foreach ($value as $child) { + $list[] = $this->encode($child); + } + + $document->setAttribute($key, $list); + } else { + $document->setAttribute($key, $this->encode($value)); + } + } else { + foreach ($filters as $filter) { + $value = $this->encodeAttribute($filter, $value); + $document->setAttribute($key, $value); + } + } + } + } + + return $document; + } + + public function decode(Document $document):Document + { + if($document->getCollection() === null) { + return $document; + } + + $collection = $this->getDocument(self::COLLECTION_COLLECTIONS, $document->getCollection(), true , false); + $rules = $collection->getAttribute('rules', []); + + foreach ($rules as $key => $rule) { + $key = $rule->getAttribute('key', null); + $type = $rule->getAttribute('type', null); + $array = $rule->getAttribute('array', false); + $filters = $rule->getAttribute('filter', []); + $value = $document->getAttribute($key, null); + + if (($value !== null)) { + if ($type === self::VAR_DOCUMENT) { + if($array) { + $list = []; + foreach ($value as $child) { + $list[] = $this->decode($child); + } + + $document->setAttribute($key, $list); + } else { + $document->setAttribute($key, $this->decode($value)); + } + } else { + foreach (array_reverse($filters) as $filter) { + $value = $this->decodeAttribute($filter, $value); + $document->setAttribute($key, $value); + } + } + } + } + + return $document; + } + + /** + * Encode Attribute + * + * @param string $name + * @param mixed $value + */ + static protected function encodeAttribute(string $name, $value) + { + if (!isset(self::$filters[$name])) { + return $value; + throw new Exception('Filter not found'); + } + + try { + $value = self::$filters[$name]['encode']($value); + } catch (\Throwable $th) { + $value = null; + } + + return $value; + } + + /** + * Decode Attribute + * + * @param string $name + * @param mixed $value + */ + static protected function decodeAttribute(string $name, $value) + { + if (!isset(self::$filters[$name])) { + return $value; + throw new Exception('Filter not found'); + } + + try { + $value = self::$filters[$name]['decode']($value); + } catch (\Throwable $th) { + $value = null; + } + + return $value; + } +} diff --git a/src/Database/Document.php b/src/Database/Document.php new file mode 100644 index 000000000..aa90fba10 --- /dev/null +++ b/src/Database/Document.php @@ -0,0 +1,254 @@ + &$value) { + if (\is_array($value)) { + if ((isset($value['$id']) || isset($value['$collection'])) && (!$value instanceof self)) { + $input[$key] = new self($value); + } else { + foreach ($value as $childKey => $child) { + if ((isset($child['$id']) || isset($child['$collection'])) && (!$child instanceof self)) { + $value[$childKey] = new self($child); + } + } + } + } + } + + parent::__construct($input, $flags, $iterator_class); + } + + /** + * @return string|null + */ + public function getId() + { + return $this->getAttribute('$id', null); + } + + /** + * @return string + */ + public function getCollection() + { + return $this->getAttribute('$collection', null); + } + + /** + * @return array + */ + public function getPermissions() + { + return $this->getAttribute('$permissions', []); + } + + /** + * Get Attribute. + * + * Method for getting a specific fields attribute. If $name is not found $default value will be returned. + * + * @param string $name + * @param mixed $default + * + * @return mixed + */ + public function getAttribute($name, $default = null) + { + $name = \explode('.', $name); + + $temp = &$this; + + foreach ($name as $key) { + if (!isset($temp[$key])) { + return $default; + } + + $temp = &$temp[$key]; + } + + return $temp; + } + + /** + * Set Attribute. + * + * Method for setting a specific field attribute + * + * @param string $key + * @param mixed $value + * @param string $type + * + * @return mixed + */ + public function setAttribute($key, $value, $type = self::SET_TYPE_ASSIGN) + { + switch ($type) { + case self::SET_TYPE_ASSIGN: + $this[$key] = $value; + break; + case self::SET_TYPE_APPEND: + $this[$key] = (!isset($this[$key]) || !\is_array($this[$key])) ? [] : $this[$key]; + \array_push($this[$key], $value); + break; + case self::SET_TYPE_PREPEND: + $this[$key] = (!isset($this[$key]) || !\is_array($this[$key])) ? [] : $this[$key]; + \array_unshift($this[$key], $value); + break; + } + + return $this; + } + + /** + * Remove Attribute. + * + * Method for removing a specific field attribute + * + * @param string $key + * @param mixed $value + * @param string $type + * + * @return mixed + */ + public function removeAttribute($key) + { + if (isset($this[$key])) { + unset($this[$key]); + } + + return $this; + } + + /** + * Search. + * + * Get array child by key and value match + * + * @param $key + * @param $value + * @param array|null $scope + * + * @return Document|Document[]|mixed|null|array + */ + public function search($key, $value, $scope = null) + { + $array = (!\is_null($scope)) ? $scope : $this; + + if (\is_array($array) || $array instanceof self) { + if (isset($array[$key]) && $array[$key] == $value) { + return $array; + } + + foreach ($array as $k => $v) { + if ((\is_array($v) || $v instanceof self) && (!empty($v))) { + $result = $this->search($key, $value, $v); + + if (!empty($result)) { + return $result; + } + } else { + if ($k === $key && $v === $value) { + return $array; + } + } + } + } + + if ($array === $value) { + return $array; + } + + return; + } + + /** + * Checks if document has data. + * + * @return bool + */ + public function isEmpty() + { + return empty($this->getId()); + } + + /** + * Checks if a document key is set. + * + * @param string $key + * + * @return bool + */ + public function isSet($key) + { + return isset($this[$key]); + } + + /** + * Get Array Copy. + * + * Outputs entity as a PHP array + * + * @param array $whitelist + * @param array $blacklist + * + * @return array + */ + public function getArrayCopy(array $whitelist = [], array $blacklist = []) + { + $array = parent::getArrayCopy(); + + $output = []; + + foreach ($array as $key => &$value) { + if (!empty($whitelist) && !\in_array($key, $whitelist)) { // Export only whitelisted fields + continue; + } + + if (!empty($blacklist) && \in_array($key, $blacklist)) { // Don't export blacklisted fields + continue; + } + + if ($value instanceof self) { + $output[$key] = $value->getArrayCopy($whitelist, $blacklist); + } elseif (\is_array($value)) { + foreach ($value as $childKey => &$child) { + if ($child instanceof self) { + $output[$key][$childKey] = $child->getArrayCopy($whitelist, $blacklist); + } else { + $output[$key][$childKey] = $child; + } + } + + if (empty($value)) { + $output[$key] = $value; + } + } else { + $output[$key] = $value; + } + } + + return $output; + } +} diff --git a/src/Database/Exception/Authorization.php b/src/Database/Exception/Authorization.php new file mode 100644 index 000000000..e87e79707 --- /dev/null +++ b/src/Database/Exception/Authorization.php @@ -0,0 +1,7 @@ + true]; + + /** + * @var Document + */ + protected $document; + + /** + * @var string + */ + protected $action = ''; + + /** + * @var string + */ + protected $message = 'Authorization Error'; + + /** + * @param Document $document + * @param string $action + */ + public function __construct(Document $document, $action) + { + $this->document = $document; + $this->action = $action; + } + + /** + * Get Description. + * + * Returns validator description + * + * @return string + */ + public function getDescription() + { + return $this->message; + } + + /** + * Is valid. + * + * Returns true if valid or false if not. + * + * @param mixed $permissions + * + * @return bool + */ + public function isValid($permissions) + { + if (!self::$status) { + return true; + } + + if (!isset($permissions[$this->action])) { + $this->message = 'Missing action key: "'.$this->action.'"'; + + return false; + } + + $permission = null; + + foreach ($permissions[$this->action] as $permission) { + $permission = \str_replace(':{self}', ':'.$this->document->getId(), $permission); + + if (\array_key_exists($permission, self::$roles)) { + return true; + } + } + + $this->message = 'Missing "'.$this->action.'" permission for role "'.$permission.'". Only this scopes "'.\json_encode(self::getRoles()).'" are given and only this are allowed "'.\json_encode($permissions[$this->action]).'".'; + + return false; + } + + /** + * @param string $role + * @return void + */ + public static function setRole(string $role): void + { + self::$roles[$role] = true; + } + + /** + * @param string $role + * + * @return void + */ + public static function unsetRole(string $role): void + { + unset(self::$roles[$role]); + } + + /** + * @return array + */ + public static function getRoles(): array + { + return \array_keys(self::$roles); + } + + /** + * @return void + */ + public static function cleanRoles(): void + { + self::$roles = []; + } + + /** + * @param string $role + * + * @return bool + */ + public static function isRole(string $role): bool + { + return (\array_key_exists($role, self::$roles)); + } + + /** + * @var bool + */ + public static $status = true; + + /** + * Default value in case we need + * to reset Authorization status + * + * @var bool + */ + public static $statusDefault = true; + + /** + * Change default status. + * This will be used for the + * value set on the self::reset() method + * + * @return void + */ + public static function setDefaultStatus($status): void + { + self::$statusDefault = $status; + self::$status = $status; + } + + /** + * Enable Authorization checks + * + * @return void + */ + public static function enable(): void + { + self::$status = true; + } + + /** + * Disable Authorization checks + * + * @return void + */ + public static function disable(): void + { + self::$status = false; + } + + /** + * Disable Authorization checks + * + * @return void + */ + public static function reset(): void + { + self::$status = self::$statusDefault; + } +} diff --git a/src/Database/Validator/Collection.php b/src/Database/Validator/Collection.php new file mode 100644 index 000000000..4941a777e --- /dev/null +++ b/src/Database/Validator/Collection.php @@ -0,0 +1,62 @@ +collections = $collections; + $this->merge = $merge; + + return parent::__construct($database); + } + + /** + * Is valid. + * + * Returns true if valid or false if not. + * + * @param mixed $document + * + * @return bool + */ + public function isValid($document) + { + $document = new Document( + \array_merge($this->merge, ($document instanceof Document) ? $document->getArrayCopy() : $document) + ); + + if (\is_null($document->getCollection())) { + $this->message = 'Missing collection attribute $collection'; + + return false; + } + + if (!\in_array($document->getCollection(), $this->collections)) { + $this->message = 'Collection is not allowed'; + + return false; + } + + return parent::isValid($document); + } +} diff --git a/src/Database/Validator/DocumentId.php b/src/Database/Validator/DocumentId.php new file mode 100644 index 000000000..23c9fb4f1 --- /dev/null +++ b/src/Database/Validator/DocumentId.php @@ -0,0 +1,81 @@ +database = $database; + $this->collection = $collection; + } + + /** + * Get Description. + * + * Returns validator description + * + * @return string + */ + public function getDescription() + { + return $this->message; + } + + /** + * Is valid. + * + * Returns true if valid or false if not. + * + * @param $value + * + * @return bool + */ + public function isValid($id) + { + $document = $this->database->getDocument($this->collection, $id); + + if (!$document) { + return false; + } + + if (!$document instanceof Document) { + return false; + } + + if (!$document->getId()) { + return false; + } + + if ($document->getCollection() !== $this->collection) { + return false; + } + + return true; + } +} diff --git a/src/Database/Validator/Key.php b/src/Database/Validator/Key.php new file mode 100644 index 000000000..53cdc3eb7 --- /dev/null +++ b/src/Database/Validator/Key.php @@ -0,0 +1,51 @@ +message; + } + + /** + * Is valid. + * + * Returns true if valid or false if not. + * + * @param $value + * + * @return bool + */ + public function isValid($value) + { + if (!\is_string($value)) { + return false; + } + + if (\preg_match('/[^A-Za-z0-9\-\_]/', $value)) { + return false; + } + + if (\mb_strlen($value) > 32) { + return false; + } + + return true; + } +} diff --git a/src/Database/Validator/Permissions.php b/src/Database/Validator/Permissions.php new file mode 100644 index 000000000..77a27e55b --- /dev/null +++ b/src/Database/Validator/Permissions.php @@ -0,0 +1,66 @@ +message; + } + + /** + * Is valid. + * + * Returns true if valid or false if not. + * + * @param mixed $value + * + * @return bool + */ + public function isValid($value) + { + if (!\is_array($value) && !empty($value)) { + $this->message = 'Invalid permissions data structure'; + + return false; + } + + foreach ($value as $action => $roles) { + if (!\in_array($action, ['read', 'write', 'execute'])) { + $this->message = 'Unknown action ("'.$action.'")'; + + return false; + } + + if(!is_array($roles)) { + $this->message = 'Permissions roles must be an array of strings'; + return false; + } + + foreach ($roles as $role) { + if (!\is_string($role)) { + $this->message = 'Permissions role must be of type string.'; + + return false; + } + } + } + + return true; + } +} diff --git a/src/Database/Validator/Structure.php b/src/Database/Validator/Structure.php new file mode 100644 index 000000000..00118beec --- /dev/null +++ b/src/Database/Validator/Structure.php @@ -0,0 +1,294 @@ + '$id', + '$collection' => Database::COLLECTION_RULES, + 'key' => '$id', + 'type' => Database::VAR_KEY, + 'default' => null, + 'required' => false, + 'array' => false, + ], + [ + 'label' => '$collection', + '$collection' => Database::COLLECTION_RULES, + 'key' => '$collection', + 'type' => Database::VAR_KEY, + 'default' => null, + 'required' => true, + 'array' => false, + ], + [ + 'label' => '$permissions', + '$collection' => Database::COLLECTION_RULES, + 'key' => '$permissions', + 'type' => 'permissions', + 'default' => null, + 'required' => true, + 'array' => false, + ], + [ + 'label' => '$createdAt', + '$collection' => Database::COLLECTION_RULES, + 'key' => '$createdAt', + 'type' => Database::VAR_INTEGER, + 'default' => null, + 'required' => false, + 'array' => false, + ], + [ + 'label' => '$updatedAt', + '$collection' => Database::COLLECTION_RULES, + 'key' => '$updatedAt', + 'type' => Database::VAR_INTEGER, + 'default' => null, + 'required' => false, + 'array' => false, + ], + ]; + + /** + * @var string + */ + protected $message = 'General Error'; + + /** + * Structure constructor. + * + * @param Database $database + */ + public function __construct(Database $database) + { + $this->database = $database; + } + + /** + * Get Description. + * + * Returns validator description + * + * @return string + */ + public function getDescription() + { + return 'Invalid document structure: '.$this->message; + } + + /** + * Is valid. + * + * Returns true if valid or false if not. + * + * @param mixed $document + * + * @return bool + */ + public function isValid($document) + { + $document = (\is_array($document)) ? new Document($document) : $document; + + $this->id = $document->getId(); + + if (\is_null($document->getCollection())) { + $this->message = 'Missing collection attribute $collection'; + + return false; + } + + $collection = $this->getCollection(Database::COLLECTION_COLLECTIONS, $document->getCollection()); + + if (\is_null($collection->getId()) || Database::COLLECTION_COLLECTIONS != $collection->getCollection()) { + $this->message = 'Collection "'.$collection->getCollection().'" not found'; + return false; + } + + $array = $document->getArrayCopy(); + $rules = \array_merge($this->rules, $collection->getAttribute('rules', [])); + + foreach ($rules as $rule) { // Check all required keys are set + if (isset($rule['key']) && !isset($array[$rule['key']]) + && isset($rule['required']) && true == $rule['required']) { + $this->message = 'Missing required key "'.$rule['key'].'"'; + + return false; + } + } + + foreach ($array as $key => $value) { + $rule = $collection->search('key', $key, $rules); + + if (!$rule) { + continue; + } + + $ruleType = $rule['type'] ?? ''; + $ruleRequired = $rule['required'] ?? true; + $ruleArray = $rule['array'] ?? false; + $validator = null; + + switch ($ruleType) { + case self::RULE_TYPE_PERMISSIONS: + $validator = new Permissions(); + break; + case Database::VAR_KEY: + $validator = new Key(); + break; + case Database::VAR_TEXT: + case self::RULE_TYPE_MARKDOWN: + $validator = new Validator\Text(0); + break; + case Database::VAR_NUMERIC: + $validator = new Validator\Numeric(); + break; + case Database::VAR_INTEGER: + $validator = new Validator\Integer(); + break; + case Database::VAR_FLOAT: + $validator = new Validator\FloatValidator(); + break; + case Database::VAR_BOOLEAN: + $validator = new Validator\Boolean(); + break; + case Database::VAR_EMAIL: + $validator = new Validator\Email(); + break; + case Database::VAR_URL: + $validator = new Validator\URL(); + break; + case self::RULE_TYPE_IP: + $validator = new Validator\IP(); + break; + case Database::VAR_IPV4: + $validator = new Validator\IP(Validator\IP::V4); + break; + case Database::VAR_IPV6: + $validator = new Validator\IP(Validator\IP::V6); + break; + case Database::VAR_DOCUMENT: + $validator = new Collection($this->database, (isset($rule['list'])) ? $rule['list'] : []); + $value = $document->getAttribute($key); + break; + case self::RULE_TYPE_DOCUMENTID: + $validator = new DocumentId($this->database, (isset($rule['list']) && isset($rule['list'][0])) ? $rule['list'][0] : ''); + $value = $document->getAttribute($key); + break; + case self::RULE_TYPE_FILEID: + $validator = new DocumentId($this->database, Database::COLLECTION_FILES); + $value = $document->getAttribute($key); + break; + } + + if (empty($validator)) { // Error creating validator for property + $this->message = 'Unknown rule type "'.$ruleType.'" for property "'.\htmlspecialchars($key, ENT_QUOTES, 'UTF-8').'"'; + + if (empty($ruleType)) { + $this->message = 'Unknown property "'.$key.'" type'. + '. Make sure to follow '.\strtolower($collection->getAttribute('name', 'unknown')).' collection structure'; + } + + return false; + } + + if ($ruleRequired && ('' === $value || null === $value)) { + $this->message = 'Required property "'.$key.'" has no value'; + + return false; + } + + if (!$ruleRequired && empty($value)) { + unset($array[$key]); + unset($rule); + + continue; + } + + if ($ruleArray) { // Array of values validation + if (!\is_array($value)) { + $this->message = 'Property "'.$key.'" must be an array'; + + return false; + } + + // TODO add is required check here + + foreach ($value as $node) { + if (!$validator->isValid($node)) { // Check if property is valid, if not required can also be empty + $this->message = 'Property "'.$key.'" has invalid input. '.$validator->getDescription(); + + return false; + } + } + } else { // Single value validation + if ((!$validator->isValid($value)) && !('' === $value && !$ruleRequired)) { // Error when value is not valid, and is not optional and empty + $this->message = 'Property "'.$key.'" has invalid input. '.$validator->getDescription(); + + return false; + } + } + + unset($array[$key]); + unset($rule); + } + + if (!empty($array)) { // No fields should be left unvalidated + $this->message = 'Unknown properties are not allowed ('.\implode(', ', \array_keys($array)).') for this collection'. + '. Make sure to follow '.\strtolower($collection->getAttribute('name', 'unknown')).' collection structure'; + + return false; + } + + return true; + } + + /** + * Get Collection + * + * Get Collection by unique ID + * + * @return Document + */ + protected function getCollection(string $collection, string $id): Document + { + return $this->database->getDocument($collection, $id); + } +} diff --git a/src/Database/Validator/UID.php b/src/Database/Validator/UID.php new file mode 100644 index 000000000..4cc3f1d5b --- /dev/null +++ b/src/Database/Validator/UID.php @@ -0,0 +1,46 @@ + 32) { + return false; + } + + return true; + } +} diff --git a/tests/Database/DatabaseTest.php b/tests/Database/DatabaseTest.php new file mode 100644 index 000000000..82a61610e --- /dev/null +++ b/tests/Database/DatabaseTest.php @@ -0,0 +1,1907 @@ + 'SET NAMES utf8mb4', + PDO::ATTR_TIMEOUT => 3, // Seconds + PDO::ATTR_PERSISTENT => true + )); + + // Connection settings + $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); // Return arrays + $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // Handle all errors with exceptions + + self::$object = new Database(); + self::$object->setAdapter(new Relational($pdo)); + + self::$object->setMocks([ + Database::COLLECTION_COLLECTIONS => [ + '$collection' => Database::COLLECTION_COLLECTIONS, + '$id' => Database::COLLECTION_COLLECTIONS, + '$permissions' => ['read' => ['*']], + 'name' => 'Collections', + 'rules' => [ + [ + '$collection' => Database::COLLECTION_RULES, + 'label' => 'Name', + 'key' => 'name', + 'type' => Database::VAR_TEXT, + 'default' => '', + 'required' => true, + 'array' => false, + ], + [ + '$collection' => Database::COLLECTION_RULES, + 'label' => 'Date Created', + 'key' => 'dateCreated', + 'type' => Database::VAR_INTEGER, + 'default' => 0, + 'required' => false, + 'array' => false, + ], + [ + '$collection' => Database::COLLECTION_RULES, + 'label' => 'Date Updated', + 'key' => 'dateUpdated', + 'type' => Database::VAR_INTEGER, + 'default' => 0, + 'required' => false, + 'array' => false, + ], + [ + '$collection' => Database::COLLECTION_RULES, + 'label' => 'Rules', + 'key' => 'rules', + 'type' => Database::VAR_DOCUMENT, + 'default' => [], + 'required' => false, + 'array' => true, + 'list' => [Database::COLLECTION_RULES], + ], + [ + '$collection' => Database::COLLECTION_RULES, + 'label' => 'Indexes', + 'key' => 'indexes', + 'type' => Database::VAR_DOCUMENT, + 'default' => [], + 'required' => false, + 'array' => true, + 'list' => [Database::COLLECTION_INDEXES], + ], + ], + 'indexes' => [ + [ + '$collection' => Database::COLLECTION_INDEXES, + '$id' => 'system1', + 'type' => Database::INDEX_KEY, + 'attributes' => [ + 'name', + ], + ], + [ + '$collection' => Database::COLLECTION_INDEXES, + '$id' => 'system2', + 'type' => Database::INDEX_FULLTEXT, + 'attributes' => [ + 'name', + ], + ], + ], + ], + Database::COLLECTION_RULES => [ + '$collection' => Database::COLLECTION_COLLECTIONS, + '$id' => Database::COLLECTION_RULES, + '$permissions' => ['read' => ['*']], + 'name' => 'Collections Rule', + 'rules' => [ + [ + '$collection' => Database::COLLECTION_RULES, + 'label' => 'Label', + 'key' => 'label', + 'type' => Database::VAR_TEXT, + 'default' => '', + 'required' => true, + 'array' => false, + ], + [ + '$collection' => Database::COLLECTION_RULES, + 'label' => 'Key', + 'key' => 'key', + 'type' => Database::VAR_KEY, + 'default' => '', + 'required' => true, + 'array' => false, + ], + [ + '$collection' => Database::COLLECTION_RULES, + 'label' => 'Type', + 'key' => 'type', + 'type' => Database::VAR_TEXT, + 'default' => '', + 'required' => true, + 'array' => false, + ], + [ + '$collection' => Database::COLLECTION_RULES, + 'label' => 'Default', + 'key' => 'default', + 'type' => Database::VAR_TEXT, + 'default' => '', + 'required' => false, + 'array' => false, + ], + [ + '$collection' => Database::COLLECTION_RULES, + 'label' => 'Required', + 'key' => 'required', + 'type' => Database::VAR_BOOLEAN, + 'default' => true, + 'required' => true, + 'array' => false, + ], + [ + '$collection' => Database::COLLECTION_RULES, + 'label' => 'Array', + 'key' => 'array', + 'type' => Database::VAR_BOOLEAN, + 'default' => true, + 'required' => true, + 'array' => false, + ], + [ + '$collection' => Database::COLLECTION_RULES, + 'label' => 'list', + 'key' => 'list', + 'type' => Database::VAR_TEXT, + //'default' => '', + 'required' => false, + 'array' => true, + ], + ], + ], + Database::COLLECTION_INDEXES => [ + '$collection' => Database::COLLECTION_COLLECTIONS, + '$id' => Database::COLLECTION_INDEXES, + '$permissions' => ['read' => ['*']], + 'name' => 'Collections Indexes', + 'rules' => [ + [ + '$collection' => Database::COLLECTION_RULES, + 'label' => 'Type', + 'key' => 'type', + 'type' => Database::VAR_TEXT, + 'default' => '', + 'required' => true, + 'array' => false, + ], + [ + '$collection' => Database::COLLECTION_RULES, + 'label' => 'Attributes', + 'key' => 'attributes', + 'type' => Database::VAR_KEY, + 'default' => '', + 'required' => true, + 'array' => true, + ], + ], + ], + Database::COLLECTION_USERS => [ + '$collection' => Database::COLLECTION_COLLECTIONS, + '$id' => Database::COLLECTION_USERS, + '$permissions' => ['read' => ['*']], + 'name' => 'User', + 'rules' => [ + [ + '$collection' => Database::COLLECTION_RULES, + 'label' => 'Name', + 'key' => 'name', + 'type' => Database::VAR_TEXT, + 'default' => '', + 'required' => false, + 'array' => false, + ], + [ + '$collection' => Database::COLLECTION_RULES, + 'label' => 'Email', + 'key' => 'email', + 'type' => Database::VAR_EMAIL, + 'default' => '', + 'required' => true, + 'array' => false, + ], + [ + '$collection' => Database::COLLECTION_RULES, + 'label' => 'Status', + 'key' => 'status', + 'type' => Database::VAR_INTEGER, + 'default' => '', + 'required' => true, + 'array' => false, + ], + [ + '$collection' => Database::COLLECTION_RULES, + 'label' => 'Password', + 'key' => 'password', + 'type' => Database::VAR_TEXT, + 'default' => '', + 'required' => true, + 'array' => false, + ], + [ + '$collection' => Database::COLLECTION_RULES, + 'label' => 'Password Update Date', + 'key' => 'password-update', + 'type' => Database::VAR_INTEGER, + 'default' => '', + 'required' => true, + 'array' => false, + ], + [ + '$collection' => Database::COLLECTION_RULES, + 'label' => 'Prefs', + 'key' => 'prefs', + 'type' => Database::VAR_TEXT, + 'default' => '', + 'required' => false, + 'array' => false, + 'filter' => ['json'] + ], + [ + '$collection' => Database::COLLECTION_RULES, + 'label' => 'Registration Date', + 'key' => 'registration', + 'type' => Database::VAR_INTEGER, + 'default' => '', + 'required' => true, + 'array' => false, + ], + [ + '$collection' => Database::COLLECTION_RULES, + 'label' => 'Email Verification Status', + 'key' => 'emailVerification', + 'type' => Database::VAR_BOOLEAN, + 'default' => '', + 'required' => true, + 'array' => false, + ], + [ + '$collection' => Database::COLLECTION_RULES, + 'label' => 'Reset', + 'key' => 'reset', + 'type' => Database::VAR_BOOLEAN, + 'default' => '', + 'required' => true, + 'array' => false, + ], + [ + '$collection' => Database::COLLECTION_RULES, + 'label' => 'Tokens', + 'key' => 'tokens', + 'type' => Database::VAR_DOCUMENT, + 'default' => [], + 'required' => false, + 'array' => true, + 'list' => [Database::COLLECTION_TOKENS], + ], + [ + '$collection' => Database::COLLECTION_RULES, + 'label' => 'Memberships', + 'key' => 'memberships', + 'type' => Database::VAR_DOCUMENT, + 'default' => [], + 'required' => false, + 'array' => true, + 'list' => [Database::COLLECTION_MEMBERSHIPS], + ], + ], + 'indexes' => [ + [ + '$collection' => Database::COLLECTION_INDEXES, + '$id' => 'system1', + 'type' => Database::INDEX_KEY, + 'attributes' => [ + 'name', + ], + ], + [ + '$collection' => Database::COLLECTION_INDEXES, + '$id' => 'system2', + 'type' => Database::INDEX_FULLTEXT, + 'attributes' => [ + 'name', + ], + ], + [ + '$collection' => Database::COLLECTION_INDEXES, + '$id' => 'system3', + 'type' => Database::INDEX_UNIQUE, + 'attributes' => [ + 'email', + ], + ], + [ + '$collection' => Database::COLLECTION_INDEXES, + '$id' => 'system4', + 'type' => Database::INDEX_KEY, + 'attributes' => [ + 'email', + ], + ], + ], + ], + ]); + + self::$object->setNamespace($namespace); + self::$object->createNamespace($namespace); + + self::$collection = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ + '$collection' => Database::COLLECTION_COLLECTIONS, + '$permissions' => ['read' => ['*']], + 'name' => 'Tasks', + 'rules' => [ + [ + '$collection' => Database::COLLECTION_RULES, + '$permissions' => ['read' => ['*']], + 'label' => 'Task Name', + 'key' => 'name', + 'type' => Database::VAR_TEXT, + 'default' => '', + 'required' => true, + 'array' => false, + ], + ], + ]); + + self::$init = true; + } + + public function tearDown(): void + { + Authorization::reset(); + } + + public function testCreateCollection() + { + $collection = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ + '$collection' => Database::COLLECTION_COLLECTIONS, + '$permissions' => ['read' => ['*']], + 'name' => 'Create', + ]); + + $this->assertEquals(true, self::$object->createCollection($collection->getId(), [], [])); + + try { + self::$object->createCollection($collection->getId(), [], []); + } + catch (\Throwable $th) { + return $this->assertEquals('42S01', $th->getCode()); + } + + throw new Exception('Expected exception'); + } + + public function testDeleteCollection() + { + + $collection = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ + '$collection' => Database::COLLECTION_COLLECTIONS, + '$permissions' => ['read' => ['*']], + 'name' => 'Delete', + ]); + + $this->assertEquals(true, self::$object->createCollection($collection->getId(), [], [])); + $this->assertEquals(true, self::$object->deleteCollection($collection->getId())); + + try { + self::$object->deleteCollection($collection->getId()); + } + catch (\Throwable $th) { + return $this->assertEquals('42S02', $th->getCode()); + } + + throw new Exception('Expected exception'); + } + + public function testCreateAttribute() + { + $collection = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ + '$collection' => Database::COLLECTION_COLLECTIONS, + '$permissions' => ['read' => ['*']], + 'name' => 'Create Attribute', + 'rules' => [], + ]); + + $this->assertEquals(true, self::$object->createCollection($collection->getId(), [], [])); + $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'title', Database::VAR_TEXT)); + $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'description', Database::VAR_TEXT)); + $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'numeric', Database::VAR_NUMERIC)); + $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'integer', Database::VAR_INTEGER)); + $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'float', Database::VAR_FLOAT)); + $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'boolean', Database::VAR_BOOLEAN)); + $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'document', Database::VAR_DOCUMENT)); + $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'email', Database::VAR_EMAIL)); + $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'url', Database::VAR_URL)); + $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'ipv4', Database::VAR_IPV4)); + $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'ipv6', Database::VAR_IPV6)); + $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'key', Database::VAR_KEY)); + + // arrays + $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'titles', Database::VAR_TEXT, true)); + $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'descriptions', Database::VAR_TEXT, true)); + $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'numerics', Database::VAR_NUMERIC, true)); + $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'integers', Database::VAR_INTEGER, true)); + $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'floats', Database::VAR_FLOAT, true)); + $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'booleans', Database::VAR_BOOLEAN, true)); + $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'documents', Database::VAR_DOCUMENT, true)); + $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'emails', Database::VAR_EMAIL, true)); + $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'urls', Database::VAR_URL, true)); + $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'ipv4s', Database::VAR_IPV4, true)); + $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'ipv6s', Database::VAR_IPV6, true)); + $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'keys', Database::VAR_KEY, true)); + } + + public function testDeleteAttribute() + { + $collection = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ + '$collection' => Database::COLLECTION_COLLECTIONS, + '$permissions' => ['read' => ['*']], + 'name' => 'Delete Attribute', + 'rules' => [], + ]); + + $this->assertEquals(true, self::$object->createCollection($collection->getId(), [], [])); + + $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'title', Database::VAR_TEXT)); + $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'description', Database::VAR_TEXT)); + $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'value', Database::VAR_NUMERIC)); + + $this->assertEquals(true, self::$object->deleteAttribute($collection->getId(), 'title', false)); + $this->assertEquals(true, self::$object->deleteAttribute($collection->getId(), 'description', false)); + $this->assertEquals(true, self::$object->deleteAttribute($collection->getId(), 'value', false)); + + $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'titles', Database::VAR_TEXT, true)); + $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'descriptions', Database::VAR_TEXT, true)); + $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'values', Database::VAR_NUMERIC, true)); + + $this->assertEquals(true, self::$object->deleteAttribute($collection->getId(), 'titles', true)); + $this->assertEquals(true, self::$object->deleteAttribute($collection->getId(), 'descriptions', true)); + $this->assertEquals(true, self::$object->deleteAttribute($collection->getId(), 'values', true)); + } + + public function testCreateIndex() + { + $collection = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ + '$collection' => Database::COLLECTION_COLLECTIONS, + '$permissions' => ['read' => ['*']], + 'name' => 'Create Index', + 'rules' => [], + ]); + + $this->assertEquals(true, self::$object->createCollection($collection->getId(), [], [])); + $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'title', Database::VAR_TEXT)); + $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'description', Database::VAR_TEXT)); + $this->assertEquals(true, self::$object->createIndex($collection->getId(), 'x', Database::INDEX_KEY, ['title'])); + $this->assertEquals(true, self::$object->createIndex($collection->getId(), 'y', Database::INDEX_KEY, ['description'])); + $this->assertEquals(true, self::$object->createIndex($collection->getId(), 'z', Database::INDEX_KEY, ['title', 'description'])); + } + + public function testDeleteIndex() + { + $collection = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ + '$collection' => Database::COLLECTION_COLLECTIONS, + '$permissions' => ['read' => ['*']], + 'name' => 'Delete Index', + 'rules' => [], + ]); + + $this->assertEquals(true, self::$object->createCollection($collection->getId(), [], [])); + $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'title', Database::VAR_TEXT)); + $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'description', Database::VAR_TEXT)); + $this->assertEquals(true, self::$object->createIndex($collection->getId(), 'x', Database::INDEX_KEY, ['title'])); + $this->assertEquals(true, self::$object->createIndex($collection->getId(), 'y', Database::INDEX_KEY, ['description'])); + $this->assertEquals(true, self::$object->createIndex($collection->getId(), 'z', Database::INDEX_KEY, ['title', 'description'])); + + $this->assertEquals(true, self::$object->deleteIndex($collection->getId(), 'x')); + $this->assertEquals(true, self::$object->deleteIndex($collection->getId(), 'y')); + $this->assertEquals(true, self::$object->deleteIndex($collection->getId(), 'z')); + } + + public function testCreateDocument() + { + $collection1 = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ + '$collection' => Database::COLLECTION_COLLECTIONS, + '$permissions' => ['read' => ['*']], + 'name' => 'Create Documents', + 'rules' => [ + [ + '$collection' => Database::COLLECTION_RULES, + '$permissions' => ['read' => ['*']], + 'label' => 'Name', + 'key' => 'name', + 'type' => Database::VAR_TEXT, + 'default' => '', + 'required' => true, + 'array' => false, + ], + [ + '$collection' => Database::COLLECTION_RULES, + '$permissions' => ['read' => ['*']], + 'label' => 'Links', + 'key' => 'links', + 'type' => Database::VAR_URL, + 'default' => '', + 'required' => true, + 'array' => true, + ], + ] + ]); + + $this->assertEquals(true, self::$object->createCollection($collection1->getId(), [], [])); + + $document0 = new Document([ + '$collection' => $collection1->getId(), + '$permissions' => [ + 'read' => ['*'], + 'write' => ['user:123'], + ], + 'name' => 'Task #0', + 'links' => [ + 'http://example.com/link-1', + 'http://example.com/link-2', + 'http://example.com/link-3', + 'http://example.com/link-4', + ], + ]); + + $document1 = self::$object->createDocument($collection1->getId(), [ + '$collection' => $collection1->getId(), + '$permissions' => [ + 'read' => ['*'], + 'write' => ['user:123'], + ], + 'name' => 'Task #1️⃣', + 'links' => [ + 'http://example.com/link-5', + 'http://example.com/link-6', + 'http://example.com/link-7', + 'http://example.com/link-8', + ], + ]); + + $this->assertNotEmpty($document1->getId()); + $this->assertIsArray($document1->getPermissions()); + $this->assertArrayHasKey('read', $document1->getPermissions()); + $this->assertArrayHasKey('write', $document1->getPermissions()); + $this->assertEquals('Task #1️⃣', $document1->getAttribute('name')); + $this->assertCount(4, $document1->getAttribute('links')); + $this->assertEquals('http://example.com/link-5', $document1->getAttribute('links')[0]); + $this->assertEquals('http://example.com/link-6', $document1->getAttribute('links')[1]); + $this->assertEquals('http://example.com/link-7', $document1->getAttribute('links')[2]); + $this->assertEquals('http://example.com/link-8', $document1->getAttribute('links')[3]); + + $document2 = self::$object->createDocument(Database::COLLECTION_USERS, [ + '$collection' => Database::COLLECTION_USERS, + '$permissions' => [ + 'read' => ['*'], + 'write' => ['user:123'], + ], + 'email' => 'test@appwrite.io', + 'emailVerification' => false, + 'status' => 0, + 'password' => 'secrethash', + 'password-update' => \time(), + 'registration' => \time(), + 'reset' => false, + 'name' => 'Test', + ]); + + $this->assertNotEmpty($document2->getId()); + $this->assertIsArray($document2->getPermissions()); + $this->assertArrayHasKey('read', $document2->getPermissions()); + $this->assertArrayHasKey('write', $document2->getPermissions()); + $this->assertEquals('test@appwrite.io', $document2->getAttribute('email')); + $this->assertIsString($document2->getAttribute('email')); + $this->assertEquals(0, $document2->getAttribute('status')); + $this->assertIsInt($document2->getAttribute('status')); + $this->assertEquals(false, $document2->getAttribute('emailVerification')); + $this->assertIsBool($document2->getAttribute('emailVerification')); + + $document2 = self::$object->getDocument(Database::COLLECTION_USERS, $document2->getId()); + + $this->assertNotEmpty($document2->getId()); + $this->assertIsArray($document2->getPermissions()); + $this->assertArrayHasKey('read', $document2->getPermissions()); + $this->assertArrayHasKey('write', $document2->getPermissions()); + $this->assertEquals('test@appwrite.io', $document2->getAttribute('email')); + $this->assertIsString($document2->getAttribute('email')); + $this->assertEquals(0, $document2->getAttribute('status')); + $this->assertIsInt($document2->getAttribute('status')); + $this->assertEquals(false, $document2->getAttribute('emailVerification')); + $this->assertIsBool($document2->getAttribute('emailVerification')); + + $types = [ + Database::VAR_TEXT, + Database::VAR_INTEGER, + Database::VAR_FLOAT, + Database::VAR_NUMERIC, + Database::VAR_BOOLEAN, + Database::VAR_DOCUMENT, + Database::VAR_EMAIL, + Database::VAR_URL, + Database::VAR_IPV4, + Database::VAR_IPV6, + Database::VAR_KEY, + ]; + + $rules = []; + + foreach($types as $type) { + $rules[] = [ + '$collection' => Database::COLLECTION_RULES, + '$permissions' => ['read' => ['*']], + 'label' => ucfirst($type), + 'key' => $type, + 'type' => $type, + 'default' => null, + 'required' => true, + 'array' => false, + 'list' => ($type === Database::VAR_DOCUMENT) ? [$collection1->getId()] : [], + ]; + + $rules[] = [ + '$collection' => Database::COLLECTION_RULES, + '$permissions' => ['read' => ['*']], + 'label' => ucfirst($type), + 'key' => $type.'s', + 'type' => $type, + 'default' => null, + 'required' => true, + 'array' => true, + 'list' => ($type === Database::VAR_DOCUMENT) ? [$collection1->getId()] : [], + ]; + } + + $rules[] = [ + '$collection' => Database::COLLECTION_RULES, + '$permissions' => ['read' => ['*']], + 'label' => 'document2', + 'key' => 'document2', + 'type' => Database::VAR_DOCUMENT, + 'default' => null, + 'required' => true, + 'array' => false, + 'list' => [$collection1->getId()], + ]; + + $rules[] = [ + '$collection' => Database::COLLECTION_RULES, + '$permissions' => ['read' => ['*']], + 'label' => 'documents2', + 'key' => 'documents2', + 'type' => Database::VAR_DOCUMENT, + 'default' => null, + 'required' => true, + 'array' => true, + 'list' => [$collection1->getId()], + ]; + + $collection2 = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ + '$collection' => Database::COLLECTION_COLLECTIONS, + '$permissions' => ['read' => ['*']], + 'name' => 'Create Documents', + 'rules' => $rules, + ]); + + $this->assertEquals(true, self::$object->createCollection($collection2->getId(), [], [])); + + $document3 = self::$object->createDocument($collection2->getId(), [ + '$collection' => $collection2->getId(), + '$permissions' => [ + 'read' => ['*'], + 'write' => ['user:123'], + ], + 'text' => 'Hello World', + 'texts' => ['Hello World 1', 'Hello World 2'], + // 'document' => $document0, + // 'documents' => [$document0], + 'document' => $document0, + 'documents' => [$document1, $document0], + 'document2' => $document1, + 'documents2' => [$document0, $document1], + 'integer' => 1, + 'integers' => [5, 3, 4], + 'float' => 2.22, + 'floats' => [1.13, 4.33, 8.9999], + 'numeric' => 1, + 'numerics' => [1, 5, 7.77], + 'boolean' => true, + 'booleans' => [true, false, true], + 'email' => 'test@appwrite.io', + 'emails' => [ + 'test4@appwrite.io', + 'test3@appwrite.io', + 'test2@appwrite.io', + 'test1@appwrite.io' + ], + 'url' => 'http://example.com/welcome', + 'urls' => [ + 'http://example.com/welcome-1', + 'http://example.com/welcome-2', + 'http://example.com/welcome-3' + ], + 'ipv4' => '172.16.254.1', + 'ipv4s' => [ + '172.16.254.1', + '172.16.254.5' + ], + 'ipv6' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334', + 'ipv6s' => [ + '2001:0db8:85a3:0000:0000:8a2e:0370:7334', + '2001:0db8:85a3:0000:0000:8a2e:0370:7337' + ], + 'key' => uniqid(), + 'keys' => [uniqid(), uniqid(), uniqid()], + ]); + + $document3 = self::$object->getDocument($collection2->getId(), $document3->getId()); + + $this->assertIsString($document3->getId()); + $this->assertIsString($document3->getCollection()); + $this->assertEquals([ + 'read' => ['*'], + 'write' => ['user:123'], + ], $document3->getPermissions()); + $this->assertEquals('Hello World', $document3->getAttribute('text')); + $this->assertCount(2, $document3->getAttribute('texts')); + + $this->assertIsString($document3->getAttribute('text')); + $this->assertEquals('Hello World', $document3->getAttribute('text')); + $this->assertEquals(['Hello World 1', 'Hello World 2'], $document3->getAttribute('texts')); + $this->assertCount(2, $document3->getAttribute('texts')); + + $this->assertInstanceOf(Document::class, $document3->getAttribute('document')); + $this->assertIsString($document3->getAttribute('document')->getId()); + $this->assertNotEmpty($document3->getAttribute('document')->getId()); + $this->assertIsArray($document3->getAttribute('document')->getPermissions()); + $this->assertArrayHasKey('read', $document3->getAttribute('document')->getPermissions()); + $this->assertArrayHasKey('write', $document3->getAttribute('document')->getPermissions()); + $this->assertEquals('Task #0', $document3->getAttribute('document')->getAttribute('name')); + $this->assertCount(4, $document3->getAttribute('document')->getAttribute('links')); + $this->assertEquals('http://example.com/link-1', $document3->getAttribute('document')->getAttribute('links')[0]); + $this->assertEquals('http://example.com/link-2', $document3->getAttribute('document')->getAttribute('links')[1]); + $this->assertEquals('http://example.com/link-3', $document3->getAttribute('document')->getAttribute('links')[2]); + $this->assertEquals('http://example.com/link-4', $document3->getAttribute('document')->getAttribute('links')[3]); + + $this->assertInstanceOf(Document::class, $document3->getAttribute('documents')[0]); + $this->assertIsString($document3->getAttribute('documents')[0]->getId()); + $this->assertNotEmpty($document3->getAttribute('documents')[0]->getId()); + $this->assertIsArray($document3->getAttribute('documents')[0]->getPermissions()); + $this->assertArrayHasKey('read', $document3->getAttribute('documents')[0]->getPermissions()); + $this->assertArrayHasKey('write', $document3->getAttribute('documents')[0]->getPermissions()); + $this->assertEquals('Task #1️⃣', $document3->getAttribute('documents')[0]->getAttribute('name')); + $this->assertCount(4, $document3->getAttribute('documents')[0]->getAttribute('links')); + $this->assertEquals('http://example.com/link-5', $document3->getAttribute('documents')[0]->getAttribute('links')[0]); + $this->assertEquals('http://example.com/link-6', $document3->getAttribute('documents')[0]->getAttribute('links')[1]); + $this->assertEquals('http://example.com/link-7', $document3->getAttribute('documents')[0]->getAttribute('links')[2]); + $this->assertEquals('http://example.com/link-8', $document3->getAttribute('documents')[0]->getAttribute('links')[3]); + + $this->assertInstanceOf(Document::class, $document3->getAttribute('documents')[1]); + $this->assertIsString($document3->getAttribute('documents')[1]->getId()); + $this->assertNotEmpty($document3->getAttribute('documents')[1]->getId()); + $this->assertIsArray($document3->getAttribute('documents')[1]->getPermissions()); + $this->assertArrayHasKey('read', $document3->getAttribute('documents')[1]->getPermissions()); + $this->assertArrayHasKey('write', $document3->getAttribute('documents')[1]->getPermissions()); + $this->assertEquals('Task #0', $document3->getAttribute('documents')[1]->getAttribute('name')); + $this->assertCount(4, $document3->getAttribute('documents')[1]->getAttribute('links')); + $this->assertEquals('http://example.com/link-1', $document3->getAttribute('documents')[1]->getAttribute('links')[0]); + $this->assertEquals('http://example.com/link-2', $document3->getAttribute('documents')[1]->getAttribute('links')[1]); + $this->assertEquals('http://example.com/link-3', $document3->getAttribute('documents')[1]->getAttribute('links')[2]); + $this->assertEquals('http://example.com/link-4', $document3->getAttribute('documents')[1]->getAttribute('links')[3]); + + $this->assertInstanceOf(Document::class, $document3->getAttribute('document2')); + $this->assertIsString($document3->getAttribute('document2')->getId()); + $this->assertNotEmpty($document3->getAttribute('document2')->getId()); + $this->assertIsArray($document3->getAttribute('document2')->getPermissions()); + $this->assertArrayHasKey('read', $document3->getAttribute('document2')->getPermissions()); + $this->assertArrayHasKey('write', $document3->getAttribute('document2')->getPermissions()); + $this->assertEquals('Task #1️⃣', $document3->getAttribute('document2')->getAttribute('name')); + $this->assertCount(4, $document3->getAttribute('document2')->getAttribute('links')); + $this->assertEquals('http://example.com/link-5', $document3->getAttribute('document2')->getAttribute('links')[0]); + $this->assertEquals('http://example.com/link-6', $document3->getAttribute('document2')->getAttribute('links')[1]); + $this->assertEquals('http://example.com/link-7', $document3->getAttribute('document2')->getAttribute('links')[2]); + $this->assertEquals('http://example.com/link-8', $document3->getAttribute('document2')->getAttribute('links')[3]); + + $this->assertInstanceOf(Document::class, $document3->getAttribute('documents2')[0]); + $this->assertIsString($document3->getAttribute('documents2')[0]->getId()); + $this->assertNotEmpty($document3->getAttribute('documents2')[0]->getId()); + $this->assertIsArray($document3->getAttribute('documents2')[0]->getPermissions()); + $this->assertArrayHasKey('read', $document3->getAttribute('documents2')[0]->getPermissions()); + $this->assertArrayHasKey('write', $document3->getAttribute('documents2')[0]->getPermissions()); + $this->assertEquals('Task #0', $document3->getAttribute('documents2')[0]->getAttribute('name')); + $this->assertCount(4, $document3->getAttribute('documents2')[0]->getAttribute('links')); + $this->assertEquals('http://example.com/link-1', $document3->getAttribute('documents2')[0]->getAttribute('links')[0]); + $this->assertEquals('http://example.com/link-2', $document3->getAttribute('documents2')[0]->getAttribute('links')[1]); + $this->assertEquals('http://example.com/link-3', $document3->getAttribute('documents2')[0]->getAttribute('links')[2]); + $this->assertEquals('http://example.com/link-4', $document3->getAttribute('documents2')[0]->getAttribute('links')[3]); + + $this->assertInstanceOf(Document::class, $document3->getAttribute('documents2')[1]); + $this->assertIsString($document3->getAttribute('documents2')[1]->getId()); + $this->assertNotEmpty($document3->getAttribute('documents2')[1]->getId()); + $this->assertIsArray($document3->getAttribute('documents2')[1]->getPermissions()); + $this->assertArrayHasKey('read', $document3->getAttribute('documents2')[1]->getPermissions()); + $this->assertArrayHasKey('write', $document3->getAttribute('documents2')[1]->getPermissions()); + $this->assertEquals('Task #1️⃣', $document3->getAttribute('documents2')[1]->getAttribute('name')); + $this->assertCount(4, $document3->getAttribute('documents2')[1]->getAttribute('links')); + $this->assertEquals('http://example.com/link-5', $document3->getAttribute('documents2')[1]->getAttribute('links')[0]); + $this->assertEquals('http://example.com/link-6', $document3->getAttribute('documents2')[1]->getAttribute('links')[1]); + $this->assertEquals('http://example.com/link-7', $document3->getAttribute('documents2')[1]->getAttribute('links')[2]); + $this->assertEquals('http://example.com/link-8', $document3->getAttribute('documents2')[1]->getAttribute('links')[3]); + + $this->assertIsInt($document3->getAttribute('integer')); + $this->assertEquals(1, $document3->getAttribute('integer')); + $this->assertIsInt($document3->getAttribute('integers')[0]); + $this->assertIsInt($document3->getAttribute('integers')[1]); + $this->assertIsInt($document3->getAttribute('integers')[2]); + $this->assertEquals([5, 3, 4], $document3->getAttribute('integers')); + $this->assertCount(3, $document3->getAttribute('integers')); + + $this->assertIsFloat($document3->getAttribute('float')); + $this->assertEquals(2.22, $document3->getAttribute('float')); + $this->assertIsFloat($document3->getAttribute('floats')[0]); + $this->assertIsFloat($document3->getAttribute('floats')[1]); + $this->assertIsFloat($document3->getAttribute('floats')[2]); + $this->assertEquals([1.13, 4.33, 8.9999], $document3->getAttribute('floats')); + $this->assertCount(3, $document3->getAttribute('floats')); + + $this->assertIsBool($document3->getAttribute('boolean')); + $this->assertEquals(true, $document3->getAttribute('boolean')); + $this->assertIsBool($document3->getAttribute('booleans')[0]); + $this->assertIsBool($document3->getAttribute('booleans')[1]); + $this->assertIsBool($document3->getAttribute('booleans')[2]); + $this->assertEquals([true, false, true], $document3->getAttribute('booleans')); + $this->assertCount(3, $document3->getAttribute('booleans')); + + $this->assertIsString($document3->getAttribute('email')); + $this->assertEquals('test@appwrite.io', $document3->getAttribute('email')); + $this->assertIsString($document3->getAttribute('emails')[0]); + $this->assertIsString($document3->getAttribute('emails')[1]); + $this->assertIsString($document3->getAttribute('emails')[2]); + $this->assertIsString($document3->getAttribute('emails')[3]); + $this->assertEquals([ + 'test4@appwrite.io', + 'test3@appwrite.io', + 'test2@appwrite.io', + 'test1@appwrite.io' + ], $document3->getAttribute('emails')); + $this->assertCount(4, $document3->getAttribute('emails')); + + $this->assertIsString($document3->getAttribute('url')); + $this->assertEquals('http://example.com/welcome', $document3->getAttribute('url')); + $this->assertIsString($document3->getAttribute('urls')[0]); + $this->assertIsString($document3->getAttribute('urls')[1]); + $this->assertIsString($document3->getAttribute('urls')[2]); + $this->assertEquals([ + 'http://example.com/welcome-1', + 'http://example.com/welcome-2', + 'http://example.com/welcome-3' + ], $document3->getAttribute('urls')); + $this->assertCount(3, $document3->getAttribute('urls')); + + $this->assertIsString($document3->getAttribute('ipv4')); + $this->assertEquals('172.16.254.1', $document3->getAttribute('ipv4')); + $this->assertIsString($document3->getAttribute('ipv4s')[0]); + $this->assertIsString($document3->getAttribute('ipv4s')[1]); + $this->assertEquals([ + '172.16.254.1', + '172.16.254.5' + ], $document3->getAttribute('ipv4s')); + $this->assertCount(2, $document3->getAttribute('ipv4s')); + + $this->assertIsString($document3->getAttribute('ipv6')); + $this->assertEquals('2001:0db8:85a3:0000:0000:8a2e:0370:7334', $document3->getAttribute('ipv6')); + $this->assertIsString($document3->getAttribute('ipv6s')[0]); + $this->assertIsString($document3->getAttribute('ipv6s')[1]); + $this->assertEquals([ + '2001:0db8:85a3:0000:0000:8a2e:0370:7334', + '2001:0db8:85a3:0000:0000:8a2e:0370:7337' + ], $document3->getAttribute('ipv6s')); + $this->assertCount(2, $document3->getAttribute('ipv6s')); + + $this->assertEquals('2001:0db8:85a3:0000:0000:8a2e:0370:7334', $document3->getAttribute('ipv6')); + $this->assertEquals([ + '2001:0db8:85a3:0000:0000:8a2e:0370:7334', + '2001:0db8:85a3:0000:0000:8a2e:0370:7337' + ], $document3->getAttribute('ipv6s')); + $this->assertCount(2, $document3->getAttribute('ipv6s')); + + $this->assertIsString($document3->getAttribute('key')); + $this->assertCount(3, $document3->getAttribute('keys')); + } + + public function testGetDocument() + { + // Mocked document + $document = self::$object->getDocument(Database::COLLECTION_COLLECTIONS, Database::COLLECTION_USERS); + + $this->assertEquals(Database::COLLECTION_USERS, $document->getId()); + $this->assertEquals(Database::COLLECTION_COLLECTIONS, $document->getCollection()); + + $collection1 = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ + '$collection' => Database::COLLECTION_COLLECTIONS, + '$permissions' => ['read' => ['*']], + 'name' => 'Create Documents', + 'rules' => [ + [ + '$collection' => Database::COLLECTION_RULES, + '$permissions' => ['read' => ['*']], + 'label' => 'Name', + 'key' => 'name', + 'type' => Database::VAR_TEXT, + 'default' => '', + 'required' => true, + 'array' => false, + ], + [ + '$collection' => Database::COLLECTION_RULES, + '$permissions' => ['read' => ['*']], + 'label' => 'Links', + 'key' => 'links', + 'type' => Database::VAR_URL, + 'default' => '', + 'required' => true, + 'array' => true, + ], + ] + ]); + + $this->assertEquals(true, self::$object->createCollection($collection1->getId(), [], [])); + + $document1 = self::$object->createDocument($collection1->getId(), [ + '$collection' => $collection1->getId(), + '$permissions' => [ + 'read' => ['*'], + 'write' => ['user:123'], + ], + 'name' => 'Task #1️⃣', + 'links' => [ + 'http://example.com/link-5', + 'http://example.com/link-6', + 'http://example.com/link-7', + 'http://example.com/link-8', + ], + ]); + + $document1 = self::$object->getDocument($collection1->getId(), $document1->getId()); + + $this->assertFalse($document1->isEmpty()); + $this->assertNotEmpty($document1->getId()); + $this->assertIsArray($document1->getPermissions()); + $this->assertArrayHasKey('read', $document1->getPermissions()); + $this->assertArrayHasKey('write', $document1->getPermissions()); + $this->assertEquals('Task #1️⃣', $document1->getAttribute('name')); + $this->assertCount(4, $document1->getAttribute('links')); + $this->assertEquals('http://example.com/link-5', $document1->getAttribute('links')[0]); + $this->assertEquals('http://example.com/link-6', $document1->getAttribute('links')[1]); + $this->assertEquals('http://example.com/link-7', $document1->getAttribute('links')[2]); + $this->assertEquals('http://example.com/link-8', $document1->getAttribute('links')[3]); + + $document1 = self::$object->getDocument($collection1->getId(), $document1->getId().'x'); + + $this->assertTrue($document1->isEmpty()); + $this->assertEmpty($document1->getId()); + $this->assertEmpty($document1->getCollection()); + $this->assertIsArray($document1->getPermissions()); + $this->assertEmpty($document1->getPermissions()); + } + + public function testUpdateDocument() + { + $collection1 = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ + '$collection' => Database::COLLECTION_COLLECTIONS, + '$permissions' => ['read' => ['*']], + 'name' => 'Create Documents', + 'rules' => [ + [ + '$collection' => Database::COLLECTION_RULES, + '$permissions' => ['read' => ['*']], + 'label' => 'Name', + 'key' => 'name', + 'type' => Database::VAR_TEXT, + 'default' => '', + 'required' => true, + 'array' => false, + ], + [ + '$collection' => Database::COLLECTION_RULES, + '$permissions' => ['read' => ['*']], + 'label' => 'Links', + 'key' => 'links', + 'type' => Database::VAR_URL, + 'default' => '', + 'required' => true, + 'array' => true, + ], + ] + ]); + + $this->assertEquals(true, self::$object->createCollection($collection1->getId(), [], [])); + + $document0 = new Document([ + '$collection' => $collection1->getId(), + '$permissions' => [ + 'read' => ['*'], + 'write' => ['user:123'], + ], + 'name' => 'Task #0', + 'links' => [ + 'http://example.com/link-1', + 'http://example.com/link-2', + 'http://example.com/link-3', + 'http://example.com/link-4', + ], + ]); + + $document1 = self::$object->createDocument($collection1->getId(), [ + '$collection' => $collection1->getId(), + '$permissions' => [ + 'read' => ['*'], + 'write' => ['user:123'], + ], + 'name' => 'Task #1️⃣', + 'links' => [ + 'http://example.com/link-5', + 'http://example.com/link-6', + 'http://example.com/link-7', + 'http://example.com/link-8', + ], + ]); + + $this->assertNotEmpty($document1->getId()); + $this->assertIsArray($document1->getPermissions()); + $this->assertArrayHasKey('read', $document1->getPermissions()); + $this->assertArrayHasKey('write', $document1->getPermissions()); + $this->assertEquals('Task #1️⃣', $document1->getAttribute('name')); + $this->assertCount(4, $document1->getAttribute('links')); + $this->assertEquals('http://example.com/link-5', $document1->getAttribute('links')[0]); + $this->assertEquals('http://example.com/link-6', $document1->getAttribute('links')[1]); + $this->assertEquals('http://example.com/link-7', $document1->getAttribute('links')[2]); + $this->assertEquals('http://example.com/link-8', $document1->getAttribute('links')[3]); + + $document1 = self::$object->getDocument($collection1->getId(), $document1->getId()); + + $this->assertNotEmpty($document1->getId()); + $this->assertIsArray($document1->getPermissions()); + $this->assertArrayHasKey('read', $document1->getPermissions()); + $this->assertArrayHasKey('write', $document1->getPermissions()); + $this->assertEquals('Task #1️⃣', $document1->getAttribute('name')); + $this->assertCount(4, $document1->getAttribute('links')); + $this->assertEquals('http://example.com/link-5', $document1->getAttribute('links')[0]); + $this->assertEquals('http://example.com/link-6', $document1->getAttribute('links')[1]); + $this->assertEquals('http://example.com/link-7', $document1->getAttribute('links')[2]); + $this->assertEquals('http://example.com/link-8', $document1->getAttribute('links')[3]); + + $document1 = self::$object->updateDocument($collection1->getId(), $document1->getId(), [ + '$id' => $document1->getId(), + '$collection' => $collection1->getId(), + '$permissions' => [ + 'read' => ['user:1234'], + 'write' => ['user:1234'], + ], + 'name' => 'Task #1x', + 'links' => [ + 'http://example.com/link-5x', + 'http://example.com/link-6x', + 'http://example.com/link-7x', + 'http://example.com/link-8x', + ], + ]); + + $this->assertNotEmpty($document1->getId()); + $this->assertIsArray($document1->getPermissions()); + $this->assertArrayHasKey('read', $document1->getPermissions()); + $this->assertArrayHasKey('write', $document1->getPermissions()); + $this->assertEquals('Task #1x', $document1->getAttribute('name')); + $this->assertCount(4, $document1->getAttribute('links')); + $this->assertEquals('http://example.com/link-5x', $document1->getAttribute('links')[0]); + $this->assertEquals('http://example.com/link-6x', $document1->getAttribute('links')[1]); + $this->assertEquals('http://example.com/link-7x', $document1->getAttribute('links')[2]); + $this->assertEquals('http://example.com/link-8x', $document1->getAttribute('links')[3]); + + $document1 = self::$object->getDocument($collection1->getId(), $document1->getId()); + + $this->assertNotEmpty($document1->getId()); + $this->assertIsArray($document1->getPermissions()); + $this->assertArrayHasKey('read', $document1->getPermissions()); + $this->assertArrayHasKey('write', $document1->getPermissions()); + $this->assertEquals('Task #1x', $document1->getAttribute('name')); + $this->assertCount(4, $document1->getAttribute('links')); + $this->assertEquals('http://example.com/link-5x', $document1->getAttribute('links')[0]); + $this->assertEquals('http://example.com/link-6x', $document1->getAttribute('links')[1]); + $this->assertEquals('http://example.com/link-7x', $document1->getAttribute('links')[2]); + $this->assertEquals('http://example.com/link-8x', $document1->getAttribute('links')[3]); + + $document2 = self::$object->createDocument(Database::COLLECTION_USERS, [ + '$collection' => Database::COLLECTION_USERS, + '$permissions' => [ + 'read' => ['*'], + 'write' => ['user:123'], + ], + 'email' => 'test5@appwrite.io', + 'emailVerification' => false, + 'status' => 0, + 'password' => 'secrethash', + 'password-update' => \time(), + 'registration' => \time(), + 'reset' => false, + 'name' => 'Test', + ]); + + $this->assertNotEmpty($document2->getId()); + $this->assertIsArray($document2->getPermissions()); + $this->assertArrayHasKey('read', $document2->getPermissions()); + $this->assertArrayHasKey('write', $document2->getPermissions()); + $this->assertEquals('test5@appwrite.io', $document2->getAttribute('email')); + $this->assertIsString($document2->getAttribute('email')); + $this->assertEquals(0, $document2->getAttribute('status')); + $this->assertIsInt($document2->getAttribute('status')); + $this->assertEquals(false, $document2->getAttribute('emailVerification')); + $this->assertIsBool($document2->getAttribute('emailVerification')); + + $document2 = self::$object->getDocument(Database::COLLECTION_USERS, $document2->getId()); + + $this->assertNotEmpty($document2->getId()); + $this->assertIsArray($document2->getPermissions()); + $this->assertArrayHasKey('read', $document2->getPermissions()); + $this->assertArrayHasKey('write', $document2->getPermissions()); + $this->assertEquals('test5@appwrite.io', $document2->getAttribute('email')); + $this->assertIsString($document2->getAttribute('email')); + $this->assertEquals(0, $document2->getAttribute('status')); + $this->assertIsInt($document2->getAttribute('status')); + $this->assertEquals(false, $document2->getAttribute('emailVerification')); + $this->assertIsBool($document2->getAttribute('emailVerification')); + + $document2 = self::$object->updateDocument(Database::COLLECTION_USERS, $document2->getId(), [ + '$id' => $document2->getId(), + '$collection' => Database::COLLECTION_USERS, + '$permissions' => [ + 'read' => ['user:1234'], + 'write' => ['user:1234'], + ], + 'email' => 'test5x@appwrite.io', + 'emailVerification' => true, + 'status' => 1, + 'password' => 'secrethashx', + 'password-update' => \time(), + 'registration' => \time(), + 'reset' => true, + 'name' => 'Testx', + ]); + + $this->assertNotEmpty($document2->getId()); + $this->assertIsArray($document2->getPermissions()); + $this->assertArrayHasKey('read', $document2->getPermissions()); + $this->assertArrayHasKey('write', $document2->getPermissions()); + $this->assertEquals('test5x@appwrite.io', $document2->getAttribute('email')); + $this->assertIsString($document2->getAttribute('email')); + $this->assertEquals(1, $document2->getAttribute('status')); + $this->assertIsInt($document2->getAttribute('status')); + $this->assertEquals(true, $document2->getAttribute('emailVerification')); + $this->assertIsBool($document2->getAttribute('emailVerification')); + + $document2 = self::$object->getDocument(Database::COLLECTION_USERS, $document2->getId()); + + $this->assertNotEmpty($document2->getId()); + $this->assertIsArray($document2->getPermissions()); + $this->assertArrayHasKey('read', $document2->getPermissions()); + $this->assertArrayHasKey('write', $document2->getPermissions()); + $this->assertEquals('test5x@appwrite.io', $document2->getAttribute('email')); + $this->assertIsString($document2->getAttribute('email')); + $this->assertEquals(1, $document2->getAttribute('status')); + $this->assertIsInt($document2->getAttribute('status')); + $this->assertEquals(true, $document2->getAttribute('emailVerification')); + $this->assertIsBool($document2->getAttribute('emailVerification')); + + $types = [ + Database::VAR_TEXT, + Database::VAR_INTEGER, + Database::VAR_FLOAT, + Database::VAR_NUMERIC, + Database::VAR_BOOLEAN, + Database::VAR_DOCUMENT, + Database::VAR_EMAIL, + Database::VAR_URL, + Database::VAR_IPV4, + Database::VAR_IPV6, + Database::VAR_KEY, + ]; + + $rules = []; + + foreach($types as $type) { + $rules[] = [ + '$collection' => Database::COLLECTION_RULES, + '$permissions' => ['read' => ['*']], + 'label' => ucfirst($type), + 'key' => $type, + 'type' => $type, + 'default' => null, + 'required' => true, + 'array' => false, + 'list' => ($type === Database::VAR_DOCUMENT) ? [$collection1->getId()] : [], + ]; + + $rules[] = [ + '$collection' => Database::COLLECTION_RULES, + '$permissions' => ['read' => ['*']], + 'label' => ucfirst($type), + 'key' => $type.'s', + 'type' => $type, + 'default' => null, + 'required' => true, + 'array' => true, + 'list' => ($type === Database::VAR_DOCUMENT) ? [$collection1->getId()] : [], + ]; + } + + $rules[] = [ + '$collection' => Database::COLLECTION_RULES, + '$permissions' => ['read' => ['*']], + 'label' => 'document2', + 'key' => 'document2', + 'type' => Database::VAR_DOCUMENT, + 'default' => null, + 'required' => true, + 'array' => false, + 'list' => [$collection1->getId()], + ]; + + $rules[] = [ + '$collection' => Database::COLLECTION_RULES, + '$permissions' => ['read' => ['*']], + 'label' => 'documents2', + 'key' => 'documents2', + 'type' => Database::VAR_DOCUMENT, + 'default' => null, + 'required' => true, + 'array' => true, + 'list' => [$collection1->getId()], + ]; + + $collection2 = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ + '$collection' => Database::COLLECTION_COLLECTIONS, + '$permissions' => ['read' => ['*']], + 'name' => 'Create Documents', + 'rules' => $rules, + ]); + + $this->assertEquals(true, self::$object->createCollection($collection2->getId(), [], [])); + + $document3 = self::$object->createDocument($collection2->getId(), [ + '$collection' => $collection2->getId(), + '$permissions' => [ + 'read' => ['*'], + 'write' => ['user:123'], + ], + 'text' => 'Hello World', + 'texts' => ['Hello World 1', 'Hello World 2'], + // 'document' => $document0, + // 'documents' => [$document0], + 'document' => $document0, + 'documents' => [$document1, $document0], + 'document2' => $document1, + 'documents2' => [$document0, $document1], + 'integer' => 1, + 'integers' => [5, 3, 4], + 'float' => 2.22, + 'floats' => [1.13, 4.33, 8.9999], + 'numeric' => 1, + 'numerics' => [1, 5, 7.77], + 'boolean' => true, + 'booleans' => [true, false, true], + 'email' => 'test@appwrite.io', + 'emails' => [ + 'test4@appwrite.io', + 'test3@appwrite.io', + 'test2@appwrite.io', + 'test1@appwrite.io' + ], + 'url' => 'http://example.com/welcome', + 'urls' => [ + 'http://example.com/welcome-1', + 'http://example.com/welcome-2', + 'http://example.com/welcome-3' + ], + 'ipv4' => '172.16.254.1', + 'ipv4s' => [ + '172.16.254.1', + '172.16.254.5' + ], + 'ipv6' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334', + 'ipv6s' => [ + '2001:0db8:85a3:0000:0000:8a2e:0370:7334', + '2001:0db8:85a3:0000:0000:8a2e:0370:7337' + ], + 'key' => uniqid(), + 'keys' => [uniqid(), uniqid(), uniqid()], + ]); + + $document3 = self::$object->getDocument($collection2->getId(), $document3->getId()); + + $this->assertIsString($document3->getId()); + $this->assertIsString($document3->getCollection()); + $this->assertEquals([ + 'read' => ['*'], + 'write' => ['user:123'], + ], $document3->getPermissions()); + $this->assertEquals('Hello World', $document3->getAttribute('text')); + $this->assertCount(2, $document3->getAttribute('texts')); + + $this->assertIsString($document3->getAttribute('text')); + $this->assertEquals('Hello World', $document3->getAttribute('text')); + $this->assertEquals(['Hello World 1', 'Hello World 2'], $document3->getAttribute('texts')); + $this->assertCount(2, $document3->getAttribute('texts')); + + $this->assertInstanceOf(Document::class, $document3->getAttribute('document')); + $this->assertIsString($document3->getAttribute('document')->getId()); + $this->assertNotEmpty($document3->getAttribute('document')->getId()); + $this->assertIsArray($document3->getAttribute('document')->getPermissions()); + $this->assertArrayHasKey('read', $document3->getAttribute('document')->getPermissions()); + $this->assertArrayHasKey('write', $document3->getAttribute('document')->getPermissions()); + $this->assertEquals('Task #0', $document3->getAttribute('document')->getAttribute('name')); + $this->assertCount(4, $document3->getAttribute('document')->getAttribute('links')); + $this->assertEquals('http://example.com/link-1', $document3->getAttribute('document')->getAttribute('links')[0]); + $this->assertEquals('http://example.com/link-2', $document3->getAttribute('document')->getAttribute('links')[1]); + $this->assertEquals('http://example.com/link-3', $document3->getAttribute('document')->getAttribute('links')[2]); + $this->assertEquals('http://example.com/link-4', $document3->getAttribute('document')->getAttribute('links')[3]); + + $this->assertInstanceOf(Document::class, $document3->getAttribute('documents')[0]); + $this->assertIsString($document3->getAttribute('documents')[0]->getId()); + $this->assertNotEmpty($document3->getAttribute('documents')[0]->getId()); + $this->assertIsArray($document3->getAttribute('documents')[0]->getPermissions()); + $this->assertArrayHasKey('read', $document3->getAttribute('documents')[0]->getPermissions()); + $this->assertArrayHasKey('write', $document3->getAttribute('documents')[0]->getPermissions()); + $this->assertEquals('Task #1x', $document3->getAttribute('documents')[0]->getAttribute('name')); + $this->assertCount(4, $document3->getAttribute('documents')[0]->getAttribute('links')); + $this->assertEquals('http://example.com/link-5x', $document3->getAttribute('documents')[0]->getAttribute('links')[0]); + $this->assertEquals('http://example.com/link-6x', $document3->getAttribute('documents')[0]->getAttribute('links')[1]); + $this->assertEquals('http://example.com/link-7x', $document3->getAttribute('documents')[0]->getAttribute('links')[2]); + $this->assertEquals('http://example.com/link-8x', $document3->getAttribute('documents')[0]->getAttribute('links')[3]); + + $this->assertInstanceOf(Document::class, $document3->getAttribute('documents')[1]); + $this->assertIsString($document3->getAttribute('documents')[1]->getId()); + $this->assertNotEmpty($document3->getAttribute('documents')[1]->getId()); + $this->assertIsArray($document3->getAttribute('documents')[1]->getPermissions()); + $this->assertArrayHasKey('read', $document3->getAttribute('documents')[1]->getPermissions()); + $this->assertArrayHasKey('write', $document3->getAttribute('documents')[1]->getPermissions()); + $this->assertEquals('Task #0', $document3->getAttribute('documents')[1]->getAttribute('name')); + $this->assertCount(4, $document3->getAttribute('documents')[1]->getAttribute('links')); + $this->assertEquals('http://example.com/link-1', $document3->getAttribute('documents')[1]->getAttribute('links')[0]); + $this->assertEquals('http://example.com/link-2', $document3->getAttribute('documents')[1]->getAttribute('links')[1]); + $this->assertEquals('http://example.com/link-3', $document3->getAttribute('documents')[1]->getAttribute('links')[2]); + $this->assertEquals('http://example.com/link-4', $document3->getAttribute('documents')[1]->getAttribute('links')[3]); + + $this->assertInstanceOf(Document::class, $document3->getAttribute('document2')); + $this->assertIsString($document3->getAttribute('document2')->getId()); + $this->assertNotEmpty($document3->getAttribute('document2')->getId()); + $this->assertIsArray($document3->getAttribute('document2')->getPermissions()); + $this->assertArrayHasKey('read', $document3->getAttribute('document2')->getPermissions()); + $this->assertArrayHasKey('write', $document3->getAttribute('document2')->getPermissions()); + $this->assertEquals('Task #1x', $document3->getAttribute('document2')->getAttribute('name')); + $this->assertCount(4, $document3->getAttribute('document2')->getAttribute('links')); + $this->assertEquals('http://example.com/link-5x', $document3->getAttribute('document2')->getAttribute('links')[0]); + $this->assertEquals('http://example.com/link-6x', $document3->getAttribute('document2')->getAttribute('links')[1]); + $this->assertEquals('http://example.com/link-7x', $document3->getAttribute('document2')->getAttribute('links')[2]); + $this->assertEquals('http://example.com/link-8x', $document3->getAttribute('document2')->getAttribute('links')[3]); + + $this->assertInstanceOf(Document::class, $document3->getAttribute('documents2')[0]); + $this->assertIsString($document3->getAttribute('documents2')[0]->getId()); + $this->assertNotEmpty($document3->getAttribute('documents2')[0]->getId()); + $this->assertIsArray($document3->getAttribute('documents2')[0]->getPermissions()); + $this->assertArrayHasKey('read', $document3->getAttribute('documents2')[0]->getPermissions()); + $this->assertArrayHasKey('write', $document3->getAttribute('documents2')[0]->getPermissions()); + $this->assertEquals('Task #0', $document3->getAttribute('documents2')[0]->getAttribute('name')); + $this->assertCount(4, $document3->getAttribute('documents2')[0]->getAttribute('links')); + $this->assertEquals('http://example.com/link-1', $document3->getAttribute('documents2')[0]->getAttribute('links')[0]); + $this->assertEquals('http://example.com/link-2', $document3->getAttribute('documents2')[0]->getAttribute('links')[1]); + $this->assertEquals('http://example.com/link-3', $document3->getAttribute('documents2')[0]->getAttribute('links')[2]); + $this->assertEquals('http://example.com/link-4', $document3->getAttribute('documents2')[0]->getAttribute('links')[3]); + + $this->assertInstanceOf(Document::class, $document3->getAttribute('documents2')[1]); + $this->assertIsString($document3->getAttribute('documents2')[1]->getId()); + $this->assertNotEmpty($document3->getAttribute('documents2')[1]->getId()); + $this->assertIsArray($document3->getAttribute('documents2')[1]->getPermissions()); + $this->assertArrayHasKey('read', $document3->getAttribute('documents2')[1]->getPermissions()); + $this->assertArrayHasKey('write', $document3->getAttribute('documents2')[1]->getPermissions()); + $this->assertEquals('Task #1x', $document3->getAttribute('documents2')[1]->getAttribute('name')); + $this->assertCount(4, $document3->getAttribute('documents2')[1]->getAttribute('links')); + $this->assertEquals('http://example.com/link-5x', $document3->getAttribute('documents2')[1]->getAttribute('links')[0]); + $this->assertEquals('http://example.com/link-6x', $document3->getAttribute('documents2')[1]->getAttribute('links')[1]); + $this->assertEquals('http://example.com/link-7x', $document3->getAttribute('documents2')[1]->getAttribute('links')[2]); + $this->assertEquals('http://example.com/link-8x', $document3->getAttribute('documents2')[1]->getAttribute('links')[3]); + + $this->assertIsInt($document3->getAttribute('integer')); + $this->assertEquals(1, $document3->getAttribute('integer')); + $this->assertIsInt($document3->getAttribute('integers')[0]); + $this->assertIsInt($document3->getAttribute('integers')[1]); + $this->assertIsInt($document3->getAttribute('integers')[2]); + $this->assertEquals([5, 3, 4], $document3->getAttribute('integers')); + $this->assertCount(3, $document3->getAttribute('integers')); + + $this->assertIsFloat($document3->getAttribute('float')); + $this->assertEquals(2.22, $document3->getAttribute('float')); + $this->assertIsFloat($document3->getAttribute('floats')[0]); + $this->assertIsFloat($document3->getAttribute('floats')[1]); + $this->assertIsFloat($document3->getAttribute('floats')[2]); + $this->assertEquals([1.13, 4.33, 8.9999], $document3->getAttribute('floats')); + $this->assertCount(3, $document3->getAttribute('floats')); + + $this->assertIsBool($document3->getAttribute('boolean')); + $this->assertEquals(true, $document3->getAttribute('boolean')); + $this->assertIsBool($document3->getAttribute('booleans')[0]); + $this->assertIsBool($document3->getAttribute('booleans')[1]); + $this->assertIsBool($document3->getAttribute('booleans')[2]); + $this->assertEquals([true, false, true], $document3->getAttribute('booleans')); + $this->assertCount(3, $document3->getAttribute('booleans')); + + $this->assertIsString($document3->getAttribute('email')); + $this->assertEquals('test@appwrite.io', $document3->getAttribute('email')); + $this->assertIsString($document3->getAttribute('emails')[0]); + $this->assertIsString($document3->getAttribute('emails')[1]); + $this->assertIsString($document3->getAttribute('emails')[2]); + $this->assertIsString($document3->getAttribute('emails')[3]); + $this->assertEquals([ + 'test4@appwrite.io', + 'test3@appwrite.io', + 'test2@appwrite.io', + 'test1@appwrite.io' + ], $document3->getAttribute('emails')); + $this->assertCount(4, $document3->getAttribute('emails')); + + $this->assertIsString($document3->getAttribute('url')); + $this->assertEquals('http://example.com/welcome', $document3->getAttribute('url')); + $this->assertIsString($document3->getAttribute('urls')[0]); + $this->assertIsString($document3->getAttribute('urls')[1]); + $this->assertIsString($document3->getAttribute('urls')[2]); + $this->assertEquals([ + 'http://example.com/welcome-1', + 'http://example.com/welcome-2', + 'http://example.com/welcome-3' + ], $document3->getAttribute('urls')); + $this->assertCount(3, $document3->getAttribute('urls')); + + $this->assertIsString($document3->getAttribute('ipv4')); + $this->assertEquals('172.16.254.1', $document3->getAttribute('ipv4')); + $this->assertIsString($document3->getAttribute('ipv4s')[0]); + $this->assertIsString($document3->getAttribute('ipv4s')[1]); + $this->assertEquals([ + '172.16.254.1', + '172.16.254.5' + ], $document3->getAttribute('ipv4s')); + $this->assertCount(2, $document3->getAttribute('ipv4s')); + + $this->assertIsString($document3->getAttribute('ipv6')); + $this->assertEquals('2001:0db8:85a3:0000:0000:8a2e:0370:7334', $document3->getAttribute('ipv6')); + $this->assertIsString($document3->getAttribute('ipv6s')[0]); + $this->assertIsString($document3->getAttribute('ipv6s')[1]); + $this->assertEquals([ + '2001:0db8:85a3:0000:0000:8a2e:0370:7334', + '2001:0db8:85a3:0000:0000:8a2e:0370:7337' + ], $document3->getAttribute('ipv6s')); + $this->assertCount(2, $document3->getAttribute('ipv6s')); + + $this->assertEquals('2001:0db8:85a3:0000:0000:8a2e:0370:7334', $document3->getAttribute('ipv6')); + $this->assertEquals([ + '2001:0db8:85a3:0000:0000:8a2e:0370:7334', + '2001:0db8:85a3:0000:0000:8a2e:0370:7337' + ], $document3->getAttribute('ipv6s')); + $this->assertCount(2, $document3->getAttribute('ipv6s')); + + $this->assertIsString($document3->getAttribute('key')); + $this->assertCount(3, $document3->getAttribute('keys')); + + // Update + + $document3 = self::$object->updateDocument($collection2->getId(), $document3->getId(), [ + '$id' => $document3->getId(), + '$collection' => $collection2->getId(), + '$permissions' => [ + 'read' => ['user:1234'], + 'write' => ['user:1234'], + ], + 'text' => 'Hello Worldx', + 'texts' => ['Hello World 1x', 'Hello World 2x'], + 'document' => $document0, + 'documents' => [$document1, $document0], + 'document2' => $document1, + 'documents2' => [$document0, $document1], + 'integer' => 2, + 'integers' => [6, 4, 5], + 'float' => 3.22, + 'floats' => [2.13, 5.33, 9.9999], + 'numeric' => 2, + 'numerics' => [2, 6, 8.77], + 'boolean' => false, + 'booleans' => [false, true, false], + 'email' => 'testx@appwrite.io', + 'emails' => [ + 'test4x@appwrite.io', + 'test3x@appwrite.io', + 'test2x@appwrite.io', + 'test1x@appwrite.io' + ], + 'url' => 'http://example.com/welcomex', + 'urls' => [ + 'http://example.com/welcome-1x', + 'http://example.com/welcome-2x', + 'http://example.com/welcome-3x' + ], + 'ipv4' => '172.16.254.2', + 'ipv4s' => [ + '172.16.254.2', + '172.16.254.6' + ], + 'ipv6' => '2001:0db8:85a3:0000:0000:8a2e:0370:7335', + 'ipv6s' => [ + '2001:0db8:85a3:0000:0000:8a2e:0370:7335', + '2001:0db8:85a3:0000:0000:8a2e:0370:7338' + ], + 'key' => uniqid().'x', + 'keys' => [uniqid().'x', uniqid().'x', uniqid().'x'], + ]); + + $document3 = self::$object->getDocument($collection2->getId(), $document3->getId()); + + $this->assertIsString($document3->getId()); + $this->assertIsString($document3->getCollection()); + $this->assertEquals([ + 'read' => ['user:1234'], + 'write' => ['user:1234'], + ], $document3->getPermissions()); + $this->assertEquals('Hello Worldx', $document3->getAttribute('text')); + $this->assertCount(2, $document3->getAttribute('texts')); + + $this->assertIsString($document3->getAttribute('text')); + $this->assertEquals('Hello Worldx', $document3->getAttribute('text')); + $this->assertEquals(['Hello World 1x', 'Hello World 2x'], $document3->getAttribute('texts')); + $this->assertCount(2, $document3->getAttribute('texts')); + + $this->assertInstanceOf(Document::class, $document3->getAttribute('document')); + $this->assertIsString($document3->getAttribute('document')->getId()); + $this->assertNotEmpty($document3->getAttribute('document')->getId()); + $this->assertIsArray($document3->getAttribute('document')->getPermissions()); + $this->assertArrayHasKey('read', $document3->getAttribute('document')->getPermissions()); + $this->assertArrayHasKey('write', $document3->getAttribute('document')->getPermissions()); + $this->assertEquals('Task #0', $document3->getAttribute('document')->getAttribute('name')); + $this->assertCount(4, $document3->getAttribute('document')->getAttribute('links')); + $this->assertEquals('http://example.com/link-1', $document3->getAttribute('document')->getAttribute('links')[0]); + $this->assertEquals('http://example.com/link-2', $document3->getAttribute('document')->getAttribute('links')[1]); + $this->assertEquals('http://example.com/link-3', $document3->getAttribute('document')->getAttribute('links')[2]); + $this->assertEquals('http://example.com/link-4', $document3->getAttribute('document')->getAttribute('links')[3]); + + $this->assertInstanceOf(Document::class, $document3->getAttribute('documents')[0]); + $this->assertIsString($document3->getAttribute('documents')[0]->getId()); + $this->assertNotEmpty($document3->getAttribute('documents')[0]->getId()); + $this->assertIsArray($document3->getAttribute('documents')[0]->getPermissions()); + $this->assertArrayHasKey('read', $document3->getAttribute('documents')[0]->getPermissions()); + $this->assertArrayHasKey('write', $document3->getAttribute('documents')[0]->getPermissions()); + $this->assertEquals('Task #1x', $document3->getAttribute('documents')[0]->getAttribute('name')); + $this->assertCount(4, $document3->getAttribute('documents')[0]->getAttribute('links')); + $this->assertEquals('http://example.com/link-5x', $document3->getAttribute('documents')[0]->getAttribute('links')[0]); + $this->assertEquals('http://example.com/link-6x', $document3->getAttribute('documents')[0]->getAttribute('links')[1]); + $this->assertEquals('http://example.com/link-7x', $document3->getAttribute('documents')[0]->getAttribute('links')[2]); + $this->assertEquals('http://example.com/link-8x', $document3->getAttribute('documents')[0]->getAttribute('links')[3]); + + $this->assertInstanceOf(Document::class, $document3->getAttribute('documents')[1]); + $this->assertIsString($document3->getAttribute('documents')[1]->getId()); + $this->assertNotEmpty($document3->getAttribute('documents')[1]->getId()); + $this->assertIsArray($document3->getAttribute('documents')[1]->getPermissions()); + $this->assertArrayHasKey('read', $document3->getAttribute('documents')[1]->getPermissions()); + $this->assertArrayHasKey('write', $document3->getAttribute('documents')[1]->getPermissions()); + $this->assertEquals('Task #0', $document3->getAttribute('documents')[1]->getAttribute('name')); + $this->assertCount(4, $document3->getAttribute('documents')[1]->getAttribute('links')); + $this->assertEquals('http://example.com/link-1', $document3->getAttribute('documents')[1]->getAttribute('links')[0]); + $this->assertEquals('http://example.com/link-2', $document3->getAttribute('documents')[1]->getAttribute('links')[1]); + $this->assertEquals('http://example.com/link-3', $document3->getAttribute('documents')[1]->getAttribute('links')[2]); + $this->assertEquals('http://example.com/link-4', $document3->getAttribute('documents')[1]->getAttribute('links')[3]); + + $this->assertInstanceOf(Document::class, $document3->getAttribute('document2')); + $this->assertIsString($document3->getAttribute('document2')->getId()); + $this->assertNotEmpty($document3->getAttribute('document2')->getId()); + $this->assertIsArray($document3->getAttribute('document2')->getPermissions()); + $this->assertArrayHasKey('read', $document3->getAttribute('document2')->getPermissions()); + $this->assertArrayHasKey('write', $document3->getAttribute('document2')->getPermissions()); + $this->assertEquals('Task #1x', $document3->getAttribute('document2')->getAttribute('name')); + $this->assertCount(4, $document3->getAttribute('document2')->getAttribute('links')); + $this->assertEquals('http://example.com/link-5x', $document3->getAttribute('document2')->getAttribute('links')[0]); + $this->assertEquals('http://example.com/link-6x', $document3->getAttribute('document2')->getAttribute('links')[1]); + $this->assertEquals('http://example.com/link-7x', $document3->getAttribute('document2')->getAttribute('links')[2]); + $this->assertEquals('http://example.com/link-8x', $document3->getAttribute('document2')->getAttribute('links')[3]); + + $this->assertInstanceOf(Document::class, $document3->getAttribute('documents2')[0]); + $this->assertIsString($document3->getAttribute('documents2')[0]->getId()); + $this->assertNotEmpty($document3->getAttribute('documents2')[0]->getId()); + $this->assertIsArray($document3->getAttribute('documents2')[0]->getPermissions()); + $this->assertArrayHasKey('read', $document3->getAttribute('documents2')[0]->getPermissions()); + $this->assertArrayHasKey('write', $document3->getAttribute('documents2')[0]->getPermissions()); + $this->assertEquals('Task #0', $document3->getAttribute('documents2')[0]->getAttribute('name')); + $this->assertCount(4, $document3->getAttribute('documents2')[0]->getAttribute('links')); + $this->assertEquals('http://example.com/link-1', $document3->getAttribute('documents2')[0]->getAttribute('links')[0]); + $this->assertEquals('http://example.com/link-2', $document3->getAttribute('documents2')[0]->getAttribute('links')[1]); + $this->assertEquals('http://example.com/link-3', $document3->getAttribute('documents2')[0]->getAttribute('links')[2]); + $this->assertEquals('http://example.com/link-4', $document3->getAttribute('documents2')[0]->getAttribute('links')[3]); + + $this->assertInstanceOf(Document::class, $document3->getAttribute('documents2')[1]); + $this->assertIsString($document3->getAttribute('documents2')[1]->getId()); + $this->assertNotEmpty($document3->getAttribute('documents2')[1]->getId()); + $this->assertIsArray($document3->getAttribute('documents2')[1]->getPermissions()); + $this->assertArrayHasKey('read', $document3->getAttribute('documents2')[1]->getPermissions()); + $this->assertArrayHasKey('write', $document3->getAttribute('documents2')[1]->getPermissions()); + $this->assertEquals('Task #1x', $document3->getAttribute('documents2')[1]->getAttribute('name')); + $this->assertCount(4, $document3->getAttribute('documents2')[1]->getAttribute('links')); + $this->assertEquals('http://example.com/link-5x', $document3->getAttribute('documents2')[1]->getAttribute('links')[0]); + $this->assertEquals('http://example.com/link-6x', $document3->getAttribute('documents2')[1]->getAttribute('links')[1]); + $this->assertEquals('http://example.com/link-7x', $document3->getAttribute('documents2')[1]->getAttribute('links')[2]); + $this->assertEquals('http://example.com/link-8x', $document3->getAttribute('documents2')[1]->getAttribute('links')[3]); + + $this->assertIsInt($document3->getAttribute('integer')); + $this->assertEquals(2, $document3->getAttribute('integer')); + $this->assertIsInt($document3->getAttribute('integers')[0]); + $this->assertIsInt($document3->getAttribute('integers')[1]); + $this->assertIsInt($document3->getAttribute('integers')[2]); + $this->assertEquals([6, 4, 5], $document3->getAttribute('integers')); + $this->assertCount(3, $document3->getAttribute('integers')); + + $this->assertIsFloat($document3->getAttribute('float')); + $this->assertEquals(3.22, $document3->getAttribute('float')); + $this->assertIsFloat($document3->getAttribute('floats')[0]); + $this->assertIsFloat($document3->getAttribute('floats')[1]); + $this->assertIsFloat($document3->getAttribute('floats')[2]); + $this->assertEquals([2.13, 5.33, 9.9999], $document3->getAttribute('floats')); + $this->assertCount(3, $document3->getAttribute('floats')); + + $this->assertIsBool($document3->getAttribute('boolean')); + $this->assertEquals(false, $document3->getAttribute('boolean')); + $this->assertIsBool($document3->getAttribute('booleans')[0]); + $this->assertIsBool($document3->getAttribute('booleans')[1]); + $this->assertIsBool($document3->getAttribute('booleans')[2]); + $this->assertEquals([false, true, false], $document3->getAttribute('booleans')); + $this->assertCount(3, $document3->getAttribute('booleans')); + + $this->assertIsString($document3->getAttribute('email')); + $this->assertEquals('testx@appwrite.io', $document3->getAttribute('email')); + $this->assertIsString($document3->getAttribute('emails')[0]); + $this->assertIsString($document3->getAttribute('emails')[1]); + $this->assertIsString($document3->getAttribute('emails')[2]); + $this->assertIsString($document3->getAttribute('emails')[3]); + $this->assertEquals([ + 'test4x@appwrite.io', + 'test3x@appwrite.io', + 'test2x@appwrite.io', + 'test1x@appwrite.io' + ], $document3->getAttribute('emails')); + $this->assertCount(4, $document3->getAttribute('emails')); + + $this->assertIsString($document3->getAttribute('url')); + $this->assertEquals('http://example.com/welcomex', $document3->getAttribute('url')); + $this->assertIsString($document3->getAttribute('urls')[0]); + $this->assertIsString($document3->getAttribute('urls')[1]); + $this->assertIsString($document3->getAttribute('urls')[2]); + $this->assertEquals([ + 'http://example.com/welcome-1x', + 'http://example.com/welcome-2x', + 'http://example.com/welcome-3x' + ], $document3->getAttribute('urls')); + $this->assertCount(3, $document3->getAttribute('urls')); + + $this->assertIsString($document3->getAttribute('ipv4')); + $this->assertEquals('172.16.254.2', $document3->getAttribute('ipv4')); + $this->assertIsString($document3->getAttribute('ipv4s')[0]); + $this->assertIsString($document3->getAttribute('ipv4s')[1]); + $this->assertEquals([ + '172.16.254.2', + '172.16.254.6' + ], $document3->getAttribute('ipv4s')); + $this->assertCount(2, $document3->getAttribute('ipv4s')); + + $this->assertIsString($document3->getAttribute('ipv6')); + $this->assertEquals('2001:0db8:85a3:0000:0000:8a2e:0370:7335', $document3->getAttribute('ipv6')); + $this->assertIsString($document3->getAttribute('ipv6s')[0]); + $this->assertIsString($document3->getAttribute('ipv6s')[1]); + $this->assertEquals([ + '2001:0db8:85a3:0000:0000:8a2e:0370:7335', + '2001:0db8:85a3:0000:0000:8a2e:0370:7338' + ], $document3->getAttribute('ipv6s')); + $this->assertCount(2, $document3->getAttribute('ipv6s')); + + $this->assertEquals('2001:0db8:85a3:0000:0000:8a2e:0370:7335', $document3->getAttribute('ipv6')); + $this->assertEquals([ + '2001:0db8:85a3:0000:0000:8a2e:0370:7335', + '2001:0db8:85a3:0000:0000:8a2e:0370:7338' + ], $document3->getAttribute('ipv6s')); + $this->assertCount(2, $document3->getAttribute('ipv6s')); + + $this->assertIsString($document3->getAttribute('key')); + $this->assertCount(3, $document3->getAttribute('keys')); + } + + public function testDeleteDocument() + { + $collection1 = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ + '$collection' => Database::COLLECTION_COLLECTIONS, + '$permissions' => ['read' => ['*']], + 'name' => 'Create Documents', + 'rules' => [ + [ + '$collection' => Database::COLLECTION_RULES, + '$permissions' => ['read' => ['*']], + 'label' => 'Name', + 'key' => 'name', + 'type' => Database::VAR_TEXT, + 'default' => '', + 'required' => true, + 'array' => false, + ], + [ + '$collection' => Database::COLLECTION_RULES, + '$permissions' => ['read' => ['*']], + 'label' => 'Links', + 'key' => 'links', + 'type' => Database::VAR_URL, + 'default' => '', + 'required' => true, + 'array' => true, + ], + ] + ]); + + $this->assertEquals(true, self::$object->createCollection($collection1->getId(), [], [])); + + $document1 = self::$object->createDocument($collection1->getId(), [ + '$collection' => $collection1->getId(), + '$permissions' => [ + 'read' => ['*'], + 'write' => ['user:123'], + ], + 'name' => 'Task #1️⃣', + 'links' => [ + 'http://example.com/link-5', + 'http://example.com/link-6', + 'http://example.com/link-7', + 'http://example.com/link-8', + ], + ]); + + $this->assertNotEmpty($document1->getId()); + $this->assertIsArray($document1->getPermissions()); + $this->assertArrayHasKey('read', $document1->getPermissions()); + $this->assertArrayHasKey('write', $document1->getPermissions()); + $this->assertEquals('Task #1️⃣', $document1->getAttribute('name')); + $this->assertCount(4, $document1->getAttribute('links')); + $this->assertEquals('http://example.com/link-5', $document1->getAttribute('links')[0]); + $this->assertEquals('http://example.com/link-6', $document1->getAttribute('links')[1]); + $this->assertEquals('http://example.com/link-7', $document1->getAttribute('links')[2]); + $this->assertEquals('http://example.com/link-8', $document1->getAttribute('links')[3]); + + $document1 = self::$object->getDocument($collection1->getId(), $document1->getId()); + + $this->assertNotEmpty($document1->getId()); + $this->assertIsArray($document1->getPermissions()); + $this->assertArrayHasKey('read', $document1->getPermissions()); + $this->assertArrayHasKey('write', $document1->getPermissions()); + $this->assertEquals('Task #1️⃣', $document1->getAttribute('name')); + $this->assertCount(4, $document1->getAttribute('links')); + $this->assertEquals('http://example.com/link-5', $document1->getAttribute('links')[0]); + $this->assertEquals('http://example.com/link-6', $document1->getAttribute('links')[1]); + $this->assertEquals('http://example.com/link-7', $document1->getAttribute('links')[2]); + $this->assertEquals('http://example.com/link-8', $document1->getAttribute('links')[3]); + + self::$object->deleteDocument($collection1->getId(), $document1->getId()); + + $document1 = self::$object->getDocument($collection1->getId(), $document1->getId()); + + $this->assertTrue($document1->isEmpty()); + $this->assertEmpty($document1->getId()); + $this->assertEmpty($document1->getCollection()); + $this->assertIsArray($document1->getPermissions()); + $this->assertEmpty($document1->getPermissions()); + } + + public function testFind() + { + $data = include __DIR__.'/../../resources/database/movies.php'; + + $collections = $data['collections']; + $movies = $data['movies']; + + foreach ($collections as $key => &$collection) { + $collection = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, $collection); + self::$object->createCollection($collection->getId(), [], []); + } + + foreach ($movies as $key => &$movie) { + $movie['$collection'] = $collection->getId(); + $movie['$permissions'] = []; + $movie = self::$object->createDocument($collection->getId(), $movie); + } + + self::$object->find($collection->getId(), [ + 'limit' => 5, + 'filters' => [ + 'name=Hello World', + 'releaseYear=1999', + 'langauges=English', + ], + ]); + $this->assertEquals('1', '1'); + } + + public function testFindFirst() + { + $this->assertEquals('1', '1'); + } + + public function testFindLast() + { + $this->assertEquals('1', '1'); + } + + public function countTest() + { + $this->assertEquals('1', '1'); + } + + public function addFilterTest() + { + $this->assertEquals('1', '1'); + } + + public function encodeTest() + { + $this->assertEquals('1', '1'); + } + + public function decodeTest() + { + $this->assertEquals('1', '1'); + } +} \ No newline at end of file diff --git a/tests/Database/Validator/AuthorizationTest.php b/tests/Database/Validator/AuthorizationTest.php new file mode 100644 index 000000000..d46fec3bc --- /dev/null +++ b/tests/Database/Validator/AuthorizationTest.php @@ -0,0 +1,90 @@ +document = new Document([ + '$id' => uniqid(), + '$collection' => uniqid(), + '$permissions' => [ + 'read' => ['user:123', 'team:123'], + 'write' => ['*'], + ], + ]); + $this->object = new Authorization($this->document, 'read'); + } + + public function tearDown(): void + { + } + + public function testValues() + { + $this->assertEquals($this->object->isValid($this->document->getPermissions()), false); + + Authorization::setRole('user:456'); + Authorization::setRole('user:123'); + + $this->assertEquals(Authorization::isRole('user:456'), true); + $this->assertEquals(Authorization::isRole('user:457'), false); + $this->assertEquals(Authorization::isRole(''), false); + $this->assertEquals(Authorization::isRole('*'), true); + + $this->assertEquals($this->object->isValid($this->document->getPermissions()), true); + + Authorization::cleanRoles(); + + $this->assertEquals($this->object->isValid($this->document->getPermissions()), false); + + Authorization::setRole('team:123'); + + $this->assertEquals($this->object->isValid($this->document->getPermissions()), true); + + Authorization::cleanRoles(); + Authorization::disable(); + + $this->assertEquals($this->object->isValid($this->document->getPermissions()), true); + + Authorization::reset(); + + $this->assertEquals($this->object->isValid($this->document->getPermissions()), false); + + Authorization::setDefaultStatus(false); + Authorization::disable(); + + $this->assertEquals($this->object->isValid($this->document->getPermissions()), true); + + Authorization::reset(); + + $this->assertEquals($this->object->isValid($this->document->getPermissions()), true); + + Authorization::enable(); + + $this->assertEquals($this->object->isValid($this->document->getPermissions()), false); + + Authorization::setRole('textX'); + + $this->assertContains('textX', Authorization::getRoles()); + + Authorization::unsetRole('textX'); + + $this->assertNotContains('textX', Authorization::getRoles()); + } +} \ No newline at end of file diff --git a/tests/Database/Validator/KeyTest.php b/tests/Database/Validator/KeyTest.php new file mode 100644 index 000000000..68362c22f --- /dev/null +++ b/tests/Database/Validator/KeyTest.php @@ -0,0 +1,36 @@ +object = new Key(); + } + + public function tearDown(): void + { + } + + public function testValues() + { + $this->assertEquals($this->object->isValid('dasda asdasd'), false); + $this->assertEquals($this->object->isValid('asdasdasdas'), true); + $this->assertEquals($this->object->isValid('as$$5dasdasdas'), false); + $this->assertEquals($this->object->isValid(false), false); + $this->assertEquals($this->object->isValid(null), false); + $this->assertEquals($this->object->isValid('socialAccountForYoutubeSubscribers'), false); + $this->assertEquals($this->object->isValid('socialAccountForYoutubeSubscriber'), false); + $this->assertEquals($this->object->isValid('socialAccountForYoutubeSubscribe'), true); + $this->assertEquals($this->object->isValid('socialAccountForYoutubeSubscrib'), true); + } +} diff --git a/tests/Database/Validator/PermissionsTest.php b/tests/Database/Validator/PermissionsTest.php new file mode 100644 index 000000000..f2be50b43 --- /dev/null +++ b/tests/Database/Validator/PermissionsTest.php @@ -0,0 +1,78 @@ + uniqid(), + '$collection' => uniqid(), + '$permissions' => [ + 'read' => ['user:123', 'team:123'], + 'write' => ['*'], + ], + ]); + + $this->assertEquals($object->isValid($document->getPermissions()), true); + + $document = new Document([ + '$id' => uniqid(), + '$collection' => uniqid(), + '$permissions' => [ + 'read' => ['user:123', 'team:123'], + ], + ]); + + $this->assertEquals($object->isValid($document->getPermissions()), true); + + $document = new Document([ + '$id' => uniqid(), + '$collection' => uniqid(), + '$permissions' => [ + 'read' => ['user:123', 'team:123'], + 'write' => ['*'], + 'unknown' => ['*'], + ], + ]); + + $this->assertEquals($object->isValid($document->getPermissions()), false); + + $document = new Document([ + '$id' => uniqid(), + '$collection' => uniqid(), + '$permissions' => 'test', + ]); + + $this->assertEquals($object->isValid($document->getPermissions()), false); + + $document = new Document([ + '$id' => uniqid(), + '$collection' => uniqid(), + '$permissions' => [ + 'read' => 'unknown', + 'write' => ['*'], + ], + ]); + + $this->assertEquals($object->isValid($document->getPermissions()), false); + + } +} \ No newline at end of file diff --git a/tests/Database/Validator/UIDTest.php b/tests/Database/Validator/UIDTest.php new file mode 100644 index 000000000..bc7045113 --- /dev/null +++ b/tests/Database/Validator/UIDTest.php @@ -0,0 +1,30 @@ +object = new UID(); + } + + public function tearDown(): void + { + } + + public function testValues() + { + $this->assertEquals($this->object->isValid('5f058a8925807'), true); + $this->assertEquals($this->object->isValid('5f058a89258075f058a89258075f058t'), true); + $this->assertEquals($this->object->isValid('5f058a89258075f058a89258075f058tx'), false); + } +} From 9af310005601cd824fe9004c8cf4d484071439a8 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 21 Jan 2021 22:43:00 +0200 Subject: [PATCH 002/190] Updated CI --- .travis.yml | 7 ------- composer.json | 3 ++- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6f5e8afad..5200826ef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,13 +12,6 @@ notifications: email: - team@appwrite.io -before_install: -- curl -fsSL https://get.docker.com | sh -- echo '{"experimental":"enabled"}' | sudo tee /etc/docker/daemon.json -- mkdir -p $HOME/.docker -- echo '{"experimental":"enabled"}' | sudo tee $HOME/.docker/config.json -- service docker start - install: - docker-compose up -d - sleep 10 diff --git a/composer.json b/composer.json index cd9bee8df..836589279 100755 --- a/composer.json +++ b/composer.json @@ -16,7 +16,8 @@ }, "require": { "php": ">=7.1", - "ext-pdo": "*" + "ext-pdo": "*", + "utopia-php/framework": "0.*.*" }, "require-dev": { "phpunit/phpunit": "^9.4", From 5d4039f803897e7146600c89ae03f5d784d3fa2f Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 21 Jan 2021 22:46:36 +0200 Subject: [PATCH 003/190] Updated CI --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 5200826ef..01ffa5c22 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,8 @@ notifications: email: - team@appwrite.io +before_script: composer install --ignore-platform-reqs + install: - docker-compose up -d - sleep 10 From 0ad828867fbe9b102587c493243eda53ed276cfc Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 21 Jan 2021 22:50:38 +0200 Subject: [PATCH 004/190] Updated tests --- tests/Database/Validator/PermissionsTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Database/Validator/PermissionsTest.php b/tests/Database/Validator/PermissionsTest.php index f2be50b43..e524d45ad 100644 --- a/tests/Database/Validator/PermissionsTest.php +++ b/tests/Database/Validator/PermissionsTest.php @@ -9,12 +9,12 @@ class PermissionsTest extends TestCase { - public function setUp() + public function setUp(): void { } - public function tearDown() + public function tearDown(): void { } From c0490ba65130281e39d55883a7f92272647c4eec Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 21 Jan 2021 23:08:02 +0200 Subject: [PATCH 005/190] Changed namespace --- src/Database/Adapter.php | 2 +- src/Database/Adapter/MySQL.php | 10 +++--- src/Database/Adapter/Redis.php | 6 ++-- src/Database/Adapter/Relational.php | 12 +++---- src/Database/Database.php | 32 +++---------------- src/Database/Document.php | 2 +- src/Database/Exception/Authorization.php | 2 +- src/Database/Exception/Duplicate.php | 2 +- src/Database/Exception/Structure.php | 2 +- src/Database/Validator/Authorization.php | 4 +-- src/Database/Validator/Collection.php | 6 ++-- src/Database/Validator/DocumentId.php | 6 ++-- src/Database/Validator/Key.php | 2 +- src/Database/Validator/Permissions.php | 2 +- src/Database/Validator/Structure.php | 6 ++-- src/Database/Validator/UID.php | 2 +- tests/Database/DatabaseTest.php | 10 +++--- .../Database/Validator/AuthorizationTest.php | 6 ++-- tests/Database/Validator/KeyTest.php | 4 +-- tests/Database/Validator/PermissionsTest.php | 6 ++-- tests/Database/Validator/UIDTest.php | 4 +-- 21 files changed, 53 insertions(+), 75 deletions(-) diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index 34d0a591c..44523852c 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -1,6 +1,6 @@ Date: Sun, 24 Jan 2021 01:29:39 +0200 Subject: [PATCH 006/190] Fixed autoloading --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 836589279..2cd2ca6e1 100755 --- a/composer.json +++ b/composer.json @@ -12,7 +12,7 @@ } ], "autoload": { - "psr-4": {"Utopia\\Abuse\\": "src/Database"} + "psr-4": {"Utopia\\Database\\": "src/Database"} }, "require": { "php": ">=7.1", From 808536a538c92094b44da678eafa5d998eb406dc Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 24 Jan 2021 01:29:52 +0200 Subject: [PATCH 007/190] New Adapters --- src/Database/Adapter.php | 113 +--- src/Database/Adapter/MariaDB.php | 72 +++ src/Database/Adapter/MySQL.php | 995 +------------------------------ 3 files changed, 102 insertions(+), 1078 deletions(-) create mode 100644 src/Database/Adapter/MariaDB.php diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index 44523852c..2fb8a746f 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -16,23 +16,13 @@ abstract class Adapter */ protected $debug = []; - /** - * @var array - */ - protected $mocks = []; - - /** - * @var Database - */ - protected $database = null; - /** * @param string $key * @param mixed $value * * @return $this */ - public function setDebug(string $key, $value) + public function setDebug(string $key, $value): self { $this->debug[$key] = $value; @@ -42,7 +32,7 @@ public function setDebug(string $key, $value) /** * @return array */ - public function getDebug() + public function getDebug(): array { return $this->debug; } @@ -50,9 +40,11 @@ public function getDebug() /** * return $this */ - public function resetDebug() + public function resetDebug(): self { $this->debug = []; + + return $this; } /** @@ -66,7 +58,7 @@ public function resetDebug() * * @return bool */ - public function setNamespace($namespace) + public function setNamespace(string $namespace): bool { if (empty($namespace)) { throw new Exception('Missing namespace'); @@ -86,7 +78,7 @@ public function setNamespace($namespace) * * @return string */ - public function getNamespace() + public function getNamespace(): string { if (empty($this->namespace)) { throw new Exception('Missing namespace'); @@ -95,6 +87,24 @@ public function getNamespace() return $this->namespace; } + /** + * Create Database. + * + * @param string $name + * + * @return bool + */ + abstract public function create(string $name): bool; + + /** + * Delete Database. + * + * @param string $name + * + * @return bool + */ + abstract public function delete(string $name): bool; + /** * Create Collection * @@ -201,36 +211,9 @@ abstract public function updateDocument(Document $collection, string $id, array abstract public function deleteDocument(Document $collection, string $id); /** - * Delete Unique Key. - * - * @param int $key - * - * @return array - */ - abstract public function deleteUniqueKey($key); - - /** - * Create Namespace. - * - * @param string $namespace - * - * @return bool - */ - abstract public function createNamespace($namespace); - - /** - * Delete Namespace. + * Find. * - * @param string $namespace - * - * @return bool - */ - abstract public function deleteNamespace($namespace); - - /** - * Filter. - * - * Filter data sets using chosen queries + * Find data sets using chosen queries * * @param Document $collection * @param array $options @@ -247,50 +230,10 @@ abstract public function find(Document $collection, array $options); abstract public function count(array $options); /** - * Get Unique Document ID. + * Get Unique ID. */ public function getId() { return \uniqid(); } - - /** - * @param Database $database - * - * @return $this - */ - public function setDatabase(Database $database) - { - $this->database = $database; - - return $this; - } - - /** - * @return Database - */ - public function getDatabase() - { - return $this->database; - } - - /** - * @param string $mocks - * - * @return $this - */ - public function setMocks(array $mocks) - { - $this->mocks = $mocks; - - return $this; - } - - /** - * @return array - */ - public function getMocks() - { - return $this->mocks; - } } diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php new file mode 100644 index 000000000..de23300ae --- /dev/null +++ b/src/Database/Adapter/MariaDB.php @@ -0,0 +1,72 @@ +pdo = $pdo; + } + + /** + * Create Database + * + * @param string $name + * @return bool + */ + public function create(string $name): bool + { + return $this->getPDO() + ->prepare('CREATE DATABASE `'.$this->getNamespace().'_'.$name.'` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;') + ->execute(); + } + + /** + * Delete Database + * + * @param string $name + * @return bool + */ + public function delete(string $name): bool + { + return $this->getPDO() + ->prepare('DROP DATABASE `'.$this->getNamespace().'_'.$name.'`;') + ->execute(); + } + + /** + * @return PDO + * + * @throws Exception + */ + protected function getPDO() + { + return $this->pdo; + } +} \ No newline at end of file diff --git a/src/Database/Adapter/MySQL.php b/src/Database/Adapter/MySQL.php index b7a87b105..589fe3e4b 100644 --- a/src/Database/Adapter/MySQL.php +++ b/src/Database/Adapter/MySQL.php @@ -2,998 +2,7 @@ namespace Utopia\Database\Adapter; -use Utopia\Registry\Registry; -use Utopia\Database\Adapter; -use Utopia\Database\Document; -use Utopia\Database\Exception\Duplicate; -use Utopia\Database\Validator\Authorization; -use Exception; -use PDO; -use Redis as Client; - -class MySQL extends Adapter +class MySQL extends MariaDB { - const DATA_TYPE_STRING = 'string'; - const DATA_TYPE_INTEGER = 'integer'; - const DATA_TYPE_FLOAT = 'float'; - const DATA_TYPE_BOOLEAN = 'boolean'; - const DATA_TYPE_OBJECT = 'object'; - const DATA_TYPE_DICTIONARY = 'dictionary'; - const DATA_TYPE_ARRAY = 'array'; - const DATA_TYPE_NULL = 'null'; - - /** - * @var Registry - */ - protected $register; - - /** - * Constructor. - * - * Set connection and settings - * - * @param Registry $register - */ - public function __construct(Registry $register) - { - $this->register = $register; - } - - /** - * Create Collection - * - * @param Document $collection - * @param string $id - * - * @return bool - */ - public function createCollection(Document $collection, string $id): bool - { - return true; - } - - /** - * Delete Collection - * - * @param Document $collection - * - * @return bool - */ - public function deleteCollection(Document $collection): bool - { - return true; - } - - /** - * Create Attribute - * - * @param Document $collection - * @param string $id - * @param string $type - * @param bool $array - * - * @return bool - */ - public function createAttribute(Document $collection, string $id, string $type, bool $array = false): bool - { - return true; - } - - /** - * Delete Attribute - * - * @param Document $collection - * @param string $id - * @param bool $array - * - * @return bool - */ - public function deleteAttribute(Document $collection, string $id, bool $array = false): bool - { - return true; - } - - - /** - * Create Index - * - * @param Document $collection - * @param string $id - * @param string $type - * @param array $attributes - * - * @return bool - */ - public function createIndex(Document $collection, string $id, string $type, array $attributes): bool - { - return true; - } - - /** - * Delete Index - * - * @param Document $collection - * @param string $id - * - * @return bool - */ - public function deleteIndex(Document $collection, string $id): bool - { - return true; - } - - /** - * Get Document. - * - * @param Document $collection - * @param string $id - * - * @return array - * - * @throws Exception - */ - public function getDocument(Document $collection, $id) - { - // Get fields abstraction - $st = $this->getPDO()->prepare('SELECT * FROM `'.$this->getNamespace().'.database.documents` a - WHERE a.uid = :uid AND a.status = 0 - ORDER BY a.updatedAt DESC LIMIT 10; - '); - - $st->bindValue(':uid', $id, PDO::PARAM_STR); - - $st->execute(); - - $document = $st->fetch(); - - if (empty($document)) { // Not Found - return []; - } - - // Get fields abstraction - $st = $this->getPDO()->prepare('SELECT * FROM `'.$this->getNamespace().'.database.properties` a - WHERE a.documentUid = :documentUid AND a.documentRevision = :documentRevision - ORDER BY `order` - '); - - $st->bindParam(':documentUid', $document['uid'], PDO::PARAM_STR); - $st->bindParam(':documentRevision', $document['revision'], PDO::PARAM_STR); - - $st->execute(); - - $properties = $st->fetchAll(); - - $output = [ - '$id' => null, - '$collection' => null, - '$permissions' => (!empty($document['permissions'])) ? \json_decode($document['permissions'], true) : [], - ]; - - foreach ($properties as &$property) { - \settype($property['value'], $property['primitive']); - - if ($property['array']) { - $output[$property['key']][] = $property['value']; - } else { - $output[$property['key']] = $property['value']; - } - } - - // Get fields abstraction - $st = $this->getPDO()->prepare('SELECT * FROM `'.$this->getNamespace().'.database.relationships` a - WHERE a.start = :start AND revision = :revision - ORDER BY `order` - '); - - $st->bindParam(':start', $document['uid'], PDO::PARAM_STR); - $st->bindParam(':revision', $document['revision'], PDO::PARAM_STR); - - $st->execute(); - - $output['temp-relations'] = $st->fetchAll(); - - return $output; - } - - /** - * Create Document. - * - * @param Document $collection - * @param array $data - * - * @throws \Exception - * - * @return array - */ - public function createDocument(Document $collection, array $data, array $unique = []) - { - $order = 0; - $data = \array_merge(['$id' => null, '$permissions' => []], $data); // Merge data with default params - $signature = \md5(\json_encode($data)); - $revision = \uniqid('', true); - $data['$id'] = (empty($data['$id'])) ? null : $data['$id']; - - /* - * When updating node, check if there are any changes to update - * by comparing data md5 signatures - */ - if (null !== $data['$id']) { - $st = $this->getPDO()->prepare('SELECT signature FROM `'.$this->getNamespace().'.database.documents` a - WHERE a.uid = :uid AND a.status = 0 - ORDER BY a.updatedAt DESC LIMIT 1; - '); - - $st->bindValue(':uid', $data['$id'], PDO::PARAM_STR); - - $st->execute(); - - $result = $st->fetch(); - - if ($result && isset($result['signature'])) { - $oldSignature = $result['signature']; - - if ($signature === $oldSignature) { - return $data; - } - } - } - - /** - * Check Unique Keys - */ - foreach ($unique as $key => $value) { - $st = $this->getPDO()->prepare('INSERT INTO `'.$this->getNamespace().'.database.unique` - SET `key` = :key; - '); - - $st->bindValue(':key', \md5($data['$collection'].':'.$key.'='.$value), PDO::PARAM_STR); - - if (!$st->execute()) { - throw new Duplicate('Duplicated Property: '.$key.'='.$value); - } - } - - // Add or update fields abstraction level - $st1 = $this->getPDO()->prepare('INSERT INTO `'.$this->getNamespace().'.database.documents` - SET uid = :uid, createdAt = :createdAt, updatedAt = :updatedAt, signature = :signature, revision = :revision, permissions = :permissions, status = 0 - ON DUPLICATE KEY UPDATE uid = :uid, updatedAt = :updatedAt, signature = :signature, revision = :revision, permissions = :permissions; - '); - - // Adding fields properties - if (null === $data['$id'] || !isset($data['$id'])) { // Get new fields UID - $data['$id'] = $this->getId(); - } - - $st1->bindValue(':uid', $data['$id'], PDO::PARAM_STR); - $st1->bindValue(':revision', $revision, PDO::PARAM_STR); - $st1->bindValue(':signature', $signature, PDO::PARAM_STR); - $st1->bindValue(':createdAt', \date('Y-m-d H:i:s', \time()), PDO::PARAM_STR); - $st1->bindValue(':updatedAt', \date('Y-m-d H:i:s', \time()), PDO::PARAM_STR); - $st1->bindValue(':permissions', \json_encode($data['$permissions']), PDO::PARAM_STR); - - $st1->execute(); - - // Delete old properties - $rms1 = $this->getPDO()->prepare('DELETE FROM `'.$this->getNamespace().'.database.properties` WHERE documentUid = :documentUid AND documentRevision != :documentRevision'); - $rms1->bindValue(':documentUid', $data['$id'], PDO::PARAM_STR); - $rms1->bindValue(':documentRevision', $revision, PDO::PARAM_STR); - $rms1->execute(); - - // Delete old relationships - $rms2 = $this->getPDO()->prepare('DELETE FROM `'.$this->getNamespace().'.database.relationships` WHERE start = :start AND revision != :revision'); - $rms2->bindValue(':start', $data['$id'], PDO::PARAM_STR); - $rms2->bindValue(':revision', $revision, PDO::PARAM_STR); - $rms2->execute(); - - // Create new properties - $st2 = $this->getPDO()->prepare('INSERT INTO `'.$this->getNamespace().'.database.properties` - (`documentUid`, `documentRevision`, `key`, `value`, `primitive`, `array`, `order`) - VALUES (:documentUid, :documentRevision, :key, :value, :primitive, :array, :order)'); - - $props = []; - - foreach ($data as $key => $value) { // Prepare properties data - - if (\in_array($key, ['$permissions'])) { - continue; - } - - $type = $this->getDataType($value); - - // Handle array of relations - if (self::DATA_TYPE_ARRAY === $type) { - if (!is_array($value)) { // Property should be of type array, if not = skip - continue; - } - - foreach ($value as $i => $child) { - if (self::DATA_TYPE_DICTIONARY !== $this->getDataType($child)) { // not dictionary - - $props[] = [ - 'type' => $this->getDataType($child), - 'key' => $key, - 'value' => $child, - 'array' => true, - 'order' => $order++, - ]; - - continue; - } - - $data[$key][$i] = $this->createDocument(new Document([]), $child); - - $this->createRelationship($revision, $data['$id'], $data[$key][$i]['$id'], $key, true, $i); - } - - continue; - } - - // Handle relation - if (self::DATA_TYPE_DICTIONARY === $type) { - $value = $this->createDocument(new Document([]), $value); - $this->createRelationship($revision, $data['$id'], $value['$id'], $key); //xxx - continue; - } - - // Handle empty values - if (self::DATA_TYPE_NULL === $type) { - continue; - } - - $props[] = [ - 'type' => $type, - 'key' => $key, - 'value' => $value, - 'array' => false, - 'order' => $order++, - ]; - } - - foreach ($props as $prop) { - if (\is_array($prop['value'])) { - throw new Exception('Value can\'t be an array: '.\json_encode($prop['value'])); - } - $st2->bindValue(':documentUid', $data['$id'], PDO::PARAM_STR); - $st2->bindValue(':documentRevision', $revision, PDO::PARAM_STR); - - $st2->bindValue(':key', $prop['key'], PDO::PARAM_STR); - $st2->bindValue(':value', $prop['value'], PDO::PARAM_STR); - $st2->bindValue(':primitive', $prop['type'], PDO::PARAM_STR); - $st2->bindValue(':array', $prop['array'], PDO::PARAM_BOOL); - $st2->bindValue(':order', $prop['order'], PDO::PARAM_STR); - - $st2->execute(); - } - - //TODO remove this dependency (check if related to nested documents) - $this->getRedis()->expire($this->getNamespace().':document-'.$data['$id'], 0); - $this->getRedis()->expire($this->getNamespace().':document-'.$data['$id'], 0); - - return $data; - } - - /** - * Update Document. - * - * @param Document $collection - * @param string $id - * @param array $data - * - * @return array - * - * @throws Exception - */ - public function updateDocument(Document $collection, string $id, array $data) - { - return $this->createDocument($collection, $data); - } - - /** - * Delete Document. - * - * @param Document $collection - * @param string $id - * - * @return array - * - * @throws Exception - */ - public function deleteDocument(Document $collection, string $id) - { - $st1 = $this->getPDO()->prepare('DELETE FROM `'.$this->getNamespace().'.database.documents` - WHERE uid = :id - '); - - $st1->bindValue(':id', $id, PDO::PARAM_STR); - - $st1->execute(); - - $st2 = $this->getPDO()->prepare('DELETE FROM `'.$this->getNamespace().'.database.properties` - WHERE documentUid = :id - '); - - $st2->bindValue(':id', $id, PDO::PARAM_STR); - - $st2->execute(); - - $st3 = $this->getPDO()->prepare('DELETE FROM `'.$this->getNamespace().'.database.relationships` - WHERE start = :id OR end = :id - '); - - $st3->bindValue(':id', $id, PDO::PARAM_STR); - - $st3->execute(); - - return []; - } - - /** - * Delete Unique Key. - * - * @param int $key - * - * @return array - * - * @throws Exception - */ - public function deleteUniqueKey($key) - { - $st1 = $this->getPDO()->prepare('DELETE FROM `'.$this->getNamespace().'.database.unique` WHERE `key` = :key'); - - $st1->bindValue(':key', $key, PDO::PARAM_STR); - - $st1->execute(); - - return []; - } - - /** - * Create Relation. - * - * Adds a new relationship between different nodes - * - * @param string $revision - * @param int $start - * @param int $end - * @param string $key - * @param bool $isArray - * @param int $order - * - * @return array - * - * @throws Exception - */ - protected function createRelationship($revision, $start, $end, $key, $isArray = false, $order = 0) - { - $st2 = $this->getPDO()->prepare('INSERT INTO `'.$this->getNamespace().'.database.relationships` - (`revision`, `start`, `end`, `key`, `array`, `order`) - VALUES (:revision, :start, :end, :key, :array, :order)'); - - $st2->bindValue(':revision', $revision, PDO::PARAM_STR); - $st2->bindValue(':start', $start, PDO::PARAM_STR); - $st2->bindValue(':end', $end, PDO::PARAM_STR); - $st2->bindValue(':key', $key, PDO::PARAM_STR); - $st2->bindValue(':array', $isArray, PDO::PARAM_INT); - $st2->bindValue(':order', $order, PDO::PARAM_INT); - - $st2->execute(); - - return []; - } - - /** - * Create Namespace. - * - * @param $namespace - * - * @throws Exception - * - * @return bool - */ - public function createNamespace($namespace) - { - if (empty($namespace)) { - throw new Exception('Empty namespace'); - } - - $documents = 'app_'.$namespace.'.database.documents'; - $properties = 'app_'.$namespace.'.database.properties'; - $relationships = 'app_'.$namespace.'.database.relationships'; - $unique = 'app_'.$namespace.'.database.unique'; - $audit = 'app_'.$namespace.'.audit.audit'; - $abuse = 'app_'.$namespace.'.abuse.abuse'; - - try { - $this->getPDO()->prepare('CREATE TABLE `'.$documents.'` LIKE `template.database.documents`;')->execute(); - $this->getPDO()->prepare('CREATE TABLE `'.$properties.'` LIKE `template.database.properties`;')->execute(); - $this->getPDO()->prepare('CREATE TABLE `'.$relationships.'` LIKE `template.database.relationships`;')->execute(); - $this->getPDO()->prepare('CREATE TABLE `'.$unique.'` LIKE `template.database.unique`;')->execute(); - $this->getPDO()->prepare('CREATE TABLE `'.$audit.'` LIKE `template.audit.audit`;')->execute(); - $this->getPDO()->prepare('CREATE TABLE `'.$abuse.'` LIKE `template.abuse.abuse`;')->execute(); - } catch (Exception $e) { - throw $e; - } - - return true; - } - - /** - * Delete Namespace. - * - * @param $namespace - * - * @throws Exception - * - * @return bool - */ - public function deleteNamespace($namespace) - { - if (empty($namespace)) { - throw new Exception('Empty namespace'); - } - - $unique = 'app_'.$namespace.'.database.unique'; - $documents = 'app_'.$namespace.'.database.documents'; - $properties = 'app_'.$namespace.'.database.properties'; - $relationships = 'app_'.$namespace.'.database.relationships'; - $audit = 'app_'.$namespace.'.audit.audit'; - $abuse = 'app_'.$namespace.'.abuse.abuse'; - - try { - $this->getPDO()->prepare('DROP TABLE `'.$unique.'`;')->execute(); - $this->getPDO()->prepare('DROP TABLE `'.$documents.'`;')->execute(); - $this->getPDO()->prepare('DROP TABLE `'.$properties.'`;')->execute(); - $this->getPDO()->prepare('DROP TABLE `'.$relationships.'`;')->execute(); - $this->getPDO()->prepare('DROP TABLE `'.$audit.'`;')->execute(); - $this->getPDO()->prepare('DROP TABLE `'.$abuse.'`;')->execute(); - } catch (Exception $e) { - throw $e; - } - - return true; - } - - /** - * Get Collection. - * - * @param array $options - * - * @throws Exception - * - * @return array - */ - public function find(Document $collection, array $options) - { - $start = \microtime(true); - $orderCastMap = [ - 'int' => 'UNSIGNED', - 'string' => 'CHAR', - 'date' => 'DATE', - 'time' => 'TIME', - 'datetime' => 'DATETIME', - ]; - $orderTypeMap = ['DESC', 'ASC']; - - $options['orderField'] = (empty($options['orderField'])) ? '' : $options['orderField']; // Set default order field - $options['orderCast'] = (empty($options['orderCast'])) ? 'string' : $options['orderCast']; // Set default order field - - if (!\array_key_exists($options['orderCast'], $orderCastMap)) { - throw new Exception('Invalid order cast'); - } - - if (!\in_array($options['orderType'], $orderTypeMap)) { - throw new Exception('Invalid order type'); - } - - $where = []; - $join = []; - $sorts = []; - $search = ''; - - $options['filters'][] = '$collection='.$collection->getId(); - - // Filters - foreach ($options['filters'] as $i => $filter) { - $filter = $this->parseFilter($filter); - $key = $filter['key']; - $value = $filter['value']; - $operator = $filter['operator']; - - $path = \explode('.', $key); - $original = $path; - - if (1 < \count($path)) { - $key = \array_pop($path); - } else { - $path = []; - } - - //$path = implode('.', $path); - - $key = $this->getPDO()->quote($key, PDO::PARAM_STR); - $value = $this->getPDO()->quote($value, PDO::PARAM_STR); - //$path = $this->getPDO()->quote($path, PDO::PARAM_STR); - $options['offset'] = (int) $options['offset']; - $options['limit'] = (int) $options['limit']; - - if (empty($path)) { - //if($path == "''") { // Handle direct attributes queries - $where[] = 'JOIN `'.$this->getNamespace().".database.properties` b{$i} ON a.uid IS NOT NULL AND b{$i}.documentUid = a.uid AND (b{$i}.key = {$key} AND b{$i}.value {$operator} {$value})"; - } else { // Handle direct child attributes queries - $len = \count($original); - $prev = 'c'.$i; - - foreach ($original as $y => $part) { - $part = $this->getPDO()->quote($part, PDO::PARAM_STR); - - if (0 === $y) { // First key - $join[$i] = 'JOIN `'.$this->getNamespace().".database.relationships` c{$i} ON a.uid IS NOT NULL AND c{$i}.start = a.uid AND c{$i}.key = {$part}"; - } elseif ($y == $len - 1) { // Last key - $join[$i] .= 'JOIN `'.$this->getNamespace().".database.properties` e{$i} ON e{$i}.documentUid = {$prev}.end AND e{$i}.key = {$part} AND e{$i}.value {$operator} {$value}"; - } else { - $join[$i] .= 'JOIN `'.$this->getNamespace().".database.relationships` d{$i}{$y} ON d{$i}{$y}.start = {$prev}.end AND d{$i}{$y}.key = {$part}"; - $prev = 'd'.$i.$y; - } - } - - //$join[] = "JOIN `" . $this->getNamespace() . ".database.relationships` c{$i} ON a.uid IS NOT NULL AND c{$i}.start = a.uid AND c{$i}.key = {$path} - // JOIN `" . $this->getNamespace() . ".database.properties` d{$i} ON d{$i}.documentUid = c{$i}.end AND d{$i}.key = {$key} AND d{$i}.value {$operator} {$value}"; - } - } - - // Sorting - if(!empty($options['orderField'])) { - $orderPath = \explode('.', $options['orderField']); - $len = \count($orderPath); - $orderKey = 'order_b'; - $part = $this->getPDO()->quote(\implode('', $orderPath), PDO::PARAM_STR); - $orderSelect = "CASE WHEN {$orderKey}.key = {$part} THEN CAST({$orderKey}.value AS {$orderCastMap[$options['orderCast']]}) END AS sort_ff"; - - if (1 === $len) { - //if($path == "''") { // Handle direct attributes queries - $sorts[] = 'LEFT JOIN `'.$this->getNamespace().".database.properties` order_b ON a.uid IS NOT NULL AND order_b.documentUid = a.uid AND (order_b.key = {$part})"; - } else { // Handle direct child attributes queries - $prev = 'c'; - $orderKey = 'order_e'; - - foreach ($orderPath as $y => $part) { - $part = $this->getPDO()->quote($part, PDO::PARAM_STR); - $x = $y - 1; - - if (0 === $y) { // First key - $sorts[] = 'JOIN `'.$this->getNamespace().".database.relationships` order_c{$y} ON a.uid IS NOT NULL AND order_c{$y}.start = a.uid AND order_c{$y}.key = {$part}"; - } elseif ($y == $len - 1) { // Last key - $sorts[] .= 'JOIN `'.$this->getNamespace().".database.properties` order_e ON order_e.documentUid = order_{$prev}{$x}.end AND order_e.key = {$part}"; - } else { - $sorts[] .= 'JOIN `'.$this->getNamespace().".database.relationships` order_d{$y} ON order_d{$y}.start = order_{$prev}{$x}.end AND order_d{$y}.key = {$part}"; - $prev = 'd'; - } - } - } - } - else { - $orderSelect = 'a.uid AS sort_ff'; - } - - /* - * Workaround for a MySQL bug as reported here: - * https://bugs.mysql.com/bug.php?id=78485 - */ - $options['search'] = ($options['search'] === '*') ? '' : $options['search']; - - // Search - if (!empty($options['search'])) { // Handle free search - $where[] = 'LEFT JOIN `'.$this->getNamespace().".database.properties` b_search ON a.uid IS NOT NULL AND b_search.documentUid = a.uid AND b_search.primitive = 'string' - LEFT JOIN - `".$this->getNamespace().'.database.relationships` c_search ON c_search.start = b_search.documentUid - LEFT JOIN - `'.$this->getNamespace().".database.properties` d_search ON d_search.documentUid = c_search.end AND d_search.primitive = 'string' - \n"; - - $search = "AND (MATCH (b_search.value) AGAINST ({$this->getPDO()->quote($options['search'], PDO::PARAM_STR)} IN BOOLEAN MODE) - OR MATCH (d_search.value) AGAINST ({$this->getPDO()->quote($options['search'], PDO::PARAM_STR)} IN BOOLEAN MODE) - )"; - } - - $select = 'DISTINCT a.uid'; - $where = \implode("\n", $where); - $join = \implode("\n", $join); - $sorts = \implode("\n", $sorts); - $range = "LIMIT {$options['offset']}, {$options['limit']}"; - $roles = []; - - foreach (Authorization::getRoles() as $role) { - $roles[] = 'JSON_CONTAINS(REPLACE(a.permissions, \'{self}\', a.uid), \'"'.$role.'"\', \'$.read\')'; - } - - if (false === Authorization::$status) { // FIXME temporary solution (hopefully) - $roles = ['1=1']; - } - - $query = "SELECT %s, {$orderSelect} - FROM `".$this->getNamespace().".database.documents` a {$where}{$join}{$sorts} - WHERE status = 0 - {$search} - AND (".\implode('||', $roles).") - ORDER BY sort_ff {$options['orderType']} %s"; - - $st = $this->getPDO()->prepare(\sprintf($query, $select, $range)); - - $st->execute(); - - $results = ['data' => []]; - - // Get entire fields data for each id - foreach ($st->fetchAll() as $node) { - $results['data'][] = $node['uid']; - } - - $count = $this->getPDO()->prepare(\sprintf($query, 'count(DISTINCT a.uid) as sum', '')); - - $count->execute(); - - $count = $count->fetch(); - - $this->resetDebug(); - - $this - ->setDebug('query', \preg_replace('/\s+/', ' ', \sprintf($query, $select, $range))) - ->setDebug('time', \microtime(true) - $start) - ->setDebug('filters', \count($options['filters'])) - ->setDebug('joins', \substr_count($query, 'JOIN')) - ->setDebug('count', \count($results['data'])) - ->setDebug('sum', (int) $count['sum']) - ; - - return $results['data']; - } - - /** - * Get Collection. - * - * @param array $options - * - * @throws Exception - * - * @return int - */ - public function count(array $options) - { - $start = \microtime(true); - $where = []; - $join = []; - - $options = array_merge([ - 'attribute' => '', - 'filters' => [], - ], $options); - - // Filters - foreach ($options['filters'] as $i => $filter) { - $filter = $this->parseFilter($filter); - $key = $filter['key']; - $value = $filter['value']; - $operator = $filter['operator']; - $path = \explode('.', $key); - $original = $path; - - if (1 < \count($path)) { - $key = \array_pop($path); - } else { - $path = []; - } - - $key = $this->getPDO()->quote($key, PDO::PARAM_STR); - $value = $this->getPDO()->quote($value, PDO::PARAM_STR); - - if (empty($path)) { - //if($path == "''") { // Handle direct attributes queries - $where[] = 'JOIN `'.$this->getNamespace().".database.properties` b{$i} ON a.uid IS NOT NULL AND b{$i}.documentUid = a.uid AND (b{$i}.key = {$key} AND b{$i}.value {$operator} {$value})"; - } else { // Handle direct child attributes queries - $len = \count($original); - $prev = 'c'.$i; - - foreach ($original as $y => $part) { - $part = $this->getPDO()->quote($part, PDO::PARAM_STR); - - if (0 === $y) { // First key - $join[$i] = 'JOIN `'.$this->getNamespace().".database.relationships` c{$i} ON a.uid IS NOT NULL AND c{$i}.start = a.uid AND c{$i}.key = {$part}"; - } elseif ($y == $len - 1) { // Last key - $join[$i] .= 'JOIN `'.$this->getNamespace().".database.properties` e{$i} ON e{$i}.documentUid = {$prev}.end AND e{$i}.key = {$part} AND e{$i}.value {$operator} {$value}"; - } else { - $join[$i] .= 'JOIN `'.$this->getNamespace().".database.relationships` d{$i}{$y} ON d{$i}{$y}.start = {$prev}.end AND d{$i}{$y}.key = {$part}"; - $prev = 'd'.$i.$y; - } - } - } - } - - $where = \implode("\n", $where); - $join = \implode("\n", $join); - $attribute = $this->getPDO()->quote($options['attribute'], PDO::PARAM_STR); - $func = 'JOIN `'.$this->getNamespace().".database.properties` b_func ON a.uid IS NOT NULL - AND a.uid = b_func.documentUid - AND (b_func.key = {$attribute})"; - $roles = []; - - foreach (Authorization::getRoles() as $role) { - $roles[] = 'JSON_CONTAINS(REPLACE(a.permissions, \'{self}\', a.uid), \'"'.$role.'"\', \'$.read\')'; - } - - if (false === Authorization::$status) { // FIXME temporary solution (hopefully) - $roles = ['1=1']; - } - - $query = "SELECT SUM(b_func.value) as result - FROM `".$this->getNamespace().".database.documents` a {$where}{$join}{$func} - WHERE status = 0 - AND (".\implode('||', $roles).')'; - - $st = $this->getPDO()->prepare(\sprintf($query)); - - $st->execute(); - - $result = $st->fetch(); - - $this->resetDebug(); - - $this - ->setDebug('query', \preg_replace('/\s+/', ' ', \sprintf($query))) - ->setDebug('time', \microtime(true) - $start) - ->setDebug('filters', \count($options['filters'])) - ->setDebug('joins', \substr_count($query, 'JOIN')) - ; - - return (isset($result['result'])) ? (int)$result['result'] : 0; - } - - /** - * Get Unique Document ID. - * - * @return string - */ - public function getId(): string - { - return \uniqid(); - } - - /** - * Parse Filter. - * - * @param string $filter - * - * @return array - * - * @throws Exception - */ - protected function parseFilter($filter) - { - $operatorsMap = ['!=', '>=', '<=', '=', '>', '<']; // Do not edit order of this array - - //FIXME bug with >= <= operators - - $operator = null; - - foreach ($operatorsMap as $node) { - if (\strpos($filter, $node) !== false) { - $operator = $node; - break; - } - } - - if (empty($operator)) { - throw new Exception('Invalid operator'); - } - - $filter = \explode($operator, $filter); - - if (\count($filter) != 2) { - throw new Exception('Invalid filter expression'); - } - - return [ - 'key' => $filter[0], - 'value' => $filter[1], - 'operator' => $operator, - ]; - } - - /** - * Get Data Type. - * - * Check value data type. return value can be on of the following: - * string, integer, float, boolean, object, list or null - * - * @param $value - * - * @return string - * - * @throws \Exception - */ - protected function getDataType($value) - { - switch (\gettype($value)) { - - case 'string': - return self::DATA_TYPE_STRING; - break; - - case 'integer': - return self::DATA_TYPE_INTEGER; - break; - - case 'double': - return self::DATA_TYPE_FLOAT; - break; - - case 'boolean': - return self::DATA_TYPE_BOOLEAN; - break; - - case 'array': - if ((bool) \count(\array_filter(\array_keys($value), 'is_string'))) { - return self::DATA_TYPE_DICTIONARY; - } - - return self::DATA_TYPE_ARRAY; - break; - - case 'NULL': - return self::DATA_TYPE_NULL; - break; - } - - throw new Exception('Unknown data type: '.$value.' ('.\gettype($value).')'); - } - - /** - * @param string $key - * @param mixed $value - * - * @return $this - */ - public function setDebug(string $key, $value): self - { - $this->debug[$key] = $value; - - return $this; - } - - /** - * @return array - */ - public function getDebug(): array - { - return $this->debug; - } - - /** - * return $this;. - * - * @return void - */ - public function resetDebug(): void - { - $this->debug = []; - } - - /** - * @return PDO - * - * @throws Exception - */ - protected function getPDO(): PDO - { - return $this->register->get('db'); - } - /** - * @throws Exception - * - * @return Client - */ - protected function getRedis(): Client - { - return $this->register->get('cache'); - } -} +} \ No newline at end of file From 3a2fd114d53afb7b2d4723ae9cd34c5b12c34954 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 24 Jan 2021 01:30:03 +0200 Subject: [PATCH 008/190] New Adapters --- src/Database/Adapter/Redis.php | 362 --------- src/Database/Adapter/Relational.php | 1148 --------------------------- 2 files changed, 1510 deletions(-) delete mode 100644 src/Database/Adapter/Redis.php delete mode 100644 src/Database/Adapter/Relational.php diff --git a/src/Database/Adapter/Redis.php b/src/Database/Adapter/Redis.php deleted file mode 100644 index 08694389e..000000000 --- a/src/Database/Adapter/Redis.php +++ /dev/null @@ -1,362 +0,0 @@ -register = $register; - $this->adapter = $adapter; - } - - /** - * Create Collection - * - * @param Document $collection - * @param string $id - * - * @return bool - */ - public function createCollection(Document $collection, string $id): bool - { - return $this->adapter->createCollection($collection, $id); - } - - /** - * Delete Collection - * - * @param Document $collection - * - * @return bool - */ - public function deleteCollection(Document $collection): bool - { - return $this->adapter->deleteCollection($collection); - } - - /** - * Create Attribute - * - * @param Document $collection - * @param string $id - * @param string $type - * @param bool $array - * - * @return bool - */ - public function createAttribute(Document $collection, string $id, string $type, bool $array = false): bool - { - return $this->adapter->createAttribute($collection, $id, $type, $array); - } - - /** - * Delete Attribute - * - * @param Document $collection - * @param string $id - * @param bool $array - * - * @return bool - */ - public function deleteAttribute(Document $collection, string $id, bool $array = false): bool - { - return $this->adapter->deleteAttribute($collection, $id, $array); - } - - /** - * Create Index - * - * @param Document $collection - * @param string $id - * @param string $type - * @param array $attributes - * - * @return bool - */ - public function createIndex(Document $collection, string $id, string $type, array $attributes): bool - { - return $this->adapter->createIndex($collection, $id, $type, $attributes); - } - - /** - * Delete Index - * - * @param Document $collection - * @param string $id - * - * @return bool - */ - public function deleteIndex(Document $collection, string $id): bool - { - return $this->adapter->deleteIndex($collection, $id); - } - - /** - * Get Document. - * - * @param Document $collection - * @param string $id - * - * @return array - * - * @throws Exception - */ - public function getDocument(Document $collection, $id) - { - $output = \json_decode($this->getRedis()->get($this->getNamespace().':document-'.$id), true); - - if (!$output) { - $output = $this->adapter->getDocument($collection, $id); - $this->getRedis()->set($this->getNamespace().':document-'.$id, \json_encode($output, JSON_UNESCAPED_UNICODE)); - } - - $output = $this->parseRelations($output); - - return $output; - } - - /** - * @param $output - * - * @return mixed - * - * @throws Exception - */ - protected function parseRelations($output) - { - $keys = []; - - if (empty($output) || !isset($output['temp-relations'])) { - return $output; - } - - foreach ($output['temp-relations'] as $relationship) { - $keys[] = $this->getNamespace().':document-'.$relationship['end']; - } - - $nodes = (!empty($keys)) ? $this->getRedis()->mget($keys) : []; - - foreach ($output['temp-relations'] as $i => $relationship) { - $node = $relationship['end']; - - $node = (!empty($nodes[$i])) ? $this->parseRelations(\json_decode($nodes[$i], true)) : $this->getDocument(new Document([]), $node); - - if (empty($node)) { - continue; - } - - if ($relationship['array']) { - $output[$relationship['key']][] = $node; - } else { - $output[$relationship['key']] = $node; - } - } - - unset($output['temp-relations']); - - return $output; - } - - /** - * Create Document. - * - * @param Document $collection - * @param array $data - * @param array $unique - * - * @return array - * - * @throws Exception - */ - public function createDocument(Document $collection, array $data, array $unique = []) - { - $data = $this->adapter->createDocument($collection, $data, $unique); - - $this->getRedis()->expire($this->getNamespace().':document-'.$data['$id'], 0); - $this->getRedis()->expire($this->getNamespace().':document-'.$data['$id'], 0); - - return $data; - } - - /** - * Update Document. - * - * @param Document $collection - * @param string $id - * @param array $data - * - * @return array - * - * @throws Exception - */ - public function updateDocument(Document $collection, string $id, array $data) - { - $data = $this->adapter->updateDocument($collection, $id, $data); - - $this->getRedis()->expire($this->getNamespace().':document-'.$data['$id'], 0); - $this->getRedis()->expire($this->getNamespace().':document-'.$data['$id'], 0); - - return $data; - } - - /** - * Delete Document. - * - * @param Document $collection - * @param string $id - * - * @return array - * - * @throws Exception - */ - public function deleteDocument(Document $collection, string $id) - { - $data = $this->adapter->deleteDocument($collection, $id); - - $this->getRedis()->expire($this->getNamespace().':document-'.$id, 0); - $this->getRedis()->expire($this->getNamespace().':document-'.$id, 0); - - return $data; - } - - /** - * Delete Unique Key. - * - * @param $key - * - * @return array - * - * @throws Exception - */ - public function deleteUniqueKey($key) - { - $data = $this->adapter->deleteUniqueKey($key); - - return $data; - } - - /** - * Create Namespace. - * - * @param string $namespace - * - * @return bool - */ - public function createNamespace($namespace) - { - return $this->adapter->createNamespace($namespace); - } - - /** - * Delete Namespace. - * - * @param string $namespace - * - * @return bool - */ - public function deleteNamespace($namespace) - { - return $this->adapter->deleteNamespace($namespace); - } - - /** - * @param Document $collection - * @param array $options - * - * @return array - * - * @throws Exception - */ - public function find(Document $collection, array $options) - { - $data = $this->adapter->find($collection, $options); - $keys = []; - - foreach ($data as $node) { - $keys[] = $this->getNamespace().':document-'.$node; - } - - $nodes = (!empty($keys)) ? $this->getRedis()->mget($keys) : []; - - foreach ($data as $i => &$node) { - $temp = (!empty($nodes[$i])) ? $this->parseRelations(\json_decode($nodes[$i], true)) : $this->getDocument($collection, $node); - - if (!empty($temp)) { - $node = $temp; - } - } - - return $data; - } - - /** - * @param array $options - * - * @return int - * - * @throws Exception - */ - public function count(array $options) - { - return $this->adapter->count($options); - } - - /** - * @return array - */ - public function getDebug() - { - return $this->adapter->getDebug(); - } - - /** - * @throws Exception - * - * @return Client - */ - protected function getRedis(): Client - { - return $this->register->get('cache'); - } - - /** - * Set Namespace. - * - * Set namespace to divide different scope of data sets - * - * @param $namespace - * - * @return bool - * - * @throws Exception - */ - public function setNamespace($namespace) - { - $this->adapter->setNamespace($namespace); - - return parent::setNamespace($namespace); - } -} diff --git a/src/Database/Adapter/Relational.php b/src/Database/Adapter/Relational.php deleted file mode 100644 index 84a4a5f89..000000000 --- a/src/Database/Adapter/Relational.php +++ /dev/null @@ -1,1148 +0,0 @@ - true, - '$collection' => true, - '$permissions' => true - ]; - - /** - * @var bool - */ - protected $transaction = false; - - /** - * Constructor. - * - * Set connection and settings - * - * @param Registry $register - */ - public function __construct(PDO $pdo) - { - $this->pdo = $pdo; - } - - /** - * Create Collection - * - * @param Document $collection - * @param string $id - * - * @return bool - */ - public function createCollection(Document $collection, string $id): bool - { - if($collection->isEmpty()) { - throw new Exception('Missing Collection'); - } - - $rules = $collection->getAttribute('rules', []); - $indexes = $collection->getAttribute('indexes', []); - $PDOColumns = []; - $PDOIndexes = []; - - foreach ($rules as $rule) { /** @var Document $attribute */ - $key = $rule->getAttribute('key'); - $type = $rule->getAttribute('type'); - $array = $rule->getAttribute('array'); - - if($array) { - $this->createAttribute($collection, $key, $type, $array); - continue; - } - - $PDOColumns[] = $this->getAttributeType($key, $type); - } - - foreach ($indexes as $index) { /** @var Document $index */ - $type = $index->getAttribute('type', ''); - $attributes = $index->getAttribute('attributes', []); - - $PDOIndexes[] = $this->getIndexType($index->getId(), $type, $attributes); - } - - $PDOColumns = (!empty($PDOColumns)) ? implode(",\n", $PDOColumns) . ",\n" : ''; - $PDOIndexes = (!empty($PDOIndexes)) ? ",\n" . implode(",\n", $PDOIndexes) : ''; - - $query = $this->getPDO()->prepare('CREATE TABLE `app_'.$this->getNamespace().'.collection.'.$id.'` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `uid` varchar(45) DEFAULT NULL, - `createdAt` datetime DEFAULT NULL, - `updatedAt` datetime DEFAULT NULL, - `permissions` longtext DEFAULT NULL, - '.$PDOColumns.' - PRIMARY KEY (`id`), - UNIQUE KEY `index1` (`uid`) - '.$PDOIndexes.' - ) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4; - '); - - if (!$query->execute()) { - return false; - } - - return true; - } - - /** - * Delete Collection - * - * @param Document $collection - * - * @return bool - */ - public function deleteCollection(Document $collection): bool - { - $query = $this->getPDO()->prepare('DROP TABLE `app_'.$this->getNamespace().'.collection.'.$collection->getId().'`;'); - - if (!$query->execute()) { - return false; - } - - $rules = $collection->getAttribute('rules', []); - - foreach ($rules as $attribute) { /** @var Document $attribute */ - $key = $attribute->getAttribute('key'); - $array = $attribute->getAttribute('array'); - - if($array) { - $this->deleteAttribute($collection, $key, $array); - } - } - - return true; - } - - /** - * Create Attribute - * - * @param Document $collection - * @param string $id - * @param string $type - * @param bool $array - * - * @return bool - */ - public function createAttribute(Document $collection, string $id, string $type, bool $array = false): bool - { - if($array) { - $query = $this->getPDO()->prepare('CREATE TABLE `app_'.$this->getNamespace().'.collection.'.$collection->getId().'.'.$id.'` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `uid` varchar(45) DEFAULT NULL, - '.$this->getAttributeType($id, $type).', - PRIMARY KEY (`id`), - KEY `index1` (`uid`) - ) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4; - ;'); - } - else { - $query = $this->getPDO()->prepare('ALTER TABLE `app_'.$this->getNamespace().'.collection.'.$collection->getId().'` - ADD COLUMN '.$this->getAttributeType($id, $type).';'); - } - - if (!$query->execute()) { - return false; - } - - return true; - } - - /** - * Delete Attribute - * - * @param Document $collection - * @param string $id - * @param bool $array - * - * @return bool - */ - public function deleteAttribute(Document $collection, string $id, bool $array = false): bool - { - if($array) { - $query = $this->getPDO()->prepare('DROP TABLE `app_'.$this->getNamespace().'.collection.'.$collection->getId().'.'.$id.'`;'); - } - else { - $query = $this->getPDO()->prepare('ALTER TABLE `app_'.$this->getNamespace().'.collection.'.$collection->getId().'` - DROP COLUMN `col_'.$id.'`;'); - } - - if (!$query->execute()) { - return false; - } - - return true; - } - - /** - * Create Index - * - * @param Document $collection - * @param string $id - * @param string $type - * @param array $attributes - * - * @return bool - */ - public function createIndex(Document $collection, string $id, string $type, array $attributes): bool - { - $query = $this->getPDO()->prepare('ALTER TABLE `app_'.$this->getNamespace().'.collection.'.$collection->getId().'` - ADD '.$this->getIndexType($id, $type, $attributes) . ';'); - - if (!$query->execute()) { - return false; - } - - return true; - } - - /** - * Delete Index - * - * @param Document $collection - * @param string $id - * - * @return bool - */ - public function deleteIndex(Document $collection, string $id): bool - { - $query = $this->getPDO()->prepare('ALTER TABLE `app_'.$this->getNamespace().'.collection.'.$collection->getId().'` - DROP INDEX `index_'.$id.'`;'); - - if (!$query->execute()) { - return false; - } - - return true; - } - - /** - * Get Document. - * - * @param Document $collection - * @param string $id - * - * @return array - * - * @throws Exception - */ - public function getDocument(Document $collection, $id) - { - // Get fields abstraction - $st = $this->getPDO()->prepare('SELECT * FROM `app_'.$this->getNamespace().'.collection.'.$collection->getId().'` documents - WHERE documents.uid = :uid; - '); - - $st->bindValue(':uid', $id, PDO::PARAM_STR); - - $st->execute(); - - $document = $st->fetch(); - - if (empty($document)) { // Not Found - return []; - } - - $rules = $collection->getAttribute('rules', []); - $data = []; - - $data['$id'] = (isset($document['uid'])) ? $document['uid'] : null; - $data['$collection'] = $collection->getId(); - $data['$permissions'] = (isset($document['permissions'])) ? json_decode($document['permissions'], true) : new stdClass; - - foreach($rules as $i => $rule) { /** @var Document $rule */ - $key = $rule->getAttribute('key'); - $type = $rule->getAttribute('type'); - $array = $rule->getAttribute('array'); - $list = $rule->getAttribute('list', []); - $value = (isset($document['col_'.$key])) ? $document['col_'.$key] : null; - - if(array_key_exists($key, $this->protected)) { - continue; - } - - if($array) { - $st = $this->getPDO()->prepare('SELECT * FROM `app_'.$this->getNamespace().'.collection.'.$collection->getId().'.'.$key.'` documents - WHERE documents.uid = :uid; - '); - - $st->bindValue(':uid', $id, PDO::PARAM_STR); - - $st->execute(); - - $elements = $st->fetchAll(); - - $value = []; - - foreach ($elements as $element) { - $value[] = (isset($element['col_'.$key])) ? $element['col_'.$key] : null; - } - } - - $value = ($array) ? $value : [$value]; - - if($array && !\is_array($value)) { - continue; - } - - foreach($value as $i => $element) { - switch($type) { - case Database::VAR_INTEGER: - $value[$i] = (int)$element; - break; - case Database::VAR_FLOAT: - case Database::VAR_NUMERIC: - $value[$i] = (float)$element; - break; - case Database::VAR_BOOLEAN: - $value[$i] = ($element === '1'); - break; - case Database::VAR_DOCUMENT: - $value[$i] = $this->getDatabase()->getDocument(array_pop(array_reverse($list)), $element); - break; - } - } - - $data[$key] = ($array) ? $value : $value[0]; - } - - return $data; - } - - /** - * Create Document. - * - * @param Document $collection - * @param array $data - * @param array $unique - * - * @throws \Exception - * - * @return array - */ - public function createDocument(Document $collection, array $data, array $unique = []) - { - $data['$id'] = $this->getId(); - $data['$permissions'] = (!isset($data['$permissions'])) ? [] : $data['$permissions']; - $columns = []; - $rules = $collection->getAttribute('rules', []); - - foreach($rules as $i => $rule) { - $key = $rule->getAttribute('key'); - $type = $rule->getAttribute('type'); - $array = $rule->getAttribute('array'); - - if(array_key_exists($key, $this->protected) || $array) { - continue; - } - - $columns[] = '`col_'.$key.'` = :col_'.$i; - } - - $columns = (!empty($columns)) ? ', '.implode(', ', $columns) : ''; - - /** - * Check Unique Keys - */ - //throw new Duplicate('Duplicated Property'); - - $this->beginTransaction(); - - $st = $this->getPDO()->prepare('INSERT INTO `app_'.$this->getNamespace().'.collection.'.$collection->getId().'` - SET uid = :uid, createdAt = :createdAt, updatedAt = :updatedAt, permissions = :permissions'.$columns.'; - '); - - $st->bindValue(':uid', $data['$id'], PDO::PARAM_STR); - $st->bindValue(':createdAt', \date('Y-m-d H:i:s', \time()), PDO::PARAM_STR); - $st->bindValue(':updatedAt', \date('Y-m-d H:i:s', \time()), PDO::PARAM_STR); - $st->bindValue(':permissions', \json_encode($data['$permissions']), PDO::PARAM_STR); - - foreach($rules as $i => $rule) { /** @var Document $rule */ - $key = $rule->getAttribute('key'); - $type = $rule->getAttribute('type'); - $array = $rule->getAttribute('array'); - $list = $rule->getAttribute('list', []); - $value = (isset($data[$key])) ? $data[$key] : null; - - if(array_key_exists($key, $this->protected)) { - continue; - } - - $value = ($array) ? $value : [$value]; - $value = ($array && !\is_array($value)) ? [] : $value; - - foreach ($value as $x => $element) { - switch($type) { - case Database::VAR_DOCUMENT: - $id = (isset($element['$id'])) ? $element['$id'] : null; - - $value[$x] = (empty($id)) - ? $this->getDatabase()->createDocument(array_pop(array_reverse($list)), $element)->getId() - : $this->getDatabase()->updateDocument(array_pop(array_reverse($list)), $id, $element)->getId(); - break; - } - } - - $value = ($array) ? $value : $value[0]; - - if($array) { - if(!is_array($value)) { - continue; - } - - foreach ($value as $element) { - $stArray = $this->getPDO()->prepare('INSERT INTO `app_'.$this->getNamespace().'.collection.'.$collection->getId().'.'.$key.'` - SET uid = :uid, `col_'.$key.'` = :col_x; - '); - - $stArray->bindValue(':uid', $data['$id'], PDO::PARAM_STR); - $stArray->bindValue(':col_x', $element, $this->getDataType($type)); - $stArray->execute(); - } - - continue; - } - - if(!$array) { - $st->bindValue(':col_'.$i, $value, $this->getDataType($type)); - } - } - - try { - $st->execute(); - } catch (\Throwable $th) { - switch ($th->getCode()) { - case '23000': - throw new Duplicate('Duplicated documents'); - break; - } - - throw $th; - } - - $this->commit(); - - //TODO remove this dependency (check if related to nested documents) - // $this->getRedis()->expire($this->getNamespace().':document-'.$data['$id'], 0); - // $this->getRedis()->expire($this->getNamespace().':document-'.$data['$id'], 0); - - return $data; - } - - /** - * Update Document. - * - * @param Document $collection - * @param string $id - * @param array $data - * - * @return array - * - * @throws Exception - */ - public function updateDocument(Document $collection, string $id, array $data) - { - if(!isset($data['$id']) || empty($data['$id']) || empty($id)) { - throw new Exception('$id is missing'); - } - - $data['$permissions'] = (!isset($data['$permissions'])) ? [] : $data['$permissions']; - $columns = []; - $rules = $collection->getAttribute('rules', []); - - foreach($rules as $i => $rule) { - $key = $rule->getAttribute('key'); - $type = $rule->getAttribute('type'); - $array = $rule->getAttribute('array'); - - if(array_key_exists($key, $this->protected) || $array) { - continue; - } - - $columns[] = '`col_'.$key.'` = :col_'.$i; - } - - $columns = (!empty($columns)) ? ', '.implode(', ', $columns) : ''; - - /** - * Check Unique Keys - */ - //throw new Duplicate('Duplicated Property'); - - $this->beginTransaction(); - - $st = $this->getPDO()->prepare('UPDATE `app_'.$this->getNamespace().'.collection.'.$collection->getId().'` - SET updatedAt = :updatedAt, permissions = :permissions'.$columns.' - WHERE uid = :uid; - '); - - $st->bindValue(':uid', $data['$id'], PDO::PARAM_STR); - $st->bindValue(':updatedAt', \date('Y-m-d H:i:s', \time()), PDO::PARAM_STR); - $st->bindValue(':permissions', \json_encode($data['$permissions']), PDO::PARAM_STR); - - foreach($rules as $i => $rule) { /** @var Document $rule */ - $key = $rule->getAttribute('key'); - $type = $rule->getAttribute('type'); - $array = $rule->getAttribute('array'); - $list = $rule->getAttribute('list', []); - $value = (isset($data[$key])) ? $data[$key] : null; - - if(array_key_exists($key, $this->protected)) { - continue; - } - - $value = ($array) ? $value : [$value]; - $value = ($array && !\is_array($value)) ? [] : $value; - - foreach ($value as $x => $element) { - switch($type) { - case Database::VAR_DOCUMENT: - $id = (isset($element['$id'])) ? $element['$id'] : null; - $value[$x] = (empty($id)) - ? $this->getDatabase()->createDocument(array_pop(array_reverse($list)), $element)->getId() - : $this->getDatabase()->updateDocument(array_pop(array_reverse($list)), $id, $element)->getId(); - break; - } - } - - $value = ($array) ? $value : $value[0]; - - if($array) { - if(!is_array($value)) { - continue; - } - - $stArray = $this->getPDO()->prepare('DELETE FROM `app_'.$this->getNamespace().'.collection.'.$collection->getId().'.'.$key.'` - WHERE uid = :uid; - '); - - $stArray->bindValue(':uid', $data['$id'], PDO::PARAM_STR); - $stArray->execute(); - - foreach ($value as $element) { - $stArray = $this->getPDO()->prepare('INSERT INTO `app_'.$this->getNamespace().'.collection.'.$collection->getId().'.'.$key.'` - SET uid = :uid, `col_'.$key.'` = :col_x; - '); - - $stArray->bindValue(':uid', $data['$id'], PDO::PARAM_STR); - $stArray->bindValue(':col_x', $element, $this->getDataType($type)); - $stArray->execute(); - } - - continue; - } - - if(!$array) { - $st->bindValue(':col_'.$i, $value, $this->getDataType($type)); - } - } - - try { - $st->execute(); - } catch (\Throwable $th) { - switch ($th->getCode()) { - case '23000': - throw new Duplicate('Duplicated documents'); - break; - } - - throw $th; - } - - $this->commit(); - - //TODO remove this dependency (check if related to nested documents) - // $this->getRedis()->expire($this->getNamespace().':document-'.$data['$id'], 0); - // $this->getRedis()->expire($this->getNamespace().':document-'.$data['$id'], 0); - - return $data; - } - - /** - * Delete Document. - * - * @param Document $collection - * @param string $id - * - * @return array - * - * @throws Exception - */ - public function deleteDocument(Document $collection, string $id) - { - $rules = $collection->getAttribute('rules', []); - - $this->beginTransaction(); - - $st = $this->getPDO()->prepare('DELETE FROM `app_'.$this->getNamespace().'.collection.'.$collection->getId().'` - WHERE uid = :uid - '); - - $st->bindValue(':uid', $id, PDO::PARAM_STR); - - $st->execute(); - - foreach($rules as $i => $rule) { /** @var Document $rule */ - $key = $rule->getAttribute('key'); - $array = $rule->getAttribute('array'); - - if($array) { - $stArray = $this->getPDO()->prepare('DELETE FROM `app_'.$this->getNamespace().'.collection.'.$collection->getId().'.'.$key.'` - WHERE uid = :uid; - '); - - $stArray->bindValue(':uid', $id, PDO::PARAM_STR); - $stArray->execute(); - } - } - - $st->execute(); - - $this->commit(); - - return []; - } - - /** - * Create Namespace. - * - * @param $namespace - * - * @throws Exception - * - * @return bool - */ - public function createNamespace($namespace) - { - if (empty($namespace)) { - throw new Exception('Empty namespace'); - } - - $audit = 'app_'.$namespace.'.audit.audit'; - $abuse = 'app_'.$namespace.'.abuse.abuse'; - - /** - * 1. Itterate default collections - * 2. Create collection - * 3. Create all regular and array fields - * 4. Create all indexes - * 5. Create audit / abuse tables - */ - - foreach($this->getMocks() as $collection) { /** @var Document $collection */ - $this->createCollection($collection, $collection->getId()); - } - - try { - $this->getPDO()->prepare('CREATE TABLE `'.$audit.'` LIKE `template.audit.audit`;')->execute(); - $this->getPDO()->prepare('CREATE TABLE `'.$abuse.'` LIKE `template.abuse.abuse`;')->execute(); - } catch (Exception $e) { - throw $e; - } - - return true; - } - - /** - * Delete Namespace. - * - * @param $namespace - * - * @throws Exception - * - * @return bool - */ - public function deleteNamespace($namespace) - { - if (empty($namespace)) { - throw new Exception('Empty namespace'); - } - - $audit = 'app_'.$namespace.'.audit.audit'; - $abuse = 'app_'.$namespace.'.abuse.abuse'; - - foreach($this->getMocks() as $collection) { /** @var Document $collection */ - $this->deleteCollection($collection, $collection->getId()); - } - - // TODO Delete all custom collections - - try { - $this->getPDO()->prepare('DROP TABLE `'.$audit.'`;')->execute(); - $this->getPDO()->prepare('DROP TABLE `'.$abuse.'`;')->execute(); - } catch (Exception $e) { - throw $e; - } - - return true; - } - - /** - * Find - * - * @param Document $collection - * @param array $options - * - * @throws Exception - * - * @return array - */ - public function find(Document $collection, array $options) - { - $start = \microtime(true); - - $rules = $collection->getAttribute('rules', []); /** @var Document[] $rules */ - $where = []; - $join = []; - $sorts = []; - $columns = []; - $search = ''; - - foreach ($rules as $key => $rule) { - $columns[$rule->getAttribute('key', '')] = $rule; - } - - var_dump(array_keys($columns)); - - $options['orderField'] = (empty($options['orderField']) || $options['orderField'] === '$id') // Set default order field - ? '' - : $options['orderField']; - - - if (!\in_array($options['orderType'], ['DESC', 'ASC'])) { - throw new Exception('Invalid order type'); - } - - if(!array_key_exists($options['orderField'], $columns) && !empty($options['orderField'])) { - throw new Exception('Unknown oreder field: '.$options['orderField']); - } - - $options['orderField'] = (!empty($options['orderField'])) ? 'col_'.$options['orderField'] : 'a.uid'; - - foreach ($options['filters'] as $i => $filter) { // Filters - $filter = $this->parseFilter($filter); - $key = $filter['key']; - $value = $filter['value']; - $operator = $filter['operator']; - - $path = \explode('.', $key); - $original = $path; - - if (1 < \count($path)) { - $key = \array_pop($path); - } - else { - $path = []; - } - - $value = $this->getPDO()->quote($value, PDO::PARAM_STR); - - $options['offset'] = (int) $options['offset']; - $options['limit'] = (int) $options['limit']; - - if (empty($path)) { - if(!array_key_exists($key, $columns)) { - throw new Exception('Unknown key: '.$key); - } - - var_dump($columns[$key]); - var_dump($columns[$key]->getAttribute('array')); - if($columns[$key]->getAttribute('array') === true) { - $join[] = 'JOIN `app_'.$this->getNamespace().'.collection.'.$collection->getId().'.'.$key.'` a'.$i.' - ON a.uid IS NOT NULL AND a'.$i.'.uid = a.uid'; - $where[] = '(a'.$i.'.col_'.$key.' '.$operator.' '.$value.')'; - $where[] = '(a'.$i.'.col_'.$key.' '.$operator.' '.$value.')'; - //AND a.uid NOT IN (SELECT a2.uid FROM `app_'.$this->getNamespace().'.collection.'.$collection->getId().'.'.$key.'` a'.$i.' a'.$i.' WHERE (a'.$i.'.col_langauges = '.$value.')) - - } - else { - $where[] = "(col_{$key} {$operator} {$value})"; - } - } - else { // Handle relationships - $len = \count($original); - $prev = 'c'.$i; - - foreach ($original as $y => $part) { - $part = $this->getPDO()->quote($part, PDO::PARAM_STR); - - if (0 === $y) { // First key - $join[$i] = 'JOIN `'.$this->getNamespace().".database.relationships` c{$i} ON a.uid IS NOT NULL AND c{$i}.start = a.uid AND c{$i}.key = {$part}"; - } elseif ($y == $len - 1) { // Last key - $join[$i] .= 'JOIN `'.$this->getNamespace().".database.properties` e{$i} ON e{$i}.documentUid = {$prev}.end AND e{$i}.key = {$part} AND e{$i}.value {$operator} {$value}"; - } else { - $join[$i] .= 'JOIN `'.$this->getNamespace().".database.relationships` d{$i}{$y} ON d{$i}{$y}.start = {$prev}.end AND d{$i}{$y}.key = {$part}"; - $prev = 'd'.$i.$y; - } - } - } - } - - $select = 'DISTINCT a.uid'; - $where = \implode(" AND \n", $where); - $join = \implode("\n", $join); - $sorts = \implode("\n", $sorts); - $range = "LIMIT {$options['offset']}, {$options['limit']}"; - $roles = []; - - if (false === Authorization::$status) { // FIXME temporary solution (hopefully) - $roles = ['1=1']; - } - else { - foreach (Authorization::getRoles() as $role) { - $roles[] = 'JSON_CONTAINS(REPLACE(a.permissions, \'{self}\', a.uid), \'"'.$role.'"\', \'$.read\')'; - } - } - - $query = 'SELECT %s - FROM `app_'.$this->getNamespace().'.collection.'.$collection->getId().'` a - '.$join.' - '.$sorts.' - WHERE - '.$where.' - '.$search.' - AND ('.\implode('||', $roles).') - ORDER BY '.$options['orderField'].' '.$options['orderType'].' %s'; - - var_dump(\preg_replace('/\s+/', ' ', \sprintf($query, $select, $range))); - - return []; - - $st = $this->getPDO()->prepare(\sprintf($query, $select, $range)); - - $st->execute(); - - $results = ['data' => []]; - - // Get entire fields data for each id - foreach ($st->fetchAll() as $node) { - $results['data'][] = $node['uid']; - } - - $this->resetDebug(); - - $this - ->setDebug('query', \preg_replace('/\s+/', ' ', \sprintf($query, $select, $range))) - ->setDebug('time', \microtime(true) - $start) - ->setDebug('filters', \count($options['filters'])) - ->setDebug('joins', \substr_count($query, 'JOIN')) - ->setDebug('count', \count($results['data'])) - ->setDebug('sum', (int) 0) - ; - - return $results['data']; - } - - /** - * Count - * - * @param array $options - * - * @throws Exception - * - * @return int - */ - public function count(array $options) - { - return 0; - } - - /** - * Parse Filter. - * - * @param string $filter - * - * @return array - * - * @throws Exception - */ - protected function parseFilter($filter) - { - $operatorsMap = ['!=', '>=', '<=', '=', '>', '<']; // Do not edit order of this array - - //FIXME bug with >= <= operators - - $operator = null; - - foreach ($operatorsMap as $node) { - if (\strpos($filter, $node) !== false) { - $operator = $node; - break; - } - } - - if (empty($operator)) { - throw new Exception('Invalid operator'); - } - - $filter = \explode($operator, $filter); - - if (\count($filter) != 2) { - throw new Exception('Invalid filter expression'); - } - - return [ - 'key' => $filter[0], - 'value' => $filter[1], - 'operator' => $operator, - ]; - } - - /** - * Get PDO Data Type. - * - * @param $type - * - * @return string - * - * @throws \Exception - */ - protected function getDataType($type) - { - switch ($type) { - case Database::VAR_TEXT: - case Database::VAR_URL: - case Database::VAR_KEY: - case Database::VAR_IPV4: - case Database::VAR_IPV6: - case Database::VAR_EMAIL: - case Database::VAR_FLOAT: - case Database::VAR_NUMERIC: - return PDO::PARAM_STR; - break; - - case Database::VAR_DOCUMENT: - return PDO::PARAM_STR; - break; - - case Database::VAR_INTEGER: - return PDO::PARAM_INT; - break; - - case Database::VAR_BOOLEAN: - return PDO::PARAM_BOOL; - break; - - default: - throw new Exception('Unsupported attribute: '.$type); - break; - } - } - - /** - * Get Column - * - * @var string $key - * @var string $type - * - * @return string - */ - protected function getAttributeType(string $key, string $type): string - { - switch ($type) { - case Database::VAR_TEXT: - case Database::VAR_URL: - return '`col_'.$key.'` TEXT NULL'; - break; - - case Database::VAR_KEY: - case Database::VAR_DOCUMENT: - return '`col_'.$key.'` VARCHAR(36) NULL'; - break; - - case Database::VAR_IPV4: - //return '`col_'.$key.'` INT UNSIGNED NULL'; - return '`col_'.$key.'` VARCHAR(15) NULL'; - break; - - case Database::VAR_IPV6: - //return '`col_'.$key.'` BINARY(16) NULL'; - return '`col_'.$key.'` VARCHAR(39) NULL'; - break; - - case Database::VAR_EMAIL: - return '`col_'.$key.'` VARCHAR(255) NULL'; - break; - - case Database::VAR_INTEGER: - return '`col_'.$key.'` INT NULL'; - break; - - case Database::VAR_FLOAT: - case Database::VAR_NUMERIC: - return '`col_'.$key.'` FLOAT NULL'; - break; - - case Database::VAR_BOOLEAN: - return '`col_'.$key.'` BOOLEAN NULL'; - break; - - default: - throw new Exception('Unsupported attribute: '.$type); - break; - } - } - - /** - * @var string $type - * - * @throws Exceptions - * - * @return string - */ - protected function getIndexType(string $key, string $type, array $attributes): string - { - $index = ''; - $columns = []; - - foreach ($attributes as $attribute) { - $columns[] = '`col_'.$attribute.'`(32) ASC'; // TODO custom size limit per type - } - - switch ($type) { - case Database::INDEX_KEY: - $index = 'INDEX'; - break; - - case Database::INDEX_FULLTEXT: - $index = 'FULLTEXT INDEX'; - break; - - case Database::INDEX_UNIQUE: - $index = 'UNIQUE INDEX'; - break; - - case Database::INDEX_SPATIAL: - $index = 'SPATIAL INDEX'; - break; - - default: - throw new Exception('Unsupported indext type'); - break; - } - - return $index .' `index_'.$key.'` ('.implode(',', $columns).')'; - } - - /** - * @return false - */ - protected function beginTransaction(): bool - { - if($this->transaction) { - return false; - } - - $this->transaction = true; - - $this->getPDO()->beginTransaction(); - - return true; - } - - /** - * @return false - */ - protected function commit(): bool - { - if(!$this->transaction) { - return false; - } - - $this->getPDO()->commit(); - - $this->transaction = false; - - return true; - } - - /** - * @return PDO - * - * @throws Exception - */ - protected function getPDO() - { - return $this->pdo; - } - - public function deleteUniqueKey($key) - { - return []; - } -} - - -// // Sorting -// $orderPath = \explode('.', $options['orderField']); -// $len = \count($orderPath); -// $orderKey = 'order_b'; -// $part = $this->getPDO()->quote(\implode('', $orderPath), PDO::PARAM_STR); -// $orderSelect = "CASE WHEN {$orderKey}.key = {$part} THEN CAST({$orderKey}.value AS {$orderCastMap[$options['orderCast']]}) END AS sort_ff"; - -// if (1 === $len) { -// //if($path == "''") { // Handle direct attributes queries -// $sorts[] = 'LEFT JOIN `'.$this->getNamespace().".database.properties` order_b ON a.uid IS NOT NULL AND order_b.documentUid = a.uid AND (order_b.key = {$part})"; -// } else { // Handle direct child attributes queries -// $prev = 'c'; -// $orderKey = 'order_e'; - -// foreach ($orderPath as $y => $part) { -// $part = $this->getPDO()->quote($part, PDO::PARAM_STR); -// $x = $y - 1; - -// if (0 === $y) { // First key -// $sorts[] = 'JOIN `'.$this->getNamespace().".database.relationships` order_c{$y} ON a.uid IS NOT NULL AND order_c{$y}.start = a.uid AND order_c{$y}.key = {$part}"; -// } elseif ($y == $len - 1) { // Last key -// $sorts[] .= 'JOIN `'.$this->getNamespace().".database.properties` order_e ON order_e.documentUid = order_{$prev}{$x}.end AND order_e.key = {$part}"; -// } else { -// $sorts[] .= 'JOIN `'.$this->getNamespace().".database.relationships` order_d{$y} ON order_d{$y}.start = order_{$prev}{$x}.end AND order_d{$y}.key = {$part}"; -// $prev = 'd'; -// } -// } -// } - -/* - * Workaround for a MySQL bug as reported here: - * https://bugs.mysql.com/bug.php?id=78485 - */ -// $options['search'] = ($options['search'] === '*') ? '' : $options['search']; - -// Search -// if (!empty($options['search'])) { // Handle free search -// $where[] = 'LEFT JOIN `'.$this->getNamespace().".database.properties` b_search ON a.uid IS NOT NULL AND b_search.documentUid = a.uid AND b_search.primitive = 'string' -// LEFT JOIN -// `".$this->getNamespace().'.database.relationships` c_search ON c_search.start = b_search.documentUid -// LEFT JOIN -// `'.$this->getNamespace().".database.properties` d_search ON d_search.documentUid = c_search.end AND d_search.primitive = 'string' -// \n"; - -// $search = "AND (MATCH (b_search.value) AGAINST ({$this->getPDO()->quote($options['search'], PDO::PARAM_STR)} IN BOOLEAN MODE) -// OR MATCH (d_search.value) AGAINST ({$this->getPDO()->quote($options['search'], PDO::PARAM_STR)} IN BOOLEAN MODE) -// )"; -// } - -// $count = $this->getPDO()->prepare(\sprintf($query, 'count(DISTINCT a.uid) as sum', '')); -// $count->execute(); -// $count = $count->fetch(); \ No newline at end of file From f129112c48047c50c08a9b0642836cf581112d66 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 24 Jan 2021 01:30:18 +0200 Subject: [PATCH 009/190] Removed mocks --- src/Database/Database.php | 122 +++++++------------------------------- 1 file changed, 22 insertions(+), 100 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index f92e27cd9..1eb40d96d 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -10,20 +10,16 @@ class Database { - static public $cache = []; - - // Var Types + // Simple Types const VAR_TEXT = 'text'; - const VAR_INTEGER = 'integer'; - const VAR_FLOAT = 'float'; - const VAR_NUMERIC = 'numeric'; + const VAR_NUMBER = 'integer'; const VAR_BOOLEAN = 'boolean'; + const VAR_NULL = 'null'; + const VAR_ARRAY = 'array'; + const VAR_OBJECT = 'object'; + + // Relationships Types const VAR_DOCUMENT = 'document'; - const VAR_EMAIL = 'email'; - const VAR_URL = 'url'; - const VAR_IPV4 = 'ipv4'; - const VAR_IPV6 = 'ipv6'; - const VAR_KEY = 'key'; // Index Types const INDEX_KEY = 'text'; @@ -31,15 +27,8 @@ class Database const INDEX_UNIQUE = 'unique'; const INDEX_SPATIAL = 'spatial'; - /** - * @var array - */ - static protected $filters = []; - - /** - * @var array - */ - protected $mocks = []; + // Collections + const COLLECTION_COLLECTIONS = 'collections'; /** * @var Adapter @@ -47,33 +36,22 @@ class Database protected $adapter; /** - * Set Adapter. - * - * @param Adapter $adapter - * - * @return $this + * @var array */ - public function setAdapter(Adapter $adapter) - { - $this->adapter = $adapter; - - $this->adapter->setDatabase($this); - - return $this; - } + static public $filters = []; /** * Set Namespace. * * Set namespace to divide different scope of data sets * - * @param $namespace + * @param string $namespace * * @return $this * * @throws Exception */ - public function setNamespace($namespace) + public function setNamespace(string $namespace): self { $this->adapter->setNamespace($namespace); @@ -89,33 +67,33 @@ public function setNamespace($namespace) * * @throws Exception */ - public function getNamespace() + public function getNamespace(): string { return $this->adapter->getNamespace(); } /** - * Create Namespace. + * Create Database. * - * @param string $namespace + * @param string $name * * @return bool */ - public function createNamespace($namespace) + public function create(string $name): bool { - return $this->adapter->createNamespace($namespace); + return $this->adapter->create($name); } /** - * Delete Namespace. + * Delete Database. * - * @param string $namespace + * @param string $name * * @return bool */ - public function deleteNamespace($namespace) + public function delete(string $name): bool { - return $this->adapter->deleteNamespace($namespace); + return $this->adapter->delete($name); } /** @@ -284,13 +262,6 @@ public function getDocument($collection, $id, bool $mock = true, bool $decode = return new Document([]); } - if(isset(self::$cache[$this->getNamespace().'/'.$collection.'/'.$id])) { - self::$cache[$this->getNamespace().'/'.$collection.'/'.$id]++; - } - else { - self::$cache[$this->getNamespace().'/'.$collection.'/'.$id] = 0; - } - if($mock === true && isset($this->mocks[$id])) { $document = $this->mocks[$id]; @@ -459,18 +430,6 @@ public function deleteDocument(string $collection, string $id) return new Document($this->adapter->deleteDocument($this->getDocument(self::COLLECTION_COLLECTIONS, $collection), $id)); } - /** - * @param int $key - * - * @return Document|false - * - * @throws AuthorizationException - */ - public function deleteUniqueKey($key) - { - return new Document($this->adapter->deleteUniqueKey($key)); - } - /** * @return array */ @@ -489,43 +448,6 @@ public function getSum() return (isset($debug['sum'])) ? $debug['sum'] : 0; } - /** - * @param string $key - * @param string $value - * - * @return self - */ - public function setMock($key, $value): self - { - $this->mocks[$key] = $value; - - return $this; - } - - /** - * @param array $mocks - * - * @return self - */ - public function setMocks(array $mocks): self - { - foreach ($mocks as $key => $mock) { - $this->mocks[$key] = new Document($mock); - } - - $this->adapter->setMocks($this->mocks); - - return $this; - } - - /** - * @return array - */ - public function getMocks() - { - return $this->mocks; - } - /** * Add Attribute Filter * From ebf396d0034c1c97283b247bf16ec0492cb7f08c Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 24 Jan 2021 01:30:33 +0200 Subject: [PATCH 010/190] Dont allow key with _ --- src/Database/Validator/Key.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Database/Validator/Key.php b/src/Database/Validator/Key.php index ebfbd639f..ffb6d0eb1 100644 --- a/src/Database/Validator/Key.php +++ b/src/Database/Validator/Key.php @@ -37,6 +37,10 @@ public function isValid($value) if (!\is_string($value)) { return false; } + + if(mb_substr($value, 0, 1) === '_') { + return false; + } if (\preg_match('/[^A-Za-z0-9\-\_]/', $value)) { return false; From 59ad0d215c91c0a42bb278702a988549758e5040 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 24 Jan 2021 01:31:43 +0200 Subject: [PATCH 011/190] Updated key tests --- tests/Database/Validator/KeyTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/Database/Validator/KeyTest.php b/tests/Database/Validator/KeyTest.php index bd5bc569e..99fa7b005 100644 --- a/tests/Database/Validator/KeyTest.php +++ b/tests/Database/Validator/KeyTest.php @@ -25,6 +25,9 @@ public function testValues() { $this->assertEquals($this->object->isValid('dasda asdasd'), false); $this->assertEquals($this->object->isValid('asdasdasdas'), true); + $this->assertEquals($this->object->isValid('_asdasdasdas'), false); + $this->assertEquals($this->object->isValid('asd"asdasdas'), false); + $this->assertEquals($this->object->isValid('asd\'asdasdas'), false); $this->assertEquals($this->object->isValid('as$$5dasdasdas'), false); $this->assertEquals($this->object->isValid(false), false); $this->assertEquals($this->object->isValid(null), false); From 43e132c773afca279842b013c35f8241ef666c4a Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 24 Jan 2021 01:32:11 +0200 Subject: [PATCH 012/190] Added types --- src/Database/Document.php | 46 +++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/src/Database/Document.php b/src/Database/Document.php index 643490ba8..6c6ee3545 100644 --- a/src/Database/Document.php +++ b/src/Database/Document.php @@ -43,23 +43,23 @@ public function __construct($input = [], $flags = 0, $iterator_class = 'ArrayIte /** * @return string|null */ - public function getId() + public function getId(): string { - return $this->getAttribute('$id', null); + return $this->getAttribute('$id', ''); } /** * @return string */ - public function getCollection() + public function getCollection(): string { - return $this->getAttribute('$collection', null); + return $this->getAttribute('$collection', ''); } /** * @return array */ - public function getPermissions() + public function getPermissions(): array { return $this->getAttribute('$permissions', []); } @@ -74,7 +74,7 @@ public function getPermissions() * * @return mixed */ - public function getAttribute($name, $default = null) + public function getAttribute(string $name, $default = null) { $name = \explode('.', $name); @@ -100,9 +100,9 @@ public function getAttribute($name, $default = null) * @param mixed $value * @param string $type * - * @return mixed + * @return self */ - public function setAttribute($key, $value, $type = self::SET_TYPE_ASSIGN) + public function setAttribute(string $key, $value, string $type = self::SET_TYPE_ASSIGN): self { switch ($type) { case self::SET_TYPE_ASSIGN: @@ -127,12 +127,10 @@ public function setAttribute($key, $value, $type = self::SET_TYPE_ASSIGN) * Method for removing a specific field attribute * * @param string $key - * @param mixed $value - * @param string $type * - * @return mixed + * @return self */ - public function removeAttribute($key) + public function removeAttribute(string $key): self { if (isset($this[$key])) { unset($this[$key]); @@ -146,13 +144,13 @@ public function removeAttribute($key) * * Get array child by key and value match * - * @param $key - * @param $value + * @param string $key + * @param mixed $value * @param array|null $scope * * @return Document|Document[]|mixed|null|array */ - public function search($key, $value, $scope = null) + public function search(string $key, $value, $scope = null) { $array = (!\is_null($scope)) ? $scope : $this; @@ -188,7 +186,7 @@ public function search($key, $value, $scope = null) * * @return bool */ - public function isEmpty() + public function isEmpty(): bool { return empty($this->getId()); } @@ -200,7 +198,7 @@ public function isEmpty() * * @return bool */ - public function isSet($key) + public function isSet($key): bool { return isset($this[$key]); } @@ -210,32 +208,32 @@ public function isSet($key) * * Outputs entity as a PHP array * - * @param array $whitelist - * @param array $blacklist + * @param array $allow + * @param array $disallow * * @return array */ - public function getArrayCopy(array $whitelist = [], array $blacklist = []) + public function getArrayCopy(array $allow = [], array $disallow = []): array { $array = parent::getArrayCopy(); $output = []; foreach ($array as $key => &$value) { - if (!empty($whitelist) && !\in_array($key, $whitelist)) { // Export only whitelisted fields + if (!empty($allow) && !\in_array($key, $allow)) { // Export only allow fields continue; } - if (!empty($blacklist) && \in_array($key, $blacklist)) { // Don't export blacklisted fields + if (!empty($disallow) && \in_array($key, $disallow)) { // Don't export disallowed fields continue; } if ($value instanceof self) { - $output[$key] = $value->getArrayCopy($whitelist, $blacklist); + $output[$key] = $value->getArrayCopy($allow, $disallow); } elseif (\is_array($value)) { foreach ($value as $childKey => &$child) { if ($child instanceof self) { - $output[$key][$childKey] = $child->getArrayCopy($whitelist, $blacklist); + $output[$key][$childKey] = $child->getArrayCopy($allow, $disallow); } else { $output[$key][$childKey] = $child; } From 1922ef009e32096ed2fa32aefab9d5b61230554c Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 24 Jan 2021 01:32:26 +0200 Subject: [PATCH 013/190] Added tests --- tests/Database/DatabaseTest.php | 3750 +++++++++--------- tests/Database/DocumentTest.php | 166 + tests/Database/Validator/PermissionsTest.php | 8 - 3 files changed, 2041 insertions(+), 1883 deletions(-) create mode 100644 tests/Database/DocumentTest.php diff --git a/tests/Database/DatabaseTest.php b/tests/Database/DatabaseTest.php index 61e65e42d..a094a451f 100644 --- a/tests/Database/DatabaseTest.php +++ b/tests/Database/DatabaseTest.php @@ -1,1907 +1,1907 @@ 'SET NAMES utf8mb4', - PDO::ATTR_TIMEOUT => 3, // Seconds - PDO::ATTR_PERSISTENT => true - )); - - // Connection settings - $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); // Return arrays - $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // Handle all errors with exceptions - - self::$object = new Database(); - self::$object->setAdapter(new Relational($pdo)); - - self::$object->setMocks([ - Database::COLLECTION_COLLECTIONS => [ - '$collection' => Database::COLLECTION_COLLECTIONS, - '$id' => Database::COLLECTION_COLLECTIONS, - '$permissions' => ['read' => ['*']], - 'name' => 'Collections', - 'rules' => [ - [ - '$collection' => Database::COLLECTION_RULES, - 'label' => 'Name', - 'key' => 'name', - 'type' => Database::VAR_TEXT, - 'default' => '', - 'required' => true, - 'array' => false, - ], - [ - '$collection' => Database::COLLECTION_RULES, - 'label' => 'Date Created', - 'key' => 'dateCreated', - 'type' => Database::VAR_INTEGER, - 'default' => 0, - 'required' => false, - 'array' => false, - ], - [ - '$collection' => Database::COLLECTION_RULES, - 'label' => 'Date Updated', - 'key' => 'dateUpdated', - 'type' => Database::VAR_INTEGER, - 'default' => 0, - 'required' => false, - 'array' => false, - ], - [ - '$collection' => Database::COLLECTION_RULES, - 'label' => 'Rules', - 'key' => 'rules', - 'type' => Database::VAR_DOCUMENT, - 'default' => [], - 'required' => false, - 'array' => true, - 'list' => [Database::COLLECTION_RULES], - ], - [ - '$collection' => Database::COLLECTION_RULES, - 'label' => 'Indexes', - 'key' => 'indexes', - 'type' => Database::VAR_DOCUMENT, - 'default' => [], - 'required' => false, - 'array' => true, - 'list' => [Database::COLLECTION_INDEXES], - ], - ], - 'indexes' => [ - [ - '$collection' => Database::COLLECTION_INDEXES, - '$id' => 'system1', - 'type' => Database::INDEX_KEY, - 'attributes' => [ - 'name', - ], - ], - [ - '$collection' => Database::COLLECTION_INDEXES, - '$id' => 'system2', - 'type' => Database::INDEX_FULLTEXT, - 'attributes' => [ - 'name', - ], - ], - ], - ], - Database::COLLECTION_RULES => [ - '$collection' => Database::COLLECTION_COLLECTIONS, - '$id' => Database::COLLECTION_RULES, - '$permissions' => ['read' => ['*']], - 'name' => 'Collections Rule', - 'rules' => [ - [ - '$collection' => Database::COLLECTION_RULES, - 'label' => 'Label', - 'key' => 'label', - 'type' => Database::VAR_TEXT, - 'default' => '', - 'required' => true, - 'array' => false, - ], - [ - '$collection' => Database::COLLECTION_RULES, - 'label' => 'Key', - 'key' => 'key', - 'type' => Database::VAR_KEY, - 'default' => '', - 'required' => true, - 'array' => false, - ], - [ - '$collection' => Database::COLLECTION_RULES, - 'label' => 'Type', - 'key' => 'type', - 'type' => Database::VAR_TEXT, - 'default' => '', - 'required' => true, - 'array' => false, - ], - [ - '$collection' => Database::COLLECTION_RULES, - 'label' => 'Default', - 'key' => 'default', - 'type' => Database::VAR_TEXT, - 'default' => '', - 'required' => false, - 'array' => false, - ], - [ - '$collection' => Database::COLLECTION_RULES, - 'label' => 'Required', - 'key' => 'required', - 'type' => Database::VAR_BOOLEAN, - 'default' => true, - 'required' => true, - 'array' => false, - ], - [ - '$collection' => Database::COLLECTION_RULES, - 'label' => 'Array', - 'key' => 'array', - 'type' => Database::VAR_BOOLEAN, - 'default' => true, - 'required' => true, - 'array' => false, - ], - [ - '$collection' => Database::COLLECTION_RULES, - 'label' => 'list', - 'key' => 'list', - 'type' => Database::VAR_TEXT, - //'default' => '', - 'required' => false, - 'array' => true, - ], - ], - ], - Database::COLLECTION_INDEXES => [ - '$collection' => Database::COLLECTION_COLLECTIONS, - '$id' => Database::COLLECTION_INDEXES, - '$permissions' => ['read' => ['*']], - 'name' => 'Collections Indexes', - 'rules' => [ - [ - '$collection' => Database::COLLECTION_RULES, - 'label' => 'Type', - 'key' => 'type', - 'type' => Database::VAR_TEXT, - 'default' => '', - 'required' => true, - 'array' => false, - ], - [ - '$collection' => Database::COLLECTION_RULES, - 'label' => 'Attributes', - 'key' => 'attributes', - 'type' => Database::VAR_KEY, - 'default' => '', - 'required' => true, - 'array' => true, - ], - ], - ], - Database::COLLECTION_USERS => [ - '$collection' => Database::COLLECTION_COLLECTIONS, - '$id' => Database::COLLECTION_USERS, - '$permissions' => ['read' => ['*']], - 'name' => 'User', - 'rules' => [ - [ - '$collection' => Database::COLLECTION_RULES, - 'label' => 'Name', - 'key' => 'name', - 'type' => Database::VAR_TEXT, - 'default' => '', - 'required' => false, - 'array' => false, - ], - [ - '$collection' => Database::COLLECTION_RULES, - 'label' => 'Email', - 'key' => 'email', - 'type' => Database::VAR_EMAIL, - 'default' => '', - 'required' => true, - 'array' => false, - ], - [ - '$collection' => Database::COLLECTION_RULES, - 'label' => 'Status', - 'key' => 'status', - 'type' => Database::VAR_INTEGER, - 'default' => '', - 'required' => true, - 'array' => false, - ], - [ - '$collection' => Database::COLLECTION_RULES, - 'label' => 'Password', - 'key' => 'password', - 'type' => Database::VAR_TEXT, - 'default' => '', - 'required' => true, - 'array' => false, - ], - [ - '$collection' => Database::COLLECTION_RULES, - 'label' => 'Password Update Date', - 'key' => 'password-update', - 'type' => Database::VAR_INTEGER, - 'default' => '', - 'required' => true, - 'array' => false, - ], - [ - '$collection' => Database::COLLECTION_RULES, - 'label' => 'Prefs', - 'key' => 'prefs', - 'type' => Database::VAR_TEXT, - 'default' => '', - 'required' => false, - 'array' => false, - 'filter' => ['json'] - ], - [ - '$collection' => Database::COLLECTION_RULES, - 'label' => 'Registration Date', - 'key' => 'registration', - 'type' => Database::VAR_INTEGER, - 'default' => '', - 'required' => true, - 'array' => false, - ], - [ - '$collection' => Database::COLLECTION_RULES, - 'label' => 'Email Verification Status', - 'key' => 'emailVerification', - 'type' => Database::VAR_BOOLEAN, - 'default' => '', - 'required' => true, - 'array' => false, - ], - [ - '$collection' => Database::COLLECTION_RULES, - 'label' => 'Reset', - 'key' => 'reset', - 'type' => Database::VAR_BOOLEAN, - 'default' => '', - 'required' => true, - 'array' => false, - ], - [ - '$collection' => Database::COLLECTION_RULES, - 'label' => 'Tokens', - 'key' => 'tokens', - 'type' => Database::VAR_DOCUMENT, - 'default' => [], - 'required' => false, - 'array' => true, - 'list' => [Database::COLLECTION_TOKENS], - ], - [ - '$collection' => Database::COLLECTION_RULES, - 'label' => 'Memberships', - 'key' => 'memberships', - 'type' => Database::VAR_DOCUMENT, - 'default' => [], - 'required' => false, - 'array' => true, - 'list' => [Database::COLLECTION_MEMBERSHIPS], - ], - ], - 'indexes' => [ - [ - '$collection' => Database::COLLECTION_INDEXES, - '$id' => 'system1', - 'type' => Database::INDEX_KEY, - 'attributes' => [ - 'name', - ], - ], - [ - '$collection' => Database::COLLECTION_INDEXES, - '$id' => 'system2', - 'type' => Database::INDEX_FULLTEXT, - 'attributes' => [ - 'name', - ], - ], - [ - '$collection' => Database::COLLECTION_INDEXES, - '$id' => 'system3', - 'type' => Database::INDEX_UNIQUE, - 'attributes' => [ - 'email', - ], - ], - [ - '$collection' => Database::COLLECTION_INDEXES, - '$id' => 'system4', - 'type' => Database::INDEX_KEY, - 'attributes' => [ - 'email', - ], - ], - ], - ], - ]); - - self::$object->setNamespace($namespace); - self::$object->createNamespace($namespace); - - self::$collection = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ - '$collection' => Database::COLLECTION_COLLECTIONS, - '$permissions' => ['read' => ['*']], - 'name' => 'Tasks', - 'rules' => [ - [ - '$collection' => Database::COLLECTION_RULES, - '$permissions' => ['read' => ['*']], - 'label' => 'Task Name', - 'key' => 'name', - 'type' => Database::VAR_TEXT, - 'default' => '', - 'required' => true, - 'array' => false, - ], - ], - ]); - - self::$init = true; - } - - public function tearDown(): void - { - Authorization::reset(); - } - - public function testCreateCollection() - { - $collection = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ - '$collection' => Database::COLLECTION_COLLECTIONS, - '$permissions' => ['read' => ['*']], - 'name' => 'Create', - ]); - - $this->assertEquals(true, self::$object->createCollection($collection->getId(), [], [])); +// namespace Utopia\Tests; + +// use PDO; +// use Exception; +// use Utopia\Database\Adapter\Relational; +// use Utopia\Database\Database; +// use Utopia\Database\Document; +// use Utopia\Database\Validator\Authorization; +// use PHPUnit\Framework\TestCase; + +// class DatabaseTest extends TestCase +// { +// /** +// * @var bool +// */ +// static protected $init = false; + +// /** +// * @var Database +// */ +// static protected $object = null; + +// /** +// * @var Document +// */ +// static protected $collection = ''; + +// public function setUp(): void +// { +// Authorization::disable(); + +// if(self::$init === true) { +// return; +// } + +// $dbHost = getenv('_APP_DB_HOST'); +// $dbUser = getenv('_APP_DB_USER'); +// $dbPass = getenv('_APP_DB_PASS'); +// $dbScheme = getenv('_APP_DB_SCHEMA'); +// $namespace = 'test_'.uniqid(); + +// $pdo = new PDO("mysql:host={$dbHost};dbname={$dbScheme};charset=utf8mb4", $dbUser, $dbPass, array( +// PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4', +// PDO::ATTR_TIMEOUT => 3, // Seconds +// PDO::ATTR_PERSISTENT => true +// )); + +// // Connection settings +// $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); // Return arrays +// $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // Handle all errors with exceptions + +// self::$object = new Database(); +// self::$object->setAdapter(new Relational($pdo)); + +// self::$object->setMocks([ +// Database::COLLECTION_COLLECTIONS => [ +// '$collection' => Database::COLLECTION_COLLECTIONS, +// '$id' => Database::COLLECTION_COLLECTIONS, +// '$permissions' => ['read' => ['*']], +// 'name' => 'Collections', +// 'rules' => [ +// [ +// '$collection' => Database::COLLECTION_RULES, +// 'label' => 'Name', +// 'key' => 'name', +// 'type' => Database::VAR_TEXT, +// 'default' => '', +// 'required' => true, +// 'array' => false, +// ], +// [ +// '$collection' => Database::COLLECTION_RULES, +// 'label' => 'Date Created', +// 'key' => 'dateCreated', +// 'type' => Database::VAR_INTEGER, +// 'default' => 0, +// 'required' => false, +// 'array' => false, +// ], +// [ +// '$collection' => Database::COLLECTION_RULES, +// 'label' => 'Date Updated', +// 'key' => 'dateUpdated', +// 'type' => Database::VAR_INTEGER, +// 'default' => 0, +// 'required' => false, +// 'array' => false, +// ], +// [ +// '$collection' => Database::COLLECTION_RULES, +// 'label' => 'Rules', +// 'key' => 'rules', +// 'type' => Database::VAR_DOCUMENT, +// 'default' => [], +// 'required' => false, +// 'array' => true, +// 'list' => [Database::COLLECTION_RULES], +// ], +// [ +// '$collection' => Database::COLLECTION_RULES, +// 'label' => 'Indexes', +// 'key' => 'indexes', +// 'type' => Database::VAR_DOCUMENT, +// 'default' => [], +// 'required' => false, +// 'array' => true, +// 'list' => [Database::COLLECTION_INDEXES], +// ], +// ], +// 'indexes' => [ +// [ +// '$collection' => Database::COLLECTION_INDEXES, +// '$id' => 'system1', +// 'type' => Database::INDEX_KEY, +// 'attributes' => [ +// 'name', +// ], +// ], +// [ +// '$collection' => Database::COLLECTION_INDEXES, +// '$id' => 'system2', +// 'type' => Database::INDEX_FULLTEXT, +// 'attributes' => [ +// 'name', +// ], +// ], +// ], +// ], +// Database::COLLECTION_RULES => [ +// '$collection' => Database::COLLECTION_COLLECTIONS, +// '$id' => Database::COLLECTION_RULES, +// '$permissions' => ['read' => ['*']], +// 'name' => 'Collections Rule', +// 'rules' => [ +// [ +// '$collection' => Database::COLLECTION_RULES, +// 'label' => 'Label', +// 'key' => 'label', +// 'type' => Database::VAR_TEXT, +// 'default' => '', +// 'required' => true, +// 'array' => false, +// ], +// [ +// '$collection' => Database::COLLECTION_RULES, +// 'label' => 'Key', +// 'key' => 'key', +// 'type' => Database::VAR_KEY, +// 'default' => '', +// 'required' => true, +// 'array' => false, +// ], +// [ +// '$collection' => Database::COLLECTION_RULES, +// 'label' => 'Type', +// 'key' => 'type', +// 'type' => Database::VAR_TEXT, +// 'default' => '', +// 'required' => true, +// 'array' => false, +// ], +// [ +// '$collection' => Database::COLLECTION_RULES, +// 'label' => 'Default', +// 'key' => 'default', +// 'type' => Database::VAR_TEXT, +// 'default' => '', +// 'required' => false, +// 'array' => false, +// ], +// [ +// '$collection' => Database::COLLECTION_RULES, +// 'label' => 'Required', +// 'key' => 'required', +// 'type' => Database::VAR_BOOLEAN, +// 'default' => true, +// 'required' => true, +// 'array' => false, +// ], +// [ +// '$collection' => Database::COLLECTION_RULES, +// 'label' => 'Array', +// 'key' => 'array', +// 'type' => Database::VAR_BOOLEAN, +// 'default' => true, +// 'required' => true, +// 'array' => false, +// ], +// [ +// '$collection' => Database::COLLECTION_RULES, +// 'label' => 'list', +// 'key' => 'list', +// 'type' => Database::VAR_TEXT, +// //'default' => '', +// 'required' => false, +// 'array' => true, +// ], +// ], +// ], +// Database::COLLECTION_INDEXES => [ +// '$collection' => Database::COLLECTION_COLLECTIONS, +// '$id' => Database::COLLECTION_INDEXES, +// '$permissions' => ['read' => ['*']], +// 'name' => 'Collections Indexes', +// 'rules' => [ +// [ +// '$collection' => Database::COLLECTION_RULES, +// 'label' => 'Type', +// 'key' => 'type', +// 'type' => Database::VAR_TEXT, +// 'default' => '', +// 'required' => true, +// 'array' => false, +// ], +// [ +// '$collection' => Database::COLLECTION_RULES, +// 'label' => 'Attributes', +// 'key' => 'attributes', +// 'type' => Database::VAR_KEY, +// 'default' => '', +// 'required' => true, +// 'array' => true, +// ], +// ], +// ], +// Database::COLLECTION_USERS => [ +// '$collection' => Database::COLLECTION_COLLECTIONS, +// '$id' => Database::COLLECTION_USERS, +// '$permissions' => ['read' => ['*']], +// 'name' => 'User', +// 'rules' => [ +// [ +// '$collection' => Database::COLLECTION_RULES, +// 'label' => 'Name', +// 'key' => 'name', +// 'type' => Database::VAR_TEXT, +// 'default' => '', +// 'required' => false, +// 'array' => false, +// ], +// [ +// '$collection' => Database::COLLECTION_RULES, +// 'label' => 'Email', +// 'key' => 'email', +// 'type' => Database::VAR_EMAIL, +// 'default' => '', +// 'required' => true, +// 'array' => false, +// ], +// [ +// '$collection' => Database::COLLECTION_RULES, +// 'label' => 'Status', +// 'key' => 'status', +// 'type' => Database::VAR_INTEGER, +// 'default' => '', +// 'required' => true, +// 'array' => false, +// ], +// [ +// '$collection' => Database::COLLECTION_RULES, +// 'label' => 'Password', +// 'key' => 'password', +// 'type' => Database::VAR_TEXT, +// 'default' => '', +// 'required' => true, +// 'array' => false, +// ], +// [ +// '$collection' => Database::COLLECTION_RULES, +// 'label' => 'Password Update Date', +// 'key' => 'password-update', +// 'type' => Database::VAR_INTEGER, +// 'default' => '', +// 'required' => true, +// 'array' => false, +// ], +// [ +// '$collection' => Database::COLLECTION_RULES, +// 'label' => 'Prefs', +// 'key' => 'prefs', +// 'type' => Database::VAR_TEXT, +// 'default' => '', +// 'required' => false, +// 'array' => false, +// 'filter' => ['json'] +// ], +// [ +// '$collection' => Database::COLLECTION_RULES, +// 'label' => 'Registration Date', +// 'key' => 'registration', +// 'type' => Database::VAR_INTEGER, +// 'default' => '', +// 'required' => true, +// 'array' => false, +// ], +// [ +// '$collection' => Database::COLLECTION_RULES, +// 'label' => 'Email Verification Status', +// 'key' => 'emailVerification', +// 'type' => Database::VAR_BOOLEAN, +// 'default' => '', +// 'required' => true, +// 'array' => false, +// ], +// [ +// '$collection' => Database::COLLECTION_RULES, +// 'label' => 'Reset', +// 'key' => 'reset', +// 'type' => Database::VAR_BOOLEAN, +// 'default' => '', +// 'required' => true, +// 'array' => false, +// ], +// [ +// '$collection' => Database::COLLECTION_RULES, +// 'label' => 'Tokens', +// 'key' => 'tokens', +// 'type' => Database::VAR_DOCUMENT, +// 'default' => [], +// 'required' => false, +// 'array' => true, +// 'list' => [Database::COLLECTION_TOKENS], +// ], +// [ +// '$collection' => Database::COLLECTION_RULES, +// 'label' => 'Memberships', +// 'key' => 'memberships', +// 'type' => Database::VAR_DOCUMENT, +// 'default' => [], +// 'required' => false, +// 'array' => true, +// 'list' => [Database::COLLECTION_MEMBERSHIPS], +// ], +// ], +// 'indexes' => [ +// [ +// '$collection' => Database::COLLECTION_INDEXES, +// '$id' => 'system1', +// 'type' => Database::INDEX_KEY, +// 'attributes' => [ +// 'name', +// ], +// ], +// [ +// '$collection' => Database::COLLECTION_INDEXES, +// '$id' => 'system2', +// 'type' => Database::INDEX_FULLTEXT, +// 'attributes' => [ +// 'name', +// ], +// ], +// [ +// '$collection' => Database::COLLECTION_INDEXES, +// '$id' => 'system3', +// 'type' => Database::INDEX_UNIQUE, +// 'attributes' => [ +// 'email', +// ], +// ], +// [ +// '$collection' => Database::COLLECTION_INDEXES, +// '$id' => 'system4', +// 'type' => Database::INDEX_KEY, +// 'attributes' => [ +// 'email', +// ], +// ], +// ], +// ], +// ]); + +// self::$object->setNamespace($namespace); +// self::$object->createNamespace($namespace); + +// self::$collection = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ +// '$collection' => Database::COLLECTION_COLLECTIONS, +// '$permissions' => ['read' => ['*']], +// 'name' => 'Tasks', +// 'rules' => [ +// [ +// '$collection' => Database::COLLECTION_RULES, +// '$permissions' => ['read' => ['*']], +// 'label' => 'Task Name', +// 'key' => 'name', +// 'type' => Database::VAR_TEXT, +// 'default' => '', +// 'required' => true, +// 'array' => false, +// ], +// ], +// ]); + +// self::$init = true; +// } + +// public function tearDown(): void +// { +// Authorization::reset(); +// } + +// public function testCreateCollection() +// { +// $collection = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ +// '$collection' => Database::COLLECTION_COLLECTIONS, +// '$permissions' => ['read' => ['*']], +// 'name' => 'Create', +// ]); + +// $this->assertEquals(true, self::$object->createCollection($collection->getId(), [], [])); - try { - self::$object->createCollection($collection->getId(), [], []); - } - catch (\Throwable $th) { - return $this->assertEquals('42S01', $th->getCode()); - } - - throw new Exception('Expected exception'); - } - - public function testDeleteCollection() - { - - $collection = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ - '$collection' => Database::COLLECTION_COLLECTIONS, - '$permissions' => ['read' => ['*']], - 'name' => 'Delete', - ]); - - $this->assertEquals(true, self::$object->createCollection($collection->getId(), [], [])); - $this->assertEquals(true, self::$object->deleteCollection($collection->getId())); +// try { +// self::$object->createCollection($collection->getId(), [], []); +// } +// catch (\Throwable $th) { +// return $this->assertEquals('42S01', $th->getCode()); +// } + +// throw new Exception('Expected exception'); +// } + +// public function testDeleteCollection() +// { + +// $collection = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ +// '$collection' => Database::COLLECTION_COLLECTIONS, +// '$permissions' => ['read' => ['*']], +// 'name' => 'Delete', +// ]); + +// $this->assertEquals(true, self::$object->createCollection($collection->getId(), [], [])); +// $this->assertEquals(true, self::$object->deleteCollection($collection->getId())); - try { - self::$object->deleteCollection($collection->getId()); - } - catch (\Throwable $th) { - return $this->assertEquals('42S02', $th->getCode()); - } - - throw new Exception('Expected exception'); - } - - public function testCreateAttribute() - { - $collection = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ - '$collection' => Database::COLLECTION_COLLECTIONS, - '$permissions' => ['read' => ['*']], - 'name' => 'Create Attribute', - 'rules' => [], - ]); - - $this->assertEquals(true, self::$object->createCollection($collection->getId(), [], [])); - $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'title', Database::VAR_TEXT)); - $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'description', Database::VAR_TEXT)); - $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'numeric', Database::VAR_NUMERIC)); - $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'integer', Database::VAR_INTEGER)); - $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'float', Database::VAR_FLOAT)); - $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'boolean', Database::VAR_BOOLEAN)); - $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'document', Database::VAR_DOCUMENT)); - $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'email', Database::VAR_EMAIL)); - $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'url', Database::VAR_URL)); - $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'ipv4', Database::VAR_IPV4)); - $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'ipv6', Database::VAR_IPV6)); - $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'key', Database::VAR_KEY)); +// try { +// self::$object->deleteCollection($collection->getId()); +// } +// catch (\Throwable $th) { +// return $this->assertEquals('42S02', $th->getCode()); +// } + +// throw new Exception('Expected exception'); +// } + +// public function testCreateAttribute() +// { +// $collection = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ +// '$collection' => Database::COLLECTION_COLLECTIONS, +// '$permissions' => ['read' => ['*']], +// 'name' => 'Create Attribute', +// 'rules' => [], +// ]); + +// $this->assertEquals(true, self::$object->createCollection($collection->getId(), [], [])); +// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'title', Database::VAR_TEXT)); +// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'description', Database::VAR_TEXT)); +// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'numeric', Database::VAR_NUMERIC)); +// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'integer', Database::VAR_INTEGER)); +// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'float', Database::VAR_FLOAT)); +// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'boolean', Database::VAR_BOOLEAN)); +// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'document', Database::VAR_DOCUMENT)); +// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'email', Database::VAR_EMAIL)); +// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'url', Database::VAR_URL)); +// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'ipv4', Database::VAR_IPV4)); +// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'ipv6', Database::VAR_IPV6)); +// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'key', Database::VAR_KEY)); - // arrays - $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'titles', Database::VAR_TEXT, true)); - $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'descriptions', Database::VAR_TEXT, true)); - $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'numerics', Database::VAR_NUMERIC, true)); - $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'integers', Database::VAR_INTEGER, true)); - $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'floats', Database::VAR_FLOAT, true)); - $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'booleans', Database::VAR_BOOLEAN, true)); - $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'documents', Database::VAR_DOCUMENT, true)); - $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'emails', Database::VAR_EMAIL, true)); - $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'urls', Database::VAR_URL, true)); - $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'ipv4s', Database::VAR_IPV4, true)); - $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'ipv6s', Database::VAR_IPV6, true)); - $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'keys', Database::VAR_KEY, true)); - } - - public function testDeleteAttribute() - { - $collection = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ - '$collection' => Database::COLLECTION_COLLECTIONS, - '$permissions' => ['read' => ['*']], - 'name' => 'Delete Attribute', - 'rules' => [], - ]); - - $this->assertEquals(true, self::$object->createCollection($collection->getId(), [], [])); +// // arrays +// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'titles', Database::VAR_TEXT, true)); +// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'descriptions', Database::VAR_TEXT, true)); +// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'numerics', Database::VAR_NUMERIC, true)); +// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'integers', Database::VAR_INTEGER, true)); +// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'floats', Database::VAR_FLOAT, true)); +// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'booleans', Database::VAR_BOOLEAN, true)); +// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'documents', Database::VAR_DOCUMENT, true)); +// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'emails', Database::VAR_EMAIL, true)); +// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'urls', Database::VAR_URL, true)); +// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'ipv4s', Database::VAR_IPV4, true)); +// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'ipv6s', Database::VAR_IPV6, true)); +// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'keys', Database::VAR_KEY, true)); +// } + +// public function testDeleteAttribute() +// { +// $collection = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ +// '$collection' => Database::COLLECTION_COLLECTIONS, +// '$permissions' => ['read' => ['*']], +// 'name' => 'Delete Attribute', +// 'rules' => [], +// ]); + +// $this->assertEquals(true, self::$object->createCollection($collection->getId(), [], [])); - $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'title', Database::VAR_TEXT)); - $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'description', Database::VAR_TEXT)); - $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'value', Database::VAR_NUMERIC)); - - $this->assertEquals(true, self::$object->deleteAttribute($collection->getId(), 'title', false)); - $this->assertEquals(true, self::$object->deleteAttribute($collection->getId(), 'description', false)); - $this->assertEquals(true, self::$object->deleteAttribute($collection->getId(), 'value', false)); - - $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'titles', Database::VAR_TEXT, true)); - $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'descriptions', Database::VAR_TEXT, true)); - $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'values', Database::VAR_NUMERIC, true)); - - $this->assertEquals(true, self::$object->deleteAttribute($collection->getId(), 'titles', true)); - $this->assertEquals(true, self::$object->deleteAttribute($collection->getId(), 'descriptions', true)); - $this->assertEquals(true, self::$object->deleteAttribute($collection->getId(), 'values', true)); - } - - public function testCreateIndex() - { - $collection = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ - '$collection' => Database::COLLECTION_COLLECTIONS, - '$permissions' => ['read' => ['*']], - 'name' => 'Create Index', - 'rules' => [], - ]); - - $this->assertEquals(true, self::$object->createCollection($collection->getId(), [], [])); - $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'title', Database::VAR_TEXT)); - $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'description', Database::VAR_TEXT)); - $this->assertEquals(true, self::$object->createIndex($collection->getId(), 'x', Database::INDEX_KEY, ['title'])); - $this->assertEquals(true, self::$object->createIndex($collection->getId(), 'y', Database::INDEX_KEY, ['description'])); - $this->assertEquals(true, self::$object->createIndex($collection->getId(), 'z', Database::INDEX_KEY, ['title', 'description'])); - } - - public function testDeleteIndex() - { - $collection = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ - '$collection' => Database::COLLECTION_COLLECTIONS, - '$permissions' => ['read' => ['*']], - 'name' => 'Delete Index', - 'rules' => [], - ]); - - $this->assertEquals(true, self::$object->createCollection($collection->getId(), [], [])); - $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'title', Database::VAR_TEXT)); - $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'description', Database::VAR_TEXT)); - $this->assertEquals(true, self::$object->createIndex($collection->getId(), 'x', Database::INDEX_KEY, ['title'])); - $this->assertEquals(true, self::$object->createIndex($collection->getId(), 'y', Database::INDEX_KEY, ['description'])); - $this->assertEquals(true, self::$object->createIndex($collection->getId(), 'z', Database::INDEX_KEY, ['title', 'description'])); +// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'title', Database::VAR_TEXT)); +// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'description', Database::VAR_TEXT)); +// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'value', Database::VAR_NUMERIC)); + +// $this->assertEquals(true, self::$object->deleteAttribute($collection->getId(), 'title', false)); +// $this->assertEquals(true, self::$object->deleteAttribute($collection->getId(), 'description', false)); +// $this->assertEquals(true, self::$object->deleteAttribute($collection->getId(), 'value', false)); + +// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'titles', Database::VAR_TEXT, true)); +// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'descriptions', Database::VAR_TEXT, true)); +// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'values', Database::VAR_NUMERIC, true)); + +// $this->assertEquals(true, self::$object->deleteAttribute($collection->getId(), 'titles', true)); +// $this->assertEquals(true, self::$object->deleteAttribute($collection->getId(), 'descriptions', true)); +// $this->assertEquals(true, self::$object->deleteAttribute($collection->getId(), 'values', true)); +// } + +// public function testCreateIndex() +// { +// $collection = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ +// '$collection' => Database::COLLECTION_COLLECTIONS, +// '$permissions' => ['read' => ['*']], +// 'name' => 'Create Index', +// 'rules' => [], +// ]); + +// $this->assertEquals(true, self::$object->createCollection($collection->getId(), [], [])); +// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'title', Database::VAR_TEXT)); +// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'description', Database::VAR_TEXT)); +// $this->assertEquals(true, self::$object->createIndex($collection->getId(), 'x', Database::INDEX_KEY, ['title'])); +// $this->assertEquals(true, self::$object->createIndex($collection->getId(), 'y', Database::INDEX_KEY, ['description'])); +// $this->assertEquals(true, self::$object->createIndex($collection->getId(), 'z', Database::INDEX_KEY, ['title', 'description'])); +// } + +// public function testDeleteIndex() +// { +// $collection = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ +// '$collection' => Database::COLLECTION_COLLECTIONS, +// '$permissions' => ['read' => ['*']], +// 'name' => 'Delete Index', +// 'rules' => [], +// ]); + +// $this->assertEquals(true, self::$object->createCollection($collection->getId(), [], [])); +// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'title', Database::VAR_TEXT)); +// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'description', Database::VAR_TEXT)); +// $this->assertEquals(true, self::$object->createIndex($collection->getId(), 'x', Database::INDEX_KEY, ['title'])); +// $this->assertEquals(true, self::$object->createIndex($collection->getId(), 'y', Database::INDEX_KEY, ['description'])); +// $this->assertEquals(true, self::$object->createIndex($collection->getId(), 'z', Database::INDEX_KEY, ['title', 'description'])); - $this->assertEquals(true, self::$object->deleteIndex($collection->getId(), 'x')); - $this->assertEquals(true, self::$object->deleteIndex($collection->getId(), 'y')); - $this->assertEquals(true, self::$object->deleteIndex($collection->getId(), 'z')); - } - - public function testCreateDocument() - { - $collection1 = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ - '$collection' => Database::COLLECTION_COLLECTIONS, - '$permissions' => ['read' => ['*']], - 'name' => 'Create Documents', - 'rules' => [ - [ - '$collection' => Database::COLLECTION_RULES, - '$permissions' => ['read' => ['*']], - 'label' => 'Name', - 'key' => 'name', - 'type' => Database::VAR_TEXT, - 'default' => '', - 'required' => true, - 'array' => false, - ], - [ - '$collection' => Database::COLLECTION_RULES, - '$permissions' => ['read' => ['*']], - 'label' => 'Links', - 'key' => 'links', - 'type' => Database::VAR_URL, - 'default' => '', - 'required' => true, - 'array' => true, - ], - ] - ]); - - $this->assertEquals(true, self::$object->createCollection($collection1->getId(), [], [])); +// $this->assertEquals(true, self::$object->deleteIndex($collection->getId(), 'x')); +// $this->assertEquals(true, self::$object->deleteIndex($collection->getId(), 'y')); +// $this->assertEquals(true, self::$object->deleteIndex($collection->getId(), 'z')); +// } + +// public function testCreateDocument() +// { +// $collection1 = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ +// '$collection' => Database::COLLECTION_COLLECTIONS, +// '$permissions' => ['read' => ['*']], +// 'name' => 'Create Documents', +// 'rules' => [ +// [ +// '$collection' => Database::COLLECTION_RULES, +// '$permissions' => ['read' => ['*']], +// 'label' => 'Name', +// 'key' => 'name', +// 'type' => Database::VAR_TEXT, +// 'default' => '', +// 'required' => true, +// 'array' => false, +// ], +// [ +// '$collection' => Database::COLLECTION_RULES, +// '$permissions' => ['read' => ['*']], +// 'label' => 'Links', +// 'key' => 'links', +// 'type' => Database::VAR_URL, +// 'default' => '', +// 'required' => true, +// 'array' => true, +// ], +// ] +// ]); + +// $this->assertEquals(true, self::$object->createCollection($collection1->getId(), [], [])); - $document0 = new Document([ - '$collection' => $collection1->getId(), - '$permissions' => [ - 'read' => ['*'], - 'write' => ['user:123'], - ], - 'name' => 'Task #0', - 'links' => [ - 'http://example.com/link-1', - 'http://example.com/link-2', - 'http://example.com/link-3', - 'http://example.com/link-4', - ], - ]); +// $document0 = new Document([ +// '$collection' => $collection1->getId(), +// '$permissions' => [ +// 'read' => ['*'], +// 'write' => ['user:123'], +// ], +// 'name' => 'Task #0', +// 'links' => [ +// 'http://example.com/link-1', +// 'http://example.com/link-2', +// 'http://example.com/link-3', +// 'http://example.com/link-4', +// ], +// ]); - $document1 = self::$object->createDocument($collection1->getId(), [ - '$collection' => $collection1->getId(), - '$permissions' => [ - 'read' => ['*'], - 'write' => ['user:123'], - ], - 'name' => 'Task #1️⃣', - 'links' => [ - 'http://example.com/link-5', - 'http://example.com/link-6', - 'http://example.com/link-7', - 'http://example.com/link-8', - ], - ]); - - $this->assertNotEmpty($document1->getId()); - $this->assertIsArray($document1->getPermissions()); - $this->assertArrayHasKey('read', $document1->getPermissions()); - $this->assertArrayHasKey('write', $document1->getPermissions()); - $this->assertEquals('Task #1️⃣', $document1->getAttribute('name')); - $this->assertCount(4, $document1->getAttribute('links')); - $this->assertEquals('http://example.com/link-5', $document1->getAttribute('links')[0]); - $this->assertEquals('http://example.com/link-6', $document1->getAttribute('links')[1]); - $this->assertEquals('http://example.com/link-7', $document1->getAttribute('links')[2]); - $this->assertEquals('http://example.com/link-8', $document1->getAttribute('links')[3]); - - $document2 = self::$object->createDocument(Database::COLLECTION_USERS, [ - '$collection' => Database::COLLECTION_USERS, - '$permissions' => [ - 'read' => ['*'], - 'write' => ['user:123'], - ], - 'email' => 'test@appwrite.io', - 'emailVerification' => false, - 'status' => 0, - 'password' => 'secrethash', - 'password-update' => \time(), - 'registration' => \time(), - 'reset' => false, - 'name' => 'Test', - ]); - - $this->assertNotEmpty($document2->getId()); - $this->assertIsArray($document2->getPermissions()); - $this->assertArrayHasKey('read', $document2->getPermissions()); - $this->assertArrayHasKey('write', $document2->getPermissions()); - $this->assertEquals('test@appwrite.io', $document2->getAttribute('email')); - $this->assertIsString($document2->getAttribute('email')); - $this->assertEquals(0, $document2->getAttribute('status')); - $this->assertIsInt($document2->getAttribute('status')); - $this->assertEquals(false, $document2->getAttribute('emailVerification')); - $this->assertIsBool($document2->getAttribute('emailVerification')); - - $document2 = self::$object->getDocument(Database::COLLECTION_USERS, $document2->getId()); - - $this->assertNotEmpty($document2->getId()); - $this->assertIsArray($document2->getPermissions()); - $this->assertArrayHasKey('read', $document2->getPermissions()); - $this->assertArrayHasKey('write', $document2->getPermissions()); - $this->assertEquals('test@appwrite.io', $document2->getAttribute('email')); - $this->assertIsString($document2->getAttribute('email')); - $this->assertEquals(0, $document2->getAttribute('status')); - $this->assertIsInt($document2->getAttribute('status')); - $this->assertEquals(false, $document2->getAttribute('emailVerification')); - $this->assertIsBool($document2->getAttribute('emailVerification')); - - $types = [ - Database::VAR_TEXT, - Database::VAR_INTEGER, - Database::VAR_FLOAT, - Database::VAR_NUMERIC, - Database::VAR_BOOLEAN, - Database::VAR_DOCUMENT, - Database::VAR_EMAIL, - Database::VAR_URL, - Database::VAR_IPV4, - Database::VAR_IPV6, - Database::VAR_KEY, - ]; - - $rules = []; - - foreach($types as $type) { - $rules[] = [ - '$collection' => Database::COLLECTION_RULES, - '$permissions' => ['read' => ['*']], - 'label' => ucfirst($type), - 'key' => $type, - 'type' => $type, - 'default' => null, - 'required' => true, - 'array' => false, - 'list' => ($type === Database::VAR_DOCUMENT) ? [$collection1->getId()] : [], - ]; - - $rules[] = [ - '$collection' => Database::COLLECTION_RULES, - '$permissions' => ['read' => ['*']], - 'label' => ucfirst($type), - 'key' => $type.'s', - 'type' => $type, - 'default' => null, - 'required' => true, - 'array' => true, - 'list' => ($type === Database::VAR_DOCUMENT) ? [$collection1->getId()] : [], - ]; - } - - $rules[] = [ - '$collection' => Database::COLLECTION_RULES, - '$permissions' => ['read' => ['*']], - 'label' => 'document2', - 'key' => 'document2', - 'type' => Database::VAR_DOCUMENT, - 'default' => null, - 'required' => true, - 'array' => false, - 'list' => [$collection1->getId()], - ]; - - $rules[] = [ - '$collection' => Database::COLLECTION_RULES, - '$permissions' => ['read' => ['*']], - 'label' => 'documents2', - 'key' => 'documents2', - 'type' => Database::VAR_DOCUMENT, - 'default' => null, - 'required' => true, - 'array' => true, - 'list' => [$collection1->getId()], - ]; - - $collection2 = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ - '$collection' => Database::COLLECTION_COLLECTIONS, - '$permissions' => ['read' => ['*']], - 'name' => 'Create Documents', - 'rules' => $rules, - ]); - - $this->assertEquals(true, self::$object->createCollection($collection2->getId(), [], [])); +// $document1 = self::$object->createDocument($collection1->getId(), [ +// '$collection' => $collection1->getId(), +// '$permissions' => [ +// 'read' => ['*'], +// 'write' => ['user:123'], +// ], +// 'name' => 'Task #1️⃣', +// 'links' => [ +// 'http://example.com/link-5', +// 'http://example.com/link-6', +// 'http://example.com/link-7', +// 'http://example.com/link-8', +// ], +// ]); + +// $this->assertNotEmpty($document1->getId()); +// $this->assertIsArray($document1->getPermissions()); +// $this->assertArrayHasKey('read', $document1->getPermissions()); +// $this->assertArrayHasKey('write', $document1->getPermissions()); +// $this->assertEquals('Task #1️⃣', $document1->getAttribute('name')); +// $this->assertCount(4, $document1->getAttribute('links')); +// $this->assertEquals('http://example.com/link-5', $document1->getAttribute('links')[0]); +// $this->assertEquals('http://example.com/link-6', $document1->getAttribute('links')[1]); +// $this->assertEquals('http://example.com/link-7', $document1->getAttribute('links')[2]); +// $this->assertEquals('http://example.com/link-8', $document1->getAttribute('links')[3]); + +// $document2 = self::$object->createDocument(Database::COLLECTION_USERS, [ +// '$collection' => Database::COLLECTION_USERS, +// '$permissions' => [ +// 'read' => ['*'], +// 'write' => ['user:123'], +// ], +// 'email' => 'test@appwrite.io', +// 'emailVerification' => false, +// 'status' => 0, +// 'password' => 'secrethash', +// 'password-update' => \time(), +// 'registration' => \time(), +// 'reset' => false, +// 'name' => 'Test', +// ]); + +// $this->assertNotEmpty($document2->getId()); +// $this->assertIsArray($document2->getPermissions()); +// $this->assertArrayHasKey('read', $document2->getPermissions()); +// $this->assertArrayHasKey('write', $document2->getPermissions()); +// $this->assertEquals('test@appwrite.io', $document2->getAttribute('email')); +// $this->assertIsString($document2->getAttribute('email')); +// $this->assertEquals(0, $document2->getAttribute('status')); +// $this->assertIsInt($document2->getAttribute('status')); +// $this->assertEquals(false, $document2->getAttribute('emailVerification')); +// $this->assertIsBool($document2->getAttribute('emailVerification')); + +// $document2 = self::$object->getDocument(Database::COLLECTION_USERS, $document2->getId()); + +// $this->assertNotEmpty($document2->getId()); +// $this->assertIsArray($document2->getPermissions()); +// $this->assertArrayHasKey('read', $document2->getPermissions()); +// $this->assertArrayHasKey('write', $document2->getPermissions()); +// $this->assertEquals('test@appwrite.io', $document2->getAttribute('email')); +// $this->assertIsString($document2->getAttribute('email')); +// $this->assertEquals(0, $document2->getAttribute('status')); +// $this->assertIsInt($document2->getAttribute('status')); +// $this->assertEquals(false, $document2->getAttribute('emailVerification')); +// $this->assertIsBool($document2->getAttribute('emailVerification')); + +// $types = [ +// Database::VAR_TEXT, +// Database::VAR_INTEGER, +// Database::VAR_FLOAT, +// Database::VAR_NUMERIC, +// Database::VAR_BOOLEAN, +// Database::VAR_DOCUMENT, +// Database::VAR_EMAIL, +// Database::VAR_URL, +// Database::VAR_IPV4, +// Database::VAR_IPV6, +// Database::VAR_KEY, +// ]; + +// $rules = []; + +// foreach($types as $type) { +// $rules[] = [ +// '$collection' => Database::COLLECTION_RULES, +// '$permissions' => ['read' => ['*']], +// 'label' => ucfirst($type), +// 'key' => $type, +// 'type' => $type, +// 'default' => null, +// 'required' => true, +// 'array' => false, +// 'list' => ($type === Database::VAR_DOCUMENT) ? [$collection1->getId()] : [], +// ]; + +// $rules[] = [ +// '$collection' => Database::COLLECTION_RULES, +// '$permissions' => ['read' => ['*']], +// 'label' => ucfirst($type), +// 'key' => $type.'s', +// 'type' => $type, +// 'default' => null, +// 'required' => true, +// 'array' => true, +// 'list' => ($type === Database::VAR_DOCUMENT) ? [$collection1->getId()] : [], +// ]; +// } + +// $rules[] = [ +// '$collection' => Database::COLLECTION_RULES, +// '$permissions' => ['read' => ['*']], +// 'label' => 'document2', +// 'key' => 'document2', +// 'type' => Database::VAR_DOCUMENT, +// 'default' => null, +// 'required' => true, +// 'array' => false, +// 'list' => [$collection1->getId()], +// ]; + +// $rules[] = [ +// '$collection' => Database::COLLECTION_RULES, +// '$permissions' => ['read' => ['*']], +// 'label' => 'documents2', +// 'key' => 'documents2', +// 'type' => Database::VAR_DOCUMENT, +// 'default' => null, +// 'required' => true, +// 'array' => true, +// 'list' => [$collection1->getId()], +// ]; + +// $collection2 = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ +// '$collection' => Database::COLLECTION_COLLECTIONS, +// '$permissions' => ['read' => ['*']], +// 'name' => 'Create Documents', +// 'rules' => $rules, +// ]); + +// $this->assertEquals(true, self::$object->createCollection($collection2->getId(), [], [])); - $document3 = self::$object->createDocument($collection2->getId(), [ - '$collection' => $collection2->getId(), - '$permissions' => [ - 'read' => ['*'], - 'write' => ['user:123'], - ], - 'text' => 'Hello World', - 'texts' => ['Hello World 1', 'Hello World 2'], - // 'document' => $document0, - // 'documents' => [$document0], - 'document' => $document0, - 'documents' => [$document1, $document0], - 'document2' => $document1, - 'documents2' => [$document0, $document1], - 'integer' => 1, - 'integers' => [5, 3, 4], - 'float' => 2.22, - 'floats' => [1.13, 4.33, 8.9999], - 'numeric' => 1, - 'numerics' => [1, 5, 7.77], - 'boolean' => true, - 'booleans' => [true, false, true], - 'email' => 'test@appwrite.io', - 'emails' => [ - 'test4@appwrite.io', - 'test3@appwrite.io', - 'test2@appwrite.io', - 'test1@appwrite.io' - ], - 'url' => 'http://example.com/welcome', - 'urls' => [ - 'http://example.com/welcome-1', - 'http://example.com/welcome-2', - 'http://example.com/welcome-3' - ], - 'ipv4' => '172.16.254.1', - 'ipv4s' => [ - '172.16.254.1', - '172.16.254.5' - ], - 'ipv6' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334', - 'ipv6s' => [ - '2001:0db8:85a3:0000:0000:8a2e:0370:7334', - '2001:0db8:85a3:0000:0000:8a2e:0370:7337' - ], - 'key' => uniqid(), - 'keys' => [uniqid(), uniqid(), uniqid()], - ]); - - $document3 = self::$object->getDocument($collection2->getId(), $document3->getId()); - - $this->assertIsString($document3->getId()); - $this->assertIsString($document3->getCollection()); - $this->assertEquals([ - 'read' => ['*'], - 'write' => ['user:123'], - ], $document3->getPermissions()); - $this->assertEquals('Hello World', $document3->getAttribute('text')); - $this->assertCount(2, $document3->getAttribute('texts')); +// $document3 = self::$object->createDocument($collection2->getId(), [ +// '$collection' => $collection2->getId(), +// '$permissions' => [ +// 'read' => ['*'], +// 'write' => ['user:123'], +// ], +// 'text' => 'Hello World', +// 'texts' => ['Hello World 1', 'Hello World 2'], +// // 'document' => $document0, +// // 'documents' => [$document0], +// 'document' => $document0, +// 'documents' => [$document1, $document0], +// 'document2' => $document1, +// 'documents2' => [$document0, $document1], +// 'integer' => 1, +// 'integers' => [5, 3, 4], +// 'float' => 2.22, +// 'floats' => [1.13, 4.33, 8.9999], +// 'numeric' => 1, +// 'numerics' => [1, 5, 7.77], +// 'boolean' => true, +// 'booleans' => [true, false, true], +// 'email' => 'test@appwrite.io', +// 'emails' => [ +// 'test4@appwrite.io', +// 'test3@appwrite.io', +// 'test2@appwrite.io', +// 'test1@appwrite.io' +// ], +// 'url' => 'http://example.com/welcome', +// 'urls' => [ +// 'http://example.com/welcome-1', +// 'http://example.com/welcome-2', +// 'http://example.com/welcome-3' +// ], +// 'ipv4' => '172.16.254.1', +// 'ipv4s' => [ +// '172.16.254.1', +// '172.16.254.5' +// ], +// 'ipv6' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334', +// 'ipv6s' => [ +// '2001:0db8:85a3:0000:0000:8a2e:0370:7334', +// '2001:0db8:85a3:0000:0000:8a2e:0370:7337' +// ], +// 'key' => uniqid(), +// 'keys' => [uniqid(), uniqid(), uniqid()], +// ]); + +// $document3 = self::$object->getDocument($collection2->getId(), $document3->getId()); + +// $this->assertIsString($document3->getId()); +// $this->assertIsString($document3->getCollection()); +// $this->assertEquals([ +// 'read' => ['*'], +// 'write' => ['user:123'], +// ], $document3->getPermissions()); +// $this->assertEquals('Hello World', $document3->getAttribute('text')); +// $this->assertCount(2, $document3->getAttribute('texts')); - $this->assertIsString($document3->getAttribute('text')); - $this->assertEquals('Hello World', $document3->getAttribute('text')); - $this->assertEquals(['Hello World 1', 'Hello World 2'], $document3->getAttribute('texts')); - $this->assertCount(2, $document3->getAttribute('texts')); +// $this->assertIsString($document3->getAttribute('text')); +// $this->assertEquals('Hello World', $document3->getAttribute('text')); +// $this->assertEquals(['Hello World 1', 'Hello World 2'], $document3->getAttribute('texts')); +// $this->assertCount(2, $document3->getAttribute('texts')); - $this->assertInstanceOf(Document::class, $document3->getAttribute('document')); - $this->assertIsString($document3->getAttribute('document')->getId()); - $this->assertNotEmpty($document3->getAttribute('document')->getId()); - $this->assertIsArray($document3->getAttribute('document')->getPermissions()); - $this->assertArrayHasKey('read', $document3->getAttribute('document')->getPermissions()); - $this->assertArrayHasKey('write', $document3->getAttribute('document')->getPermissions()); - $this->assertEquals('Task #0', $document3->getAttribute('document')->getAttribute('name')); - $this->assertCount(4, $document3->getAttribute('document')->getAttribute('links')); - $this->assertEquals('http://example.com/link-1', $document3->getAttribute('document')->getAttribute('links')[0]); - $this->assertEquals('http://example.com/link-2', $document3->getAttribute('document')->getAttribute('links')[1]); - $this->assertEquals('http://example.com/link-3', $document3->getAttribute('document')->getAttribute('links')[2]); - $this->assertEquals('http://example.com/link-4', $document3->getAttribute('document')->getAttribute('links')[3]); +// $this->assertInstanceOf(Document::class, $document3->getAttribute('document')); +// $this->assertIsString($document3->getAttribute('document')->getId()); +// $this->assertNotEmpty($document3->getAttribute('document')->getId()); +// $this->assertIsArray($document3->getAttribute('document')->getPermissions()); +// $this->assertArrayHasKey('read', $document3->getAttribute('document')->getPermissions()); +// $this->assertArrayHasKey('write', $document3->getAttribute('document')->getPermissions()); +// $this->assertEquals('Task #0', $document3->getAttribute('document')->getAttribute('name')); +// $this->assertCount(4, $document3->getAttribute('document')->getAttribute('links')); +// $this->assertEquals('http://example.com/link-1', $document3->getAttribute('document')->getAttribute('links')[0]); +// $this->assertEquals('http://example.com/link-2', $document3->getAttribute('document')->getAttribute('links')[1]); +// $this->assertEquals('http://example.com/link-3', $document3->getAttribute('document')->getAttribute('links')[2]); +// $this->assertEquals('http://example.com/link-4', $document3->getAttribute('document')->getAttribute('links')[3]); - $this->assertInstanceOf(Document::class, $document3->getAttribute('documents')[0]); - $this->assertIsString($document3->getAttribute('documents')[0]->getId()); - $this->assertNotEmpty($document3->getAttribute('documents')[0]->getId()); - $this->assertIsArray($document3->getAttribute('documents')[0]->getPermissions()); - $this->assertArrayHasKey('read', $document3->getAttribute('documents')[0]->getPermissions()); - $this->assertArrayHasKey('write', $document3->getAttribute('documents')[0]->getPermissions()); - $this->assertEquals('Task #1️⃣', $document3->getAttribute('documents')[0]->getAttribute('name')); - $this->assertCount(4, $document3->getAttribute('documents')[0]->getAttribute('links')); - $this->assertEquals('http://example.com/link-5', $document3->getAttribute('documents')[0]->getAttribute('links')[0]); - $this->assertEquals('http://example.com/link-6', $document3->getAttribute('documents')[0]->getAttribute('links')[1]); - $this->assertEquals('http://example.com/link-7', $document3->getAttribute('documents')[0]->getAttribute('links')[2]); - $this->assertEquals('http://example.com/link-8', $document3->getAttribute('documents')[0]->getAttribute('links')[3]); +// $this->assertInstanceOf(Document::class, $document3->getAttribute('documents')[0]); +// $this->assertIsString($document3->getAttribute('documents')[0]->getId()); +// $this->assertNotEmpty($document3->getAttribute('documents')[0]->getId()); +// $this->assertIsArray($document3->getAttribute('documents')[0]->getPermissions()); +// $this->assertArrayHasKey('read', $document3->getAttribute('documents')[0]->getPermissions()); +// $this->assertArrayHasKey('write', $document3->getAttribute('documents')[0]->getPermissions()); +// $this->assertEquals('Task #1️⃣', $document3->getAttribute('documents')[0]->getAttribute('name')); +// $this->assertCount(4, $document3->getAttribute('documents')[0]->getAttribute('links')); +// $this->assertEquals('http://example.com/link-5', $document3->getAttribute('documents')[0]->getAttribute('links')[0]); +// $this->assertEquals('http://example.com/link-6', $document3->getAttribute('documents')[0]->getAttribute('links')[1]); +// $this->assertEquals('http://example.com/link-7', $document3->getAttribute('documents')[0]->getAttribute('links')[2]); +// $this->assertEquals('http://example.com/link-8', $document3->getAttribute('documents')[0]->getAttribute('links')[3]); - $this->assertInstanceOf(Document::class, $document3->getAttribute('documents')[1]); - $this->assertIsString($document3->getAttribute('documents')[1]->getId()); - $this->assertNotEmpty($document3->getAttribute('documents')[1]->getId()); - $this->assertIsArray($document3->getAttribute('documents')[1]->getPermissions()); - $this->assertArrayHasKey('read', $document3->getAttribute('documents')[1]->getPermissions()); - $this->assertArrayHasKey('write', $document3->getAttribute('documents')[1]->getPermissions()); - $this->assertEquals('Task #0', $document3->getAttribute('documents')[1]->getAttribute('name')); - $this->assertCount(4, $document3->getAttribute('documents')[1]->getAttribute('links')); - $this->assertEquals('http://example.com/link-1', $document3->getAttribute('documents')[1]->getAttribute('links')[0]); - $this->assertEquals('http://example.com/link-2', $document3->getAttribute('documents')[1]->getAttribute('links')[1]); - $this->assertEquals('http://example.com/link-3', $document3->getAttribute('documents')[1]->getAttribute('links')[2]); - $this->assertEquals('http://example.com/link-4', $document3->getAttribute('documents')[1]->getAttribute('links')[3]); - - $this->assertInstanceOf(Document::class, $document3->getAttribute('document2')); - $this->assertIsString($document3->getAttribute('document2')->getId()); - $this->assertNotEmpty($document3->getAttribute('document2')->getId()); - $this->assertIsArray($document3->getAttribute('document2')->getPermissions()); - $this->assertArrayHasKey('read', $document3->getAttribute('document2')->getPermissions()); - $this->assertArrayHasKey('write', $document3->getAttribute('document2')->getPermissions()); - $this->assertEquals('Task #1️⃣', $document3->getAttribute('document2')->getAttribute('name')); - $this->assertCount(4, $document3->getAttribute('document2')->getAttribute('links')); - $this->assertEquals('http://example.com/link-5', $document3->getAttribute('document2')->getAttribute('links')[0]); - $this->assertEquals('http://example.com/link-6', $document3->getAttribute('document2')->getAttribute('links')[1]); - $this->assertEquals('http://example.com/link-7', $document3->getAttribute('document2')->getAttribute('links')[2]); - $this->assertEquals('http://example.com/link-8', $document3->getAttribute('document2')->getAttribute('links')[3]); - - $this->assertInstanceOf(Document::class, $document3->getAttribute('documents2')[0]); - $this->assertIsString($document3->getAttribute('documents2')[0]->getId()); - $this->assertNotEmpty($document3->getAttribute('documents2')[0]->getId()); - $this->assertIsArray($document3->getAttribute('documents2')[0]->getPermissions()); - $this->assertArrayHasKey('read', $document3->getAttribute('documents2')[0]->getPermissions()); - $this->assertArrayHasKey('write', $document3->getAttribute('documents2')[0]->getPermissions()); - $this->assertEquals('Task #0', $document3->getAttribute('documents2')[0]->getAttribute('name')); - $this->assertCount(4, $document3->getAttribute('documents2')[0]->getAttribute('links')); - $this->assertEquals('http://example.com/link-1', $document3->getAttribute('documents2')[0]->getAttribute('links')[0]); - $this->assertEquals('http://example.com/link-2', $document3->getAttribute('documents2')[0]->getAttribute('links')[1]); - $this->assertEquals('http://example.com/link-3', $document3->getAttribute('documents2')[0]->getAttribute('links')[2]); - $this->assertEquals('http://example.com/link-4', $document3->getAttribute('documents2')[0]->getAttribute('links')[3]); - - $this->assertInstanceOf(Document::class, $document3->getAttribute('documents2')[1]); - $this->assertIsString($document3->getAttribute('documents2')[1]->getId()); - $this->assertNotEmpty($document3->getAttribute('documents2')[1]->getId()); - $this->assertIsArray($document3->getAttribute('documents2')[1]->getPermissions()); - $this->assertArrayHasKey('read', $document3->getAttribute('documents2')[1]->getPermissions()); - $this->assertArrayHasKey('write', $document3->getAttribute('documents2')[1]->getPermissions()); - $this->assertEquals('Task #1️⃣', $document3->getAttribute('documents2')[1]->getAttribute('name')); - $this->assertCount(4, $document3->getAttribute('documents2')[1]->getAttribute('links')); - $this->assertEquals('http://example.com/link-5', $document3->getAttribute('documents2')[1]->getAttribute('links')[0]); - $this->assertEquals('http://example.com/link-6', $document3->getAttribute('documents2')[1]->getAttribute('links')[1]); - $this->assertEquals('http://example.com/link-7', $document3->getAttribute('documents2')[1]->getAttribute('links')[2]); - $this->assertEquals('http://example.com/link-8', $document3->getAttribute('documents2')[1]->getAttribute('links')[3]); +// $this->assertInstanceOf(Document::class, $document3->getAttribute('documents')[1]); +// $this->assertIsString($document3->getAttribute('documents')[1]->getId()); +// $this->assertNotEmpty($document3->getAttribute('documents')[1]->getId()); +// $this->assertIsArray($document3->getAttribute('documents')[1]->getPermissions()); +// $this->assertArrayHasKey('read', $document3->getAttribute('documents')[1]->getPermissions()); +// $this->assertArrayHasKey('write', $document3->getAttribute('documents')[1]->getPermissions()); +// $this->assertEquals('Task #0', $document3->getAttribute('documents')[1]->getAttribute('name')); +// $this->assertCount(4, $document3->getAttribute('documents')[1]->getAttribute('links')); +// $this->assertEquals('http://example.com/link-1', $document3->getAttribute('documents')[1]->getAttribute('links')[0]); +// $this->assertEquals('http://example.com/link-2', $document3->getAttribute('documents')[1]->getAttribute('links')[1]); +// $this->assertEquals('http://example.com/link-3', $document3->getAttribute('documents')[1]->getAttribute('links')[2]); +// $this->assertEquals('http://example.com/link-4', $document3->getAttribute('documents')[1]->getAttribute('links')[3]); + +// $this->assertInstanceOf(Document::class, $document3->getAttribute('document2')); +// $this->assertIsString($document3->getAttribute('document2')->getId()); +// $this->assertNotEmpty($document3->getAttribute('document2')->getId()); +// $this->assertIsArray($document3->getAttribute('document2')->getPermissions()); +// $this->assertArrayHasKey('read', $document3->getAttribute('document2')->getPermissions()); +// $this->assertArrayHasKey('write', $document3->getAttribute('document2')->getPermissions()); +// $this->assertEquals('Task #1️⃣', $document3->getAttribute('document2')->getAttribute('name')); +// $this->assertCount(4, $document3->getAttribute('document2')->getAttribute('links')); +// $this->assertEquals('http://example.com/link-5', $document3->getAttribute('document2')->getAttribute('links')[0]); +// $this->assertEquals('http://example.com/link-6', $document3->getAttribute('document2')->getAttribute('links')[1]); +// $this->assertEquals('http://example.com/link-7', $document3->getAttribute('document2')->getAttribute('links')[2]); +// $this->assertEquals('http://example.com/link-8', $document3->getAttribute('document2')->getAttribute('links')[3]); + +// $this->assertInstanceOf(Document::class, $document3->getAttribute('documents2')[0]); +// $this->assertIsString($document3->getAttribute('documents2')[0]->getId()); +// $this->assertNotEmpty($document3->getAttribute('documents2')[0]->getId()); +// $this->assertIsArray($document3->getAttribute('documents2')[0]->getPermissions()); +// $this->assertArrayHasKey('read', $document3->getAttribute('documents2')[0]->getPermissions()); +// $this->assertArrayHasKey('write', $document3->getAttribute('documents2')[0]->getPermissions()); +// $this->assertEquals('Task #0', $document3->getAttribute('documents2')[0]->getAttribute('name')); +// $this->assertCount(4, $document3->getAttribute('documents2')[0]->getAttribute('links')); +// $this->assertEquals('http://example.com/link-1', $document3->getAttribute('documents2')[0]->getAttribute('links')[0]); +// $this->assertEquals('http://example.com/link-2', $document3->getAttribute('documents2')[0]->getAttribute('links')[1]); +// $this->assertEquals('http://example.com/link-3', $document3->getAttribute('documents2')[0]->getAttribute('links')[2]); +// $this->assertEquals('http://example.com/link-4', $document3->getAttribute('documents2')[0]->getAttribute('links')[3]); + +// $this->assertInstanceOf(Document::class, $document3->getAttribute('documents2')[1]); +// $this->assertIsString($document3->getAttribute('documents2')[1]->getId()); +// $this->assertNotEmpty($document3->getAttribute('documents2')[1]->getId()); +// $this->assertIsArray($document3->getAttribute('documents2')[1]->getPermissions()); +// $this->assertArrayHasKey('read', $document3->getAttribute('documents2')[1]->getPermissions()); +// $this->assertArrayHasKey('write', $document3->getAttribute('documents2')[1]->getPermissions()); +// $this->assertEquals('Task #1️⃣', $document3->getAttribute('documents2')[1]->getAttribute('name')); +// $this->assertCount(4, $document3->getAttribute('documents2')[1]->getAttribute('links')); +// $this->assertEquals('http://example.com/link-5', $document3->getAttribute('documents2')[1]->getAttribute('links')[0]); +// $this->assertEquals('http://example.com/link-6', $document3->getAttribute('documents2')[1]->getAttribute('links')[1]); +// $this->assertEquals('http://example.com/link-7', $document3->getAttribute('documents2')[1]->getAttribute('links')[2]); +// $this->assertEquals('http://example.com/link-8', $document3->getAttribute('documents2')[1]->getAttribute('links')[3]); - $this->assertIsInt($document3->getAttribute('integer')); - $this->assertEquals(1, $document3->getAttribute('integer')); - $this->assertIsInt($document3->getAttribute('integers')[0]); - $this->assertIsInt($document3->getAttribute('integers')[1]); - $this->assertIsInt($document3->getAttribute('integers')[2]); - $this->assertEquals([5, 3, 4], $document3->getAttribute('integers')); - $this->assertCount(3, $document3->getAttribute('integers')); - - $this->assertIsFloat($document3->getAttribute('float')); - $this->assertEquals(2.22, $document3->getAttribute('float')); - $this->assertIsFloat($document3->getAttribute('floats')[0]); - $this->assertIsFloat($document3->getAttribute('floats')[1]); - $this->assertIsFloat($document3->getAttribute('floats')[2]); - $this->assertEquals([1.13, 4.33, 8.9999], $document3->getAttribute('floats')); - $this->assertCount(3, $document3->getAttribute('floats')); - - $this->assertIsBool($document3->getAttribute('boolean')); - $this->assertEquals(true, $document3->getAttribute('boolean')); - $this->assertIsBool($document3->getAttribute('booleans')[0]); - $this->assertIsBool($document3->getAttribute('booleans')[1]); - $this->assertIsBool($document3->getAttribute('booleans')[2]); - $this->assertEquals([true, false, true], $document3->getAttribute('booleans')); - $this->assertCount(3, $document3->getAttribute('booleans')); - - $this->assertIsString($document3->getAttribute('email')); - $this->assertEquals('test@appwrite.io', $document3->getAttribute('email')); - $this->assertIsString($document3->getAttribute('emails')[0]); - $this->assertIsString($document3->getAttribute('emails')[1]); - $this->assertIsString($document3->getAttribute('emails')[2]); - $this->assertIsString($document3->getAttribute('emails')[3]); - $this->assertEquals([ - 'test4@appwrite.io', - 'test3@appwrite.io', - 'test2@appwrite.io', - 'test1@appwrite.io' - ], $document3->getAttribute('emails')); - $this->assertCount(4, $document3->getAttribute('emails')); - - $this->assertIsString($document3->getAttribute('url')); - $this->assertEquals('http://example.com/welcome', $document3->getAttribute('url')); - $this->assertIsString($document3->getAttribute('urls')[0]); - $this->assertIsString($document3->getAttribute('urls')[1]); - $this->assertIsString($document3->getAttribute('urls')[2]); - $this->assertEquals([ - 'http://example.com/welcome-1', - 'http://example.com/welcome-2', - 'http://example.com/welcome-3' - ], $document3->getAttribute('urls')); - $this->assertCount(3, $document3->getAttribute('urls')); - - $this->assertIsString($document3->getAttribute('ipv4')); - $this->assertEquals('172.16.254.1', $document3->getAttribute('ipv4')); - $this->assertIsString($document3->getAttribute('ipv4s')[0]); - $this->assertIsString($document3->getAttribute('ipv4s')[1]); - $this->assertEquals([ - '172.16.254.1', - '172.16.254.5' - ], $document3->getAttribute('ipv4s')); - $this->assertCount(2, $document3->getAttribute('ipv4s')); - - $this->assertIsString($document3->getAttribute('ipv6')); - $this->assertEquals('2001:0db8:85a3:0000:0000:8a2e:0370:7334', $document3->getAttribute('ipv6')); - $this->assertIsString($document3->getAttribute('ipv6s')[0]); - $this->assertIsString($document3->getAttribute('ipv6s')[1]); - $this->assertEquals([ - '2001:0db8:85a3:0000:0000:8a2e:0370:7334', - '2001:0db8:85a3:0000:0000:8a2e:0370:7337' - ], $document3->getAttribute('ipv6s')); - $this->assertCount(2, $document3->getAttribute('ipv6s')); - - $this->assertEquals('2001:0db8:85a3:0000:0000:8a2e:0370:7334', $document3->getAttribute('ipv6')); - $this->assertEquals([ - '2001:0db8:85a3:0000:0000:8a2e:0370:7334', - '2001:0db8:85a3:0000:0000:8a2e:0370:7337' - ], $document3->getAttribute('ipv6s')); - $this->assertCount(2, $document3->getAttribute('ipv6s')); - - $this->assertIsString($document3->getAttribute('key')); - $this->assertCount(3, $document3->getAttribute('keys')); - } - - public function testGetDocument() - { - // Mocked document - $document = self::$object->getDocument(Database::COLLECTION_COLLECTIONS, Database::COLLECTION_USERS); - - $this->assertEquals(Database::COLLECTION_USERS, $document->getId()); - $this->assertEquals(Database::COLLECTION_COLLECTIONS, $document->getCollection()); - - $collection1 = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ - '$collection' => Database::COLLECTION_COLLECTIONS, - '$permissions' => ['read' => ['*']], - 'name' => 'Create Documents', - 'rules' => [ - [ - '$collection' => Database::COLLECTION_RULES, - '$permissions' => ['read' => ['*']], - 'label' => 'Name', - 'key' => 'name', - 'type' => Database::VAR_TEXT, - 'default' => '', - 'required' => true, - 'array' => false, - ], - [ - '$collection' => Database::COLLECTION_RULES, - '$permissions' => ['read' => ['*']], - 'label' => 'Links', - 'key' => 'links', - 'type' => Database::VAR_URL, - 'default' => '', - 'required' => true, - 'array' => true, - ], - ] - ]); - - $this->assertEquals(true, self::$object->createCollection($collection1->getId(), [], [])); +// $this->assertIsInt($document3->getAttribute('integer')); +// $this->assertEquals(1, $document3->getAttribute('integer')); +// $this->assertIsInt($document3->getAttribute('integers')[0]); +// $this->assertIsInt($document3->getAttribute('integers')[1]); +// $this->assertIsInt($document3->getAttribute('integers')[2]); +// $this->assertEquals([5, 3, 4], $document3->getAttribute('integers')); +// $this->assertCount(3, $document3->getAttribute('integers')); + +// $this->assertIsFloat($document3->getAttribute('float')); +// $this->assertEquals(2.22, $document3->getAttribute('float')); +// $this->assertIsFloat($document3->getAttribute('floats')[0]); +// $this->assertIsFloat($document3->getAttribute('floats')[1]); +// $this->assertIsFloat($document3->getAttribute('floats')[2]); +// $this->assertEquals([1.13, 4.33, 8.9999], $document3->getAttribute('floats')); +// $this->assertCount(3, $document3->getAttribute('floats')); + +// $this->assertIsBool($document3->getAttribute('boolean')); +// $this->assertEquals(true, $document3->getAttribute('boolean')); +// $this->assertIsBool($document3->getAttribute('booleans')[0]); +// $this->assertIsBool($document3->getAttribute('booleans')[1]); +// $this->assertIsBool($document3->getAttribute('booleans')[2]); +// $this->assertEquals([true, false, true], $document3->getAttribute('booleans')); +// $this->assertCount(3, $document3->getAttribute('booleans')); + +// $this->assertIsString($document3->getAttribute('email')); +// $this->assertEquals('test@appwrite.io', $document3->getAttribute('email')); +// $this->assertIsString($document3->getAttribute('emails')[0]); +// $this->assertIsString($document3->getAttribute('emails')[1]); +// $this->assertIsString($document3->getAttribute('emails')[2]); +// $this->assertIsString($document3->getAttribute('emails')[3]); +// $this->assertEquals([ +// 'test4@appwrite.io', +// 'test3@appwrite.io', +// 'test2@appwrite.io', +// 'test1@appwrite.io' +// ], $document3->getAttribute('emails')); +// $this->assertCount(4, $document3->getAttribute('emails')); + +// $this->assertIsString($document3->getAttribute('url')); +// $this->assertEquals('http://example.com/welcome', $document3->getAttribute('url')); +// $this->assertIsString($document3->getAttribute('urls')[0]); +// $this->assertIsString($document3->getAttribute('urls')[1]); +// $this->assertIsString($document3->getAttribute('urls')[2]); +// $this->assertEquals([ +// 'http://example.com/welcome-1', +// 'http://example.com/welcome-2', +// 'http://example.com/welcome-3' +// ], $document3->getAttribute('urls')); +// $this->assertCount(3, $document3->getAttribute('urls')); + +// $this->assertIsString($document3->getAttribute('ipv4')); +// $this->assertEquals('172.16.254.1', $document3->getAttribute('ipv4')); +// $this->assertIsString($document3->getAttribute('ipv4s')[0]); +// $this->assertIsString($document3->getAttribute('ipv4s')[1]); +// $this->assertEquals([ +// '172.16.254.1', +// '172.16.254.5' +// ], $document3->getAttribute('ipv4s')); +// $this->assertCount(2, $document3->getAttribute('ipv4s')); + +// $this->assertIsString($document3->getAttribute('ipv6')); +// $this->assertEquals('2001:0db8:85a3:0000:0000:8a2e:0370:7334', $document3->getAttribute('ipv6')); +// $this->assertIsString($document3->getAttribute('ipv6s')[0]); +// $this->assertIsString($document3->getAttribute('ipv6s')[1]); +// $this->assertEquals([ +// '2001:0db8:85a3:0000:0000:8a2e:0370:7334', +// '2001:0db8:85a3:0000:0000:8a2e:0370:7337' +// ], $document3->getAttribute('ipv6s')); +// $this->assertCount(2, $document3->getAttribute('ipv6s')); + +// $this->assertEquals('2001:0db8:85a3:0000:0000:8a2e:0370:7334', $document3->getAttribute('ipv6')); +// $this->assertEquals([ +// '2001:0db8:85a3:0000:0000:8a2e:0370:7334', +// '2001:0db8:85a3:0000:0000:8a2e:0370:7337' +// ], $document3->getAttribute('ipv6s')); +// $this->assertCount(2, $document3->getAttribute('ipv6s')); + +// $this->assertIsString($document3->getAttribute('key')); +// $this->assertCount(3, $document3->getAttribute('keys')); +// } + +// public function testGetDocument() +// { +// // Mocked document +// $document = self::$object->getDocument(Database::COLLECTION_COLLECTIONS, Database::COLLECTION_USERS); + +// $this->assertEquals(Database::COLLECTION_USERS, $document->getId()); +// $this->assertEquals(Database::COLLECTION_COLLECTIONS, $document->getCollection()); + +// $collection1 = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ +// '$collection' => Database::COLLECTION_COLLECTIONS, +// '$permissions' => ['read' => ['*']], +// 'name' => 'Create Documents', +// 'rules' => [ +// [ +// '$collection' => Database::COLLECTION_RULES, +// '$permissions' => ['read' => ['*']], +// 'label' => 'Name', +// 'key' => 'name', +// 'type' => Database::VAR_TEXT, +// 'default' => '', +// 'required' => true, +// 'array' => false, +// ], +// [ +// '$collection' => Database::COLLECTION_RULES, +// '$permissions' => ['read' => ['*']], +// 'label' => 'Links', +// 'key' => 'links', +// 'type' => Database::VAR_URL, +// 'default' => '', +// 'required' => true, +// 'array' => true, +// ], +// ] +// ]); + +// $this->assertEquals(true, self::$object->createCollection($collection1->getId(), [], [])); - $document1 = self::$object->createDocument($collection1->getId(), [ - '$collection' => $collection1->getId(), - '$permissions' => [ - 'read' => ['*'], - 'write' => ['user:123'], - ], - 'name' => 'Task #1️⃣', - 'links' => [ - 'http://example.com/link-5', - 'http://example.com/link-6', - 'http://example.com/link-7', - 'http://example.com/link-8', - ], - ]); - - $document1 = self::$object->getDocument($collection1->getId(), $document1->getId()); - - $this->assertFalse($document1->isEmpty()); - $this->assertNotEmpty($document1->getId()); - $this->assertIsArray($document1->getPermissions()); - $this->assertArrayHasKey('read', $document1->getPermissions()); - $this->assertArrayHasKey('write', $document1->getPermissions()); - $this->assertEquals('Task #1️⃣', $document1->getAttribute('name')); - $this->assertCount(4, $document1->getAttribute('links')); - $this->assertEquals('http://example.com/link-5', $document1->getAttribute('links')[0]); - $this->assertEquals('http://example.com/link-6', $document1->getAttribute('links')[1]); - $this->assertEquals('http://example.com/link-7', $document1->getAttribute('links')[2]); - $this->assertEquals('http://example.com/link-8', $document1->getAttribute('links')[3]); - - $document1 = self::$object->getDocument($collection1->getId(), $document1->getId().'x'); - - $this->assertTrue($document1->isEmpty()); - $this->assertEmpty($document1->getId()); - $this->assertEmpty($document1->getCollection()); - $this->assertIsArray($document1->getPermissions()); - $this->assertEmpty($document1->getPermissions()); - } - - public function testUpdateDocument() - { - $collection1 = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ - '$collection' => Database::COLLECTION_COLLECTIONS, - '$permissions' => ['read' => ['*']], - 'name' => 'Create Documents', - 'rules' => [ - [ - '$collection' => Database::COLLECTION_RULES, - '$permissions' => ['read' => ['*']], - 'label' => 'Name', - 'key' => 'name', - 'type' => Database::VAR_TEXT, - 'default' => '', - 'required' => true, - 'array' => false, - ], - [ - '$collection' => Database::COLLECTION_RULES, - '$permissions' => ['read' => ['*']], - 'label' => 'Links', - 'key' => 'links', - 'type' => Database::VAR_URL, - 'default' => '', - 'required' => true, - 'array' => true, - ], - ] - ]); - - $this->assertEquals(true, self::$object->createCollection($collection1->getId(), [], [])); +// $document1 = self::$object->createDocument($collection1->getId(), [ +// '$collection' => $collection1->getId(), +// '$permissions' => [ +// 'read' => ['*'], +// 'write' => ['user:123'], +// ], +// 'name' => 'Task #1️⃣', +// 'links' => [ +// 'http://example.com/link-5', +// 'http://example.com/link-6', +// 'http://example.com/link-7', +// 'http://example.com/link-8', +// ], +// ]); + +// $document1 = self::$object->getDocument($collection1->getId(), $document1->getId()); + +// $this->assertFalse($document1->isEmpty()); +// $this->assertNotEmpty($document1->getId()); +// $this->assertIsArray($document1->getPermissions()); +// $this->assertArrayHasKey('read', $document1->getPermissions()); +// $this->assertArrayHasKey('write', $document1->getPermissions()); +// $this->assertEquals('Task #1️⃣', $document1->getAttribute('name')); +// $this->assertCount(4, $document1->getAttribute('links')); +// $this->assertEquals('http://example.com/link-5', $document1->getAttribute('links')[0]); +// $this->assertEquals('http://example.com/link-6', $document1->getAttribute('links')[1]); +// $this->assertEquals('http://example.com/link-7', $document1->getAttribute('links')[2]); +// $this->assertEquals('http://example.com/link-8', $document1->getAttribute('links')[3]); + +// $document1 = self::$object->getDocument($collection1->getId(), $document1->getId().'x'); + +// $this->assertTrue($document1->isEmpty()); +// $this->assertEmpty($document1->getId()); +// $this->assertEmpty($document1->getCollection()); +// $this->assertIsArray($document1->getPermissions()); +// $this->assertEmpty($document1->getPermissions()); +// } + +// public function testUpdateDocument() +// { +// $collection1 = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ +// '$collection' => Database::COLLECTION_COLLECTIONS, +// '$permissions' => ['read' => ['*']], +// 'name' => 'Create Documents', +// 'rules' => [ +// [ +// '$collection' => Database::COLLECTION_RULES, +// '$permissions' => ['read' => ['*']], +// 'label' => 'Name', +// 'key' => 'name', +// 'type' => Database::VAR_TEXT, +// 'default' => '', +// 'required' => true, +// 'array' => false, +// ], +// [ +// '$collection' => Database::COLLECTION_RULES, +// '$permissions' => ['read' => ['*']], +// 'label' => 'Links', +// 'key' => 'links', +// 'type' => Database::VAR_URL, +// 'default' => '', +// 'required' => true, +// 'array' => true, +// ], +// ] +// ]); + +// $this->assertEquals(true, self::$object->createCollection($collection1->getId(), [], [])); - $document0 = new Document([ - '$collection' => $collection1->getId(), - '$permissions' => [ - 'read' => ['*'], - 'write' => ['user:123'], - ], - 'name' => 'Task #0', - 'links' => [ - 'http://example.com/link-1', - 'http://example.com/link-2', - 'http://example.com/link-3', - 'http://example.com/link-4', - ], - ]); +// $document0 = new Document([ +// '$collection' => $collection1->getId(), +// '$permissions' => [ +// 'read' => ['*'], +// 'write' => ['user:123'], +// ], +// 'name' => 'Task #0', +// 'links' => [ +// 'http://example.com/link-1', +// 'http://example.com/link-2', +// 'http://example.com/link-3', +// 'http://example.com/link-4', +// ], +// ]); - $document1 = self::$object->createDocument($collection1->getId(), [ - '$collection' => $collection1->getId(), - '$permissions' => [ - 'read' => ['*'], - 'write' => ['user:123'], - ], - 'name' => 'Task #1️⃣', - 'links' => [ - 'http://example.com/link-5', - 'http://example.com/link-6', - 'http://example.com/link-7', - 'http://example.com/link-8', - ], - ]); - - $this->assertNotEmpty($document1->getId()); - $this->assertIsArray($document1->getPermissions()); - $this->assertArrayHasKey('read', $document1->getPermissions()); - $this->assertArrayHasKey('write', $document1->getPermissions()); - $this->assertEquals('Task #1️⃣', $document1->getAttribute('name')); - $this->assertCount(4, $document1->getAttribute('links')); - $this->assertEquals('http://example.com/link-5', $document1->getAttribute('links')[0]); - $this->assertEquals('http://example.com/link-6', $document1->getAttribute('links')[1]); - $this->assertEquals('http://example.com/link-7', $document1->getAttribute('links')[2]); - $this->assertEquals('http://example.com/link-8', $document1->getAttribute('links')[3]); +// $document1 = self::$object->createDocument($collection1->getId(), [ +// '$collection' => $collection1->getId(), +// '$permissions' => [ +// 'read' => ['*'], +// 'write' => ['user:123'], +// ], +// 'name' => 'Task #1️⃣', +// 'links' => [ +// 'http://example.com/link-5', +// 'http://example.com/link-6', +// 'http://example.com/link-7', +// 'http://example.com/link-8', +// ], +// ]); + +// $this->assertNotEmpty($document1->getId()); +// $this->assertIsArray($document1->getPermissions()); +// $this->assertArrayHasKey('read', $document1->getPermissions()); +// $this->assertArrayHasKey('write', $document1->getPermissions()); +// $this->assertEquals('Task #1️⃣', $document1->getAttribute('name')); +// $this->assertCount(4, $document1->getAttribute('links')); +// $this->assertEquals('http://example.com/link-5', $document1->getAttribute('links')[0]); +// $this->assertEquals('http://example.com/link-6', $document1->getAttribute('links')[1]); +// $this->assertEquals('http://example.com/link-7', $document1->getAttribute('links')[2]); +// $this->assertEquals('http://example.com/link-8', $document1->getAttribute('links')[3]); - $document1 = self::$object->getDocument($collection1->getId(), $document1->getId()); - - $this->assertNotEmpty($document1->getId()); - $this->assertIsArray($document1->getPermissions()); - $this->assertArrayHasKey('read', $document1->getPermissions()); - $this->assertArrayHasKey('write', $document1->getPermissions()); - $this->assertEquals('Task #1️⃣', $document1->getAttribute('name')); - $this->assertCount(4, $document1->getAttribute('links')); - $this->assertEquals('http://example.com/link-5', $document1->getAttribute('links')[0]); - $this->assertEquals('http://example.com/link-6', $document1->getAttribute('links')[1]); - $this->assertEquals('http://example.com/link-7', $document1->getAttribute('links')[2]); - $this->assertEquals('http://example.com/link-8', $document1->getAttribute('links')[3]); - - $document1 = self::$object->updateDocument($collection1->getId(), $document1->getId(), [ - '$id' => $document1->getId(), - '$collection' => $collection1->getId(), - '$permissions' => [ - 'read' => ['user:1234'], - 'write' => ['user:1234'], - ], - 'name' => 'Task #1x', - 'links' => [ - 'http://example.com/link-5x', - 'http://example.com/link-6x', - 'http://example.com/link-7x', - 'http://example.com/link-8x', - ], - ]); - - $this->assertNotEmpty($document1->getId()); - $this->assertIsArray($document1->getPermissions()); - $this->assertArrayHasKey('read', $document1->getPermissions()); - $this->assertArrayHasKey('write', $document1->getPermissions()); - $this->assertEquals('Task #1x', $document1->getAttribute('name')); - $this->assertCount(4, $document1->getAttribute('links')); - $this->assertEquals('http://example.com/link-5x', $document1->getAttribute('links')[0]); - $this->assertEquals('http://example.com/link-6x', $document1->getAttribute('links')[1]); - $this->assertEquals('http://example.com/link-7x', $document1->getAttribute('links')[2]); - $this->assertEquals('http://example.com/link-8x', $document1->getAttribute('links')[3]); - - $document1 = self::$object->getDocument($collection1->getId(), $document1->getId()); - - $this->assertNotEmpty($document1->getId()); - $this->assertIsArray($document1->getPermissions()); - $this->assertArrayHasKey('read', $document1->getPermissions()); - $this->assertArrayHasKey('write', $document1->getPermissions()); - $this->assertEquals('Task #1x', $document1->getAttribute('name')); - $this->assertCount(4, $document1->getAttribute('links')); - $this->assertEquals('http://example.com/link-5x', $document1->getAttribute('links')[0]); - $this->assertEquals('http://example.com/link-6x', $document1->getAttribute('links')[1]); - $this->assertEquals('http://example.com/link-7x', $document1->getAttribute('links')[2]); - $this->assertEquals('http://example.com/link-8x', $document1->getAttribute('links')[3]); - - $document2 = self::$object->createDocument(Database::COLLECTION_USERS, [ - '$collection' => Database::COLLECTION_USERS, - '$permissions' => [ - 'read' => ['*'], - 'write' => ['user:123'], - ], - 'email' => 'test5@appwrite.io', - 'emailVerification' => false, - 'status' => 0, - 'password' => 'secrethash', - 'password-update' => \time(), - 'registration' => \time(), - 'reset' => false, - 'name' => 'Test', - ]); - - $this->assertNotEmpty($document2->getId()); - $this->assertIsArray($document2->getPermissions()); - $this->assertArrayHasKey('read', $document2->getPermissions()); - $this->assertArrayHasKey('write', $document2->getPermissions()); - $this->assertEquals('test5@appwrite.io', $document2->getAttribute('email')); - $this->assertIsString($document2->getAttribute('email')); - $this->assertEquals(0, $document2->getAttribute('status')); - $this->assertIsInt($document2->getAttribute('status')); - $this->assertEquals(false, $document2->getAttribute('emailVerification')); - $this->assertIsBool($document2->getAttribute('emailVerification')); - - $document2 = self::$object->getDocument(Database::COLLECTION_USERS, $document2->getId()); - - $this->assertNotEmpty($document2->getId()); - $this->assertIsArray($document2->getPermissions()); - $this->assertArrayHasKey('read', $document2->getPermissions()); - $this->assertArrayHasKey('write', $document2->getPermissions()); - $this->assertEquals('test5@appwrite.io', $document2->getAttribute('email')); - $this->assertIsString($document2->getAttribute('email')); - $this->assertEquals(0, $document2->getAttribute('status')); - $this->assertIsInt($document2->getAttribute('status')); - $this->assertEquals(false, $document2->getAttribute('emailVerification')); - $this->assertIsBool($document2->getAttribute('emailVerification')); - - $document2 = self::$object->updateDocument(Database::COLLECTION_USERS, $document2->getId(), [ - '$id' => $document2->getId(), - '$collection' => Database::COLLECTION_USERS, - '$permissions' => [ - 'read' => ['user:1234'], - 'write' => ['user:1234'], - ], - 'email' => 'test5x@appwrite.io', - 'emailVerification' => true, - 'status' => 1, - 'password' => 'secrethashx', - 'password-update' => \time(), - 'registration' => \time(), - 'reset' => true, - 'name' => 'Testx', - ]); - - $this->assertNotEmpty($document2->getId()); - $this->assertIsArray($document2->getPermissions()); - $this->assertArrayHasKey('read', $document2->getPermissions()); - $this->assertArrayHasKey('write', $document2->getPermissions()); - $this->assertEquals('test5x@appwrite.io', $document2->getAttribute('email')); - $this->assertIsString($document2->getAttribute('email')); - $this->assertEquals(1, $document2->getAttribute('status')); - $this->assertIsInt($document2->getAttribute('status')); - $this->assertEquals(true, $document2->getAttribute('emailVerification')); - $this->assertIsBool($document2->getAttribute('emailVerification')); - - $document2 = self::$object->getDocument(Database::COLLECTION_USERS, $document2->getId()); - - $this->assertNotEmpty($document2->getId()); - $this->assertIsArray($document2->getPermissions()); - $this->assertArrayHasKey('read', $document2->getPermissions()); - $this->assertArrayHasKey('write', $document2->getPermissions()); - $this->assertEquals('test5x@appwrite.io', $document2->getAttribute('email')); - $this->assertIsString($document2->getAttribute('email')); - $this->assertEquals(1, $document2->getAttribute('status')); - $this->assertIsInt($document2->getAttribute('status')); - $this->assertEquals(true, $document2->getAttribute('emailVerification')); - $this->assertIsBool($document2->getAttribute('emailVerification')); - - $types = [ - Database::VAR_TEXT, - Database::VAR_INTEGER, - Database::VAR_FLOAT, - Database::VAR_NUMERIC, - Database::VAR_BOOLEAN, - Database::VAR_DOCUMENT, - Database::VAR_EMAIL, - Database::VAR_URL, - Database::VAR_IPV4, - Database::VAR_IPV6, - Database::VAR_KEY, - ]; - - $rules = []; - - foreach($types as $type) { - $rules[] = [ - '$collection' => Database::COLLECTION_RULES, - '$permissions' => ['read' => ['*']], - 'label' => ucfirst($type), - 'key' => $type, - 'type' => $type, - 'default' => null, - 'required' => true, - 'array' => false, - 'list' => ($type === Database::VAR_DOCUMENT) ? [$collection1->getId()] : [], - ]; - - $rules[] = [ - '$collection' => Database::COLLECTION_RULES, - '$permissions' => ['read' => ['*']], - 'label' => ucfirst($type), - 'key' => $type.'s', - 'type' => $type, - 'default' => null, - 'required' => true, - 'array' => true, - 'list' => ($type === Database::VAR_DOCUMENT) ? [$collection1->getId()] : [], - ]; - } - - $rules[] = [ - '$collection' => Database::COLLECTION_RULES, - '$permissions' => ['read' => ['*']], - 'label' => 'document2', - 'key' => 'document2', - 'type' => Database::VAR_DOCUMENT, - 'default' => null, - 'required' => true, - 'array' => false, - 'list' => [$collection1->getId()], - ]; - - $rules[] = [ - '$collection' => Database::COLLECTION_RULES, - '$permissions' => ['read' => ['*']], - 'label' => 'documents2', - 'key' => 'documents2', - 'type' => Database::VAR_DOCUMENT, - 'default' => null, - 'required' => true, - 'array' => true, - 'list' => [$collection1->getId()], - ]; - - $collection2 = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ - '$collection' => Database::COLLECTION_COLLECTIONS, - '$permissions' => ['read' => ['*']], - 'name' => 'Create Documents', - 'rules' => $rules, - ]); - - $this->assertEquals(true, self::$object->createCollection($collection2->getId(), [], [])); +// $document1 = self::$object->getDocument($collection1->getId(), $document1->getId()); + +// $this->assertNotEmpty($document1->getId()); +// $this->assertIsArray($document1->getPermissions()); +// $this->assertArrayHasKey('read', $document1->getPermissions()); +// $this->assertArrayHasKey('write', $document1->getPermissions()); +// $this->assertEquals('Task #1️⃣', $document1->getAttribute('name')); +// $this->assertCount(4, $document1->getAttribute('links')); +// $this->assertEquals('http://example.com/link-5', $document1->getAttribute('links')[0]); +// $this->assertEquals('http://example.com/link-6', $document1->getAttribute('links')[1]); +// $this->assertEquals('http://example.com/link-7', $document1->getAttribute('links')[2]); +// $this->assertEquals('http://example.com/link-8', $document1->getAttribute('links')[3]); + +// $document1 = self::$object->updateDocument($collection1->getId(), $document1->getId(), [ +// '$id' => $document1->getId(), +// '$collection' => $collection1->getId(), +// '$permissions' => [ +// 'read' => ['user:1234'], +// 'write' => ['user:1234'], +// ], +// 'name' => 'Task #1x', +// 'links' => [ +// 'http://example.com/link-5x', +// 'http://example.com/link-6x', +// 'http://example.com/link-7x', +// 'http://example.com/link-8x', +// ], +// ]); + +// $this->assertNotEmpty($document1->getId()); +// $this->assertIsArray($document1->getPermissions()); +// $this->assertArrayHasKey('read', $document1->getPermissions()); +// $this->assertArrayHasKey('write', $document1->getPermissions()); +// $this->assertEquals('Task #1x', $document1->getAttribute('name')); +// $this->assertCount(4, $document1->getAttribute('links')); +// $this->assertEquals('http://example.com/link-5x', $document1->getAttribute('links')[0]); +// $this->assertEquals('http://example.com/link-6x', $document1->getAttribute('links')[1]); +// $this->assertEquals('http://example.com/link-7x', $document1->getAttribute('links')[2]); +// $this->assertEquals('http://example.com/link-8x', $document1->getAttribute('links')[3]); + +// $document1 = self::$object->getDocument($collection1->getId(), $document1->getId()); + +// $this->assertNotEmpty($document1->getId()); +// $this->assertIsArray($document1->getPermissions()); +// $this->assertArrayHasKey('read', $document1->getPermissions()); +// $this->assertArrayHasKey('write', $document1->getPermissions()); +// $this->assertEquals('Task #1x', $document1->getAttribute('name')); +// $this->assertCount(4, $document1->getAttribute('links')); +// $this->assertEquals('http://example.com/link-5x', $document1->getAttribute('links')[0]); +// $this->assertEquals('http://example.com/link-6x', $document1->getAttribute('links')[1]); +// $this->assertEquals('http://example.com/link-7x', $document1->getAttribute('links')[2]); +// $this->assertEquals('http://example.com/link-8x', $document1->getAttribute('links')[3]); + +// $document2 = self::$object->createDocument(Database::COLLECTION_USERS, [ +// '$collection' => Database::COLLECTION_USERS, +// '$permissions' => [ +// 'read' => ['*'], +// 'write' => ['user:123'], +// ], +// 'email' => 'test5@appwrite.io', +// 'emailVerification' => false, +// 'status' => 0, +// 'password' => 'secrethash', +// 'password-update' => \time(), +// 'registration' => \time(), +// 'reset' => false, +// 'name' => 'Test', +// ]); + +// $this->assertNotEmpty($document2->getId()); +// $this->assertIsArray($document2->getPermissions()); +// $this->assertArrayHasKey('read', $document2->getPermissions()); +// $this->assertArrayHasKey('write', $document2->getPermissions()); +// $this->assertEquals('test5@appwrite.io', $document2->getAttribute('email')); +// $this->assertIsString($document2->getAttribute('email')); +// $this->assertEquals(0, $document2->getAttribute('status')); +// $this->assertIsInt($document2->getAttribute('status')); +// $this->assertEquals(false, $document2->getAttribute('emailVerification')); +// $this->assertIsBool($document2->getAttribute('emailVerification')); + +// $document2 = self::$object->getDocument(Database::COLLECTION_USERS, $document2->getId()); + +// $this->assertNotEmpty($document2->getId()); +// $this->assertIsArray($document2->getPermissions()); +// $this->assertArrayHasKey('read', $document2->getPermissions()); +// $this->assertArrayHasKey('write', $document2->getPermissions()); +// $this->assertEquals('test5@appwrite.io', $document2->getAttribute('email')); +// $this->assertIsString($document2->getAttribute('email')); +// $this->assertEquals(0, $document2->getAttribute('status')); +// $this->assertIsInt($document2->getAttribute('status')); +// $this->assertEquals(false, $document2->getAttribute('emailVerification')); +// $this->assertIsBool($document2->getAttribute('emailVerification')); + +// $document2 = self::$object->updateDocument(Database::COLLECTION_USERS, $document2->getId(), [ +// '$id' => $document2->getId(), +// '$collection' => Database::COLLECTION_USERS, +// '$permissions' => [ +// 'read' => ['user:1234'], +// 'write' => ['user:1234'], +// ], +// 'email' => 'test5x@appwrite.io', +// 'emailVerification' => true, +// 'status' => 1, +// 'password' => 'secrethashx', +// 'password-update' => \time(), +// 'registration' => \time(), +// 'reset' => true, +// 'name' => 'Testx', +// ]); + +// $this->assertNotEmpty($document2->getId()); +// $this->assertIsArray($document2->getPermissions()); +// $this->assertArrayHasKey('read', $document2->getPermissions()); +// $this->assertArrayHasKey('write', $document2->getPermissions()); +// $this->assertEquals('test5x@appwrite.io', $document2->getAttribute('email')); +// $this->assertIsString($document2->getAttribute('email')); +// $this->assertEquals(1, $document2->getAttribute('status')); +// $this->assertIsInt($document2->getAttribute('status')); +// $this->assertEquals(true, $document2->getAttribute('emailVerification')); +// $this->assertIsBool($document2->getAttribute('emailVerification')); + +// $document2 = self::$object->getDocument(Database::COLLECTION_USERS, $document2->getId()); + +// $this->assertNotEmpty($document2->getId()); +// $this->assertIsArray($document2->getPermissions()); +// $this->assertArrayHasKey('read', $document2->getPermissions()); +// $this->assertArrayHasKey('write', $document2->getPermissions()); +// $this->assertEquals('test5x@appwrite.io', $document2->getAttribute('email')); +// $this->assertIsString($document2->getAttribute('email')); +// $this->assertEquals(1, $document2->getAttribute('status')); +// $this->assertIsInt($document2->getAttribute('status')); +// $this->assertEquals(true, $document2->getAttribute('emailVerification')); +// $this->assertIsBool($document2->getAttribute('emailVerification')); + +// $types = [ +// Database::VAR_TEXT, +// Database::VAR_INTEGER, +// Database::VAR_FLOAT, +// Database::VAR_NUMERIC, +// Database::VAR_BOOLEAN, +// Database::VAR_DOCUMENT, +// Database::VAR_EMAIL, +// Database::VAR_URL, +// Database::VAR_IPV4, +// Database::VAR_IPV6, +// Database::VAR_KEY, +// ]; + +// $rules = []; + +// foreach($types as $type) { +// $rules[] = [ +// '$collection' => Database::COLLECTION_RULES, +// '$permissions' => ['read' => ['*']], +// 'label' => ucfirst($type), +// 'key' => $type, +// 'type' => $type, +// 'default' => null, +// 'required' => true, +// 'array' => false, +// 'list' => ($type === Database::VAR_DOCUMENT) ? [$collection1->getId()] : [], +// ]; + +// $rules[] = [ +// '$collection' => Database::COLLECTION_RULES, +// '$permissions' => ['read' => ['*']], +// 'label' => ucfirst($type), +// 'key' => $type.'s', +// 'type' => $type, +// 'default' => null, +// 'required' => true, +// 'array' => true, +// 'list' => ($type === Database::VAR_DOCUMENT) ? [$collection1->getId()] : [], +// ]; +// } + +// $rules[] = [ +// '$collection' => Database::COLLECTION_RULES, +// '$permissions' => ['read' => ['*']], +// 'label' => 'document2', +// 'key' => 'document2', +// 'type' => Database::VAR_DOCUMENT, +// 'default' => null, +// 'required' => true, +// 'array' => false, +// 'list' => [$collection1->getId()], +// ]; + +// $rules[] = [ +// '$collection' => Database::COLLECTION_RULES, +// '$permissions' => ['read' => ['*']], +// 'label' => 'documents2', +// 'key' => 'documents2', +// 'type' => Database::VAR_DOCUMENT, +// 'default' => null, +// 'required' => true, +// 'array' => true, +// 'list' => [$collection1->getId()], +// ]; + +// $collection2 = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ +// '$collection' => Database::COLLECTION_COLLECTIONS, +// '$permissions' => ['read' => ['*']], +// 'name' => 'Create Documents', +// 'rules' => $rules, +// ]); + +// $this->assertEquals(true, self::$object->createCollection($collection2->getId(), [], [])); - $document3 = self::$object->createDocument($collection2->getId(), [ - '$collection' => $collection2->getId(), - '$permissions' => [ - 'read' => ['*'], - 'write' => ['user:123'], - ], - 'text' => 'Hello World', - 'texts' => ['Hello World 1', 'Hello World 2'], - // 'document' => $document0, - // 'documents' => [$document0], - 'document' => $document0, - 'documents' => [$document1, $document0], - 'document2' => $document1, - 'documents2' => [$document0, $document1], - 'integer' => 1, - 'integers' => [5, 3, 4], - 'float' => 2.22, - 'floats' => [1.13, 4.33, 8.9999], - 'numeric' => 1, - 'numerics' => [1, 5, 7.77], - 'boolean' => true, - 'booleans' => [true, false, true], - 'email' => 'test@appwrite.io', - 'emails' => [ - 'test4@appwrite.io', - 'test3@appwrite.io', - 'test2@appwrite.io', - 'test1@appwrite.io' - ], - 'url' => 'http://example.com/welcome', - 'urls' => [ - 'http://example.com/welcome-1', - 'http://example.com/welcome-2', - 'http://example.com/welcome-3' - ], - 'ipv4' => '172.16.254.1', - 'ipv4s' => [ - '172.16.254.1', - '172.16.254.5' - ], - 'ipv6' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334', - 'ipv6s' => [ - '2001:0db8:85a3:0000:0000:8a2e:0370:7334', - '2001:0db8:85a3:0000:0000:8a2e:0370:7337' - ], - 'key' => uniqid(), - 'keys' => [uniqid(), uniqid(), uniqid()], - ]); - - $document3 = self::$object->getDocument($collection2->getId(), $document3->getId()); - - $this->assertIsString($document3->getId()); - $this->assertIsString($document3->getCollection()); - $this->assertEquals([ - 'read' => ['*'], - 'write' => ['user:123'], - ], $document3->getPermissions()); - $this->assertEquals('Hello World', $document3->getAttribute('text')); - $this->assertCount(2, $document3->getAttribute('texts')); +// $document3 = self::$object->createDocument($collection2->getId(), [ +// '$collection' => $collection2->getId(), +// '$permissions' => [ +// 'read' => ['*'], +// 'write' => ['user:123'], +// ], +// 'text' => 'Hello World', +// 'texts' => ['Hello World 1', 'Hello World 2'], +// // 'document' => $document0, +// // 'documents' => [$document0], +// 'document' => $document0, +// 'documents' => [$document1, $document0], +// 'document2' => $document1, +// 'documents2' => [$document0, $document1], +// 'integer' => 1, +// 'integers' => [5, 3, 4], +// 'float' => 2.22, +// 'floats' => [1.13, 4.33, 8.9999], +// 'numeric' => 1, +// 'numerics' => [1, 5, 7.77], +// 'boolean' => true, +// 'booleans' => [true, false, true], +// 'email' => 'test@appwrite.io', +// 'emails' => [ +// 'test4@appwrite.io', +// 'test3@appwrite.io', +// 'test2@appwrite.io', +// 'test1@appwrite.io' +// ], +// 'url' => 'http://example.com/welcome', +// 'urls' => [ +// 'http://example.com/welcome-1', +// 'http://example.com/welcome-2', +// 'http://example.com/welcome-3' +// ], +// 'ipv4' => '172.16.254.1', +// 'ipv4s' => [ +// '172.16.254.1', +// '172.16.254.5' +// ], +// 'ipv6' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334', +// 'ipv6s' => [ +// '2001:0db8:85a3:0000:0000:8a2e:0370:7334', +// '2001:0db8:85a3:0000:0000:8a2e:0370:7337' +// ], +// 'key' => uniqid(), +// 'keys' => [uniqid(), uniqid(), uniqid()], +// ]); + +// $document3 = self::$object->getDocument($collection2->getId(), $document3->getId()); + +// $this->assertIsString($document3->getId()); +// $this->assertIsString($document3->getCollection()); +// $this->assertEquals([ +// 'read' => ['*'], +// 'write' => ['user:123'], +// ], $document3->getPermissions()); +// $this->assertEquals('Hello World', $document3->getAttribute('text')); +// $this->assertCount(2, $document3->getAttribute('texts')); - $this->assertIsString($document3->getAttribute('text')); - $this->assertEquals('Hello World', $document3->getAttribute('text')); - $this->assertEquals(['Hello World 1', 'Hello World 2'], $document3->getAttribute('texts')); - $this->assertCount(2, $document3->getAttribute('texts')); +// $this->assertIsString($document3->getAttribute('text')); +// $this->assertEquals('Hello World', $document3->getAttribute('text')); +// $this->assertEquals(['Hello World 1', 'Hello World 2'], $document3->getAttribute('texts')); +// $this->assertCount(2, $document3->getAttribute('texts')); - $this->assertInstanceOf(Document::class, $document3->getAttribute('document')); - $this->assertIsString($document3->getAttribute('document')->getId()); - $this->assertNotEmpty($document3->getAttribute('document')->getId()); - $this->assertIsArray($document3->getAttribute('document')->getPermissions()); - $this->assertArrayHasKey('read', $document3->getAttribute('document')->getPermissions()); - $this->assertArrayHasKey('write', $document3->getAttribute('document')->getPermissions()); - $this->assertEquals('Task #0', $document3->getAttribute('document')->getAttribute('name')); - $this->assertCount(4, $document3->getAttribute('document')->getAttribute('links')); - $this->assertEquals('http://example.com/link-1', $document3->getAttribute('document')->getAttribute('links')[0]); - $this->assertEquals('http://example.com/link-2', $document3->getAttribute('document')->getAttribute('links')[1]); - $this->assertEquals('http://example.com/link-3', $document3->getAttribute('document')->getAttribute('links')[2]); - $this->assertEquals('http://example.com/link-4', $document3->getAttribute('document')->getAttribute('links')[3]); +// $this->assertInstanceOf(Document::class, $document3->getAttribute('document')); +// $this->assertIsString($document3->getAttribute('document')->getId()); +// $this->assertNotEmpty($document3->getAttribute('document')->getId()); +// $this->assertIsArray($document3->getAttribute('document')->getPermissions()); +// $this->assertArrayHasKey('read', $document3->getAttribute('document')->getPermissions()); +// $this->assertArrayHasKey('write', $document3->getAttribute('document')->getPermissions()); +// $this->assertEquals('Task #0', $document3->getAttribute('document')->getAttribute('name')); +// $this->assertCount(4, $document3->getAttribute('document')->getAttribute('links')); +// $this->assertEquals('http://example.com/link-1', $document3->getAttribute('document')->getAttribute('links')[0]); +// $this->assertEquals('http://example.com/link-2', $document3->getAttribute('document')->getAttribute('links')[1]); +// $this->assertEquals('http://example.com/link-3', $document3->getAttribute('document')->getAttribute('links')[2]); +// $this->assertEquals('http://example.com/link-4', $document3->getAttribute('document')->getAttribute('links')[3]); - $this->assertInstanceOf(Document::class, $document3->getAttribute('documents')[0]); - $this->assertIsString($document3->getAttribute('documents')[0]->getId()); - $this->assertNotEmpty($document3->getAttribute('documents')[0]->getId()); - $this->assertIsArray($document3->getAttribute('documents')[0]->getPermissions()); - $this->assertArrayHasKey('read', $document3->getAttribute('documents')[0]->getPermissions()); - $this->assertArrayHasKey('write', $document3->getAttribute('documents')[0]->getPermissions()); - $this->assertEquals('Task #1x', $document3->getAttribute('documents')[0]->getAttribute('name')); - $this->assertCount(4, $document3->getAttribute('documents')[0]->getAttribute('links')); - $this->assertEquals('http://example.com/link-5x', $document3->getAttribute('documents')[0]->getAttribute('links')[0]); - $this->assertEquals('http://example.com/link-6x', $document3->getAttribute('documents')[0]->getAttribute('links')[1]); - $this->assertEquals('http://example.com/link-7x', $document3->getAttribute('documents')[0]->getAttribute('links')[2]); - $this->assertEquals('http://example.com/link-8x', $document3->getAttribute('documents')[0]->getAttribute('links')[3]); +// $this->assertInstanceOf(Document::class, $document3->getAttribute('documents')[0]); +// $this->assertIsString($document3->getAttribute('documents')[0]->getId()); +// $this->assertNotEmpty($document3->getAttribute('documents')[0]->getId()); +// $this->assertIsArray($document3->getAttribute('documents')[0]->getPermissions()); +// $this->assertArrayHasKey('read', $document3->getAttribute('documents')[0]->getPermissions()); +// $this->assertArrayHasKey('write', $document3->getAttribute('documents')[0]->getPermissions()); +// $this->assertEquals('Task #1x', $document3->getAttribute('documents')[0]->getAttribute('name')); +// $this->assertCount(4, $document3->getAttribute('documents')[0]->getAttribute('links')); +// $this->assertEquals('http://example.com/link-5x', $document3->getAttribute('documents')[0]->getAttribute('links')[0]); +// $this->assertEquals('http://example.com/link-6x', $document3->getAttribute('documents')[0]->getAttribute('links')[1]); +// $this->assertEquals('http://example.com/link-7x', $document3->getAttribute('documents')[0]->getAttribute('links')[2]); +// $this->assertEquals('http://example.com/link-8x', $document3->getAttribute('documents')[0]->getAttribute('links')[3]); - $this->assertInstanceOf(Document::class, $document3->getAttribute('documents')[1]); - $this->assertIsString($document3->getAttribute('documents')[1]->getId()); - $this->assertNotEmpty($document3->getAttribute('documents')[1]->getId()); - $this->assertIsArray($document3->getAttribute('documents')[1]->getPermissions()); - $this->assertArrayHasKey('read', $document3->getAttribute('documents')[1]->getPermissions()); - $this->assertArrayHasKey('write', $document3->getAttribute('documents')[1]->getPermissions()); - $this->assertEquals('Task #0', $document3->getAttribute('documents')[1]->getAttribute('name')); - $this->assertCount(4, $document3->getAttribute('documents')[1]->getAttribute('links')); - $this->assertEquals('http://example.com/link-1', $document3->getAttribute('documents')[1]->getAttribute('links')[0]); - $this->assertEquals('http://example.com/link-2', $document3->getAttribute('documents')[1]->getAttribute('links')[1]); - $this->assertEquals('http://example.com/link-3', $document3->getAttribute('documents')[1]->getAttribute('links')[2]); - $this->assertEquals('http://example.com/link-4', $document3->getAttribute('documents')[1]->getAttribute('links')[3]); - - $this->assertInstanceOf(Document::class, $document3->getAttribute('document2')); - $this->assertIsString($document3->getAttribute('document2')->getId()); - $this->assertNotEmpty($document3->getAttribute('document2')->getId()); - $this->assertIsArray($document3->getAttribute('document2')->getPermissions()); - $this->assertArrayHasKey('read', $document3->getAttribute('document2')->getPermissions()); - $this->assertArrayHasKey('write', $document3->getAttribute('document2')->getPermissions()); - $this->assertEquals('Task #1x', $document3->getAttribute('document2')->getAttribute('name')); - $this->assertCount(4, $document3->getAttribute('document2')->getAttribute('links')); - $this->assertEquals('http://example.com/link-5x', $document3->getAttribute('document2')->getAttribute('links')[0]); - $this->assertEquals('http://example.com/link-6x', $document3->getAttribute('document2')->getAttribute('links')[1]); - $this->assertEquals('http://example.com/link-7x', $document3->getAttribute('document2')->getAttribute('links')[2]); - $this->assertEquals('http://example.com/link-8x', $document3->getAttribute('document2')->getAttribute('links')[3]); - - $this->assertInstanceOf(Document::class, $document3->getAttribute('documents2')[0]); - $this->assertIsString($document3->getAttribute('documents2')[0]->getId()); - $this->assertNotEmpty($document3->getAttribute('documents2')[0]->getId()); - $this->assertIsArray($document3->getAttribute('documents2')[0]->getPermissions()); - $this->assertArrayHasKey('read', $document3->getAttribute('documents2')[0]->getPermissions()); - $this->assertArrayHasKey('write', $document3->getAttribute('documents2')[0]->getPermissions()); - $this->assertEquals('Task #0', $document3->getAttribute('documents2')[0]->getAttribute('name')); - $this->assertCount(4, $document3->getAttribute('documents2')[0]->getAttribute('links')); - $this->assertEquals('http://example.com/link-1', $document3->getAttribute('documents2')[0]->getAttribute('links')[0]); - $this->assertEquals('http://example.com/link-2', $document3->getAttribute('documents2')[0]->getAttribute('links')[1]); - $this->assertEquals('http://example.com/link-3', $document3->getAttribute('documents2')[0]->getAttribute('links')[2]); - $this->assertEquals('http://example.com/link-4', $document3->getAttribute('documents2')[0]->getAttribute('links')[3]); - - $this->assertInstanceOf(Document::class, $document3->getAttribute('documents2')[1]); - $this->assertIsString($document3->getAttribute('documents2')[1]->getId()); - $this->assertNotEmpty($document3->getAttribute('documents2')[1]->getId()); - $this->assertIsArray($document3->getAttribute('documents2')[1]->getPermissions()); - $this->assertArrayHasKey('read', $document3->getAttribute('documents2')[1]->getPermissions()); - $this->assertArrayHasKey('write', $document3->getAttribute('documents2')[1]->getPermissions()); - $this->assertEquals('Task #1x', $document3->getAttribute('documents2')[1]->getAttribute('name')); - $this->assertCount(4, $document3->getAttribute('documents2')[1]->getAttribute('links')); - $this->assertEquals('http://example.com/link-5x', $document3->getAttribute('documents2')[1]->getAttribute('links')[0]); - $this->assertEquals('http://example.com/link-6x', $document3->getAttribute('documents2')[1]->getAttribute('links')[1]); - $this->assertEquals('http://example.com/link-7x', $document3->getAttribute('documents2')[1]->getAttribute('links')[2]); - $this->assertEquals('http://example.com/link-8x', $document3->getAttribute('documents2')[1]->getAttribute('links')[3]); +// $this->assertInstanceOf(Document::class, $document3->getAttribute('documents')[1]); +// $this->assertIsString($document3->getAttribute('documents')[1]->getId()); +// $this->assertNotEmpty($document3->getAttribute('documents')[1]->getId()); +// $this->assertIsArray($document3->getAttribute('documents')[1]->getPermissions()); +// $this->assertArrayHasKey('read', $document3->getAttribute('documents')[1]->getPermissions()); +// $this->assertArrayHasKey('write', $document3->getAttribute('documents')[1]->getPermissions()); +// $this->assertEquals('Task #0', $document3->getAttribute('documents')[1]->getAttribute('name')); +// $this->assertCount(4, $document3->getAttribute('documents')[1]->getAttribute('links')); +// $this->assertEquals('http://example.com/link-1', $document3->getAttribute('documents')[1]->getAttribute('links')[0]); +// $this->assertEquals('http://example.com/link-2', $document3->getAttribute('documents')[1]->getAttribute('links')[1]); +// $this->assertEquals('http://example.com/link-3', $document3->getAttribute('documents')[1]->getAttribute('links')[2]); +// $this->assertEquals('http://example.com/link-4', $document3->getAttribute('documents')[1]->getAttribute('links')[3]); + +// $this->assertInstanceOf(Document::class, $document3->getAttribute('document2')); +// $this->assertIsString($document3->getAttribute('document2')->getId()); +// $this->assertNotEmpty($document3->getAttribute('document2')->getId()); +// $this->assertIsArray($document3->getAttribute('document2')->getPermissions()); +// $this->assertArrayHasKey('read', $document3->getAttribute('document2')->getPermissions()); +// $this->assertArrayHasKey('write', $document3->getAttribute('document2')->getPermissions()); +// $this->assertEquals('Task #1x', $document3->getAttribute('document2')->getAttribute('name')); +// $this->assertCount(4, $document3->getAttribute('document2')->getAttribute('links')); +// $this->assertEquals('http://example.com/link-5x', $document3->getAttribute('document2')->getAttribute('links')[0]); +// $this->assertEquals('http://example.com/link-6x', $document3->getAttribute('document2')->getAttribute('links')[1]); +// $this->assertEquals('http://example.com/link-7x', $document3->getAttribute('document2')->getAttribute('links')[2]); +// $this->assertEquals('http://example.com/link-8x', $document3->getAttribute('document2')->getAttribute('links')[3]); + +// $this->assertInstanceOf(Document::class, $document3->getAttribute('documents2')[0]); +// $this->assertIsString($document3->getAttribute('documents2')[0]->getId()); +// $this->assertNotEmpty($document3->getAttribute('documents2')[0]->getId()); +// $this->assertIsArray($document3->getAttribute('documents2')[0]->getPermissions()); +// $this->assertArrayHasKey('read', $document3->getAttribute('documents2')[0]->getPermissions()); +// $this->assertArrayHasKey('write', $document3->getAttribute('documents2')[0]->getPermissions()); +// $this->assertEquals('Task #0', $document3->getAttribute('documents2')[0]->getAttribute('name')); +// $this->assertCount(4, $document3->getAttribute('documents2')[0]->getAttribute('links')); +// $this->assertEquals('http://example.com/link-1', $document3->getAttribute('documents2')[0]->getAttribute('links')[0]); +// $this->assertEquals('http://example.com/link-2', $document3->getAttribute('documents2')[0]->getAttribute('links')[1]); +// $this->assertEquals('http://example.com/link-3', $document3->getAttribute('documents2')[0]->getAttribute('links')[2]); +// $this->assertEquals('http://example.com/link-4', $document3->getAttribute('documents2')[0]->getAttribute('links')[3]); + +// $this->assertInstanceOf(Document::class, $document3->getAttribute('documents2')[1]); +// $this->assertIsString($document3->getAttribute('documents2')[1]->getId()); +// $this->assertNotEmpty($document3->getAttribute('documents2')[1]->getId()); +// $this->assertIsArray($document3->getAttribute('documents2')[1]->getPermissions()); +// $this->assertArrayHasKey('read', $document3->getAttribute('documents2')[1]->getPermissions()); +// $this->assertArrayHasKey('write', $document3->getAttribute('documents2')[1]->getPermissions()); +// $this->assertEquals('Task #1x', $document3->getAttribute('documents2')[1]->getAttribute('name')); +// $this->assertCount(4, $document3->getAttribute('documents2')[1]->getAttribute('links')); +// $this->assertEquals('http://example.com/link-5x', $document3->getAttribute('documents2')[1]->getAttribute('links')[0]); +// $this->assertEquals('http://example.com/link-6x', $document3->getAttribute('documents2')[1]->getAttribute('links')[1]); +// $this->assertEquals('http://example.com/link-7x', $document3->getAttribute('documents2')[1]->getAttribute('links')[2]); +// $this->assertEquals('http://example.com/link-8x', $document3->getAttribute('documents2')[1]->getAttribute('links')[3]); - $this->assertIsInt($document3->getAttribute('integer')); - $this->assertEquals(1, $document3->getAttribute('integer')); - $this->assertIsInt($document3->getAttribute('integers')[0]); - $this->assertIsInt($document3->getAttribute('integers')[1]); - $this->assertIsInt($document3->getAttribute('integers')[2]); - $this->assertEquals([5, 3, 4], $document3->getAttribute('integers')); - $this->assertCount(3, $document3->getAttribute('integers')); - - $this->assertIsFloat($document3->getAttribute('float')); - $this->assertEquals(2.22, $document3->getAttribute('float')); - $this->assertIsFloat($document3->getAttribute('floats')[0]); - $this->assertIsFloat($document3->getAttribute('floats')[1]); - $this->assertIsFloat($document3->getAttribute('floats')[2]); - $this->assertEquals([1.13, 4.33, 8.9999], $document3->getAttribute('floats')); - $this->assertCount(3, $document3->getAttribute('floats')); - - $this->assertIsBool($document3->getAttribute('boolean')); - $this->assertEquals(true, $document3->getAttribute('boolean')); - $this->assertIsBool($document3->getAttribute('booleans')[0]); - $this->assertIsBool($document3->getAttribute('booleans')[1]); - $this->assertIsBool($document3->getAttribute('booleans')[2]); - $this->assertEquals([true, false, true], $document3->getAttribute('booleans')); - $this->assertCount(3, $document3->getAttribute('booleans')); - - $this->assertIsString($document3->getAttribute('email')); - $this->assertEquals('test@appwrite.io', $document3->getAttribute('email')); - $this->assertIsString($document3->getAttribute('emails')[0]); - $this->assertIsString($document3->getAttribute('emails')[1]); - $this->assertIsString($document3->getAttribute('emails')[2]); - $this->assertIsString($document3->getAttribute('emails')[3]); - $this->assertEquals([ - 'test4@appwrite.io', - 'test3@appwrite.io', - 'test2@appwrite.io', - 'test1@appwrite.io' - ], $document3->getAttribute('emails')); - $this->assertCount(4, $document3->getAttribute('emails')); - - $this->assertIsString($document3->getAttribute('url')); - $this->assertEquals('http://example.com/welcome', $document3->getAttribute('url')); - $this->assertIsString($document3->getAttribute('urls')[0]); - $this->assertIsString($document3->getAttribute('urls')[1]); - $this->assertIsString($document3->getAttribute('urls')[2]); - $this->assertEquals([ - 'http://example.com/welcome-1', - 'http://example.com/welcome-2', - 'http://example.com/welcome-3' - ], $document3->getAttribute('urls')); - $this->assertCount(3, $document3->getAttribute('urls')); - - $this->assertIsString($document3->getAttribute('ipv4')); - $this->assertEquals('172.16.254.1', $document3->getAttribute('ipv4')); - $this->assertIsString($document3->getAttribute('ipv4s')[0]); - $this->assertIsString($document3->getAttribute('ipv4s')[1]); - $this->assertEquals([ - '172.16.254.1', - '172.16.254.5' - ], $document3->getAttribute('ipv4s')); - $this->assertCount(2, $document3->getAttribute('ipv4s')); - - $this->assertIsString($document3->getAttribute('ipv6')); - $this->assertEquals('2001:0db8:85a3:0000:0000:8a2e:0370:7334', $document3->getAttribute('ipv6')); - $this->assertIsString($document3->getAttribute('ipv6s')[0]); - $this->assertIsString($document3->getAttribute('ipv6s')[1]); - $this->assertEquals([ - '2001:0db8:85a3:0000:0000:8a2e:0370:7334', - '2001:0db8:85a3:0000:0000:8a2e:0370:7337' - ], $document3->getAttribute('ipv6s')); - $this->assertCount(2, $document3->getAttribute('ipv6s')); - - $this->assertEquals('2001:0db8:85a3:0000:0000:8a2e:0370:7334', $document3->getAttribute('ipv6')); - $this->assertEquals([ - '2001:0db8:85a3:0000:0000:8a2e:0370:7334', - '2001:0db8:85a3:0000:0000:8a2e:0370:7337' - ], $document3->getAttribute('ipv6s')); - $this->assertCount(2, $document3->getAttribute('ipv6s')); - - $this->assertIsString($document3->getAttribute('key')); - $this->assertCount(3, $document3->getAttribute('keys')); - - // Update - - $document3 = self::$object->updateDocument($collection2->getId(), $document3->getId(), [ - '$id' => $document3->getId(), - '$collection' => $collection2->getId(), - '$permissions' => [ - 'read' => ['user:1234'], - 'write' => ['user:1234'], - ], - 'text' => 'Hello Worldx', - 'texts' => ['Hello World 1x', 'Hello World 2x'], - 'document' => $document0, - 'documents' => [$document1, $document0], - 'document2' => $document1, - 'documents2' => [$document0, $document1], - 'integer' => 2, - 'integers' => [6, 4, 5], - 'float' => 3.22, - 'floats' => [2.13, 5.33, 9.9999], - 'numeric' => 2, - 'numerics' => [2, 6, 8.77], - 'boolean' => false, - 'booleans' => [false, true, false], - 'email' => 'testx@appwrite.io', - 'emails' => [ - 'test4x@appwrite.io', - 'test3x@appwrite.io', - 'test2x@appwrite.io', - 'test1x@appwrite.io' - ], - 'url' => 'http://example.com/welcomex', - 'urls' => [ - 'http://example.com/welcome-1x', - 'http://example.com/welcome-2x', - 'http://example.com/welcome-3x' - ], - 'ipv4' => '172.16.254.2', - 'ipv4s' => [ - '172.16.254.2', - '172.16.254.6' - ], - 'ipv6' => '2001:0db8:85a3:0000:0000:8a2e:0370:7335', - 'ipv6s' => [ - '2001:0db8:85a3:0000:0000:8a2e:0370:7335', - '2001:0db8:85a3:0000:0000:8a2e:0370:7338' - ], - 'key' => uniqid().'x', - 'keys' => [uniqid().'x', uniqid().'x', uniqid().'x'], - ]); - - $document3 = self::$object->getDocument($collection2->getId(), $document3->getId()); - - $this->assertIsString($document3->getId()); - $this->assertIsString($document3->getCollection()); - $this->assertEquals([ - 'read' => ['user:1234'], - 'write' => ['user:1234'], - ], $document3->getPermissions()); - $this->assertEquals('Hello Worldx', $document3->getAttribute('text')); - $this->assertCount(2, $document3->getAttribute('texts')); +// $this->assertIsInt($document3->getAttribute('integer')); +// $this->assertEquals(1, $document3->getAttribute('integer')); +// $this->assertIsInt($document3->getAttribute('integers')[0]); +// $this->assertIsInt($document3->getAttribute('integers')[1]); +// $this->assertIsInt($document3->getAttribute('integers')[2]); +// $this->assertEquals([5, 3, 4], $document3->getAttribute('integers')); +// $this->assertCount(3, $document3->getAttribute('integers')); + +// $this->assertIsFloat($document3->getAttribute('float')); +// $this->assertEquals(2.22, $document3->getAttribute('float')); +// $this->assertIsFloat($document3->getAttribute('floats')[0]); +// $this->assertIsFloat($document3->getAttribute('floats')[1]); +// $this->assertIsFloat($document3->getAttribute('floats')[2]); +// $this->assertEquals([1.13, 4.33, 8.9999], $document3->getAttribute('floats')); +// $this->assertCount(3, $document3->getAttribute('floats')); + +// $this->assertIsBool($document3->getAttribute('boolean')); +// $this->assertEquals(true, $document3->getAttribute('boolean')); +// $this->assertIsBool($document3->getAttribute('booleans')[0]); +// $this->assertIsBool($document3->getAttribute('booleans')[1]); +// $this->assertIsBool($document3->getAttribute('booleans')[2]); +// $this->assertEquals([true, false, true], $document3->getAttribute('booleans')); +// $this->assertCount(3, $document3->getAttribute('booleans')); + +// $this->assertIsString($document3->getAttribute('email')); +// $this->assertEquals('test@appwrite.io', $document3->getAttribute('email')); +// $this->assertIsString($document3->getAttribute('emails')[0]); +// $this->assertIsString($document3->getAttribute('emails')[1]); +// $this->assertIsString($document3->getAttribute('emails')[2]); +// $this->assertIsString($document3->getAttribute('emails')[3]); +// $this->assertEquals([ +// 'test4@appwrite.io', +// 'test3@appwrite.io', +// 'test2@appwrite.io', +// 'test1@appwrite.io' +// ], $document3->getAttribute('emails')); +// $this->assertCount(4, $document3->getAttribute('emails')); + +// $this->assertIsString($document3->getAttribute('url')); +// $this->assertEquals('http://example.com/welcome', $document3->getAttribute('url')); +// $this->assertIsString($document3->getAttribute('urls')[0]); +// $this->assertIsString($document3->getAttribute('urls')[1]); +// $this->assertIsString($document3->getAttribute('urls')[2]); +// $this->assertEquals([ +// 'http://example.com/welcome-1', +// 'http://example.com/welcome-2', +// 'http://example.com/welcome-3' +// ], $document3->getAttribute('urls')); +// $this->assertCount(3, $document3->getAttribute('urls')); + +// $this->assertIsString($document3->getAttribute('ipv4')); +// $this->assertEquals('172.16.254.1', $document3->getAttribute('ipv4')); +// $this->assertIsString($document3->getAttribute('ipv4s')[0]); +// $this->assertIsString($document3->getAttribute('ipv4s')[1]); +// $this->assertEquals([ +// '172.16.254.1', +// '172.16.254.5' +// ], $document3->getAttribute('ipv4s')); +// $this->assertCount(2, $document3->getAttribute('ipv4s')); + +// $this->assertIsString($document3->getAttribute('ipv6')); +// $this->assertEquals('2001:0db8:85a3:0000:0000:8a2e:0370:7334', $document3->getAttribute('ipv6')); +// $this->assertIsString($document3->getAttribute('ipv6s')[0]); +// $this->assertIsString($document3->getAttribute('ipv6s')[1]); +// $this->assertEquals([ +// '2001:0db8:85a3:0000:0000:8a2e:0370:7334', +// '2001:0db8:85a3:0000:0000:8a2e:0370:7337' +// ], $document3->getAttribute('ipv6s')); +// $this->assertCount(2, $document3->getAttribute('ipv6s')); + +// $this->assertEquals('2001:0db8:85a3:0000:0000:8a2e:0370:7334', $document3->getAttribute('ipv6')); +// $this->assertEquals([ +// '2001:0db8:85a3:0000:0000:8a2e:0370:7334', +// '2001:0db8:85a3:0000:0000:8a2e:0370:7337' +// ], $document3->getAttribute('ipv6s')); +// $this->assertCount(2, $document3->getAttribute('ipv6s')); + +// $this->assertIsString($document3->getAttribute('key')); +// $this->assertCount(3, $document3->getAttribute('keys')); + +// // Update + +// $document3 = self::$object->updateDocument($collection2->getId(), $document3->getId(), [ +// '$id' => $document3->getId(), +// '$collection' => $collection2->getId(), +// '$permissions' => [ +// 'read' => ['user:1234'], +// 'write' => ['user:1234'], +// ], +// 'text' => 'Hello Worldx', +// 'texts' => ['Hello World 1x', 'Hello World 2x'], +// 'document' => $document0, +// 'documents' => [$document1, $document0], +// 'document2' => $document1, +// 'documents2' => [$document0, $document1], +// 'integer' => 2, +// 'integers' => [6, 4, 5], +// 'float' => 3.22, +// 'floats' => [2.13, 5.33, 9.9999], +// 'numeric' => 2, +// 'numerics' => [2, 6, 8.77], +// 'boolean' => false, +// 'booleans' => [false, true, false], +// 'email' => 'testx@appwrite.io', +// 'emails' => [ +// 'test4x@appwrite.io', +// 'test3x@appwrite.io', +// 'test2x@appwrite.io', +// 'test1x@appwrite.io' +// ], +// 'url' => 'http://example.com/welcomex', +// 'urls' => [ +// 'http://example.com/welcome-1x', +// 'http://example.com/welcome-2x', +// 'http://example.com/welcome-3x' +// ], +// 'ipv4' => '172.16.254.2', +// 'ipv4s' => [ +// '172.16.254.2', +// '172.16.254.6' +// ], +// 'ipv6' => '2001:0db8:85a3:0000:0000:8a2e:0370:7335', +// 'ipv6s' => [ +// '2001:0db8:85a3:0000:0000:8a2e:0370:7335', +// '2001:0db8:85a3:0000:0000:8a2e:0370:7338' +// ], +// 'key' => uniqid().'x', +// 'keys' => [uniqid().'x', uniqid().'x', uniqid().'x'], +// ]); + +// $document3 = self::$object->getDocument($collection2->getId(), $document3->getId()); + +// $this->assertIsString($document3->getId()); +// $this->assertIsString($document3->getCollection()); +// $this->assertEquals([ +// 'read' => ['user:1234'], +// 'write' => ['user:1234'], +// ], $document3->getPermissions()); +// $this->assertEquals('Hello Worldx', $document3->getAttribute('text')); +// $this->assertCount(2, $document3->getAttribute('texts')); - $this->assertIsString($document3->getAttribute('text')); - $this->assertEquals('Hello Worldx', $document3->getAttribute('text')); - $this->assertEquals(['Hello World 1x', 'Hello World 2x'], $document3->getAttribute('texts')); - $this->assertCount(2, $document3->getAttribute('texts')); +// $this->assertIsString($document3->getAttribute('text')); +// $this->assertEquals('Hello Worldx', $document3->getAttribute('text')); +// $this->assertEquals(['Hello World 1x', 'Hello World 2x'], $document3->getAttribute('texts')); +// $this->assertCount(2, $document3->getAttribute('texts')); - $this->assertInstanceOf(Document::class, $document3->getAttribute('document')); - $this->assertIsString($document3->getAttribute('document')->getId()); - $this->assertNotEmpty($document3->getAttribute('document')->getId()); - $this->assertIsArray($document3->getAttribute('document')->getPermissions()); - $this->assertArrayHasKey('read', $document3->getAttribute('document')->getPermissions()); - $this->assertArrayHasKey('write', $document3->getAttribute('document')->getPermissions()); - $this->assertEquals('Task #0', $document3->getAttribute('document')->getAttribute('name')); - $this->assertCount(4, $document3->getAttribute('document')->getAttribute('links')); - $this->assertEquals('http://example.com/link-1', $document3->getAttribute('document')->getAttribute('links')[0]); - $this->assertEquals('http://example.com/link-2', $document3->getAttribute('document')->getAttribute('links')[1]); - $this->assertEquals('http://example.com/link-3', $document3->getAttribute('document')->getAttribute('links')[2]); - $this->assertEquals('http://example.com/link-4', $document3->getAttribute('document')->getAttribute('links')[3]); +// $this->assertInstanceOf(Document::class, $document3->getAttribute('document')); +// $this->assertIsString($document3->getAttribute('document')->getId()); +// $this->assertNotEmpty($document3->getAttribute('document')->getId()); +// $this->assertIsArray($document3->getAttribute('document')->getPermissions()); +// $this->assertArrayHasKey('read', $document3->getAttribute('document')->getPermissions()); +// $this->assertArrayHasKey('write', $document3->getAttribute('document')->getPermissions()); +// $this->assertEquals('Task #0', $document3->getAttribute('document')->getAttribute('name')); +// $this->assertCount(4, $document3->getAttribute('document')->getAttribute('links')); +// $this->assertEquals('http://example.com/link-1', $document3->getAttribute('document')->getAttribute('links')[0]); +// $this->assertEquals('http://example.com/link-2', $document3->getAttribute('document')->getAttribute('links')[1]); +// $this->assertEquals('http://example.com/link-3', $document3->getAttribute('document')->getAttribute('links')[2]); +// $this->assertEquals('http://example.com/link-4', $document3->getAttribute('document')->getAttribute('links')[3]); - $this->assertInstanceOf(Document::class, $document3->getAttribute('documents')[0]); - $this->assertIsString($document3->getAttribute('documents')[0]->getId()); - $this->assertNotEmpty($document3->getAttribute('documents')[0]->getId()); - $this->assertIsArray($document3->getAttribute('documents')[0]->getPermissions()); - $this->assertArrayHasKey('read', $document3->getAttribute('documents')[0]->getPermissions()); - $this->assertArrayHasKey('write', $document3->getAttribute('documents')[0]->getPermissions()); - $this->assertEquals('Task #1x', $document3->getAttribute('documents')[0]->getAttribute('name')); - $this->assertCount(4, $document3->getAttribute('documents')[0]->getAttribute('links')); - $this->assertEquals('http://example.com/link-5x', $document3->getAttribute('documents')[0]->getAttribute('links')[0]); - $this->assertEquals('http://example.com/link-6x', $document3->getAttribute('documents')[0]->getAttribute('links')[1]); - $this->assertEquals('http://example.com/link-7x', $document3->getAttribute('documents')[0]->getAttribute('links')[2]); - $this->assertEquals('http://example.com/link-8x', $document3->getAttribute('documents')[0]->getAttribute('links')[3]); +// $this->assertInstanceOf(Document::class, $document3->getAttribute('documents')[0]); +// $this->assertIsString($document3->getAttribute('documents')[0]->getId()); +// $this->assertNotEmpty($document3->getAttribute('documents')[0]->getId()); +// $this->assertIsArray($document3->getAttribute('documents')[0]->getPermissions()); +// $this->assertArrayHasKey('read', $document3->getAttribute('documents')[0]->getPermissions()); +// $this->assertArrayHasKey('write', $document3->getAttribute('documents')[0]->getPermissions()); +// $this->assertEquals('Task #1x', $document3->getAttribute('documents')[0]->getAttribute('name')); +// $this->assertCount(4, $document3->getAttribute('documents')[0]->getAttribute('links')); +// $this->assertEquals('http://example.com/link-5x', $document3->getAttribute('documents')[0]->getAttribute('links')[0]); +// $this->assertEquals('http://example.com/link-6x', $document3->getAttribute('documents')[0]->getAttribute('links')[1]); +// $this->assertEquals('http://example.com/link-7x', $document3->getAttribute('documents')[0]->getAttribute('links')[2]); +// $this->assertEquals('http://example.com/link-8x', $document3->getAttribute('documents')[0]->getAttribute('links')[3]); - $this->assertInstanceOf(Document::class, $document3->getAttribute('documents')[1]); - $this->assertIsString($document3->getAttribute('documents')[1]->getId()); - $this->assertNotEmpty($document3->getAttribute('documents')[1]->getId()); - $this->assertIsArray($document3->getAttribute('documents')[1]->getPermissions()); - $this->assertArrayHasKey('read', $document3->getAttribute('documents')[1]->getPermissions()); - $this->assertArrayHasKey('write', $document3->getAttribute('documents')[1]->getPermissions()); - $this->assertEquals('Task #0', $document3->getAttribute('documents')[1]->getAttribute('name')); - $this->assertCount(4, $document3->getAttribute('documents')[1]->getAttribute('links')); - $this->assertEquals('http://example.com/link-1', $document3->getAttribute('documents')[1]->getAttribute('links')[0]); - $this->assertEquals('http://example.com/link-2', $document3->getAttribute('documents')[1]->getAttribute('links')[1]); - $this->assertEquals('http://example.com/link-3', $document3->getAttribute('documents')[1]->getAttribute('links')[2]); - $this->assertEquals('http://example.com/link-4', $document3->getAttribute('documents')[1]->getAttribute('links')[3]); - - $this->assertInstanceOf(Document::class, $document3->getAttribute('document2')); - $this->assertIsString($document3->getAttribute('document2')->getId()); - $this->assertNotEmpty($document3->getAttribute('document2')->getId()); - $this->assertIsArray($document3->getAttribute('document2')->getPermissions()); - $this->assertArrayHasKey('read', $document3->getAttribute('document2')->getPermissions()); - $this->assertArrayHasKey('write', $document3->getAttribute('document2')->getPermissions()); - $this->assertEquals('Task #1x', $document3->getAttribute('document2')->getAttribute('name')); - $this->assertCount(4, $document3->getAttribute('document2')->getAttribute('links')); - $this->assertEquals('http://example.com/link-5x', $document3->getAttribute('document2')->getAttribute('links')[0]); - $this->assertEquals('http://example.com/link-6x', $document3->getAttribute('document2')->getAttribute('links')[1]); - $this->assertEquals('http://example.com/link-7x', $document3->getAttribute('document2')->getAttribute('links')[2]); - $this->assertEquals('http://example.com/link-8x', $document3->getAttribute('document2')->getAttribute('links')[3]); - - $this->assertInstanceOf(Document::class, $document3->getAttribute('documents2')[0]); - $this->assertIsString($document3->getAttribute('documents2')[0]->getId()); - $this->assertNotEmpty($document3->getAttribute('documents2')[0]->getId()); - $this->assertIsArray($document3->getAttribute('documents2')[0]->getPermissions()); - $this->assertArrayHasKey('read', $document3->getAttribute('documents2')[0]->getPermissions()); - $this->assertArrayHasKey('write', $document3->getAttribute('documents2')[0]->getPermissions()); - $this->assertEquals('Task #0', $document3->getAttribute('documents2')[0]->getAttribute('name')); - $this->assertCount(4, $document3->getAttribute('documents2')[0]->getAttribute('links')); - $this->assertEquals('http://example.com/link-1', $document3->getAttribute('documents2')[0]->getAttribute('links')[0]); - $this->assertEquals('http://example.com/link-2', $document3->getAttribute('documents2')[0]->getAttribute('links')[1]); - $this->assertEquals('http://example.com/link-3', $document3->getAttribute('documents2')[0]->getAttribute('links')[2]); - $this->assertEquals('http://example.com/link-4', $document3->getAttribute('documents2')[0]->getAttribute('links')[3]); - - $this->assertInstanceOf(Document::class, $document3->getAttribute('documents2')[1]); - $this->assertIsString($document3->getAttribute('documents2')[1]->getId()); - $this->assertNotEmpty($document3->getAttribute('documents2')[1]->getId()); - $this->assertIsArray($document3->getAttribute('documents2')[1]->getPermissions()); - $this->assertArrayHasKey('read', $document3->getAttribute('documents2')[1]->getPermissions()); - $this->assertArrayHasKey('write', $document3->getAttribute('documents2')[1]->getPermissions()); - $this->assertEquals('Task #1x', $document3->getAttribute('documents2')[1]->getAttribute('name')); - $this->assertCount(4, $document3->getAttribute('documents2')[1]->getAttribute('links')); - $this->assertEquals('http://example.com/link-5x', $document3->getAttribute('documents2')[1]->getAttribute('links')[0]); - $this->assertEquals('http://example.com/link-6x', $document3->getAttribute('documents2')[1]->getAttribute('links')[1]); - $this->assertEquals('http://example.com/link-7x', $document3->getAttribute('documents2')[1]->getAttribute('links')[2]); - $this->assertEquals('http://example.com/link-8x', $document3->getAttribute('documents2')[1]->getAttribute('links')[3]); +// $this->assertInstanceOf(Document::class, $document3->getAttribute('documents')[1]); +// $this->assertIsString($document3->getAttribute('documents')[1]->getId()); +// $this->assertNotEmpty($document3->getAttribute('documents')[1]->getId()); +// $this->assertIsArray($document3->getAttribute('documents')[1]->getPermissions()); +// $this->assertArrayHasKey('read', $document3->getAttribute('documents')[1]->getPermissions()); +// $this->assertArrayHasKey('write', $document3->getAttribute('documents')[1]->getPermissions()); +// $this->assertEquals('Task #0', $document3->getAttribute('documents')[1]->getAttribute('name')); +// $this->assertCount(4, $document3->getAttribute('documents')[1]->getAttribute('links')); +// $this->assertEquals('http://example.com/link-1', $document3->getAttribute('documents')[1]->getAttribute('links')[0]); +// $this->assertEquals('http://example.com/link-2', $document3->getAttribute('documents')[1]->getAttribute('links')[1]); +// $this->assertEquals('http://example.com/link-3', $document3->getAttribute('documents')[1]->getAttribute('links')[2]); +// $this->assertEquals('http://example.com/link-4', $document3->getAttribute('documents')[1]->getAttribute('links')[3]); + +// $this->assertInstanceOf(Document::class, $document3->getAttribute('document2')); +// $this->assertIsString($document3->getAttribute('document2')->getId()); +// $this->assertNotEmpty($document3->getAttribute('document2')->getId()); +// $this->assertIsArray($document3->getAttribute('document2')->getPermissions()); +// $this->assertArrayHasKey('read', $document3->getAttribute('document2')->getPermissions()); +// $this->assertArrayHasKey('write', $document3->getAttribute('document2')->getPermissions()); +// $this->assertEquals('Task #1x', $document3->getAttribute('document2')->getAttribute('name')); +// $this->assertCount(4, $document3->getAttribute('document2')->getAttribute('links')); +// $this->assertEquals('http://example.com/link-5x', $document3->getAttribute('document2')->getAttribute('links')[0]); +// $this->assertEquals('http://example.com/link-6x', $document3->getAttribute('document2')->getAttribute('links')[1]); +// $this->assertEquals('http://example.com/link-7x', $document3->getAttribute('document2')->getAttribute('links')[2]); +// $this->assertEquals('http://example.com/link-8x', $document3->getAttribute('document2')->getAttribute('links')[3]); + +// $this->assertInstanceOf(Document::class, $document3->getAttribute('documents2')[0]); +// $this->assertIsString($document3->getAttribute('documents2')[0]->getId()); +// $this->assertNotEmpty($document3->getAttribute('documents2')[0]->getId()); +// $this->assertIsArray($document3->getAttribute('documents2')[0]->getPermissions()); +// $this->assertArrayHasKey('read', $document3->getAttribute('documents2')[0]->getPermissions()); +// $this->assertArrayHasKey('write', $document3->getAttribute('documents2')[0]->getPermissions()); +// $this->assertEquals('Task #0', $document3->getAttribute('documents2')[0]->getAttribute('name')); +// $this->assertCount(4, $document3->getAttribute('documents2')[0]->getAttribute('links')); +// $this->assertEquals('http://example.com/link-1', $document3->getAttribute('documents2')[0]->getAttribute('links')[0]); +// $this->assertEquals('http://example.com/link-2', $document3->getAttribute('documents2')[0]->getAttribute('links')[1]); +// $this->assertEquals('http://example.com/link-3', $document3->getAttribute('documents2')[0]->getAttribute('links')[2]); +// $this->assertEquals('http://example.com/link-4', $document3->getAttribute('documents2')[0]->getAttribute('links')[3]); + +// $this->assertInstanceOf(Document::class, $document3->getAttribute('documents2')[1]); +// $this->assertIsString($document3->getAttribute('documents2')[1]->getId()); +// $this->assertNotEmpty($document3->getAttribute('documents2')[1]->getId()); +// $this->assertIsArray($document3->getAttribute('documents2')[1]->getPermissions()); +// $this->assertArrayHasKey('read', $document3->getAttribute('documents2')[1]->getPermissions()); +// $this->assertArrayHasKey('write', $document3->getAttribute('documents2')[1]->getPermissions()); +// $this->assertEquals('Task #1x', $document3->getAttribute('documents2')[1]->getAttribute('name')); +// $this->assertCount(4, $document3->getAttribute('documents2')[1]->getAttribute('links')); +// $this->assertEquals('http://example.com/link-5x', $document3->getAttribute('documents2')[1]->getAttribute('links')[0]); +// $this->assertEquals('http://example.com/link-6x', $document3->getAttribute('documents2')[1]->getAttribute('links')[1]); +// $this->assertEquals('http://example.com/link-7x', $document3->getAttribute('documents2')[1]->getAttribute('links')[2]); +// $this->assertEquals('http://example.com/link-8x', $document3->getAttribute('documents2')[1]->getAttribute('links')[3]); - $this->assertIsInt($document3->getAttribute('integer')); - $this->assertEquals(2, $document3->getAttribute('integer')); - $this->assertIsInt($document3->getAttribute('integers')[0]); - $this->assertIsInt($document3->getAttribute('integers')[1]); - $this->assertIsInt($document3->getAttribute('integers')[2]); - $this->assertEquals([6, 4, 5], $document3->getAttribute('integers')); - $this->assertCount(3, $document3->getAttribute('integers')); - - $this->assertIsFloat($document3->getAttribute('float')); - $this->assertEquals(3.22, $document3->getAttribute('float')); - $this->assertIsFloat($document3->getAttribute('floats')[0]); - $this->assertIsFloat($document3->getAttribute('floats')[1]); - $this->assertIsFloat($document3->getAttribute('floats')[2]); - $this->assertEquals([2.13, 5.33, 9.9999], $document3->getAttribute('floats')); - $this->assertCount(3, $document3->getAttribute('floats')); - - $this->assertIsBool($document3->getAttribute('boolean')); - $this->assertEquals(false, $document3->getAttribute('boolean')); - $this->assertIsBool($document3->getAttribute('booleans')[0]); - $this->assertIsBool($document3->getAttribute('booleans')[1]); - $this->assertIsBool($document3->getAttribute('booleans')[2]); - $this->assertEquals([false, true, false], $document3->getAttribute('booleans')); - $this->assertCount(3, $document3->getAttribute('booleans')); - - $this->assertIsString($document3->getAttribute('email')); - $this->assertEquals('testx@appwrite.io', $document3->getAttribute('email')); - $this->assertIsString($document3->getAttribute('emails')[0]); - $this->assertIsString($document3->getAttribute('emails')[1]); - $this->assertIsString($document3->getAttribute('emails')[2]); - $this->assertIsString($document3->getAttribute('emails')[3]); - $this->assertEquals([ - 'test4x@appwrite.io', - 'test3x@appwrite.io', - 'test2x@appwrite.io', - 'test1x@appwrite.io' - ], $document3->getAttribute('emails')); - $this->assertCount(4, $document3->getAttribute('emails')); - - $this->assertIsString($document3->getAttribute('url')); - $this->assertEquals('http://example.com/welcomex', $document3->getAttribute('url')); - $this->assertIsString($document3->getAttribute('urls')[0]); - $this->assertIsString($document3->getAttribute('urls')[1]); - $this->assertIsString($document3->getAttribute('urls')[2]); - $this->assertEquals([ - 'http://example.com/welcome-1x', - 'http://example.com/welcome-2x', - 'http://example.com/welcome-3x' - ], $document3->getAttribute('urls')); - $this->assertCount(3, $document3->getAttribute('urls')); - - $this->assertIsString($document3->getAttribute('ipv4')); - $this->assertEquals('172.16.254.2', $document3->getAttribute('ipv4')); - $this->assertIsString($document3->getAttribute('ipv4s')[0]); - $this->assertIsString($document3->getAttribute('ipv4s')[1]); - $this->assertEquals([ - '172.16.254.2', - '172.16.254.6' - ], $document3->getAttribute('ipv4s')); - $this->assertCount(2, $document3->getAttribute('ipv4s')); - - $this->assertIsString($document3->getAttribute('ipv6')); - $this->assertEquals('2001:0db8:85a3:0000:0000:8a2e:0370:7335', $document3->getAttribute('ipv6')); - $this->assertIsString($document3->getAttribute('ipv6s')[0]); - $this->assertIsString($document3->getAttribute('ipv6s')[1]); - $this->assertEquals([ - '2001:0db8:85a3:0000:0000:8a2e:0370:7335', - '2001:0db8:85a3:0000:0000:8a2e:0370:7338' - ], $document3->getAttribute('ipv6s')); - $this->assertCount(2, $document3->getAttribute('ipv6s')); - - $this->assertEquals('2001:0db8:85a3:0000:0000:8a2e:0370:7335', $document3->getAttribute('ipv6')); - $this->assertEquals([ - '2001:0db8:85a3:0000:0000:8a2e:0370:7335', - '2001:0db8:85a3:0000:0000:8a2e:0370:7338' - ], $document3->getAttribute('ipv6s')); - $this->assertCount(2, $document3->getAttribute('ipv6s')); - - $this->assertIsString($document3->getAttribute('key')); - $this->assertCount(3, $document3->getAttribute('keys')); - } - - public function testDeleteDocument() - { - $collection1 = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ - '$collection' => Database::COLLECTION_COLLECTIONS, - '$permissions' => ['read' => ['*']], - 'name' => 'Create Documents', - 'rules' => [ - [ - '$collection' => Database::COLLECTION_RULES, - '$permissions' => ['read' => ['*']], - 'label' => 'Name', - 'key' => 'name', - 'type' => Database::VAR_TEXT, - 'default' => '', - 'required' => true, - 'array' => false, - ], - [ - '$collection' => Database::COLLECTION_RULES, - '$permissions' => ['read' => ['*']], - 'label' => 'Links', - 'key' => 'links', - 'type' => Database::VAR_URL, - 'default' => '', - 'required' => true, - 'array' => true, - ], - ] - ]); - - $this->assertEquals(true, self::$object->createCollection($collection1->getId(), [], [])); +// $this->assertIsInt($document3->getAttribute('integer')); +// $this->assertEquals(2, $document3->getAttribute('integer')); +// $this->assertIsInt($document3->getAttribute('integers')[0]); +// $this->assertIsInt($document3->getAttribute('integers')[1]); +// $this->assertIsInt($document3->getAttribute('integers')[2]); +// $this->assertEquals([6, 4, 5], $document3->getAttribute('integers')); +// $this->assertCount(3, $document3->getAttribute('integers')); + +// $this->assertIsFloat($document3->getAttribute('float')); +// $this->assertEquals(3.22, $document3->getAttribute('float')); +// $this->assertIsFloat($document3->getAttribute('floats')[0]); +// $this->assertIsFloat($document3->getAttribute('floats')[1]); +// $this->assertIsFloat($document3->getAttribute('floats')[2]); +// $this->assertEquals([2.13, 5.33, 9.9999], $document3->getAttribute('floats')); +// $this->assertCount(3, $document3->getAttribute('floats')); + +// $this->assertIsBool($document3->getAttribute('boolean')); +// $this->assertEquals(false, $document3->getAttribute('boolean')); +// $this->assertIsBool($document3->getAttribute('booleans')[0]); +// $this->assertIsBool($document3->getAttribute('booleans')[1]); +// $this->assertIsBool($document3->getAttribute('booleans')[2]); +// $this->assertEquals([false, true, false], $document3->getAttribute('booleans')); +// $this->assertCount(3, $document3->getAttribute('booleans')); + +// $this->assertIsString($document3->getAttribute('email')); +// $this->assertEquals('testx@appwrite.io', $document3->getAttribute('email')); +// $this->assertIsString($document3->getAttribute('emails')[0]); +// $this->assertIsString($document3->getAttribute('emails')[1]); +// $this->assertIsString($document3->getAttribute('emails')[2]); +// $this->assertIsString($document3->getAttribute('emails')[3]); +// $this->assertEquals([ +// 'test4x@appwrite.io', +// 'test3x@appwrite.io', +// 'test2x@appwrite.io', +// 'test1x@appwrite.io' +// ], $document3->getAttribute('emails')); +// $this->assertCount(4, $document3->getAttribute('emails')); + +// $this->assertIsString($document3->getAttribute('url')); +// $this->assertEquals('http://example.com/welcomex', $document3->getAttribute('url')); +// $this->assertIsString($document3->getAttribute('urls')[0]); +// $this->assertIsString($document3->getAttribute('urls')[1]); +// $this->assertIsString($document3->getAttribute('urls')[2]); +// $this->assertEquals([ +// 'http://example.com/welcome-1x', +// 'http://example.com/welcome-2x', +// 'http://example.com/welcome-3x' +// ], $document3->getAttribute('urls')); +// $this->assertCount(3, $document3->getAttribute('urls')); + +// $this->assertIsString($document3->getAttribute('ipv4')); +// $this->assertEquals('172.16.254.2', $document3->getAttribute('ipv4')); +// $this->assertIsString($document3->getAttribute('ipv4s')[0]); +// $this->assertIsString($document3->getAttribute('ipv4s')[1]); +// $this->assertEquals([ +// '172.16.254.2', +// '172.16.254.6' +// ], $document3->getAttribute('ipv4s')); +// $this->assertCount(2, $document3->getAttribute('ipv4s')); + +// $this->assertIsString($document3->getAttribute('ipv6')); +// $this->assertEquals('2001:0db8:85a3:0000:0000:8a2e:0370:7335', $document3->getAttribute('ipv6')); +// $this->assertIsString($document3->getAttribute('ipv6s')[0]); +// $this->assertIsString($document3->getAttribute('ipv6s')[1]); +// $this->assertEquals([ +// '2001:0db8:85a3:0000:0000:8a2e:0370:7335', +// '2001:0db8:85a3:0000:0000:8a2e:0370:7338' +// ], $document3->getAttribute('ipv6s')); +// $this->assertCount(2, $document3->getAttribute('ipv6s')); + +// $this->assertEquals('2001:0db8:85a3:0000:0000:8a2e:0370:7335', $document3->getAttribute('ipv6')); +// $this->assertEquals([ +// '2001:0db8:85a3:0000:0000:8a2e:0370:7335', +// '2001:0db8:85a3:0000:0000:8a2e:0370:7338' +// ], $document3->getAttribute('ipv6s')); +// $this->assertCount(2, $document3->getAttribute('ipv6s')); + +// $this->assertIsString($document3->getAttribute('key')); +// $this->assertCount(3, $document3->getAttribute('keys')); +// } + +// public function testDeleteDocument() +// { +// $collection1 = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ +// '$collection' => Database::COLLECTION_COLLECTIONS, +// '$permissions' => ['read' => ['*']], +// 'name' => 'Create Documents', +// 'rules' => [ +// [ +// '$collection' => Database::COLLECTION_RULES, +// '$permissions' => ['read' => ['*']], +// 'label' => 'Name', +// 'key' => 'name', +// 'type' => Database::VAR_TEXT, +// 'default' => '', +// 'required' => true, +// 'array' => false, +// ], +// [ +// '$collection' => Database::COLLECTION_RULES, +// '$permissions' => ['read' => ['*']], +// 'label' => 'Links', +// 'key' => 'links', +// 'type' => Database::VAR_URL, +// 'default' => '', +// 'required' => true, +// 'array' => true, +// ], +// ] +// ]); + +// $this->assertEquals(true, self::$object->createCollection($collection1->getId(), [], [])); - $document1 = self::$object->createDocument($collection1->getId(), [ - '$collection' => $collection1->getId(), - '$permissions' => [ - 'read' => ['*'], - 'write' => ['user:123'], - ], - 'name' => 'Task #1️⃣', - 'links' => [ - 'http://example.com/link-5', - 'http://example.com/link-6', - 'http://example.com/link-7', - 'http://example.com/link-8', - ], - ]); - - $this->assertNotEmpty($document1->getId()); - $this->assertIsArray($document1->getPermissions()); - $this->assertArrayHasKey('read', $document1->getPermissions()); - $this->assertArrayHasKey('write', $document1->getPermissions()); - $this->assertEquals('Task #1️⃣', $document1->getAttribute('name')); - $this->assertCount(4, $document1->getAttribute('links')); - $this->assertEquals('http://example.com/link-5', $document1->getAttribute('links')[0]); - $this->assertEquals('http://example.com/link-6', $document1->getAttribute('links')[1]); - $this->assertEquals('http://example.com/link-7', $document1->getAttribute('links')[2]); - $this->assertEquals('http://example.com/link-8', $document1->getAttribute('links')[3]); +// $document1 = self::$object->createDocument($collection1->getId(), [ +// '$collection' => $collection1->getId(), +// '$permissions' => [ +// 'read' => ['*'], +// 'write' => ['user:123'], +// ], +// 'name' => 'Task #1️⃣', +// 'links' => [ +// 'http://example.com/link-5', +// 'http://example.com/link-6', +// 'http://example.com/link-7', +// 'http://example.com/link-8', +// ], +// ]); + +// $this->assertNotEmpty($document1->getId()); +// $this->assertIsArray($document1->getPermissions()); +// $this->assertArrayHasKey('read', $document1->getPermissions()); +// $this->assertArrayHasKey('write', $document1->getPermissions()); +// $this->assertEquals('Task #1️⃣', $document1->getAttribute('name')); +// $this->assertCount(4, $document1->getAttribute('links')); +// $this->assertEquals('http://example.com/link-5', $document1->getAttribute('links')[0]); +// $this->assertEquals('http://example.com/link-6', $document1->getAttribute('links')[1]); +// $this->assertEquals('http://example.com/link-7', $document1->getAttribute('links')[2]); +// $this->assertEquals('http://example.com/link-8', $document1->getAttribute('links')[3]); - $document1 = self::$object->getDocument($collection1->getId(), $document1->getId()); - - $this->assertNotEmpty($document1->getId()); - $this->assertIsArray($document1->getPermissions()); - $this->assertArrayHasKey('read', $document1->getPermissions()); - $this->assertArrayHasKey('write', $document1->getPermissions()); - $this->assertEquals('Task #1️⃣', $document1->getAttribute('name')); - $this->assertCount(4, $document1->getAttribute('links')); - $this->assertEquals('http://example.com/link-5', $document1->getAttribute('links')[0]); - $this->assertEquals('http://example.com/link-6', $document1->getAttribute('links')[1]); - $this->assertEquals('http://example.com/link-7', $document1->getAttribute('links')[2]); - $this->assertEquals('http://example.com/link-8', $document1->getAttribute('links')[3]); - - self::$object->deleteDocument($collection1->getId(), $document1->getId()); - - $document1 = self::$object->getDocument($collection1->getId(), $document1->getId()); - - $this->assertTrue($document1->isEmpty()); - $this->assertEmpty($document1->getId()); - $this->assertEmpty($document1->getCollection()); - $this->assertIsArray($document1->getPermissions()); - $this->assertEmpty($document1->getPermissions()); - } - - public function testFind() - { - $data = include __DIR__.'/../../resources/database/movies.php'; - - $collections = $data['collections']; - $movies = $data['movies']; - - foreach ($collections as $key => &$collection) { - $collection = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, $collection); - self::$object->createCollection($collection->getId(), [], []); - } - - foreach ($movies as $key => &$movie) { - $movie['$collection'] = $collection->getId(); - $movie['$permissions'] = []; - $movie = self::$object->createDocument($collection->getId(), $movie); - } - - self::$object->find($collection->getId(), [ - 'limit' => 5, - 'filters' => [ - 'name=Hello World', - 'releaseYear=1999', - 'langauges=English', - ], - ]); - $this->assertEquals('1', '1'); - } - - public function testFindFirst() - { - $this->assertEquals('1', '1'); - } - - public function testFindLast() - { - $this->assertEquals('1', '1'); - } - - public function countTest() - { - $this->assertEquals('1', '1'); - } - - public function addFilterTest() - { - $this->assertEquals('1', '1'); - } - - public function encodeTest() - { - $this->assertEquals('1', '1'); - } - - public function decodeTest() - { - $this->assertEquals('1', '1'); - } -} \ No newline at end of file +// $document1 = self::$object->getDocument($collection1->getId(), $document1->getId()); + +// $this->assertNotEmpty($document1->getId()); +// $this->assertIsArray($document1->getPermissions()); +// $this->assertArrayHasKey('read', $document1->getPermissions()); +// $this->assertArrayHasKey('write', $document1->getPermissions()); +// $this->assertEquals('Task #1️⃣', $document1->getAttribute('name')); +// $this->assertCount(4, $document1->getAttribute('links')); +// $this->assertEquals('http://example.com/link-5', $document1->getAttribute('links')[0]); +// $this->assertEquals('http://example.com/link-6', $document1->getAttribute('links')[1]); +// $this->assertEquals('http://example.com/link-7', $document1->getAttribute('links')[2]); +// $this->assertEquals('http://example.com/link-8', $document1->getAttribute('links')[3]); + +// self::$object->deleteDocument($collection1->getId(), $document1->getId()); + +// $document1 = self::$object->getDocument($collection1->getId(), $document1->getId()); + +// $this->assertTrue($document1->isEmpty()); +// $this->assertEmpty($document1->getId()); +// $this->assertEmpty($document1->getCollection()); +// $this->assertIsArray($document1->getPermissions()); +// $this->assertEmpty($document1->getPermissions()); +// } + +// public function testFind() +// { +// $data = include __DIR__.'/../../resources/database/movies.php'; + +// $collections = $data['collections']; +// $movies = $data['movies']; + +// foreach ($collections as $key => &$collection) { +// $collection = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, $collection); +// self::$object->createCollection($collection->getId(), [], []); +// } + +// foreach ($movies as $key => &$movie) { +// $movie['$collection'] = $collection->getId(); +// $movie['$permissions'] = []; +// $movie = self::$object->createDocument($collection->getId(), $movie); +// } + +// self::$object->find($collection->getId(), [ +// 'limit' => 5, +// 'filters' => [ +// 'name=Hello World', +// 'releaseYear=1999', +// 'langauges=English', +// ], +// ]); +// $this->assertEquals('1', '1'); +// } + +// public function testFindFirst() +// { +// $this->assertEquals('1', '1'); +// } + +// public function testFindLast() +// { +// $this->assertEquals('1', '1'); +// } + +// public function countTest() +// { +// $this->assertEquals('1', '1'); +// } + +// public function addFilterTest() +// { +// $this->assertEquals('1', '1'); +// } + +// public function encodeTest() +// { +// $this->assertEquals('1', '1'); +// } + +// public function decodeTest() +// { +// $this->assertEquals('1', '1'); +// } +// } \ No newline at end of file diff --git a/tests/Database/DocumentTest.php b/tests/Database/DocumentTest.php new file mode 100644 index 000000000..6b72ddfc2 --- /dev/null +++ b/tests/Database/DocumentTest.php @@ -0,0 +1,166 @@ +id = uniqid(); + + $this->collection = uniqid(); + + $this->permissions = [ + 'read' => ['user:123', 'team:123'], + 'write' => ['*'], + ]; + + $this->document = new Document([ + '$id' => $this->id, + '$collection' => $this->collection, + '$permissions' => $this->permissions, + 'title' => 'This is a test.', + 'list' => [ + 'one' + ], + 'children' => [ + new Document(['name' => 'x']), + new Document(['name' => 'y']), + new Document(['name' => 'z']), + ] + ]); + + $this->empty = new Document(); + } + + public function tearDown(): void + { + } + + public function testId() + { + $this->assertEquals($this->id, $this->document->getId()); + $this->assertEquals(null, $this->empty->getId()); + } + + public function testCollection() + { + $this->assertEquals($this->collection, $this->document->getCollection()); + $this->assertEquals(null, $this->empty->getCollection()); + } + + public function testPermissions() + { + $this->assertEquals($this->permissions, $this->document->getPermissions()); + $this->assertEquals([], $this->empty->getPermissions()); + } + + public function testGetAttribute() + { + $this->assertEquals('This is a test.', $this->document->getAttribute('title', '')); + $this->assertEquals('', $this->document->getAttribute('titlex', '')); + } + + public function testSetAttribute() + { + $this->assertEquals('This is a test.', $this->document->getAttribute('title', '')); + $this->assertEquals(['one'], $this->document->getAttribute('list', [])); + $this->assertEquals('', $this->document->getAttribute('titlex', '')); + + $this->document->setAttribute('title', 'New title'); + + $this->assertEquals('New title', $this->document->getAttribute('title', '')); + $this->assertEquals('', $this->document->getAttribute('titlex', '')); + + $this->document->setAttribute('list', 'two', Document::SET_TYPE_APPEND); + $this->assertEquals(['one', 'two'], $this->document->getAttribute('list', [])); + + $this->document->setAttribute('list', 'zero', Document::SET_TYPE_PREPEND); + $this->assertEquals(['zero', 'one', 'two'], $this->document->getAttribute('list', [])); + + $this->document->setAttribute('list', ['one'], Document::SET_TYPE_ASSIGN); + $this->assertEquals(['one'], $this->document->getAttribute('list', [])); + } + + public function testRemoveAttribute() + { + $this->document->removeAttribute('list'); + $this->assertEquals([], $this->document->getAttribute('list', [])); + } + + public function testSearch() + { + $this->assertEquals(null, $this->document->search('find', 'one')); + + $this->document->setAttribute('findString', 'demo'); + $this->assertEquals($this->document, $this->document->search('findString', 'demo')); + + $this->document->setAttribute('findArray', ['demo']); + $this->assertEquals(null, $this->document->search('findArray', 'demo')); + $this->assertEquals($this->document, $this->document->search('findArray', ['demo'])); + + $this->assertEquals($this->document->getAttribute('children')[0], $this->document->search('name', 'x', $this->document->getAttribute('children'))); + $this->assertEquals($this->document->getAttribute('children')[2], $this->document->search('name', 'z', $this->document->getAttribute('children'))); + $this->assertEquals(null, $this->document->search('name', 'v', $this->document->getAttribute('children'))); + } + + public function testIsEmpty() + { + $this->assertEquals(false, $this->document->isEmpty()); + $this->assertEquals(true, $this->empty->isEmpty()); + } + + public function testIsSet() + { + $this->assertEquals(false, $this->document->isSet('titlex')); + $this->assertEquals(false, $this->empty->isSet('titlex')); + $this->assertEquals(true, $this->document->isSet('title')); + } + + public function testGetArrayCopy() + { + $this->assertEquals([ + '$id' => $this->id, + '$collection' => $this->collection, + '$permissions' => $this->permissions, + 'title' => 'This is a test.', + 'list' => [ + 'one' + ], + 'children' => [ + ['name' => 'x'], + ['name' => 'y'], + ['name' => 'z'], + ] + ], $this->document->getArrayCopy()); + $this->assertEquals([], $this->empty->getArrayCopy()); + } +} \ No newline at end of file diff --git a/tests/Database/Validator/PermissionsTest.php b/tests/Database/Validator/PermissionsTest.php index 058ff6377..0d252a622 100644 --- a/tests/Database/Validator/PermissionsTest.php +++ b/tests/Database/Validator/PermissionsTest.php @@ -55,14 +55,6 @@ public function testValues() $this->assertEquals($object->isValid($document->getPermissions()), false); - $document = new Document([ - '$id' => uniqid(), - '$collection' => uniqid(), - '$permissions' => 'test', - ]); - - $this->assertEquals($object->isValid($document->getPermissions()), false); - $document = new Document([ '$id' => uniqid(), '$collection' => uniqid(), From 3320f6d266b1531047cf2368786d1e483c56f773 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 25 Jan 2021 00:22:09 +0200 Subject: [PATCH 014/190] Updated tests --- Dockerfile | 33 + composer.json | 3 + docker-compose.yml | 23 +- src/Database/Adapter.php | 246 +-- src/Database/Adapter/MariaDB.php | 4 - src/Database/Adapter/Postgres.php | 72 + src/Database/Database.php | 10 +- src/Database/Validator/Structure.php | 2 +- tests/Database/Adapter/MariaDBTest.php | 47 + tests/Database/Adapter/PostgresTest.php | 46 + tests/Database/Base.php | 1526 +++++++++++++ tests/Database/DatabaseTest.php | 1907 ----------------- .../Database/Validator/AuthorizationTest.php | 2 +- tests/Database/Validator/KeyTest.php | 2 +- tests/Database/Validator/PermissionsTest.php | 2 +- tests/Database/Validator/UIDTest.php | 2 +- 16 files changed, 1885 insertions(+), 2042 deletions(-) create mode 100755 Dockerfile create mode 100644 src/Database/Adapter/Postgres.php create mode 100644 tests/Database/Adapter/MariaDBTest.php create mode 100644 tests/Database/Adapter/PostgresTest.php create mode 100644 tests/Database/Base.php delete mode 100644 tests/Database/DatabaseTest.php diff --git a/Dockerfile b/Dockerfile new file mode 100755 index 000000000..647947a55 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,33 @@ +FROM composer:2.0 as step0 + +ARG TESTING=false +ENV TESTING=$TESTING + +WORKDIR /usr/local/src/ + +COPY composer.lock /usr/local/src/ +COPY composer.json /usr/local/src/ + +RUN composer update --ignore-platform-reqs --optimize-autoloader \ + --no-plugins --no-scripts --prefer-dist + +FROM php:7.4-cli-alpine as final + +LABEL maintainer="team@appwrite.io" + +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +RUN \ + apk update \ + && apk add --no-cache postgresql-libs postgresql-dev \ + && docker-php-ext-install opcache pgsql pdo_mysql pdo_pgsql \ + && rm -rf /var/cache/apk/* + +WORKDIR /usr/src/code + +COPY --from=step0 /usr/local/src/vendor /usr/src/code/vendor + +# Add Source Code +COPY ./ /usr/src/code + +CMD [ "tail", "-f", "/dev/null" ] diff --git a/composer.json b/composer.json index 2cd2ca6e1..89f94c253 100755 --- a/composer.json +++ b/composer.json @@ -14,6 +14,9 @@ "autoload": { "psr-4": {"Utopia\\Database\\": "src/Database"} }, + "autoload-dev": { + "psr-4": {"Utopia\\Tests\\": "tests/Database"} + }, "require": { "php": ">=7.1", "ext-pdo": "*", diff --git a/docker-compose.yml b/docker-compose.yml index d83c7cbca..a2ba5a4ae 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,17 +2,31 @@ version: '3.1' services: + tests: + container_name: tests + build: + context: . + networks: + - database + volumes: + - ./:/usr/src/code + postgres: image: postgres:13 container_name: postgres + networks: + - database ports: - - "8700:8080" + - "8700:5432" environment: + POSTGRES_USER: root POSTGRES_PASSWORD: password mariadb: image: mariadb:10.5 container_name: mariadb + networks: + - database ports: - "8701:3306" environment: @@ -21,8 +35,13 @@ services: mongo: image: mongo:3.6 container_name: mongo + networks: + - database ports: - "8702:27017" environment: MONGO_INITDB_ROOT_USERNAME: root - MONGO_INITDB_ROOT_PASSWORD: example \ No newline at end of file + MONGO_INITDB_ROOT_PASSWORD: example + +networks: + database: \ No newline at end of file diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index 2fb8a746f..258123db3 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -105,129 +105,129 @@ abstract public function create(string $name): bool; */ abstract public function delete(string $name): bool; - /** - * Create Collection - * - * @param Document $collection - * @param string $id - * - * @return bool - */ - abstract public function createCollection(Document $collection, string $id): bool; - - /** - * Delete Collection - * - * @param Document $collection - * - * @return bool - */ - abstract public function deleteCollection(Document $collection): bool; - - /** - * Create Attribute - * - * @param Document $collection - * @param string $id - * @param string $type - * @param bool $array - * - * @return bool - */ - abstract public function createAttribute(Document $collection, string $id, string $type, bool $array = false): bool; - - /** - * Delete Attribute - * - * @param Document $collection - * @param string $id - * @param bool $array - * - * @return bool - */ - abstract public function deleteAttribute(Document $collection, string $id, bool $array = false): bool; - - /** - * Create Index - * - * @param Document $collection - * @param string $id - * @param string $type - * @param array $attributes - * - * @return bool - */ - abstract public function createIndex(Document $collection, string $id, string $type, array $attributes): bool; - - /** - * Delete Index - * - * @param Document $collection - * @param string $id - * - * @return bool - */ - abstract public function deleteIndex(Document $collection, string $id): bool; - - /** - * Get Document. - * - * @param Document $collection - * @param string $id - * - * @return array - */ - abstract public function getDocument(Document $collection, $id); - - /** - * Create Document - * - * @param Document $collection - * @param array $data - * @param array $unique - * - * @return array - */ - abstract public function createDocument(Document $collection, array $data, array $unique = []); - - /** - * Update Document. - * - * @param Document $collection - * @param array $data - * - * @return array - */ - abstract public function updateDocument(Document $collection, string $id, array $data); - - /** - * Delete Node. - * - * @param Document $collection - * @param string $id - * - * @return array - */ - abstract public function deleteDocument(Document $collection, string $id); - - /** - * Find. - * - * Find data sets using chosen queries - * - * @param Document $collection - * @param array $options - * - * @return array - */ - abstract public function find(Document $collection, array $options); - - /** - * @param array $options - * - * @return int - */ - abstract public function count(array $options); + // /** + // * Create Collection + // * + // * @param Document $collection + // * @param string $id + // * + // * @return bool + // */ + // abstract public function createCollection(Document $collection, string $id): bool; + + // /** + // * Delete Collection + // * + // * @param Document $collection + // * + // * @return bool + // */ + // abstract public function deleteCollection(Document $collection): bool; + + // /** + // * Create Attribute + // * + // * @param Document $collection + // * @param string $id + // * @param string $type + // * @param bool $array + // * + // * @return bool + // */ + // abstract public function createAttribute(Document $collection, string $id, string $type, bool $array = false): bool; + + // /** + // * Delete Attribute + // * + // * @param Document $collection + // * @param string $id + // * @param bool $array + // * + // * @return bool + // */ + // abstract public function deleteAttribute(Document $collection, string $id, bool $array = false): bool; + + // /** + // * Create Index + // * + // * @param Document $collection + // * @param string $id + // * @param string $type + // * @param array $attributes + // * + // * @return bool + // */ + // abstract public function createIndex(Document $collection, string $id, string $type, array $attributes): bool; + + // /** + // * Delete Index + // * + // * @param Document $collection + // * @param string $id + // * + // * @return bool + // */ + // abstract public function deleteIndex(Document $collection, string $id): bool; + + // /** + // * Get Document. + // * + // * @param Document $collection + // * @param string $id + // * + // * @return array + // */ + // abstract public function getDocument(Document $collection, $id); + + // /** + // * Create Document + // * + // * @param Document $collection + // * @param array $data + // * @param array $unique + // * + // * @return array + // */ + // abstract public function createDocument(Document $collection, array $data, array $unique = []); + + // /** + // * Update Document. + // * + // * @param Document $collection + // * @param array $data + // * + // * @return array + // */ + // abstract public function updateDocument(Document $collection, string $id, array $data); + + // /** + // * Delete Node. + // * + // * @param Document $collection + // * @param string $id + // * + // * @return array + // */ + // abstract public function deleteDocument(Document $collection, string $id); + + // /** + // * Find. + // * + // * Find data sets using chosen queries + // * + // * @param Document $collection + // * @param array $options + // * + // * @return array + // */ + // abstract public function find(Document $collection, array $options); + + // /** + // * @param array $options + // * + // * @return int + // */ + // abstract public function count(array $options); /** * Get Unique ID. diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index de23300ae..4c8569deb 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -3,10 +3,6 @@ namespace Utopia\Database\Adapter; use Utopia\Database\Adapter; -use Utopia\Database\Database; -use Utopia\Database\Document; -use Utopia\Database\Exception\Duplicate; -use Utopia\Database\Validator\Authorization; use Exception; use PDO; diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php new file mode 100644 index 000000000..25953d399 --- /dev/null +++ b/src/Database/Adapter/Postgres.php @@ -0,0 +1,72 @@ +pdo = $pdo; + } + + /** + * Create Database + * + * @param string $name + * @return bool + */ + public function create(string $name): bool + { + return $this->getPDO() + ->prepare('CREATE DATABASE '.$this->getNamespace().'_'.$name.' /*!40100 DEFAULT CHARACTER SET utf8mb4 */;') + ->execute(); + } + + /** + * Delete Database + * + * @param string $name + * @return bool + */ + public function delete(string $name): bool + { + return $this->getPDO() + ->prepare('DROP DATABASE '.$this->getNamespace().'_'.$name.';') + ->execute(); + } + + /** + * @return PDO + * + * @throws Exception + */ + protected function getPDO() + { + return $this->pdo; + } +} \ No newline at end of file diff --git a/src/Database/Database.php b/src/Database/Database.php index 1eb40d96d..02ec4f3ff 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -11,7 +11,7 @@ class Database { // Simple Types - const VAR_TEXT = 'text'; + const VAR_STRING = 'text'; const VAR_NUMBER = 'integer'; const VAR_BOOLEAN = 'boolean'; const VAR_NULL = 'null'; @@ -40,6 +40,14 @@ class Database */ static public $filters = []; + /** + * @param Adapter $adapter + */ + public function __construct(Adapter $adapter) + { + $this->adapter = $adapter; + } + /** * Set Namespace. * diff --git a/src/Database/Validator/Structure.php b/src/Database/Validator/Structure.php index 12e9f4808..71857db6e 100644 --- a/src/Database/Validator/Structure.php +++ b/src/Database/Validator/Structure.php @@ -173,7 +173,7 @@ public function isValid($document) case Database::VAR_KEY: $validator = new Key(); break; - case Database::VAR_TEXT: + case Database::VAR_STRING: case self::RULE_TYPE_MARKDOWN: $validator = new Validator\Text(0); break; diff --git a/tests/Database/Adapter/MariaDBTest.php b/tests/Database/Adapter/MariaDBTest.php new file mode 100644 index 000000000..5b0f3a64f --- /dev/null +++ b/tests/Database/Adapter/MariaDBTest.php @@ -0,0 +1,47 @@ + 'SET NAMES utf8mb4', + PDO::ATTR_TIMEOUT => 3, // Seconds + PDO::ATTR_PERSISTENT => true + )); + + // Connection settings + $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); // Return arrays + $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // Handle all errors with exceptions + + $database = new Database(new MariaDB($pdo)); + $database->setNamespace('myapp_'.uniqid()); + + return self::$database = $database; + } +} \ No newline at end of file diff --git a/tests/Database/Adapter/PostgresTest.php b/tests/Database/Adapter/PostgresTest.php new file mode 100644 index 000000000..da70c5fa5 --- /dev/null +++ b/tests/Database/Adapter/PostgresTest.php @@ -0,0 +1,46 @@ + 'SET NAMES utf8mb4', + PDO::ATTR_TIMEOUT => 3, // Seconds + PDO::ATTR_PERSISTENT => true + )); + + // Connection settings + $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); // Return arrays + $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // Handle all errors with exceptions + + $database = new Database(new Postgres($pdo)); + $database->setNamespace('myapp_'.uniqid()); + + return self::$database = $database; + } +} \ No newline at end of file diff --git a/tests/Database/Base.php b/tests/Database/Base.php new file mode 100644 index 000000000..944373342 --- /dev/null +++ b/tests/Database/Base.php @@ -0,0 +1,1526 @@ +assertEquals(true, static::getDatabase()->create('test_database')); + } + + public function testDelete() + { + $this->assertEquals(true, static::getDatabase()->delete('test_database')); + } + + // public function testCreateCollection() + // { + // $collection = self::$database->createDocument(Database::COLLECTION_COLLECTIONS, [ + // '$collection' => Database::COLLECTION_COLLECTIONS, + // '$permissions' => ['read' => ['*']], + // 'name' => 'Create', + // ]); + + // $this->assertEquals(true, self::$database->createCollection($collection->getId(), [], [])); + + // try { + // self::$database->createCollection($collection->getId(), [], []); + // } + // catch (\Throwable $th) { + // return $this->assertEquals('42S01', $th->getCode()); + // } + + // throw new Exception('Expected exception'); + // } + + // public function testDeleteCollection() + // { + // $collection = self::$database->createDocument(Database::COLLECTION_COLLECTIONS, [ + // '$collection' => Database::COLLECTION_COLLECTIONS, + // '$permissions' => ['read' => ['*']], + // 'name' => 'Delete', + // ]); + + // $this->assertEquals(true, self::$database->createCollection($collection->getId(), [], [])); + // $this->assertEquals(true, self::$database->deleteCollection($collection->getId())); + + // try { + // self::$database->deleteCollection($collection->getId()); + // } + // catch (\Throwable $th) { + // return $this->assertEquals('42S02', $th->getCode()); + // } + + // throw new Exception('Expected exception'); + // } + + // public function testCreateAttribute() + // { + // $collection = self::$database->createDocument(Database::COLLECTION_COLLECTIONS, [ + // '$collection' => Database::COLLECTION_COLLECTIONS, + // '$permissions' => ['read' => ['*']], + // 'name' => 'Create Attribute', + // 'rules' => [], + // ]); + + // $this->assertEquals(true, self::$database->createCollection($collection->getId(), [], [])); + // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'title', Database::VAR_STRING)); + // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'description', Database::VAR_STRING)); + // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'numeric', Database::VAR_NUMBER)); + // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'integer', Database::VAR_NUMBER)); + // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'float', Database::VAR_NUMBER)); + // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'boolean', Database::VAR_BOOLEAN)); + // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'document', Database::VAR_DOCUMENT)); + // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'email', Database::VAR_STRING)); + // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'url', Database::VAR_STRING)); + // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'ipv4', Database::VAR_STRING)); + // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'ipv6', Database::VAR_STRING)); + // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'key', Database::VAR_STRING)); + + // // arrays + // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'titles', Database::VAR_STRING, true)); + // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'descriptions', Database::VAR_STRING, true)); + // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'numerics', Database::VAR_NUMBER, true)); + // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'integers', Database::VAR_NUMBER, true)); + // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'floats', Database::VAR_NUMBER, true)); + // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'booleans', Database::VAR_BOOLEAN, true)); + // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'documents', Database::VAR_DOCUMENT, true)); + // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'emails', Database::VAR_STRING, true)); + // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'urls', Database::VAR_STRING, true)); + // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'ipv4s', Database::VAR_STRING, true)); + // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'ipv6s', Database::VAR_STRING, true)); + // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'keys', Database::VAR_STRING, true)); + // } + + // public function testDeleteAttribute() + // { + // $collection = self::$database->createDocument(Database::COLLECTION_COLLECTIONS, [ + // '$collection' => Database::COLLECTION_COLLECTIONS, + // '$permissions' => ['read' => ['*']], + // 'name' => 'Delete Attribute', + // 'rules' => [], + // ]); + + // $this->assertEquals(true, self::$database->createCollection($collection->getId(), [], [])); + + // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'title', Database::VAR_STRING)); + // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'description', Database::VAR_STRING)); + // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'value', Database::VAR_NUMBER)); + + // $this->assertEquals(true, self::$database->deleteAttribute($collection->getId(), 'title', false)); + // $this->assertEquals(true, self::$database->deleteAttribute($collection->getId(), 'description', false)); + // $this->assertEquals(true, self::$database->deleteAttribute($collection->getId(), 'value', false)); + + // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'titles', Database::VAR_STRING, true)); + // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'descriptions', Database::VAR_STRING, true)); + // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'values', Database::VAR_NUMBER, true)); + + // $this->assertEquals(true, self::$database->deleteAttribute($collection->getId(), 'titles', true)); + // $this->assertEquals(true, self::$database->deleteAttribute($collection->getId(), 'descriptions', true)); + // $this->assertEquals(true, self::$database->deleteAttribute($collection->getId(), 'values', true)); + // } + + // public function testCreateIndex() + // { + // $collection = self::$database->createDocument(Database::COLLECTION_COLLECTIONS, [ + // '$collection' => Database::COLLECTION_COLLECTIONS, + // '$permissions' => ['read' => ['*']], + // 'name' => 'Create Index', + // 'rules' => [], + // ]); + + // $this->assertEquals(true, self::$database->createCollection($collection->getId(), [], [])); + // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'title', Database::VAR_STRING)); + // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'description', Database::VAR_STRING)); + // $this->assertEquals(true, self::$database->createIndex($collection->getId(), 'x', Database::INDEX_KEY, ['title'])); + // $this->assertEquals(true, self::$database->createIndex($collection->getId(), 'y', Database::INDEX_KEY, ['description'])); + // $this->assertEquals(true, self::$database->createIndex($collection->getId(), 'z', Database::INDEX_KEY, ['title', 'description'])); + // } + + // public function testDeleteIndex() + // { + // $collection = self::$database->createDocument(Database::COLLECTION_COLLECTIONS, [ + // '$collection' => Database::COLLECTION_COLLECTIONS, + // '$permissions' => ['read' => ['*']], + // 'name' => 'Delete Index', + // 'rules' => [], + // ]); + + // $this->assertEquals(true, self::$database->createCollection($collection->getId(), [], [])); + // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'title', Database::VAR_STRING)); + // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'description', Database::VAR_STRING)); + // $this->assertEquals(true, self::$database->createIndex($collection->getId(), 'x', Database::INDEX_KEY, ['title'])); + // $this->assertEquals(true, self::$database->createIndex($collection->getId(), 'y', Database::INDEX_KEY, ['description'])); + // $this->assertEquals(true, self::$database->createIndex($collection->getId(), 'z', Database::INDEX_KEY, ['title', 'description'])); + + // $this->assertEquals(true, self::$database->deleteIndex($collection->getId(), 'x')); + // $this->assertEquals(true, self::$database->deleteIndex($collection->getId(), 'y')); + // $this->assertEquals(true, self::$database->deleteIndex($collection->getId(), 'z')); + // } + + // public function testCreateDocument() + // { + // $collection1 = self::$database->createDocument(Database::COLLECTION_COLLECTIONS, [ + // '$collection' => Database::COLLECTION_COLLECTIONS, + // '$permissions' => ['read' => ['*']], + // 'name' => 'Create Documents', + // 'rules' => [ + // [ + // '$collection' => Database::COLLECTION_RULES, + // '$permissions' => ['read' => ['*']], + // 'label' => 'Name', + // 'key' => 'name', + // 'type' => Database::VAR_STRING, + // 'default' => '', + // 'required' => true, + // 'array' => false, + // ], + // [ + // '$collection' => Database::COLLECTION_RULES, + // '$permissions' => ['read' => ['*']], + // 'label' => 'Links', + // 'key' => 'links', + // 'type' => Database::VAR_STRING, + // 'default' => '', + // 'required' => true, + // 'array' => true, + // ], + // ] + // ]); + + // $this->assertEquals(true, self::$database->createCollection($collection1->getId(), [], [])); + + // $document0 = new Document([ + // '$collection' => $collection1->getId(), + // '$permissions' => [ + // 'read' => ['*'], + // 'write' => ['user:123'], + // ], + // 'name' => 'Task #0', + // 'links' => [ + // 'http://example.com/link-1', + // 'http://example.com/link-2', + // 'http://example.com/link-3', + // 'http://example.com/link-4', + // ], + // ]); + + // $document1 = self::$database->createDocument($collection1->getId(), [ + // '$collection' => $collection1->getId(), + // '$permissions' => [ + // 'read' => ['*'], + // 'write' => ['user:123'], + // ], + // 'name' => 'Task #1️⃣', + // 'links' => [ + // 'http://example.com/link-5', + // 'http://example.com/link-6', + // 'http://example.com/link-7', + // 'http://example.com/link-8', + // ], + // ]); + + // $this->assertNotEmpty($document1->getId()); + // $this->assertIsArray($document1->getPermissions()); + // $this->assertArrayHasKey('read', $document1->getPermissions()); + // $this->assertArrayHasKey('write', $document1->getPermissions()); + // $this->assertEquals('Task #1️⃣', $document1->getAttribute('name')); + // $this->assertCount(4, $document1->getAttribute('links')); + // $this->assertEquals('http://example.com/link-5', $document1->getAttribute('links')[0]); + // $this->assertEquals('http://example.com/link-6', $document1->getAttribute('links')[1]); + // $this->assertEquals('http://example.com/link-7', $document1->getAttribute('links')[2]); + // $this->assertEquals('http://example.com/link-8', $document1->getAttribute('links')[3]); + + // $document2 = self::$database->createDocument(Database::COLLECTION_USERS, [ + // '$collection' => Database::COLLECTION_USERS, + // '$permissions' => [ + // 'read' => ['*'], + // 'write' => ['user:123'], + // ], + // 'email' => 'test@appwrite.io', + // 'emailVerification' => false, + // 'status' => 0, + // 'password' => 'secrethash', + // 'password-update' => \time(), + // 'registration' => \time(), + // 'reset' => false, + // 'name' => 'Test', + // ]); + + // $this->assertNotEmpty($document2->getId()); + // $this->assertIsArray($document2->getPermissions()); + // $this->assertArrayHasKey('read', $document2->getPermissions()); + // $this->assertArrayHasKey('write', $document2->getPermissions()); + // $this->assertEquals('test@appwrite.io', $document2->getAttribute('email')); + // $this->assertIsString($document2->getAttribute('email')); + // $this->assertEquals(0, $document2->getAttribute('status')); + // $this->assertIsInt($document2->getAttribute('status')); + // $this->assertEquals(false, $document2->getAttribute('emailVerification')); + // $this->assertIsBool($document2->getAttribute('emailVerification')); + + // $document2 = self::$database->getDocument(Database::COLLECTION_USERS, $document2->getId()); + + // $this->assertNotEmpty($document2->getId()); + // $this->assertIsArray($document2->getPermissions()); + // $this->assertArrayHasKey('read', $document2->getPermissions()); + // $this->assertArrayHasKey('write', $document2->getPermissions()); + // $this->assertEquals('test@appwrite.io', $document2->getAttribute('email')); + // $this->assertIsString($document2->getAttribute('email')); + // $this->assertEquals(0, $document2->getAttribute('status')); + // $this->assertIsInt($document2->getAttribute('status')); + // $this->assertEquals(false, $document2->getAttribute('emailVerification')); + // $this->assertIsBool($document2->getAttribute('emailVerification')); + + // $types = [ + // Database::VAR_STRING, + // Database::VAR_NUMBER, + // Database::VAR_BOOLEAN, + // Database::VAR_DOCUMENT, + // ]; + + // $rules = []; + + // foreach($types as $type) { + // $rules[] = [ + // '$collection' => Database::COLLECTION_RULES, + // '$permissions' => ['read' => ['*']], + // 'label' => ucfirst($type), + // 'key' => $type, + // 'type' => $type, + // 'default' => null, + // 'required' => true, + // 'array' => false, + // 'list' => ($type === Database::VAR_DOCUMENT) ? [$collection1->getId()] : [], + // ]; + + // $rules[] = [ + // '$collection' => Database::COLLECTION_RULES, + // '$permissions' => ['read' => ['*']], + // 'label' => ucfirst($type), + // 'key' => $type.'s', + // 'type' => $type, + // 'default' => null, + // 'required' => true, + // 'array' => true, + // 'list' => ($type === Database::VAR_DOCUMENT) ? [$collection1->getId()] : [], + // ]; + // } + + // $rules[] = [ + // '$collection' => Database::COLLECTION_RULES, + // '$permissions' => ['read' => ['*']], + // 'label' => 'document2', + // 'key' => 'document2', + // 'type' => Database::VAR_DOCUMENT, + // 'default' => null, + // 'required' => true, + // 'array' => false, + // 'list' => [$collection1->getId()], + // ]; + + // $rules[] = [ + // '$collection' => Database::COLLECTION_RULES, + // '$permissions' => ['read' => ['*']], + // 'label' => 'documents2', + // 'key' => 'documents2', + // 'type' => Database::VAR_DOCUMENT, + // 'default' => null, + // 'required' => true, + // 'array' => true, + // 'list' => [$collection1->getId()], + // ]; + + // $collection2 = self::$database->createDocument(Database::COLLECTION_COLLECTIONS, [ + // '$collection' => Database::COLLECTION_COLLECTIONS, + // '$permissions' => ['read' => ['*']], + // 'name' => 'Create Documents', + // 'rules' => $rules, + // ]); + + // $this->assertEquals(true, self::$database->createCollection($collection2->getId(), [], [])); + + // $document3 = self::$database->createDocument($collection2->getId(), [ + // '$collection' => $collection2->getId(), + // '$permissions' => [ + // 'read' => ['*'], + // 'write' => ['user:123'], + // ], + // 'text' => 'Hello World', + // 'texts' => ['Hello World 1', 'Hello World 2'], + // // 'document' => $document0, + // // 'documents' => [$document0], + // 'document' => $document0, + // 'documents' => [$document1, $document0], + // 'document2' => $document1, + // 'documents2' => [$document0, $document1], + // 'integer' => 1, + // 'integers' => [5, 3, 4], + // 'float' => 2.22, + // 'floats' => [1.13, 4.33, 8.9999], + // 'numeric' => 1, + // 'numerics' => [1, 5, 7.77], + // 'boolean' => true, + // 'booleans' => [true, false, true], + // 'email' => 'test@appwrite.io', + // 'emails' => [ + // 'test4@appwrite.io', + // 'test3@appwrite.io', + // 'test2@appwrite.io', + // 'test1@appwrite.io' + // ], + // 'url' => 'http://example.com/welcome', + // 'urls' => [ + // 'http://example.com/welcome-1', + // 'http://example.com/welcome-2', + // 'http://example.com/welcome-3' + // ], + // 'ipv4' => '172.16.254.1', + // 'ipv4s' => [ + // '172.16.254.1', + // '172.16.254.5' + // ], + // 'ipv6' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334', + // 'ipv6s' => [ + // '2001:0db8:85a3:0000:0000:8a2e:0370:7334', + // '2001:0db8:85a3:0000:0000:8a2e:0370:7337' + // ], + // 'key' => uniqid(), + // 'keys' => [uniqid(), uniqid(), uniqid()], + // ]); + + // $document3 = self::$database->getDocument($collection2->getId(), $document3->getId()); + + // $this->assertIsString($document3->getId()); + // $this->assertIsString($document3->getCollection()); + // $this->assertEquals([ + // 'read' => ['*'], + // 'write' => ['user:123'], + // ], $document3->getPermissions()); + // $this->assertEquals('Hello World', $document3->getAttribute('text')); + // $this->assertCount(2, $document3->getAttribute('texts')); + + // $this->assertIsString($document3->getAttribute('text')); + // $this->assertEquals('Hello World', $document3->getAttribute('text')); + // $this->assertEquals(['Hello World 1', 'Hello World 2'], $document3->getAttribute('texts')); + // $this->assertCount(2, $document3->getAttribute('texts')); + + // $this->assertInstanceOf(Document::class, $document3->getAttribute('document')); + // $this->assertIsString($document3->getAttribute('document')->getId()); + // $this->assertNotEmpty($document3->getAttribute('document')->getId()); + // $this->assertIsArray($document3->getAttribute('document')->getPermissions()); + // $this->assertArrayHasKey('read', $document3->getAttribute('document')->getPermissions()); + // $this->assertArrayHasKey('write', $document3->getAttribute('document')->getPermissions()); + // $this->assertEquals('Task #0', $document3->getAttribute('document')->getAttribute('name')); + // $this->assertCount(4, $document3->getAttribute('document')->getAttribute('links')); + // $this->assertEquals('http://example.com/link-1', $document3->getAttribute('document')->getAttribute('links')[0]); + // $this->assertEquals('http://example.com/link-2', $document3->getAttribute('document')->getAttribute('links')[1]); + // $this->assertEquals('http://example.com/link-3', $document3->getAttribute('document')->getAttribute('links')[2]); + // $this->assertEquals('http://example.com/link-4', $document3->getAttribute('document')->getAttribute('links')[3]); + + // $this->assertInstanceOf(Document::class, $document3->getAttribute('documents')[0]); + // $this->assertIsString($document3->getAttribute('documents')[0]->getId()); + // $this->assertNotEmpty($document3->getAttribute('documents')[0]->getId()); + // $this->assertIsArray($document3->getAttribute('documents')[0]->getPermissions()); + // $this->assertArrayHasKey('read', $document3->getAttribute('documents')[0]->getPermissions()); + // $this->assertArrayHasKey('write', $document3->getAttribute('documents')[0]->getPermissions()); + // $this->assertEquals('Task #1️⃣', $document3->getAttribute('documents')[0]->getAttribute('name')); + // $this->assertCount(4, $document3->getAttribute('documents')[0]->getAttribute('links')); + // $this->assertEquals('http://example.com/link-5', $document3->getAttribute('documents')[0]->getAttribute('links')[0]); + // $this->assertEquals('http://example.com/link-6', $document3->getAttribute('documents')[0]->getAttribute('links')[1]); + // $this->assertEquals('http://example.com/link-7', $document3->getAttribute('documents')[0]->getAttribute('links')[2]); + // $this->assertEquals('http://example.com/link-8', $document3->getAttribute('documents')[0]->getAttribute('links')[3]); + + // $this->assertInstanceOf(Document::class, $document3->getAttribute('documents')[1]); + // $this->assertIsString($document3->getAttribute('documents')[1]->getId()); + // $this->assertNotEmpty($document3->getAttribute('documents')[1]->getId()); + // $this->assertIsArray($document3->getAttribute('documents')[1]->getPermissions()); + // $this->assertArrayHasKey('read', $document3->getAttribute('documents')[1]->getPermissions()); + // $this->assertArrayHasKey('write', $document3->getAttribute('documents')[1]->getPermissions()); + // $this->assertEquals('Task #0', $document3->getAttribute('documents')[1]->getAttribute('name')); + // $this->assertCount(4, $document3->getAttribute('documents')[1]->getAttribute('links')); + // $this->assertEquals('http://example.com/link-1', $document3->getAttribute('documents')[1]->getAttribute('links')[0]); + // $this->assertEquals('http://example.com/link-2', $document3->getAttribute('documents')[1]->getAttribute('links')[1]); + // $this->assertEquals('http://example.com/link-3', $document3->getAttribute('documents')[1]->getAttribute('links')[2]); + // $this->assertEquals('http://example.com/link-4', $document3->getAttribute('documents')[1]->getAttribute('links')[3]); + + // $this->assertInstanceOf(Document::class, $document3->getAttribute('document2')); + // $this->assertIsString($document3->getAttribute('document2')->getId()); + // $this->assertNotEmpty($document3->getAttribute('document2')->getId()); + // $this->assertIsArray($document3->getAttribute('document2')->getPermissions()); + // $this->assertArrayHasKey('read', $document3->getAttribute('document2')->getPermissions()); + // $this->assertArrayHasKey('write', $document3->getAttribute('document2')->getPermissions()); + // $this->assertEquals('Task #1️⃣', $document3->getAttribute('document2')->getAttribute('name')); + // $this->assertCount(4, $document3->getAttribute('document2')->getAttribute('links')); + // $this->assertEquals('http://example.com/link-5', $document3->getAttribute('document2')->getAttribute('links')[0]); + // $this->assertEquals('http://example.com/link-6', $document3->getAttribute('document2')->getAttribute('links')[1]); + // $this->assertEquals('http://example.com/link-7', $document3->getAttribute('document2')->getAttribute('links')[2]); + // $this->assertEquals('http://example.com/link-8', $document3->getAttribute('document2')->getAttribute('links')[3]); + + // $this->assertInstanceOf(Document::class, $document3->getAttribute('documents2')[0]); + // $this->assertIsString($document3->getAttribute('documents2')[0]->getId()); + // $this->assertNotEmpty($document3->getAttribute('documents2')[0]->getId()); + // $this->assertIsArray($document3->getAttribute('documents2')[0]->getPermissions()); + // $this->assertArrayHasKey('read', $document3->getAttribute('documents2')[0]->getPermissions()); + // $this->assertArrayHasKey('write', $document3->getAttribute('documents2')[0]->getPermissions()); + // $this->assertEquals('Task #0', $document3->getAttribute('documents2')[0]->getAttribute('name')); + // $this->assertCount(4, $document3->getAttribute('documents2')[0]->getAttribute('links')); + // $this->assertEquals('http://example.com/link-1', $document3->getAttribute('documents2')[0]->getAttribute('links')[0]); + // $this->assertEquals('http://example.com/link-2', $document3->getAttribute('documents2')[0]->getAttribute('links')[1]); + // $this->assertEquals('http://example.com/link-3', $document3->getAttribute('documents2')[0]->getAttribute('links')[2]); + // $this->assertEquals('http://example.com/link-4', $document3->getAttribute('documents2')[0]->getAttribute('links')[3]); + + // $this->assertInstanceOf(Document::class, $document3->getAttribute('documents2')[1]); + // $this->assertIsString($document3->getAttribute('documents2')[1]->getId()); + // $this->assertNotEmpty($document3->getAttribute('documents2')[1]->getId()); + // $this->assertIsArray($document3->getAttribute('documents2')[1]->getPermissions()); + // $this->assertArrayHasKey('read', $document3->getAttribute('documents2')[1]->getPermissions()); + // $this->assertArrayHasKey('write', $document3->getAttribute('documents2')[1]->getPermissions()); + // $this->assertEquals('Task #1️⃣', $document3->getAttribute('documents2')[1]->getAttribute('name')); + // $this->assertCount(4, $document3->getAttribute('documents2')[1]->getAttribute('links')); + // $this->assertEquals('http://example.com/link-5', $document3->getAttribute('documents2')[1]->getAttribute('links')[0]); + // $this->assertEquals('http://example.com/link-6', $document3->getAttribute('documents2')[1]->getAttribute('links')[1]); + // $this->assertEquals('http://example.com/link-7', $document3->getAttribute('documents2')[1]->getAttribute('links')[2]); + // $this->assertEquals('http://example.com/link-8', $document3->getAttribute('documents2')[1]->getAttribute('links')[3]); + + // $this->assertIsInt($document3->getAttribute('integer')); + // $this->assertEquals(1, $document3->getAttribute('integer')); + // $this->assertIsInt($document3->getAttribute('integers')[0]); + // $this->assertIsInt($document3->getAttribute('integers')[1]); + // $this->assertIsInt($document3->getAttribute('integers')[2]); + // $this->assertEquals([5, 3, 4], $document3->getAttribute('integers')); + // $this->assertCount(3, $document3->getAttribute('integers')); + + // $this->assertIsFloat($document3->getAttribute('float')); + // $this->assertEquals(2.22, $document3->getAttribute('float')); + // $this->assertIsFloat($document3->getAttribute('floats')[0]); + // $this->assertIsFloat($document3->getAttribute('floats')[1]); + // $this->assertIsFloat($document3->getAttribute('floats')[2]); + // $this->assertEquals([1.13, 4.33, 8.9999], $document3->getAttribute('floats')); + // $this->assertCount(3, $document3->getAttribute('floats')); + + // $this->assertIsBool($document3->getAttribute('boolean')); + // $this->assertEquals(true, $document3->getAttribute('boolean')); + // $this->assertIsBool($document3->getAttribute('booleans')[0]); + // $this->assertIsBool($document3->getAttribute('booleans')[1]); + // $this->assertIsBool($document3->getAttribute('booleans')[2]); + // $this->assertEquals([true, false, true], $document3->getAttribute('booleans')); + // $this->assertCount(3, $document3->getAttribute('booleans')); + + // $this->assertIsString($document3->getAttribute('email')); + // $this->assertEquals('test@appwrite.io', $document3->getAttribute('email')); + // $this->assertIsString($document3->getAttribute('emails')[0]); + // $this->assertIsString($document3->getAttribute('emails')[1]); + // $this->assertIsString($document3->getAttribute('emails')[2]); + // $this->assertIsString($document3->getAttribute('emails')[3]); + // $this->assertEquals([ + // 'test4@appwrite.io', + // 'test3@appwrite.io', + // 'test2@appwrite.io', + // 'test1@appwrite.io' + // ], $document3->getAttribute('emails')); + // $this->assertCount(4, $document3->getAttribute('emails')); + + // $this->assertIsString($document3->getAttribute('url')); + // $this->assertEquals('http://example.com/welcome', $document3->getAttribute('url')); + // $this->assertIsString($document3->getAttribute('urls')[0]); + // $this->assertIsString($document3->getAttribute('urls')[1]); + // $this->assertIsString($document3->getAttribute('urls')[2]); + // $this->assertEquals([ + // 'http://example.com/welcome-1', + // 'http://example.com/welcome-2', + // 'http://example.com/welcome-3' + // ], $document3->getAttribute('urls')); + // $this->assertCount(3, $document3->getAttribute('urls')); + + // $this->assertIsString($document3->getAttribute('ipv4')); + // $this->assertEquals('172.16.254.1', $document3->getAttribute('ipv4')); + // $this->assertIsString($document3->getAttribute('ipv4s')[0]); + // $this->assertIsString($document3->getAttribute('ipv4s')[1]); + // $this->assertEquals([ + // '172.16.254.1', + // '172.16.254.5' + // ], $document3->getAttribute('ipv4s')); + // $this->assertCount(2, $document3->getAttribute('ipv4s')); + + // $this->assertIsString($document3->getAttribute('ipv6')); + // $this->assertEquals('2001:0db8:85a3:0000:0000:8a2e:0370:7334', $document3->getAttribute('ipv6')); + // $this->assertIsString($document3->getAttribute('ipv6s')[0]); + // $this->assertIsString($document3->getAttribute('ipv6s')[1]); + // $this->assertEquals([ + // '2001:0db8:85a3:0000:0000:8a2e:0370:7334', + // '2001:0db8:85a3:0000:0000:8a2e:0370:7337' + // ], $document3->getAttribute('ipv6s')); + // $this->assertCount(2, $document3->getAttribute('ipv6s')); + + // $this->assertEquals('2001:0db8:85a3:0000:0000:8a2e:0370:7334', $document3->getAttribute('ipv6')); + // $this->assertEquals([ + // '2001:0db8:85a3:0000:0000:8a2e:0370:7334', + // '2001:0db8:85a3:0000:0000:8a2e:0370:7337' + // ], $document3->getAttribute('ipv6s')); + // $this->assertCount(2, $document3->getAttribute('ipv6s')); + + // $this->assertIsString($document3->getAttribute('key')); + // $this->assertCount(3, $document3->getAttribute('keys')); + // } + + // public function testGetDocument() + // { + // // Mocked document + // $document = self::$database->getDocument(Database::COLLECTION_COLLECTIONS, Database::COLLECTION_USERS); + + // $this->assertEquals(Database::COLLECTION_USERS, $document->getId()); + // $this->assertEquals(Database::COLLECTION_COLLECTIONS, $document->getCollection()); + + // $collection1 = self::$database->createDocument(Database::COLLECTION_COLLECTIONS, [ + // '$collection' => Database::COLLECTION_COLLECTIONS, + // '$permissions' => ['read' => ['*']], + // 'name' => 'Create Documents', + // 'rules' => [ + // [ + // '$collection' => Database::COLLECTION_RULES, + // '$permissions' => ['read' => ['*']], + // 'label' => 'Name', + // 'key' => 'name', + // 'type' => Database::VAR_STRING, + // 'default' => '', + // 'required' => true, + // 'array' => false, + // ], + // [ + // '$collection' => Database::COLLECTION_RULES, + // '$permissions' => ['read' => ['*']], + // 'label' => 'Links', + // 'key' => 'links', + // 'type' => Database::VAR_STRING, + // 'default' => '', + // 'required' => true, + // 'array' => true, + // ], + // ] + // ]); + + // $this->assertEquals(true, self::$database->createCollection($collection1->getId(), [], [])); + + // $document1 = self::$database->createDocument($collection1->getId(), [ + // '$collection' => $collection1->getId(), + // '$permissions' => [ + // 'read' => ['*'], + // 'write' => ['user:123'], + // ], + // 'name' => 'Task #1️⃣', + // 'links' => [ + // 'http://example.com/link-5', + // 'http://example.com/link-6', + // 'http://example.com/link-7', + // 'http://example.com/link-8', + // ], + // ]); + + // $document1 = self::$database->getDocument($collection1->getId(), $document1->getId()); + + // $this->assertFalse($document1->isEmpty()); + // $this->assertNotEmpty($document1->getId()); + // $this->assertIsArray($document1->getPermissions()); + // $this->assertArrayHasKey('read', $document1->getPermissions()); + // $this->assertArrayHasKey('write', $document1->getPermissions()); + // $this->assertEquals('Task #1️⃣', $document1->getAttribute('name')); + // $this->assertCount(4, $document1->getAttribute('links')); + // $this->assertEquals('http://example.com/link-5', $document1->getAttribute('links')[0]); + // $this->assertEquals('http://example.com/link-6', $document1->getAttribute('links')[1]); + // $this->assertEquals('http://example.com/link-7', $document1->getAttribute('links')[2]); + // $this->assertEquals('http://example.com/link-8', $document1->getAttribute('links')[3]); + + // $document1 = self::$database->getDocument($collection1->getId(), $document1->getId().'x'); + + // $this->assertTrue($document1->isEmpty()); + // $this->assertEmpty($document1->getId()); + // $this->assertEmpty($document1->getCollection()); + // $this->assertIsArray($document1->getPermissions()); + // $this->assertEmpty($document1->getPermissions()); + // } + + // public function testUpdateDocument() + // { + // $collection1 = self::$database->createDocument(Database::COLLECTION_COLLECTIONS, [ + // '$collection' => Database::COLLECTION_COLLECTIONS, + // '$permissions' => ['read' => ['*']], + // 'name' => 'Create Documents', + // 'rules' => [ + // [ + // '$collection' => Database::COLLECTION_RULES, + // '$permissions' => ['read' => ['*']], + // 'label' => 'Name', + // 'key' => 'name', + // 'type' => Database::VAR_STRING, + // 'default' => '', + // 'required' => true, + // 'array' => false, + // ], + // [ + // '$collection' => Database::COLLECTION_RULES, + // '$permissions' => ['read' => ['*']], + // 'label' => 'Links', + // 'key' => 'links', + // 'type' => Database::VAR_STRING, + // 'default' => '', + // 'required' => true, + // 'array' => true, + // ], + // ] + // ]); + + // $this->assertEquals(true, self::$database->createCollection($collection1->getId(), [], [])); + + // $document0 = new Document([ + // '$collection' => $collection1->getId(), + // '$permissions' => [ + // 'read' => ['*'], + // 'write' => ['user:123'], + // ], + // 'name' => 'Task #0', + // 'links' => [ + // 'http://example.com/link-1', + // 'http://example.com/link-2', + // 'http://example.com/link-3', + // 'http://example.com/link-4', + // ], + // ]); + + // $document1 = self::$database->createDocument($collection1->getId(), [ + // '$collection' => $collection1->getId(), + // '$permissions' => [ + // 'read' => ['*'], + // 'write' => ['user:123'], + // ], + // 'name' => 'Task #1️⃣', + // 'links' => [ + // 'http://example.com/link-5', + // 'http://example.com/link-6', + // 'http://example.com/link-7', + // 'http://example.com/link-8', + // ], + // ]); + + // $this->assertNotEmpty($document1->getId()); + // $this->assertIsArray($document1->getPermissions()); + // $this->assertArrayHasKey('read', $document1->getPermissions()); + // $this->assertArrayHasKey('write', $document1->getPermissions()); + // $this->assertEquals('Task #1️⃣', $document1->getAttribute('name')); + // $this->assertCount(4, $document1->getAttribute('links')); + // $this->assertEquals('http://example.com/link-5', $document1->getAttribute('links')[0]); + // $this->assertEquals('http://example.com/link-6', $document1->getAttribute('links')[1]); + // $this->assertEquals('http://example.com/link-7', $document1->getAttribute('links')[2]); + // $this->assertEquals('http://example.com/link-8', $document1->getAttribute('links')[3]); + + // $document1 = self::$database->getDocument($collection1->getId(), $document1->getId()); + + // $this->assertNotEmpty($document1->getId()); + // $this->assertIsArray($document1->getPermissions()); + // $this->assertArrayHasKey('read', $document1->getPermissions()); + // $this->assertArrayHasKey('write', $document1->getPermissions()); + // $this->assertEquals('Task #1️⃣', $document1->getAttribute('name')); + // $this->assertCount(4, $document1->getAttribute('links')); + // $this->assertEquals('http://example.com/link-5', $document1->getAttribute('links')[0]); + // $this->assertEquals('http://example.com/link-6', $document1->getAttribute('links')[1]); + // $this->assertEquals('http://example.com/link-7', $document1->getAttribute('links')[2]); + // $this->assertEquals('http://example.com/link-8', $document1->getAttribute('links')[3]); + + // $document1 = self::$database->updateDocument($collection1->getId(), $document1->getId(), [ + // '$id' => $document1->getId(), + // '$collection' => $collection1->getId(), + // '$permissions' => [ + // 'read' => ['user:1234'], + // 'write' => ['user:1234'], + // ], + // 'name' => 'Task #1x', + // 'links' => [ + // 'http://example.com/link-5x', + // 'http://example.com/link-6x', + // 'http://example.com/link-7x', + // 'http://example.com/link-8x', + // ], + // ]); + + // $this->assertNotEmpty($document1->getId()); + // $this->assertIsArray($document1->getPermissions()); + // $this->assertArrayHasKey('read', $document1->getPermissions()); + // $this->assertArrayHasKey('write', $document1->getPermissions()); + // $this->assertEquals('Task #1x', $document1->getAttribute('name')); + // $this->assertCount(4, $document1->getAttribute('links')); + // $this->assertEquals('http://example.com/link-5x', $document1->getAttribute('links')[0]); + // $this->assertEquals('http://example.com/link-6x', $document1->getAttribute('links')[1]); + // $this->assertEquals('http://example.com/link-7x', $document1->getAttribute('links')[2]); + // $this->assertEquals('http://example.com/link-8x', $document1->getAttribute('links')[3]); + + // $document1 = self::$database->getDocument($collection1->getId(), $document1->getId()); + + // $this->assertNotEmpty($document1->getId()); + // $this->assertIsArray($document1->getPermissions()); + // $this->assertArrayHasKey('read', $document1->getPermissions()); + // $this->assertArrayHasKey('write', $document1->getPermissions()); + // $this->assertEquals('Task #1x', $document1->getAttribute('name')); + // $this->assertCount(4, $document1->getAttribute('links')); + // $this->assertEquals('http://example.com/link-5x', $document1->getAttribute('links')[0]); + // $this->assertEquals('http://example.com/link-6x', $document1->getAttribute('links')[1]); + // $this->assertEquals('http://example.com/link-7x', $document1->getAttribute('links')[2]); + // $this->assertEquals('http://example.com/link-8x', $document1->getAttribute('links')[3]); + + // $document2 = self::$database->createDocument(Database::COLLECTION_USERS, [ + // '$collection' => Database::COLLECTION_USERS, + // '$permissions' => [ + // 'read' => ['*'], + // 'write' => ['user:123'], + // ], + // 'email' => 'test5@appwrite.io', + // 'emailVerification' => false, + // 'status' => 0, + // 'password' => 'secrethash', + // 'password-update' => \time(), + // 'registration' => \time(), + // 'reset' => false, + // 'name' => 'Test', + // ]); + + // $this->assertNotEmpty($document2->getId()); + // $this->assertIsArray($document2->getPermissions()); + // $this->assertArrayHasKey('read', $document2->getPermissions()); + // $this->assertArrayHasKey('write', $document2->getPermissions()); + // $this->assertEquals('test5@appwrite.io', $document2->getAttribute('email')); + // $this->assertIsString($document2->getAttribute('email')); + // $this->assertEquals(0, $document2->getAttribute('status')); + // $this->assertIsInt($document2->getAttribute('status')); + // $this->assertEquals(false, $document2->getAttribute('emailVerification')); + // $this->assertIsBool($document2->getAttribute('emailVerification')); + + // $document2 = self::$database->getDocument(Database::COLLECTION_USERS, $document2->getId()); + + // $this->assertNotEmpty($document2->getId()); + // $this->assertIsArray($document2->getPermissions()); + // $this->assertArrayHasKey('read', $document2->getPermissions()); + // $this->assertArrayHasKey('write', $document2->getPermissions()); + // $this->assertEquals('test5@appwrite.io', $document2->getAttribute('email')); + // $this->assertIsString($document2->getAttribute('email')); + // $this->assertEquals(0, $document2->getAttribute('status')); + // $this->assertIsInt($document2->getAttribute('status')); + // $this->assertEquals(false, $document2->getAttribute('emailVerification')); + // $this->assertIsBool($document2->getAttribute('emailVerification')); + + // $document2 = self::$database->updateDocument(Database::COLLECTION_USERS, $document2->getId(), [ + // '$id' => $document2->getId(), + // '$collection' => Database::COLLECTION_USERS, + // '$permissions' => [ + // 'read' => ['user:1234'], + // 'write' => ['user:1234'], + // ], + // 'email' => 'test5x@appwrite.io', + // 'emailVerification' => true, + // 'status' => 1, + // 'password' => 'secrethashx', + // 'password-update' => \time(), + // 'registration' => \time(), + // 'reset' => true, + // 'name' => 'Testx', + // ]); + + // $this->assertNotEmpty($document2->getId()); + // $this->assertIsArray($document2->getPermissions()); + // $this->assertArrayHasKey('read', $document2->getPermissions()); + // $this->assertArrayHasKey('write', $document2->getPermissions()); + // $this->assertEquals('test5x@appwrite.io', $document2->getAttribute('email')); + // $this->assertIsString($document2->getAttribute('email')); + // $this->assertEquals(1, $document2->getAttribute('status')); + // $this->assertIsInt($document2->getAttribute('status')); + // $this->assertEquals(true, $document2->getAttribute('emailVerification')); + // $this->assertIsBool($document2->getAttribute('emailVerification')); + + // $document2 = self::$database->getDocument(Database::COLLECTION_USERS, $document2->getId()); + + // $this->assertNotEmpty($document2->getId()); + // $this->assertIsArray($document2->getPermissions()); + // $this->assertArrayHasKey('read', $document2->getPermissions()); + // $this->assertArrayHasKey('write', $document2->getPermissions()); + // $this->assertEquals('test5x@appwrite.io', $document2->getAttribute('email')); + // $this->assertIsString($document2->getAttribute('email')); + // $this->assertEquals(1, $document2->getAttribute('status')); + // $this->assertIsInt($document2->getAttribute('status')); + // $this->assertEquals(true, $document2->getAttribute('emailVerification')); + // $this->assertIsBool($document2->getAttribute('emailVerification')); + + // $types = [ + // Database::VAR_STRING, + // Database::VAR_NUMBER, + // Database::VAR_BOOLEAN, + // Database::VAR_DOCUMENT, + // ]; + + // $rules = []; + + // foreach($types as $type) { + // $rules[] = [ + // '$collection' => Database::COLLECTION_RULES, + // '$permissions' => ['read' => ['*']], + // 'label' => ucfirst($type), + // 'key' => $type, + // 'type' => $type, + // 'default' => null, + // 'required' => true, + // 'array' => false, + // 'list' => ($type === Database::VAR_DOCUMENT) ? [$collection1->getId()] : [], + // ]; + + // $rules[] = [ + // '$collection' => Database::COLLECTION_RULES, + // '$permissions' => ['read' => ['*']], + // 'label' => ucfirst($type), + // 'key' => $type.'s', + // 'type' => $type, + // 'default' => null, + // 'required' => true, + // 'array' => true, + // 'list' => ($type === Database::VAR_DOCUMENT) ? [$collection1->getId()] : [], + // ]; + // } + + // $rules[] = [ + // '$collection' => Database::COLLECTION_RULES, + // '$permissions' => ['read' => ['*']], + // 'label' => 'document2', + // 'key' => 'document2', + // 'type' => Database::VAR_DOCUMENT, + // 'default' => null, + // 'required' => true, + // 'array' => false, + // 'list' => [$collection1->getId()], + // ]; + + // $rules[] = [ + // '$collection' => Database::COLLECTION_RULES, + // '$permissions' => ['read' => ['*']], + // 'label' => 'documents2', + // 'key' => 'documents2', + // 'type' => Database::VAR_DOCUMENT, + // 'default' => null, + // 'required' => true, + // 'array' => true, + // 'list' => [$collection1->getId()], + // ]; + + // $collection2 = self::$database->createDocument(Database::COLLECTION_COLLECTIONS, [ + // '$collection' => Database::COLLECTION_COLLECTIONS, + // '$permissions' => ['read' => ['*']], + // 'name' => 'Create Documents', + // 'rules' => $rules, + // ]); + + // $this->assertEquals(true, self::$database->createCollection($collection2->getId(), [], [])); + + // $document3 = self::$database->createDocument($collection2->getId(), [ + // '$collection' => $collection2->getId(), + // '$permissions' => [ + // 'read' => ['*'], + // 'write' => ['user:123'], + // ], + // 'text' => 'Hello World', + // 'texts' => ['Hello World 1', 'Hello World 2'], + // // 'document' => $document0, + // // 'documents' => [$document0], + // 'document' => $document0, + // 'documents' => [$document1, $document0], + // 'document2' => $document1, + // 'documents2' => [$document0, $document1], + // 'integer' => 1, + // 'integers' => [5, 3, 4], + // 'float' => 2.22, + // 'floats' => [1.13, 4.33, 8.9999], + // 'numeric' => 1, + // 'numerics' => [1, 5, 7.77], + // 'boolean' => true, + // 'booleans' => [true, false, true], + // 'email' => 'test@appwrite.io', + // 'emails' => [ + // 'test4@appwrite.io', + // 'test3@appwrite.io', + // 'test2@appwrite.io', + // 'test1@appwrite.io' + // ], + // 'url' => 'http://example.com/welcome', + // 'urls' => [ + // 'http://example.com/welcome-1', + // 'http://example.com/welcome-2', + // 'http://example.com/welcome-3' + // ], + // 'ipv4' => '172.16.254.1', + // 'ipv4s' => [ + // '172.16.254.1', + // '172.16.254.5' + // ], + // 'ipv6' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334', + // 'ipv6s' => [ + // '2001:0db8:85a3:0000:0000:8a2e:0370:7334', + // '2001:0db8:85a3:0000:0000:8a2e:0370:7337' + // ], + // 'key' => uniqid(), + // 'keys' => [uniqid(), uniqid(), uniqid()], + // ]); + + // $document3 = self::$database->getDocument($collection2->getId(), $document3->getId()); + + // $this->assertIsString($document3->getId()); + // $this->assertIsString($document3->getCollection()); + // $this->assertEquals([ + // 'read' => ['*'], + // 'write' => ['user:123'], + // ], $document3->getPermissions()); + // $this->assertEquals('Hello World', $document3->getAttribute('text')); + // $this->assertCount(2, $document3->getAttribute('texts')); + + // $this->assertIsString($document3->getAttribute('text')); + // $this->assertEquals('Hello World', $document3->getAttribute('text')); + // $this->assertEquals(['Hello World 1', 'Hello World 2'], $document3->getAttribute('texts')); + // $this->assertCount(2, $document3->getAttribute('texts')); + + // $this->assertInstanceOf(Document::class, $document3->getAttribute('document')); + // $this->assertIsString($document3->getAttribute('document')->getId()); + // $this->assertNotEmpty($document3->getAttribute('document')->getId()); + // $this->assertIsArray($document3->getAttribute('document')->getPermissions()); + // $this->assertArrayHasKey('read', $document3->getAttribute('document')->getPermissions()); + // $this->assertArrayHasKey('write', $document3->getAttribute('document')->getPermissions()); + // $this->assertEquals('Task #0', $document3->getAttribute('document')->getAttribute('name')); + // $this->assertCount(4, $document3->getAttribute('document')->getAttribute('links')); + // $this->assertEquals('http://example.com/link-1', $document3->getAttribute('document')->getAttribute('links')[0]); + // $this->assertEquals('http://example.com/link-2', $document3->getAttribute('document')->getAttribute('links')[1]); + // $this->assertEquals('http://example.com/link-3', $document3->getAttribute('document')->getAttribute('links')[2]); + // $this->assertEquals('http://example.com/link-4', $document3->getAttribute('document')->getAttribute('links')[3]); + + // $this->assertInstanceOf(Document::class, $document3->getAttribute('documents')[0]); + // $this->assertIsString($document3->getAttribute('documents')[0]->getId()); + // $this->assertNotEmpty($document3->getAttribute('documents')[0]->getId()); + // $this->assertIsArray($document3->getAttribute('documents')[0]->getPermissions()); + // $this->assertArrayHasKey('read', $document3->getAttribute('documents')[0]->getPermissions()); + // $this->assertArrayHasKey('write', $document3->getAttribute('documents')[0]->getPermissions()); + // $this->assertEquals('Task #1x', $document3->getAttribute('documents')[0]->getAttribute('name')); + // $this->assertCount(4, $document3->getAttribute('documents')[0]->getAttribute('links')); + // $this->assertEquals('http://example.com/link-5x', $document3->getAttribute('documents')[0]->getAttribute('links')[0]); + // $this->assertEquals('http://example.com/link-6x', $document3->getAttribute('documents')[0]->getAttribute('links')[1]); + // $this->assertEquals('http://example.com/link-7x', $document3->getAttribute('documents')[0]->getAttribute('links')[2]); + // $this->assertEquals('http://example.com/link-8x', $document3->getAttribute('documents')[0]->getAttribute('links')[3]); + + // $this->assertInstanceOf(Document::class, $document3->getAttribute('documents')[1]); + // $this->assertIsString($document3->getAttribute('documents')[1]->getId()); + // $this->assertNotEmpty($document3->getAttribute('documents')[1]->getId()); + // $this->assertIsArray($document3->getAttribute('documents')[1]->getPermissions()); + // $this->assertArrayHasKey('read', $document3->getAttribute('documents')[1]->getPermissions()); + // $this->assertArrayHasKey('write', $document3->getAttribute('documents')[1]->getPermissions()); + // $this->assertEquals('Task #0', $document3->getAttribute('documents')[1]->getAttribute('name')); + // $this->assertCount(4, $document3->getAttribute('documents')[1]->getAttribute('links')); + // $this->assertEquals('http://example.com/link-1', $document3->getAttribute('documents')[1]->getAttribute('links')[0]); + // $this->assertEquals('http://example.com/link-2', $document3->getAttribute('documents')[1]->getAttribute('links')[1]); + // $this->assertEquals('http://example.com/link-3', $document3->getAttribute('documents')[1]->getAttribute('links')[2]); + // $this->assertEquals('http://example.com/link-4', $document3->getAttribute('documents')[1]->getAttribute('links')[3]); + + // $this->assertInstanceOf(Document::class, $document3->getAttribute('document2')); + // $this->assertIsString($document3->getAttribute('document2')->getId()); + // $this->assertNotEmpty($document3->getAttribute('document2')->getId()); + // $this->assertIsArray($document3->getAttribute('document2')->getPermissions()); + // $this->assertArrayHasKey('read', $document3->getAttribute('document2')->getPermissions()); + // $this->assertArrayHasKey('write', $document3->getAttribute('document2')->getPermissions()); + // $this->assertEquals('Task #1x', $document3->getAttribute('document2')->getAttribute('name')); + // $this->assertCount(4, $document3->getAttribute('document2')->getAttribute('links')); + // $this->assertEquals('http://example.com/link-5x', $document3->getAttribute('document2')->getAttribute('links')[0]); + // $this->assertEquals('http://example.com/link-6x', $document3->getAttribute('document2')->getAttribute('links')[1]); + // $this->assertEquals('http://example.com/link-7x', $document3->getAttribute('document2')->getAttribute('links')[2]); + // $this->assertEquals('http://example.com/link-8x', $document3->getAttribute('document2')->getAttribute('links')[3]); + + // $this->assertInstanceOf(Document::class, $document3->getAttribute('documents2')[0]); + // $this->assertIsString($document3->getAttribute('documents2')[0]->getId()); + // $this->assertNotEmpty($document3->getAttribute('documents2')[0]->getId()); + // $this->assertIsArray($document3->getAttribute('documents2')[0]->getPermissions()); + // $this->assertArrayHasKey('read', $document3->getAttribute('documents2')[0]->getPermissions()); + // $this->assertArrayHasKey('write', $document3->getAttribute('documents2')[0]->getPermissions()); + // $this->assertEquals('Task #0', $document3->getAttribute('documents2')[0]->getAttribute('name')); + // $this->assertCount(4, $document3->getAttribute('documents2')[0]->getAttribute('links')); + // $this->assertEquals('http://example.com/link-1', $document3->getAttribute('documents2')[0]->getAttribute('links')[0]); + // $this->assertEquals('http://example.com/link-2', $document3->getAttribute('documents2')[0]->getAttribute('links')[1]); + // $this->assertEquals('http://example.com/link-3', $document3->getAttribute('documents2')[0]->getAttribute('links')[2]); + // $this->assertEquals('http://example.com/link-4', $document3->getAttribute('documents2')[0]->getAttribute('links')[3]); + + // $this->assertInstanceOf(Document::class, $document3->getAttribute('documents2')[1]); + // $this->assertIsString($document3->getAttribute('documents2')[1]->getId()); + // $this->assertNotEmpty($document3->getAttribute('documents2')[1]->getId()); + // $this->assertIsArray($document3->getAttribute('documents2')[1]->getPermissions()); + // $this->assertArrayHasKey('read', $document3->getAttribute('documents2')[1]->getPermissions()); + // $this->assertArrayHasKey('write', $document3->getAttribute('documents2')[1]->getPermissions()); + // $this->assertEquals('Task #1x', $document3->getAttribute('documents2')[1]->getAttribute('name')); + // $this->assertCount(4, $document3->getAttribute('documents2')[1]->getAttribute('links')); + // $this->assertEquals('http://example.com/link-5x', $document3->getAttribute('documents2')[1]->getAttribute('links')[0]); + // $this->assertEquals('http://example.com/link-6x', $document3->getAttribute('documents2')[1]->getAttribute('links')[1]); + // $this->assertEquals('http://example.com/link-7x', $document3->getAttribute('documents2')[1]->getAttribute('links')[2]); + // $this->assertEquals('http://example.com/link-8x', $document3->getAttribute('documents2')[1]->getAttribute('links')[3]); + + // $this->assertIsInt($document3->getAttribute('integer')); + // $this->assertEquals(1, $document3->getAttribute('integer')); + // $this->assertIsInt($document3->getAttribute('integers')[0]); + // $this->assertIsInt($document3->getAttribute('integers')[1]); + // $this->assertIsInt($document3->getAttribute('integers')[2]); + // $this->assertEquals([5, 3, 4], $document3->getAttribute('integers')); + // $this->assertCount(3, $document3->getAttribute('integers')); + + // $this->assertIsFloat($document3->getAttribute('float')); + // $this->assertEquals(2.22, $document3->getAttribute('float')); + // $this->assertIsFloat($document3->getAttribute('floats')[0]); + // $this->assertIsFloat($document3->getAttribute('floats')[1]); + // $this->assertIsFloat($document3->getAttribute('floats')[2]); + // $this->assertEquals([1.13, 4.33, 8.9999], $document3->getAttribute('floats')); + // $this->assertCount(3, $document3->getAttribute('floats')); + + // $this->assertIsBool($document3->getAttribute('boolean')); + // $this->assertEquals(true, $document3->getAttribute('boolean')); + // $this->assertIsBool($document3->getAttribute('booleans')[0]); + // $this->assertIsBool($document3->getAttribute('booleans')[1]); + // $this->assertIsBool($document3->getAttribute('booleans')[2]); + // $this->assertEquals([true, false, true], $document3->getAttribute('booleans')); + // $this->assertCount(3, $document3->getAttribute('booleans')); + + // $this->assertIsString($document3->getAttribute('email')); + // $this->assertEquals('test@appwrite.io', $document3->getAttribute('email')); + // $this->assertIsString($document3->getAttribute('emails')[0]); + // $this->assertIsString($document3->getAttribute('emails')[1]); + // $this->assertIsString($document3->getAttribute('emails')[2]); + // $this->assertIsString($document3->getAttribute('emails')[3]); + // $this->assertEquals([ + // 'test4@appwrite.io', + // 'test3@appwrite.io', + // 'test2@appwrite.io', + // 'test1@appwrite.io' + // ], $document3->getAttribute('emails')); + // $this->assertCount(4, $document3->getAttribute('emails')); + + // $this->assertIsString($document3->getAttribute('url')); + // $this->assertEquals('http://example.com/welcome', $document3->getAttribute('url')); + // $this->assertIsString($document3->getAttribute('urls')[0]); + // $this->assertIsString($document3->getAttribute('urls')[1]); + // $this->assertIsString($document3->getAttribute('urls')[2]); + // $this->assertEquals([ + // 'http://example.com/welcome-1', + // 'http://example.com/welcome-2', + // 'http://example.com/welcome-3' + // ], $document3->getAttribute('urls')); + // $this->assertCount(3, $document3->getAttribute('urls')); + + // $this->assertIsString($document3->getAttribute('ipv4')); + // $this->assertEquals('172.16.254.1', $document3->getAttribute('ipv4')); + // $this->assertIsString($document3->getAttribute('ipv4s')[0]); + // $this->assertIsString($document3->getAttribute('ipv4s')[1]); + // $this->assertEquals([ + // '172.16.254.1', + // '172.16.254.5' + // ], $document3->getAttribute('ipv4s')); + // $this->assertCount(2, $document3->getAttribute('ipv4s')); + + // $this->assertIsString($document3->getAttribute('ipv6')); + // $this->assertEquals('2001:0db8:85a3:0000:0000:8a2e:0370:7334', $document3->getAttribute('ipv6')); + // $this->assertIsString($document3->getAttribute('ipv6s')[0]); + // $this->assertIsString($document3->getAttribute('ipv6s')[1]); + // $this->assertEquals([ + // '2001:0db8:85a3:0000:0000:8a2e:0370:7334', + // '2001:0db8:85a3:0000:0000:8a2e:0370:7337' + // ], $document3->getAttribute('ipv6s')); + // $this->assertCount(2, $document3->getAttribute('ipv6s')); + + // $this->assertEquals('2001:0db8:85a3:0000:0000:8a2e:0370:7334', $document3->getAttribute('ipv6')); + // $this->assertEquals([ + // '2001:0db8:85a3:0000:0000:8a2e:0370:7334', + // '2001:0db8:85a3:0000:0000:8a2e:0370:7337' + // ], $document3->getAttribute('ipv6s')); + // $this->assertCount(2, $document3->getAttribute('ipv6s')); + + // $this->assertIsString($document3->getAttribute('key')); + // $this->assertCount(3, $document3->getAttribute('keys')); + + // // Update + + // $document3 = self::$database->updateDocument($collection2->getId(), $document3->getId(), [ + // '$id' => $document3->getId(), + // '$collection' => $collection2->getId(), + // '$permissions' => [ + // 'read' => ['user:1234'], + // 'write' => ['user:1234'], + // ], + // 'text' => 'Hello Worldx', + // 'texts' => ['Hello World 1x', 'Hello World 2x'], + // 'document' => $document0, + // 'documents' => [$document1, $document0], + // 'document2' => $document1, + // 'documents2' => [$document0, $document1], + // 'integer' => 2, + // 'integers' => [6, 4, 5], + // 'float' => 3.22, + // 'floats' => [2.13, 5.33, 9.9999], + // 'numeric' => 2, + // 'numerics' => [2, 6, 8.77], + // 'boolean' => false, + // 'booleans' => [false, true, false], + // 'email' => 'testx@appwrite.io', + // 'emails' => [ + // 'test4x@appwrite.io', + // 'test3x@appwrite.io', + // 'test2x@appwrite.io', + // 'test1x@appwrite.io' + // ], + // 'url' => 'http://example.com/welcomex', + // 'urls' => [ + // 'http://example.com/welcome-1x', + // 'http://example.com/welcome-2x', + // 'http://example.com/welcome-3x' + // ], + // 'ipv4' => '172.16.254.2', + // 'ipv4s' => [ + // '172.16.254.2', + // '172.16.254.6' + // ], + // 'ipv6' => '2001:0db8:85a3:0000:0000:8a2e:0370:7335', + // 'ipv6s' => [ + // '2001:0db8:85a3:0000:0000:8a2e:0370:7335', + // '2001:0db8:85a3:0000:0000:8a2e:0370:7338' + // ], + // 'key' => uniqid().'x', + // 'keys' => [uniqid().'x', uniqid().'x', uniqid().'x'], + // ]); + + // $document3 = self::$database->getDocument($collection2->getId(), $document3->getId()); + + // $this->assertIsString($document3->getId()); + // $this->assertIsString($document3->getCollection()); + // $this->assertEquals([ + // 'read' => ['user:1234'], + // 'write' => ['user:1234'], + // ], $document3->getPermissions()); + // $this->assertEquals('Hello Worldx', $document3->getAttribute('text')); + // $this->assertCount(2, $document3->getAttribute('texts')); + + // $this->assertIsString($document3->getAttribute('text')); + // $this->assertEquals('Hello Worldx', $document3->getAttribute('text')); + // $this->assertEquals(['Hello World 1x', 'Hello World 2x'], $document3->getAttribute('texts')); + // $this->assertCount(2, $document3->getAttribute('texts')); + + // $this->assertInstanceOf(Document::class, $document3->getAttribute('document')); + // $this->assertIsString($document3->getAttribute('document')->getId()); + // $this->assertNotEmpty($document3->getAttribute('document')->getId()); + // $this->assertIsArray($document3->getAttribute('document')->getPermissions()); + // $this->assertArrayHasKey('read', $document3->getAttribute('document')->getPermissions()); + // $this->assertArrayHasKey('write', $document3->getAttribute('document')->getPermissions()); + // $this->assertEquals('Task #0', $document3->getAttribute('document')->getAttribute('name')); + // $this->assertCount(4, $document3->getAttribute('document')->getAttribute('links')); + // $this->assertEquals('http://example.com/link-1', $document3->getAttribute('document')->getAttribute('links')[0]); + // $this->assertEquals('http://example.com/link-2', $document3->getAttribute('document')->getAttribute('links')[1]); + // $this->assertEquals('http://example.com/link-3', $document3->getAttribute('document')->getAttribute('links')[2]); + // $this->assertEquals('http://example.com/link-4', $document3->getAttribute('document')->getAttribute('links')[3]); + + // $this->assertInstanceOf(Document::class, $document3->getAttribute('documents')[0]); + // $this->assertIsString($document3->getAttribute('documents')[0]->getId()); + // $this->assertNotEmpty($document3->getAttribute('documents')[0]->getId()); + // $this->assertIsArray($document3->getAttribute('documents')[0]->getPermissions()); + // $this->assertArrayHasKey('read', $document3->getAttribute('documents')[0]->getPermissions()); + // $this->assertArrayHasKey('write', $document3->getAttribute('documents')[0]->getPermissions()); + // $this->assertEquals('Task #1x', $document3->getAttribute('documents')[0]->getAttribute('name')); + // $this->assertCount(4, $document3->getAttribute('documents')[0]->getAttribute('links')); + // $this->assertEquals('http://example.com/link-5x', $document3->getAttribute('documents')[0]->getAttribute('links')[0]); + // $this->assertEquals('http://example.com/link-6x', $document3->getAttribute('documents')[0]->getAttribute('links')[1]); + // $this->assertEquals('http://example.com/link-7x', $document3->getAttribute('documents')[0]->getAttribute('links')[2]); + // $this->assertEquals('http://example.com/link-8x', $document3->getAttribute('documents')[0]->getAttribute('links')[3]); + + // $this->assertInstanceOf(Document::class, $document3->getAttribute('documents')[1]); + // $this->assertIsString($document3->getAttribute('documents')[1]->getId()); + // $this->assertNotEmpty($document3->getAttribute('documents')[1]->getId()); + // $this->assertIsArray($document3->getAttribute('documents')[1]->getPermissions()); + // $this->assertArrayHasKey('read', $document3->getAttribute('documents')[1]->getPermissions()); + // $this->assertArrayHasKey('write', $document3->getAttribute('documents')[1]->getPermissions()); + // $this->assertEquals('Task #0', $document3->getAttribute('documents')[1]->getAttribute('name')); + // $this->assertCount(4, $document3->getAttribute('documents')[1]->getAttribute('links')); + // $this->assertEquals('http://example.com/link-1', $document3->getAttribute('documents')[1]->getAttribute('links')[0]); + // $this->assertEquals('http://example.com/link-2', $document3->getAttribute('documents')[1]->getAttribute('links')[1]); + // $this->assertEquals('http://example.com/link-3', $document3->getAttribute('documents')[1]->getAttribute('links')[2]); + // $this->assertEquals('http://example.com/link-4', $document3->getAttribute('documents')[1]->getAttribute('links')[3]); + + // $this->assertInstanceOf(Document::class, $document3->getAttribute('document2')); + // $this->assertIsString($document3->getAttribute('document2')->getId()); + // $this->assertNotEmpty($document3->getAttribute('document2')->getId()); + // $this->assertIsArray($document3->getAttribute('document2')->getPermissions()); + // $this->assertArrayHasKey('read', $document3->getAttribute('document2')->getPermissions()); + // $this->assertArrayHasKey('write', $document3->getAttribute('document2')->getPermissions()); + // $this->assertEquals('Task #1x', $document3->getAttribute('document2')->getAttribute('name')); + // $this->assertCount(4, $document3->getAttribute('document2')->getAttribute('links')); + // $this->assertEquals('http://example.com/link-5x', $document3->getAttribute('document2')->getAttribute('links')[0]); + // $this->assertEquals('http://example.com/link-6x', $document3->getAttribute('document2')->getAttribute('links')[1]); + // $this->assertEquals('http://example.com/link-7x', $document3->getAttribute('document2')->getAttribute('links')[2]); + // $this->assertEquals('http://example.com/link-8x', $document3->getAttribute('document2')->getAttribute('links')[3]); + + // $this->assertInstanceOf(Document::class, $document3->getAttribute('documents2')[0]); + // $this->assertIsString($document3->getAttribute('documents2')[0]->getId()); + // $this->assertNotEmpty($document3->getAttribute('documents2')[0]->getId()); + // $this->assertIsArray($document3->getAttribute('documents2')[0]->getPermissions()); + // $this->assertArrayHasKey('read', $document3->getAttribute('documents2')[0]->getPermissions()); + // $this->assertArrayHasKey('write', $document3->getAttribute('documents2')[0]->getPermissions()); + // $this->assertEquals('Task #0', $document3->getAttribute('documents2')[0]->getAttribute('name')); + // $this->assertCount(4, $document3->getAttribute('documents2')[0]->getAttribute('links')); + // $this->assertEquals('http://example.com/link-1', $document3->getAttribute('documents2')[0]->getAttribute('links')[0]); + // $this->assertEquals('http://example.com/link-2', $document3->getAttribute('documents2')[0]->getAttribute('links')[1]); + // $this->assertEquals('http://example.com/link-3', $document3->getAttribute('documents2')[0]->getAttribute('links')[2]); + // $this->assertEquals('http://example.com/link-4', $document3->getAttribute('documents2')[0]->getAttribute('links')[3]); + + // $this->assertInstanceOf(Document::class, $document3->getAttribute('documents2')[1]); + // $this->assertIsString($document3->getAttribute('documents2')[1]->getId()); + // $this->assertNotEmpty($document3->getAttribute('documents2')[1]->getId()); + // $this->assertIsArray($document3->getAttribute('documents2')[1]->getPermissions()); + // $this->assertArrayHasKey('read', $document3->getAttribute('documents2')[1]->getPermissions()); + // $this->assertArrayHasKey('write', $document3->getAttribute('documents2')[1]->getPermissions()); + // $this->assertEquals('Task #1x', $document3->getAttribute('documents2')[1]->getAttribute('name')); + // $this->assertCount(4, $document3->getAttribute('documents2')[1]->getAttribute('links')); + // $this->assertEquals('http://example.com/link-5x', $document3->getAttribute('documents2')[1]->getAttribute('links')[0]); + // $this->assertEquals('http://example.com/link-6x', $document3->getAttribute('documents2')[1]->getAttribute('links')[1]); + // $this->assertEquals('http://example.com/link-7x', $document3->getAttribute('documents2')[1]->getAttribute('links')[2]); + // $this->assertEquals('http://example.com/link-8x', $document3->getAttribute('documents2')[1]->getAttribute('links')[3]); + + // $this->assertIsInt($document3->getAttribute('integer')); + // $this->assertEquals(2, $document3->getAttribute('integer')); + // $this->assertIsInt($document3->getAttribute('integers')[0]); + // $this->assertIsInt($document3->getAttribute('integers')[1]); + // $this->assertIsInt($document3->getAttribute('integers')[2]); + // $this->assertEquals([6, 4, 5], $document3->getAttribute('integers')); + // $this->assertCount(3, $document3->getAttribute('integers')); + + // $this->assertIsFloat($document3->getAttribute('float')); + // $this->assertEquals(3.22, $document3->getAttribute('float')); + // $this->assertIsFloat($document3->getAttribute('floats')[0]); + // $this->assertIsFloat($document3->getAttribute('floats')[1]); + // $this->assertIsFloat($document3->getAttribute('floats')[2]); + // $this->assertEquals([2.13, 5.33, 9.9999], $document3->getAttribute('floats')); + // $this->assertCount(3, $document3->getAttribute('floats')); + + // $this->assertIsBool($document3->getAttribute('boolean')); + // $this->assertEquals(false, $document3->getAttribute('boolean')); + // $this->assertIsBool($document3->getAttribute('booleans')[0]); + // $this->assertIsBool($document3->getAttribute('booleans')[1]); + // $this->assertIsBool($document3->getAttribute('booleans')[2]); + // $this->assertEquals([false, true, false], $document3->getAttribute('booleans')); + // $this->assertCount(3, $document3->getAttribute('booleans')); + + // $this->assertIsString($document3->getAttribute('email')); + // $this->assertEquals('testx@appwrite.io', $document3->getAttribute('email')); + // $this->assertIsString($document3->getAttribute('emails')[0]); + // $this->assertIsString($document3->getAttribute('emails')[1]); + // $this->assertIsString($document3->getAttribute('emails')[2]); + // $this->assertIsString($document3->getAttribute('emails')[3]); + // $this->assertEquals([ + // 'test4x@appwrite.io', + // 'test3x@appwrite.io', + // 'test2x@appwrite.io', + // 'test1x@appwrite.io' + // ], $document3->getAttribute('emails')); + // $this->assertCount(4, $document3->getAttribute('emails')); + + // $this->assertIsString($document3->getAttribute('url')); + // $this->assertEquals('http://example.com/welcomex', $document3->getAttribute('url')); + // $this->assertIsString($document3->getAttribute('urls')[0]); + // $this->assertIsString($document3->getAttribute('urls')[1]); + // $this->assertIsString($document3->getAttribute('urls')[2]); + // $this->assertEquals([ + // 'http://example.com/welcome-1x', + // 'http://example.com/welcome-2x', + // 'http://example.com/welcome-3x' + // ], $document3->getAttribute('urls')); + // $this->assertCount(3, $document3->getAttribute('urls')); + + // $this->assertIsString($document3->getAttribute('ipv4')); + // $this->assertEquals('172.16.254.2', $document3->getAttribute('ipv4')); + // $this->assertIsString($document3->getAttribute('ipv4s')[0]); + // $this->assertIsString($document3->getAttribute('ipv4s')[1]); + // $this->assertEquals([ + // '172.16.254.2', + // '172.16.254.6' + // ], $document3->getAttribute('ipv4s')); + // $this->assertCount(2, $document3->getAttribute('ipv4s')); + + // $this->assertIsString($document3->getAttribute('ipv6')); + // $this->assertEquals('2001:0db8:85a3:0000:0000:8a2e:0370:7335', $document3->getAttribute('ipv6')); + // $this->assertIsString($document3->getAttribute('ipv6s')[0]); + // $this->assertIsString($document3->getAttribute('ipv6s')[1]); + // $this->assertEquals([ + // '2001:0db8:85a3:0000:0000:8a2e:0370:7335', + // '2001:0db8:85a3:0000:0000:8a2e:0370:7338' + // ], $document3->getAttribute('ipv6s')); + // $this->assertCount(2, $document3->getAttribute('ipv6s')); + + // $this->assertEquals('2001:0db8:85a3:0000:0000:8a2e:0370:7335', $document3->getAttribute('ipv6')); + // $this->assertEquals([ + // '2001:0db8:85a3:0000:0000:8a2e:0370:7335', + // '2001:0db8:85a3:0000:0000:8a2e:0370:7338' + // ], $document3->getAttribute('ipv6s')); + // $this->assertCount(2, $document3->getAttribute('ipv6s')); + + // $this->assertIsString($document3->getAttribute('key')); + // $this->assertCount(3, $document3->getAttribute('keys')); + // } + + // public function testDeleteDocument() + // { + // $collection1 = self::$database->createDocument(Database::COLLECTION_COLLECTIONS, [ + // '$collection' => Database::COLLECTION_COLLECTIONS, + // '$permissions' => ['read' => ['*']], + // 'name' => 'Create Documents', + // 'rules' => [ + // [ + // '$collection' => Database::COLLECTION_RULES, + // '$permissions' => ['read' => ['*']], + // 'label' => 'Name', + // 'key' => 'name', + // 'type' => Database::VAR_STRING, + // 'default' => '', + // 'required' => true, + // 'array' => false, + // ], + // [ + // '$collection' => Database::COLLECTION_RULES, + // '$permissions' => ['read' => ['*']], + // 'label' => 'Links', + // 'key' => 'links', + // 'type' => Database::VAR_STRING, + // 'default' => '', + // 'required' => true, + // 'array' => true, + // ], + // ] + // ]); + + // $this->assertEquals(true, self::$database->createCollection($collection1->getId(), [], [])); + + // $document1 = self::$database->createDocument($collection1->getId(), [ + // '$collection' => $collection1->getId(), + // '$permissions' => [ + // 'read' => ['*'], + // 'write' => ['user:123'], + // ], + // 'name' => 'Task #1️⃣', + // 'links' => [ + // 'http://example.com/link-5', + // 'http://example.com/link-6', + // 'http://example.com/link-7', + // 'http://example.com/link-8', + // ], + // ]); + + // $this->assertNotEmpty($document1->getId()); + // $this->assertIsArray($document1->getPermissions()); + // $this->assertArrayHasKey('read', $document1->getPermissions()); + // $this->assertArrayHasKey('write', $document1->getPermissions()); + // $this->assertEquals('Task #1️⃣', $document1->getAttribute('name')); + // $this->assertCount(4, $document1->getAttribute('links')); + // $this->assertEquals('http://example.com/link-5', $document1->getAttribute('links')[0]); + // $this->assertEquals('http://example.com/link-6', $document1->getAttribute('links')[1]); + // $this->assertEquals('http://example.com/link-7', $document1->getAttribute('links')[2]); + // $this->assertEquals('http://example.com/link-8', $document1->getAttribute('links')[3]); + + // $document1 = self::$database->getDocument($collection1->getId(), $document1->getId()); + + // $this->assertNotEmpty($document1->getId()); + // $this->assertIsArray($document1->getPermissions()); + // $this->assertArrayHasKey('read', $document1->getPermissions()); + // $this->assertArrayHasKey('write', $document1->getPermissions()); + // $this->assertEquals('Task #1️⃣', $document1->getAttribute('name')); + // $this->assertCount(4, $document1->getAttribute('links')); + // $this->assertEquals('http://example.com/link-5', $document1->getAttribute('links')[0]); + // $this->assertEquals('http://example.com/link-6', $document1->getAttribute('links')[1]); + // $this->assertEquals('http://example.com/link-7', $document1->getAttribute('links')[2]); + // $this->assertEquals('http://example.com/link-8', $document1->getAttribute('links')[3]); + + // self::$database->deleteDocument($collection1->getId(), $document1->getId()); + + // $document1 = self::$database->getDocument($collection1->getId(), $document1->getId()); + + // $this->assertTrue($document1->isEmpty()); + // $this->assertEmpty($document1->getId()); + // $this->assertEmpty($document1->getCollection()); + // $this->assertIsArray($document1->getPermissions()); + // $this->assertEmpty($document1->getPermissions()); + // } + + // public function testFind() + // { + // $data = include __DIR__.'/../../resources/database/movies.php'; + + // $collections = $data['collections']; + // $movies = $data['movies']; + + // foreach ($collections as $key => &$collection) { + // $collection = self::$database->createDocument(Database::COLLECTION_COLLECTIONS, $collection); + // self::$database->createCollection($collection->getId(), [], []); + // } + + // foreach ($movies as $key => &$movie) { + // $movie['$collection'] = $collection->getId(); + // $movie['$permissions'] = []; + // $movie = self::$database->createDocument($collection->getId(), $movie); + // } + + // self::$database->find($collection->getId(), [ + // 'limit' => 5, + // 'filters' => [ + // 'name=Hello World', + // 'releaseYear=1999', + // 'langauges=English', + // ], + // ]); + // $this->assertEquals('1', '1'); + // } + + public function testFindFirst() + { + $this->assertEquals('1', '1'); + } + + public function testFindLast() + { + $this->assertEquals('1', '1'); + } + + public function countTest() + { + $this->assertEquals('1', '1'); + } + + public function addFilterTest() + { + $this->assertEquals('1', '1'); + } + + public function encodeTest() + { + $this->assertEquals('1', '1'); + } + + public function decodeTest() + { + $this->assertEquals('1', '1'); + } +} \ No newline at end of file diff --git a/tests/Database/DatabaseTest.php b/tests/Database/DatabaseTest.php deleted file mode 100644 index a094a451f..000000000 --- a/tests/Database/DatabaseTest.php +++ /dev/null @@ -1,1907 +0,0 @@ - 'SET NAMES utf8mb4', -// PDO::ATTR_TIMEOUT => 3, // Seconds -// PDO::ATTR_PERSISTENT => true -// )); - -// // Connection settings -// $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); // Return arrays -// $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // Handle all errors with exceptions - -// self::$object = new Database(); -// self::$object->setAdapter(new Relational($pdo)); - -// self::$object->setMocks([ -// Database::COLLECTION_COLLECTIONS => [ -// '$collection' => Database::COLLECTION_COLLECTIONS, -// '$id' => Database::COLLECTION_COLLECTIONS, -// '$permissions' => ['read' => ['*']], -// 'name' => 'Collections', -// 'rules' => [ -// [ -// '$collection' => Database::COLLECTION_RULES, -// 'label' => 'Name', -// 'key' => 'name', -// 'type' => Database::VAR_TEXT, -// 'default' => '', -// 'required' => true, -// 'array' => false, -// ], -// [ -// '$collection' => Database::COLLECTION_RULES, -// 'label' => 'Date Created', -// 'key' => 'dateCreated', -// 'type' => Database::VAR_INTEGER, -// 'default' => 0, -// 'required' => false, -// 'array' => false, -// ], -// [ -// '$collection' => Database::COLLECTION_RULES, -// 'label' => 'Date Updated', -// 'key' => 'dateUpdated', -// 'type' => Database::VAR_INTEGER, -// 'default' => 0, -// 'required' => false, -// 'array' => false, -// ], -// [ -// '$collection' => Database::COLLECTION_RULES, -// 'label' => 'Rules', -// 'key' => 'rules', -// 'type' => Database::VAR_DOCUMENT, -// 'default' => [], -// 'required' => false, -// 'array' => true, -// 'list' => [Database::COLLECTION_RULES], -// ], -// [ -// '$collection' => Database::COLLECTION_RULES, -// 'label' => 'Indexes', -// 'key' => 'indexes', -// 'type' => Database::VAR_DOCUMENT, -// 'default' => [], -// 'required' => false, -// 'array' => true, -// 'list' => [Database::COLLECTION_INDEXES], -// ], -// ], -// 'indexes' => [ -// [ -// '$collection' => Database::COLLECTION_INDEXES, -// '$id' => 'system1', -// 'type' => Database::INDEX_KEY, -// 'attributes' => [ -// 'name', -// ], -// ], -// [ -// '$collection' => Database::COLLECTION_INDEXES, -// '$id' => 'system2', -// 'type' => Database::INDEX_FULLTEXT, -// 'attributes' => [ -// 'name', -// ], -// ], -// ], -// ], -// Database::COLLECTION_RULES => [ -// '$collection' => Database::COLLECTION_COLLECTIONS, -// '$id' => Database::COLLECTION_RULES, -// '$permissions' => ['read' => ['*']], -// 'name' => 'Collections Rule', -// 'rules' => [ -// [ -// '$collection' => Database::COLLECTION_RULES, -// 'label' => 'Label', -// 'key' => 'label', -// 'type' => Database::VAR_TEXT, -// 'default' => '', -// 'required' => true, -// 'array' => false, -// ], -// [ -// '$collection' => Database::COLLECTION_RULES, -// 'label' => 'Key', -// 'key' => 'key', -// 'type' => Database::VAR_KEY, -// 'default' => '', -// 'required' => true, -// 'array' => false, -// ], -// [ -// '$collection' => Database::COLLECTION_RULES, -// 'label' => 'Type', -// 'key' => 'type', -// 'type' => Database::VAR_TEXT, -// 'default' => '', -// 'required' => true, -// 'array' => false, -// ], -// [ -// '$collection' => Database::COLLECTION_RULES, -// 'label' => 'Default', -// 'key' => 'default', -// 'type' => Database::VAR_TEXT, -// 'default' => '', -// 'required' => false, -// 'array' => false, -// ], -// [ -// '$collection' => Database::COLLECTION_RULES, -// 'label' => 'Required', -// 'key' => 'required', -// 'type' => Database::VAR_BOOLEAN, -// 'default' => true, -// 'required' => true, -// 'array' => false, -// ], -// [ -// '$collection' => Database::COLLECTION_RULES, -// 'label' => 'Array', -// 'key' => 'array', -// 'type' => Database::VAR_BOOLEAN, -// 'default' => true, -// 'required' => true, -// 'array' => false, -// ], -// [ -// '$collection' => Database::COLLECTION_RULES, -// 'label' => 'list', -// 'key' => 'list', -// 'type' => Database::VAR_TEXT, -// //'default' => '', -// 'required' => false, -// 'array' => true, -// ], -// ], -// ], -// Database::COLLECTION_INDEXES => [ -// '$collection' => Database::COLLECTION_COLLECTIONS, -// '$id' => Database::COLLECTION_INDEXES, -// '$permissions' => ['read' => ['*']], -// 'name' => 'Collections Indexes', -// 'rules' => [ -// [ -// '$collection' => Database::COLLECTION_RULES, -// 'label' => 'Type', -// 'key' => 'type', -// 'type' => Database::VAR_TEXT, -// 'default' => '', -// 'required' => true, -// 'array' => false, -// ], -// [ -// '$collection' => Database::COLLECTION_RULES, -// 'label' => 'Attributes', -// 'key' => 'attributes', -// 'type' => Database::VAR_KEY, -// 'default' => '', -// 'required' => true, -// 'array' => true, -// ], -// ], -// ], -// Database::COLLECTION_USERS => [ -// '$collection' => Database::COLLECTION_COLLECTIONS, -// '$id' => Database::COLLECTION_USERS, -// '$permissions' => ['read' => ['*']], -// 'name' => 'User', -// 'rules' => [ -// [ -// '$collection' => Database::COLLECTION_RULES, -// 'label' => 'Name', -// 'key' => 'name', -// 'type' => Database::VAR_TEXT, -// 'default' => '', -// 'required' => false, -// 'array' => false, -// ], -// [ -// '$collection' => Database::COLLECTION_RULES, -// 'label' => 'Email', -// 'key' => 'email', -// 'type' => Database::VAR_EMAIL, -// 'default' => '', -// 'required' => true, -// 'array' => false, -// ], -// [ -// '$collection' => Database::COLLECTION_RULES, -// 'label' => 'Status', -// 'key' => 'status', -// 'type' => Database::VAR_INTEGER, -// 'default' => '', -// 'required' => true, -// 'array' => false, -// ], -// [ -// '$collection' => Database::COLLECTION_RULES, -// 'label' => 'Password', -// 'key' => 'password', -// 'type' => Database::VAR_TEXT, -// 'default' => '', -// 'required' => true, -// 'array' => false, -// ], -// [ -// '$collection' => Database::COLLECTION_RULES, -// 'label' => 'Password Update Date', -// 'key' => 'password-update', -// 'type' => Database::VAR_INTEGER, -// 'default' => '', -// 'required' => true, -// 'array' => false, -// ], -// [ -// '$collection' => Database::COLLECTION_RULES, -// 'label' => 'Prefs', -// 'key' => 'prefs', -// 'type' => Database::VAR_TEXT, -// 'default' => '', -// 'required' => false, -// 'array' => false, -// 'filter' => ['json'] -// ], -// [ -// '$collection' => Database::COLLECTION_RULES, -// 'label' => 'Registration Date', -// 'key' => 'registration', -// 'type' => Database::VAR_INTEGER, -// 'default' => '', -// 'required' => true, -// 'array' => false, -// ], -// [ -// '$collection' => Database::COLLECTION_RULES, -// 'label' => 'Email Verification Status', -// 'key' => 'emailVerification', -// 'type' => Database::VAR_BOOLEAN, -// 'default' => '', -// 'required' => true, -// 'array' => false, -// ], -// [ -// '$collection' => Database::COLLECTION_RULES, -// 'label' => 'Reset', -// 'key' => 'reset', -// 'type' => Database::VAR_BOOLEAN, -// 'default' => '', -// 'required' => true, -// 'array' => false, -// ], -// [ -// '$collection' => Database::COLLECTION_RULES, -// 'label' => 'Tokens', -// 'key' => 'tokens', -// 'type' => Database::VAR_DOCUMENT, -// 'default' => [], -// 'required' => false, -// 'array' => true, -// 'list' => [Database::COLLECTION_TOKENS], -// ], -// [ -// '$collection' => Database::COLLECTION_RULES, -// 'label' => 'Memberships', -// 'key' => 'memberships', -// 'type' => Database::VAR_DOCUMENT, -// 'default' => [], -// 'required' => false, -// 'array' => true, -// 'list' => [Database::COLLECTION_MEMBERSHIPS], -// ], -// ], -// 'indexes' => [ -// [ -// '$collection' => Database::COLLECTION_INDEXES, -// '$id' => 'system1', -// 'type' => Database::INDEX_KEY, -// 'attributes' => [ -// 'name', -// ], -// ], -// [ -// '$collection' => Database::COLLECTION_INDEXES, -// '$id' => 'system2', -// 'type' => Database::INDEX_FULLTEXT, -// 'attributes' => [ -// 'name', -// ], -// ], -// [ -// '$collection' => Database::COLLECTION_INDEXES, -// '$id' => 'system3', -// 'type' => Database::INDEX_UNIQUE, -// 'attributes' => [ -// 'email', -// ], -// ], -// [ -// '$collection' => Database::COLLECTION_INDEXES, -// '$id' => 'system4', -// 'type' => Database::INDEX_KEY, -// 'attributes' => [ -// 'email', -// ], -// ], -// ], -// ], -// ]); - -// self::$object->setNamespace($namespace); -// self::$object->createNamespace($namespace); - -// self::$collection = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ -// '$collection' => Database::COLLECTION_COLLECTIONS, -// '$permissions' => ['read' => ['*']], -// 'name' => 'Tasks', -// 'rules' => [ -// [ -// '$collection' => Database::COLLECTION_RULES, -// '$permissions' => ['read' => ['*']], -// 'label' => 'Task Name', -// 'key' => 'name', -// 'type' => Database::VAR_TEXT, -// 'default' => '', -// 'required' => true, -// 'array' => false, -// ], -// ], -// ]); - -// self::$init = true; -// } - -// public function tearDown(): void -// { -// Authorization::reset(); -// } - -// public function testCreateCollection() -// { -// $collection = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ -// '$collection' => Database::COLLECTION_COLLECTIONS, -// '$permissions' => ['read' => ['*']], -// 'name' => 'Create', -// ]); - -// $this->assertEquals(true, self::$object->createCollection($collection->getId(), [], [])); - -// try { -// self::$object->createCollection($collection->getId(), [], []); -// } -// catch (\Throwable $th) { -// return $this->assertEquals('42S01', $th->getCode()); -// } - -// throw new Exception('Expected exception'); -// } - -// public function testDeleteCollection() -// { - -// $collection = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ -// '$collection' => Database::COLLECTION_COLLECTIONS, -// '$permissions' => ['read' => ['*']], -// 'name' => 'Delete', -// ]); - -// $this->assertEquals(true, self::$object->createCollection($collection->getId(), [], [])); -// $this->assertEquals(true, self::$object->deleteCollection($collection->getId())); - -// try { -// self::$object->deleteCollection($collection->getId()); -// } -// catch (\Throwable $th) { -// return $this->assertEquals('42S02', $th->getCode()); -// } - -// throw new Exception('Expected exception'); -// } - -// public function testCreateAttribute() -// { -// $collection = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ -// '$collection' => Database::COLLECTION_COLLECTIONS, -// '$permissions' => ['read' => ['*']], -// 'name' => 'Create Attribute', -// 'rules' => [], -// ]); - -// $this->assertEquals(true, self::$object->createCollection($collection->getId(), [], [])); -// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'title', Database::VAR_TEXT)); -// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'description', Database::VAR_TEXT)); -// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'numeric', Database::VAR_NUMERIC)); -// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'integer', Database::VAR_INTEGER)); -// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'float', Database::VAR_FLOAT)); -// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'boolean', Database::VAR_BOOLEAN)); -// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'document', Database::VAR_DOCUMENT)); -// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'email', Database::VAR_EMAIL)); -// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'url', Database::VAR_URL)); -// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'ipv4', Database::VAR_IPV4)); -// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'ipv6', Database::VAR_IPV6)); -// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'key', Database::VAR_KEY)); - -// // arrays -// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'titles', Database::VAR_TEXT, true)); -// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'descriptions', Database::VAR_TEXT, true)); -// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'numerics', Database::VAR_NUMERIC, true)); -// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'integers', Database::VAR_INTEGER, true)); -// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'floats', Database::VAR_FLOAT, true)); -// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'booleans', Database::VAR_BOOLEAN, true)); -// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'documents', Database::VAR_DOCUMENT, true)); -// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'emails', Database::VAR_EMAIL, true)); -// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'urls', Database::VAR_URL, true)); -// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'ipv4s', Database::VAR_IPV4, true)); -// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'ipv6s', Database::VAR_IPV6, true)); -// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'keys', Database::VAR_KEY, true)); -// } - -// public function testDeleteAttribute() -// { -// $collection = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ -// '$collection' => Database::COLLECTION_COLLECTIONS, -// '$permissions' => ['read' => ['*']], -// 'name' => 'Delete Attribute', -// 'rules' => [], -// ]); - -// $this->assertEquals(true, self::$object->createCollection($collection->getId(), [], [])); - -// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'title', Database::VAR_TEXT)); -// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'description', Database::VAR_TEXT)); -// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'value', Database::VAR_NUMERIC)); - -// $this->assertEquals(true, self::$object->deleteAttribute($collection->getId(), 'title', false)); -// $this->assertEquals(true, self::$object->deleteAttribute($collection->getId(), 'description', false)); -// $this->assertEquals(true, self::$object->deleteAttribute($collection->getId(), 'value', false)); - -// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'titles', Database::VAR_TEXT, true)); -// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'descriptions', Database::VAR_TEXT, true)); -// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'values', Database::VAR_NUMERIC, true)); - -// $this->assertEquals(true, self::$object->deleteAttribute($collection->getId(), 'titles', true)); -// $this->assertEquals(true, self::$object->deleteAttribute($collection->getId(), 'descriptions', true)); -// $this->assertEquals(true, self::$object->deleteAttribute($collection->getId(), 'values', true)); -// } - -// public function testCreateIndex() -// { -// $collection = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ -// '$collection' => Database::COLLECTION_COLLECTIONS, -// '$permissions' => ['read' => ['*']], -// 'name' => 'Create Index', -// 'rules' => [], -// ]); - -// $this->assertEquals(true, self::$object->createCollection($collection->getId(), [], [])); -// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'title', Database::VAR_TEXT)); -// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'description', Database::VAR_TEXT)); -// $this->assertEquals(true, self::$object->createIndex($collection->getId(), 'x', Database::INDEX_KEY, ['title'])); -// $this->assertEquals(true, self::$object->createIndex($collection->getId(), 'y', Database::INDEX_KEY, ['description'])); -// $this->assertEquals(true, self::$object->createIndex($collection->getId(), 'z', Database::INDEX_KEY, ['title', 'description'])); -// } - -// public function testDeleteIndex() -// { -// $collection = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ -// '$collection' => Database::COLLECTION_COLLECTIONS, -// '$permissions' => ['read' => ['*']], -// 'name' => 'Delete Index', -// 'rules' => [], -// ]); - -// $this->assertEquals(true, self::$object->createCollection($collection->getId(), [], [])); -// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'title', Database::VAR_TEXT)); -// $this->assertEquals(true, self::$object->createAttribute($collection->getId(), 'description', Database::VAR_TEXT)); -// $this->assertEquals(true, self::$object->createIndex($collection->getId(), 'x', Database::INDEX_KEY, ['title'])); -// $this->assertEquals(true, self::$object->createIndex($collection->getId(), 'y', Database::INDEX_KEY, ['description'])); -// $this->assertEquals(true, self::$object->createIndex($collection->getId(), 'z', Database::INDEX_KEY, ['title', 'description'])); - -// $this->assertEquals(true, self::$object->deleteIndex($collection->getId(), 'x')); -// $this->assertEquals(true, self::$object->deleteIndex($collection->getId(), 'y')); -// $this->assertEquals(true, self::$object->deleteIndex($collection->getId(), 'z')); -// } - -// public function testCreateDocument() -// { -// $collection1 = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ -// '$collection' => Database::COLLECTION_COLLECTIONS, -// '$permissions' => ['read' => ['*']], -// 'name' => 'Create Documents', -// 'rules' => [ -// [ -// '$collection' => Database::COLLECTION_RULES, -// '$permissions' => ['read' => ['*']], -// 'label' => 'Name', -// 'key' => 'name', -// 'type' => Database::VAR_TEXT, -// 'default' => '', -// 'required' => true, -// 'array' => false, -// ], -// [ -// '$collection' => Database::COLLECTION_RULES, -// '$permissions' => ['read' => ['*']], -// 'label' => 'Links', -// 'key' => 'links', -// 'type' => Database::VAR_URL, -// 'default' => '', -// 'required' => true, -// 'array' => true, -// ], -// ] -// ]); - -// $this->assertEquals(true, self::$object->createCollection($collection1->getId(), [], [])); - -// $document0 = new Document([ -// '$collection' => $collection1->getId(), -// '$permissions' => [ -// 'read' => ['*'], -// 'write' => ['user:123'], -// ], -// 'name' => 'Task #0', -// 'links' => [ -// 'http://example.com/link-1', -// 'http://example.com/link-2', -// 'http://example.com/link-3', -// 'http://example.com/link-4', -// ], -// ]); - -// $document1 = self::$object->createDocument($collection1->getId(), [ -// '$collection' => $collection1->getId(), -// '$permissions' => [ -// 'read' => ['*'], -// 'write' => ['user:123'], -// ], -// 'name' => 'Task #1️⃣', -// 'links' => [ -// 'http://example.com/link-5', -// 'http://example.com/link-6', -// 'http://example.com/link-7', -// 'http://example.com/link-8', -// ], -// ]); - -// $this->assertNotEmpty($document1->getId()); -// $this->assertIsArray($document1->getPermissions()); -// $this->assertArrayHasKey('read', $document1->getPermissions()); -// $this->assertArrayHasKey('write', $document1->getPermissions()); -// $this->assertEquals('Task #1️⃣', $document1->getAttribute('name')); -// $this->assertCount(4, $document1->getAttribute('links')); -// $this->assertEquals('http://example.com/link-5', $document1->getAttribute('links')[0]); -// $this->assertEquals('http://example.com/link-6', $document1->getAttribute('links')[1]); -// $this->assertEquals('http://example.com/link-7', $document1->getAttribute('links')[2]); -// $this->assertEquals('http://example.com/link-8', $document1->getAttribute('links')[3]); - -// $document2 = self::$object->createDocument(Database::COLLECTION_USERS, [ -// '$collection' => Database::COLLECTION_USERS, -// '$permissions' => [ -// 'read' => ['*'], -// 'write' => ['user:123'], -// ], -// 'email' => 'test@appwrite.io', -// 'emailVerification' => false, -// 'status' => 0, -// 'password' => 'secrethash', -// 'password-update' => \time(), -// 'registration' => \time(), -// 'reset' => false, -// 'name' => 'Test', -// ]); - -// $this->assertNotEmpty($document2->getId()); -// $this->assertIsArray($document2->getPermissions()); -// $this->assertArrayHasKey('read', $document2->getPermissions()); -// $this->assertArrayHasKey('write', $document2->getPermissions()); -// $this->assertEquals('test@appwrite.io', $document2->getAttribute('email')); -// $this->assertIsString($document2->getAttribute('email')); -// $this->assertEquals(0, $document2->getAttribute('status')); -// $this->assertIsInt($document2->getAttribute('status')); -// $this->assertEquals(false, $document2->getAttribute('emailVerification')); -// $this->assertIsBool($document2->getAttribute('emailVerification')); - -// $document2 = self::$object->getDocument(Database::COLLECTION_USERS, $document2->getId()); - -// $this->assertNotEmpty($document2->getId()); -// $this->assertIsArray($document2->getPermissions()); -// $this->assertArrayHasKey('read', $document2->getPermissions()); -// $this->assertArrayHasKey('write', $document2->getPermissions()); -// $this->assertEquals('test@appwrite.io', $document2->getAttribute('email')); -// $this->assertIsString($document2->getAttribute('email')); -// $this->assertEquals(0, $document2->getAttribute('status')); -// $this->assertIsInt($document2->getAttribute('status')); -// $this->assertEquals(false, $document2->getAttribute('emailVerification')); -// $this->assertIsBool($document2->getAttribute('emailVerification')); - -// $types = [ -// Database::VAR_TEXT, -// Database::VAR_INTEGER, -// Database::VAR_FLOAT, -// Database::VAR_NUMERIC, -// Database::VAR_BOOLEAN, -// Database::VAR_DOCUMENT, -// Database::VAR_EMAIL, -// Database::VAR_URL, -// Database::VAR_IPV4, -// Database::VAR_IPV6, -// Database::VAR_KEY, -// ]; - -// $rules = []; - -// foreach($types as $type) { -// $rules[] = [ -// '$collection' => Database::COLLECTION_RULES, -// '$permissions' => ['read' => ['*']], -// 'label' => ucfirst($type), -// 'key' => $type, -// 'type' => $type, -// 'default' => null, -// 'required' => true, -// 'array' => false, -// 'list' => ($type === Database::VAR_DOCUMENT) ? [$collection1->getId()] : [], -// ]; - -// $rules[] = [ -// '$collection' => Database::COLLECTION_RULES, -// '$permissions' => ['read' => ['*']], -// 'label' => ucfirst($type), -// 'key' => $type.'s', -// 'type' => $type, -// 'default' => null, -// 'required' => true, -// 'array' => true, -// 'list' => ($type === Database::VAR_DOCUMENT) ? [$collection1->getId()] : [], -// ]; -// } - -// $rules[] = [ -// '$collection' => Database::COLLECTION_RULES, -// '$permissions' => ['read' => ['*']], -// 'label' => 'document2', -// 'key' => 'document2', -// 'type' => Database::VAR_DOCUMENT, -// 'default' => null, -// 'required' => true, -// 'array' => false, -// 'list' => [$collection1->getId()], -// ]; - -// $rules[] = [ -// '$collection' => Database::COLLECTION_RULES, -// '$permissions' => ['read' => ['*']], -// 'label' => 'documents2', -// 'key' => 'documents2', -// 'type' => Database::VAR_DOCUMENT, -// 'default' => null, -// 'required' => true, -// 'array' => true, -// 'list' => [$collection1->getId()], -// ]; - -// $collection2 = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ -// '$collection' => Database::COLLECTION_COLLECTIONS, -// '$permissions' => ['read' => ['*']], -// 'name' => 'Create Documents', -// 'rules' => $rules, -// ]); - -// $this->assertEquals(true, self::$object->createCollection($collection2->getId(), [], [])); - -// $document3 = self::$object->createDocument($collection2->getId(), [ -// '$collection' => $collection2->getId(), -// '$permissions' => [ -// 'read' => ['*'], -// 'write' => ['user:123'], -// ], -// 'text' => 'Hello World', -// 'texts' => ['Hello World 1', 'Hello World 2'], -// // 'document' => $document0, -// // 'documents' => [$document0], -// 'document' => $document0, -// 'documents' => [$document1, $document0], -// 'document2' => $document1, -// 'documents2' => [$document0, $document1], -// 'integer' => 1, -// 'integers' => [5, 3, 4], -// 'float' => 2.22, -// 'floats' => [1.13, 4.33, 8.9999], -// 'numeric' => 1, -// 'numerics' => [1, 5, 7.77], -// 'boolean' => true, -// 'booleans' => [true, false, true], -// 'email' => 'test@appwrite.io', -// 'emails' => [ -// 'test4@appwrite.io', -// 'test3@appwrite.io', -// 'test2@appwrite.io', -// 'test1@appwrite.io' -// ], -// 'url' => 'http://example.com/welcome', -// 'urls' => [ -// 'http://example.com/welcome-1', -// 'http://example.com/welcome-2', -// 'http://example.com/welcome-3' -// ], -// 'ipv4' => '172.16.254.1', -// 'ipv4s' => [ -// '172.16.254.1', -// '172.16.254.5' -// ], -// 'ipv6' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334', -// 'ipv6s' => [ -// '2001:0db8:85a3:0000:0000:8a2e:0370:7334', -// '2001:0db8:85a3:0000:0000:8a2e:0370:7337' -// ], -// 'key' => uniqid(), -// 'keys' => [uniqid(), uniqid(), uniqid()], -// ]); - -// $document3 = self::$object->getDocument($collection2->getId(), $document3->getId()); - -// $this->assertIsString($document3->getId()); -// $this->assertIsString($document3->getCollection()); -// $this->assertEquals([ -// 'read' => ['*'], -// 'write' => ['user:123'], -// ], $document3->getPermissions()); -// $this->assertEquals('Hello World', $document3->getAttribute('text')); -// $this->assertCount(2, $document3->getAttribute('texts')); - -// $this->assertIsString($document3->getAttribute('text')); -// $this->assertEquals('Hello World', $document3->getAttribute('text')); -// $this->assertEquals(['Hello World 1', 'Hello World 2'], $document3->getAttribute('texts')); -// $this->assertCount(2, $document3->getAttribute('texts')); - -// $this->assertInstanceOf(Document::class, $document3->getAttribute('document')); -// $this->assertIsString($document3->getAttribute('document')->getId()); -// $this->assertNotEmpty($document3->getAttribute('document')->getId()); -// $this->assertIsArray($document3->getAttribute('document')->getPermissions()); -// $this->assertArrayHasKey('read', $document3->getAttribute('document')->getPermissions()); -// $this->assertArrayHasKey('write', $document3->getAttribute('document')->getPermissions()); -// $this->assertEquals('Task #0', $document3->getAttribute('document')->getAttribute('name')); -// $this->assertCount(4, $document3->getAttribute('document')->getAttribute('links')); -// $this->assertEquals('http://example.com/link-1', $document3->getAttribute('document')->getAttribute('links')[0]); -// $this->assertEquals('http://example.com/link-2', $document3->getAttribute('document')->getAttribute('links')[1]); -// $this->assertEquals('http://example.com/link-3', $document3->getAttribute('document')->getAttribute('links')[2]); -// $this->assertEquals('http://example.com/link-4', $document3->getAttribute('document')->getAttribute('links')[3]); - -// $this->assertInstanceOf(Document::class, $document3->getAttribute('documents')[0]); -// $this->assertIsString($document3->getAttribute('documents')[0]->getId()); -// $this->assertNotEmpty($document3->getAttribute('documents')[0]->getId()); -// $this->assertIsArray($document3->getAttribute('documents')[0]->getPermissions()); -// $this->assertArrayHasKey('read', $document3->getAttribute('documents')[0]->getPermissions()); -// $this->assertArrayHasKey('write', $document3->getAttribute('documents')[0]->getPermissions()); -// $this->assertEquals('Task #1️⃣', $document3->getAttribute('documents')[0]->getAttribute('name')); -// $this->assertCount(4, $document3->getAttribute('documents')[0]->getAttribute('links')); -// $this->assertEquals('http://example.com/link-5', $document3->getAttribute('documents')[0]->getAttribute('links')[0]); -// $this->assertEquals('http://example.com/link-6', $document3->getAttribute('documents')[0]->getAttribute('links')[1]); -// $this->assertEquals('http://example.com/link-7', $document3->getAttribute('documents')[0]->getAttribute('links')[2]); -// $this->assertEquals('http://example.com/link-8', $document3->getAttribute('documents')[0]->getAttribute('links')[3]); - -// $this->assertInstanceOf(Document::class, $document3->getAttribute('documents')[1]); -// $this->assertIsString($document3->getAttribute('documents')[1]->getId()); -// $this->assertNotEmpty($document3->getAttribute('documents')[1]->getId()); -// $this->assertIsArray($document3->getAttribute('documents')[1]->getPermissions()); -// $this->assertArrayHasKey('read', $document3->getAttribute('documents')[1]->getPermissions()); -// $this->assertArrayHasKey('write', $document3->getAttribute('documents')[1]->getPermissions()); -// $this->assertEquals('Task #0', $document3->getAttribute('documents')[1]->getAttribute('name')); -// $this->assertCount(4, $document3->getAttribute('documents')[1]->getAttribute('links')); -// $this->assertEquals('http://example.com/link-1', $document3->getAttribute('documents')[1]->getAttribute('links')[0]); -// $this->assertEquals('http://example.com/link-2', $document3->getAttribute('documents')[1]->getAttribute('links')[1]); -// $this->assertEquals('http://example.com/link-3', $document3->getAttribute('documents')[1]->getAttribute('links')[2]); -// $this->assertEquals('http://example.com/link-4', $document3->getAttribute('documents')[1]->getAttribute('links')[3]); - -// $this->assertInstanceOf(Document::class, $document3->getAttribute('document2')); -// $this->assertIsString($document3->getAttribute('document2')->getId()); -// $this->assertNotEmpty($document3->getAttribute('document2')->getId()); -// $this->assertIsArray($document3->getAttribute('document2')->getPermissions()); -// $this->assertArrayHasKey('read', $document3->getAttribute('document2')->getPermissions()); -// $this->assertArrayHasKey('write', $document3->getAttribute('document2')->getPermissions()); -// $this->assertEquals('Task #1️⃣', $document3->getAttribute('document2')->getAttribute('name')); -// $this->assertCount(4, $document3->getAttribute('document2')->getAttribute('links')); -// $this->assertEquals('http://example.com/link-5', $document3->getAttribute('document2')->getAttribute('links')[0]); -// $this->assertEquals('http://example.com/link-6', $document3->getAttribute('document2')->getAttribute('links')[1]); -// $this->assertEquals('http://example.com/link-7', $document3->getAttribute('document2')->getAttribute('links')[2]); -// $this->assertEquals('http://example.com/link-8', $document3->getAttribute('document2')->getAttribute('links')[3]); - -// $this->assertInstanceOf(Document::class, $document3->getAttribute('documents2')[0]); -// $this->assertIsString($document3->getAttribute('documents2')[0]->getId()); -// $this->assertNotEmpty($document3->getAttribute('documents2')[0]->getId()); -// $this->assertIsArray($document3->getAttribute('documents2')[0]->getPermissions()); -// $this->assertArrayHasKey('read', $document3->getAttribute('documents2')[0]->getPermissions()); -// $this->assertArrayHasKey('write', $document3->getAttribute('documents2')[0]->getPermissions()); -// $this->assertEquals('Task #0', $document3->getAttribute('documents2')[0]->getAttribute('name')); -// $this->assertCount(4, $document3->getAttribute('documents2')[0]->getAttribute('links')); -// $this->assertEquals('http://example.com/link-1', $document3->getAttribute('documents2')[0]->getAttribute('links')[0]); -// $this->assertEquals('http://example.com/link-2', $document3->getAttribute('documents2')[0]->getAttribute('links')[1]); -// $this->assertEquals('http://example.com/link-3', $document3->getAttribute('documents2')[0]->getAttribute('links')[2]); -// $this->assertEquals('http://example.com/link-4', $document3->getAttribute('documents2')[0]->getAttribute('links')[3]); - -// $this->assertInstanceOf(Document::class, $document3->getAttribute('documents2')[1]); -// $this->assertIsString($document3->getAttribute('documents2')[1]->getId()); -// $this->assertNotEmpty($document3->getAttribute('documents2')[1]->getId()); -// $this->assertIsArray($document3->getAttribute('documents2')[1]->getPermissions()); -// $this->assertArrayHasKey('read', $document3->getAttribute('documents2')[1]->getPermissions()); -// $this->assertArrayHasKey('write', $document3->getAttribute('documents2')[1]->getPermissions()); -// $this->assertEquals('Task #1️⃣', $document3->getAttribute('documents2')[1]->getAttribute('name')); -// $this->assertCount(4, $document3->getAttribute('documents2')[1]->getAttribute('links')); -// $this->assertEquals('http://example.com/link-5', $document3->getAttribute('documents2')[1]->getAttribute('links')[0]); -// $this->assertEquals('http://example.com/link-6', $document3->getAttribute('documents2')[1]->getAttribute('links')[1]); -// $this->assertEquals('http://example.com/link-7', $document3->getAttribute('documents2')[1]->getAttribute('links')[2]); -// $this->assertEquals('http://example.com/link-8', $document3->getAttribute('documents2')[1]->getAttribute('links')[3]); - -// $this->assertIsInt($document3->getAttribute('integer')); -// $this->assertEquals(1, $document3->getAttribute('integer')); -// $this->assertIsInt($document3->getAttribute('integers')[0]); -// $this->assertIsInt($document3->getAttribute('integers')[1]); -// $this->assertIsInt($document3->getAttribute('integers')[2]); -// $this->assertEquals([5, 3, 4], $document3->getAttribute('integers')); -// $this->assertCount(3, $document3->getAttribute('integers')); - -// $this->assertIsFloat($document3->getAttribute('float')); -// $this->assertEquals(2.22, $document3->getAttribute('float')); -// $this->assertIsFloat($document3->getAttribute('floats')[0]); -// $this->assertIsFloat($document3->getAttribute('floats')[1]); -// $this->assertIsFloat($document3->getAttribute('floats')[2]); -// $this->assertEquals([1.13, 4.33, 8.9999], $document3->getAttribute('floats')); -// $this->assertCount(3, $document3->getAttribute('floats')); - -// $this->assertIsBool($document3->getAttribute('boolean')); -// $this->assertEquals(true, $document3->getAttribute('boolean')); -// $this->assertIsBool($document3->getAttribute('booleans')[0]); -// $this->assertIsBool($document3->getAttribute('booleans')[1]); -// $this->assertIsBool($document3->getAttribute('booleans')[2]); -// $this->assertEquals([true, false, true], $document3->getAttribute('booleans')); -// $this->assertCount(3, $document3->getAttribute('booleans')); - -// $this->assertIsString($document3->getAttribute('email')); -// $this->assertEquals('test@appwrite.io', $document3->getAttribute('email')); -// $this->assertIsString($document3->getAttribute('emails')[0]); -// $this->assertIsString($document3->getAttribute('emails')[1]); -// $this->assertIsString($document3->getAttribute('emails')[2]); -// $this->assertIsString($document3->getAttribute('emails')[3]); -// $this->assertEquals([ -// 'test4@appwrite.io', -// 'test3@appwrite.io', -// 'test2@appwrite.io', -// 'test1@appwrite.io' -// ], $document3->getAttribute('emails')); -// $this->assertCount(4, $document3->getAttribute('emails')); - -// $this->assertIsString($document3->getAttribute('url')); -// $this->assertEquals('http://example.com/welcome', $document3->getAttribute('url')); -// $this->assertIsString($document3->getAttribute('urls')[0]); -// $this->assertIsString($document3->getAttribute('urls')[1]); -// $this->assertIsString($document3->getAttribute('urls')[2]); -// $this->assertEquals([ -// 'http://example.com/welcome-1', -// 'http://example.com/welcome-2', -// 'http://example.com/welcome-3' -// ], $document3->getAttribute('urls')); -// $this->assertCount(3, $document3->getAttribute('urls')); - -// $this->assertIsString($document3->getAttribute('ipv4')); -// $this->assertEquals('172.16.254.1', $document3->getAttribute('ipv4')); -// $this->assertIsString($document3->getAttribute('ipv4s')[0]); -// $this->assertIsString($document3->getAttribute('ipv4s')[1]); -// $this->assertEquals([ -// '172.16.254.1', -// '172.16.254.5' -// ], $document3->getAttribute('ipv4s')); -// $this->assertCount(2, $document3->getAttribute('ipv4s')); - -// $this->assertIsString($document3->getAttribute('ipv6')); -// $this->assertEquals('2001:0db8:85a3:0000:0000:8a2e:0370:7334', $document3->getAttribute('ipv6')); -// $this->assertIsString($document3->getAttribute('ipv6s')[0]); -// $this->assertIsString($document3->getAttribute('ipv6s')[1]); -// $this->assertEquals([ -// '2001:0db8:85a3:0000:0000:8a2e:0370:7334', -// '2001:0db8:85a3:0000:0000:8a2e:0370:7337' -// ], $document3->getAttribute('ipv6s')); -// $this->assertCount(2, $document3->getAttribute('ipv6s')); - -// $this->assertEquals('2001:0db8:85a3:0000:0000:8a2e:0370:7334', $document3->getAttribute('ipv6')); -// $this->assertEquals([ -// '2001:0db8:85a3:0000:0000:8a2e:0370:7334', -// '2001:0db8:85a3:0000:0000:8a2e:0370:7337' -// ], $document3->getAttribute('ipv6s')); -// $this->assertCount(2, $document3->getAttribute('ipv6s')); - -// $this->assertIsString($document3->getAttribute('key')); -// $this->assertCount(3, $document3->getAttribute('keys')); -// } - -// public function testGetDocument() -// { -// // Mocked document -// $document = self::$object->getDocument(Database::COLLECTION_COLLECTIONS, Database::COLLECTION_USERS); - -// $this->assertEquals(Database::COLLECTION_USERS, $document->getId()); -// $this->assertEquals(Database::COLLECTION_COLLECTIONS, $document->getCollection()); - -// $collection1 = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ -// '$collection' => Database::COLLECTION_COLLECTIONS, -// '$permissions' => ['read' => ['*']], -// 'name' => 'Create Documents', -// 'rules' => [ -// [ -// '$collection' => Database::COLLECTION_RULES, -// '$permissions' => ['read' => ['*']], -// 'label' => 'Name', -// 'key' => 'name', -// 'type' => Database::VAR_TEXT, -// 'default' => '', -// 'required' => true, -// 'array' => false, -// ], -// [ -// '$collection' => Database::COLLECTION_RULES, -// '$permissions' => ['read' => ['*']], -// 'label' => 'Links', -// 'key' => 'links', -// 'type' => Database::VAR_URL, -// 'default' => '', -// 'required' => true, -// 'array' => true, -// ], -// ] -// ]); - -// $this->assertEquals(true, self::$object->createCollection($collection1->getId(), [], [])); - -// $document1 = self::$object->createDocument($collection1->getId(), [ -// '$collection' => $collection1->getId(), -// '$permissions' => [ -// 'read' => ['*'], -// 'write' => ['user:123'], -// ], -// 'name' => 'Task #1️⃣', -// 'links' => [ -// 'http://example.com/link-5', -// 'http://example.com/link-6', -// 'http://example.com/link-7', -// 'http://example.com/link-8', -// ], -// ]); - -// $document1 = self::$object->getDocument($collection1->getId(), $document1->getId()); - -// $this->assertFalse($document1->isEmpty()); -// $this->assertNotEmpty($document1->getId()); -// $this->assertIsArray($document1->getPermissions()); -// $this->assertArrayHasKey('read', $document1->getPermissions()); -// $this->assertArrayHasKey('write', $document1->getPermissions()); -// $this->assertEquals('Task #1️⃣', $document1->getAttribute('name')); -// $this->assertCount(4, $document1->getAttribute('links')); -// $this->assertEquals('http://example.com/link-5', $document1->getAttribute('links')[0]); -// $this->assertEquals('http://example.com/link-6', $document1->getAttribute('links')[1]); -// $this->assertEquals('http://example.com/link-7', $document1->getAttribute('links')[2]); -// $this->assertEquals('http://example.com/link-8', $document1->getAttribute('links')[3]); - -// $document1 = self::$object->getDocument($collection1->getId(), $document1->getId().'x'); - -// $this->assertTrue($document1->isEmpty()); -// $this->assertEmpty($document1->getId()); -// $this->assertEmpty($document1->getCollection()); -// $this->assertIsArray($document1->getPermissions()); -// $this->assertEmpty($document1->getPermissions()); -// } - -// public function testUpdateDocument() -// { -// $collection1 = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ -// '$collection' => Database::COLLECTION_COLLECTIONS, -// '$permissions' => ['read' => ['*']], -// 'name' => 'Create Documents', -// 'rules' => [ -// [ -// '$collection' => Database::COLLECTION_RULES, -// '$permissions' => ['read' => ['*']], -// 'label' => 'Name', -// 'key' => 'name', -// 'type' => Database::VAR_TEXT, -// 'default' => '', -// 'required' => true, -// 'array' => false, -// ], -// [ -// '$collection' => Database::COLLECTION_RULES, -// '$permissions' => ['read' => ['*']], -// 'label' => 'Links', -// 'key' => 'links', -// 'type' => Database::VAR_URL, -// 'default' => '', -// 'required' => true, -// 'array' => true, -// ], -// ] -// ]); - -// $this->assertEquals(true, self::$object->createCollection($collection1->getId(), [], [])); - -// $document0 = new Document([ -// '$collection' => $collection1->getId(), -// '$permissions' => [ -// 'read' => ['*'], -// 'write' => ['user:123'], -// ], -// 'name' => 'Task #0', -// 'links' => [ -// 'http://example.com/link-1', -// 'http://example.com/link-2', -// 'http://example.com/link-3', -// 'http://example.com/link-4', -// ], -// ]); - -// $document1 = self::$object->createDocument($collection1->getId(), [ -// '$collection' => $collection1->getId(), -// '$permissions' => [ -// 'read' => ['*'], -// 'write' => ['user:123'], -// ], -// 'name' => 'Task #1️⃣', -// 'links' => [ -// 'http://example.com/link-5', -// 'http://example.com/link-6', -// 'http://example.com/link-7', -// 'http://example.com/link-8', -// ], -// ]); - -// $this->assertNotEmpty($document1->getId()); -// $this->assertIsArray($document1->getPermissions()); -// $this->assertArrayHasKey('read', $document1->getPermissions()); -// $this->assertArrayHasKey('write', $document1->getPermissions()); -// $this->assertEquals('Task #1️⃣', $document1->getAttribute('name')); -// $this->assertCount(4, $document1->getAttribute('links')); -// $this->assertEquals('http://example.com/link-5', $document1->getAttribute('links')[0]); -// $this->assertEquals('http://example.com/link-6', $document1->getAttribute('links')[1]); -// $this->assertEquals('http://example.com/link-7', $document1->getAttribute('links')[2]); -// $this->assertEquals('http://example.com/link-8', $document1->getAttribute('links')[3]); - -// $document1 = self::$object->getDocument($collection1->getId(), $document1->getId()); - -// $this->assertNotEmpty($document1->getId()); -// $this->assertIsArray($document1->getPermissions()); -// $this->assertArrayHasKey('read', $document1->getPermissions()); -// $this->assertArrayHasKey('write', $document1->getPermissions()); -// $this->assertEquals('Task #1️⃣', $document1->getAttribute('name')); -// $this->assertCount(4, $document1->getAttribute('links')); -// $this->assertEquals('http://example.com/link-5', $document1->getAttribute('links')[0]); -// $this->assertEquals('http://example.com/link-6', $document1->getAttribute('links')[1]); -// $this->assertEquals('http://example.com/link-7', $document1->getAttribute('links')[2]); -// $this->assertEquals('http://example.com/link-8', $document1->getAttribute('links')[3]); - -// $document1 = self::$object->updateDocument($collection1->getId(), $document1->getId(), [ -// '$id' => $document1->getId(), -// '$collection' => $collection1->getId(), -// '$permissions' => [ -// 'read' => ['user:1234'], -// 'write' => ['user:1234'], -// ], -// 'name' => 'Task #1x', -// 'links' => [ -// 'http://example.com/link-5x', -// 'http://example.com/link-6x', -// 'http://example.com/link-7x', -// 'http://example.com/link-8x', -// ], -// ]); - -// $this->assertNotEmpty($document1->getId()); -// $this->assertIsArray($document1->getPermissions()); -// $this->assertArrayHasKey('read', $document1->getPermissions()); -// $this->assertArrayHasKey('write', $document1->getPermissions()); -// $this->assertEquals('Task #1x', $document1->getAttribute('name')); -// $this->assertCount(4, $document1->getAttribute('links')); -// $this->assertEquals('http://example.com/link-5x', $document1->getAttribute('links')[0]); -// $this->assertEquals('http://example.com/link-6x', $document1->getAttribute('links')[1]); -// $this->assertEquals('http://example.com/link-7x', $document1->getAttribute('links')[2]); -// $this->assertEquals('http://example.com/link-8x', $document1->getAttribute('links')[3]); - -// $document1 = self::$object->getDocument($collection1->getId(), $document1->getId()); - -// $this->assertNotEmpty($document1->getId()); -// $this->assertIsArray($document1->getPermissions()); -// $this->assertArrayHasKey('read', $document1->getPermissions()); -// $this->assertArrayHasKey('write', $document1->getPermissions()); -// $this->assertEquals('Task #1x', $document1->getAttribute('name')); -// $this->assertCount(4, $document1->getAttribute('links')); -// $this->assertEquals('http://example.com/link-5x', $document1->getAttribute('links')[0]); -// $this->assertEquals('http://example.com/link-6x', $document1->getAttribute('links')[1]); -// $this->assertEquals('http://example.com/link-7x', $document1->getAttribute('links')[2]); -// $this->assertEquals('http://example.com/link-8x', $document1->getAttribute('links')[3]); - -// $document2 = self::$object->createDocument(Database::COLLECTION_USERS, [ -// '$collection' => Database::COLLECTION_USERS, -// '$permissions' => [ -// 'read' => ['*'], -// 'write' => ['user:123'], -// ], -// 'email' => 'test5@appwrite.io', -// 'emailVerification' => false, -// 'status' => 0, -// 'password' => 'secrethash', -// 'password-update' => \time(), -// 'registration' => \time(), -// 'reset' => false, -// 'name' => 'Test', -// ]); - -// $this->assertNotEmpty($document2->getId()); -// $this->assertIsArray($document2->getPermissions()); -// $this->assertArrayHasKey('read', $document2->getPermissions()); -// $this->assertArrayHasKey('write', $document2->getPermissions()); -// $this->assertEquals('test5@appwrite.io', $document2->getAttribute('email')); -// $this->assertIsString($document2->getAttribute('email')); -// $this->assertEquals(0, $document2->getAttribute('status')); -// $this->assertIsInt($document2->getAttribute('status')); -// $this->assertEquals(false, $document2->getAttribute('emailVerification')); -// $this->assertIsBool($document2->getAttribute('emailVerification')); - -// $document2 = self::$object->getDocument(Database::COLLECTION_USERS, $document2->getId()); - -// $this->assertNotEmpty($document2->getId()); -// $this->assertIsArray($document2->getPermissions()); -// $this->assertArrayHasKey('read', $document2->getPermissions()); -// $this->assertArrayHasKey('write', $document2->getPermissions()); -// $this->assertEquals('test5@appwrite.io', $document2->getAttribute('email')); -// $this->assertIsString($document2->getAttribute('email')); -// $this->assertEquals(0, $document2->getAttribute('status')); -// $this->assertIsInt($document2->getAttribute('status')); -// $this->assertEquals(false, $document2->getAttribute('emailVerification')); -// $this->assertIsBool($document2->getAttribute('emailVerification')); - -// $document2 = self::$object->updateDocument(Database::COLLECTION_USERS, $document2->getId(), [ -// '$id' => $document2->getId(), -// '$collection' => Database::COLLECTION_USERS, -// '$permissions' => [ -// 'read' => ['user:1234'], -// 'write' => ['user:1234'], -// ], -// 'email' => 'test5x@appwrite.io', -// 'emailVerification' => true, -// 'status' => 1, -// 'password' => 'secrethashx', -// 'password-update' => \time(), -// 'registration' => \time(), -// 'reset' => true, -// 'name' => 'Testx', -// ]); - -// $this->assertNotEmpty($document2->getId()); -// $this->assertIsArray($document2->getPermissions()); -// $this->assertArrayHasKey('read', $document2->getPermissions()); -// $this->assertArrayHasKey('write', $document2->getPermissions()); -// $this->assertEquals('test5x@appwrite.io', $document2->getAttribute('email')); -// $this->assertIsString($document2->getAttribute('email')); -// $this->assertEquals(1, $document2->getAttribute('status')); -// $this->assertIsInt($document2->getAttribute('status')); -// $this->assertEquals(true, $document2->getAttribute('emailVerification')); -// $this->assertIsBool($document2->getAttribute('emailVerification')); - -// $document2 = self::$object->getDocument(Database::COLLECTION_USERS, $document2->getId()); - -// $this->assertNotEmpty($document2->getId()); -// $this->assertIsArray($document2->getPermissions()); -// $this->assertArrayHasKey('read', $document2->getPermissions()); -// $this->assertArrayHasKey('write', $document2->getPermissions()); -// $this->assertEquals('test5x@appwrite.io', $document2->getAttribute('email')); -// $this->assertIsString($document2->getAttribute('email')); -// $this->assertEquals(1, $document2->getAttribute('status')); -// $this->assertIsInt($document2->getAttribute('status')); -// $this->assertEquals(true, $document2->getAttribute('emailVerification')); -// $this->assertIsBool($document2->getAttribute('emailVerification')); - -// $types = [ -// Database::VAR_TEXT, -// Database::VAR_INTEGER, -// Database::VAR_FLOAT, -// Database::VAR_NUMERIC, -// Database::VAR_BOOLEAN, -// Database::VAR_DOCUMENT, -// Database::VAR_EMAIL, -// Database::VAR_URL, -// Database::VAR_IPV4, -// Database::VAR_IPV6, -// Database::VAR_KEY, -// ]; - -// $rules = []; - -// foreach($types as $type) { -// $rules[] = [ -// '$collection' => Database::COLLECTION_RULES, -// '$permissions' => ['read' => ['*']], -// 'label' => ucfirst($type), -// 'key' => $type, -// 'type' => $type, -// 'default' => null, -// 'required' => true, -// 'array' => false, -// 'list' => ($type === Database::VAR_DOCUMENT) ? [$collection1->getId()] : [], -// ]; - -// $rules[] = [ -// '$collection' => Database::COLLECTION_RULES, -// '$permissions' => ['read' => ['*']], -// 'label' => ucfirst($type), -// 'key' => $type.'s', -// 'type' => $type, -// 'default' => null, -// 'required' => true, -// 'array' => true, -// 'list' => ($type === Database::VAR_DOCUMENT) ? [$collection1->getId()] : [], -// ]; -// } - -// $rules[] = [ -// '$collection' => Database::COLLECTION_RULES, -// '$permissions' => ['read' => ['*']], -// 'label' => 'document2', -// 'key' => 'document2', -// 'type' => Database::VAR_DOCUMENT, -// 'default' => null, -// 'required' => true, -// 'array' => false, -// 'list' => [$collection1->getId()], -// ]; - -// $rules[] = [ -// '$collection' => Database::COLLECTION_RULES, -// '$permissions' => ['read' => ['*']], -// 'label' => 'documents2', -// 'key' => 'documents2', -// 'type' => Database::VAR_DOCUMENT, -// 'default' => null, -// 'required' => true, -// 'array' => true, -// 'list' => [$collection1->getId()], -// ]; - -// $collection2 = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ -// '$collection' => Database::COLLECTION_COLLECTIONS, -// '$permissions' => ['read' => ['*']], -// 'name' => 'Create Documents', -// 'rules' => $rules, -// ]); - -// $this->assertEquals(true, self::$object->createCollection($collection2->getId(), [], [])); - -// $document3 = self::$object->createDocument($collection2->getId(), [ -// '$collection' => $collection2->getId(), -// '$permissions' => [ -// 'read' => ['*'], -// 'write' => ['user:123'], -// ], -// 'text' => 'Hello World', -// 'texts' => ['Hello World 1', 'Hello World 2'], -// // 'document' => $document0, -// // 'documents' => [$document0], -// 'document' => $document0, -// 'documents' => [$document1, $document0], -// 'document2' => $document1, -// 'documents2' => [$document0, $document1], -// 'integer' => 1, -// 'integers' => [5, 3, 4], -// 'float' => 2.22, -// 'floats' => [1.13, 4.33, 8.9999], -// 'numeric' => 1, -// 'numerics' => [1, 5, 7.77], -// 'boolean' => true, -// 'booleans' => [true, false, true], -// 'email' => 'test@appwrite.io', -// 'emails' => [ -// 'test4@appwrite.io', -// 'test3@appwrite.io', -// 'test2@appwrite.io', -// 'test1@appwrite.io' -// ], -// 'url' => 'http://example.com/welcome', -// 'urls' => [ -// 'http://example.com/welcome-1', -// 'http://example.com/welcome-2', -// 'http://example.com/welcome-3' -// ], -// 'ipv4' => '172.16.254.1', -// 'ipv4s' => [ -// '172.16.254.1', -// '172.16.254.5' -// ], -// 'ipv6' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334', -// 'ipv6s' => [ -// '2001:0db8:85a3:0000:0000:8a2e:0370:7334', -// '2001:0db8:85a3:0000:0000:8a2e:0370:7337' -// ], -// 'key' => uniqid(), -// 'keys' => [uniqid(), uniqid(), uniqid()], -// ]); - -// $document3 = self::$object->getDocument($collection2->getId(), $document3->getId()); - -// $this->assertIsString($document3->getId()); -// $this->assertIsString($document3->getCollection()); -// $this->assertEquals([ -// 'read' => ['*'], -// 'write' => ['user:123'], -// ], $document3->getPermissions()); -// $this->assertEquals('Hello World', $document3->getAttribute('text')); -// $this->assertCount(2, $document3->getAttribute('texts')); - -// $this->assertIsString($document3->getAttribute('text')); -// $this->assertEquals('Hello World', $document3->getAttribute('text')); -// $this->assertEquals(['Hello World 1', 'Hello World 2'], $document3->getAttribute('texts')); -// $this->assertCount(2, $document3->getAttribute('texts')); - -// $this->assertInstanceOf(Document::class, $document3->getAttribute('document')); -// $this->assertIsString($document3->getAttribute('document')->getId()); -// $this->assertNotEmpty($document3->getAttribute('document')->getId()); -// $this->assertIsArray($document3->getAttribute('document')->getPermissions()); -// $this->assertArrayHasKey('read', $document3->getAttribute('document')->getPermissions()); -// $this->assertArrayHasKey('write', $document3->getAttribute('document')->getPermissions()); -// $this->assertEquals('Task #0', $document3->getAttribute('document')->getAttribute('name')); -// $this->assertCount(4, $document3->getAttribute('document')->getAttribute('links')); -// $this->assertEquals('http://example.com/link-1', $document3->getAttribute('document')->getAttribute('links')[0]); -// $this->assertEquals('http://example.com/link-2', $document3->getAttribute('document')->getAttribute('links')[1]); -// $this->assertEquals('http://example.com/link-3', $document3->getAttribute('document')->getAttribute('links')[2]); -// $this->assertEquals('http://example.com/link-4', $document3->getAttribute('document')->getAttribute('links')[3]); - -// $this->assertInstanceOf(Document::class, $document3->getAttribute('documents')[0]); -// $this->assertIsString($document3->getAttribute('documents')[0]->getId()); -// $this->assertNotEmpty($document3->getAttribute('documents')[0]->getId()); -// $this->assertIsArray($document3->getAttribute('documents')[0]->getPermissions()); -// $this->assertArrayHasKey('read', $document3->getAttribute('documents')[0]->getPermissions()); -// $this->assertArrayHasKey('write', $document3->getAttribute('documents')[0]->getPermissions()); -// $this->assertEquals('Task #1x', $document3->getAttribute('documents')[0]->getAttribute('name')); -// $this->assertCount(4, $document3->getAttribute('documents')[0]->getAttribute('links')); -// $this->assertEquals('http://example.com/link-5x', $document3->getAttribute('documents')[0]->getAttribute('links')[0]); -// $this->assertEquals('http://example.com/link-6x', $document3->getAttribute('documents')[0]->getAttribute('links')[1]); -// $this->assertEquals('http://example.com/link-7x', $document3->getAttribute('documents')[0]->getAttribute('links')[2]); -// $this->assertEquals('http://example.com/link-8x', $document3->getAttribute('documents')[0]->getAttribute('links')[3]); - -// $this->assertInstanceOf(Document::class, $document3->getAttribute('documents')[1]); -// $this->assertIsString($document3->getAttribute('documents')[1]->getId()); -// $this->assertNotEmpty($document3->getAttribute('documents')[1]->getId()); -// $this->assertIsArray($document3->getAttribute('documents')[1]->getPermissions()); -// $this->assertArrayHasKey('read', $document3->getAttribute('documents')[1]->getPermissions()); -// $this->assertArrayHasKey('write', $document3->getAttribute('documents')[1]->getPermissions()); -// $this->assertEquals('Task #0', $document3->getAttribute('documents')[1]->getAttribute('name')); -// $this->assertCount(4, $document3->getAttribute('documents')[1]->getAttribute('links')); -// $this->assertEquals('http://example.com/link-1', $document3->getAttribute('documents')[1]->getAttribute('links')[0]); -// $this->assertEquals('http://example.com/link-2', $document3->getAttribute('documents')[1]->getAttribute('links')[1]); -// $this->assertEquals('http://example.com/link-3', $document3->getAttribute('documents')[1]->getAttribute('links')[2]); -// $this->assertEquals('http://example.com/link-4', $document3->getAttribute('documents')[1]->getAttribute('links')[3]); - -// $this->assertInstanceOf(Document::class, $document3->getAttribute('document2')); -// $this->assertIsString($document3->getAttribute('document2')->getId()); -// $this->assertNotEmpty($document3->getAttribute('document2')->getId()); -// $this->assertIsArray($document3->getAttribute('document2')->getPermissions()); -// $this->assertArrayHasKey('read', $document3->getAttribute('document2')->getPermissions()); -// $this->assertArrayHasKey('write', $document3->getAttribute('document2')->getPermissions()); -// $this->assertEquals('Task #1x', $document3->getAttribute('document2')->getAttribute('name')); -// $this->assertCount(4, $document3->getAttribute('document2')->getAttribute('links')); -// $this->assertEquals('http://example.com/link-5x', $document3->getAttribute('document2')->getAttribute('links')[0]); -// $this->assertEquals('http://example.com/link-6x', $document3->getAttribute('document2')->getAttribute('links')[1]); -// $this->assertEquals('http://example.com/link-7x', $document3->getAttribute('document2')->getAttribute('links')[2]); -// $this->assertEquals('http://example.com/link-8x', $document3->getAttribute('document2')->getAttribute('links')[3]); - -// $this->assertInstanceOf(Document::class, $document3->getAttribute('documents2')[0]); -// $this->assertIsString($document3->getAttribute('documents2')[0]->getId()); -// $this->assertNotEmpty($document3->getAttribute('documents2')[0]->getId()); -// $this->assertIsArray($document3->getAttribute('documents2')[0]->getPermissions()); -// $this->assertArrayHasKey('read', $document3->getAttribute('documents2')[0]->getPermissions()); -// $this->assertArrayHasKey('write', $document3->getAttribute('documents2')[0]->getPermissions()); -// $this->assertEquals('Task #0', $document3->getAttribute('documents2')[0]->getAttribute('name')); -// $this->assertCount(4, $document3->getAttribute('documents2')[0]->getAttribute('links')); -// $this->assertEquals('http://example.com/link-1', $document3->getAttribute('documents2')[0]->getAttribute('links')[0]); -// $this->assertEquals('http://example.com/link-2', $document3->getAttribute('documents2')[0]->getAttribute('links')[1]); -// $this->assertEquals('http://example.com/link-3', $document3->getAttribute('documents2')[0]->getAttribute('links')[2]); -// $this->assertEquals('http://example.com/link-4', $document3->getAttribute('documents2')[0]->getAttribute('links')[3]); - -// $this->assertInstanceOf(Document::class, $document3->getAttribute('documents2')[1]); -// $this->assertIsString($document3->getAttribute('documents2')[1]->getId()); -// $this->assertNotEmpty($document3->getAttribute('documents2')[1]->getId()); -// $this->assertIsArray($document3->getAttribute('documents2')[1]->getPermissions()); -// $this->assertArrayHasKey('read', $document3->getAttribute('documents2')[1]->getPermissions()); -// $this->assertArrayHasKey('write', $document3->getAttribute('documents2')[1]->getPermissions()); -// $this->assertEquals('Task #1x', $document3->getAttribute('documents2')[1]->getAttribute('name')); -// $this->assertCount(4, $document3->getAttribute('documents2')[1]->getAttribute('links')); -// $this->assertEquals('http://example.com/link-5x', $document3->getAttribute('documents2')[1]->getAttribute('links')[0]); -// $this->assertEquals('http://example.com/link-6x', $document3->getAttribute('documents2')[1]->getAttribute('links')[1]); -// $this->assertEquals('http://example.com/link-7x', $document3->getAttribute('documents2')[1]->getAttribute('links')[2]); -// $this->assertEquals('http://example.com/link-8x', $document3->getAttribute('documents2')[1]->getAttribute('links')[3]); - -// $this->assertIsInt($document3->getAttribute('integer')); -// $this->assertEquals(1, $document3->getAttribute('integer')); -// $this->assertIsInt($document3->getAttribute('integers')[0]); -// $this->assertIsInt($document3->getAttribute('integers')[1]); -// $this->assertIsInt($document3->getAttribute('integers')[2]); -// $this->assertEquals([5, 3, 4], $document3->getAttribute('integers')); -// $this->assertCount(3, $document3->getAttribute('integers')); - -// $this->assertIsFloat($document3->getAttribute('float')); -// $this->assertEquals(2.22, $document3->getAttribute('float')); -// $this->assertIsFloat($document3->getAttribute('floats')[0]); -// $this->assertIsFloat($document3->getAttribute('floats')[1]); -// $this->assertIsFloat($document3->getAttribute('floats')[2]); -// $this->assertEquals([1.13, 4.33, 8.9999], $document3->getAttribute('floats')); -// $this->assertCount(3, $document3->getAttribute('floats')); - -// $this->assertIsBool($document3->getAttribute('boolean')); -// $this->assertEquals(true, $document3->getAttribute('boolean')); -// $this->assertIsBool($document3->getAttribute('booleans')[0]); -// $this->assertIsBool($document3->getAttribute('booleans')[1]); -// $this->assertIsBool($document3->getAttribute('booleans')[2]); -// $this->assertEquals([true, false, true], $document3->getAttribute('booleans')); -// $this->assertCount(3, $document3->getAttribute('booleans')); - -// $this->assertIsString($document3->getAttribute('email')); -// $this->assertEquals('test@appwrite.io', $document3->getAttribute('email')); -// $this->assertIsString($document3->getAttribute('emails')[0]); -// $this->assertIsString($document3->getAttribute('emails')[1]); -// $this->assertIsString($document3->getAttribute('emails')[2]); -// $this->assertIsString($document3->getAttribute('emails')[3]); -// $this->assertEquals([ -// 'test4@appwrite.io', -// 'test3@appwrite.io', -// 'test2@appwrite.io', -// 'test1@appwrite.io' -// ], $document3->getAttribute('emails')); -// $this->assertCount(4, $document3->getAttribute('emails')); - -// $this->assertIsString($document3->getAttribute('url')); -// $this->assertEquals('http://example.com/welcome', $document3->getAttribute('url')); -// $this->assertIsString($document3->getAttribute('urls')[0]); -// $this->assertIsString($document3->getAttribute('urls')[1]); -// $this->assertIsString($document3->getAttribute('urls')[2]); -// $this->assertEquals([ -// 'http://example.com/welcome-1', -// 'http://example.com/welcome-2', -// 'http://example.com/welcome-3' -// ], $document3->getAttribute('urls')); -// $this->assertCount(3, $document3->getAttribute('urls')); - -// $this->assertIsString($document3->getAttribute('ipv4')); -// $this->assertEquals('172.16.254.1', $document3->getAttribute('ipv4')); -// $this->assertIsString($document3->getAttribute('ipv4s')[0]); -// $this->assertIsString($document3->getAttribute('ipv4s')[1]); -// $this->assertEquals([ -// '172.16.254.1', -// '172.16.254.5' -// ], $document3->getAttribute('ipv4s')); -// $this->assertCount(2, $document3->getAttribute('ipv4s')); - -// $this->assertIsString($document3->getAttribute('ipv6')); -// $this->assertEquals('2001:0db8:85a3:0000:0000:8a2e:0370:7334', $document3->getAttribute('ipv6')); -// $this->assertIsString($document3->getAttribute('ipv6s')[0]); -// $this->assertIsString($document3->getAttribute('ipv6s')[1]); -// $this->assertEquals([ -// '2001:0db8:85a3:0000:0000:8a2e:0370:7334', -// '2001:0db8:85a3:0000:0000:8a2e:0370:7337' -// ], $document3->getAttribute('ipv6s')); -// $this->assertCount(2, $document3->getAttribute('ipv6s')); - -// $this->assertEquals('2001:0db8:85a3:0000:0000:8a2e:0370:7334', $document3->getAttribute('ipv6')); -// $this->assertEquals([ -// '2001:0db8:85a3:0000:0000:8a2e:0370:7334', -// '2001:0db8:85a3:0000:0000:8a2e:0370:7337' -// ], $document3->getAttribute('ipv6s')); -// $this->assertCount(2, $document3->getAttribute('ipv6s')); - -// $this->assertIsString($document3->getAttribute('key')); -// $this->assertCount(3, $document3->getAttribute('keys')); - -// // Update - -// $document3 = self::$object->updateDocument($collection2->getId(), $document3->getId(), [ -// '$id' => $document3->getId(), -// '$collection' => $collection2->getId(), -// '$permissions' => [ -// 'read' => ['user:1234'], -// 'write' => ['user:1234'], -// ], -// 'text' => 'Hello Worldx', -// 'texts' => ['Hello World 1x', 'Hello World 2x'], -// 'document' => $document0, -// 'documents' => [$document1, $document0], -// 'document2' => $document1, -// 'documents2' => [$document0, $document1], -// 'integer' => 2, -// 'integers' => [6, 4, 5], -// 'float' => 3.22, -// 'floats' => [2.13, 5.33, 9.9999], -// 'numeric' => 2, -// 'numerics' => [2, 6, 8.77], -// 'boolean' => false, -// 'booleans' => [false, true, false], -// 'email' => 'testx@appwrite.io', -// 'emails' => [ -// 'test4x@appwrite.io', -// 'test3x@appwrite.io', -// 'test2x@appwrite.io', -// 'test1x@appwrite.io' -// ], -// 'url' => 'http://example.com/welcomex', -// 'urls' => [ -// 'http://example.com/welcome-1x', -// 'http://example.com/welcome-2x', -// 'http://example.com/welcome-3x' -// ], -// 'ipv4' => '172.16.254.2', -// 'ipv4s' => [ -// '172.16.254.2', -// '172.16.254.6' -// ], -// 'ipv6' => '2001:0db8:85a3:0000:0000:8a2e:0370:7335', -// 'ipv6s' => [ -// '2001:0db8:85a3:0000:0000:8a2e:0370:7335', -// '2001:0db8:85a3:0000:0000:8a2e:0370:7338' -// ], -// 'key' => uniqid().'x', -// 'keys' => [uniqid().'x', uniqid().'x', uniqid().'x'], -// ]); - -// $document3 = self::$object->getDocument($collection2->getId(), $document3->getId()); - -// $this->assertIsString($document3->getId()); -// $this->assertIsString($document3->getCollection()); -// $this->assertEquals([ -// 'read' => ['user:1234'], -// 'write' => ['user:1234'], -// ], $document3->getPermissions()); -// $this->assertEquals('Hello Worldx', $document3->getAttribute('text')); -// $this->assertCount(2, $document3->getAttribute('texts')); - -// $this->assertIsString($document3->getAttribute('text')); -// $this->assertEquals('Hello Worldx', $document3->getAttribute('text')); -// $this->assertEquals(['Hello World 1x', 'Hello World 2x'], $document3->getAttribute('texts')); -// $this->assertCount(2, $document3->getAttribute('texts')); - -// $this->assertInstanceOf(Document::class, $document3->getAttribute('document')); -// $this->assertIsString($document3->getAttribute('document')->getId()); -// $this->assertNotEmpty($document3->getAttribute('document')->getId()); -// $this->assertIsArray($document3->getAttribute('document')->getPermissions()); -// $this->assertArrayHasKey('read', $document3->getAttribute('document')->getPermissions()); -// $this->assertArrayHasKey('write', $document3->getAttribute('document')->getPermissions()); -// $this->assertEquals('Task #0', $document3->getAttribute('document')->getAttribute('name')); -// $this->assertCount(4, $document3->getAttribute('document')->getAttribute('links')); -// $this->assertEquals('http://example.com/link-1', $document3->getAttribute('document')->getAttribute('links')[0]); -// $this->assertEquals('http://example.com/link-2', $document3->getAttribute('document')->getAttribute('links')[1]); -// $this->assertEquals('http://example.com/link-3', $document3->getAttribute('document')->getAttribute('links')[2]); -// $this->assertEquals('http://example.com/link-4', $document3->getAttribute('document')->getAttribute('links')[3]); - -// $this->assertInstanceOf(Document::class, $document3->getAttribute('documents')[0]); -// $this->assertIsString($document3->getAttribute('documents')[0]->getId()); -// $this->assertNotEmpty($document3->getAttribute('documents')[0]->getId()); -// $this->assertIsArray($document3->getAttribute('documents')[0]->getPermissions()); -// $this->assertArrayHasKey('read', $document3->getAttribute('documents')[0]->getPermissions()); -// $this->assertArrayHasKey('write', $document3->getAttribute('documents')[0]->getPermissions()); -// $this->assertEquals('Task #1x', $document3->getAttribute('documents')[0]->getAttribute('name')); -// $this->assertCount(4, $document3->getAttribute('documents')[0]->getAttribute('links')); -// $this->assertEquals('http://example.com/link-5x', $document3->getAttribute('documents')[0]->getAttribute('links')[0]); -// $this->assertEquals('http://example.com/link-6x', $document3->getAttribute('documents')[0]->getAttribute('links')[1]); -// $this->assertEquals('http://example.com/link-7x', $document3->getAttribute('documents')[0]->getAttribute('links')[2]); -// $this->assertEquals('http://example.com/link-8x', $document3->getAttribute('documents')[0]->getAttribute('links')[3]); - -// $this->assertInstanceOf(Document::class, $document3->getAttribute('documents')[1]); -// $this->assertIsString($document3->getAttribute('documents')[1]->getId()); -// $this->assertNotEmpty($document3->getAttribute('documents')[1]->getId()); -// $this->assertIsArray($document3->getAttribute('documents')[1]->getPermissions()); -// $this->assertArrayHasKey('read', $document3->getAttribute('documents')[1]->getPermissions()); -// $this->assertArrayHasKey('write', $document3->getAttribute('documents')[1]->getPermissions()); -// $this->assertEquals('Task #0', $document3->getAttribute('documents')[1]->getAttribute('name')); -// $this->assertCount(4, $document3->getAttribute('documents')[1]->getAttribute('links')); -// $this->assertEquals('http://example.com/link-1', $document3->getAttribute('documents')[1]->getAttribute('links')[0]); -// $this->assertEquals('http://example.com/link-2', $document3->getAttribute('documents')[1]->getAttribute('links')[1]); -// $this->assertEquals('http://example.com/link-3', $document3->getAttribute('documents')[1]->getAttribute('links')[2]); -// $this->assertEquals('http://example.com/link-4', $document3->getAttribute('documents')[1]->getAttribute('links')[3]); - -// $this->assertInstanceOf(Document::class, $document3->getAttribute('document2')); -// $this->assertIsString($document3->getAttribute('document2')->getId()); -// $this->assertNotEmpty($document3->getAttribute('document2')->getId()); -// $this->assertIsArray($document3->getAttribute('document2')->getPermissions()); -// $this->assertArrayHasKey('read', $document3->getAttribute('document2')->getPermissions()); -// $this->assertArrayHasKey('write', $document3->getAttribute('document2')->getPermissions()); -// $this->assertEquals('Task #1x', $document3->getAttribute('document2')->getAttribute('name')); -// $this->assertCount(4, $document3->getAttribute('document2')->getAttribute('links')); -// $this->assertEquals('http://example.com/link-5x', $document3->getAttribute('document2')->getAttribute('links')[0]); -// $this->assertEquals('http://example.com/link-6x', $document3->getAttribute('document2')->getAttribute('links')[1]); -// $this->assertEquals('http://example.com/link-7x', $document3->getAttribute('document2')->getAttribute('links')[2]); -// $this->assertEquals('http://example.com/link-8x', $document3->getAttribute('document2')->getAttribute('links')[3]); - -// $this->assertInstanceOf(Document::class, $document3->getAttribute('documents2')[0]); -// $this->assertIsString($document3->getAttribute('documents2')[0]->getId()); -// $this->assertNotEmpty($document3->getAttribute('documents2')[0]->getId()); -// $this->assertIsArray($document3->getAttribute('documents2')[0]->getPermissions()); -// $this->assertArrayHasKey('read', $document3->getAttribute('documents2')[0]->getPermissions()); -// $this->assertArrayHasKey('write', $document3->getAttribute('documents2')[0]->getPermissions()); -// $this->assertEquals('Task #0', $document3->getAttribute('documents2')[0]->getAttribute('name')); -// $this->assertCount(4, $document3->getAttribute('documents2')[0]->getAttribute('links')); -// $this->assertEquals('http://example.com/link-1', $document3->getAttribute('documents2')[0]->getAttribute('links')[0]); -// $this->assertEquals('http://example.com/link-2', $document3->getAttribute('documents2')[0]->getAttribute('links')[1]); -// $this->assertEquals('http://example.com/link-3', $document3->getAttribute('documents2')[0]->getAttribute('links')[2]); -// $this->assertEquals('http://example.com/link-4', $document3->getAttribute('documents2')[0]->getAttribute('links')[3]); - -// $this->assertInstanceOf(Document::class, $document3->getAttribute('documents2')[1]); -// $this->assertIsString($document3->getAttribute('documents2')[1]->getId()); -// $this->assertNotEmpty($document3->getAttribute('documents2')[1]->getId()); -// $this->assertIsArray($document3->getAttribute('documents2')[1]->getPermissions()); -// $this->assertArrayHasKey('read', $document3->getAttribute('documents2')[1]->getPermissions()); -// $this->assertArrayHasKey('write', $document3->getAttribute('documents2')[1]->getPermissions()); -// $this->assertEquals('Task #1x', $document3->getAttribute('documents2')[1]->getAttribute('name')); -// $this->assertCount(4, $document3->getAttribute('documents2')[1]->getAttribute('links')); -// $this->assertEquals('http://example.com/link-5x', $document3->getAttribute('documents2')[1]->getAttribute('links')[0]); -// $this->assertEquals('http://example.com/link-6x', $document3->getAttribute('documents2')[1]->getAttribute('links')[1]); -// $this->assertEquals('http://example.com/link-7x', $document3->getAttribute('documents2')[1]->getAttribute('links')[2]); -// $this->assertEquals('http://example.com/link-8x', $document3->getAttribute('documents2')[1]->getAttribute('links')[3]); - -// $this->assertIsInt($document3->getAttribute('integer')); -// $this->assertEquals(2, $document3->getAttribute('integer')); -// $this->assertIsInt($document3->getAttribute('integers')[0]); -// $this->assertIsInt($document3->getAttribute('integers')[1]); -// $this->assertIsInt($document3->getAttribute('integers')[2]); -// $this->assertEquals([6, 4, 5], $document3->getAttribute('integers')); -// $this->assertCount(3, $document3->getAttribute('integers')); - -// $this->assertIsFloat($document3->getAttribute('float')); -// $this->assertEquals(3.22, $document3->getAttribute('float')); -// $this->assertIsFloat($document3->getAttribute('floats')[0]); -// $this->assertIsFloat($document3->getAttribute('floats')[1]); -// $this->assertIsFloat($document3->getAttribute('floats')[2]); -// $this->assertEquals([2.13, 5.33, 9.9999], $document3->getAttribute('floats')); -// $this->assertCount(3, $document3->getAttribute('floats')); - -// $this->assertIsBool($document3->getAttribute('boolean')); -// $this->assertEquals(false, $document3->getAttribute('boolean')); -// $this->assertIsBool($document3->getAttribute('booleans')[0]); -// $this->assertIsBool($document3->getAttribute('booleans')[1]); -// $this->assertIsBool($document3->getAttribute('booleans')[2]); -// $this->assertEquals([false, true, false], $document3->getAttribute('booleans')); -// $this->assertCount(3, $document3->getAttribute('booleans')); - -// $this->assertIsString($document3->getAttribute('email')); -// $this->assertEquals('testx@appwrite.io', $document3->getAttribute('email')); -// $this->assertIsString($document3->getAttribute('emails')[0]); -// $this->assertIsString($document3->getAttribute('emails')[1]); -// $this->assertIsString($document3->getAttribute('emails')[2]); -// $this->assertIsString($document3->getAttribute('emails')[3]); -// $this->assertEquals([ -// 'test4x@appwrite.io', -// 'test3x@appwrite.io', -// 'test2x@appwrite.io', -// 'test1x@appwrite.io' -// ], $document3->getAttribute('emails')); -// $this->assertCount(4, $document3->getAttribute('emails')); - -// $this->assertIsString($document3->getAttribute('url')); -// $this->assertEquals('http://example.com/welcomex', $document3->getAttribute('url')); -// $this->assertIsString($document3->getAttribute('urls')[0]); -// $this->assertIsString($document3->getAttribute('urls')[1]); -// $this->assertIsString($document3->getAttribute('urls')[2]); -// $this->assertEquals([ -// 'http://example.com/welcome-1x', -// 'http://example.com/welcome-2x', -// 'http://example.com/welcome-3x' -// ], $document3->getAttribute('urls')); -// $this->assertCount(3, $document3->getAttribute('urls')); - -// $this->assertIsString($document3->getAttribute('ipv4')); -// $this->assertEquals('172.16.254.2', $document3->getAttribute('ipv4')); -// $this->assertIsString($document3->getAttribute('ipv4s')[0]); -// $this->assertIsString($document3->getAttribute('ipv4s')[1]); -// $this->assertEquals([ -// '172.16.254.2', -// '172.16.254.6' -// ], $document3->getAttribute('ipv4s')); -// $this->assertCount(2, $document3->getAttribute('ipv4s')); - -// $this->assertIsString($document3->getAttribute('ipv6')); -// $this->assertEquals('2001:0db8:85a3:0000:0000:8a2e:0370:7335', $document3->getAttribute('ipv6')); -// $this->assertIsString($document3->getAttribute('ipv6s')[0]); -// $this->assertIsString($document3->getAttribute('ipv6s')[1]); -// $this->assertEquals([ -// '2001:0db8:85a3:0000:0000:8a2e:0370:7335', -// '2001:0db8:85a3:0000:0000:8a2e:0370:7338' -// ], $document3->getAttribute('ipv6s')); -// $this->assertCount(2, $document3->getAttribute('ipv6s')); - -// $this->assertEquals('2001:0db8:85a3:0000:0000:8a2e:0370:7335', $document3->getAttribute('ipv6')); -// $this->assertEquals([ -// '2001:0db8:85a3:0000:0000:8a2e:0370:7335', -// '2001:0db8:85a3:0000:0000:8a2e:0370:7338' -// ], $document3->getAttribute('ipv6s')); -// $this->assertCount(2, $document3->getAttribute('ipv6s')); - -// $this->assertIsString($document3->getAttribute('key')); -// $this->assertCount(3, $document3->getAttribute('keys')); -// } - -// public function testDeleteDocument() -// { -// $collection1 = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, [ -// '$collection' => Database::COLLECTION_COLLECTIONS, -// '$permissions' => ['read' => ['*']], -// 'name' => 'Create Documents', -// 'rules' => [ -// [ -// '$collection' => Database::COLLECTION_RULES, -// '$permissions' => ['read' => ['*']], -// 'label' => 'Name', -// 'key' => 'name', -// 'type' => Database::VAR_TEXT, -// 'default' => '', -// 'required' => true, -// 'array' => false, -// ], -// [ -// '$collection' => Database::COLLECTION_RULES, -// '$permissions' => ['read' => ['*']], -// 'label' => 'Links', -// 'key' => 'links', -// 'type' => Database::VAR_URL, -// 'default' => '', -// 'required' => true, -// 'array' => true, -// ], -// ] -// ]); - -// $this->assertEquals(true, self::$object->createCollection($collection1->getId(), [], [])); - -// $document1 = self::$object->createDocument($collection1->getId(), [ -// '$collection' => $collection1->getId(), -// '$permissions' => [ -// 'read' => ['*'], -// 'write' => ['user:123'], -// ], -// 'name' => 'Task #1️⃣', -// 'links' => [ -// 'http://example.com/link-5', -// 'http://example.com/link-6', -// 'http://example.com/link-7', -// 'http://example.com/link-8', -// ], -// ]); - -// $this->assertNotEmpty($document1->getId()); -// $this->assertIsArray($document1->getPermissions()); -// $this->assertArrayHasKey('read', $document1->getPermissions()); -// $this->assertArrayHasKey('write', $document1->getPermissions()); -// $this->assertEquals('Task #1️⃣', $document1->getAttribute('name')); -// $this->assertCount(4, $document1->getAttribute('links')); -// $this->assertEquals('http://example.com/link-5', $document1->getAttribute('links')[0]); -// $this->assertEquals('http://example.com/link-6', $document1->getAttribute('links')[1]); -// $this->assertEquals('http://example.com/link-7', $document1->getAttribute('links')[2]); -// $this->assertEquals('http://example.com/link-8', $document1->getAttribute('links')[3]); - -// $document1 = self::$object->getDocument($collection1->getId(), $document1->getId()); - -// $this->assertNotEmpty($document1->getId()); -// $this->assertIsArray($document1->getPermissions()); -// $this->assertArrayHasKey('read', $document1->getPermissions()); -// $this->assertArrayHasKey('write', $document1->getPermissions()); -// $this->assertEquals('Task #1️⃣', $document1->getAttribute('name')); -// $this->assertCount(4, $document1->getAttribute('links')); -// $this->assertEquals('http://example.com/link-5', $document1->getAttribute('links')[0]); -// $this->assertEquals('http://example.com/link-6', $document1->getAttribute('links')[1]); -// $this->assertEquals('http://example.com/link-7', $document1->getAttribute('links')[2]); -// $this->assertEquals('http://example.com/link-8', $document1->getAttribute('links')[3]); - -// self::$object->deleteDocument($collection1->getId(), $document1->getId()); - -// $document1 = self::$object->getDocument($collection1->getId(), $document1->getId()); - -// $this->assertTrue($document1->isEmpty()); -// $this->assertEmpty($document1->getId()); -// $this->assertEmpty($document1->getCollection()); -// $this->assertIsArray($document1->getPermissions()); -// $this->assertEmpty($document1->getPermissions()); -// } - -// public function testFind() -// { -// $data = include __DIR__.'/../../resources/database/movies.php'; - -// $collections = $data['collections']; -// $movies = $data['movies']; - -// foreach ($collections as $key => &$collection) { -// $collection = self::$object->createDocument(Database::COLLECTION_COLLECTIONS, $collection); -// self::$object->createCollection($collection->getId(), [], []); -// } - -// foreach ($movies as $key => &$movie) { -// $movie['$collection'] = $collection->getId(); -// $movie['$permissions'] = []; -// $movie = self::$object->createDocument($collection->getId(), $movie); -// } - -// self::$object->find($collection->getId(), [ -// 'limit' => 5, -// 'filters' => [ -// 'name=Hello World', -// 'releaseYear=1999', -// 'langauges=English', -// ], -// ]); -// $this->assertEquals('1', '1'); -// } - -// public function testFindFirst() -// { -// $this->assertEquals('1', '1'); -// } - -// public function testFindLast() -// { -// $this->assertEquals('1', '1'); -// } - -// public function countTest() -// { -// $this->assertEquals('1', '1'); -// } - -// public function addFilterTest() -// { -// $this->assertEquals('1', '1'); -// } - -// public function encodeTest() -// { -// $this->assertEquals('1', '1'); -// } - -// public function decodeTest() -// { -// $this->assertEquals('1', '1'); -// } -// } \ No newline at end of file diff --git a/tests/Database/Validator/AuthorizationTest.php b/tests/Database/Validator/AuthorizationTest.php index 106bee6bb..1b0cfc6f2 100644 --- a/tests/Database/Validator/AuthorizationTest.php +++ b/tests/Database/Validator/AuthorizationTest.php @@ -1,6 +1,6 @@ Date: Tue, 26 Jan 2021 01:11:54 +0200 Subject: [PATCH 015/190] Added MongoDB adapter --- Dockerfile | 4 +- composer.json | 3 +- src/Database/Adapter/MariaDB.php | 6 +- src/Database/Adapter/MongoDB.php | 81 ++++++++++++++++++++++++++ src/Database/Adapter/Postgres.php | 10 +--- tests/Database/Adapter/MongoDBTest.php | 41 +++++++++++++ 6 files changed, 133 insertions(+), 12 deletions(-) create mode 100644 src/Database/Adapter/MongoDB.php create mode 100644 tests/Database/Adapter/MongoDBTest.php diff --git a/Dockerfile b/Dockerfile index 647947a55..f3f281f43 100755 --- a/Dockerfile +++ b/Dockerfile @@ -19,7 +19,9 @@ RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone RUN \ apk update \ - && apk add --no-cache postgresql-libs postgresql-dev \ + && apk add --no-cache postgresql-libs postgresql-dev make automake autoconf gcc g++ \ + && pecl install mongodb \ + && docker-php-ext-enable mongodb \ && docker-php-ext-install opcache pgsql pdo_mysql pdo_pgsql \ && rm -rf /var/cache/apk/* diff --git a/composer.json b/composer.json index 89f94c253..60d53994e 100755 --- a/composer.json +++ b/composer.json @@ -20,7 +20,8 @@ "require": { "php": ">=7.1", "ext-pdo": "*", - "utopia-php/framework": "0.*.*" + "utopia-php/framework": "0.*.*", + "mongodb/mongodb": "1.8.0" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 4c8569deb..96860de61 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -2,9 +2,9 @@ namespace Utopia\Database\Adapter; -use Utopia\Database\Adapter; -use Exception; use PDO; +use Exception; +use Utopia\Database\Adapter; class MariaDB extends Adapter { @@ -23,7 +23,7 @@ class MariaDB extends Adapter * * Set connection and settings * - * @param Registry $register + * @param PDO $pdo */ public function __construct(PDO $pdo) { diff --git a/src/Database/Adapter/MongoDB.php b/src/Database/Adapter/MongoDB.php new file mode 100644 index 000000000..6677e6f40 --- /dev/null +++ b/src/Database/Adapter/MongoDB.php @@ -0,0 +1,81 @@ +client = $client; + } + + /** + * Create Database + * + * @param string $name + * @return bool + */ + public function create(string $name): bool + { + return (!!$this->getDatabase()->createCollection($name)); + } + + /** + * Delete Database + * + * @param string $name + * @return bool + */ + public function delete(string $name): bool + { + return (!!$this->getDatabase()->dropCollection($name)); + } + + /** + * @return Database + * + * @throws Exception + */ + protected function getDatabase() + { + if($this->database) { + return $this->database; + } + + $namespace = $this->getNamespace(); + + return $this->client->$namespace; + } + + /** + * @return Client + * + * @throws Exception + */ + protected function getClient() + { + return $this->client; + } +} \ No newline at end of file diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php index 25953d399..f1c6ed4b9 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -2,13 +2,9 @@ namespace Utopia\Database\Adapter; -use Utopia\Database\Adapter; -use Utopia\Database\Database; -use Utopia\Database\Document; -use Utopia\Database\Exception\Duplicate; -use Utopia\Database\Validator\Authorization; -use Exception; use PDO; +use Exception; +use Utopia\Database\Adapter; class Postgres extends Adapter { @@ -27,7 +23,7 @@ class Postgres extends Adapter * * Set connection and settings * - * @param Registry $register + * @param PDO $pdo */ public function __construct(PDO $pdo) { diff --git a/tests/Database/Adapter/MongoDBTest.php b/tests/Database/Adapter/MongoDBTest.php new file mode 100644 index 000000000..6412e66d5 --- /dev/null +++ b/tests/Database/Adapter/MongoDBTest.php @@ -0,0 +1,41 @@ + 'root', + 'password' => 'example', + ], + ); + + $database = new Database(new MongoDB($client)); + $database->setNamespace('myapp_'.uniqid()); + + return self::$database = $database; + } +} \ No newline at end of file From f0b2433509a80e07842ffe3ac2b521fb5f57271b Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 28 Jan 2021 22:16:55 +0200 Subject: [PATCH 016/190] Create database from namespace --- src/Database/Adapter.php | 48 ++++++++++++------------- src/Database/Adapter/MariaDB.php | 50 ++++++++++++++++++++++---- src/Database/Adapter/MongoDB.php | 27 ++++++++++++-- src/Database/Adapter/Postgres.php | 51 ++++++++++++++++++++------ src/Database/Database.php | 24 ++++++------- tests/Database/Base.php | 60 +++++++++---------------------- 6 files changed, 159 insertions(+), 101 deletions(-) diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index 258123db3..323057788 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -64,7 +64,7 @@ public function setNamespace(string $namespace): bool throw new Exception('Missing namespace'); } - $this->namespace = $namespace; + $this->namespace = $this->filter($namespace); return true; } @@ -90,39 +90,34 @@ public function getNamespace(): string /** * Create Database. * - * @param string $name - * * @return bool */ - abstract public function create(string $name): bool; + abstract public function create(): bool; /** * Delete Database. * - * @param string $name - * * @return bool */ - abstract public function delete(string $name): bool; + abstract public function delete(): bool; - // /** - // * Create Collection - // * - // * @param Document $collection - // * @param string $id - // * - // * @return bool - // */ - // abstract public function createCollection(Document $collection, string $id): bool; + /** + * Create Collection + * + * @param string $name + * + * @return bool + */ + abstract public function createCollection(string $name): bool; - // /** - // * Delete Collection - // * - // * @param Document $collection - // * - // * @return bool - // */ - // abstract public function deleteCollection(Document $collection): bool; + /** + * Delete Collection + * + * @param string $name + * + * @return bool + */ + abstract public function deleteCollection(string $name): bool; // /** // * Create Attribute @@ -229,6 +224,11 @@ abstract public function delete(string $name): bool; // */ // abstract public function count(array $options); + public function filter($value) + { + return preg_replace("/[^A-Za-z0-9 _]/", '', $value); + } + /** * Get Unique ID. */ diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 96860de61..3349d7d15 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -29,30 +29,68 @@ public function __construct(PDO $pdo) { $this->pdo = $pdo; } - + /** * Create Database * - * @param string $name * @return bool */ - public function create(string $name): bool + public function create(): bool { + $name = $this->getNamespace(); + return $this->getPDO() - ->prepare('CREATE DATABASE `'.$this->getNamespace().'_'.$name.'` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;') + ->prepare("CREATE DATABASE {$name} /*!40100 DEFAULT CHARACTER SET utf8mb4 */;") ->execute(); } /** * Delete Database * + * @return bool + */ + public function delete(): bool + { + $name = $this->getNamespace(); + + return $this->getPDO() + ->prepare("DROP DATABASE {$name};") + ->execute(); + } + + /** + * Create Collection + * * @param string $name * @return bool */ - public function delete(string $name): bool + public function createCollection(string $name): bool { + $name = $this->filter($name).'_documents'; + + return $this->getPDO() + ->prepare("CREATE TABLE IF NOT EXISTS {$this->getNamespace()}.{$name} ( + `_id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `_uid` varchar(45) NOT NULL, + -- 'custom2' text() DEFAULT NULL, + PRIMARY KEY (`_id`), + UNIQUE KEY `_index1` (`_uid`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;") + ->execute(); + } + + /** + * Delete Collection + * + * @param string $name + * @return bool + */ + public function deleteCollection(string $name): bool + { + $name = $this->filter($name).'_documents'; + return $this->getPDO() - ->prepare('DROP DATABASE `'.$this->getNamespace().'_'.$name.'`;') + ->prepare("DROP TABLE {$this->getNamespace()}.{$name};") ->execute(); } diff --git a/src/Database/Adapter/MongoDB.php b/src/Database/Adapter/MongoDB.php index 6677e6f40..14b961781 100644 --- a/src/Database/Adapter/MongoDB.php +++ b/src/Database/Adapter/MongoDB.php @@ -34,21 +34,42 @@ public function __construct(Client $client) /** * Create Database * + * @return bool + */ + public function create(): bool + { + $namespace = $this->getNamespace(); + return (!!$this->client->$namespace); + } + + /** + * Delete Database + * + * @return bool + */ + public function delete(): bool + { + return (!!$this->getDatabase()->dropCollection($this->getNamespace())); + } + + /** + * Create Collection + * * @param string $name * @return bool */ - public function create(string $name): bool + public function createCollection(string $name): bool { return (!!$this->getDatabase()->createCollection($name)); } /** - * Delete Database + * Delete Collection * * @param string $name * @return bool */ - public function delete(string $name): bool + public function deleteCollection(string $name): bool { return (!!$this->getDatabase()->dropCollection($name)); } diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php index f1c6ed4b9..5fddb8671 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -13,11 +13,6 @@ class Postgres extends Adapter */ protected $pdo; - /** - * @var bool - */ - protected $transaction = false; - /** * Constructor. * @@ -30,29 +25,65 @@ public function __construct(PDO $pdo) $this->pdo = $pdo; } + /** * Create Database * - * @param string $name * @return bool */ - public function create(string $name): bool + public function create(): bool { + $name = $this->getNamespace(); + return $this->getPDO() - ->prepare('CREATE DATABASE '.$this->getNamespace().'_'.$name.' /*!40100 DEFAULT CHARACTER SET utf8mb4 */;') + ->prepare("CREATE SCHEMA {$name} /*!40100 DEFAULT CHARACTER SET utf8mb4 */;") ->execute(); } /** * Delete Database * + * @return bool + */ + public function delete(): bool + { + $name = $this->getNamespace(); + + return $this->getPDO() + ->prepare("DROP SCHEMA {$name};") + ->execute(); + } + + /** + * Create Collection + * * @param string $name * @return bool */ - public function delete(string $name): bool + public function createCollection(string $name): bool { + $name = $this->filter($name).'_documents'; + + return $this->getPDO() + ->prepare("CREATE TABLE {$this->getNamespace()}.{$name}( + _id INT PRIMARY KEY NOT NULL, + _uid CHAR(50) NOT NULL + );") + ->execute(); + } + + /** + * Delete Collection + * + * @param string $name + * @return bool + */ + public function deleteCollection(string $name): bool + { + $name = $this->filter($name).'_documents'; + return $this->getPDO() - ->prepare('DROP DATABASE '.$this->getNamespace().'_'.$name.';') + ->prepare("DROP TABLE {$this->getNamespace()}.{$name};") ->execute(); } diff --git a/src/Database/Database.php b/src/Database/Database.php index 02ec4f3ff..9ecb4a1a2 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -83,25 +83,21 @@ public function getNamespace(): string /** * Create Database. * - * @param string $name - * * @return bool */ - public function create(string $name): bool + public function create(): bool { - return $this->adapter->create($name); + return $this->adapter->create(); } /** * Delete Database. * - * @param string $name - * * @return bool */ - public function delete(string $name): bool + public function delete(): bool { - return $this->adapter->delete($name); + return $this->adapter->delete(); } /** @@ -175,25 +171,25 @@ public function count(array $options) /** * Create Collection * - * @param string $id + * @param string $name * * @return bool */ - public function createCollection(string $id): bool + public function createCollection(string $name): bool { - return $this->adapter->createCollection($this->getDocument(self::COLLECTION_COLLECTIONS, $id), $id); + return $this->adapter->createCollection($name); } /** * Delete Collection * - * @param string $id + * @param string $name * * @return bool */ - public function deleteCollection(string $id): bool + public function deleteCollection(string $name): bool { - return $this->adapter->deleteCollection($this->getDocument(self::COLLECTION_COLLECTIONS, $id)); + return $this->adapter->deleteCollection($name); } /** diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 944373342..e93a34dc9 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -24,56 +24,28 @@ public function tearDown(): void Authorization::reset(); } - public function testCreate() + public function testCreateAndDelete() { - $this->assertEquals(true, static::getDatabase()->create('test_database')); + $this->assertEquals(true, static::getDatabase()->create()); + $this->assertEquals(true, static::getDatabase()->delete()); + $this->assertEquals(true, static::getDatabase()->create()); } - public function testDelete() + /** + * @depends testCreateAndDelete + */ + public function testCreateCollection() { - $this->assertEquals(true, static::getDatabase()->delete('test_database')); + $this->assertEquals(true, static::getDatabase()->createCollection('actors')); } - // public function testCreateCollection() - // { - // $collection = self::$database->createDocument(Database::COLLECTION_COLLECTIONS, [ - // '$collection' => Database::COLLECTION_COLLECTIONS, - // '$permissions' => ['read' => ['*']], - // 'name' => 'Create', - // ]); - - // $this->assertEquals(true, self::$database->createCollection($collection->getId(), [], [])); - - // try { - // self::$database->createCollection($collection->getId(), [], []); - // } - // catch (\Throwable $th) { - // return $this->assertEquals('42S01', $th->getCode()); - // } - - // throw new Exception('Expected exception'); - // } - - // public function testDeleteCollection() - // { - // $collection = self::$database->createDocument(Database::COLLECTION_COLLECTIONS, [ - // '$collection' => Database::COLLECTION_COLLECTIONS, - // '$permissions' => ['read' => ['*']], - // 'name' => 'Delete', - // ]); - - // $this->assertEquals(true, self::$database->createCollection($collection->getId(), [], [])); - // $this->assertEquals(true, self::$database->deleteCollection($collection->getId())); - - // try { - // self::$database->deleteCollection($collection->getId()); - // } - // catch (\Throwable $th) { - // return $this->assertEquals('42S02', $th->getCode()); - // } - - // throw new Exception('Expected exception'); - // } + /** + * @depends testCreateCollection + */ + public function testDeleteCollection() + { + $this->assertEquals(true, static::getDatabase()->deleteCollection('actors')); + } // public function testCreateAttribute() // { From 733481fe354fa60fe8265f87edb9cef108d25bff Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 4 Feb 2021 08:41:48 +0200 Subject: [PATCH 017/190] Draft spec --- SPEC.md | 171 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 SPEC.md diff --git a/SPEC.md b/SPEC.md new file mode 100644 index 000000000..ceead832e --- /dev/null +++ b/SPEC.md @@ -0,0 +1,171 @@ +# Database Abstraction Layer + +The goal of this library is to serve as an abstraction layer on top of multiple database adapters and allow storage and query of JSON based documents and relationships. + +## Adapters + +This library will abstract multiple database technologies using the adapter's design patterns. Below is a list of various adapters we should consider to support: +* MariaDB +* MySQL +* Postgres +* MongoDB + +## Data Types + +This library will support storing and fetching of all common JSON simple and complex [data types](https://restfulapi.net/json-data-types/). + +### Simple Types + +* String +* Number +* Boolean +* Null + +### Complex Types +* Array +* Object +* Relationships + * Reference (collection / document) + * References (Array of - collection / document) + +Databases that don't support the storage of complex data types should store them as strings and parse them correctly when fetched. + +## Persistency + +Each database adapter should support the following action for fast storing and retrieval of collections of documents. + +**Databases** (Schemas for MariaDB) +* create +* delete + +**Collections** (Tables for MariaDB) +* createCollection($name) +* deleteCollection($name) + +**Attributes** (Table columns for MariaDB) +* createAttribute(string $collection, string $name, string $type) +* deleteAttribute(string $collection, string $name) + +**Indices** (Table indices for MariaDB) +* createIndex(string $collection, string $name, string $type) +* deleteIndex(string $collection, string $name, string $type) + +**Documents** (Table rows columns for MariaDB) +* getDocument(string $collection, $id) +* createDocument(string $collection, array $data) +* updateDocument(string $collection, $id, array $data) +* deleteDocument(string $collection, $id) + +## Queries + +Each database adapter should allow querying simple and advanced queries in consideration of underline limitations. + +Method for quering data: +* find(string $collection, $filters) +* findFirst(string $collection, $filters) +* findLast(string $collection, $filters) +* count(string $collection, $filters) + +### Supported Query Operations +* Equal (==) +* Not Equal (!=) +* Less Than (<) +* Less or equal (<=) +* Bigger Than (>) +* Bigger or equal (>=) +* Containes / In +* Is Null +* Is Empty + +### Joins / Relationships + +## Paging + +Each database adapter should support two methods for paging. The first method is the classic `Limit and Offset`. The second method is `After` paging, which allows better performance at a larger scale. + +## Orders + +Multi-column support, order type, use native types + +## Features + +> Each database collection should hold parallel tables (if needed) with row-level metadata, used to abstract features that are not enabled in all the adapters. + +### Row-level Security + +### GEO Queries + +### Free Search + +### Filters + +Allow to apply custom filters on specific pre-chosen fields. Avaliable filters: + +* Encryption +* JSON (might be redundent with object support) +* Hashing (md5,bcrypt) + +## Caching + +The library should support memory caching using internal or external memory devices for all read operations. Write operations should actively clean or update the cache. + +## Encoding + +All database adapters should support UTF-8 encoding and accept emoji characters. + +## Documentation + +* Data Types +* Operations +* Security +* Benchmarks +* Performance Tips +* Known Limitations + +## Tests + +* Check for SQL Injections + +## Examples (MariaDB) + +**Collections Metadata** + +```sql +CREATE TABLE IF NOT EXISTS `collections` ( + `_id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `_metadata` text() DEFAULT NULL, + PRIMARY KEY (`id`), +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +**Documents** + +```sql +CREATE TABLE IF NOT EXISTS `documents_[NAME]` ( + `_id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `_uid` varchar(128) NOT NULL AUTO_INCREMENT, + `custom1` text() DEFAULT NULL, + `custom2` text() DEFAULT NULL, + `custom3` text() DEFAULT NULL, + PRIMARY KEY (`_id`), + UNIQUE KEY `_index1` (`$id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +**Documents Authorization** + +```sql +CREATE TABLE IF NOT EXISTS `documents_[NAME]_authorization` ( + `_id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `_document` varchar(128) DEFAULT NULL, + `_role` varchar(128) DEFAULT NULL, + `_action` varchar(128) DEFAULT NULL, + PRIMARY KEY (`_id`), + KEY `_index1` (`_document`) + KEY `_index1` (`_document`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +## Optimization Tools + +https://www.eversql.com/ \ No newline at end of file From cc80ce47fca2fb67946a3e6c04596a4d24cce9e0 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 6 Feb 2021 08:32:12 +0200 Subject: [PATCH 018/190] Updated tests --- .phpunit.result.cache | 1 + .travis.yml | 4 +- architecture.drawio.svg | 68 ++++++++++++++++++++++++++ docker-compose.yml | 12 +++++ tests/Database/Adapter/MariaDBTest.php | 1 - tests/Database/Adapter/MongoDBTest.php | 2 - tests/Database/Adapter/MySQL.php | 46 +++++++++++++++++ 7 files changed, 129 insertions(+), 5 deletions(-) create mode 100644 .phpunit.result.cache create mode 100644 architecture.drawio.svg create mode 100644 tests/Database/Adapter/MySQL.php diff --git a/.phpunit.result.cache b/.phpunit.result.cache new file mode 100644 index 000000000..06a009e34 --- /dev/null +++ b/.phpunit.result.cache @@ -0,0 +1 @@ +C:37:"PHPUnit\Runner\DefaultTestResultCache":4358:{a:2:{s:7:"defects";a:30:{s:42:"Utopia\Tests\AuthorizationTest::testValues";i:4;s:32:"Utopia\Tests\KeyTest::testValues";i:4;s:40:"Utopia\Tests\PermissionsTest::testValues";i:4;s:32:"Utopia\Tests\UIDTest::testValues";i:4;s:42:"Utopia\Tests\DocumentTest::testPermissions";i:4;s:33:"Utopia\Tests\DocumentTest::testId";i:4;s:41:"Utopia\Tests\DocumentTest::testCollection";i:4;s:43:"Utopia\Tests\DocumentTest::testGetAttribute";i:4;s:43:"Utopia\Tests\DocumentTest::testSetAttribute";i:3;s:37:"Utopia\Tests\DocumentTest::testSearch";i:3;s:36:"Utopia\Tests\DocumentTest::testIsSet";i:3;s:43:"Utopia\Tests\DocumentTest::testGetArrayCopy";i:3;s:54:"Utopia\Tests\Adapter\MariaDBTest::testCreateCollection";i:6;s:54:"Utopia\Tests\Adapter\MariaDBTest::testDeleteCollection";i:4;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAttribute";i:4;s:53:"Utopia\Tests\Adapter\MariaDBTest::testDeleteAttribute";i:4;s:49:"Utopia\Tests\Adapter\MariaDBTest::testCreateIndex";i:4;s:49:"Utopia\Tests\Adapter\MariaDBTest::testDeleteIndex";i:4;s:47:"Utopia\Tests\Adapter\MariaDBTest::testFindFirst";i:4;s:46:"Utopia\Tests\Adapter\MariaDBTest::testFindLast";i:4;s:44:"Utopia\Tests\Adapter\MariaDBTest::testCreate";i:4;s:44:"Utopia\Tests\Adapter\MariaDBTest::testDelete";i:4;s:45:"Utopia\Tests\Adapter\PostgresTest::testCreate";i:4;s:45:"Utopia\Tests\Adapter\PostgresTest::testDelete";i:4;s:44:"Utopia\Tests\Adapter\MongoDBTest::testCreate";i:4;s:44:"Utopia\Tests\Adapter\MongoDBTest::testDelete";i:4;s:54:"Utopia\Tests\Adapter\MongoDBTest::testCreateCollection";i:6;s:54:"Utopia\Tests\Adapter\MongoDBTest::testDeleteCollection";i:6;s:55:"Utopia\Tests\Adapter\PostgresTest::testCreateCollection";i:4;s:55:"Utopia\Tests\Adapter\PostgresTest::testDeleteCollection";i:1;}s:5:"times";a:43:{s:42:"Utopia\Tests\AuthorizationTest::testValues";d:0.003;s:32:"Utopia\Tests\KeyTest::testValues";d:0.001;s:40:"Utopia\Tests\PermissionsTest::testValues";d:0.001;s:32:"Utopia\Tests\UIDTest::testValues";d:0.001;s:33:"Utopia\Tests\DocumentTest::testId";d:0.002;s:41:"Utopia\Tests\DocumentTest::testCollection";d:0;s:42:"Utopia\Tests\DocumentTest::testPermissions";d:0;s:43:"Utopia\Tests\DocumentTest::testGetAttribute";d:0;s:43:"Utopia\Tests\DocumentTest::testSetAttribute";d:0;s:46:"Utopia\Tests\DocumentTest::testRemoveAttribute";d:0;s:37:"Utopia\Tests\DocumentTest::testSearch";d:0;s:38:"Utopia\Tests\DocumentTest::testIsEmpty";d:0;s:36:"Utopia\Tests\DocumentTest::testIsSet";d:0;s:43:"Utopia\Tests\DocumentTest::testGetArrayCopy";d:0;s:52:"Utopia\Tests\Validator\AuthorizationTest::testValues";d:0.006;s:42:"Utopia\Tests\Validator\KeyTest::testValues";d:0.002;s:50:"Utopia\Tests\Validator\PermissionsTest::testValues";d:0.001;s:42:"Utopia\Tests\Validator\UIDTest::testValues";d:0.002;s:54:"Utopia\Tests\Adapter\MariaDBTest::testCreateCollection";d:0.009;s:54:"Utopia\Tests\Adapter\MariaDBTest::testDeleteCollection";d:0.004;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAttribute";d:0.002;s:53:"Utopia\Tests\Adapter\MariaDBTest::testDeleteAttribute";d:0.002;s:49:"Utopia\Tests\Adapter\MariaDBTest::testCreateIndex";d:0.001;s:49:"Utopia\Tests\Adapter\MariaDBTest::testDeleteIndex";d:0.002;s:47:"Utopia\Tests\Adapter\MariaDBTest::testFindFirst";d:0;s:46:"Utopia\Tests\Adapter\MariaDBTest::testFindLast";d:0;s:44:"Utopia\Tests\Adapter\MariaDBTest::testCreate";d:0.038;s:44:"Utopia\Tests\Adapter\MariaDBTest::testDelete";d:0.002;s:45:"Utopia\Tests\Adapter\PostgresTest::testCreate";d:0.108;s:45:"Utopia\Tests\Adapter\PostgresTest::testDelete";d:0.017;s:48:"Utopia\Tests\Adapter\PostgresTest::testFindFirst";d:0;s:47:"Utopia\Tests\Adapter\PostgresTest::testFindLast";d:0;s:44:"Utopia\Tests\Adapter\MongoDBTest::testCreate";d:0.008;s:44:"Utopia\Tests\Adapter\MongoDBTest::testDelete";d:0.032;s:47:"Utopia\Tests\Adapter\MongoDBTest::testFindFirst";d:0;s:46:"Utopia\Tests\Adapter\MongoDBTest::testFindLast";d:0;s:54:"Utopia\Tests\Adapter\MongoDBTest::testCreateCollection";d:0.016;s:54:"Utopia\Tests\Adapter\MongoDBTest::testDeleteCollection";d:0.006;s:55:"Utopia\Tests\Adapter\PostgresTest::testCreateCollection";d:0.012;s:55:"Utopia\Tests\Adapter\PostgresTest::testDeleteCollection";d:0.002;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAndDelete";d:0.042;s:53:"Utopia\Tests\Adapter\MongoDBTest::testCreateAndDelete";d:0.034;s:54:"Utopia\Tests\Adapter\PostgresTest::testCreateAndDelete";d:0.017;}}} \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 01ffa5c22..51108e4dc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ language: php php: -- 7.3 - 7.4 +- 8.0 - nightly services: @@ -20,5 +20,5 @@ install: script: - docker ps -- vendor/bin/phpunit --configuration phpunit.xml +- vendor/bin/phpunit --configuration phpunit.xml tests - vendor/bin/psalm --show-info=true diff --git a/architecture.drawio.svg b/architecture.drawio.svg new file mode 100644 index 000000000..85dfc931a --- /dev/null +++ b/architecture.drawio.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + +
+
+
+ Database +
+
+
+
+ + Database + +
+
+ + + + +
+
+
+ Documents +
+
+
+
+ + Documents + +
+
+ + + + +
+
+
+ Collections +
+
+
+
+ + Collections + +
+
+
+ + + + + Viewer does not support full SVG 1.1 + + + +
\ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index a2ba5a4ae..81d9aaba1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -43,5 +43,17 @@ services: MONGO_INITDB_ROOT_USERNAME: root MONGO_INITDB_ROOT_PASSWORD: example + mysql: + image: mysql:5.7 + container_name: mysql + ports: + - "8703:3307" + environment: + MYSQL_ROOT_PASSWORD: password + MYSQL_DATABASE: default + MYSQL_USER: user + MYSQL_PASSWORD: password + MYSQL_TCP_PORT: 3307 + networks: database: \ No newline at end of file diff --git a/tests/Database/Adapter/MariaDBTest.php b/tests/Database/Adapter/MariaDBTest.php index 5b0f3a64f..10f4d98f3 100644 --- a/tests/Database/Adapter/MariaDBTest.php +++ b/tests/Database/Adapter/MariaDBTest.php @@ -4,7 +4,6 @@ use PDO; use Utopia\Database\Database; -use Utopia\Database\Adapter; use Utopia\Database\Adapter\MariaDB; use Utopia\Tests\Base; diff --git a/tests/Database/Adapter/MongoDBTest.php b/tests/Database/Adapter/MongoDBTest.php index 6412e66d5..7eb314f50 100644 --- a/tests/Database/Adapter/MongoDBTest.php +++ b/tests/Database/Adapter/MongoDBTest.php @@ -5,8 +5,6 @@ use MongoDB\Client; use PDO; use Utopia\Database\Database; -use Utopia\Database\Adapter; -use Utopia\Database\Adapter\MariaDB; use Utopia\Database\Adapter\MongoDB; use Utopia\Tests\Base; diff --git a/tests/Database/Adapter/MySQL.php b/tests/Database/Adapter/MySQL.php new file mode 100644 index 000000000..0b7bfbdd8 --- /dev/null +++ b/tests/Database/Adapter/MySQL.php @@ -0,0 +1,46 @@ + 'SET NAMES utf8mb4', + PDO::ATTR_TIMEOUT => 3, // Seconds + PDO::ATTR_PERSISTENT => true + )); + + // Connection settings + $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); // Return arrays + $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // Handle all errors with exceptions + + $database = new Database(new MySQL($pdo)); + $database->setNamespace('myapp_'.uniqid()); + + return self::$database = $database; + } +} \ No newline at end of file From 85a2aab8dd79fe373c4942af3a401b6c4e81cea0 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 6 Feb 2021 09:02:45 +0200 Subject: [PATCH 019/190] Commit lock file --- .gitignore | 1 - composer.lock | 3874 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 3874 insertions(+), 1 deletion(-) create mode 100644 composer.lock diff --git a/.gitignore b/.gitignore index 8924c8079..e244eda0b 100755 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ -composer.lock /vendor/ /.idea/ \ No newline at end of file diff --git a/composer.lock b/composer.lock new file mode 100644 index 000000000..ad0a0d8ce --- /dev/null +++ b/composer.lock @@ -0,0 +1,3874 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "3f6639dbd84b14f560220fff38fcde12", + "packages": [ + { + "name": "composer/package-versions-deprecated", + "version": "1.11.99.1", + "source": { + "type": "git", + "url": "https://github.com/composer/package-versions-deprecated.git", + "reference": "7413f0b55a051e89485c5cb9f765fe24bb02a7b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/7413f0b55a051e89485c5cb9f765fe24bb02a7b6", + "reference": "7413f0b55a051e89485c5cb9f765fe24bb02a7b6", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1.0 || ^2.0", + "php": "^7 || ^8" + }, + "replace": { + "ocramius/package-versions": "1.11.99" + }, + "require-dev": { + "composer/composer": "^1.9.3 || ^2.0@dev", + "ext-zip": "^1.13", + "phpunit/phpunit": "^6.5 || ^7" + }, + "type": "composer-plugin", + "extra": { + "class": "PackageVersions\\Installer", + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "PackageVersions\\": "src/PackageVersions" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", + "support": { + "issues": "https://github.com/composer/package-versions-deprecated/issues", + "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.1" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2020-11-11T10:22:58+00:00" + }, + { + "name": "jean85/pretty-package-versions", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/Jean85/pretty-package-versions.git", + "reference": "a917488320c20057da87f67d0d40543dd9427f7a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/a917488320c20057da87f67d0d40543dd9427f7a", + "reference": "a917488320c20057da87f67d0d40543dd9427f7a", + "shasum": "" + }, + "require": { + "composer/package-versions-deprecated": "^1.8.0", + "php": "^7.0|^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0|^8.5|^9.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Jean85\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alessandro Lai", + "email": "alessandro.lai85@gmail.com" + } + ], + "description": "A wrapper for ocramius/package-versions to get pretty versions strings", + "keywords": [ + "composer", + "package", + "release", + "versions" + ], + "support": { + "issues": "https://github.com/Jean85/pretty-package-versions/issues", + "source": "https://github.com/Jean85/pretty-package-versions/tree/1.5.1" + }, + "time": "2020-09-14T08:43:34+00:00" + }, + { + "name": "mongodb/mongodb", + "version": "1.8.0", + "source": { + "type": "git", + "url": "https://github.com/mongodb/mongo-php-library.git", + "reference": "953dbc19443aa9314c44b7217a16873347e6840d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/953dbc19443aa9314c44b7217a16873347e6840d", + "reference": "953dbc19443aa9314c44b7217a16873347e6840d", + "shasum": "" + }, + "require": { + "ext-hash": "*", + "ext-json": "*", + "ext-mongodb": "^1.8.1", + "jean85/pretty-package-versions": "^1.2", + "php": "^7.0 || ^8.0", + "symfony/polyfill-php80": "^1.19" + }, + "require-dev": { + "squizlabs/php_codesniffer": "^3.5, <3.5.5", + "symfony/phpunit-bridge": "5.x-dev" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8.x-dev" + } + }, + "autoload": { + "psr-4": { + "MongoDB\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Andreas Braun", + "email": "andreas.braun@mongodb.com" + }, + { + "name": "Jeremy Mikola", + "email": "jmikola@gmail.com" + } + ], + "description": "MongoDB driver library", + "homepage": "https://jira.mongodb.org/browse/PHPLIB", + "keywords": [ + "database", + "driver", + "mongodb", + "persistence" + ], + "support": { + "issues": "https://github.com/mongodb/mongo-php-library/issues", + "source": "https://github.com/mongodb/mongo-php-library/tree/1.8.0" + }, + "time": "2020-11-25T12:26:02+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.22.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dc3063ba22c2a1fd2f45ed856374d79114998f91", + "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.22.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-07T16:49:33+00:00" + }, + { + "name": "utopia-php/framework", + "version": "0.10.0", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/framework.git", + "reference": "65909bdb24ef6b6c6751abfdea90caf96bbc6c50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/65909bdb24ef6b6c6751abfdea90caf96bbc6c50", + "reference": "65909bdb24ef6b6c6751abfdea90caf96bbc6c50", + "shasum": "" + }, + "require": { + "php": ">=7.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.4", + "vimeo/psalm": "4.0.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eldad Fux", + "email": "eldad@appwrite.io" + } + ], + "description": "A simple, light and advanced PHP framework", + "keywords": [ + "framework", + "php", + "upf" + ], + "support": { + "issues": "https://github.com/utopia-php/framework/issues", + "source": "https://github.com/utopia-php/framework/tree/0.10.0" + }, + "time": "2020-12-26T12:02:39+00:00" + } + ], + "packages-dev": [ + { + "name": "amphp/amp", + "version": "v2.5.2", + "source": { + "type": "git", + "url": "https://github.com/amphp/amp.git", + "reference": "efca2b32a7580087adb8aabbff6be1dc1bb924a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/amp/zipball/efca2b32a7580087adb8aabbff6be1dc1bb924a9", + "reference": "efca2b32a7580087adb8aabbff6be1dc1bb924a9", + "shasum": "" + }, + "require": { + "php": ">=7" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1", + "ext-json": "*", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^6.0.9 | ^7", + "psalm/phar": "^3.11@dev", + "react/promise": "^2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Amp\\": "lib" + }, + "files": [ + "lib/functions.php", + "lib/Internal/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A non-blocking concurrency framework for PHP applications.", + "homepage": "http://amphp.org/amp", + "keywords": [ + "async", + "asynchronous", + "awaitable", + "concurrency", + "event", + "event-loop", + "future", + "non-blocking", + "promise" + ], + "support": { + "irc": "irc://irc.freenode.org/amphp", + "issues": "https://github.com/amphp/amp/issues", + "source": "https://github.com/amphp/amp/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2021-01-10T17:06:37+00:00" + }, + { + "name": "amphp/byte-stream", + "version": "v1.8.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/byte-stream.git", + "reference": "f0c20cf598a958ba2aa8c6e5a71c697d652c7088" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/f0c20cf598a958ba2aa8c6e5a71c697d652c7088", + "reference": "f0c20cf598a958ba2aa8c6e5a71c697d652c7088", + "shasum": "" + }, + "require": { + "amphp/amp": "^2", + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1.4", + "friendsofphp/php-cs-fixer": "^2.3", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^6 || ^7 || ^8", + "psalm/phar": "^3.11.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Amp\\ByteStream\\": "lib" + }, + "files": [ + "lib/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A stream abstraction to make working with non-blocking I/O simple.", + "homepage": "http://amphp.org/byte-stream", + "keywords": [ + "amp", + "amphp", + "async", + "io", + "non-blocking", + "stream" + ], + "support": { + "irc": "irc://irc.freenode.org/amphp", + "issues": "https://github.com/amphp/byte-stream/issues", + "source": "https://github.com/amphp/byte-stream/tree/master" + }, + "time": "2020-06-29T18:35:05+00:00" + }, + { + "name": "composer/semver", + "version": "3.2.4", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "a02fdf930a3c1c3ed3a49b5f63859c0c20e10464" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/a02fdf930a3c1c3ed3a49b5f63859c0c20e10464", + "reference": "a02fdf930a3c1c3ed3a49b5f63859c0c20e10464", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.54", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.2.4" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2020-11-13T08:59:24+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "1.4.5", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "f28d44c286812c714741478d968104c5e604a1d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/f28d44c286812c714741478d968104c5e604a1d4", + "reference": "f28d44c286812c714741478d968104c5e604a1d4", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0", + "psr/log": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/1.4.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2020-11-13T08:04:11+00:00" + }, + { + "name": "dnoegel/php-xdg-base-dir", + "version": "v0.1.1", + "source": { + "type": "git", + "url": "https://github.com/dnoegel/php-xdg-base-dir.git", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "XdgBaseDir\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "implementation of xdg base directory specification for php", + "support": { + "issues": "https://github.com/dnoegel/php-xdg-base-dir/issues", + "source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1" + }, + "time": "2019-12-04T15:06:13+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b", + "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^8.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13 || 1.0.0-alpha2", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.4.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2020-11-10T18:47:58+00:00" + }, + { + "name": "felixfbecker/advanced-json-rpc", + "version": "v3.2.0", + "source": { + "type": "git", + "url": "https://github.com/felixfbecker/php-advanced-json-rpc.git", + "reference": "06f0b06043c7438959dbdeed8bb3f699a19be22e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/06f0b06043c7438959dbdeed8bb3f699a19be22e", + "reference": "06f0b06043c7438959dbdeed8bb3f699a19be22e", + "shasum": "" + }, + "require": { + "netresearch/jsonmapper": "^1.0 || ^2.0", + "php": "^7.1 || ^8.0", + "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "AdvancedJsonRpc\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], + "description": "A more advanced JSONRPC implementation", + "support": { + "issues": "https://github.com/felixfbecker/php-advanced-json-rpc/issues", + "source": "https://github.com/felixfbecker/php-advanced-json-rpc/tree/v3.2.0" + }, + "time": "2021-01-10T17:48:47+00:00" + }, + { + "name": "felixfbecker/language-server-protocol", + "version": "v1.5.0", + "source": { + "type": "git", + "url": "https://github.com/felixfbecker/php-language-server-protocol.git", + "reference": "85e83cacd2ed573238678c6875f8f0d7ec699541" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/85e83cacd2ed573238678c6875f8f0d7ec699541", + "reference": "85e83cacd2ed573238678c6875f8f0d7ec699541", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpstan/phpstan": "*", + "squizlabs/php_codesniffer": "^3.1", + "vimeo/psalm": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "LanguageServerProtocol\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], + "description": "PHP classes for the Language Server Protocol", + "keywords": [ + "language", + "microsoft", + "php", + "server" + ], + "support": { + "issues": "https://github.com/felixfbecker/php-language-server-protocol/issues", + "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.0" + }, + "time": "2020-10-23T13:55:30+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.10.2", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220", + "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "replace": { + "myclabs/deep-copy": "self.version" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.10.2" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2020-11-13T09:40:50+00:00" + }, + { + "name": "netresearch/jsonmapper", + "version": "v2.1.0", + "source": { + "type": "git", + "url": "https://github.com/cweiske/jsonmapper.git", + "reference": "e0f1e33a71587aca81be5cffbb9746510e1fe04e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/e0f1e33a71587aca81be5cffbb9746510e1fe04e", + "reference": "e0f1e33a71587aca81be5cffbb9746510e1fe04e", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "~4.8.35 || ~5.7 || ~6.4 || ~7.0", + "squizlabs/php_codesniffer": "~3.5" + }, + "type": "library", + "autoload": { + "psr-0": { + "JsonMapper": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "OSL-3.0" + ], + "authors": [ + { + "name": "Christian Weiske", + "email": "cweiske@cweiske.de", + "homepage": "http://github.com/cweiske/jsonmapper/", + "role": "Developer" + } + ], + "description": "Map nested JSON structures onto PHP classes", + "support": { + "email": "cweiske@cweiske.de", + "issues": "https://github.com/cweiske/jsonmapper/issues", + "source": "https://github.com/cweiske/jsonmapper/tree/master" + }, + "time": "2020-04-16T18:48:43+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.10.4", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "c6d052fc58cb876152f89f532b95a8d7907e7f0e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/c6d052fc58cb876152f89f532b95a8d7907e7f0e", + "reference": "c6d052fc58cb876152f89f532b95a8d7907e7f0e", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.0" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.10.4" + }, + "time": "2020-12-20T10:01:03+00:00" + }, + { + "name": "openlss/lib-array2xml", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/nullivex/lib-array2xml.git", + "reference": "a91f18a8dfc69ffabe5f9b068bc39bb202c81d90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nullivex/lib-array2xml/zipball/a91f18a8dfc69ffabe5f9b068bc39bb202c81d90", + "reference": "a91f18a8dfc69ffabe5f9b068bc39bb202c81d90", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "autoload": { + "psr-0": { + "LSS": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Bryan Tong", + "email": "bryan@nullivex.com", + "homepage": "https://www.nullivex.com" + }, + { + "name": "Tony Butler", + "email": "spudz76@gmail.com", + "homepage": "https://www.nullivex.com" + } + ], + "description": "Array2XML conversion library credit to lalit.org", + "homepage": "https://www.nullivex.com", + "keywords": [ + "array", + "array conversion", + "xml", + "xml conversion" + ], + "support": { + "issues": "https://github.com/nullivex/lib-array2xml/issues", + "source": "https://github.com/nullivex/lib-array2xml/tree/master" + }, + "time": "2019-03-29T20:06:56+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", + "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/master" + }, + "time": "2020-06-27T14:33:11+00:00" + }, + { + "name": "phar-io/version", + "version": "3.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "e4782611070e50613683d2b9a57730e9a3ba5451" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/e4782611070e50613683d2b9a57730e9a3ba5451", + "reference": "e4782611070e50613683d2b9a57730e9a3ba5451", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.0.4" + }, + "time": "2020-12-13T23:18:30+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.2.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556", + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.3", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master" + }, + "time": "2020-09-03T19:13:55+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.4.0" + }, + "time": "2020-09-17T18:55:26+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "1.12.2", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "245710e971a030f42e08f4912863805570f23d39" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/245710e971a030f42e08f4912863805570f23d39", + "reference": "245710e971a030f42e08f4912863805570f23d39", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.2", + "php": "^7.2 || ~8.0, <8.1", + "phpdocumentor/reflection-docblock": "^5.2", + "sebastian/comparator": "^3.0 || ^4.0", + "sebastian/recursion-context": "^3.0 || ^4.0" + }, + "require-dev": { + "phpspec/phpspec": "^6.0", + "phpunit/phpunit": "^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "support": { + "issues": "https://github.com/phpspec/prophecy/issues", + "source": "https://github.com/phpspec/prophecy/tree/1.12.2" + }, + "time": "2020-12-19T10:15:11+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "f3e026641cc91909d421802dd3ac7827ebfd97e1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f3e026641cc91909d421802dd3ac7827ebfd97e1", + "reference": "f3e026641cc91909d421802dd3ac7827ebfd97e1", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.10.2", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0.3", + "sebastian/version": "^3.0.1", + "theseer/tokenizer": "^1.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcov": "*", + "ext-xdebug": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-28T06:44:49+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/aa4be8575f26070b100fccb67faabb28f21f66f8", + "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:57:25+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.5.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "e7bdf4085de85a825f4424eae52c99a1cec2f360" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e7bdf4085de85a825f4424eae52c99a1cec2f360", + "reference": "e7bdf4085de85a825f4424eae52c99a1cec2f360", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.3.1", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.1", + "phar-io/version": "^3.0.2", + "php": ">=7.3", + "phpspec/prophecy": "^1.12.1", + "phpunit/php-code-coverage": "^9.2.3", + "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.3", + "phpunit/php-timer": "^5.0.2", + "sebastian/cli-parser": "^1.0.1", + "sebastian/code-unit": "^1.0.6", + "sebastian/comparator": "^4.0.5", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.3", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^2.3", + "sebastian/version": "^3.0.2" + }, + "require-dev": { + "ext-pdo": "*", + "phpspec/prophecy-phpunit": "^2.0.1" + }, + "suggest": { + "ext-soap": "*", + "ext-xdebug": "*" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.5-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ], + "files": [ + "src/Framework/Assert/Functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.1" + }, + "funding": [ + { + "url": "https://phpunit.de/donate.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-01-17T07:42:25+00:00" + }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/master" + }, + "time": "2017-02-14T16:28:37+00:00" + }, + { + "name": "psr/log", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.3" + }, + "time": "2020-03-23T09:12:05+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:08:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:08:54+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:30:19+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "55f4261989e546dc112258c7a75935a81a7ce382" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382", + "reference": "55f4261989e546dc112258c7a75935a81a7ce382", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:49:45+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.7", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:52:27+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:10:38+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "388b6ced16caa751030f6a69e588299fa09200ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/388b6ced16caa751030f6a69e588299fa09200ac", + "reference": "388b6ced16caa751030f6a69e588299fa09200ac", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:52:38+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/d89cc98761b8cb5a1a235a6b703ae50d34080e65", + "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:24:23+00:00" + }, + { + "name": "sebastian/global-state", + "version": "5.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "a90ccbddffa067b51f574dea6eb25d5680839455" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/a90ccbddffa067b51f574dea6eb25d5680839455", + "reference": "a90ccbddffa067b51f574dea6eb25d5680839455", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:55:19+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.6", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-28T06:42:11+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:12:34+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:14:26+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:17:30+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:45:17+00:00" + }, + { + "name": "sebastian/type", + "version": "2.3.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "81cd61ab7bbf2de744aba0ea61fae32f721df3d2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/81cd61ab7bbf2de744aba0ea61fae32f721df3d2", + "reference": "81cd61ab7bbf2de744aba0ea61fae32f721df3d2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/2.3.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:18:59+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" + }, + { + "name": "symfony/console", + "version": "v5.2.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "47c02526c532fb381374dab26df05e7313978976" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/47c02526c532fb381374dab26df05e7313978976", + "reference": "47c02526c532fb381374dab26df05e7313978976", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/polyfill-php80": "^1.15", + "symfony/service-contracts": "^1.1|^2", + "symfony/string": "^5.1" + }, + "conflict": { + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" + }, + "provide": { + "psr/log-implementation": "1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/event-dispatcher": "^4.4|^5.0", + "symfony/lock": "^4.4|^5.0", + "symfony/process": "^4.4|^5.0", + "symfony/var-dumper": "^4.4|^5.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v5.2.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-12-18T08:03:05+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.22.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "c6c942b1ac76c82448322025e084cadc56048b4e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/c6c942b1ac76c82448322025e084cadc56048b4e", + "reference": "c6c942b1ac76c82448322025e084cadc56048b4e", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.22.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-07T16:49:33+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.22.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "267a9adeb8ecb8071040a740930e077cdfb987af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/267a9adeb8ecb8071040a740930e077cdfb987af", + "reference": "267a9adeb8ecb8071040a740930e077cdfb987af", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.22.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-07T16:49:33+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.22.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "6e971c891537eb617a00bb07a43d182a6915faba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/6e971c891537eb617a00bb07a43d182a6915faba", + "reference": "6e971c891537eb617a00bb07a43d182a6915faba", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.22.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-07T17:09:11+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.22.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "f377a3dd1fde44d37b9831d68dc8dea3ffd28e13" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/f377a3dd1fde44d37b9831d68dc8dea3ffd28e13", + "reference": "f377a3dd1fde44d37b9831d68dc8dea3ffd28e13", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.22.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-07T16:49:33+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.22.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "a678b42e92f86eca04b7fa4c0f6f19d097fb69e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/a678b42e92f86eca04b7fa4c0f6f19d097fb69e2", + "reference": "a678b42e92f86eca04b7fa4c0f6f19d097fb69e2", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.22.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-07T16:49:33+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d15da7ba4957ffb8f1747218be9e1a121fd298a1", + "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.0" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/master" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-07T11:33:47+00:00" + }, + { + "name": "symfony/string", + "version": "v5.2.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "5bd67751d2e3f7d6f770c9154b8fbcb2aa05f7ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/5bd67751d2e3f7d6f770c9154b8fbcb2aa05f7ed", + "reference": "5bd67751d2e3f7d6f770c9154b8fbcb2aa05f7ed", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" + }, + "require-dev": { + "symfony/error-handler": "^4.4|^5.0", + "symfony/http-client": "^4.4|^5.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "files": [ + "Resources/functions.php" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony String component", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v5.2.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-12-05T07:33:16+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "75a63c33a8577608444246075ea0af0d052e452a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/75a63c33a8577608444246075ea0af0d052e452a", + "reference": "75a63c33a8577608444246075ea0af0d052e452a", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/master" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2020-07-12T23:59:07+00:00" + }, + { + "name": "vimeo/psalm", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/vimeo/psalm.git", + "reference": "b1e2e30026936ef8d5bf6a354d1c3959b6231f44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/b1e2e30026936ef8d5bf6a354d1c3959b6231f44", + "reference": "b1e2e30026936ef8d5bf6a354d1c3959b6231f44", + "shasum": "" + }, + "require": { + "amphp/amp": "^2.1", + "amphp/byte-stream": "^1.5", + "composer/package-versions-deprecated": "^1.8.0", + "composer/semver": "^1.4 || ^2.0 || ^3.0", + "composer/xdebug-handler": "^1.1", + "dnoegel/php-xdg-base-dir": "^0.1.1", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-tokenizer": "*", + "felixfbecker/advanced-json-rpc": "^3.0.3", + "felixfbecker/language-server-protocol": "^1.4", + "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0", + "nikic/php-parser": "^4.10.1", + "openlss/lib-array2xml": "^1.0", + "php": "^7.3|^8", + "sebastian/diff": "^3.0 || ^4.0", + "symfony/console": "^3.4.17 || ^4.1.6 || ^5.0", + "webmozart/glob": "^4.1", + "webmozart/path-util": "^2.3" + }, + "provide": { + "psalm/psalm": "self.version" + }, + "require-dev": { + "amphp/amp": "^2.4.2", + "bamarni/composer-bin-plugin": "^1.2", + "brianium/paratest": "^4.0.0", + "ext-curl": "*", + "phpdocumentor/reflection-docblock": "^5", + "phpmyadmin/sql-parser": "5.1.0", + "phpspec/prophecy": ">=1.9.0", + "phpunit/phpunit": "^9.0", + "psalm/plugin-phpunit": "^0.13", + "slevomat/coding-standard": "^5.0", + "squizlabs/php_codesniffer": "^3.5", + "symfony/process": "^4.3", + "weirdan/prophecy-shim": "^1.0 || ^2.0" + }, + "suggest": { + "ext-igbinary": "^2.0.5" + }, + "bin": [ + "psalm", + "psalm-language-server", + "psalm-plugin", + "psalm-refactor", + "psalter" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev", + "dev-3.x": "3.x-dev", + "dev-2.x": "2.x-dev", + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psalm\\": "src/Psalm/" + }, + "files": [ + "src/functions.php", + "src/spl_object_id.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthew Brown" + } + ], + "description": "A static analysis tool for finding errors in PHP applications", + "keywords": [ + "code", + "inspection", + "php" + ], + "support": { + "issues": "https://github.com/vimeo/psalm/issues", + "source": "https://github.com/vimeo/psalm/tree/4.0.1" + }, + "time": "2020-10-20T13:40:17+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.9.1", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0 || ^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<3.9.1" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^7.5.13" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.9.1" + }, + "time": "2020-07-08T17:02:28+00:00" + }, + { + "name": "webmozart/glob", + "version": "4.3.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/glob.git", + "reference": "06358fafde0f32edb4513f4fd88fe113a40c90ee" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/glob/zipball/06358fafde0f32edb4513f4fd88fe113a40c90ee", + "reference": "06358fafde0f32edb4513f4fd88fe113a40c90ee", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8.0.0", + "webmozart/path-util": "^2.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.0", + "symfony/filesystem": "^5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Glob\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "A PHP implementation of Ant's glob.", + "support": { + "issues": "https://github.com/webmozarts/glob/issues", + "source": "https://github.com/webmozarts/glob/tree/4.3.0" + }, + "time": "2021-01-21T06:17:15+00:00" + }, + { + "name": "webmozart/path-util", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/path-util.git", + "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/path-util/zipball/d939f7edc24c9a1bb9c0dee5cb05d8e859490725", + "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "webmozart/assert": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\PathUtil\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "A robust cross-platform utility for normalizing, comparing and modifying file paths.", + "support": { + "issues": "https://github.com/webmozart/path-util/issues", + "source": "https://github.com/webmozart/path-util/tree/2.3.0" + }, + "time": "2015-12-17T08:42:14+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=7.1", + "ext-pdo": "*" + }, + "platform-dev": [], + "plugin-api-version": "2.0.0" +} From 871441b72723820945600a458b94dde742c3e368 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 6 Feb 2021 09:29:03 +0200 Subject: [PATCH 020/190] Updated networks --- docker-compose.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 81d9aaba1..f1f218d05 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -46,6 +46,8 @@ services: mysql: image: mysql:5.7 container_name: mysql + networks: + - database ports: - "8703:3307" environment: From e257ed9a0452ebadd3ebaa9e52f814302c984aa4 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 6 Feb 2021 09:42:01 +0200 Subject: [PATCH 021/190] Updatd CI flow --- .phpunit.result.cache | 2 +- .travis.yml | 4 ++-- docker-compose.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.phpunit.result.cache b/.phpunit.result.cache index 06a009e34..65adf1e7f 100644 --- a/.phpunit.result.cache +++ b/.phpunit.result.cache @@ -1 +1 @@ -C:37:"PHPUnit\Runner\DefaultTestResultCache":4358:{a:2:{s:7:"defects";a:30:{s:42:"Utopia\Tests\AuthorizationTest::testValues";i:4;s:32:"Utopia\Tests\KeyTest::testValues";i:4;s:40:"Utopia\Tests\PermissionsTest::testValues";i:4;s:32:"Utopia\Tests\UIDTest::testValues";i:4;s:42:"Utopia\Tests\DocumentTest::testPermissions";i:4;s:33:"Utopia\Tests\DocumentTest::testId";i:4;s:41:"Utopia\Tests\DocumentTest::testCollection";i:4;s:43:"Utopia\Tests\DocumentTest::testGetAttribute";i:4;s:43:"Utopia\Tests\DocumentTest::testSetAttribute";i:3;s:37:"Utopia\Tests\DocumentTest::testSearch";i:3;s:36:"Utopia\Tests\DocumentTest::testIsSet";i:3;s:43:"Utopia\Tests\DocumentTest::testGetArrayCopy";i:3;s:54:"Utopia\Tests\Adapter\MariaDBTest::testCreateCollection";i:6;s:54:"Utopia\Tests\Adapter\MariaDBTest::testDeleteCollection";i:4;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAttribute";i:4;s:53:"Utopia\Tests\Adapter\MariaDBTest::testDeleteAttribute";i:4;s:49:"Utopia\Tests\Adapter\MariaDBTest::testCreateIndex";i:4;s:49:"Utopia\Tests\Adapter\MariaDBTest::testDeleteIndex";i:4;s:47:"Utopia\Tests\Adapter\MariaDBTest::testFindFirst";i:4;s:46:"Utopia\Tests\Adapter\MariaDBTest::testFindLast";i:4;s:44:"Utopia\Tests\Adapter\MariaDBTest::testCreate";i:4;s:44:"Utopia\Tests\Adapter\MariaDBTest::testDelete";i:4;s:45:"Utopia\Tests\Adapter\PostgresTest::testCreate";i:4;s:45:"Utopia\Tests\Adapter\PostgresTest::testDelete";i:4;s:44:"Utopia\Tests\Adapter\MongoDBTest::testCreate";i:4;s:44:"Utopia\Tests\Adapter\MongoDBTest::testDelete";i:4;s:54:"Utopia\Tests\Adapter\MongoDBTest::testCreateCollection";i:6;s:54:"Utopia\Tests\Adapter\MongoDBTest::testDeleteCollection";i:6;s:55:"Utopia\Tests\Adapter\PostgresTest::testCreateCollection";i:4;s:55:"Utopia\Tests\Adapter\PostgresTest::testDeleteCollection";i:1;}s:5:"times";a:43:{s:42:"Utopia\Tests\AuthorizationTest::testValues";d:0.003;s:32:"Utopia\Tests\KeyTest::testValues";d:0.001;s:40:"Utopia\Tests\PermissionsTest::testValues";d:0.001;s:32:"Utopia\Tests\UIDTest::testValues";d:0.001;s:33:"Utopia\Tests\DocumentTest::testId";d:0.002;s:41:"Utopia\Tests\DocumentTest::testCollection";d:0;s:42:"Utopia\Tests\DocumentTest::testPermissions";d:0;s:43:"Utopia\Tests\DocumentTest::testGetAttribute";d:0;s:43:"Utopia\Tests\DocumentTest::testSetAttribute";d:0;s:46:"Utopia\Tests\DocumentTest::testRemoveAttribute";d:0;s:37:"Utopia\Tests\DocumentTest::testSearch";d:0;s:38:"Utopia\Tests\DocumentTest::testIsEmpty";d:0;s:36:"Utopia\Tests\DocumentTest::testIsSet";d:0;s:43:"Utopia\Tests\DocumentTest::testGetArrayCopy";d:0;s:52:"Utopia\Tests\Validator\AuthorizationTest::testValues";d:0.006;s:42:"Utopia\Tests\Validator\KeyTest::testValues";d:0.002;s:50:"Utopia\Tests\Validator\PermissionsTest::testValues";d:0.001;s:42:"Utopia\Tests\Validator\UIDTest::testValues";d:0.002;s:54:"Utopia\Tests\Adapter\MariaDBTest::testCreateCollection";d:0.009;s:54:"Utopia\Tests\Adapter\MariaDBTest::testDeleteCollection";d:0.004;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAttribute";d:0.002;s:53:"Utopia\Tests\Adapter\MariaDBTest::testDeleteAttribute";d:0.002;s:49:"Utopia\Tests\Adapter\MariaDBTest::testCreateIndex";d:0.001;s:49:"Utopia\Tests\Adapter\MariaDBTest::testDeleteIndex";d:0.002;s:47:"Utopia\Tests\Adapter\MariaDBTest::testFindFirst";d:0;s:46:"Utopia\Tests\Adapter\MariaDBTest::testFindLast";d:0;s:44:"Utopia\Tests\Adapter\MariaDBTest::testCreate";d:0.038;s:44:"Utopia\Tests\Adapter\MariaDBTest::testDelete";d:0.002;s:45:"Utopia\Tests\Adapter\PostgresTest::testCreate";d:0.108;s:45:"Utopia\Tests\Adapter\PostgresTest::testDelete";d:0.017;s:48:"Utopia\Tests\Adapter\PostgresTest::testFindFirst";d:0;s:47:"Utopia\Tests\Adapter\PostgresTest::testFindLast";d:0;s:44:"Utopia\Tests\Adapter\MongoDBTest::testCreate";d:0.008;s:44:"Utopia\Tests\Adapter\MongoDBTest::testDelete";d:0.032;s:47:"Utopia\Tests\Adapter\MongoDBTest::testFindFirst";d:0;s:46:"Utopia\Tests\Adapter\MongoDBTest::testFindLast";d:0;s:54:"Utopia\Tests\Adapter\MongoDBTest::testCreateCollection";d:0.016;s:54:"Utopia\Tests\Adapter\MongoDBTest::testDeleteCollection";d:0.006;s:55:"Utopia\Tests\Adapter\PostgresTest::testCreateCollection";d:0.012;s:55:"Utopia\Tests\Adapter\PostgresTest::testDeleteCollection";d:0.002;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAndDelete";d:0.042;s:53:"Utopia\Tests\Adapter\MongoDBTest::testCreateAndDelete";d:0.034;s:54:"Utopia\Tests\Adapter\PostgresTest::testCreateAndDelete";d:0.017;}}} \ No newline at end of file +C:37:"PHPUnit\Runner\DefaultTestResultCache":4366:{a:2:{s:7:"defects";a:30:{s:42:"Utopia\Tests\AuthorizationTest::testValues";i:4;s:32:"Utopia\Tests\KeyTest::testValues";i:4;s:40:"Utopia\Tests\PermissionsTest::testValues";i:4;s:32:"Utopia\Tests\UIDTest::testValues";i:4;s:42:"Utopia\Tests\DocumentTest::testPermissions";i:4;s:33:"Utopia\Tests\DocumentTest::testId";i:4;s:41:"Utopia\Tests\DocumentTest::testCollection";i:4;s:43:"Utopia\Tests\DocumentTest::testGetAttribute";i:4;s:43:"Utopia\Tests\DocumentTest::testSetAttribute";i:3;s:37:"Utopia\Tests\DocumentTest::testSearch";i:3;s:36:"Utopia\Tests\DocumentTest::testIsSet";i:3;s:43:"Utopia\Tests\DocumentTest::testGetArrayCopy";i:3;s:54:"Utopia\Tests\Adapter\MariaDBTest::testCreateCollection";i:6;s:54:"Utopia\Tests\Adapter\MariaDBTest::testDeleteCollection";i:4;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAttribute";i:4;s:53:"Utopia\Tests\Adapter\MariaDBTest::testDeleteAttribute";i:4;s:49:"Utopia\Tests\Adapter\MariaDBTest::testCreateIndex";i:4;s:49:"Utopia\Tests\Adapter\MariaDBTest::testDeleteIndex";i:4;s:47:"Utopia\Tests\Adapter\MariaDBTest::testFindFirst";i:4;s:46:"Utopia\Tests\Adapter\MariaDBTest::testFindLast";i:4;s:44:"Utopia\Tests\Adapter\MariaDBTest::testCreate";i:4;s:44:"Utopia\Tests\Adapter\MariaDBTest::testDelete";i:4;s:45:"Utopia\Tests\Adapter\PostgresTest::testCreate";i:4;s:45:"Utopia\Tests\Adapter\PostgresTest::testDelete";i:4;s:44:"Utopia\Tests\Adapter\MongoDBTest::testCreate";i:4;s:44:"Utopia\Tests\Adapter\MongoDBTest::testDelete";i:4;s:54:"Utopia\Tests\Adapter\MongoDBTest::testCreateCollection";i:6;s:54:"Utopia\Tests\Adapter\MongoDBTest::testDeleteCollection";i:6;s:55:"Utopia\Tests\Adapter\PostgresTest::testCreateCollection";i:4;s:55:"Utopia\Tests\Adapter\PostgresTest::testDeleteCollection";i:1;}s:5:"times";a:43:{s:42:"Utopia\Tests\AuthorizationTest::testValues";d:0.003;s:32:"Utopia\Tests\KeyTest::testValues";d:0.001;s:40:"Utopia\Tests\PermissionsTest::testValues";d:0.001;s:32:"Utopia\Tests\UIDTest::testValues";d:0.001;s:33:"Utopia\Tests\DocumentTest::testId";d:0.012;s:41:"Utopia\Tests\DocumentTest::testCollection";d:0;s:42:"Utopia\Tests\DocumentTest::testPermissions";d:0.002;s:43:"Utopia\Tests\DocumentTest::testGetAttribute";d:0;s:43:"Utopia\Tests\DocumentTest::testSetAttribute";d:0;s:46:"Utopia\Tests\DocumentTest::testRemoveAttribute";d:0;s:37:"Utopia\Tests\DocumentTest::testSearch";d:0;s:38:"Utopia\Tests\DocumentTest::testIsEmpty";d:0;s:36:"Utopia\Tests\DocumentTest::testIsSet";d:0;s:43:"Utopia\Tests\DocumentTest::testGetArrayCopy";d:0;s:52:"Utopia\Tests\Validator\AuthorizationTest::testValues";d:0.051;s:42:"Utopia\Tests\Validator\KeyTest::testValues";d:0.007;s:50:"Utopia\Tests\Validator\PermissionsTest::testValues";d:0.008;s:42:"Utopia\Tests\Validator\UIDTest::testValues";d:0.006;s:54:"Utopia\Tests\Adapter\MariaDBTest::testCreateCollection";d:0.039;s:54:"Utopia\Tests\Adapter\MariaDBTest::testDeleteCollection";d:0.016;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAttribute";d:0.002;s:53:"Utopia\Tests\Adapter\MariaDBTest::testDeleteAttribute";d:0.002;s:49:"Utopia\Tests\Adapter\MariaDBTest::testCreateIndex";d:0.001;s:49:"Utopia\Tests\Adapter\MariaDBTest::testDeleteIndex";d:0.002;s:47:"Utopia\Tests\Adapter\MariaDBTest::testFindFirst";d:0;s:46:"Utopia\Tests\Adapter\MariaDBTest::testFindLast";d:0;s:44:"Utopia\Tests\Adapter\MariaDBTest::testCreate";d:0.038;s:44:"Utopia\Tests\Adapter\MariaDBTest::testDelete";d:0.002;s:45:"Utopia\Tests\Adapter\PostgresTest::testCreate";d:0.108;s:45:"Utopia\Tests\Adapter\PostgresTest::testDelete";d:0.017;s:48:"Utopia\Tests\Adapter\PostgresTest::testFindFirst";d:0;s:47:"Utopia\Tests\Adapter\PostgresTest::testFindLast";d:0.001;s:44:"Utopia\Tests\Adapter\MongoDBTest::testCreate";d:0.008;s:44:"Utopia\Tests\Adapter\MongoDBTest::testDelete";d:0.032;s:47:"Utopia\Tests\Adapter\MongoDBTest::testFindFirst";d:0;s:46:"Utopia\Tests\Adapter\MongoDBTest::testFindLast";d:0;s:54:"Utopia\Tests\Adapter\MongoDBTest::testCreateCollection";d:0.126;s:54:"Utopia\Tests\Adapter\MongoDBTest::testDeleteCollection";d:0.017;s:55:"Utopia\Tests\Adapter\PostgresTest::testCreateCollection";d:0.078;s:55:"Utopia\Tests\Adapter\PostgresTest::testDeleteCollection";d:0.043;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAndDelete";d:0.401;s:53:"Utopia\Tests\Adapter\MongoDBTest::testCreateAndDelete";d:0.246;s:54:"Utopia\Tests\Adapter\PostgresTest::testCreateAndDelete";d:0.754;}}} \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 51108e4dc..a5fda9f40 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,5 +20,5 @@ install: script: - docker ps -- vendor/bin/phpunit --configuration phpunit.xml tests -- vendor/bin/psalm --show-info=true +- docker-compose exec tests vendor/bin/phpunit --configuration phpunit.xml tests +- docker-compose exec tests vendor/bin/psalm --show-info=true diff --git a/docker-compose.yml b/docker-compose.yml index f1f218d05..ee4aeebde 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -44,7 +44,7 @@ services: MONGO_INITDB_ROOT_PASSWORD: example mysql: - image: mysql:5.7 + image: mysql:8.0 container_name: mysql networks: - database From a74f051af7b4f03de7dfd196740f537eba7caf45 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 6 Feb 2021 09:44:12 +0200 Subject: [PATCH 022/190] Enabled MySQL tests --- .phpunit.result.cache | 2 +- tests/Database/Adapter/{MySQL.php => MySQLTest.php} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename tests/Database/Adapter/{MySQL.php => MySQLTest.php} (100%) diff --git a/.phpunit.result.cache b/.phpunit.result.cache index 65adf1e7f..b8789c5da 100644 --- a/.phpunit.result.cache +++ b/.phpunit.result.cache @@ -1 +1 @@ -C:37:"PHPUnit\Runner\DefaultTestResultCache":4366:{a:2:{s:7:"defects";a:30:{s:42:"Utopia\Tests\AuthorizationTest::testValues";i:4;s:32:"Utopia\Tests\KeyTest::testValues";i:4;s:40:"Utopia\Tests\PermissionsTest::testValues";i:4;s:32:"Utopia\Tests\UIDTest::testValues";i:4;s:42:"Utopia\Tests\DocumentTest::testPermissions";i:4;s:33:"Utopia\Tests\DocumentTest::testId";i:4;s:41:"Utopia\Tests\DocumentTest::testCollection";i:4;s:43:"Utopia\Tests\DocumentTest::testGetAttribute";i:4;s:43:"Utopia\Tests\DocumentTest::testSetAttribute";i:3;s:37:"Utopia\Tests\DocumentTest::testSearch";i:3;s:36:"Utopia\Tests\DocumentTest::testIsSet";i:3;s:43:"Utopia\Tests\DocumentTest::testGetArrayCopy";i:3;s:54:"Utopia\Tests\Adapter\MariaDBTest::testCreateCollection";i:6;s:54:"Utopia\Tests\Adapter\MariaDBTest::testDeleteCollection";i:4;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAttribute";i:4;s:53:"Utopia\Tests\Adapter\MariaDBTest::testDeleteAttribute";i:4;s:49:"Utopia\Tests\Adapter\MariaDBTest::testCreateIndex";i:4;s:49:"Utopia\Tests\Adapter\MariaDBTest::testDeleteIndex";i:4;s:47:"Utopia\Tests\Adapter\MariaDBTest::testFindFirst";i:4;s:46:"Utopia\Tests\Adapter\MariaDBTest::testFindLast";i:4;s:44:"Utopia\Tests\Adapter\MariaDBTest::testCreate";i:4;s:44:"Utopia\Tests\Adapter\MariaDBTest::testDelete";i:4;s:45:"Utopia\Tests\Adapter\PostgresTest::testCreate";i:4;s:45:"Utopia\Tests\Adapter\PostgresTest::testDelete";i:4;s:44:"Utopia\Tests\Adapter\MongoDBTest::testCreate";i:4;s:44:"Utopia\Tests\Adapter\MongoDBTest::testDelete";i:4;s:54:"Utopia\Tests\Adapter\MongoDBTest::testCreateCollection";i:6;s:54:"Utopia\Tests\Adapter\MongoDBTest::testDeleteCollection";i:6;s:55:"Utopia\Tests\Adapter\PostgresTest::testCreateCollection";i:4;s:55:"Utopia\Tests\Adapter\PostgresTest::testDeleteCollection";i:1;}s:5:"times";a:43:{s:42:"Utopia\Tests\AuthorizationTest::testValues";d:0.003;s:32:"Utopia\Tests\KeyTest::testValues";d:0.001;s:40:"Utopia\Tests\PermissionsTest::testValues";d:0.001;s:32:"Utopia\Tests\UIDTest::testValues";d:0.001;s:33:"Utopia\Tests\DocumentTest::testId";d:0.012;s:41:"Utopia\Tests\DocumentTest::testCollection";d:0;s:42:"Utopia\Tests\DocumentTest::testPermissions";d:0.002;s:43:"Utopia\Tests\DocumentTest::testGetAttribute";d:0;s:43:"Utopia\Tests\DocumentTest::testSetAttribute";d:0;s:46:"Utopia\Tests\DocumentTest::testRemoveAttribute";d:0;s:37:"Utopia\Tests\DocumentTest::testSearch";d:0;s:38:"Utopia\Tests\DocumentTest::testIsEmpty";d:0;s:36:"Utopia\Tests\DocumentTest::testIsSet";d:0;s:43:"Utopia\Tests\DocumentTest::testGetArrayCopy";d:0;s:52:"Utopia\Tests\Validator\AuthorizationTest::testValues";d:0.051;s:42:"Utopia\Tests\Validator\KeyTest::testValues";d:0.007;s:50:"Utopia\Tests\Validator\PermissionsTest::testValues";d:0.008;s:42:"Utopia\Tests\Validator\UIDTest::testValues";d:0.006;s:54:"Utopia\Tests\Adapter\MariaDBTest::testCreateCollection";d:0.039;s:54:"Utopia\Tests\Adapter\MariaDBTest::testDeleteCollection";d:0.016;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAttribute";d:0.002;s:53:"Utopia\Tests\Adapter\MariaDBTest::testDeleteAttribute";d:0.002;s:49:"Utopia\Tests\Adapter\MariaDBTest::testCreateIndex";d:0.001;s:49:"Utopia\Tests\Adapter\MariaDBTest::testDeleteIndex";d:0.002;s:47:"Utopia\Tests\Adapter\MariaDBTest::testFindFirst";d:0;s:46:"Utopia\Tests\Adapter\MariaDBTest::testFindLast";d:0;s:44:"Utopia\Tests\Adapter\MariaDBTest::testCreate";d:0.038;s:44:"Utopia\Tests\Adapter\MariaDBTest::testDelete";d:0.002;s:45:"Utopia\Tests\Adapter\PostgresTest::testCreate";d:0.108;s:45:"Utopia\Tests\Adapter\PostgresTest::testDelete";d:0.017;s:48:"Utopia\Tests\Adapter\PostgresTest::testFindFirst";d:0;s:47:"Utopia\Tests\Adapter\PostgresTest::testFindLast";d:0.001;s:44:"Utopia\Tests\Adapter\MongoDBTest::testCreate";d:0.008;s:44:"Utopia\Tests\Adapter\MongoDBTest::testDelete";d:0.032;s:47:"Utopia\Tests\Adapter\MongoDBTest::testFindFirst";d:0;s:46:"Utopia\Tests\Adapter\MongoDBTest::testFindLast";d:0;s:54:"Utopia\Tests\Adapter\MongoDBTest::testCreateCollection";d:0.126;s:54:"Utopia\Tests\Adapter\MongoDBTest::testDeleteCollection";d:0.017;s:55:"Utopia\Tests\Adapter\PostgresTest::testCreateCollection";d:0.078;s:55:"Utopia\Tests\Adapter\PostgresTest::testDeleteCollection";d:0.043;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAndDelete";d:0.401;s:53:"Utopia\Tests\Adapter\MongoDBTest::testCreateAndDelete";d:0.246;s:54:"Utopia\Tests\Adapter\PostgresTest::testCreateAndDelete";d:0.754;}}} \ No newline at end of file +C:37:"PHPUnit\Runner\DefaultTestResultCache":4672:{a:2:{s:7:"defects";a:30:{s:42:"Utopia\Tests\AuthorizationTest::testValues";i:4;s:32:"Utopia\Tests\KeyTest::testValues";i:4;s:40:"Utopia\Tests\PermissionsTest::testValues";i:4;s:32:"Utopia\Tests\UIDTest::testValues";i:4;s:42:"Utopia\Tests\DocumentTest::testPermissions";i:4;s:33:"Utopia\Tests\DocumentTest::testId";i:4;s:41:"Utopia\Tests\DocumentTest::testCollection";i:4;s:43:"Utopia\Tests\DocumentTest::testGetAttribute";i:4;s:43:"Utopia\Tests\DocumentTest::testSetAttribute";i:3;s:37:"Utopia\Tests\DocumentTest::testSearch";i:3;s:36:"Utopia\Tests\DocumentTest::testIsSet";i:3;s:43:"Utopia\Tests\DocumentTest::testGetArrayCopy";i:3;s:54:"Utopia\Tests\Adapter\MariaDBTest::testCreateCollection";i:6;s:54:"Utopia\Tests\Adapter\MariaDBTest::testDeleteCollection";i:4;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAttribute";i:4;s:53:"Utopia\Tests\Adapter\MariaDBTest::testDeleteAttribute";i:4;s:49:"Utopia\Tests\Adapter\MariaDBTest::testCreateIndex";i:4;s:49:"Utopia\Tests\Adapter\MariaDBTest::testDeleteIndex";i:4;s:47:"Utopia\Tests\Adapter\MariaDBTest::testFindFirst";i:4;s:46:"Utopia\Tests\Adapter\MariaDBTest::testFindLast";i:4;s:44:"Utopia\Tests\Adapter\MariaDBTest::testCreate";i:4;s:44:"Utopia\Tests\Adapter\MariaDBTest::testDelete";i:4;s:45:"Utopia\Tests\Adapter\PostgresTest::testCreate";i:4;s:45:"Utopia\Tests\Adapter\PostgresTest::testDelete";i:4;s:44:"Utopia\Tests\Adapter\MongoDBTest::testCreate";i:4;s:44:"Utopia\Tests\Adapter\MongoDBTest::testDelete";i:4;s:54:"Utopia\Tests\Adapter\MongoDBTest::testCreateCollection";i:6;s:54:"Utopia\Tests\Adapter\MongoDBTest::testDeleteCollection";i:6;s:55:"Utopia\Tests\Adapter\PostgresTest::testCreateCollection";i:4;s:55:"Utopia\Tests\Adapter\PostgresTest::testDeleteCollection";i:1;}s:5:"times";a:48:{s:42:"Utopia\Tests\AuthorizationTest::testValues";d:0.003;s:32:"Utopia\Tests\KeyTest::testValues";d:0.001;s:40:"Utopia\Tests\PermissionsTest::testValues";d:0.001;s:32:"Utopia\Tests\UIDTest::testValues";d:0.001;s:33:"Utopia\Tests\DocumentTest::testId";d:0.004;s:41:"Utopia\Tests\DocumentTest::testCollection";d:0;s:42:"Utopia\Tests\DocumentTest::testPermissions";d:0;s:43:"Utopia\Tests\DocumentTest::testGetAttribute";d:0;s:43:"Utopia\Tests\DocumentTest::testSetAttribute";d:0;s:46:"Utopia\Tests\DocumentTest::testRemoveAttribute";d:0;s:37:"Utopia\Tests\DocumentTest::testSearch";d:0;s:38:"Utopia\Tests\DocumentTest::testIsEmpty";d:0;s:36:"Utopia\Tests\DocumentTest::testIsSet";d:0;s:43:"Utopia\Tests\DocumentTest::testGetArrayCopy";d:0;s:52:"Utopia\Tests\Validator\AuthorizationTest::testValues";d:0.031;s:42:"Utopia\Tests\Validator\KeyTest::testValues";d:0.003;s:50:"Utopia\Tests\Validator\PermissionsTest::testValues";d:0.006;s:42:"Utopia\Tests\Validator\UIDTest::testValues";d:0.003;s:54:"Utopia\Tests\Adapter\MariaDBTest::testCreateCollection";d:0.011;s:54:"Utopia\Tests\Adapter\MariaDBTest::testDeleteCollection";d:0.009;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAttribute";d:0.002;s:53:"Utopia\Tests\Adapter\MariaDBTest::testDeleteAttribute";d:0.002;s:49:"Utopia\Tests\Adapter\MariaDBTest::testCreateIndex";d:0.001;s:49:"Utopia\Tests\Adapter\MariaDBTest::testDeleteIndex";d:0.002;s:47:"Utopia\Tests\Adapter\MariaDBTest::testFindFirst";d:0;s:46:"Utopia\Tests\Adapter\MariaDBTest::testFindLast";d:0;s:44:"Utopia\Tests\Adapter\MariaDBTest::testCreate";d:0.038;s:44:"Utopia\Tests\Adapter\MariaDBTest::testDelete";d:0.002;s:45:"Utopia\Tests\Adapter\PostgresTest::testCreate";d:0.108;s:45:"Utopia\Tests\Adapter\PostgresTest::testDelete";d:0.017;s:48:"Utopia\Tests\Adapter\PostgresTest::testFindFirst";d:0;s:47:"Utopia\Tests\Adapter\PostgresTest::testFindLast";d:0;s:44:"Utopia\Tests\Adapter\MongoDBTest::testCreate";d:0.008;s:44:"Utopia\Tests\Adapter\MongoDBTest::testDelete";d:0.032;s:47:"Utopia\Tests\Adapter\MongoDBTest::testFindFirst";d:0;s:46:"Utopia\Tests\Adapter\MongoDBTest::testFindLast";d:0;s:54:"Utopia\Tests\Adapter\MongoDBTest::testCreateCollection";d:0.018;s:54:"Utopia\Tests\Adapter\MongoDBTest::testDeleteCollection";d:0.003;s:55:"Utopia\Tests\Adapter\PostgresTest::testCreateCollection";d:0.008;s:55:"Utopia\Tests\Adapter\PostgresTest::testDeleteCollection";d:0.004;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAndDelete";d:0.115;s:53:"Utopia\Tests\Adapter\MongoDBTest::testCreateAndDelete";d:0.058;s:54:"Utopia\Tests\Adapter\PostgresTest::testCreateAndDelete";d:0.03;s:51:"Utopia\Tests\Adapter\MySQLTest::testCreateAndDelete";d:0.028;s:52:"Utopia\Tests\Adapter\MySQLTest::testCreateCollection";d:0.04;s:52:"Utopia\Tests\Adapter\MySQLTest::testDeleteCollection";d:0.018;s:45:"Utopia\Tests\Adapter\MySQLTest::testFindFirst";d:0;s:44:"Utopia\Tests\Adapter\MySQLTest::testFindLast";d:0;}}} \ No newline at end of file diff --git a/tests/Database/Adapter/MySQL.php b/tests/Database/Adapter/MySQLTest.php similarity index 100% rename from tests/Database/Adapter/MySQL.php rename to tests/Database/Adapter/MySQLTest.php From e1bee5a037539b400949c680050ed6782abc0960 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 6 Feb 2021 10:08:18 +0200 Subject: [PATCH 023/190] Fixed Psalm errors --- src/Database/Adapter.php | 20 +- src/Database/Adapter/MongoDB.php | 2 +- src/Database/Database.php | 914 +++++++++++------------ src/Database/Document.php | 12 +- src/Database/Validator/Authorization.php | 3 +- src/Database/Validator/Collection.php | 120 +-- src/Database/Validator/Structure.php | 582 +++++++-------- 7 files changed, 834 insertions(+), 819 deletions(-) diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index 323057788..6db9ac8b4 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -224,15 +224,29 @@ abstract public function deleteCollection(string $name): bool; // */ // abstract public function count(array $options); - public function filter($value) + /** + * Filter Keys + * + * @throws Exception + * @return string + */ + public function filter(string $value):string { - return preg_replace("/[^A-Za-z0-9 _]/", '', $value); + $value = preg_replace("/[^A-Za-z0-9 _]/", '', $value); + + if(\is_null($value)) { + throw new Exception('Failed to filter key'); + } + + return $value; } /** * Get Unique ID. + * + * @return string */ - public function getId() + public function getId(): string { return \uniqid(); } diff --git a/src/Database/Adapter/MongoDB.php b/src/Database/Adapter/MongoDB.php index 14b961781..01fb5f47a 100644 --- a/src/Database/Adapter/MongoDB.php +++ b/src/Database/Adapter/MongoDB.php @@ -15,7 +15,7 @@ class MongoDB extends Adapter protected $client; /** - * @var Database + * @var Database|null */ protected $database; diff --git a/src/Database/Database.php b/src/Database/Database.php index 9ecb4a1a2..7a0a06477 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -3,10 +3,10 @@ namespace Utopia\Database; use Exception; -use Utopia\Database\Validator\Authorization; -use Utopia\Database\Validator\Structure; -use Utopia\Database\Exception\Authorization as AuthorizationException; -use Utopia\Database\Exception\Structure as StructureException; +// use Utopia\Database\Validator\Authorization; +// use Utopia\Database\Validator\Structure; +// use Utopia\Database\Exception\Authorization as AuthorizationException; +// use Utopia\Database\Exception\Structure as StructureException; class Database { @@ -100,74 +100,6 @@ public function delete(): bool return $this->adapter->delete(); } - /** - * @param string $collection - * @param array $options - * - * @return Document[] - */ - public function find(string $collection, array $options) - { - $options = \array_merge([ - 'offset' => 0, - 'limit' => 15, - 'search' => '', - 'relations' => true, - 'orderField' => '', - 'orderType' => 'ASC', - 'orderCast' => 'int', - 'filters' => [], - ], $options); - - $results = $this->adapter->find($this->getDocument(self::COLLECTION_COLLECTIONS, $collection), $options); - - foreach ($results as &$node) { - $node = $this->decode(new Document($node)); - } - - return $results; - } - - /** - * @param string $collection - * @param array $options - * - * @return Document - */ - public function findFirst(string $collection, array $options) - { - $results = $this->find($collection, $options); - return \reset($results); - } - - /** - * @param string $collection - * @param array $options - * - * @return Document - */ - public function findLast(string $collection, array $options) - { - $results = $this->find($collection, $options); - return \end($results); - } - - /** - * @param array $options - * - * @return int - */ - public function count(array $options) - { - $options = \array_merge([ - 'filters' => [], - ], $options); - - $results = $this->adapter->count($options); - - return $results; - } - /** * Create Collection * @@ -192,404 +124,472 @@ public function deleteCollection(string $name): bool return $this->adapter->deleteCollection($name); } - /** - * Create Attribute - * - * @param string $collection - * @param string $id - * @param string $type - * @param bool $array - * - * @return bool - */ - public function createAttribute(string $collection, string $id, string $type, bool $array = false): bool - { - $collection = $this->getDocument(self::COLLECTION_COLLECTIONS, $collection); - return $this->adapter->createAttribute($collection, $id, $type, $array); - } - - /** - * Delete Attribute - * - * @param string $collection - * @param string $id - * @param bool $array - * - * @return bool - */ - public function deleteAttribute(string $collection, string $id, bool $array): bool - { - return $this->adapter->deleteAttribute($this->getDocument(self::COLLECTION_COLLECTIONS, $collection), $id, $array); - } - - /** - * Create Index - * - * @param string $collection - * @param string $id - * @param string $type - * @param array $attributes - * - * @return bool - */ - public function createIndex(string $collection, string $id, string $type, array $attributes): bool - { - $collection = $this->getDocument(self::COLLECTION_COLLECTIONS, $collection); - return $this->adapter->createIndex($collection, $id, $type, $attributes); - } - - /** - * Delete Index - * - * @param string $collection - * @param string $id - * - * @return bool - */ - public function deleteIndex(string $collection, string $id): bool - { - $collection = $this->getDocument(self::COLLECTION_COLLECTIONS, $collection); - return $this->adapter->deleteIndex($collection, $id); - } - - /** - * @param string $collection - * @param string $id - * @param bool $mock is mocked data allowed? - * @param bool $decode - * - * @return Document - */ - public function getDocument($collection, $id, bool $mock = true, bool $decode = true) - { - if (\is_null($id)) { - return new Document([]); - } - - if($mock === true - && isset($this->mocks[$id])) { - $document = $this->mocks[$id]; - } - else { - $document = new Document($this->adapter->getDocument($this->getDocument(self::COLLECTION_COLLECTIONS, $collection), $id)); - } - - $validator = new Authorization($document, 'read'); - - if (!$validator->isValid($document->getPermissions())) { // Check if user has read access to this document - return new Document(); - } - - $document = ($decode) ? $this->decode($document) : $document; - - return $document; - } - - /** - * @param string $collection - * @param array $data - * @param array $unique - * - * @return Document|bool - * - * @throws AuthorizationException - * @throws StructureException - */ - public function createDocument(string $collection, array $data, array $unique = []) - { - if(isset($data['$id'])) { - throw new Exception('Use update method instead of create'); - } - - $document = new Document($data); - - $validator = new Authorization($document, 'write'); - - if (!$validator->isValid($document->getPermissions())) { // Check if user has write access to this document - throw new AuthorizationException($validator->getDescription()); - } - - $validator = new Structure($this); - $document = $this->encode($document); - - if (!$validator->isValid($document)) { - throw new StructureException($validator->getDescription()); // var_dump($validator->getDescription()); return false; - } + // /** + // * @param string $collection + // * @param array $options + // * + // * @return Document[] + // */ + // public function find(string $collection, array $options) + // { + // $options = \array_merge([ + // 'offset' => 0, + // 'limit' => 15, + // 'search' => '', + // 'relations' => true, + // 'orderField' => '', + // 'orderType' => 'ASC', + // 'orderCast' => 'int', + // 'filters' => [], + // ], $options); + + // $results = $this->adapter->find($this->getDocument(self::COLLECTION_COLLECTIONS, $collection), $options); + + // foreach ($results as &$node) { + // $node = $this->decode(new Document($node)); + // } + + // return $results; + // } + + // /** + // * @param string $collection + // * @param array $options + // * + // * @return Document + // */ + // public function findFirst(string $collection, array $options) + // { + // $results = $this->find($collection, $options); + // return \reset($results); + // } + + // /** + // * @param string $collection + // * @param array $options + // * + // * @return Document + // */ + // public function findLast(string $collection, array $options) + // { + // $results = $this->find($collection, $options); + // return \end($results); + // } + + // /** + // * @param array $options + // * + // * @return int + // */ + // public function count(array $options) + // { + // $options = \array_merge([ + // 'filters' => [], + // ], $options); + + // $results = $this->adapter->count($options); + + // return $results; + // } + + // /** + // * Create Attribute + // * + // * @param string $collection + // * @param string $id + // * @param string $type + // * @param bool $array + // * + // * @return bool + // */ + // public function createAttribute(string $collection, string $id, string $type, bool $array = false): bool + // { + // $collection = $this->getDocument(self::COLLECTION_COLLECTIONS, $collection); + // return $this->adapter->createAttribute($collection, $id, $type, $array); + // } + + // /** + // * Delete Attribute + // * + // * @param string $collection + // * @param string $id + // * @param bool $array + // * + // * @return bool + // */ + // public function deleteAttribute(string $collection, string $id, bool $array): bool + // { + // return $this->adapter->deleteAttribute($this->getDocument(self::COLLECTION_COLLECTIONS, $collection), $id, $array); + // } + + // /** + // * Create Index + // * + // * @param string $collection + // * @param string $id + // * @param string $type + // * @param array $attributes + // * + // * @return bool + // */ + // public function createIndex(string $collection, string $id, string $type, array $attributes): bool + // { + // $collection = $this->getDocument(self::COLLECTION_COLLECTIONS, $collection); + // return $this->adapter->createIndex($collection, $id, $type, $attributes); + // } + + // /** + // * Delete Index + // * + // * @param string $collection + // * @param string $id + // * + // * @return bool + // */ + // public function deleteIndex(string $collection, string $id): bool + // { + // $collection = $this->getDocument(self::COLLECTION_COLLECTIONS, $collection); + // return $this->adapter->deleteIndex($collection, $id); + // } + + // /** + // * @param string $collection + // * @param string $id + // * @param bool $mock is mocked data allowed? + // * @param bool $decode + // * + // * @return Document + // */ + // public function getDocument($collection, $id, bool $mock = true, bool $decode = true) + // { + // if (\is_null($id)) { + // return new Document([]); + // } + + // if($mock === true + // && isset($this->mocks[$id])) { + // $document = $this->mocks[$id]; + // } + // else { + // $document = new Document($this->adapter->getDocument($this->getDocument(self::COLLECTION_COLLECTIONS, $collection), $id)); + // } + + // $validator = new Authorization($document, 'read'); + + // if (!$validator->isValid($document->getPermissions())) { // Check if user has read access to this document + // return new Document(); + // } + + // $document = ($decode) ? $this->decode($document) : $document; + + // return $document; + // } + + // /** + // * @param string $collection + // * @param array $data + // * @param array $unique + // * + // * @return Document|bool + // * + // * @throws AuthorizationException + // * @throws StructureException + // */ + // public function createDocument(string $collection, array $data, array $unique = []) + // { + // if(isset($data['$id'])) { + // throw new Exception('Use update method instead of create'); + // } + + // $document = new Document($data); + + // $validator = new Authorization($document, 'write'); + + // if (!$validator->isValid($document->getPermissions())) { // Check if user has write access to this document + // throw new AuthorizationException($validator->getDescription()); + // } + + // $validator = new Structure($this); + // $document = $this->encode($document); + + // if (!$validator->isValid($document)) { + // throw new StructureException($validator->getDescription()); // var_dump($validator->getDescription()); return false; + // } - $document = new Document($this->adapter->createDocument($this->getDocument(self::COLLECTION_COLLECTIONS, $collection), $document->getArrayCopy(), $unique)); + // $document = new Document($this->adapter->createDocument($this->getDocument(self::COLLECTION_COLLECTIONS, $collection), $document->getArrayCopy(), $unique)); - $document = $this->decode($document); + // $document = $this->decode($document); - return $document; - } + // return $document; + // } - /** - * @param array $collection - * @param array $id - * @param array $data - * - * @return Document|false - * - * @throws Exception - */ - public function updateDocument(string $collection, string $id, array $data) - { - if (!isset($data['$id'])) { - throw new Exception('Must define $id attribute'); - } + // /** + // * @param array $collection + // * @param array $id + // * @param array $data + // * + // * @return Document|false + // * + // * @throws Exception + // */ + // public function updateDocument(string $collection, string $id, array $data) + // { + // if (!isset($data['$id'])) { + // throw new Exception('Must define $id attribute'); + // } - $document = $this->getDocument($collection, $id); // TODO make sure user don\'t need read permission for write operations + // $document = $this->getDocument($collection, $id); // TODO make sure user don\'t need read permission for write operations - // Make sure reserved keys stay constant - $data['$id'] = $document->getId(); - $data['$collection'] = $document->getCollection(); + // // Make sure reserved keys stay constant + // $data['$id'] = $document->getId(); + // $data['$collection'] = $document->getCollection(); - $validator = new Authorization($document, 'write'); + // $validator = new Authorization($document, 'write'); - if (!$validator->isValid($document->getPermissions())) { // Check if user has write access to this document - throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false; - } + // if (!$validator->isValid($document->getPermissions())) { // Check if user has write access to this document + // throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false; + // } - $new = new Document($data); + // $new = new Document($data); - if (!$validator->isValid($new->getPermissions())) { // Check if user has write access to this document - throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false; - } + // if (!$validator->isValid($new->getPermissions())) { // Check if user has write access to this document + // throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false; + // } - $new = $this->encode($new); + // $new = $this->encode($new); - $validator = new Structure($this); + // $validator = new Structure($this); - if (!$validator->isValid($new)) { // Make sure updated structure still apply collection rules (if any) - throw new StructureException($validator->getDescription()); // var_dump($validator->getDescription()); return false; - } + // if (!$validator->isValid($new)) { // Make sure updated structure still apply collection rules (if any) + // throw new StructureException($validator->getDescription()); // var_dump($validator->getDescription()); return false; + // } - $new = new Document($this->adapter->updateDocument($this->getDocument(self::COLLECTION_COLLECTIONS, $collection), $id, $new->getArrayCopy())); + // $new = new Document($this->adapter->updateDocument($this->getDocument(self::COLLECTION_COLLECTIONS, $collection), $id, $new->getArrayCopy())); - $new = $this->decode($new); - - return $new; - } - - /** - * @param array $data - * - * @return Document|false - * - * @throws Exception - */ - public function overwriteDocument(array $data) - { - if (!isset($data['$id'])) { - throw new Exception('Must define $id attribute'); - } - - $document = $this->getDocument($data['$collection'], $data['$id']); // TODO make sure user don\'t need read permission for write operations - - $validator = new Authorization($document, 'write'); - - if (!$validator->isValid($document->getPermissions())) { // Check if user has write access to this document - throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false; - } - - $new = new Document($data); - - if (!$validator->isValid($new->getPermissions())) { // Check if user has write access to this document - throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false; - } - - $new = $this->encode($new); - - $validator = new Structure($this); - - if (!$validator->isValid($new)) { // Make sure updated structure still apply collection rules (if any) - throw new StructureException($validator->getDescription()); // var_dump($validator->getDescription()); return false; - } - - $new = new Document($this->adapter->updateDocument($this->getDocument(self::COLLECTION_COLLECTIONS, $new->getCollection()), $new->getId(), $new->getArrayCopy())); - - $new = $this->decode($new); - - return $new; - } - - /** - * @param string $collection - * @param string $id - * - * @return Document|false - * - * @throws AuthorizationException - */ - public function deleteDocument(string $collection, string $id) - { - $document = $this->getDocument($collection, $id); - - $validator = new Authorization($document, 'write'); - - if (!$validator->isValid($document->getPermissions())) { // Check if user has write access to this document - throw new AuthorizationException($validator->getDescription()); - } - - return new Document($this->adapter->deleteDocument($this->getDocument(self::COLLECTION_COLLECTIONS, $collection), $id)); - } - - /** - * @return array - */ - public function getDebug() - { - return $this->adapter->getDebug(); - } - - /** - * @return int - */ - public function getSum() - { - $debug = $this->getDebug(); - - return (isset($debug['sum'])) ? $debug['sum'] : 0; - } - - /** - * Add Attribute Filter - * - * @param string $name - * @param callable $encode - * @param callable $decode - * - * @return void - */ - static public function addFilter(string $name, callable $encode, callable $decode): void - { - self::$filters[$name] = [ - 'encode' => $encode, - 'decode' => $decode, - ]; - } - - public function encode(Document $document):Document - { - if($document->getCollection() === null) { - return $document; - } - - $collection = $this->getDocument(self::COLLECTION_COLLECTIONS, $document->getCollection(), true , false); - $rules = $collection->getAttribute('rules', []); - - foreach ($rules as $key => $rule) { - $key = $rule->getAttribute('key', null); - $type = $rule->getAttribute('type', null); - $array = $rule->getAttribute('array', false); - $filters = $rule->getAttribute('filter', []); - $value = $document->getAttribute($key, null); - - if (($value !== null)) { - if ($type === self::VAR_DOCUMENT) { - if($array) { - $list = []; - foreach ($value as $child) { - $list[] = $this->encode($child); - } - - $document->setAttribute($key, $list); - } else { - $document->setAttribute($key, $this->encode($value)); - } - } else { - foreach ($filters as $filter) { - $value = $this->encodeAttribute($filter, $value); - $document->setAttribute($key, $value); - } - } - } - } - - return $document; - } - - public function decode(Document $document):Document - { - if($document->getCollection() === null) { - return $document; - } - - $collection = $this->getDocument(self::COLLECTION_COLLECTIONS, $document->getCollection(), true , false); - $rules = $collection->getAttribute('rules', []); - - foreach ($rules as $key => $rule) { - $key = $rule->getAttribute('key', null); - $type = $rule->getAttribute('type', null); - $array = $rule->getAttribute('array', false); - $filters = $rule->getAttribute('filter', []); - $value = $document->getAttribute($key, null); - - if (($value !== null)) { - if ($type === self::VAR_DOCUMENT) { - if($array) { - $list = []; - foreach ($value as $child) { - $list[] = $this->decode($child); - } - - $document->setAttribute($key, $list); - } else { - $document->setAttribute($key, $this->decode($value)); - } - } else { - foreach (array_reverse($filters) as $filter) { - $value = $this->decodeAttribute($filter, $value); - $document->setAttribute($key, $value); - } - } - } - } - - return $document; - } - - /** - * Encode Attribute - * - * @param string $name - * @param mixed $value - */ - static protected function encodeAttribute(string $name, $value) - { - if (!isset(self::$filters[$name])) { - return $value; - throw new Exception('Filter not found'); - } - - try { - $value = self::$filters[$name]['encode']($value); - } catch (\Throwable $th) { - $value = null; - } - - return $value; - } - - /** - * Decode Attribute - * - * @param string $name - * @param mixed $value - */ - static protected function decodeAttribute(string $name, $value) - { - if (!isset(self::$filters[$name])) { - return $value; - throw new Exception('Filter not found'); - } - - try { - $value = self::$filters[$name]['decode']($value); - } catch (\Throwable $th) { - $value = null; - } - - return $value; - } + // $new = $this->decode($new); + + // return $new; + // } + + // /** + // * @param array $data + // * + // * @return Document|false + // * + // * @throws Exception + // */ + // public function overwriteDocument(array $data) + // { + // if (!isset($data['$id'])) { + // throw new Exception('Must define $id attribute'); + // } + + // $document = $this->getDocument($data['$collection'], $data['$id']); // TODO make sure user don\'t need read permission for write operations + + // $validator = new Authorization($document, 'write'); + + // if (!$validator->isValid($document->getPermissions())) { // Check if user has write access to this document + // throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false; + // } + + // $new = new Document($data); + + // if (!$validator->isValid($new->getPermissions())) { // Check if user has write access to this document + // throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false; + // } + + // $new = $this->encode($new); + + // $validator = new Structure($this); + + // if (!$validator->isValid($new)) { // Make sure updated structure still apply collection rules (if any) + // throw new StructureException($validator->getDescription()); // var_dump($validator->getDescription()); return false; + // } + + // $new = new Document($this->adapter->updateDocument($this->getDocument(self::COLLECTION_COLLECTIONS, $new->getCollection()), $new->getId(), $new->getArrayCopy())); + + // $new = $this->decode($new); + + // return $new; + // } + + // /** + // * @param string $collection + // * @param string $id + // * + // * @return Document|false + // * + // * @throws AuthorizationException + // */ + // public function deleteDocument(string $collection, string $id) + // { + // $document = $this->getDocument($collection, $id); + + // $validator = new Authorization($document, 'write'); + + // if (!$validator->isValid($document->getPermissions())) { // Check if user has write access to this document + // throw new AuthorizationException($validator->getDescription()); + // } + + // return new Document($this->adapter->deleteDocument($this->getDocument(self::COLLECTION_COLLECTIONS, $collection), $id)); + // } + + // /** + // * @return array + // */ + // public function getDebug() + // { + // return $this->adapter->getDebug(); + // } + + // /** + // * @return int + // */ + // public function getSum() + // { + // $debug = $this->getDebug(); + + // return (isset($debug['sum'])) ? $debug['sum'] : 0; + // } + + // /** + // * Add Attribute Filter + // * + // * @param string $name + // * @param callable $encode + // * @param callable $decode + // * + // * @return void + // */ + // static public function addFilter(string $name, callable $encode, callable $decode): void + // { + // self::$filters[$name] = [ + // 'encode' => $encode, + // 'decode' => $decode, + // ]; + // } + + // public function encode(Document $document):Document + // { + // if($document->getCollection() === null) { + // return $document; + // } + + // $collection = $this->getDocument(self::COLLECTION_COLLECTIONS, $document->getCollection(), true , false); + // $rules = $collection->getAttribute('rules', []); + + // foreach ($rules as $key => $rule) { + // $key = $rule->getAttribute('key', null); + // $type = $rule->getAttribute('type', null); + // $array = $rule->getAttribute('array', false); + // $filters = $rule->getAttribute('filter', []); + // $value = $document->getAttribute($key, null); + + // if (($value !== null)) { + // if ($type === self::VAR_DOCUMENT) { + // if($array) { + // $list = []; + // foreach ($value as $child) { + // $list[] = $this->encode($child); + // } + + // $document->setAttribute($key, $list); + // } else { + // $document->setAttribute($key, $this->encode($value)); + // } + // } else { + // foreach ($filters as $filter) { + // $value = $this->encodeAttribute($filter, $value); + // $document->setAttribute($key, $value); + // } + // } + // } + // } + + // return $document; + // } + + // public function decode(Document $document):Document + // { + // if($document->getCollection() === null) { + // return $document; + // } + + // $collection = $this->getDocument(self::COLLECTION_COLLECTIONS, $document->getCollection(), true , false); + // $rules = $collection->getAttribute('rules', []); + + // foreach ($rules as $key => $rule) { + // $key = $rule->getAttribute('key', null); + // $type = $rule->getAttribute('type', null); + // $array = $rule->getAttribute('array', false); + // $filters = $rule->getAttribute('filter', []); + // $value = $document->getAttribute($key, null); + + // if (($value !== null)) { + // if ($type === self::VAR_DOCUMENT) { + // if($array) { + // $list = []; + // foreach ($value as $child) { + // $list[] = $this->decode($child); + // } + + // $document->setAttribute($key, $list); + // } else { + // $document->setAttribute($key, $this->decode($value)); + // } + // } else { + // foreach (array_reverse($filters) as $filter) { + // $value = $this->decodeAttribute($filter, $value); + // $document->setAttribute($key, $value); + // } + // } + // } + // } + + // return $document; + // } + + // /** + // * Encode Attribute + // * + // * @param string $name + // * @param mixed $value + // */ + // static protected function encodeAttribute(string $name, $value) + // { + // if (!isset(self::$filters[$name])) { + // return $value; + // throw new Exception('Filter not found'); + // } + + // try { + // $value = self::$filters[$name]['encode']($value); + // } catch (\Throwable $th) { + // $value = null; + // } + + // return $value; + // } + + // /** + // * Decode Attribute + // * + // * @param string $name + // * @param mixed $value + // */ + // static protected function decodeAttribute(string $name, $value) + // { + // if (!isset(self::$filters[$name])) { + // return $value; + // throw new Exception('Filter not found'); + // } + + // try { + // $value = self::$filters[$name]['decode']($value); + // } catch (\Throwable $th) { + // $value = null; + // } + + // return $value; + // } } diff --git a/src/Database/Document.php b/src/Database/Document.php index 6c6ee3545..fcc5c742a 100644 --- a/src/Database/Document.php +++ b/src/Database/Document.php @@ -21,11 +21,11 @@ class Document extends ArrayObject * @param int $flags * @param string $iterator_class */ - public function __construct($input = [], $flags = 0, $iterator_class = 'ArrayIterator') + public function __construct(array $input = []) { foreach ($input as $key => &$value) { if (\is_array($value)) { - if ((isset($value['$id']) || isset($value['$collection'])) && (!$value instanceof self)) { + if ((isset($value['$id']) || isset($value['$collection']))) { $input[$key] = new self($value); } else { foreach ($value as $childKey => $child) { @@ -37,11 +37,11 @@ public function __construct($input = [], $flags = 0, $iterator_class = 'ArrayIte } } - parent::__construct($input, $flags, $iterator_class); + parent::__construct($input); } /** - * @return string|null + * @return string */ public function getId(): string { @@ -146,9 +146,9 @@ public function removeAttribute(string $key): self * * @param string $key * @param mixed $value - * @param array|null $scope + * @param mixed $scope * - * @return Document|Document[]|mixed|null|array + * @return mixed */ public function search(string $key, $value, $scope = null) { diff --git a/src/Database/Validator/Authorization.php b/src/Database/Validator/Authorization.php index 31334f5a2..0dbb2543c 100644 --- a/src/Database/Validator/Authorization.php +++ b/src/Database/Validator/Authorization.php @@ -148,9 +148,10 @@ public static function isRole(string $role): bool * This will be used for the * value set on the self::reset() method * + * @param bool $status * @return void */ - public static function setDefaultStatus($status): void + public static function setDefaultStatus(bool $status): void { self::$statusDefault = $status; self::$status = $status; diff --git a/src/Database/Validator/Collection.php b/src/Database/Validator/Collection.php index 4450df6fa..f39cae13a 100644 --- a/src/Database/Validator/Collection.php +++ b/src/Database/Validator/Collection.php @@ -1,62 +1,62 @@ collections = $collections; - $this->merge = $merge; - - return parent::__construct($database); - } - - /** - * Is valid. - * - * Returns true if valid or false if not. - * - * @param mixed $document - * - * @return bool - */ - public function isValid($document) - { - $document = new Document( - \array_merge($this->merge, ($document instanceof Document) ? $document->getArrayCopy() : $document) - ); - - if (\is_null($document->getCollection())) { - $this->message = 'Missing collection attribute $collection'; - - return false; - } - - if (!\in_array($document->getCollection(), $this->collections)) { - $this->message = 'Collection is not allowed'; - - return false; - } - - return parent::isValid($document); - } -} +// namespace Utopia\Database\Validator; + +// use Utopia\Database\Database; +// use Utopia\Database\Document; + +// class Collection extends Structure +// { +// /** +// * @var array +// */ +// protected $collections = []; + +// /** +// * @var array +// */ +// protected $merge = []; + +// /** +// * @param Database $database +// * @param array $collections +// * @param array $merge +// */ +// public function __construct(Database $database, array $collections, array $merge = []) +// { +// $this->collections = $collections; +// $this->merge = $merge; + +// return parent::__construct($database); +// } + +// /** +// * Is valid. +// * +// * Returns true if valid or false if not. +// * +// * @param mixed $document +// * +// * @return bool +// */ +// public function isValid($document) +// { +// $document = new Document( +// \array_merge($this->merge, ($document instanceof Document) ? $document->getArrayCopy() : $document) +// ); + +// if (\is_null($document->getCollection())) { +// $this->message = 'Missing collection attribute $collection'; + +// return false; +// } + +// if (!\in_array($document->getCollection(), $this->collections)) { +// $this->message = 'Collection is not allowed'; + +// return false; +// } + +// return parent::isValid($document); +// } +// } diff --git a/src/Database/Validator/Structure.php b/src/Database/Validator/Structure.php index 71857db6e..89a658f97 100644 --- a/src/Database/Validator/Structure.php +++ b/src/Database/Validator/Structure.php @@ -1,294 +1,294 @@ '$id', - '$collection' => Database::COLLECTION_RULES, - 'key' => '$id', - 'type' => Database::VAR_KEY, - 'default' => null, - 'required' => false, - 'array' => false, - ], - [ - 'label' => '$collection', - '$collection' => Database::COLLECTION_RULES, - 'key' => '$collection', - 'type' => Database::VAR_KEY, - 'default' => null, - 'required' => true, - 'array' => false, - ], - [ - 'label' => '$permissions', - '$collection' => Database::COLLECTION_RULES, - 'key' => '$permissions', - 'type' => 'permissions', - 'default' => null, - 'required' => true, - 'array' => false, - ], - [ - 'label' => '$createdAt', - '$collection' => Database::COLLECTION_RULES, - 'key' => '$createdAt', - 'type' => Database::VAR_INTEGER, - 'default' => null, - 'required' => false, - 'array' => false, - ], - [ - 'label' => '$updatedAt', - '$collection' => Database::COLLECTION_RULES, - 'key' => '$updatedAt', - 'type' => Database::VAR_INTEGER, - 'default' => null, - 'required' => false, - 'array' => false, - ], - ]; - - /** - * @var string - */ - protected $message = 'General Error'; - - /** - * Structure constructor. - * - * @param Database $database - */ - public function __construct(Database $database) - { - $this->database = $database; - } - - /** - * Get Description. - * - * Returns validator description - * - * @return string - */ - public function getDescription() - { - return 'Invalid document structure: '.$this->message; - } - - /** - * Is valid. - * - * Returns true if valid or false if not. - * - * @param mixed $document - * - * @return bool - */ - public function isValid($document) - { - $document = (\is_array($document)) ? new Document($document) : $document; - - $this->id = $document->getId(); - - if (\is_null($document->getCollection())) { - $this->message = 'Missing collection attribute $collection'; - - return false; - } - - $collection = $this->getCollection(Database::COLLECTION_COLLECTIONS, $document->getCollection()); - - if (\is_null($collection->getId()) || Database::COLLECTION_COLLECTIONS != $collection->getCollection()) { - $this->message = 'Collection "'.$collection->getCollection().'" not found'; - return false; - } - - $array = $document->getArrayCopy(); - $rules = \array_merge($this->rules, $collection->getAttribute('rules', [])); - - foreach ($rules as $rule) { // Check all required keys are set - if (isset($rule['key']) && !isset($array[$rule['key']]) - && isset($rule['required']) && true == $rule['required']) { - $this->message = 'Missing required key "'.$rule['key'].'"'; - - return false; - } - } - - foreach ($array as $key => $value) { - $rule = $collection->search('key', $key, $rules); +// namespace Utopia\Database\Validator; + +// use Utopia\Database\Database; +// use Utopia\Database\Document; +// use Utopia\Validator; + +// class Structure extends Validator +// { +// // const RULE_TYPE_ID = 'id'; +// // const RULE_TYPE_KEY = 'key'; +// // const RULE_TYPE_TEXT = 'text'; +// // const RULE_TYPE_INTEGER = 'integer'; +// // const RULE_TYPE_FLOAT = 'float'; +// // const RULE_TYPE_NUMERIC = 'numeric'; +// // const RULE_TYPE_BOOLEAN = 'boolean'; +// // const RULE_TYPE_EMAIL = 'email'; +// // const RULE_TYPE_URL = 'url'; +// // const RULE_TYPE_DOCUMENT = 'document'; +// const RULE_TYPE_PERMISSIONS = 'permissions'; +// const RULE_TYPE_MARKDOWN = 'markdown'; +// const RULE_TYPE_IP = 'ip'; +// const RULE_TYPE_DOCUMENTID = 'documentId'; +// const RULE_TYPE_FILEID = 'fileId'; + +// /** +// * @var Database +// */ +// protected $database; + +// /** +// * @var string +// */ +// protected $id = ''; + +// /** +// * Basic rules to apply on all documents. +// * +// * @var array +// */ +// protected $rules = [ +// [ +// 'label' => '$id', +// '$collection' => Database::COLLECTION_RULES, +// 'key' => '$id', +// 'type' => Database::VAR_KEY, +// 'default' => null, +// 'required' => false, +// 'array' => false, +// ], +// [ +// 'label' => '$collection', +// '$collection' => Database::COLLECTION_RULES, +// 'key' => '$collection', +// 'type' => Database::VAR_KEY, +// 'default' => null, +// 'required' => true, +// 'array' => false, +// ], +// [ +// 'label' => '$permissions', +// '$collection' => Database::COLLECTION_RULES, +// 'key' => '$permissions', +// 'type' => 'permissions', +// 'default' => null, +// 'required' => true, +// 'array' => false, +// ], +// [ +// 'label' => '$createdAt', +// '$collection' => Database::COLLECTION_RULES, +// 'key' => '$createdAt', +// 'type' => Database::VAR_INTEGER, +// 'default' => null, +// 'required' => false, +// 'array' => false, +// ], +// [ +// 'label' => '$updatedAt', +// '$collection' => Database::COLLECTION_RULES, +// 'key' => '$updatedAt', +// 'type' => Database::VAR_INTEGER, +// 'default' => null, +// 'required' => false, +// 'array' => false, +// ], +// ]; + +// /** +// * @var string +// */ +// protected $message = 'General Error'; + +// /** +// * Structure constructor. +// * +// * @param Database $database +// */ +// public function __construct(Database $database) +// { +// $this->database = $database; +// } + +// /** +// * Get Description. +// * +// * Returns validator description +// * +// * @return string +// */ +// public function getDescription() +// { +// return 'Invalid document structure: '.$this->message; +// } + +// /** +// * Is valid. +// * +// * Returns true if valid or false if not. +// * +// * @param mixed $document +// * +// * @return bool +// */ +// public function isValid($document) +// { +// $document = (\is_array($document)) ? new Document($document) : $document; + +// $this->id = $document->getId(); + +// if (\is_null($document->getCollection())) { +// $this->message = 'Missing collection attribute $collection'; + +// return false; +// } + +// $collection = $this->getCollection(Database::COLLECTION_COLLECTIONS, $document->getCollection()); + +// if (\is_null($collection->getId()) || Database::COLLECTION_COLLECTIONS != $collection->getCollection()) { +// $this->message = 'Collection "'.$collection->getCollection().'" not found'; +// return false; +// } + +// $array = $document->getArrayCopy(); +// $rules = \array_merge($this->rules, $collection->getAttribute('rules', [])); + +// foreach ($rules as $rule) { // Check all required keys are set +// if (isset($rule['key']) && !isset($array[$rule['key']]) +// && isset($rule['required']) && true == $rule['required']) { +// $this->message = 'Missing required key "'.$rule['key'].'"'; + +// return false; +// } +// } + +// foreach ($array as $key => $value) { +// $rule = $collection->search('key', $key, $rules); - if (!$rule) { - continue; - } - - $ruleType = $rule['type'] ?? ''; - $ruleRequired = $rule['required'] ?? true; - $ruleArray = $rule['array'] ?? false; - $validator = null; - - switch ($ruleType) { - case self::RULE_TYPE_PERMISSIONS: - $validator = new Permissions(); - break; - case Database::VAR_KEY: - $validator = new Key(); - break; - case Database::VAR_STRING: - case self::RULE_TYPE_MARKDOWN: - $validator = new Validator\Text(0); - break; - case Database::VAR_NUMERIC: - $validator = new Validator\Numeric(); - break; - case Database::VAR_INTEGER: - $validator = new Validator\Integer(); - break; - case Database::VAR_FLOAT: - $validator = new Validator\FloatValidator(); - break; - case Database::VAR_BOOLEAN: - $validator = new Validator\Boolean(); - break; - case Database::VAR_EMAIL: - $validator = new Validator\Email(); - break; - case Database::VAR_URL: - $validator = new Validator\URL(); - break; - case self::RULE_TYPE_IP: - $validator = new Validator\IP(); - break; - case Database::VAR_IPV4: - $validator = new Validator\IP(Validator\IP::V4); - break; - case Database::VAR_IPV6: - $validator = new Validator\IP(Validator\IP::V6); - break; - case Database::VAR_DOCUMENT: - $validator = new Collection($this->database, (isset($rule['list'])) ? $rule['list'] : []); - $value = $document->getAttribute($key); - break; - case self::RULE_TYPE_DOCUMENTID: - $validator = new DocumentId($this->database, (isset($rule['list']) && isset($rule['list'][0])) ? $rule['list'][0] : ''); - $value = $document->getAttribute($key); - break; - case self::RULE_TYPE_FILEID: - $validator = new DocumentId($this->database, Database::COLLECTION_FILES); - $value = $document->getAttribute($key); - break; - } - - if (empty($validator)) { // Error creating validator for property - $this->message = 'Unknown rule type "'.$ruleType.'" for property "'.\htmlspecialchars($key, ENT_QUOTES, 'UTF-8').'"'; - - if (empty($ruleType)) { - $this->message = 'Unknown property "'.$key.'" type'. - '. Make sure to follow '.\strtolower($collection->getAttribute('name', 'unknown')).' collection structure'; - } - - return false; - } - - if ($ruleRequired && ('' === $value || null === $value)) { - $this->message = 'Required property "'.$key.'" has no value'; - - return false; - } - - if (!$ruleRequired && empty($value)) { - unset($array[$key]); - unset($rule); - - continue; - } - - if ($ruleArray) { // Array of values validation - if (!\is_array($value)) { - $this->message = 'Property "'.$key.'" must be an array'; - - return false; - } - - // TODO add is required check here - - foreach ($value as $node) { - if (!$validator->isValid($node)) { // Check if property is valid, if not required can also be empty - $this->message = 'Property "'.$key.'" has invalid input. '.$validator->getDescription(); - - return false; - } - } - } else { // Single value validation - if ((!$validator->isValid($value)) && !('' === $value && !$ruleRequired)) { // Error when value is not valid, and is not optional and empty - $this->message = 'Property "'.$key.'" has invalid input. '.$validator->getDescription(); - - return false; - } - } - - unset($array[$key]); - unset($rule); - } - - if (!empty($array)) { // No fields should be left unvalidated - $this->message = 'Unknown properties are not allowed ('.\implode(', ', \array_keys($array)).') for this collection'. - '. Make sure to follow '.\strtolower($collection->getAttribute('name', 'unknown')).' collection structure'; - - return false; - } - - return true; - } - - /** - * Get Collection - * - * Get Collection by unique ID - * - * @return Document - */ - protected function getCollection(string $collection, string $id): Document - { - return $this->database->getDocument($collection, $id); - } -} +// if (!$rule) { +// continue; +// } + +// $ruleType = $rule['type'] ?? ''; +// $ruleRequired = $rule['required'] ?? true; +// $ruleArray = $rule['array'] ?? false; +// $validator = null; + +// switch ($ruleType) { +// case self::RULE_TYPE_PERMISSIONS: +// $validator = new Permissions(); +// break; +// case Database::VAR_KEY: +// $validator = new Key(); +// break; +// case Database::VAR_STRING: +// case self::RULE_TYPE_MARKDOWN: +// $validator = new Validator\Text(0); +// break; +// case Database::VAR_NUMERIC: +// $validator = new Validator\Numeric(); +// break; +// case Database::VAR_INTEGER: +// $validator = new Validator\Integer(); +// break; +// case Database::VAR_FLOAT: +// $validator = new Validator\FloatValidator(); +// break; +// case Database::VAR_BOOLEAN: +// $validator = new Validator\Boolean(); +// break; +// case Database::VAR_EMAIL: +// $validator = new Validator\Email(); +// break; +// case Database::VAR_URL: +// $validator = new Validator\URL(); +// break; +// case self::RULE_TYPE_IP: +// $validator = new Validator\IP(); +// break; +// case Database::VAR_IPV4: +// $validator = new Validator\IP(Validator\IP::V4); +// break; +// case Database::VAR_IPV6: +// $validator = new Validator\IP(Validator\IP::V6); +// break; +// case Database::VAR_DOCUMENT: +// $validator = new Collection($this->database, (isset($rule['list'])) ? $rule['list'] : []); +// $value = $document->getAttribute($key); +// break; +// case self::RULE_TYPE_DOCUMENTID: +// $validator = new DocumentId($this->database, (isset($rule['list']) && isset($rule['list'][0])) ? $rule['list'][0] : ''); +// $value = $document->getAttribute($key); +// break; +// case self::RULE_TYPE_FILEID: +// $validator = new DocumentId($this->database, Database::COLLECTION_FILES); +// $value = $document->getAttribute($key); +// break; +// } + +// if (empty($validator)) { // Error creating validator for property +// $this->message = 'Unknown rule type "'.$ruleType.'" for property "'.\htmlspecialchars($key, ENT_QUOTES, 'UTF-8').'"'; + +// if (empty($ruleType)) { +// $this->message = 'Unknown property "'.$key.'" type'. +// '. Make sure to follow '.\strtolower($collection->getAttribute('name', 'unknown')).' collection structure'; +// } + +// return false; +// } + +// if ($ruleRequired && ('' === $value || null === $value)) { +// $this->message = 'Required property "'.$key.'" has no value'; + +// return false; +// } + +// if (!$ruleRequired && empty($value)) { +// unset($array[$key]); +// unset($rule); + +// continue; +// } + +// if ($ruleArray) { // Array of values validation +// if (!\is_array($value)) { +// $this->message = 'Property "'.$key.'" must be an array'; + +// return false; +// } + +// // TODO add is required check here + +// foreach ($value as $node) { +// if (!$validator->isValid($node)) { // Check if property is valid, if not required can also be empty +// $this->message = 'Property "'.$key.'" has invalid input. '.$validator->getDescription(); + +// return false; +// } +// } +// } else { // Single value validation +// if ((!$validator->isValid($value)) && !('' === $value && !$ruleRequired)) { // Error when value is not valid, and is not optional and empty +// $this->message = 'Property "'.$key.'" has invalid input. '.$validator->getDescription(); + +// return false; +// } +// } + +// unset($array[$key]); +// unset($rule); +// } + +// if (!empty($array)) { // No fields should be left unvalidated +// $this->message = 'Unknown properties are not allowed ('.\implode(', ', \array_keys($array)).') for this collection'. +// '. Make sure to follow '.\strtolower($collection->getAttribute('name', 'unknown')).' collection structure'; + +// return false; +// } + +// return true; +// } + +// /** +// * Get Collection +// * +// * Get Collection by unique ID +// * +// * @return Document +// */ +// protected function getCollection(string $collection, string $id): Document +// { +// return $this->database->getDocument($collection, $id); +// } +// } From e74ab8c48a8906e53b75f17d4e1fa76849b02061 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 6 Feb 2021 10:25:19 +0200 Subject: [PATCH 024/190] Updated CI --- .travis.yml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index a5fda9f40..58a7e5fe5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,11 @@ -language: php +dist: xenial -php: -- 7.4 -- 8.0 -- nightly +arch: + - amd64 + +os: linux + +language: shell services: - docker @@ -12,7 +14,7 @@ notifications: email: - team@appwrite.io -before_script: composer install --ignore-platform-reqs +before_script: docker run --rm --interactive --tty --volume "$(pwd)":/app composer update --ignore-platform-reqs --optimize-autoloader --no-plugins --no-scripts --prefer-dist install: - docker-compose up -d From fbbb26ec699972cfd989cf419fa1c1f2861d599e Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 15 Feb 2021 23:21:07 +0200 Subject: [PATCH 025/190] Added Cockroach --- docker-compose.yml | 10 ++++++ src/Database/Adapter/Cockroach.php | 8 +++++ tests/Database/Adapter/CockroachTest.php | 46 ++++++++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 src/Database/Adapter/Cockroach.php create mode 100644 tests/Database/Adapter/CockroachTest.php diff --git a/docker-compose.yml b/docker-compose.yml index ee4aeebde..310d34bd2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,6 +22,16 @@ services: POSTGRES_USER: root POSTGRES_PASSWORD: password + cockroach: + image: cockroachdb/cockroach:v20.1.1 + container_name: cockroach + command: start --insecure + ports: + - "8704:26257" + - "8705:8080" + networks: + - database + mariadb: image: mariadb:10.5 container_name: mariadb diff --git a/src/Database/Adapter/Cockroach.php b/src/Database/Adapter/Cockroach.php new file mode 100644 index 000000000..15f9abcef --- /dev/null +++ b/src/Database/Adapter/Cockroach.php @@ -0,0 +1,8 @@ + 'SET NAMES utf8mb4', + PDO::ATTR_TIMEOUT => 3, // Seconds + PDO::ATTR_PERSISTENT => true + )); + + // Connection settings + $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); // Return arrays + $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // Handle all errors with exceptions + + $database = new Database(new Cockroach($pdo)); + $database->setNamespace('myapp_'.uniqid()); + + return self::$database = $database; + } +} \ No newline at end of file From e1c6313cb12af2c00ad33921666da9238ed5ac3c Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 16 Feb 2021 00:00:51 +0200 Subject: [PATCH 026/190] Updated cockroach version --- docker-compose.yml | 8 ++++---- tests/Database/Adapter/CockroachTest.php | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 310d34bd2..fb1bdc1ed 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,14 +23,14 @@ services: POSTGRES_PASSWORD: password cockroach: - image: cockroachdb/cockroach:v20.1.1 + image: cockroachdb/cockroach:v20.2.0 container_name: cockroach - command: start --insecure + command: start-single-node --insecure --logtostderr + networks: + - database ports: - "8704:26257" - "8705:8080" - networks: - - database mariadb: image: mariadb:10.5 diff --git a/tests/Database/Adapter/CockroachTest.php b/tests/Database/Adapter/CockroachTest.php index 33247d77c..a4a6209c5 100644 --- a/tests/Database/Adapter/CockroachTest.php +++ b/tests/Database/Adapter/CockroachTest.php @@ -25,8 +25,8 @@ static function getDatabase(): Database $dbHost = 'cockroach'; $dbPort = '26257'; - $dbUser = ''; - $dbPass = ''; + $dbUser = null; + $dbPass = null; $pdo = new PDO("pgsql:host={$dbHost};port={$dbPort};", $dbUser, $dbPass, array( PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4', From 93cca4a373565df10a39472154f4ff0680ca91a8 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 27 Feb 2021 17:12:40 +0200 Subject: [PATCH 027/190] Updated test commands --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index ce1491b4a..abcb858b7 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,19 @@ require_once __DIR__ . '/../../vendor/autoload.php'; Utopia Framework requires PHP 7.3 or later. We recommend using the latest PHP version whenever possible. +## Tests + +To run all unit tests, use the following Docker command: + +```bash +docker-compose exec tests vendor/bin/phpunit --configuration phpunit.xml tests +``` + +To run static code analysis, use the following Psalm command: + +```bash +- docker-compose exec tests vendor/bin/psalm --show-info=true +``` ## Authors **Eldad Fux** From 9c5c2c79980c5b9a7dcaece2bf0fcfc59962113c Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 7 Mar 2021 16:45:54 +0200 Subject: [PATCH 028/190] Updated types --- SPEC.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SPEC.md b/SPEC.md index ceead832e..3c2422a52 100644 --- a/SPEC.md +++ b/SPEC.md @@ -17,7 +17,8 @@ This library will support storing and fetching of all common JSON simple and com ### Simple Types * String -* Number +* Integer +* Float * Boolean * Null From f109bfdae8dcb02f25cf4ebae646bb9bd0ccc63c Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 19 Mar 2021 01:54:41 +0200 Subject: [PATCH 029/190] Added list databases method --- .phpunit.result.cache | 2 +- src/Database/Adapter.php | 11 ++- src/Database/Adapter/MariaDB.php | 21 ++++++ src/Database/Adapter/MongoDB.php | 16 +++++ src/Database/Adapter/Postgres.php | 21 ++++++ src/Database/Database.php | 14 +++- tests/Database/Adapter/CockroachTest.php | 88 ++++++++++++------------ tests/Database/Adapter/MariaDBTest.php | 12 ++-- tests/Database/Adapter/PostgresTest.php | 12 ++-- tests/Database/Base.php | 8 +++ 10 files changed, 142 insertions(+), 63 deletions(-) diff --git a/.phpunit.result.cache b/.phpunit.result.cache index b8789c5da..4e88cf4b9 100644 --- a/.phpunit.result.cache +++ b/.phpunit.result.cache @@ -1 +1 @@ -C:37:"PHPUnit\Runner\DefaultTestResultCache":4672:{a:2:{s:7:"defects";a:30:{s:42:"Utopia\Tests\AuthorizationTest::testValues";i:4;s:32:"Utopia\Tests\KeyTest::testValues";i:4;s:40:"Utopia\Tests\PermissionsTest::testValues";i:4;s:32:"Utopia\Tests\UIDTest::testValues";i:4;s:42:"Utopia\Tests\DocumentTest::testPermissions";i:4;s:33:"Utopia\Tests\DocumentTest::testId";i:4;s:41:"Utopia\Tests\DocumentTest::testCollection";i:4;s:43:"Utopia\Tests\DocumentTest::testGetAttribute";i:4;s:43:"Utopia\Tests\DocumentTest::testSetAttribute";i:3;s:37:"Utopia\Tests\DocumentTest::testSearch";i:3;s:36:"Utopia\Tests\DocumentTest::testIsSet";i:3;s:43:"Utopia\Tests\DocumentTest::testGetArrayCopy";i:3;s:54:"Utopia\Tests\Adapter\MariaDBTest::testCreateCollection";i:6;s:54:"Utopia\Tests\Adapter\MariaDBTest::testDeleteCollection";i:4;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAttribute";i:4;s:53:"Utopia\Tests\Adapter\MariaDBTest::testDeleteAttribute";i:4;s:49:"Utopia\Tests\Adapter\MariaDBTest::testCreateIndex";i:4;s:49:"Utopia\Tests\Adapter\MariaDBTest::testDeleteIndex";i:4;s:47:"Utopia\Tests\Adapter\MariaDBTest::testFindFirst";i:4;s:46:"Utopia\Tests\Adapter\MariaDBTest::testFindLast";i:4;s:44:"Utopia\Tests\Adapter\MariaDBTest::testCreate";i:4;s:44:"Utopia\Tests\Adapter\MariaDBTest::testDelete";i:4;s:45:"Utopia\Tests\Adapter\PostgresTest::testCreate";i:4;s:45:"Utopia\Tests\Adapter\PostgresTest::testDelete";i:4;s:44:"Utopia\Tests\Adapter\MongoDBTest::testCreate";i:4;s:44:"Utopia\Tests\Adapter\MongoDBTest::testDelete";i:4;s:54:"Utopia\Tests\Adapter\MongoDBTest::testCreateCollection";i:6;s:54:"Utopia\Tests\Adapter\MongoDBTest::testDeleteCollection";i:6;s:55:"Utopia\Tests\Adapter\PostgresTest::testCreateCollection";i:4;s:55:"Utopia\Tests\Adapter\PostgresTest::testDeleteCollection";i:1;}s:5:"times";a:48:{s:42:"Utopia\Tests\AuthorizationTest::testValues";d:0.003;s:32:"Utopia\Tests\KeyTest::testValues";d:0.001;s:40:"Utopia\Tests\PermissionsTest::testValues";d:0.001;s:32:"Utopia\Tests\UIDTest::testValues";d:0.001;s:33:"Utopia\Tests\DocumentTest::testId";d:0.004;s:41:"Utopia\Tests\DocumentTest::testCollection";d:0;s:42:"Utopia\Tests\DocumentTest::testPermissions";d:0;s:43:"Utopia\Tests\DocumentTest::testGetAttribute";d:0;s:43:"Utopia\Tests\DocumentTest::testSetAttribute";d:0;s:46:"Utopia\Tests\DocumentTest::testRemoveAttribute";d:0;s:37:"Utopia\Tests\DocumentTest::testSearch";d:0;s:38:"Utopia\Tests\DocumentTest::testIsEmpty";d:0;s:36:"Utopia\Tests\DocumentTest::testIsSet";d:0;s:43:"Utopia\Tests\DocumentTest::testGetArrayCopy";d:0;s:52:"Utopia\Tests\Validator\AuthorizationTest::testValues";d:0.031;s:42:"Utopia\Tests\Validator\KeyTest::testValues";d:0.003;s:50:"Utopia\Tests\Validator\PermissionsTest::testValues";d:0.006;s:42:"Utopia\Tests\Validator\UIDTest::testValues";d:0.003;s:54:"Utopia\Tests\Adapter\MariaDBTest::testCreateCollection";d:0.011;s:54:"Utopia\Tests\Adapter\MariaDBTest::testDeleteCollection";d:0.009;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAttribute";d:0.002;s:53:"Utopia\Tests\Adapter\MariaDBTest::testDeleteAttribute";d:0.002;s:49:"Utopia\Tests\Adapter\MariaDBTest::testCreateIndex";d:0.001;s:49:"Utopia\Tests\Adapter\MariaDBTest::testDeleteIndex";d:0.002;s:47:"Utopia\Tests\Adapter\MariaDBTest::testFindFirst";d:0;s:46:"Utopia\Tests\Adapter\MariaDBTest::testFindLast";d:0;s:44:"Utopia\Tests\Adapter\MariaDBTest::testCreate";d:0.038;s:44:"Utopia\Tests\Adapter\MariaDBTest::testDelete";d:0.002;s:45:"Utopia\Tests\Adapter\PostgresTest::testCreate";d:0.108;s:45:"Utopia\Tests\Adapter\PostgresTest::testDelete";d:0.017;s:48:"Utopia\Tests\Adapter\PostgresTest::testFindFirst";d:0;s:47:"Utopia\Tests\Adapter\PostgresTest::testFindLast";d:0;s:44:"Utopia\Tests\Adapter\MongoDBTest::testCreate";d:0.008;s:44:"Utopia\Tests\Adapter\MongoDBTest::testDelete";d:0.032;s:47:"Utopia\Tests\Adapter\MongoDBTest::testFindFirst";d:0;s:46:"Utopia\Tests\Adapter\MongoDBTest::testFindLast";d:0;s:54:"Utopia\Tests\Adapter\MongoDBTest::testCreateCollection";d:0.018;s:54:"Utopia\Tests\Adapter\MongoDBTest::testDeleteCollection";d:0.003;s:55:"Utopia\Tests\Adapter\PostgresTest::testCreateCollection";d:0.008;s:55:"Utopia\Tests\Adapter\PostgresTest::testDeleteCollection";d:0.004;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAndDelete";d:0.115;s:53:"Utopia\Tests\Adapter\MongoDBTest::testCreateAndDelete";d:0.058;s:54:"Utopia\Tests\Adapter\PostgresTest::testCreateAndDelete";d:0.03;s:51:"Utopia\Tests\Adapter\MySQLTest::testCreateAndDelete";d:0.028;s:52:"Utopia\Tests\Adapter\MySQLTest::testCreateCollection";d:0.04;s:52:"Utopia\Tests\Adapter\MySQLTest::testDeleteCollection";d:0.018;s:45:"Utopia\Tests\Adapter\MySQLTest::testFindFirst";d:0;s:44:"Utopia\Tests\Adapter\MySQLTest::testFindLast";d:0;}}} \ No newline at end of file +C:37:"PHPUnit\Runner\DefaultTestResultCache":6041:{a:2:{s:7:"defects";a:43:{s:42:"Utopia\Tests\AuthorizationTest::testValues";i:4;s:32:"Utopia\Tests\KeyTest::testValues";i:4;s:40:"Utopia\Tests\PermissionsTest::testValues";i:4;s:32:"Utopia\Tests\UIDTest::testValues";i:4;s:42:"Utopia\Tests\DocumentTest::testPermissions";i:4;s:33:"Utopia\Tests\DocumentTest::testId";i:4;s:41:"Utopia\Tests\DocumentTest::testCollection";i:4;s:43:"Utopia\Tests\DocumentTest::testGetAttribute";i:4;s:43:"Utopia\Tests\DocumentTest::testSetAttribute";i:3;s:37:"Utopia\Tests\DocumentTest::testSearch";i:3;s:36:"Utopia\Tests\DocumentTest::testIsSet";i:3;s:43:"Utopia\Tests\DocumentTest::testGetArrayCopy";i:3;s:54:"Utopia\Tests\Adapter\MariaDBTest::testCreateCollection";i:1;s:54:"Utopia\Tests\Adapter\MariaDBTest::testDeleteCollection";i:1;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAttribute";i:4;s:53:"Utopia\Tests\Adapter\MariaDBTest::testDeleteAttribute";i:4;s:49:"Utopia\Tests\Adapter\MariaDBTest::testCreateIndex";i:4;s:49:"Utopia\Tests\Adapter\MariaDBTest::testDeleteIndex";i:4;s:47:"Utopia\Tests\Adapter\MariaDBTest::testFindFirst";i:4;s:46:"Utopia\Tests\Adapter\MariaDBTest::testFindLast";i:4;s:44:"Utopia\Tests\Adapter\MariaDBTest::testCreate";i:4;s:44:"Utopia\Tests\Adapter\MariaDBTest::testDelete";i:4;s:45:"Utopia\Tests\Adapter\PostgresTest::testCreate";i:4;s:45:"Utopia\Tests\Adapter\PostgresTest::testDelete";i:4;s:44:"Utopia\Tests\Adapter\MongoDBTest::testCreate";i:4;s:44:"Utopia\Tests\Adapter\MongoDBTest::testDelete";i:4;s:54:"Utopia\Tests\Adapter\MongoDBTest::testCreateCollection";i:1;s:54:"Utopia\Tests\Adapter\MongoDBTest::testDeleteCollection";i:1;s:55:"Utopia\Tests\Adapter\PostgresTest::testCreateCollection";i:1;s:55:"Utopia\Tests\Adapter\PostgresTest::testDeleteCollection";i:1;s:55:"Utopia\Tests\Adapter\CockroachTest::testCreateAndDelete";i:4;s:56:"Utopia\Tests\Adapter\CockroachTest::testCreateCollection";i:1;s:56:"Utopia\Tests\Adapter\CockroachTest::testDeleteCollection";i:1;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAndDelete";i:4;s:53:"Utopia\Tests\Adapter\MongoDBTest::testCreateAndDelete";i:4;s:51:"Utopia\Tests\Adapter\MySQLTest::testCreateAndDelete";i:4;s:52:"Utopia\Tests\Adapter\MySQLTest::testCreateCollection";i:1;s:52:"Utopia\Tests\Adapter\MySQLTest::testDeleteCollection";i:1;s:54:"Utopia\Tests\Adapter\PostgresTest::testCreateAndDelete";i:4;s:42:"Utopia\Tests\Adapter\MariaDBTest::testList";i:3;s:42:"Utopia\Tests\Adapter\MongoDBTest::testList";i:3;s:40:"Utopia\Tests\Adapter\MySQLTest::testList";i:3;s:43:"Utopia\Tests\Adapter\PostgresTest::testList";i:3;}s:5:"times";a:57:{s:42:"Utopia\Tests\AuthorizationTest::testValues";d:0.003;s:32:"Utopia\Tests\KeyTest::testValues";d:0.001;s:40:"Utopia\Tests\PermissionsTest::testValues";d:0.001;s:32:"Utopia\Tests\UIDTest::testValues";d:0.001;s:33:"Utopia\Tests\DocumentTest::testId";d:0.002;s:41:"Utopia\Tests\DocumentTest::testCollection";d:0;s:42:"Utopia\Tests\DocumentTest::testPermissions";d:0;s:43:"Utopia\Tests\DocumentTest::testGetAttribute";d:0;s:43:"Utopia\Tests\DocumentTest::testSetAttribute";d:0;s:46:"Utopia\Tests\DocumentTest::testRemoveAttribute";d:0;s:37:"Utopia\Tests\DocumentTest::testSearch";d:0;s:38:"Utopia\Tests\DocumentTest::testIsEmpty";d:0;s:36:"Utopia\Tests\DocumentTest::testIsSet";d:0;s:43:"Utopia\Tests\DocumentTest::testGetArrayCopy";d:0;s:52:"Utopia\Tests\Validator\AuthorizationTest::testValues";d:0.009;s:42:"Utopia\Tests\Validator\KeyTest::testValues";d:0.004;s:50:"Utopia\Tests\Validator\PermissionsTest::testValues";d:0.005;s:42:"Utopia\Tests\Validator\UIDTest::testValues";d:0.015;s:54:"Utopia\Tests\Adapter\MariaDBTest::testCreateCollection";d:0.013;s:54:"Utopia\Tests\Adapter\MariaDBTest::testDeleteCollection";d:0.005;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAttribute";d:0.002;s:53:"Utopia\Tests\Adapter\MariaDBTest::testDeleteAttribute";d:0.002;s:49:"Utopia\Tests\Adapter\MariaDBTest::testCreateIndex";d:0.001;s:49:"Utopia\Tests\Adapter\MariaDBTest::testDeleteIndex";d:0.002;s:47:"Utopia\Tests\Adapter\MariaDBTest::testFindFirst";d:0;s:46:"Utopia\Tests\Adapter\MariaDBTest::testFindLast";d:0;s:44:"Utopia\Tests\Adapter\MariaDBTest::testCreate";d:0.038;s:44:"Utopia\Tests\Adapter\MariaDBTest::testDelete";d:0.002;s:45:"Utopia\Tests\Adapter\PostgresTest::testCreate";d:0.108;s:45:"Utopia\Tests\Adapter\PostgresTest::testDelete";d:0.017;s:48:"Utopia\Tests\Adapter\PostgresTest::testFindFirst";d:0;s:47:"Utopia\Tests\Adapter\PostgresTest::testFindLast";d:0;s:44:"Utopia\Tests\Adapter\MongoDBTest::testCreate";d:0.008;s:44:"Utopia\Tests\Adapter\MongoDBTest::testDelete";d:0.032;s:47:"Utopia\Tests\Adapter\MongoDBTest::testFindFirst";d:0;s:46:"Utopia\Tests\Adapter\MongoDBTest::testFindLast";d:0;s:54:"Utopia\Tests\Adapter\MongoDBTest::testCreateCollection";d:0.015;s:54:"Utopia\Tests\Adapter\MongoDBTest::testDeleteCollection";d:0.005;s:55:"Utopia\Tests\Adapter\PostgresTest::testCreateCollection";d:0.006;s:55:"Utopia\Tests\Adapter\PostgresTest::testDeleteCollection";d:0.002;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAndDelete";d:0.051;s:53:"Utopia\Tests\Adapter\MongoDBTest::testCreateAndDelete";d:0.053;s:54:"Utopia\Tests\Adapter\PostgresTest::testCreateAndDelete";d:0.012;s:51:"Utopia\Tests\Adapter\MySQLTest::testCreateAndDelete";d:0.021;s:52:"Utopia\Tests\Adapter\MySQLTest::testCreateCollection";d:0.025;s:52:"Utopia\Tests\Adapter\MySQLTest::testDeleteCollection";d:0.014;s:45:"Utopia\Tests\Adapter\MySQLTest::testFindFirst";d:0;s:44:"Utopia\Tests\Adapter\MySQLTest::testFindLast";d:0;s:55:"Utopia\Tests\Adapter\CockroachTest::testCreateAndDelete";d:0.01;s:56:"Utopia\Tests\Adapter\CockroachTest::testCreateCollection";d:0;s:56:"Utopia\Tests\Adapter\CockroachTest::testDeleteCollection";d:0;s:49:"Utopia\Tests\Adapter\CockroachTest::testFindFirst";d:0.001;s:48:"Utopia\Tests\Adapter\CockroachTest::testFindLast";d:0;s:42:"Utopia\Tests\Adapter\MariaDBTest::testList";d:0.007;s:42:"Utopia\Tests\Adapter\MongoDBTest::testList";d:0.004;s:40:"Utopia\Tests\Adapter\MySQLTest::testList";d:0.002;s:43:"Utopia\Tests\Adapter\PostgresTest::testList";d:0.002;}}} \ No newline at end of file diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index 6db9ac8b4..ccd8958b1 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -88,14 +88,21 @@ public function getNamespace(): string } /** - * Create Database. + * Create Database * * @return bool */ abstract public function create(): bool; /** - * Delete Database. + * List Database + * + * @return bool + */ + abstract public function list(): array; + + /** + * Delete Database * * @return bool */ diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 3349d7d15..2b9ad5d20 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -44,6 +44,27 @@ public function create(): bool ->execute(); } + /** + * List Database + * + * @return bool + */ + public function list(): array + { + $stmt = $this->getPDO() + ->prepare("SHOW DATABASES;"); + + $stmt->execute(); + + $list = []; + + foreach ($stmt->fetchAll() as $key => $value) { + $list[] = $value['Database'] ?? ''; + } + + return $list; + } + /** * Delete Database * diff --git a/src/Database/Adapter/MongoDB.php b/src/Database/Adapter/MongoDB.php index 01fb5f47a..27bb83511 100644 --- a/src/Database/Adapter/MongoDB.php +++ b/src/Database/Adapter/MongoDB.php @@ -42,6 +42,22 @@ public function create(): bool return (!!$this->client->$namespace); } + /** + * List Database + * + * @return bool + */ + public function list(): array + { + $list = []; + + foreach ($this->client->listDatabaseNames() as $key => $value) { + $list[] = $value; + } + + return $list; + } + /** * Delete Database * diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php index 5fddb8671..34a16cbdb 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -40,6 +40,27 @@ public function create(): bool ->execute(); } + /** + * List Database + * + * @return bool + */ + public function list(): array + { + $stmt = $this->getPDO() + ->prepare("SELECT datname FROM pg_database;"); + + $stmt->execute(); + + $list = []; + + foreach ($stmt->fetchAll() as $key => $value) { + $list[] = $value['datname'] ?? ''; + } + + return $list; + } + /** * Delete Database * diff --git a/src/Database/Database.php b/src/Database/Database.php index 7a0a06477..55e63dfca 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -81,7 +81,7 @@ public function getNamespace(): string } /** - * Create Database. + * Create Database * * @return bool */ @@ -91,7 +91,17 @@ public function create(): bool } /** - * Delete Database. + * List Databases + * + * @return bool + */ + public function list(): array + { + return $this->adapter->list(); + } + + /** + * Delete Database * * @return bool */ diff --git a/tests/Database/Adapter/CockroachTest.php b/tests/Database/Adapter/CockroachTest.php index a4a6209c5..3ca5c3ed3 100644 --- a/tests/Database/Adapter/CockroachTest.php +++ b/tests/Database/Adapter/CockroachTest.php @@ -1,46 +1,46 @@ 'SET NAMES utf8mb4', - PDO::ATTR_TIMEOUT => 3, // Seconds - PDO::ATTR_PERSISTENT => true - )); - - // Connection settings - $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); // Return arrays - $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // Handle all errors with exceptions - - $database = new Database(new Cockroach($pdo)); - $database->setNamespace('myapp_'.uniqid()); - - return self::$database = $database; - } -} \ No newline at end of file +// namespace Utopia\Tests\Adapter; + +// use PDO; +// use Utopia\Database\Adapter\Cockroach; +// use Utopia\Database\Database; +// use Utopia\Tests\Base; + +// class CockroachTest extends Base +// { +// /** +// * @var Database +// */ +// static $database = null; + +// /** +// * @reture Adapter +// */ +// static function getDatabase(): Database +// { +// if(!is_null(self::$database)) { +// return self::$database; +// } + +// $dbHost = 'cockroach'; +// $dbPort = '26257'; +// $dbUser = null; +// $dbPass = null; + +// $pdo = new PDO("pgsql:host={$dbHost};port={$dbPort};", $dbUser, $dbPass, array( +// PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4', +// PDO::ATTR_TIMEOUT => 3, // Seconds +// PDO::ATTR_PERSISTENT => true +// )); + +// // Connection settings +// $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); // Return arrays +// $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // Handle all errors with exceptions + +// $database = new Database(new Cockroach($pdo)); +// $database->setNamespace('myapp_'.uniqid()); + +// return self::$database = $database; +// } +// } \ No newline at end of file diff --git a/tests/Database/Adapter/MariaDBTest.php b/tests/Database/Adapter/MariaDBTest.php index 10f4d98f3..4a01997c5 100644 --- a/tests/Database/Adapter/MariaDBTest.php +++ b/tests/Database/Adapter/MariaDBTest.php @@ -28,15 +28,13 @@ static function getDatabase(): Database $dbUser = 'root'; $dbPass = 'password'; - $pdo = new PDO("mysql:host={$dbHost};port={$dbPort};charset=utf8mb4", $dbUser, $dbPass, array( + $pdo = new PDO("mysql:host={$dbHost};port={$dbPort};charset=utf8mb4", $dbUser, $dbPass, [ PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4', PDO::ATTR_TIMEOUT => 3, // Seconds - PDO::ATTR_PERSISTENT => true - )); - - // Connection settings - $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); // Return arrays - $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // Handle all errors with exceptions + PDO::ATTR_PERSISTENT => true, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + ]); $database = new Database(new MariaDB($pdo)); $database->setNamespace('myapp_'.uniqid()); diff --git a/tests/Database/Adapter/PostgresTest.php b/tests/Database/Adapter/PostgresTest.php index da70c5fa5..2e8d8d080 100644 --- a/tests/Database/Adapter/PostgresTest.php +++ b/tests/Database/Adapter/PostgresTest.php @@ -28,15 +28,13 @@ static function getDatabase(): Database $dbUser = 'root'; $dbPass = 'password'; - $pdo = new PDO("pgsql:host={$dbHost};port={$dbPort};", $dbUser, $dbPass, array( + $pdo = new PDO("pgsql:host={$dbHost};port={$dbPort};", $dbUser, $dbPass, [ PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4', PDO::ATTR_TIMEOUT => 3, // Seconds - PDO::ATTR_PERSISTENT => true - )); - - // Connection settings - $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); // Return arrays - $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // Handle all errors with exceptions + PDO::ATTR_PERSISTENT => true, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + ]); $database = new Database(new Postgres($pdo)); $database->setNamespace('myapp_'.uniqid()); diff --git a/tests/Database/Base.php b/tests/Database/Base.php index e93a34dc9..5ded63d36 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -31,6 +31,14 @@ public function testCreateAndDelete() $this->assertEquals(true, static::getDatabase()->create()); } + public function testList() + { + $list = static::getDatabase()->list(); + + $this->assertNotEmpty($list); + $this->assertIsArray($list); + } + /** * @depends testCreateAndDelete */ From ca15bc01c8a85b969c34fe521d1d91ef4028a7ab Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 19 Mar 2021 01:55:46 +0200 Subject: [PATCH 030/190] Updated README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index abcb858b7..9a3c39587 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Utopia Database -[![Build Status](https://travis-ci.org/utopia-php/abuse.svg?branch=master)](https://travis-ci.com/utopia-php/abuse) -![Total Downloads](https://img.shields.io/packagist/dt/utopia-php/abuse.svg) +[![Build Status](https://travis-ci.org/utopia-php/abuse.svg?branch=master)](https://travis-ci.com/utopia-php/database) +![Total Downloads](https://img.shields.io/packagist/dt/utopia-php/database.svg) [![Discord](https://img.shields.io/discord/564160730845151244)](https://appwrite.io/discord) Utopia framework database library is simple and lite library for managing application persistency using multiple database adapters. This library is aiming to be as simple and easy to learn and use. This library is maintained by the [Appwrite team](https://appwrite.io). @@ -39,7 +39,7 @@ docker-compose exec tests vendor/bin/phpunit --configuration phpunit.xml tests To run static code analysis, use the following Psalm command: ```bash -- docker-compose exec tests vendor/bin/psalm --show-info=true +docker-compose exec tests vendor/bin/psalm --show-info=true ``` ## Authors From 17a07f018374981e37c727ae78e9dc4122c29732 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 22 Mar 2021 10:34:33 +0200 Subject: [PATCH 031/190] POC 2 --- .phpunit.result.cache | 2 +- src/Database/Adapter.php | 188 +++++++------ src/Database/Adapter/MariaDB.php | 427 ++++++++++++++++++++++++++++-- src/Database/Adapter/MongoDB.php | 20 +- src/Database/Adapter/Postgres.php | 17 +- src/Database/Database.php | 346 ++++++++++++++++-------- src/Database/Document.php | 20 ++ tests/Database/Base.php | 225 ++++++++-------- tests/Database/DocumentTest.php | 15 ++ 9 files changed, 937 insertions(+), 323 deletions(-) diff --git a/.phpunit.result.cache b/.phpunit.result.cache index 4e88cf4b9..7f84548de 100644 --- a/.phpunit.result.cache +++ b/.phpunit.result.cache @@ -1 +1 @@ -C:37:"PHPUnit\Runner\DefaultTestResultCache":6041:{a:2:{s:7:"defects";a:43:{s:42:"Utopia\Tests\AuthorizationTest::testValues";i:4;s:32:"Utopia\Tests\KeyTest::testValues";i:4;s:40:"Utopia\Tests\PermissionsTest::testValues";i:4;s:32:"Utopia\Tests\UIDTest::testValues";i:4;s:42:"Utopia\Tests\DocumentTest::testPermissions";i:4;s:33:"Utopia\Tests\DocumentTest::testId";i:4;s:41:"Utopia\Tests\DocumentTest::testCollection";i:4;s:43:"Utopia\Tests\DocumentTest::testGetAttribute";i:4;s:43:"Utopia\Tests\DocumentTest::testSetAttribute";i:3;s:37:"Utopia\Tests\DocumentTest::testSearch";i:3;s:36:"Utopia\Tests\DocumentTest::testIsSet";i:3;s:43:"Utopia\Tests\DocumentTest::testGetArrayCopy";i:3;s:54:"Utopia\Tests\Adapter\MariaDBTest::testCreateCollection";i:1;s:54:"Utopia\Tests\Adapter\MariaDBTest::testDeleteCollection";i:1;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAttribute";i:4;s:53:"Utopia\Tests\Adapter\MariaDBTest::testDeleteAttribute";i:4;s:49:"Utopia\Tests\Adapter\MariaDBTest::testCreateIndex";i:4;s:49:"Utopia\Tests\Adapter\MariaDBTest::testDeleteIndex";i:4;s:47:"Utopia\Tests\Adapter\MariaDBTest::testFindFirst";i:4;s:46:"Utopia\Tests\Adapter\MariaDBTest::testFindLast";i:4;s:44:"Utopia\Tests\Adapter\MariaDBTest::testCreate";i:4;s:44:"Utopia\Tests\Adapter\MariaDBTest::testDelete";i:4;s:45:"Utopia\Tests\Adapter\PostgresTest::testCreate";i:4;s:45:"Utopia\Tests\Adapter\PostgresTest::testDelete";i:4;s:44:"Utopia\Tests\Adapter\MongoDBTest::testCreate";i:4;s:44:"Utopia\Tests\Adapter\MongoDBTest::testDelete";i:4;s:54:"Utopia\Tests\Adapter\MongoDBTest::testCreateCollection";i:1;s:54:"Utopia\Tests\Adapter\MongoDBTest::testDeleteCollection";i:1;s:55:"Utopia\Tests\Adapter\PostgresTest::testCreateCollection";i:1;s:55:"Utopia\Tests\Adapter\PostgresTest::testDeleteCollection";i:1;s:55:"Utopia\Tests\Adapter\CockroachTest::testCreateAndDelete";i:4;s:56:"Utopia\Tests\Adapter\CockroachTest::testCreateCollection";i:1;s:56:"Utopia\Tests\Adapter\CockroachTest::testDeleteCollection";i:1;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAndDelete";i:4;s:53:"Utopia\Tests\Adapter\MongoDBTest::testCreateAndDelete";i:4;s:51:"Utopia\Tests\Adapter\MySQLTest::testCreateAndDelete";i:4;s:52:"Utopia\Tests\Adapter\MySQLTest::testCreateCollection";i:1;s:52:"Utopia\Tests\Adapter\MySQLTest::testDeleteCollection";i:1;s:54:"Utopia\Tests\Adapter\PostgresTest::testCreateAndDelete";i:4;s:42:"Utopia\Tests\Adapter\MariaDBTest::testList";i:3;s:42:"Utopia\Tests\Adapter\MongoDBTest::testList";i:3;s:40:"Utopia\Tests\Adapter\MySQLTest::testList";i:3;s:43:"Utopia\Tests\Adapter\PostgresTest::testList";i:3;}s:5:"times";a:57:{s:42:"Utopia\Tests\AuthorizationTest::testValues";d:0.003;s:32:"Utopia\Tests\KeyTest::testValues";d:0.001;s:40:"Utopia\Tests\PermissionsTest::testValues";d:0.001;s:32:"Utopia\Tests\UIDTest::testValues";d:0.001;s:33:"Utopia\Tests\DocumentTest::testId";d:0.002;s:41:"Utopia\Tests\DocumentTest::testCollection";d:0;s:42:"Utopia\Tests\DocumentTest::testPermissions";d:0;s:43:"Utopia\Tests\DocumentTest::testGetAttribute";d:0;s:43:"Utopia\Tests\DocumentTest::testSetAttribute";d:0;s:46:"Utopia\Tests\DocumentTest::testRemoveAttribute";d:0;s:37:"Utopia\Tests\DocumentTest::testSearch";d:0;s:38:"Utopia\Tests\DocumentTest::testIsEmpty";d:0;s:36:"Utopia\Tests\DocumentTest::testIsSet";d:0;s:43:"Utopia\Tests\DocumentTest::testGetArrayCopy";d:0;s:52:"Utopia\Tests\Validator\AuthorizationTest::testValues";d:0.009;s:42:"Utopia\Tests\Validator\KeyTest::testValues";d:0.004;s:50:"Utopia\Tests\Validator\PermissionsTest::testValues";d:0.005;s:42:"Utopia\Tests\Validator\UIDTest::testValues";d:0.015;s:54:"Utopia\Tests\Adapter\MariaDBTest::testCreateCollection";d:0.013;s:54:"Utopia\Tests\Adapter\MariaDBTest::testDeleteCollection";d:0.005;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAttribute";d:0.002;s:53:"Utopia\Tests\Adapter\MariaDBTest::testDeleteAttribute";d:0.002;s:49:"Utopia\Tests\Adapter\MariaDBTest::testCreateIndex";d:0.001;s:49:"Utopia\Tests\Adapter\MariaDBTest::testDeleteIndex";d:0.002;s:47:"Utopia\Tests\Adapter\MariaDBTest::testFindFirst";d:0;s:46:"Utopia\Tests\Adapter\MariaDBTest::testFindLast";d:0;s:44:"Utopia\Tests\Adapter\MariaDBTest::testCreate";d:0.038;s:44:"Utopia\Tests\Adapter\MariaDBTest::testDelete";d:0.002;s:45:"Utopia\Tests\Adapter\PostgresTest::testCreate";d:0.108;s:45:"Utopia\Tests\Adapter\PostgresTest::testDelete";d:0.017;s:48:"Utopia\Tests\Adapter\PostgresTest::testFindFirst";d:0;s:47:"Utopia\Tests\Adapter\PostgresTest::testFindLast";d:0;s:44:"Utopia\Tests\Adapter\MongoDBTest::testCreate";d:0.008;s:44:"Utopia\Tests\Adapter\MongoDBTest::testDelete";d:0.032;s:47:"Utopia\Tests\Adapter\MongoDBTest::testFindFirst";d:0;s:46:"Utopia\Tests\Adapter\MongoDBTest::testFindLast";d:0;s:54:"Utopia\Tests\Adapter\MongoDBTest::testCreateCollection";d:0.015;s:54:"Utopia\Tests\Adapter\MongoDBTest::testDeleteCollection";d:0.005;s:55:"Utopia\Tests\Adapter\PostgresTest::testCreateCollection";d:0.006;s:55:"Utopia\Tests\Adapter\PostgresTest::testDeleteCollection";d:0.002;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAndDelete";d:0.051;s:53:"Utopia\Tests\Adapter\MongoDBTest::testCreateAndDelete";d:0.053;s:54:"Utopia\Tests\Adapter\PostgresTest::testCreateAndDelete";d:0.012;s:51:"Utopia\Tests\Adapter\MySQLTest::testCreateAndDelete";d:0.021;s:52:"Utopia\Tests\Adapter\MySQLTest::testCreateCollection";d:0.025;s:52:"Utopia\Tests\Adapter\MySQLTest::testDeleteCollection";d:0.014;s:45:"Utopia\Tests\Adapter\MySQLTest::testFindFirst";d:0;s:44:"Utopia\Tests\Adapter\MySQLTest::testFindLast";d:0;s:55:"Utopia\Tests\Adapter\CockroachTest::testCreateAndDelete";d:0.01;s:56:"Utopia\Tests\Adapter\CockroachTest::testCreateCollection";d:0;s:56:"Utopia\Tests\Adapter\CockroachTest::testDeleteCollection";d:0;s:49:"Utopia\Tests\Adapter\CockroachTest::testFindFirst";d:0.001;s:48:"Utopia\Tests\Adapter\CockroachTest::testFindLast";d:0;s:42:"Utopia\Tests\Adapter\MariaDBTest::testList";d:0.007;s:42:"Utopia\Tests\Adapter\MongoDBTest::testList";d:0.004;s:40:"Utopia\Tests\Adapter\MySQLTest::testList";d:0.002;s:43:"Utopia\Tests\Adapter\PostgresTest::testList";d:0.002;}}} \ No newline at end of file +C:37:"PHPUnit\Runner\DefaultTestResultCache":8049:{a:2:{s:7:"defects";a:56:{s:42:"Utopia\Tests\AuthorizationTest::testValues";i:4;s:32:"Utopia\Tests\KeyTest::testValues";i:4;s:40:"Utopia\Tests\PermissionsTest::testValues";i:4;s:32:"Utopia\Tests\UIDTest::testValues";i:4;s:42:"Utopia\Tests\DocumentTest::testPermissions";i:4;s:33:"Utopia\Tests\DocumentTest::testId";i:4;s:41:"Utopia\Tests\DocumentTest::testCollection";i:4;s:43:"Utopia\Tests\DocumentTest::testGetAttribute";i:4;s:43:"Utopia\Tests\DocumentTest::testSetAttribute";i:3;s:37:"Utopia\Tests\DocumentTest::testSearch";i:3;s:36:"Utopia\Tests\DocumentTest::testIsSet";i:3;s:43:"Utopia\Tests\DocumentTest::testGetArrayCopy";i:3;s:54:"Utopia\Tests\Adapter\MariaDBTest::testCreateCollection";i:1;s:54:"Utopia\Tests\Adapter\MariaDBTest::testDeleteCollection";i:1;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAttribute";i:4;s:53:"Utopia\Tests\Adapter\MariaDBTest::testDeleteAttribute";i:4;s:49:"Utopia\Tests\Adapter\MariaDBTest::testCreateIndex";i:4;s:49:"Utopia\Tests\Adapter\MariaDBTest::testDeleteIndex";i:4;s:47:"Utopia\Tests\Adapter\MariaDBTest::testFindFirst";i:4;s:46:"Utopia\Tests\Adapter\MariaDBTest::testFindLast";i:4;s:44:"Utopia\Tests\Adapter\MariaDBTest::testCreate";i:4;s:44:"Utopia\Tests\Adapter\MariaDBTest::testDelete";i:4;s:45:"Utopia\Tests\Adapter\PostgresTest::testCreate";i:4;s:45:"Utopia\Tests\Adapter\PostgresTest::testDelete";i:4;s:44:"Utopia\Tests\Adapter\MongoDBTest::testCreate";i:4;s:44:"Utopia\Tests\Adapter\MongoDBTest::testDelete";i:4;s:54:"Utopia\Tests\Adapter\MongoDBTest::testCreateCollection";i:1;s:54:"Utopia\Tests\Adapter\MongoDBTest::testDeleteCollection";i:1;s:55:"Utopia\Tests\Adapter\PostgresTest::testCreateCollection";i:1;s:55:"Utopia\Tests\Adapter\PostgresTest::testDeleteCollection";i:1;s:55:"Utopia\Tests\Adapter\CockroachTest::testCreateAndDelete";i:4;s:56:"Utopia\Tests\Adapter\CockroachTest::testCreateCollection";i:1;s:56:"Utopia\Tests\Adapter\CockroachTest::testDeleteCollection";i:1;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAndDelete";i:4;s:53:"Utopia\Tests\Adapter\MongoDBTest::testCreateAndDelete";i:4;s:51:"Utopia\Tests\Adapter\MySQLTest::testCreateAndDelete";i:4;s:52:"Utopia\Tests\Adapter\MySQLTest::testCreateCollection";i:1;s:52:"Utopia\Tests\Adapter\MySQLTest::testDeleteCollection";i:1;s:54:"Utopia\Tests\Adapter\PostgresTest::testCreateAndDelete";i:4;s:42:"Utopia\Tests\Adapter\MariaDBTest::testList";i:3;s:42:"Utopia\Tests\Adapter\MongoDBTest::testList";i:3;s:40:"Utopia\Tests\Adapter\MySQLTest::testList";i:3;s:43:"Utopia\Tests\Adapter\PostgresTest::testList";i:3;s:53:"Utopia\Tests\Adapter\MariaDBTest::testListCollections";i:3;s:53:"Utopia\Tests\Adapter\MongoDBTest::testListCollections";i:3;s:51:"Utopia\Tests\Adapter\MySQLTest::testListCollections";i:3;s:54:"Utopia\Tests\Adapter\PostgresTest::testListCollections";i:3;s:51:"Utopia\Tests\Adapter\MySQLTest::testCreateAttribute";i:4;s:59:"Utopia\Tests\Adapter\MariaDBTest::testCreateDeleteAttribute";i:4;s:55:"Utopia\Tests\Adapter\MariaDBTest::testCreateDeleteIndex";i:4;s:50:"Utopia\Tests\Adapter\MySQLTest::testCreateDocument";i:3;s:44:"Utopia\Tests\DocumentTest::testGetAttributes";i:3;s:52:"Utopia\Tests\Adapter\MariaDBTest::testCreateDocument";i:4;s:50:"Utopia\Tests\Adapter\MariaDBTest::testCreateDelete";i:4;s:60:"Utopia\Tests\Adapter\MariaDBTest::testCreateDeleteCollection";i:1;s:49:"Utopia\Tests\Adapter\MariaDBTest::testGetDocument";i:3;}s:5:"times";a:74:{s:42:"Utopia\Tests\AuthorizationTest::testValues";d:0.003;s:32:"Utopia\Tests\KeyTest::testValues";d:0.001;s:40:"Utopia\Tests\PermissionsTest::testValues";d:0.001;s:32:"Utopia\Tests\UIDTest::testValues";d:0.001;s:33:"Utopia\Tests\DocumentTest::testId";d:0.032;s:41:"Utopia\Tests\DocumentTest::testCollection";d:0;s:42:"Utopia\Tests\DocumentTest::testPermissions";d:0;s:43:"Utopia\Tests\DocumentTest::testGetAttribute";d:0;s:43:"Utopia\Tests\DocumentTest::testSetAttribute";d:0;s:46:"Utopia\Tests\DocumentTest::testRemoveAttribute";d:0;s:37:"Utopia\Tests\DocumentTest::testSearch";d:0;s:38:"Utopia\Tests\DocumentTest::testIsEmpty";d:0;s:36:"Utopia\Tests\DocumentTest::testIsSet";d:0;s:43:"Utopia\Tests\DocumentTest::testGetArrayCopy";d:0;s:52:"Utopia\Tests\Validator\AuthorizationTest::testValues";d:0.004;s:42:"Utopia\Tests\Validator\KeyTest::testValues";d:0.004;s:50:"Utopia\Tests\Validator\PermissionsTest::testValues";d:0.002;s:42:"Utopia\Tests\Validator\UIDTest::testValues";d:0.002;s:54:"Utopia\Tests\Adapter\MariaDBTest::testCreateCollection";d:0.015;s:54:"Utopia\Tests\Adapter\MariaDBTest::testDeleteCollection";d:0.005;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAttribute";d:0.117;s:53:"Utopia\Tests\Adapter\MariaDBTest::testDeleteAttribute";d:0.002;s:49:"Utopia\Tests\Adapter\MariaDBTest::testCreateIndex";d:0.001;s:49:"Utopia\Tests\Adapter\MariaDBTest::testDeleteIndex";d:0.002;s:47:"Utopia\Tests\Adapter\MariaDBTest::testFindFirst";d:0;s:46:"Utopia\Tests\Adapter\MariaDBTest::testFindLast";d:0;s:44:"Utopia\Tests\Adapter\MariaDBTest::testCreate";d:0.038;s:44:"Utopia\Tests\Adapter\MariaDBTest::testDelete";d:0.002;s:45:"Utopia\Tests\Adapter\PostgresTest::testCreate";d:0.108;s:45:"Utopia\Tests\Adapter\PostgresTest::testDelete";d:0.017;s:48:"Utopia\Tests\Adapter\PostgresTest::testFindFirst";d:0;s:47:"Utopia\Tests\Adapter\PostgresTest::testFindLast";d:0;s:44:"Utopia\Tests\Adapter\MongoDBTest::testCreate";d:0.008;s:44:"Utopia\Tests\Adapter\MongoDBTest::testDelete";d:0.032;s:47:"Utopia\Tests\Adapter\MongoDBTest::testFindFirst";d:0;s:46:"Utopia\Tests\Adapter\MongoDBTest::testFindLast";d:0;s:54:"Utopia\Tests\Adapter\MongoDBTest::testCreateCollection";d:0.016;s:54:"Utopia\Tests\Adapter\MongoDBTest::testDeleteCollection";d:0.003;s:55:"Utopia\Tests\Adapter\PostgresTest::testCreateCollection";d:0.008;s:55:"Utopia\Tests\Adapter\PostgresTest::testDeleteCollection";d:0.003;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAndDelete";d:0.311;s:53:"Utopia\Tests\Adapter\MongoDBTest::testCreateAndDelete";d:0.031;s:54:"Utopia\Tests\Adapter\PostgresTest::testCreateAndDelete";d:0.015;s:51:"Utopia\Tests\Adapter\MySQLTest::testCreateAndDelete";d:1.54;s:52:"Utopia\Tests\Adapter\MySQLTest::testCreateCollection";d:0.026;s:52:"Utopia\Tests\Adapter\MySQLTest::testDeleteCollection";d:0.016;s:45:"Utopia\Tests\Adapter\MySQLTest::testFindFirst";d:0.001;s:44:"Utopia\Tests\Adapter\MySQLTest::testFindLast";d:0;s:55:"Utopia\Tests\Adapter\CockroachTest::testCreateAndDelete";d:0.01;s:56:"Utopia\Tests\Adapter\CockroachTest::testCreateCollection";d:0;s:56:"Utopia\Tests\Adapter\CockroachTest::testDeleteCollection";d:0;s:49:"Utopia\Tests\Adapter\CockroachTest::testFindFirst";d:0.001;s:48:"Utopia\Tests\Adapter\CockroachTest::testFindLast";d:0;s:42:"Utopia\Tests\Adapter\MariaDBTest::testList";d:0.007;s:42:"Utopia\Tests\Adapter\MongoDBTest::testList";d:0.003;s:40:"Utopia\Tests\Adapter\MySQLTest::testList";d:0.003;s:43:"Utopia\Tests\Adapter\PostgresTest::testList";d:0.002;s:53:"Utopia\Tests\Adapter\MariaDBTest::testListCollections";d:0.034;s:53:"Utopia\Tests\Adapter\MongoDBTest::testListCollections";d:0.007;s:51:"Utopia\Tests\Adapter\MySQLTest::testListCollections";d:0.003;s:54:"Utopia\Tests\Adapter\PostgresTest::testListCollections";d:0.005;s:51:"Utopia\Tests\Adapter\MySQLTest::testCreateAttribute";d:0.406;s:59:"Utopia\Tests\Adapter\MariaDBTest::testCreateDeleteAttribute";d:0.117;s:55:"Utopia\Tests\Adapter\MariaDBTest::testCreateDeleteIndex";d:0.07;s:60:"Utopia\Tests\Adapter\MariaDBTest::testCreateDeleteCollection";d:0.031;s:58:"Utopia\Tests\Adapter\MySQLTest::testCreateDeleteCollection";d:0.067;s:57:"Utopia\Tests\Adapter\MySQLTest::testCreateDeleteAttribute";d:0.874;s:53:"Utopia\Tests\Adapter\MySQLTest::testCreateDeleteIndex";d:0.242;s:48:"Utopia\Tests\Adapter\MySQLTest::testCreateDelete";d:1.016;s:50:"Utopia\Tests\Adapter\MySQLTest::testCreateDocument";d:0.126;s:44:"Utopia\Tests\DocumentTest::testGetAttributes";d:0.003;s:50:"Utopia\Tests\Adapter\MariaDBTest::testCreateDelete";d:0.372;s:52:"Utopia\Tests\Adapter\MariaDBTest::testCreateDocument";d:0.056;s:49:"Utopia\Tests\Adapter\MariaDBTest::testGetDocument";d:0.007;}}} \ No newline at end of file diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index ccd8958b1..ee58b9dac 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -6,6 +6,8 @@ abstract class Adapter { + const METADATA = 'metadata'; + /** * @var string */ @@ -64,7 +66,7 @@ public function setNamespace(string $namespace): bool throw new Exception('Missing namespace'); } - $this->namespace = $this->filter($namespace); + $this->namespace = $namespace; return true; } @@ -95,9 +97,9 @@ public function getNamespace(): string abstract public function create(): bool; /** - * List Database + * List Databases * - * @return bool + * @return array */ abstract public function list(): array; @@ -117,6 +119,13 @@ abstract public function delete(): bool; */ abstract public function createCollection(string $name): bool; + /** + * List Collections + * + * @return array + */ + abstract public function listCollections(): array; + /** * Delete Collection * @@ -126,71 +135,73 @@ abstract public function createCollection(string $name): bool; */ abstract public function deleteCollection(string $name): bool; - // /** - // * Create Attribute - // * - // * @param Document $collection - // * @param string $id - // * @param string $type - // * @param bool $array - // * - // * @return bool - // */ - // abstract public function createAttribute(Document $collection, string $id, string $type, bool $array = false): bool; + /** + * Create Attribute + * + * @param string $collection + * @param string $id + * @param string $type + * @param int $size + * @param bool $array + * + * @return bool + */ + abstract public function createAttribute(string $collection, string $id, string $type, int $size, bool $signed = true, bool $array = false): bool; - // /** - // * Delete Attribute - // * - // * @param Document $collection - // * @param string $id - // * @param bool $array - // * - // * @return bool - // */ - // abstract public function deleteAttribute(Document $collection, string $id, bool $array = false): bool; + /** + * Delete Attribute + * + * @param string $collection + * @param string $id + * @param bool $array + * + * @return bool + */ + abstract public function deleteAttribute(string $collection, string $id, bool $array = false): bool; - // /** - // * Create Index - // * - // * @param Document $collection - // * @param string $id - // * @param string $type - // * @param array $attributes - // * - // * @return bool - // */ - // abstract public function createIndex(Document $collection, string $id, string $type, array $attributes): bool; + /** + * Create Index + * + * @param string $collection + * @param string $id + * @param string $type + * @param array $attributes + * @param array $lengths + * @param array $orders + * + * @return bool + */ + abstract public function createIndex(string $collection, string $id, string $type, array $attributes, array $lengths, array $orders): bool; - // /** - // * Delete Index - // * - // * @param Document $collection - // * @param string $id - // * - // * @return bool - // */ - // abstract public function deleteIndex(Document $collection, string $id): bool; + /** + * Delete Index + * + * @param string $collection + * @param string $id + * + * @return bool + */ + abstract public function deleteIndex(string $collection, string $id): bool; - // /** - // * Get Document. - // * - // * @param Document $collection - // * @param string $id - // * - // * @return array - // */ - // abstract public function getDocument(Document $collection, $id); + /** + * Get Document + * + * @param string $collection + * @param string $id + * + * @return array + */ + abstract public function getDocument(string $collection, string $id): Document; - // /** - // * Create Document - // * - // * @param Document $collection - // * @param array $data - // * @param array $unique - // * - // * @return array - // */ - // abstract public function createDocument(Document $collection, array $data, array $unique = []); + /** + * Create Document + * + * @param string $collection + * @param Document $document + * + * @return Document + */ + abstract public function createDocument(string $collection, Document $document): Document; // /** // * Update Document. @@ -200,7 +211,7 @@ abstract public function deleteCollection(string $name): bool; // * // * @return array // */ - // abstract public function updateDocument(Document $collection, string $id, array $data); + // abstract public function updateDocument(Document $collection, string $id, array $data): array; // /** // * Delete Node. @@ -210,7 +221,7 @@ abstract public function deleteCollection(string $name): bool; // * // * @return array // */ - // abstract public function deleteDocument(Document $collection, string $id); + // abstract public function deleteDocument(Document $collection, string $id): bool; // /** // * Find. @@ -231,6 +242,41 @@ abstract public function deleteCollection(string $name): bool; // */ // abstract public function count(array $options); + /** + * Get max STRING limit + * + * @return int + */ + abstract public function getStringLimit(): int; + + /** + * Get max INT limit + * + * @return int + */ + abstract public function getIntLimit(): int; + + /** + * Is index supported? + * + * @return bool + */ + abstract public function getIndexSupport(): bool; + + /** + * Is unique index supported? + * + * @return bool + */ + abstract public function getUniqueIndexSupport(): bool; + + /** + * Is fulltext index supported? + * + * @return bool + */ + abstract public function getFulltextIndexSupport(): bool; + /** * Filter Keys * @@ -239,7 +285,7 @@ abstract public function deleteCollection(string $name): bool; */ public function filter(string $value):string { - $value = preg_replace("/[^A-Za-z0-9 _]/", '', $value); + $value = preg_replace("/[^A-Za-z0-9]_/", '', $value); if(\is_null($value)) { throw new Exception('Failed to filter key'); @@ -247,14 +293,4 @@ public function filter(string $value):string return $value; } - - /** - * Get Unique ID. - * - * @return string - */ - public function getId(): string - { - return \uniqid(); - } } diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 2b9ad5d20..45cb70956 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -5,6 +5,8 @@ use PDO; use Exception; use Utopia\Database\Adapter; +use Utopia\Database\Database; +use Utopia\Database\Document; class MariaDB extends Adapter { @@ -45,23 +47,13 @@ public function create(): bool } /** - * List Database + * List Databases * - * @return bool + * @return array */ public function list(): array { - $stmt = $this->getPDO() - ->prepare("SHOW DATABASES;"); - - $stmt->execute(); - $list = []; - - foreach ($stmt->fetchAll() as $key => $value) { - $list[] = $value['Database'] ?? ''; - } - return $list; } @@ -87,19 +79,42 @@ public function delete(): bool */ public function createCollection(string $name): bool { - $name = $this->filter($name).'_documents'; + $name = $this->filter($name); + + $this->getPDO() + ->prepare("CREATE TABLE IF NOT EXISTS {$this->getNamespace()}.{$name}_permissions ( + `_id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `_uid` CHAR(13) NOT NULL, + `_action` CHAR(128) NOT NULL, + `_role` CHAR(128) NOT NULL, + PRIMARY KEY (`_id`), + INDEX `_index1` (`_uid`), + INDEX `_index2` (`_action` ASC, `_role` ASC) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;") + ->execute(); return $this->getPDO() ->prepare("CREATE TABLE IF NOT EXISTS {$this->getNamespace()}.{$name} ( `_id` int(11) unsigned NOT NULL AUTO_INCREMENT, - `_uid` varchar(45) NOT NULL, - -- 'custom2' text() DEFAULT NULL, + `_uid` CHAR(13) NOT NULL, PRIMARY KEY (`_id`), UNIQUE KEY `_index1` (`_uid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;") ->execute(); } + /** + * List Collections + * + * @return array + */ + public function listCollections(): array + { + $list = []; + + return $list; + } + /** * Delete Collection * @@ -108,13 +123,393 @@ public function createCollection(string $name): bool */ public function deleteCollection(string $name): bool { - $name = $this->filter($name).'_documents'; + $name = $this->filter($name); + + $this->getPDO() + ->prepare("DROP TABLE {$this->getNamespace()}.{$name}_permissions;") + ->execute(); return $this->getPDO() ->prepare("DROP TABLE {$this->getNamespace()}.{$name};") ->execute(); } + /** + * Create Attribute + * + * @param string $collection + * @param string $id + * @param string $type + * @param int $size + * @param bool $array + * + * @return bool + */ + public function createAttribute(string $collection, string $id, string $type, int $size, bool $signed = true, bool $array = false): bool + { + $name = $this->filter($collection); + $id = $this->filter($id); + $type = $this->getSQLType($type, $size, $signed); + + if($array) { + return $this->getPDO() + ->prepare("CREATE TABLE IF NOT EXISTS {$this->getNamespace()}.{$name}_arrays_{$id} ( + `_id` INT(11) unsigned NOT NULL AUTO_INCREMENT, + `_uid` CHAR(13) NOT NULL, + `_order` INT(11) unsigned NOT NULL, + `{$id}` {$type}, + PRIMARY KEY (`_id`), + INDEX `_index1` (`_uid`), + INDEX `_index2` (`_uid` ASC, `_order` ASC) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;") + ->execute(); + } + + return $this->getPDO() + ->prepare("ALTER TABLE {$this->getNamespace()}.{$name} + ADD COLUMN `{$id}` {$type};") + ->execute(); + } + + /** + * Delete Attribute + * + * @param string $collection + * @param string $id + * @param bool $array + * + * @return bool + */ + public function deleteAttribute(string $collection, string $id, bool $array = false): bool + { + $name = $this->filter($collection); + $id = $this->filter($id); + + if($array) { + return $this->getPDO() + ->prepare("DROP TABLE {$this->getNamespace()}.{$name}_arrays_{$id};") + ->execute(); + } + + return $this->getPDO() + ->prepare("ALTER TABLE {$this->getNamespace()}.{$name} + DROP COLUMN `{$id}`;") + ->execute(); + } + + /** + * Create Index + * + * @param string $collection + * @param string $id + * @param string $type + * @param int $size + * + * @return bool + */ + public function createIndex(string $collection, string $id, string $type, array $attributes, array $lengths, array $orders): bool + { + $name = $this->filter($collection); + $id = $this->filter($id); + + foreach($attributes as $key => &$attribute) { + $length = $lengths[$key] ?? ''; + $length = (empty($length)) ? '' : '('.(int)$length.')'; + $order = $orders[$key] ?? 'ASC'; + $attribute = $this->filter($attribute); + + $attribute = "`{$attribute}`{$length} {$order}"; + } + + return $this->getPDO() + ->prepare("CREATE INDEX `{$id}` ON {$this->getNamespace()}.{$name} (".implode(', ', $attributes).");") + ->execute(); + } + + /** + * Delete Index + * + * @param string $collection + * @param string $id + * + * @return bool + */ + public function deleteIndex(string $collection, string $id): bool + { + $name = $this->filter($collection); + $id = $this->filter($id); + + return $this->getPDO() + ->prepare("ALTER TABLE {$this->getNamespace()}.{$name} + DROP INDEX `{$id}`;") + ->execute(); + } + + /** + * Get Document + * + * @param string $collection + * @param string $id + * + * @return array + */ + public function getDocument(string $collection, string $id): Document + { + $name = $this->filter($collection); + + $stmt = $this->getPDO()->prepare("SELECT * FROM {$this->getNamespace()}.{$name} + WHERE _uid = :_uid + LIMIT 1; + "); + + $stmt->bindValue(':_uid', $id, PDO::PARAM_STR); + $stmt->execute(); + + $document = $stmt->fetch(); + + $document['$uid'] = $document['_uid']; + $document['$permissions'] = [Database::PERMISSION_READ => [], Database::PERMISSION_WRITE => []]; + unset($document['_id']); + unset($document['_uid']); + + $stmt = $this->getPDO()->prepare("SELECT * FROM {$this->getNamespace()}.{$name}_permissions + WHERE _uid = :_uid + LIMIT 100; + "); + + $stmt->bindValue(':_uid', $id, PDO::PARAM_STR); + $stmt->execute(); + + $permissions = $stmt->fetchAll(); + + foreach ($permissions as $permission) { + $action = $permission['_action'] ?? ''; + $role = $permission['_role'] ?? ''; + $document['$permissions'][$action][] = $role; + } + + return new Document($document); + } + + /** + * Create Document + * + * @param string $collection + * @param Document $document + * + * @return Document + */ + public function createDocument(string $collection, Document $document): Document + { + $name = $this->filter($collection); + $columns = ''; + + /** + * Insert Permissions + */ + $stmt = $this->getPDO() + ->prepare("INSERT INTO {$this->getNamespace()}.{$name}_permissions + SET _uid = :_uid, _action = :_action, _role = :_role"); + + $stmt->bindValue(':_uid', $document->getId(), PDO::PARAM_STR); + + foreach ($document->getPermissions() as $action => $roles) { + foreach ($roles as $key => $role) { + $stmt->bindValue(':_action', $action, PDO::PARAM_STR); + $stmt->bindValue(':_role', $role, PDO::PARAM_STR); + + if(!$stmt->execute()) { + throw new Exception('Failed to save permission'); + } + } + } + + $arrays = []; + $attributes = $document->getAttributes(); + + /** + * Insert Attributes + */ + foreach ($attributes as $attribute => $value) { // Parse statement + if(is_array($value)) { // arrays should be saved on dedicated table + $arrays[$attribute] = $value; + unset($attributes[$attribute]); + continue; + } + + $column = $this->filter($attribute); + $columns .= "`{$column}`" . '=:' . $column . ','; + } + + $stmt = $this->getPDO() + ->prepare("INSERT INTO {$this->getNamespace()}.{$name} + SET {$columns} _uid = :_uid"); + + $stmt->bindValue(':_uid', $document->getId(), PDO::PARAM_STR); + + foreach ($attributes as $attribute => $value) { + $attribute = $this->filter($attribute); + $stmt->bindValue(':' . $attribute, $value, $this->getPDOType($value)); + } + + $stmt->execute(); + + /** + * Insert Arrays + */ + foreach ($arrays as $attribute => $array) { + $attribute = $this->filter($attribute); + + $stmt = $this->getPDO() + ->prepare("INSERT INTO {$this->getNamespace()}.{$name}_arrays_{$attribute} + SET _uid = :_uid, _order = :_order, {$attribute} = :{$attribute}"); + + foreach ($array as $order => $value) { + $stmt->bindValue(':_uid', $document->getId(), PDO::PARAM_STR); + $stmt->bindValue(':_order', $order, PDO::PARAM_INT); + $stmt->bindValue(':' . $attribute, $value, $this->getPDOType($value)); + $stmt->execute(); + } + } + + return $document; + } + + /** + * Get max STRING limit + * + * @return int + */ + public function getStringLimit(): int + { + return 4294967295; + } + + /** + * Get max INT limit + * + * @return int + */ + public function getIntLimit(): int + { + return 4294967295; + } + + /** + * Is index supported? + * + * @return bool + */ + public function getIndexSupport(): bool + { + return true; + } + + /** + * Is unique index supported? + * + * @return bool + */ + public function getUniqueIndexSupport(): bool + { + return true; + } + + /** + * Is fulltext index supported? + * + * @return bool + */ + public function getFulltextIndexSupport(): bool + { + return true; + } + + /** + * Get SQL Type + * + * @param string $type + * @param int $size + * + * @return string + */ + protected function getSQLType(string $type, int $size, bool $signed = true): string + { + switch ($type) { + case Database::VAR_STRING: + if($size > 16777215) { + return 'LONGTEXT'; + } + + if($size > 65535) { + return 'MEDIUMTEXT'; + } + + if($size > 16383) { + return 'TEXT'; + } + + return "VARCHAR({$size})"; + break; + + case Database::VAR_INTEGER: // We don't support zerofill: https://stackoverflow.com/a/5634147/2299554 + $signed = ($signed) ? '' : ' UNSIGNED'; + return 'INT'.$signed; + break; + + case Database::VAR_FLOAT: + $signed = ($signed) ? '' : ' UNSIGNED'; + return 'FLOAT'.$signed; + break; + + case Database::VAR_BOOLEAN: + return 'TINYINT(1)'; + break; + + case Database::VAR_DOCUMENT: + return 'CHAR(13)'; + break; + + default: + throw new Exception('Unknown Type'); + break; + } + } + + /** + * Get PDO Type + * + * @param string $type + * @param int $size + * + * @return string + */ + protected function getPDOType($value): string + { + switch (gettype($value)) { + case 'string': + return PDO::PARAM_STR; + break; + + case 'boolean': + return PDO::PARAM_BOOL; + break; + + case 'integer': + return PDO::PARAM_INT; + break; + + case 'float': + case 'double': + return PDO::PARAM_STR; + break; + + default: + throw new Exception('Unknown PDO Type for ' . gettype($value)); + break; + } + } + /** * @return PDO * diff --git a/src/Database/Adapter/MongoDB.php b/src/Database/Adapter/MongoDB.php index 27bb83511..357d84d7b 100644 --- a/src/Database/Adapter/MongoDB.php +++ b/src/Database/Adapter/MongoDB.php @@ -43,9 +43,9 @@ public function create(): bool } /** - * List Database + * List Databases * - * @return bool + * @return array */ public function list(): array { @@ -79,6 +79,22 @@ public function createCollection(string $name): bool return (!!$this->getDatabase()->createCollection($name)); } + /** + * List Collections + * + * @return array + */ + public function listCollections(): array + { + $list = []; + + foreach ($this->getDatabase()->listCollectionNames() as $key => $value) { + $list[] = $value; + } + + return $list; + } + /** * Delete Collection * diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php index 34a16cbdb..d63ffaa90 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -4,6 +4,7 @@ use PDO; use Exception; +use phpDocumentor\Reflection\DocBlock\Tags\Var_; use Utopia\Database\Adapter; class Postgres extends Adapter @@ -24,7 +25,6 @@ public function __construct(PDO $pdo) { $this->pdo = $pdo; } - /** * Create Database @@ -41,9 +41,9 @@ public function create(): bool } /** - * List Database + * List Databases * - * @return bool + * @return array */ public function list(): array { @@ -88,11 +88,20 @@ public function createCollection(string $name): bool return $this->getPDO() ->prepare("CREATE TABLE {$this->getNamespace()}.{$name}( _id INT PRIMARY KEY NOT NULL, - _uid CHAR(50) NOT NULL + _uid CHAR(13) NOT NULL );") ->execute(); } + /** + * List Collections + * + * @return array + */ + public function listCollections(): array + { + } + /** * Delete Collection * diff --git a/src/Database/Database.php b/src/Database/Database.php index 55e63dfca..f350d6566 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -3,20 +3,20 @@ namespace Utopia\Database; use Exception; -// use Utopia\Database\Validator\Authorization; -// use Utopia\Database\Validator\Structure; -// use Utopia\Database\Exception\Authorization as AuthorizationException; -// use Utopia\Database\Exception\Structure as StructureException; +use Utopia\Database\Validator\Authorization; +use Utopia\Database\Validator\Structure; +use Utopia\Database\Exception\Authorization as AuthorizationException; +use Utopia\Database\Exception\Structure as StructureException; class Database { + const METADATA = 'metadata'; + // Simple Types - const VAR_STRING = 'text'; - const VAR_NUMBER = 'integer'; + const VAR_STRING = 'string'; + const VAR_INTEGER = 'integer'; + const VAR_FLOAT = 'float'; const VAR_BOOLEAN = 'boolean'; - const VAR_NULL = 'null'; - const VAR_ARRAY = 'array'; - const VAR_OBJECT = 'object'; // Relationships Types const VAR_DOCUMENT = 'document'; @@ -27,6 +27,14 @@ class Database const INDEX_UNIQUE = 'unique'; const INDEX_SPATIAL = 'spatial'; + // Orders + const ORDER_ASC = 'ASC'; + const ORDER_DESC = 'DESC'; + + // Permissions + const PERMISSION_READ = 'read'; + const PERMISSION_WRITE = 'write'; + // Collections const COLLECTION_COLLECTIONS = 'collections'; @@ -87,13 +95,38 @@ public function getNamespace(): string */ public function create(): bool { - return $this->adapter->create(); + $this->adapter->create(); + + $this->createCollection(self::METADATA.'_collections'); + $this->createAttribute(self::METADATA.'_collections', 'name', self::VAR_STRING, 128); + $this->createIndex(self::METADATA.'_collections', '_key_1', self::INDEX_UNIQUE, ['name']); + + $this->createCollection(self::METADATA.'_collections_attrubutes'); + $this->createAttribute(self::METADATA.'_collections_attrubutes', 'collection', self::VAR_STRING, 128); + $this->createAttribute(self::METADATA.'_collections_attrubutes', 'id', self::VAR_STRING, 128); + $this->createAttribute(self::METADATA.'_collections_attrubutes', 'type', self::VAR_STRING, 128); + $this->createAttribute(self::METADATA.'_collections_attrubutes', 'size', self::VAR_INTEGER, 0, false); + $this->createAttribute(self::METADATA.'_collections_attrubutes', 'array', self::VAR_BOOLEAN, 0); + $this->createIndex(self::METADATA.'_collections_attrubutes', '_key_1', self::INDEX_KEY, ['collection']); + $this->createIndex(self::METADATA.'_collections_attrubutes', '_key_2', self::INDEX_KEY, ['id']); + + $this->createCollection(self::METADATA.'_collections_indexes'); + $this->createAttribute(self::METADATA.'_collections_indexes', 'collection', self::VAR_STRING, 128); + $this->createAttribute(self::METADATA.'_collections_indexes', 'id', self::VAR_STRING, 128); + $this->createAttribute(self::METADATA.'_collections_indexes', 'type', self::VAR_STRING, 128); + $this->createAttribute(self::METADATA.'_collections_indexes', 'attributes', self::VAR_STRING, 128, true, true); + $this->createAttribute(self::METADATA.'_collections_indexes', 'lengths', self::VAR_INTEGER, 11, true, true); + $this->createAttribute(self::METADATA.'_collections_indexes', 'orders', self::VAR_STRING, 4, true, true); + $this->createIndex(self::METADATA.'_collections_indexes', '_key_1', self::INDEX_KEY, ['collection']); + $this->createIndex(self::METADATA.'_collections_indexes', '_key_2', self::INDEX_KEY, ['id']); + + return true; } /** * List Databases * - * @return bool + * @return array */ public function list(): array { @@ -122,6 +155,16 @@ public function createCollection(string $name): bool return $this->adapter->createCollection($name); } + /** + * List Collections + * + * @return array + */ + public function listCollections(): array + { + return $this->adapter->listCollections(); + } + /** * Delete Collection * @@ -134,6 +177,179 @@ public function deleteCollection(string $name): bool return $this->adapter->deleteCollection($name); } + /** + * Create Attribute + * + * @param string $collection + * @param string $id + * @param string $type + * @param int $size + * @param bool $array + * + * @return bool + */ + public function createAttribute(string $collection, string $id, string $type, int $size, bool $signed = true, bool $array = false): bool + { + switch ($type) { + case self::VAR_STRING: + if($size > $this->adapter->getStringLimit()) { + throw new Exception('Max size allowed for string is: '.number_format($this->adapter->getStringLimit())); + } + break; + + case self::VAR_INTEGER: + $limit = ($signed) ? $this->adapter->getIntLimit() / 2 : $this->adapter->getIntLimit(); + if($size > $limit) { + throw new Exception('Max size allowed for int is: '.number_format($limit)); + } + break; + case self::VAR_FLOAT: + case self::VAR_BOOLEAN: + break; + default: + throw new Exception('Unknown attribute type: '.$type); + break; + } + + return $this->adapter->createAttribute($collection, $id, $type, $size, $signed, $array); + } + + /** + * Delete Attribute + * + * @param string $collection + * @param string $id + * @param bool $array + * + * @return bool + */ + public function deleteAttribute(string $collection, string $id, bool $array = false): bool + { + return $this->adapter->deleteAttribute($collection, $id, $array); + } + + /** + * Create Index + * + * @param string $collection + * @param string $id + * @param string $type + * @param array $attributes + * @param array $lengths + * @param array $orders + * + * @return bool + */ + public function createIndex(string $collection, string $id, string $type, array $attributes, array $lengths = [], array $orders = []): bool + { + if(empty($attributes)) { + throw new Exception('Missing attributes'); + } + + switch ($type) { + case self::INDEX_KEY: + if(!$this->adapter->getIndexSupport()) { + throw new Exception('Key index is not supported'); + } + break; + + case self::INDEX_UNIQUE: + if(!$this->adapter->getUniqueIndexSupport()) { + throw new Exception('Unique index is not supported'); + } + break; + + case self::INDEX_FULLTEXT: + if(!$this->adapter->getUniqueIndexSupport()) { + throw new Exception('Fulltext index is not supported'); + } + break; + + default: + throw new Exception('Unknown index type: '.$type); + break; + } + + return $this->adapter->createIndex($collection, $id, $type, $attributes, $lengths, $orders); + } + + /** + * Delete Index + * + * @param string $collection + * @param string $id + * + * @return bool + */ + public function deleteIndex(string $collection, string $id): bool + { + return $this->adapter->deleteIndex($collection, $id); + } + + /** + * Get Document + * + * @param string $collection + * @param string $id + * + * @return Document + */ + public function getDocument(string $collection, string $id): Document + { + $document = $this->adapter->getDocument($collection, $id); + + // $validator = new Authorization($document, self::PERMISSION_READ); + + // if (!$validator->isValid($document->getPermissions())) { // Check if user has read access to this document + // return new Document(); + // } + + // $document = $this->decode($document); + + return $document; + } + + /** + * Create Document + * + * @param string $collection + * @param Document $data + * + * @return Document + * + * @throws AuthorizationException + * @throws StructureException + */ + public function createDocument(string $collection, Document $document): Document + { + if(!empty($document->getId())) { + throw new Exception('Use update method instead of create'); + } + + // $validator = new Authorization($document, self::PERMISSION_WRITE); + + // if (!$validator->isValid($document->getPermissions())) { // Check if user has write access to this document + // throw new AuthorizationException($validator->getDescription()); + // } + + // $document = $this->encode($document); + // $validator = new Structure($this); + + // if (!$validator->isValid($document)) { + // throw new StructureException($validator->getDescription()); // var_dump($validator->getDescription()); return false; + // } + + $document + ->setAttribute('$id', $this->getId()) + ; + + $document = $this->adapter->createDocument($collection, $document); + + // $document = $this->decode($document); + + return $document; + } + // /** // * @param string $collection // * @param array $options @@ -202,66 +418,6 @@ public function deleteCollection(string $name): bool // return $results; // } - // /** - // * Create Attribute - // * - // * @param string $collection - // * @param string $id - // * @param string $type - // * @param bool $array - // * - // * @return bool - // */ - // public function createAttribute(string $collection, string $id, string $type, bool $array = false): bool - // { - // $collection = $this->getDocument(self::COLLECTION_COLLECTIONS, $collection); - // return $this->adapter->createAttribute($collection, $id, $type, $array); - // } - - // /** - // * Delete Attribute - // * - // * @param string $collection - // * @param string $id - // * @param bool $array - // * - // * @return bool - // */ - // public function deleteAttribute(string $collection, string $id, bool $array): bool - // { - // return $this->adapter->deleteAttribute($this->getDocument(self::COLLECTION_COLLECTIONS, $collection), $id, $array); - // } - - // /** - // * Create Index - // * - // * @param string $collection - // * @param string $id - // * @param string $type - // * @param array $attributes - // * - // * @return bool - // */ - // public function createIndex(string $collection, string $id, string $type, array $attributes): bool - // { - // $collection = $this->getDocument(self::COLLECTION_COLLECTIONS, $collection); - // return $this->adapter->createIndex($collection, $id, $type, $attributes); - // } - - // /** - // * Delete Index - // * - // * @param string $collection - // * @param string $id - // * - // * @return bool - // */ - // public function deleteIndex(string $collection, string $id): bool - // { - // $collection = $this->getDocument(self::COLLECTION_COLLECTIONS, $collection); - // return $this->adapter->deleteIndex($collection, $id); - // } - // /** // * @param string $collection // * @param string $id @@ -295,44 +451,6 @@ public function deleteCollection(string $name): bool // return $document; // } - // /** - // * @param string $collection - // * @param array $data - // * @param array $unique - // * - // * @return Document|bool - // * - // * @throws AuthorizationException - // * @throws StructureException - // */ - // public function createDocument(string $collection, array $data, array $unique = []) - // { - // if(isset($data['$id'])) { - // throw new Exception('Use update method instead of create'); - // } - - // $document = new Document($data); - - // $validator = new Authorization($document, 'write'); - - // if (!$validator->isValid($document->getPermissions())) { // Check if user has write access to this document - // throw new AuthorizationException($validator->getDescription()); - // } - - // $validator = new Structure($this); - // $document = $this->encode($document); - - // if (!$validator->isValid($document)) { - // throw new StructureException($validator->getDescription()); // var_dump($validator->getDescription()); return false; - // } - - // $document = new Document($this->adapter->createDocument($this->getDocument(self::COLLECTION_COLLECTIONS, $collection), $document->getArrayCopy(), $unique)); - - // $document = $this->decode($document); - - // return $document; - // } - // /** // * @param array $collection // * @param array $id @@ -602,4 +720,14 @@ public function deleteCollection(string $name): bool // return $value; // } + + /** + * Get 13 Chars Unique ID. + * + * @return string + */ + public function getId(): string + { + return \uniqid(); + } } diff --git a/src/Database/Document.php b/src/Database/Document.php index fcc5c742a..0e77424fe 100644 --- a/src/Database/Document.php +++ b/src/Database/Document.php @@ -64,6 +64,26 @@ public function getPermissions(): array return $this->getAttribute('$permissions', []); } + /** + * Get Document Attributes + * + * @return array + */ + public function getAttributes(): array + { + $attributes = []; + + foreach ($this as $attribute => $value) { + if(array_key_exists($attribute, ['$id' => true, '$collection' => true, '$permissions' => true])) { + continue; + } + + $attributes[$attribute] = $value; + } + + return $attributes; + } + /** * Get Attribute. * diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 5ded63d36..bf985d100 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -2,16 +2,15 @@ namespace Utopia\Tests; -use Exception; use PHPUnit\Framework\TestCase; use Utopia\Database\Database; -use Utopia\Database\Adapter; +use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; abstract class Base extends TestCase { /** - * @reture Adapter + * @return Adapter */ abstract static protected function getDatabase(): Database; @@ -24,140 +23,136 @@ public function tearDown(): void Authorization::reset(); } - public function testCreateAndDelete() + public function testCreateDelete() { $this->assertEquals(true, static::getDatabase()->create()); $this->assertEquals(true, static::getDatabase()->delete()); $this->assertEquals(true, static::getDatabase()->create()); } - public function testList() - { - $list = static::getDatabase()->list(); - - $this->assertNotEmpty($list); - $this->assertIsArray($list); - } - /** - * @depends testCreateAndDelete + * @depends testCreateDelete */ - public function testCreateCollection() + public function testCreateDeleteCollection() { $this->assertEquals(true, static::getDatabase()->createCollection('actors')); + $this->assertEquals(true, static::getDatabase()->deleteCollection('actors')); } - /** - * @depends testCreateCollection - */ - public function testDeleteCollection() + public function testCreateDeleteAttribute() { - $this->assertEquals(true, static::getDatabase()->deleteCollection('actors')); + static::getDatabase()->createCollection('attributes'); + + $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'string1', Database::VAR_STRING, 128)); + $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'string2', Database::VAR_STRING, 16383+1)); + $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'string3', Database::VAR_STRING, 65535+1)); + $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'string4', Database::VAR_STRING, 16777215+1)); + $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'integer', Database::VAR_INTEGER, 0)); + $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'float', Database::VAR_FLOAT, 0)); + $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'boolean', Database::VAR_BOOLEAN, 0)); + + // Array + $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'string', Database::VAR_STRING, 128, true, true)); + $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'integer', Database::VAR_INTEGER, 0, true, true)); + $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'float', Database::VAR_FLOAT, 0, true, true)); + $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'boolean', Database::VAR_BOOLEAN, 0, true, true)); + + // Delete + $this->assertEquals(true, static::getDatabase()->deleteAttribute('attributes', 'string1')); + $this->assertEquals(true, static::getDatabase()->deleteAttribute('attributes', 'string2')); + $this->assertEquals(true, static::getDatabase()->deleteAttribute('attributes', 'string3')); + $this->assertEquals(true, static::getDatabase()->deleteAttribute('attributes', 'string4')); + $this->assertEquals(true, static::getDatabase()->deleteAttribute('attributes', 'integer')); + $this->assertEquals(true, static::getDatabase()->deleteAttribute('attributes', 'float')); + $this->assertEquals(true, static::getDatabase()->deleteAttribute('attributes', 'boolean')); + + // Delete Array + $this->assertEquals(true, static::getDatabase()->deleteAttribute('attributes', 'string', true)); + $this->assertEquals(true, static::getDatabase()->deleteAttribute('attributes', 'integer', true)); + $this->assertEquals(true, static::getDatabase()->deleteAttribute('attributes', 'float', true)); + $this->assertEquals(true, static::getDatabase()->deleteAttribute('attributes', 'boolean', true)); + + static::getDatabase()->deleteCollection('attributes'); } - // public function testCreateAttribute() - // { - // $collection = self::$database->createDocument(Database::COLLECTION_COLLECTIONS, [ - // '$collection' => Database::COLLECTION_COLLECTIONS, - // '$permissions' => ['read' => ['*']], - // 'name' => 'Create Attribute', - // 'rules' => [], - // ]); + public function testCreateDeleteIndex() + { + static::getDatabase()->createCollection('indexes'); - // $this->assertEquals(true, self::$database->createCollection($collection->getId(), [], [])); - // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'title', Database::VAR_STRING)); - // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'description', Database::VAR_STRING)); - // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'numeric', Database::VAR_NUMBER)); - // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'integer', Database::VAR_NUMBER)); - // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'float', Database::VAR_NUMBER)); - // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'boolean', Database::VAR_BOOLEAN)); - // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'document', Database::VAR_DOCUMENT)); - // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'email', Database::VAR_STRING)); - // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'url', Database::VAR_STRING)); - // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'ipv4', Database::VAR_STRING)); - // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'ipv6', Database::VAR_STRING)); - // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'key', Database::VAR_STRING)); + $this->assertEquals(true, static::getDatabase()->createAttribute('indexes', 'string', Database::VAR_STRING, 128)); + $this->assertEquals(true, static::getDatabase()->createAttribute('indexes', 'integer', Database::VAR_INTEGER, 0)); + $this->assertEquals(true, static::getDatabase()->createAttribute('indexes', 'float', Database::VAR_FLOAT, 0)); + $this->assertEquals(true, static::getDatabase()->createAttribute('indexes', 'boolean', Database::VAR_BOOLEAN, 0)); - // // arrays - // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'titles', Database::VAR_STRING, true)); - // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'descriptions', Database::VAR_STRING, true)); - // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'numerics', Database::VAR_NUMBER, true)); - // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'integers', Database::VAR_NUMBER, true)); - // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'floats', Database::VAR_NUMBER, true)); - // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'booleans', Database::VAR_BOOLEAN, true)); - // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'documents', Database::VAR_DOCUMENT, true)); - // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'emails', Database::VAR_STRING, true)); - // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'urls', Database::VAR_STRING, true)); - // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'ipv4s', Database::VAR_STRING, true)); - // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'ipv6s', Database::VAR_STRING, true)); - // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'keys', Database::VAR_STRING, true)); - // } - - // public function testDeleteAttribute() - // { - // $collection = self::$database->createDocument(Database::COLLECTION_COLLECTIONS, [ - // '$collection' => Database::COLLECTION_COLLECTIONS, - // '$permissions' => ['read' => ['*']], - // 'name' => 'Delete Attribute', - // 'rules' => [], - // ]); - - // $this->assertEquals(true, self::$database->createCollection($collection->getId(), [], [])); + // Indexes + $this->assertEquals(true, static::getDatabase()->createIndex('indexes', 'index1', Database::INDEX_KEY, ['string', 'integer'], [128], [Database::ORDER_ASC])); + $this->assertEquals(true, static::getDatabase()->createIndex('indexes', 'index2', Database::INDEX_KEY, ['float', 'integer'], [], [Database::ORDER_ASC, Database::ORDER_DESC])); + $this->assertEquals(true, static::getDatabase()->createIndex('indexes', 'index3', Database::INDEX_KEY, ['integer', 'boolean'], [], [Database::ORDER_ASC, Database::ORDER_DESC, Database::ORDER_DESC])); - // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'title', Database::VAR_STRING)); - // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'description', Database::VAR_STRING)); - // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'value', Database::VAR_NUMBER)); - - // $this->assertEquals(true, self::$database->deleteAttribute($collection->getId(), 'title', false)); - // $this->assertEquals(true, self::$database->deleteAttribute($collection->getId(), 'description', false)); - // $this->assertEquals(true, self::$database->deleteAttribute($collection->getId(), 'value', false)); - - // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'titles', Database::VAR_STRING, true)); - // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'descriptions', Database::VAR_STRING, true)); - // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'values', Database::VAR_NUMBER, true)); + // Delete Indexes + $this->assertEquals(true, static::getDatabase()->deleteIndex('indexes', 'index1')); + $this->assertEquals(true, static::getDatabase()->deleteIndex('indexes', 'index2')); + $this->assertEquals(true, static::getDatabase()->deleteIndex('indexes', 'index3')); - // $this->assertEquals(true, self::$database->deleteAttribute($collection->getId(), 'titles', true)); - // $this->assertEquals(true, self::$database->deleteAttribute($collection->getId(), 'descriptions', true)); - // $this->assertEquals(true, self::$database->deleteAttribute($collection->getId(), 'values', true)); - // } - - // public function testCreateIndex() - // { - // $collection = self::$database->createDocument(Database::COLLECTION_COLLECTIONS, [ - // '$collection' => Database::COLLECTION_COLLECTIONS, - // '$permissions' => ['read' => ['*']], - // 'name' => 'Create Index', - // 'rules' => [], - // ]); - - // $this->assertEquals(true, self::$database->createCollection($collection->getId(), [], [])); - // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'title', Database::VAR_STRING)); - // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'description', Database::VAR_STRING)); - // $this->assertEquals(true, self::$database->createIndex($collection->getId(), 'x', Database::INDEX_KEY, ['title'])); - // $this->assertEquals(true, self::$database->createIndex($collection->getId(), 'y', Database::INDEX_KEY, ['description'])); - // $this->assertEquals(true, self::$database->createIndex($collection->getId(), 'z', Database::INDEX_KEY, ['title', 'description'])); - // } + static::getDatabase()->deleteCollection('indexes'); + } - // public function testDeleteIndex() - // { - // $collection = self::$database->createDocument(Database::COLLECTION_COLLECTIONS, [ - // '$collection' => Database::COLLECTION_COLLECTIONS, - // '$permissions' => ['read' => ['*']], - // 'name' => 'Delete Index', - // 'rules' => [], - // ]); + public function testCreateDocument() + { + static::getDatabase()->createCollection('documents'); - // $this->assertEquals(true, self::$database->createCollection($collection->getId(), [], [])); - // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'title', Database::VAR_STRING)); - // $this->assertEquals(true, self::$database->createAttribute($collection->getId(), 'description', Database::VAR_STRING)); - // $this->assertEquals(true, self::$database->createIndex($collection->getId(), 'x', Database::INDEX_KEY, ['title'])); - // $this->assertEquals(true, self::$database->createIndex($collection->getId(), 'y', Database::INDEX_KEY, ['description'])); - // $this->assertEquals(true, self::$database->createIndex($collection->getId(), 'z', Database::INDEX_KEY, ['title', 'description'])); + $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'string', Database::VAR_STRING, 128)); + $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'integer', Database::VAR_INTEGER, 0)); + $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'float', Database::VAR_FLOAT, 0)); + $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'boolean', Database::VAR_BOOLEAN, 0)); + $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'colors', Database::VAR_STRING, 32, true, true)); - // $this->assertEquals(true, self::$database->deleteIndex($collection->getId(), 'x')); - // $this->assertEquals(true, self::$database->deleteIndex($collection->getId(), 'y')); - // $this->assertEquals(true, self::$database->deleteIndex($collection->getId(), 'z')); - // } + $document = static::getDatabase()->createDocument('documents', new Document([ + '$permissions' => [ + 'read' => ['user1', 'user2'], + 'write' => ['user1x', 'user2x'], + ], + 'string' => 'text📝', + 'integer' => 5, + 'float' => 5.55, + 'boolean' => true, + 'colors' => ['pink', 'green', 'blue'], + ])); + + $this->assertNotEmpty(true, $document->getId()); + $this->assertIsString($document->getAttribute('string')); + $this->assertEquals('text📝', $document->getAttribute('string')); // Also makes sure an emoji is working + $this->assertIsInt($document->getAttribute('integer')); + $this->assertEquals(5, $document->getAttribute('integer')); + $this->assertIsFloat($document->getAttribute('float')); + $this->assertEquals(5.55, $document->getAttribute('float')); + $this->assertIsBool($document->getAttribute('boolean')); + $this->assertEquals(true, $document->getAttribute('boolean')); + $this->assertIsArray($document->getAttribute('colors')); + $this->assertEquals(['pink', 'green', 'blue'], $document->getAttribute('colors')); + + return $document; + } + + /** + * @depends testCreateDocument + */ + public function testGetDocument($document) + { + $document = static::getDatabase()->getDocument('documents', $document->getId()); + + $this->assertNotEmpty(true, $document->getId()); + $this->assertIsString($document->getAttribute('string')); + $this->assertEquals('text📝', $document->getAttribute('string')); + $this->assertIsInt($document->getAttribute('integer')); + $this->assertEquals(5, $document->getAttribute('integer')); + $this->assertIsFloat($document->getAttribute('float')); + $this->assertEquals(5.55, $document->getAttribute('float')); + $this->assertIsBool($document->getAttribute('boolean')); + $this->assertEquals(true, $document->getAttribute('boolean')); + $this->assertIsArray($document->getAttribute('colors')); + $this->assertEquals(['pink', 'green', 'blue'], $document->getAttribute('colors')); + } // public function testCreateDocument() // { diff --git a/tests/Database/DocumentTest.php b/tests/Database/DocumentTest.php index 6b72ddfc2..f175df2a9 100644 --- a/tests/Database/DocumentTest.php +++ b/tests/Database/DocumentTest.php @@ -83,6 +83,21 @@ public function testPermissions() $this->assertEquals([], $this->empty->getPermissions()); } + public function testGetAttributes() + { + $this->assertEquals([ + 'title' => 'This is a test.', + 'list' => [ + 'one' + ], + 'children' => [ + new Document(['name' => 'x']), + new Document(['name' => 'y']), + new Document(['name' => 'z']), + ] + ], $this->document->getAttributes()); + } + public function testGetAttribute() { $this->assertEquals('This is a test.', $this->document->getAttribute('title', '')); From 690e730b0e20ed64a339b06305dd0551f6dd8ac5 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 15 Apr 2021 18:18:15 +0300 Subject: [PATCH 032/190] New naming --- src/Database/Adapter.php | 6 +++--- src/Database/Adapter/MariaDB.php | 6 +++--- src/Database/Database.php | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index ee58b9dac..eb9a30bfa 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -261,21 +261,21 @@ abstract public function getIntLimit(): int; * * @return bool */ - abstract public function getIndexSupport(): bool; + abstract public function getSupportForIndex(): bool; /** * Is unique index supported? * * @return bool */ - abstract public function getUniqueIndexSupport(): bool; + abstract public function getSupportForUniqueIndex(): bool; /** * Is fulltext index supported? * * @return bool */ - abstract public function getFulltextIndexSupport(): bool; + abstract public function getSupportForFulltextIndex(): bool; /** * Filter Keys diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 45cb70956..87d959b0b 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -400,7 +400,7 @@ public function getIntLimit(): int * * @return bool */ - public function getIndexSupport(): bool + public function getSupportForIndex(): bool { return true; } @@ -410,7 +410,7 @@ public function getIndexSupport(): bool * * @return bool */ - public function getUniqueIndexSupport(): bool + public function getSupportForUniqueIndex(): bool { return true; } @@ -420,7 +420,7 @@ public function getUniqueIndexSupport(): bool * * @return bool */ - public function getFulltextIndexSupport(): bool + public function getSupportForFulltextIndex(): bool { return true; } diff --git a/src/Database/Database.php b/src/Database/Database.php index f350d6566..01cd29341 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -248,19 +248,19 @@ public function createIndex(string $collection, string $id, string $type, array switch ($type) { case self::INDEX_KEY: - if(!$this->adapter->getIndexSupport()) { + if(!$this->adapter->getSupportForIndex()) { throw new Exception('Key index is not supported'); } break; case self::INDEX_UNIQUE: - if(!$this->adapter->getUniqueIndexSupport()) { + if(!$this->adapter->getSupportForUniqueIndex()) { throw new Exception('Unique index is not supported'); } break; case self::INDEX_FULLTEXT: - if(!$this->adapter->getUniqueIndexSupport()) { + if(!$this->adapter->getSupportForUniqueIndex()) { throw new Exception('Fulltext index is not supported'); } break; From 3cb471b1d570a23df0cab54067b3159640d8d932 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 16 Apr 2021 14:07:15 +0000 Subject: [PATCH 033/190] Update validators with new abstract methods --- composer.lock | 12 ++++++------ src/Database/Validator/Authorization.php | 24 ++++++++++++++++++++++++ src/Database/Validator/DocumentId.php | 24 ++++++++++++++++++++++++ src/Database/Validator/Key.php | 23 +++++++++++++++++++++++ src/Database/Validator/Permissions.php | 23 +++++++++++++++++++++++ src/Database/Validator/UID.php | 24 ++++++++++++++++++++++++ 6 files changed, 124 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index ad0a0d8ce..c07f4982b 100644 --- a/composer.lock +++ b/composer.lock @@ -287,16 +287,16 @@ }, { "name": "utopia-php/framework", - "version": "0.10.0", + "version": "0.14.0", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "65909bdb24ef6b6c6751abfdea90caf96bbc6c50" + "reference": "92d4a36f3b0e22393a31877c5317c96e01760339" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/65909bdb24ef6b6c6751abfdea90caf96bbc6c50", - "reference": "65909bdb24ef6b6c6751abfdea90caf96bbc6c50", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/92d4a36f3b0e22393a31877c5317c96e01760339", + "reference": "92d4a36f3b0e22393a31877c5317c96e01760339", "shasum": "" }, "require": { @@ -330,9 +330,9 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.10.0" + "source": "https://github.com/utopia-php/framework/tree/0.14.0" }, - "time": "2020-12-26T12:02:39+00:00" + "time": "2021-04-15T21:01:44+00:00" } ], "packages-dev": [ diff --git a/src/Database/Validator/Authorization.php b/src/Database/Validator/Authorization.php index 0dbb2543c..b2d07ce2c 100644 --- a/src/Database/Validator/Authorization.php +++ b/src/Database/Validator/Authorization.php @@ -186,4 +186,28 @@ public static function reset(): void { self::$status = self::$statusDefault; } + + /** + * Is array + * + * Function will return true if object is array. + * + * @return bool + */ + public function isArray(): bool + { + return false; + } + + /** + * Get Type + * + * Returns validator type. + * + * @return string + */ + public function getType(): string + { + return self::TYPE_ARRAY; + } } diff --git a/src/Database/Validator/DocumentId.php b/src/Database/Validator/DocumentId.php index 9dd43f3b7..7d69e9be5 100644 --- a/src/Database/Validator/DocumentId.php +++ b/src/Database/Validator/DocumentId.php @@ -78,4 +78,28 @@ public function isValid($id) return true; } + + /** + * Is array + * + * Function will return true if object is array. + * + * @return bool + */ + public function isArray(): bool + { + return false; + } + + /** + * Get Type + * + * Returns validator type. + * + * @return string + */ + public function getType(): string + { + return self::TYPE_STRING; + } } diff --git a/src/Database/Validator/Key.php b/src/Database/Validator/Key.php index ffb6d0eb1..e6bb3b426 100644 --- a/src/Database/Validator/Key.php +++ b/src/Database/Validator/Key.php @@ -52,4 +52,27 @@ public function isValid($value) return true; } + /** + * Is array + * + * Function will return true if object is array. + * + * @return bool + */ + public function isArray(): bool + { + return false; + } + + /** + * Get Type + * + * Returns validator type. + * + * @return string + */ + public function getType(): string + { + return self::TYPE_STRING; + } } diff --git a/src/Database/Validator/Permissions.php b/src/Database/Validator/Permissions.php index 7520bde1e..4a313ad42 100644 --- a/src/Database/Validator/Permissions.php +++ b/src/Database/Validator/Permissions.php @@ -63,4 +63,27 @@ public function isValid($value) return true; } + /** + * Is array + * + * Function will return true if object is array. + * + * @return bool + */ + public function isArray(): bool + { + return false; + } + + /** + * Get Type + * + * Returns validator type. + * + * @return string + */ + public function getType(): string + { + return self::TYPE_ARRAY; + } } diff --git a/src/Database/Validator/UID.php b/src/Database/Validator/UID.php index 6b4d1123e..2742364ef 100644 --- a/src/Database/Validator/UID.php +++ b/src/Database/Validator/UID.php @@ -43,4 +43,28 @@ public function isValid($value) return true; } + + /** + * Is array + * + * Function will return true if object is array. + * + * @return bool + */ + public function isArray(): bool + { + return false; + } + + /** + * Get Type + * + * Returns validator type. + * + * @return string + */ + public function getType(): string + { + return self::TYPE_STRING; + } } From b88735ae496bc21518314bdd1494f6fed5353427 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 18 Apr 2021 20:21:17 +0300 Subject: [PATCH 034/190] Updated metadata handling --- .phpunit.result.cache | 2 +- src/Database/Adapter/MariaDB.php | 22 +-- src/Database/Adapter/MongoDB.php | 12 +- src/Database/Adapter/Postgres.php | 12 +- src/Database/Database.php | 315 +++++++++++++++++------------- tests/Database/Base.php | 2 +- 6 files changed, 201 insertions(+), 164 deletions(-) diff --git a/.phpunit.result.cache b/.phpunit.result.cache index 7f84548de..64f3ea4f7 100644 --- a/.phpunit.result.cache +++ b/.phpunit.result.cache @@ -1 +1 @@ -C:37:"PHPUnit\Runner\DefaultTestResultCache":8049:{a:2:{s:7:"defects";a:56:{s:42:"Utopia\Tests\AuthorizationTest::testValues";i:4;s:32:"Utopia\Tests\KeyTest::testValues";i:4;s:40:"Utopia\Tests\PermissionsTest::testValues";i:4;s:32:"Utopia\Tests\UIDTest::testValues";i:4;s:42:"Utopia\Tests\DocumentTest::testPermissions";i:4;s:33:"Utopia\Tests\DocumentTest::testId";i:4;s:41:"Utopia\Tests\DocumentTest::testCollection";i:4;s:43:"Utopia\Tests\DocumentTest::testGetAttribute";i:4;s:43:"Utopia\Tests\DocumentTest::testSetAttribute";i:3;s:37:"Utopia\Tests\DocumentTest::testSearch";i:3;s:36:"Utopia\Tests\DocumentTest::testIsSet";i:3;s:43:"Utopia\Tests\DocumentTest::testGetArrayCopy";i:3;s:54:"Utopia\Tests\Adapter\MariaDBTest::testCreateCollection";i:1;s:54:"Utopia\Tests\Adapter\MariaDBTest::testDeleteCollection";i:1;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAttribute";i:4;s:53:"Utopia\Tests\Adapter\MariaDBTest::testDeleteAttribute";i:4;s:49:"Utopia\Tests\Adapter\MariaDBTest::testCreateIndex";i:4;s:49:"Utopia\Tests\Adapter\MariaDBTest::testDeleteIndex";i:4;s:47:"Utopia\Tests\Adapter\MariaDBTest::testFindFirst";i:4;s:46:"Utopia\Tests\Adapter\MariaDBTest::testFindLast";i:4;s:44:"Utopia\Tests\Adapter\MariaDBTest::testCreate";i:4;s:44:"Utopia\Tests\Adapter\MariaDBTest::testDelete";i:4;s:45:"Utopia\Tests\Adapter\PostgresTest::testCreate";i:4;s:45:"Utopia\Tests\Adapter\PostgresTest::testDelete";i:4;s:44:"Utopia\Tests\Adapter\MongoDBTest::testCreate";i:4;s:44:"Utopia\Tests\Adapter\MongoDBTest::testDelete";i:4;s:54:"Utopia\Tests\Adapter\MongoDBTest::testCreateCollection";i:1;s:54:"Utopia\Tests\Adapter\MongoDBTest::testDeleteCollection";i:1;s:55:"Utopia\Tests\Adapter\PostgresTest::testCreateCollection";i:1;s:55:"Utopia\Tests\Adapter\PostgresTest::testDeleteCollection";i:1;s:55:"Utopia\Tests\Adapter\CockroachTest::testCreateAndDelete";i:4;s:56:"Utopia\Tests\Adapter\CockroachTest::testCreateCollection";i:1;s:56:"Utopia\Tests\Adapter\CockroachTest::testDeleteCollection";i:1;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAndDelete";i:4;s:53:"Utopia\Tests\Adapter\MongoDBTest::testCreateAndDelete";i:4;s:51:"Utopia\Tests\Adapter\MySQLTest::testCreateAndDelete";i:4;s:52:"Utopia\Tests\Adapter\MySQLTest::testCreateCollection";i:1;s:52:"Utopia\Tests\Adapter\MySQLTest::testDeleteCollection";i:1;s:54:"Utopia\Tests\Adapter\PostgresTest::testCreateAndDelete";i:4;s:42:"Utopia\Tests\Adapter\MariaDBTest::testList";i:3;s:42:"Utopia\Tests\Adapter\MongoDBTest::testList";i:3;s:40:"Utopia\Tests\Adapter\MySQLTest::testList";i:3;s:43:"Utopia\Tests\Adapter\PostgresTest::testList";i:3;s:53:"Utopia\Tests\Adapter\MariaDBTest::testListCollections";i:3;s:53:"Utopia\Tests\Adapter\MongoDBTest::testListCollections";i:3;s:51:"Utopia\Tests\Adapter\MySQLTest::testListCollections";i:3;s:54:"Utopia\Tests\Adapter\PostgresTest::testListCollections";i:3;s:51:"Utopia\Tests\Adapter\MySQLTest::testCreateAttribute";i:4;s:59:"Utopia\Tests\Adapter\MariaDBTest::testCreateDeleteAttribute";i:4;s:55:"Utopia\Tests\Adapter\MariaDBTest::testCreateDeleteIndex";i:4;s:50:"Utopia\Tests\Adapter\MySQLTest::testCreateDocument";i:3;s:44:"Utopia\Tests\DocumentTest::testGetAttributes";i:3;s:52:"Utopia\Tests\Adapter\MariaDBTest::testCreateDocument";i:4;s:50:"Utopia\Tests\Adapter\MariaDBTest::testCreateDelete";i:4;s:60:"Utopia\Tests\Adapter\MariaDBTest::testCreateDeleteCollection";i:1;s:49:"Utopia\Tests\Adapter\MariaDBTest::testGetDocument";i:3;}s:5:"times";a:74:{s:42:"Utopia\Tests\AuthorizationTest::testValues";d:0.003;s:32:"Utopia\Tests\KeyTest::testValues";d:0.001;s:40:"Utopia\Tests\PermissionsTest::testValues";d:0.001;s:32:"Utopia\Tests\UIDTest::testValues";d:0.001;s:33:"Utopia\Tests\DocumentTest::testId";d:0.032;s:41:"Utopia\Tests\DocumentTest::testCollection";d:0;s:42:"Utopia\Tests\DocumentTest::testPermissions";d:0;s:43:"Utopia\Tests\DocumentTest::testGetAttribute";d:0;s:43:"Utopia\Tests\DocumentTest::testSetAttribute";d:0;s:46:"Utopia\Tests\DocumentTest::testRemoveAttribute";d:0;s:37:"Utopia\Tests\DocumentTest::testSearch";d:0;s:38:"Utopia\Tests\DocumentTest::testIsEmpty";d:0;s:36:"Utopia\Tests\DocumentTest::testIsSet";d:0;s:43:"Utopia\Tests\DocumentTest::testGetArrayCopy";d:0;s:52:"Utopia\Tests\Validator\AuthorizationTest::testValues";d:0.004;s:42:"Utopia\Tests\Validator\KeyTest::testValues";d:0.004;s:50:"Utopia\Tests\Validator\PermissionsTest::testValues";d:0.002;s:42:"Utopia\Tests\Validator\UIDTest::testValues";d:0.002;s:54:"Utopia\Tests\Adapter\MariaDBTest::testCreateCollection";d:0.015;s:54:"Utopia\Tests\Adapter\MariaDBTest::testDeleteCollection";d:0.005;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAttribute";d:0.117;s:53:"Utopia\Tests\Adapter\MariaDBTest::testDeleteAttribute";d:0.002;s:49:"Utopia\Tests\Adapter\MariaDBTest::testCreateIndex";d:0.001;s:49:"Utopia\Tests\Adapter\MariaDBTest::testDeleteIndex";d:0.002;s:47:"Utopia\Tests\Adapter\MariaDBTest::testFindFirst";d:0;s:46:"Utopia\Tests\Adapter\MariaDBTest::testFindLast";d:0;s:44:"Utopia\Tests\Adapter\MariaDBTest::testCreate";d:0.038;s:44:"Utopia\Tests\Adapter\MariaDBTest::testDelete";d:0.002;s:45:"Utopia\Tests\Adapter\PostgresTest::testCreate";d:0.108;s:45:"Utopia\Tests\Adapter\PostgresTest::testDelete";d:0.017;s:48:"Utopia\Tests\Adapter\PostgresTest::testFindFirst";d:0;s:47:"Utopia\Tests\Adapter\PostgresTest::testFindLast";d:0;s:44:"Utopia\Tests\Adapter\MongoDBTest::testCreate";d:0.008;s:44:"Utopia\Tests\Adapter\MongoDBTest::testDelete";d:0.032;s:47:"Utopia\Tests\Adapter\MongoDBTest::testFindFirst";d:0;s:46:"Utopia\Tests\Adapter\MongoDBTest::testFindLast";d:0;s:54:"Utopia\Tests\Adapter\MongoDBTest::testCreateCollection";d:0.016;s:54:"Utopia\Tests\Adapter\MongoDBTest::testDeleteCollection";d:0.003;s:55:"Utopia\Tests\Adapter\PostgresTest::testCreateCollection";d:0.008;s:55:"Utopia\Tests\Adapter\PostgresTest::testDeleteCollection";d:0.003;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAndDelete";d:0.311;s:53:"Utopia\Tests\Adapter\MongoDBTest::testCreateAndDelete";d:0.031;s:54:"Utopia\Tests\Adapter\PostgresTest::testCreateAndDelete";d:0.015;s:51:"Utopia\Tests\Adapter\MySQLTest::testCreateAndDelete";d:1.54;s:52:"Utopia\Tests\Adapter\MySQLTest::testCreateCollection";d:0.026;s:52:"Utopia\Tests\Adapter\MySQLTest::testDeleteCollection";d:0.016;s:45:"Utopia\Tests\Adapter\MySQLTest::testFindFirst";d:0.001;s:44:"Utopia\Tests\Adapter\MySQLTest::testFindLast";d:0;s:55:"Utopia\Tests\Adapter\CockroachTest::testCreateAndDelete";d:0.01;s:56:"Utopia\Tests\Adapter\CockroachTest::testCreateCollection";d:0;s:56:"Utopia\Tests\Adapter\CockroachTest::testDeleteCollection";d:0;s:49:"Utopia\Tests\Adapter\CockroachTest::testFindFirst";d:0.001;s:48:"Utopia\Tests\Adapter\CockroachTest::testFindLast";d:0;s:42:"Utopia\Tests\Adapter\MariaDBTest::testList";d:0.007;s:42:"Utopia\Tests\Adapter\MongoDBTest::testList";d:0.003;s:40:"Utopia\Tests\Adapter\MySQLTest::testList";d:0.003;s:43:"Utopia\Tests\Adapter\PostgresTest::testList";d:0.002;s:53:"Utopia\Tests\Adapter\MariaDBTest::testListCollections";d:0.034;s:53:"Utopia\Tests\Adapter\MongoDBTest::testListCollections";d:0.007;s:51:"Utopia\Tests\Adapter\MySQLTest::testListCollections";d:0.003;s:54:"Utopia\Tests\Adapter\PostgresTest::testListCollections";d:0.005;s:51:"Utopia\Tests\Adapter\MySQLTest::testCreateAttribute";d:0.406;s:59:"Utopia\Tests\Adapter\MariaDBTest::testCreateDeleteAttribute";d:0.117;s:55:"Utopia\Tests\Adapter\MariaDBTest::testCreateDeleteIndex";d:0.07;s:60:"Utopia\Tests\Adapter\MariaDBTest::testCreateDeleteCollection";d:0.031;s:58:"Utopia\Tests\Adapter\MySQLTest::testCreateDeleteCollection";d:0.067;s:57:"Utopia\Tests\Adapter\MySQLTest::testCreateDeleteAttribute";d:0.874;s:53:"Utopia\Tests\Adapter\MySQLTest::testCreateDeleteIndex";d:0.242;s:48:"Utopia\Tests\Adapter\MySQLTest::testCreateDelete";d:1.016;s:50:"Utopia\Tests\Adapter\MySQLTest::testCreateDocument";d:0.126;s:44:"Utopia\Tests\DocumentTest::testGetAttributes";d:0.003;s:50:"Utopia\Tests\Adapter\MariaDBTest::testCreateDelete";d:0.372;s:52:"Utopia\Tests\Adapter\MariaDBTest::testCreateDocument";d:0.056;s:49:"Utopia\Tests\Adapter\MariaDBTest::testGetDocument";d:0.007;}}} \ No newline at end of file +C:37:"PHPUnit\Runner\DefaultTestResultCache":8050:{a:2:{s:7:"defects";a:56:{s:42:"Utopia\Tests\AuthorizationTest::testValues";i:4;s:32:"Utopia\Tests\KeyTest::testValues";i:4;s:40:"Utopia\Tests\PermissionsTest::testValues";i:4;s:32:"Utopia\Tests\UIDTest::testValues";i:4;s:42:"Utopia\Tests\DocumentTest::testPermissions";i:4;s:33:"Utopia\Tests\DocumentTest::testId";i:4;s:41:"Utopia\Tests\DocumentTest::testCollection";i:4;s:43:"Utopia\Tests\DocumentTest::testGetAttribute";i:4;s:43:"Utopia\Tests\DocumentTest::testSetAttribute";i:3;s:37:"Utopia\Tests\DocumentTest::testSearch";i:3;s:36:"Utopia\Tests\DocumentTest::testIsSet";i:3;s:43:"Utopia\Tests\DocumentTest::testGetArrayCopy";i:3;s:54:"Utopia\Tests\Adapter\MariaDBTest::testCreateCollection";i:1;s:54:"Utopia\Tests\Adapter\MariaDBTest::testDeleteCollection";i:1;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAttribute";i:4;s:53:"Utopia\Tests\Adapter\MariaDBTest::testDeleteAttribute";i:4;s:49:"Utopia\Tests\Adapter\MariaDBTest::testCreateIndex";i:4;s:49:"Utopia\Tests\Adapter\MariaDBTest::testDeleteIndex";i:4;s:47:"Utopia\Tests\Adapter\MariaDBTest::testFindFirst";i:4;s:46:"Utopia\Tests\Adapter\MariaDBTest::testFindLast";i:4;s:44:"Utopia\Tests\Adapter\MariaDBTest::testCreate";i:4;s:44:"Utopia\Tests\Adapter\MariaDBTest::testDelete";i:4;s:45:"Utopia\Tests\Adapter\PostgresTest::testCreate";i:4;s:45:"Utopia\Tests\Adapter\PostgresTest::testDelete";i:4;s:44:"Utopia\Tests\Adapter\MongoDBTest::testCreate";i:4;s:44:"Utopia\Tests\Adapter\MongoDBTest::testDelete";i:4;s:54:"Utopia\Tests\Adapter\MongoDBTest::testCreateCollection";i:1;s:54:"Utopia\Tests\Adapter\MongoDBTest::testDeleteCollection";i:1;s:55:"Utopia\Tests\Adapter\PostgresTest::testCreateCollection";i:1;s:55:"Utopia\Tests\Adapter\PostgresTest::testDeleteCollection";i:1;s:55:"Utopia\Tests\Adapter\CockroachTest::testCreateAndDelete";i:4;s:56:"Utopia\Tests\Adapter\CockroachTest::testCreateCollection";i:1;s:56:"Utopia\Tests\Adapter\CockroachTest::testDeleteCollection";i:1;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAndDelete";i:4;s:53:"Utopia\Tests\Adapter\MongoDBTest::testCreateAndDelete";i:4;s:51:"Utopia\Tests\Adapter\MySQLTest::testCreateAndDelete";i:4;s:52:"Utopia\Tests\Adapter\MySQLTest::testCreateCollection";i:1;s:52:"Utopia\Tests\Adapter\MySQLTest::testDeleteCollection";i:1;s:54:"Utopia\Tests\Adapter\PostgresTest::testCreateAndDelete";i:4;s:42:"Utopia\Tests\Adapter\MariaDBTest::testList";i:3;s:42:"Utopia\Tests\Adapter\MongoDBTest::testList";i:3;s:40:"Utopia\Tests\Adapter\MySQLTest::testList";i:3;s:43:"Utopia\Tests\Adapter\PostgresTest::testList";i:3;s:53:"Utopia\Tests\Adapter\MariaDBTest::testListCollections";i:3;s:53:"Utopia\Tests\Adapter\MongoDBTest::testListCollections";i:3;s:51:"Utopia\Tests\Adapter\MySQLTest::testListCollections";i:3;s:54:"Utopia\Tests\Adapter\PostgresTest::testListCollections";i:3;s:51:"Utopia\Tests\Adapter\MySQLTest::testCreateAttribute";i:4;s:59:"Utopia\Tests\Adapter\MariaDBTest::testCreateDeleteAttribute";i:4;s:55:"Utopia\Tests\Adapter\MariaDBTest::testCreateDeleteIndex";i:4;s:50:"Utopia\Tests\Adapter\MySQLTest::testCreateDocument";i:3;s:44:"Utopia\Tests\DocumentTest::testGetAttributes";i:3;s:52:"Utopia\Tests\Adapter\MariaDBTest::testCreateDocument";i:4;s:50:"Utopia\Tests\Adapter\MariaDBTest::testCreateDelete";i:4;s:60:"Utopia\Tests\Adapter\MariaDBTest::testCreateDeleteCollection";i:1;s:49:"Utopia\Tests\Adapter\MariaDBTest::testGetDocument";i:3;}s:5:"times";a:74:{s:42:"Utopia\Tests\AuthorizationTest::testValues";d:0.003;s:32:"Utopia\Tests\KeyTest::testValues";d:0.001;s:40:"Utopia\Tests\PermissionsTest::testValues";d:0.001;s:32:"Utopia\Tests\UIDTest::testValues";d:0.001;s:33:"Utopia\Tests\DocumentTest::testId";d:0.032;s:41:"Utopia\Tests\DocumentTest::testCollection";d:0;s:42:"Utopia\Tests\DocumentTest::testPermissions";d:0;s:43:"Utopia\Tests\DocumentTest::testGetAttribute";d:0;s:43:"Utopia\Tests\DocumentTest::testSetAttribute";d:0;s:46:"Utopia\Tests\DocumentTest::testRemoveAttribute";d:0;s:37:"Utopia\Tests\DocumentTest::testSearch";d:0;s:38:"Utopia\Tests\DocumentTest::testIsEmpty";d:0;s:36:"Utopia\Tests\DocumentTest::testIsSet";d:0;s:43:"Utopia\Tests\DocumentTest::testGetArrayCopy";d:0;s:52:"Utopia\Tests\Validator\AuthorizationTest::testValues";d:0.004;s:42:"Utopia\Tests\Validator\KeyTest::testValues";d:0.004;s:50:"Utopia\Tests\Validator\PermissionsTest::testValues";d:0.002;s:42:"Utopia\Tests\Validator\UIDTest::testValues";d:0.002;s:54:"Utopia\Tests\Adapter\MariaDBTest::testCreateCollection";d:0.015;s:54:"Utopia\Tests\Adapter\MariaDBTest::testDeleteCollection";d:0.005;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAttribute";d:0.117;s:53:"Utopia\Tests\Adapter\MariaDBTest::testDeleteAttribute";d:0.002;s:49:"Utopia\Tests\Adapter\MariaDBTest::testCreateIndex";d:0.001;s:49:"Utopia\Tests\Adapter\MariaDBTest::testDeleteIndex";d:0.002;s:47:"Utopia\Tests\Adapter\MariaDBTest::testFindFirst";d:0;s:46:"Utopia\Tests\Adapter\MariaDBTest::testFindLast";d:0;s:44:"Utopia\Tests\Adapter\MariaDBTest::testCreate";d:0.038;s:44:"Utopia\Tests\Adapter\MariaDBTest::testDelete";d:0.002;s:45:"Utopia\Tests\Adapter\PostgresTest::testCreate";d:0.108;s:45:"Utopia\Tests\Adapter\PostgresTest::testDelete";d:0.017;s:48:"Utopia\Tests\Adapter\PostgresTest::testFindFirst";d:0;s:47:"Utopia\Tests\Adapter\PostgresTest::testFindLast";d:0;s:44:"Utopia\Tests\Adapter\MongoDBTest::testCreate";d:0.008;s:44:"Utopia\Tests\Adapter\MongoDBTest::testDelete";d:0.032;s:47:"Utopia\Tests\Adapter\MongoDBTest::testFindFirst";d:0;s:46:"Utopia\Tests\Adapter\MongoDBTest::testFindLast";d:0;s:54:"Utopia\Tests\Adapter\MongoDBTest::testCreateCollection";d:0.016;s:54:"Utopia\Tests\Adapter\MongoDBTest::testDeleteCollection";d:0.003;s:55:"Utopia\Tests\Adapter\PostgresTest::testCreateCollection";d:0.008;s:55:"Utopia\Tests\Adapter\PostgresTest::testDeleteCollection";d:0.003;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAndDelete";d:0.311;s:53:"Utopia\Tests\Adapter\MongoDBTest::testCreateAndDelete";d:0.031;s:54:"Utopia\Tests\Adapter\PostgresTest::testCreateAndDelete";d:0.015;s:51:"Utopia\Tests\Adapter\MySQLTest::testCreateAndDelete";d:1.54;s:52:"Utopia\Tests\Adapter\MySQLTest::testCreateCollection";d:0.026;s:52:"Utopia\Tests\Adapter\MySQLTest::testDeleteCollection";d:0.016;s:45:"Utopia\Tests\Adapter\MySQLTest::testFindFirst";d:0.001;s:44:"Utopia\Tests\Adapter\MySQLTest::testFindLast";d:0;s:55:"Utopia\Tests\Adapter\CockroachTest::testCreateAndDelete";d:0.01;s:56:"Utopia\Tests\Adapter\CockroachTest::testCreateCollection";d:0;s:56:"Utopia\Tests\Adapter\CockroachTest::testDeleteCollection";d:0;s:49:"Utopia\Tests\Adapter\CockroachTest::testFindFirst";d:0.001;s:48:"Utopia\Tests\Adapter\CockroachTest::testFindLast";d:0;s:42:"Utopia\Tests\Adapter\MariaDBTest::testList";d:0.007;s:42:"Utopia\Tests\Adapter\MongoDBTest::testList";d:0.003;s:40:"Utopia\Tests\Adapter\MySQLTest::testList";d:0.003;s:43:"Utopia\Tests\Adapter\PostgresTest::testList";d:0.002;s:53:"Utopia\Tests\Adapter\MariaDBTest::testListCollections";d:0.034;s:53:"Utopia\Tests\Adapter\MongoDBTest::testListCollections";d:0.007;s:51:"Utopia\Tests\Adapter\MySQLTest::testListCollections";d:0.003;s:54:"Utopia\Tests\Adapter\PostgresTest::testListCollections";d:0.005;s:51:"Utopia\Tests\Adapter\MySQLTest::testCreateAttribute";d:0.406;s:59:"Utopia\Tests\Adapter\MariaDBTest::testCreateDeleteAttribute";d:0.182;s:55:"Utopia\Tests\Adapter\MariaDBTest::testCreateDeleteIndex";d:0.099;s:60:"Utopia\Tests\Adapter\MariaDBTest::testCreateDeleteCollection";d:0.047;s:58:"Utopia\Tests\Adapter\MySQLTest::testCreateDeleteCollection";d:0.067;s:57:"Utopia\Tests\Adapter\MySQLTest::testCreateDeleteAttribute";d:0.874;s:53:"Utopia\Tests\Adapter\MySQLTest::testCreateDeleteIndex";d:0.242;s:48:"Utopia\Tests\Adapter\MySQLTest::testCreateDelete";d:1.016;s:50:"Utopia\Tests\Adapter\MySQLTest::testCreateDocument";d:0.126;s:44:"Utopia\Tests\DocumentTest::testGetAttributes";d:0.003;s:50:"Utopia\Tests\Adapter\MariaDBTest::testCreateDelete";d:0.256;s:52:"Utopia\Tests\Adapter\MariaDBTest::testCreateDocument";d:0.074;s:49:"Utopia\Tests\Adapter\MariaDBTest::testGetDocument";d:0.008;}}} \ No newline at end of file diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 87d959b0b..5b9e671c8 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -74,15 +74,15 @@ public function delete(): bool /** * Create Collection * - * @param string $name + * @param string $id * @return bool */ - public function createCollection(string $name): bool + public function createCollection(string $id): bool { - $name = $this->filter($name); + $id = $this->filter($id); $this->getPDO() - ->prepare("CREATE TABLE IF NOT EXISTS {$this->getNamespace()}.{$name}_permissions ( + ->prepare("CREATE TABLE IF NOT EXISTS {$this->getNamespace()}.{$id}_permissions ( `_id` int(11) unsigned NOT NULL AUTO_INCREMENT, `_uid` CHAR(13) NOT NULL, `_action` CHAR(128) NOT NULL, @@ -94,7 +94,7 @@ public function createCollection(string $name): bool ->execute(); return $this->getPDO() - ->prepare("CREATE TABLE IF NOT EXISTS {$this->getNamespace()}.{$name} ( + ->prepare("CREATE TABLE IF NOT EXISTS {$this->getNamespace()}.{$id} ( `_id` int(11) unsigned NOT NULL AUTO_INCREMENT, `_uid` CHAR(13) NOT NULL, PRIMARY KEY (`_id`), @@ -118,19 +118,19 @@ public function listCollections(): array /** * Delete Collection * - * @param string $name + * @param string $id * @return bool */ - public function deleteCollection(string $name): bool + public function deleteCollection(string $id): bool { - $name = $this->filter($name); + $id = $this->filter($id); $this->getPDO() - ->prepare("DROP TABLE {$this->getNamespace()}.{$name}_permissions;") + ->prepare("DROP TABLE {$this->getNamespace()}.{$id}_permissions;") ->execute(); return $this->getPDO() - ->prepare("DROP TABLE {$this->getNamespace()}.{$name};") + ->prepare("DROP TABLE {$this->getNamespace()}.{$id};") ->execute(); } @@ -267,7 +267,7 @@ public function getDocument(string $collection, string $id): Document $document = $stmt->fetch(); - $document['$uid'] = $document['_uid']; + $document['$id'] = $document['_uid']; $document['$permissions'] = [Database::PERMISSION_READ => [], Database::PERMISSION_WRITE => []]; unset($document['_id']); unset($document['_uid']); diff --git a/src/Database/Adapter/MongoDB.php b/src/Database/Adapter/MongoDB.php index 357d84d7b..2a72051c2 100644 --- a/src/Database/Adapter/MongoDB.php +++ b/src/Database/Adapter/MongoDB.php @@ -71,12 +71,12 @@ public function delete(): bool /** * Create Collection * - * @param string $name + * @param string $id * @return bool */ - public function createCollection(string $name): bool + public function createCollection(string $id): bool { - return (!!$this->getDatabase()->createCollection($name)); + return (!!$this->getDatabase()->createCollection($id)); } /** @@ -98,12 +98,12 @@ public function listCollections(): array /** * Delete Collection * - * @param string $name + * @param string $id * @return bool */ - public function deleteCollection(string $name): bool + public function deleteCollection(string $id): bool { - return (!!$this->getDatabase()->dropCollection($name)); + return (!!$this->getDatabase()->dropCollection($id)); } /** diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php index d63ffaa90..141b14666 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -78,12 +78,12 @@ public function delete(): bool /** * Create Collection * - * @param string $name + * @param string $id * @return bool */ - public function createCollection(string $name): bool + public function createCollection(string $id): bool { - $name = $this->filter($name).'_documents'; + $name = $this->filter($id).'_documents'; return $this->getPDO() ->prepare("CREATE TABLE {$this->getNamespace()}.{$name}( @@ -105,12 +105,12 @@ public function listCollections(): array /** * Delete Collection * - * @param string $name + * @param string $id * @return bool */ - public function deleteCollection(string $name): bool + public function deleteCollection(string $id): bool { - $name = $this->filter($name).'_documents'; + $name = $this->filter($id).'_documents'; return $this->getPDO() ->prepare("DROP TABLE {$this->getNamespace()}.{$name};") diff --git a/src/Database/Database.php b/src/Database/Database.php index 01cd29341..1236459ad 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -43,6 +43,50 @@ class Database */ protected $adapter; + /** + * Parent Collection + * Defines the structure for both system and custom collections + */ + protected $collection = [ + '$id' => 'collections', + 'name' => 'collections', + 'attributes' => [ + [ + '$id' => 'type', + 'type' => self::VAR_STRING, + 'size' => 64, + 'signed' => true, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'size', + 'type' => self::VAR_INTEGER, + 'size' => 0, + 'signed' => true, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'signed', + 'type' => self::VAR_BOOLEAN, + 'size' => 0, + 'signed' => true, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'array', + 'type' => self::VAR_BOOLEAN, + 'size' => 0, + 'signed' => true, + 'array' => false, + 'filters' => [], + ] + ], + 'indexes' => [], + ]; + /** * @var array */ @@ -97,28 +141,11 @@ public function create(): bool { $this->adapter->create(); - $this->createCollection(self::METADATA.'_collections'); - $this->createAttribute(self::METADATA.'_collections', 'name', self::VAR_STRING, 128); - $this->createIndex(self::METADATA.'_collections', '_key_1', self::INDEX_UNIQUE, ['name']); - - $this->createCollection(self::METADATA.'_collections_attrubutes'); - $this->createAttribute(self::METADATA.'_collections_attrubutes', 'collection', self::VAR_STRING, 128); - $this->createAttribute(self::METADATA.'_collections_attrubutes', 'id', self::VAR_STRING, 128); - $this->createAttribute(self::METADATA.'_collections_attrubutes', 'type', self::VAR_STRING, 128); - $this->createAttribute(self::METADATA.'_collections_attrubutes', 'size', self::VAR_INTEGER, 0, false); - $this->createAttribute(self::METADATA.'_collections_attrubutes', 'array', self::VAR_BOOLEAN, 0); - $this->createIndex(self::METADATA.'_collections_attrubutes', '_key_1', self::INDEX_KEY, ['collection']); - $this->createIndex(self::METADATA.'_collections_attrubutes', '_key_2', self::INDEX_KEY, ['id']); - - $this->createCollection(self::METADATA.'_collections_indexes'); - $this->createAttribute(self::METADATA.'_collections_indexes', 'collection', self::VAR_STRING, 128); - $this->createAttribute(self::METADATA.'_collections_indexes', 'id', self::VAR_STRING, 128); - $this->createAttribute(self::METADATA.'_collections_indexes', 'type', self::VAR_STRING, 128); - $this->createAttribute(self::METADATA.'_collections_indexes', 'attributes', self::VAR_STRING, 128, true, true); - $this->createAttribute(self::METADATA.'_collections_indexes', 'lengths', self::VAR_INTEGER, 11, true, true); - $this->createAttribute(self::METADATA.'_collections_indexes', 'orders', self::VAR_STRING, 4, true, true); - $this->createIndex(self::METADATA.'_collections_indexes', '_key_1', self::INDEX_KEY, ['collection']); - $this->createIndex(self::METADATA.'_collections_indexes', '_key_2', self::INDEX_KEY, ['id']); + $this->createCollection(self::COLLECTION_COLLECTIONS); + $this->createAttribute(self::COLLECTION_COLLECTIONS, 'name', self::VAR_STRING, 128); + $this->createAttribute(self::COLLECTION_COLLECTIONS, 'attributes', self::VAR_STRING, 8064); + $this->createAttribute(self::COLLECTION_COLLECTIONS, 'indexes', self::VAR_STRING, 8064); + $this->createIndex(self::COLLECTION_COLLECTIONS, '_key_1', self::INDEX_UNIQUE, ['name']); return true; } @@ -146,13 +173,37 @@ public function delete(): bool /** * Create Collection * - * @param string $name + * @param string $id * - * @return bool + * @return Document + */ + public function createCollection(string $id): Document + { + $this->adapter->createCollection($id); + + if($id === self::COLLECTION_COLLECTIONS) { + return new Document($this->collection); + } + + return $this->createDocument(Database::COLLECTION_COLLECTIONS, new Document([ + '$id' => $id, + 'name' => $id, + 'attributes' => [], + 'indexes' => [], + ])); + } + + /** + * Get Collection + * + * @param string $collection + * @param string $id + * + * @return Document */ - public function createCollection(string $name): bool + public function getCollection(string $id): Document { - return $this->adapter->createCollection($name); + return $this->getDocument(self::COLLECTION_COLLECTIONS, $id); } /** @@ -168,13 +219,15 @@ public function listCollections(): array /** * Delete Collection * - * @param string $name + * @param string $id * * @return bool */ - public function deleteCollection(string $name): bool + public function deleteCollection(string $id): bool { - return $this->adapter->deleteCollection($name); + // TODO Delete collection document first + + return $this->adapter->deleteCollection($id); } /** @@ -190,6 +243,18 @@ public function deleteCollection(string $name): bool */ public function createAttribute(string $collection, string $id, string $type, int $size, bool $signed = true, bool $array = false): bool { + $collection = $this->getCollection($collection); + + $collection->setAttribute('attributes', new Document([ + '$id' => $id, + 'type' => $type, + 'size' => $size, + 'signed' => $signed, + 'array' => $array, + ]), Document::SET_TYPE_APPEND); + + //$this->updateDocument(); + switch ($type) { case self::VAR_STRING: if($size > $this->adapter->getStringLimit()) { @@ -211,7 +276,7 @@ public function createAttribute(string $collection, string $id, string $type, in break; } - return $this->adapter->createAttribute($collection, $id, $type, $size, $signed, $array); + return $this->adapter->createAttribute($collection->getId(), $id, $type, $size, $signed, $array); } /** @@ -296,7 +361,12 @@ public function deleteIndex(string $collection, string $id): bool */ public function getDocument(string $collection, string $id): Document { - $document = $this->adapter->getDocument($collection, $id); + if($collection === self::COLLECTION_COLLECTIONS && $id === self::COLLECTION_COLLECTIONS) { + return new Document($this->collection); + } + + $collection = $this->getDocument(self::COLLECTION_COLLECTIONS, $collection); + $document = $this->adapter->getDocument($collection->getId(), $id); // $validator = new Authorization($document, self::PERMISSION_READ); @@ -322,10 +392,6 @@ public function getDocument(string $collection, string $id): Document */ public function createDocument(string $collection, Document $document): Document { - if(!empty($document->getId())) { - throw new Exception('Use update method instead of create'); - } - // $validator = new Authorization($document, self::PERMISSION_WRITE); // if (!$validator->isValid($document->getPermissions())) { // Check if user has write access to this document @@ -338,9 +404,9 @@ public function createDocument(string $collection, Document $document): Document // if (!$validator->isValid($document)) { // throw new StructureException($validator->getDescription()); // var_dump($validator->getDescription()); return false; // } - + $document - ->setAttribute('$id', $this->getId()) + ->setAttribute('$id', empty($document->getId()) ? $this->getId(): $document->getId()) ; $document = $this->adapter->createDocument($collection, $document); @@ -350,6 +416,79 @@ public function createDocument(string $collection, Document $document): Document return $document; } + /** + * Update Document + * + * @param array $collection + * @param array $id + * @param array $data + * + * @return Document|false + * + * @throws Exception + */ + public function updateDocument(string $collection, string $id, array $data): Document + { + if (!isset($data['$id'])) { + throw new Exception('Must define $id attribute'); + } + + $document = $this->getDocument($collection, $id); // TODO make sure user don\'t need read permission for write operations + + // Make sure reserved keys stay constant + $data['$id'] = $document->getId(); + $data['$collection'] = $document->getCollection(); + + // $validator = new Authorization($document, 'write'); + + // if (!$validator->isValid($document->getPermissions())) { // Check if user has write access to this document + // throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false; + // } + + $new = new Document($data); + + // if (!$validator->isValid($new->getPermissions())) { // Check if user has write access to this document + // throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false; + // } + + // $new = $this->encode($new); + + // $validator = new Structure($this); + + // if (!$validator->isValid($new)) { // Make sure updated structure still apply collection rules (if any) + // throw new StructureException($validator->getDescription()); // var_dump($validator->getDescription()); return false; + // } + + // $new = new Document($this->adapter->updateDocument($collection, $new->getArrayCopy())); + + // $new = $this->decode($new); + + return $new; + } + + /** + * @param string $collection + * @param string $id + * + * @return Document|false + * + * @throws AuthorizationException + */ + public function deleteDocument(string $collection, string $id): bool + { + // $document = $this->getDocument($collection, $id); + + // $validator = new Authorization($document, 'write'); + + // if (!$validator->isValid($document->getPermissions())) { // Check if user has write access to this document + // throw new AuthorizationException($validator->getDescription()); + // } + + // return new Document($this->adapter->deleteDocument($collection, $id)); + + return false; + } + // /** // * @param string $collection // * @param array $options @@ -418,87 +557,6 @@ public function createDocument(string $collection, Document $document): Document // return $results; // } - // /** - // * @param string $collection - // * @param string $id - // * @param bool $mock is mocked data allowed? - // * @param bool $decode - // * - // * @return Document - // */ - // public function getDocument($collection, $id, bool $mock = true, bool $decode = true) - // { - // if (\is_null($id)) { - // return new Document([]); - // } - - // if($mock === true - // && isset($this->mocks[$id])) { - // $document = $this->mocks[$id]; - // } - // else { - // $document = new Document($this->adapter->getDocument($this->getDocument(self::COLLECTION_COLLECTIONS, $collection), $id)); - // } - - // $validator = new Authorization($document, 'read'); - - // if (!$validator->isValid($document->getPermissions())) { // Check if user has read access to this document - // return new Document(); - // } - - // $document = ($decode) ? $this->decode($document) : $document; - - // return $document; - // } - - // /** - // * @param array $collection - // * @param array $id - // * @param array $data - // * - // * @return Document|false - // * - // * @throws Exception - // */ - // public function updateDocument(string $collection, string $id, array $data) - // { - // if (!isset($data['$id'])) { - // throw new Exception('Must define $id attribute'); - // } - - // $document = $this->getDocument($collection, $id); // TODO make sure user don\'t need read permission for write operations - - // // Make sure reserved keys stay constant - // $data['$id'] = $document->getId(); - // $data['$collection'] = $document->getCollection(); - - // $validator = new Authorization($document, 'write'); - - // if (!$validator->isValid($document->getPermissions())) { // Check if user has write access to this document - // throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false; - // } - - // $new = new Document($data); - - // if (!$validator->isValid($new->getPermissions())) { // Check if user has write access to this document - // throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false; - // } - - // $new = $this->encode($new); - - // $validator = new Structure($this); - - // if (!$validator->isValid($new)) { // Make sure updated structure still apply collection rules (if any) - // throw new StructureException($validator->getDescription()); // var_dump($validator->getDescription()); return false; - // } - - // $new = new Document($this->adapter->updateDocument($this->getDocument(self::COLLECTION_COLLECTIONS, $collection), $id, $new->getArrayCopy())); - - // $new = $this->decode($new); - - // return $new; - // } - // /** // * @param array $data // * @@ -541,27 +599,6 @@ public function createDocument(string $collection, Document $document): Document // return $new; // } - // /** - // * @param string $collection - // * @param string $id - // * - // * @return Document|false - // * - // * @throws AuthorizationException - // */ - // public function deleteDocument(string $collection, string $id) - // { - // $document = $this->getDocument($collection, $id); - - // $validator = new Authorization($document, 'write'); - - // if (!$validator->isValid($document->getPermissions())) { // Check if user has write access to this document - // throw new AuthorizationException($validator->getDescription()); - // } - - // return new Document($this->adapter->deleteDocument($this->getDocument(self::COLLECTION_COLLECTIONS, $collection), $id)); - // } - // /** // * @return array // */ diff --git a/tests/Database/Base.php b/tests/Database/Base.php index bf985d100..a7203639e 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -35,7 +35,7 @@ public function testCreateDelete() */ public function testCreateDeleteCollection() { - $this->assertEquals(true, static::getDatabase()->createCollection('actors')); + $this->assertInstanceOf('Utopia\Database\Document', static::getDatabase()->createCollection('actors')); $this->assertEquals(true, static::getDatabase()->deleteCollection('actors')); } From 8f36578b347027bc6d1d7bce7b88f97e2785568c Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 18 Apr 2021 23:05:27 +0300 Subject: [PATCH 035/190] Save arrays and objects inline --- src/Database/Adapter.php | 3 +- src/Database/Adapter/MariaDB.php | 95 +++++++++++----------------- src/Database/Database.php | 38 +++++------ src/Database/Validator/Structure.php | 4 +- tests/Database/Base.php | 46 +++++++------- 5 files changed, 81 insertions(+), 105 deletions(-) diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index eb9a30bfa..f2246664a 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -153,11 +153,10 @@ abstract public function createAttribute(string $collection, string $id, string * * @param string $collection * @param string $id - * @param bool $array * * @return bool */ - abstract public function deleteAttribute(string $collection, string $id, bool $array = false): bool; + abstract public function deleteAttribute(string $collection, string $id): bool; /** * Create Index diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 5b9e671c8..a740de011 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -4,9 +4,11 @@ use PDO; use Exception; +use PDOException; use Utopia\Database\Adapter; use Utopia\Database\Database; use Utopia\Database\Document; +use Utopia\Database\Exception\Duplicate; class MariaDB extends Adapter { @@ -152,17 +154,7 @@ public function createAttribute(string $collection, string $id, string $type, in $type = $this->getSQLType($type, $size, $signed); if($array) { - return $this->getPDO() - ->prepare("CREATE TABLE IF NOT EXISTS {$this->getNamespace()}.{$name}_arrays_{$id} ( - `_id` INT(11) unsigned NOT NULL AUTO_INCREMENT, - `_uid` CHAR(13) NOT NULL, - `_order` INT(11) unsigned NOT NULL, - `{$id}` {$type}, - PRIMARY KEY (`_id`), - INDEX `_index1` (`_uid`), - INDEX `_index2` (`_uid` ASC, `_order` ASC) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;") - ->execute(); + $type = 'LONGTEXT'; } return $this->getPDO() @@ -185,12 +177,6 @@ public function deleteAttribute(string $collection, string $id, bool $array = fa $name = $this->filter($collection); $id = $this->filter($id); - if($array) { - return $this->getPDO() - ->prepare("DROP TABLE {$this->getNamespace()}.{$name}_arrays_{$id};") - ->execute(); - } - return $this->getPDO() ->prepare("ALTER TABLE {$this->getNamespace()}.{$name} DROP COLUMN `{$id}`;") @@ -301,42 +287,14 @@ public function getDocument(string $collection, string $id): Document */ public function createDocument(string $collection, Document $document): Document { + $attributes = $document->getAttributes(); $name = $this->filter($collection); $columns = ''; - /** - * Insert Permissions - */ - $stmt = $this->getPDO() - ->prepare("INSERT INTO {$this->getNamespace()}.{$name}_permissions - SET _uid = :_uid, _action = :_action, _role = :_role"); - - $stmt->bindValue(':_uid', $document->getId(), PDO::PARAM_STR); - - foreach ($document->getPermissions() as $action => $roles) { - foreach ($roles as $key => $role) { - $stmt->bindValue(':_action', $action, PDO::PARAM_STR); - $stmt->bindValue(':_role', $role, PDO::PARAM_STR); - - if(!$stmt->execute()) { - throw new Exception('Failed to save permission'); - } - } - } - - $arrays = []; - $attributes = $document->getAttributes(); - /** * Insert Attributes */ foreach ($attributes as $attribute => $value) { // Parse statement - if(is_array($value)) { // arrays should be saved on dedicated table - $arrays[$attribute] = $value; - unset($attributes[$attribute]); - continue; - } - $column = $this->filter($attribute); $columns .= "`{$column}`" . '=:' . $column . ','; } @@ -348,27 +306,46 @@ public function createDocument(string $collection, Document $document): Document $stmt->bindValue(':_uid', $document->getId(), PDO::PARAM_STR); foreach ($attributes as $attribute => $value) { + if(is_array($value)) { // arrays & objects should be saved as strings + $value = json_encode($value); + } + $attribute = $this->filter($attribute); $stmt->bindValue(':' . $attribute, $value, $this->getPDOType($value)); } - - $stmt->execute(); + + try { + $stmt->execute(); + } catch (PDOException $e) { + switch ($e->getCode()) { + case 1062: + case 23000: + throw new Duplicate(); + break; + + default: + throw $e; + break; + } + } /** - * Insert Arrays + * Insert Permissions */ - foreach ($arrays as $attribute => $array) { - $attribute = $this->filter($attribute); + $stmt = $this->getPDO() + ->prepare("INSERT INTO {$this->getNamespace()}.{$name}_permissions + SET _uid = :_uid, _action = :_action, _role = :_role"); - $stmt = $this->getPDO() - ->prepare("INSERT INTO {$this->getNamespace()}.{$name}_arrays_{$attribute} - SET _uid = :_uid, _order = :_order, {$attribute} = :{$attribute}"); + $stmt->bindValue(':_uid', $document->getId(), PDO::PARAM_STR); + + foreach ($document->getPermissions() as $action => $roles) { + foreach ($roles as $key => $role) { + $stmt->bindValue(':_action', $action, PDO::PARAM_STR); + $stmt->bindValue(':_role', $role, PDO::PARAM_STR); - foreach ($array as $order => $value) { - $stmt->bindValue(':_uid', $document->getId(), PDO::PARAM_STR); - $stmt->bindValue(':_order', $order, PDO::PARAM_INT); - $stmt->bindValue(':' . $attribute, $value, $this->getPDOType($value)); - $stmt->execute(); + if(!$stmt->execute()) { + throw new Exception('Failed to save permission'); + } } } diff --git a/src/Database/Database.php b/src/Database/Database.php index 1236459ad..66ca2bc59 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -36,7 +36,7 @@ class Database const PERMISSION_WRITE = 'write'; // Collections - const COLLECTION_COLLECTIONS = 'collections'; + const COLLECTIONS = 'collections'; /** * @var Adapter @@ -141,11 +141,11 @@ public function create(): bool { $this->adapter->create(); - $this->createCollection(self::COLLECTION_COLLECTIONS); - $this->createAttribute(self::COLLECTION_COLLECTIONS, 'name', self::VAR_STRING, 128); - $this->createAttribute(self::COLLECTION_COLLECTIONS, 'attributes', self::VAR_STRING, 8064); - $this->createAttribute(self::COLLECTION_COLLECTIONS, 'indexes', self::VAR_STRING, 8064); - $this->createIndex(self::COLLECTION_COLLECTIONS, '_key_1', self::INDEX_UNIQUE, ['name']); + $this->createCollection(self::COLLECTIONS); + $this->createAttribute(self::COLLECTIONS, 'name', self::VAR_STRING, 128); + $this->createAttribute(self::COLLECTIONS, 'attributes', self::VAR_STRING, 8064); + $this->createAttribute(self::COLLECTIONS, 'indexes', self::VAR_STRING, 8064); + $this->createIndex(self::COLLECTIONS, '_key_1', self::INDEX_UNIQUE, ['name']); return true; } @@ -181,11 +181,11 @@ public function createCollection(string $id): Document { $this->adapter->createCollection($id); - if($id === self::COLLECTION_COLLECTIONS) { + if($id === self::COLLECTIONS) { return new Document($this->collection); } - return $this->createDocument(Database::COLLECTION_COLLECTIONS, new Document([ + return $this->createDocument(Database::COLLECTIONS, new Document([ '$id' => $id, 'name' => $id, 'attributes' => [], @@ -203,7 +203,7 @@ public function createCollection(string $id): Document */ public function getCollection(string $id): Document { - return $this->getDocument(self::COLLECTION_COLLECTIONS, $id); + return $this->getDocument(self::COLLECTIONS, $id); } /** @@ -241,7 +241,7 @@ public function deleteCollection(string $id): bool * * @return bool */ - public function createAttribute(string $collection, string $id, string $type, int $size, bool $signed = true, bool $array = false): bool + public function createAttribute(string $collection, string $id, string $type, int $size, bool $signed = true, bool $array = false, array $filters = []): bool { $collection = $this->getCollection($collection); @@ -251,6 +251,7 @@ public function createAttribute(string $collection, string $id, string $type, in 'size' => $size, 'signed' => $signed, 'array' => $array, + 'filters' => $filters, ]), Document::SET_TYPE_APPEND); //$this->updateDocument(); @@ -284,13 +285,12 @@ public function createAttribute(string $collection, string $id, string $type, in * * @param string $collection * @param string $id - * @param bool $array * * @return bool */ - public function deleteAttribute(string $collection, string $id, bool $array = false): bool + public function deleteAttribute(string $collection, string $id): bool { - return $this->adapter->deleteAttribute($collection, $id, $array); + return $this->adapter->deleteAttribute($collection, $id); } /** @@ -361,11 +361,11 @@ public function deleteIndex(string $collection, string $id): bool */ public function getDocument(string $collection, string $id): Document { - if($collection === self::COLLECTION_COLLECTIONS && $id === self::COLLECTION_COLLECTIONS) { + if($collection === self::COLLECTIONS && $id === self::COLLECTIONS) { return new Document($this->collection); } - $collection = $this->getDocument(self::COLLECTION_COLLECTIONS, $collection); + $collection = $this->getDocument(self::COLLECTIONS, $collection); $document = $this->adapter->getDocument($collection->getId(), $id); // $validator = new Authorization($document, self::PERMISSION_READ); @@ -508,7 +508,7 @@ public function deleteDocument(string $collection, string $id): bool // 'filters' => [], // ], $options); - // $results = $this->adapter->find($this->getDocument(self::COLLECTION_COLLECTIONS, $collection), $options); + // $results = $this->adapter->find($this->getDocument(self::COLLECTIONS, $collection), $options); // foreach ($results as &$node) { // $node = $this->decode(new Document($node)); @@ -592,7 +592,7 @@ public function deleteDocument(string $collection, string $id): bool // throw new StructureException($validator->getDescription()); // var_dump($validator->getDescription()); return false; // } - // $new = new Document($this->adapter->updateDocument($this->getDocument(self::COLLECTION_COLLECTIONS, $new->getCollection()), $new->getId(), $new->getArrayCopy())); + // $new = new Document($this->adapter->updateDocument($this->getDocument(self::COLLECTIONS, $new->getCollection()), $new->getId(), $new->getArrayCopy())); // $new = $this->decode($new); @@ -640,7 +640,7 @@ public function deleteDocument(string $collection, string $id): bool // return $document; // } - // $collection = $this->getDocument(self::COLLECTION_COLLECTIONS, $document->getCollection(), true , false); + // $collection = $this->getDocument(self::COLLECTIONS, $document->getCollection(), true , false); // $rules = $collection->getAttribute('rules', []); // foreach ($rules as $key => $rule) { @@ -680,7 +680,7 @@ public function deleteDocument(string $collection, string $id): bool // return $document; // } - // $collection = $this->getDocument(self::COLLECTION_COLLECTIONS, $document->getCollection(), true , false); + // $collection = $this->getDocument(self::COLLECTIONS, $document->getCollection(), true , false); // $rules = $collection->getAttribute('rules', []); // foreach ($rules as $key => $rule) { diff --git a/src/Database/Validator/Structure.php b/src/Database/Validator/Structure.php index 89a658f97..7ada96b19 100644 --- a/src/Database/Validator/Structure.php +++ b/src/Database/Validator/Structure.php @@ -135,9 +135,9 @@ // return false; // } -// $collection = $this->getCollection(Database::COLLECTION_COLLECTIONS, $document->getCollection()); +// $collection = $this->getCollection(Database::COLLECTIONS, $document->getCollection()); -// if (\is_null($collection->getId()) || Database::COLLECTION_COLLECTIONS != $collection->getCollection()) { +// if (\is_null($collection->getId()) || Database::COLLECTIONS != $collection->getCollection()) { // $this->message = 'Collection "'.$collection->getCollection().'" not found'; // return false; // } diff --git a/tests/Database/Base.php b/tests/Database/Base.php index a7203639e..6a5b3a2a8 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -52,10 +52,10 @@ public function testCreateDeleteAttribute() $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'boolean', Database::VAR_BOOLEAN, 0)); // Array - $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'string', Database::VAR_STRING, 128, true, true)); - $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'integer', Database::VAR_INTEGER, 0, true, true)); - $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'float', Database::VAR_FLOAT, 0, true, true)); - $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'boolean', Database::VAR_BOOLEAN, 0, true, true)); + $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'string_list', Database::VAR_STRING, 128, true, true)); + $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'integer_list', Database::VAR_INTEGER, 0, true, true)); + $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'float_list', Database::VAR_FLOAT, 0, true, true)); + $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'boolean_list', Database::VAR_BOOLEAN, 0, true, true)); // Delete $this->assertEquals(true, static::getDatabase()->deleteAttribute('attributes', 'string1')); @@ -67,10 +67,10 @@ public function testCreateDeleteAttribute() $this->assertEquals(true, static::getDatabase()->deleteAttribute('attributes', 'boolean')); // Delete Array - $this->assertEquals(true, static::getDatabase()->deleteAttribute('attributes', 'string', true)); - $this->assertEquals(true, static::getDatabase()->deleteAttribute('attributes', 'integer', true)); - $this->assertEquals(true, static::getDatabase()->deleteAttribute('attributes', 'float', true)); - $this->assertEquals(true, static::getDatabase()->deleteAttribute('attributes', 'boolean', true)); + $this->assertEquals(true, static::getDatabase()->deleteAttribute('attributes', 'string_list')); + $this->assertEquals(true, static::getDatabase()->deleteAttribute('attributes', 'integer_list')); + $this->assertEquals(true, static::getDatabase()->deleteAttribute('attributes', 'float_list')); + $this->assertEquals(true, static::getDatabase()->deleteAttribute('attributes', 'boolean_list')); static::getDatabase()->deleteCollection('attributes'); } @@ -156,8 +156,8 @@ public function testGetDocument($document) // public function testCreateDocument() // { - // $collection1 = self::$database->createDocument(Database::COLLECTION_COLLECTIONS, [ - // '$collection' => Database::COLLECTION_COLLECTIONS, + // $collection1 = self::$database->createDocument(Database::COLLECTIONS, [ + // '$collection' => Database::COLLECTIONS, // '$permissions' => ['read' => ['*']], // 'name' => 'Create Documents', // 'rules' => [ @@ -326,8 +326,8 @@ public function testGetDocument($document) // 'list' => [$collection1->getId()], // ]; - // $collection2 = self::$database->createDocument(Database::COLLECTION_COLLECTIONS, [ - // '$collection' => Database::COLLECTION_COLLECTIONS, + // $collection2 = self::$database->createDocument(Database::COLLECTIONS, [ + // '$collection' => Database::COLLECTIONS, // '$permissions' => ['read' => ['*']], // 'name' => 'Create Documents', // 'rules' => $rules, @@ -562,13 +562,13 @@ public function testGetDocument($document) // public function testGetDocument() // { // // Mocked document - // $document = self::$database->getDocument(Database::COLLECTION_COLLECTIONS, Database::COLLECTION_USERS); + // $document = self::$database->getDocument(Database::COLLECTIONS, Database::COLLECTION_USERS); // $this->assertEquals(Database::COLLECTION_USERS, $document->getId()); - // $this->assertEquals(Database::COLLECTION_COLLECTIONS, $document->getCollection()); + // $this->assertEquals(Database::COLLECTIONS, $document->getCollection()); - // $collection1 = self::$database->createDocument(Database::COLLECTION_COLLECTIONS, [ - // '$collection' => Database::COLLECTION_COLLECTIONS, + // $collection1 = self::$database->createDocument(Database::COLLECTIONS, [ + // '$collection' => Database::COLLECTIONS, // '$permissions' => ['read' => ['*']], // 'name' => 'Create Documents', // 'rules' => [ @@ -637,8 +637,8 @@ public function testGetDocument($document) // public function testUpdateDocument() // { - // $collection1 = self::$database->createDocument(Database::COLLECTION_COLLECTIONS, [ - // '$collection' => Database::COLLECTION_COLLECTIONS, + // $collection1 = self::$database->createDocument(Database::COLLECTIONS, [ + // '$collection' => Database::COLLECTIONS, // '$permissions' => ['read' => ['*']], // 'name' => 'Create Documents', // 'rules' => [ @@ -901,8 +901,8 @@ public function testGetDocument($document) // 'list' => [$collection1->getId()], // ]; - // $collection2 = self::$database->createDocument(Database::COLLECTION_COLLECTIONS, [ - // '$collection' => Database::COLLECTION_COLLECTIONS, + // $collection2 = self::$database->createDocument(Database::COLLECTIONS, [ + // '$collection' => Database::COLLECTIONS, // '$permissions' => ['read' => ['*']], // 'name' => 'Create Documents', // 'rules' => $rules, @@ -1360,8 +1360,8 @@ public function testGetDocument($document) // public function testDeleteDocument() // { - // $collection1 = self::$database->createDocument(Database::COLLECTION_COLLECTIONS, [ - // '$collection' => Database::COLLECTION_COLLECTIONS, + // $collection1 = self::$database->createDocument(Database::COLLECTIONS, [ + // '$collection' => Database::COLLECTIONS, // '$permissions' => ['read' => ['*']], // 'name' => 'Create Documents', // 'rules' => [ @@ -1448,7 +1448,7 @@ public function testGetDocument($document) // $movies = $data['movies']; // foreach ($collections as $key => &$collection) { - // $collection = self::$database->createDocument(Database::COLLECTION_COLLECTIONS, $collection); + // $collection = self::$database->createDocument(Database::COLLECTIONS, $collection); // self::$database->createCollection($collection->getId(), [], []); // } From 6d3bb28237ca61e4fa8347dad6e528da7c555916 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 19 Apr 2021 00:53:56 +0300 Subject: [PATCH 036/190] POC - update, filters and casting --- src/Database/Adapter.php | 18 +- src/Database/Adapter/MariaDB.php | 76 ++++++- src/Database/Database.php | 373 +++++++++++++++---------------- 3 files changed, 269 insertions(+), 198 deletions(-) diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index f2246664a..68e358ac0 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -202,15 +202,15 @@ abstract public function getDocument(string $collection, string $id): Document; */ abstract public function createDocument(string $collection, Document $document): Document; - // /** - // * Update Document. - // * - // * @param Document $collection - // * @param array $data - // * - // * @return array - // */ - // abstract public function updateDocument(Document $collection, string $id, array $data): array; + /** + * Update Document. + * + * @param string $collection + * @param Document $document + * + * @return Document + */ + abstract public function updateDocument(string $collection, Document $document): Document; // /** // * Delete Node. diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index a740de011..ba907d4d5 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -320,7 +320,7 @@ public function createDocument(string $collection, Document $document): Document switch ($e->getCode()) { case 1062: case 23000: - throw new Duplicate(); + throw new Duplicate(); // TODO add test for catching this exception break; default: @@ -352,6 +352,80 @@ public function createDocument(string $collection, Document $document): Document return $document; } + /** + * Create Document + * + * @param string $collection + * @param Document $document + * + * @return Document + */ + public function updateDocument(string $collection, Document $document): Document + { + $attributes = $document->getAttributes(); + $name = $this->filter($collection); + $columns = ''; + + /** + * Update Attributes + */ + foreach ($attributes as $attribute => $value) { // Parse statement + $column = $this->filter($attribute); + $columns .= "`{$column}`" . '=:' . $column . ','; + } + + $stmt = $this->getPDO() + ->prepare("UPDATE {$this->getNamespace()}.{$name} + SET {$columns} _uid = :_uid WHERE _uid = :_uid"); + + $stmt->bindValue(':_uid', $document->getId(), PDO::PARAM_STR); + + foreach ($attributes as $attribute => $value) { + if(is_array($value)) { // arrays & objects should be saved as strings + $value = json_encode($value); + } + + $attribute = $this->filter($attribute); + $stmt->bindValue(':' . $attribute, $value, $this->getPDOType($value)); + } + + if(!empty($attributes)) { + $stmt->execute(); + } + + /** + * Update Permissions + */ + $stmt = $this->getPDO() + ->prepare("DELETE FROM {$this->getNamespace()}.{$name}_permissions + WHERE _uid = :_uid"); + + $stmt->bindValue(':_uid', $document->getId(), PDO::PARAM_STR); + + if(!$stmt->execute()) { + throw new Exception('Failed to clean permissions'); + } + + $stmt = $this->getPDO() + ->prepare("INSERT INTO {$this->getNamespace()}.{$name}_permissions + SET _uid = :_uid, _action = :_action, _role = :_role"); + + $stmt->bindValue(':_uid', $document->getId(), PDO::PARAM_STR); + + foreach ($document->getPermissions() as $action => $roles) { + foreach ($roles as $key => $role) { + $stmt->bindValue(':_action', $action, PDO::PARAM_STR); + $stmt->bindValue(':_role', $role, PDO::PARAM_STR); + + if(!$stmt->execute()) { + throw new Exception('Failed to save permission'); + } + } + } + + return $document; + } + /** * Get max STRING limit * diff --git a/src/Database/Database.php b/src/Database/Database.php index 66ca2bc59..7f0b7003c 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -52,41 +52,73 @@ class Database 'name' => 'collections', 'attributes' => [ [ - '$id' => 'type', + '$id' => 'name', 'type' => self::VAR_STRING, - 'size' => 64, + 'size' => 256, 'signed' => true, 'array' => false, 'filters' => [], ], [ - '$id' => 'size', - 'type' => self::VAR_INTEGER, - 'size' => 0, + '$id' => 'attributes', + 'type' => self::VAR_STRING, + 'size' => 1000000, 'signed' => true, - 'array' => false, + 'array' => true, 'filters' => [], ], [ - '$id' => 'signed', - 'type' => self::VAR_BOOLEAN, - 'size' => 0, + '$id' => 'indexes', + 'type' => self::VAR_STRING, + 'size' => 1000000, 'signed' => true, - 'array' => false, + 'array' => true, 'filters' => [], ], - [ - '$id' => 'array', - 'type' => self::VAR_BOOLEAN, - 'size' => 0, - 'signed' => true, - 'array' => false, - 'filters' => [], - ] ], 'indexes' => [], ]; + // protected $collection = [ + // '$id' => 'collections', + // 'name' => 'collections', + // 'attributes' => [ + // [ + // '$id' => 'type', + // 'type' => self::VAR_STRING, + // 'size' => 64, + // 'signed' => true, + // 'array' => false, + // 'filters' => [], + // ], + // [ + // '$id' => 'size', + // 'type' => self::VAR_INTEGER, + // 'size' => 0, + // 'signed' => true, + // 'array' => false, + // 'filters' => [], + // ], + // [ + // '$id' => 'signed', + // 'type' => self::VAR_BOOLEAN, + // 'size' => 0, + // 'signed' => true, + // 'array' => false, + // 'filters' => [], + // ], + // [ + // '$id' => 'array', + // 'type' => self::VAR_BOOLEAN, + // 'size' => 0, + // 'signed' => true, + // 'array' => false, + // 'filters' => [], + // ] + // ], + // 'indexes' => [], + // ]; + /** * @var array */ @@ -253,9 +285,11 @@ public function createAttribute(string $collection, string $id, string $type, in 'array' => $array, 'filters' => $filters, ]), Document::SET_TYPE_APPEND); - - //$this->updateDocument(); - + + if($collection->getId() !== self::COLLECTIONS) { + $this->updateDocument(self::COLLECTIONS, $collection); + } + switch ($type) { case self::VAR_STRING: if($size > $this->adapter->getStringLimit()) { @@ -374,7 +408,7 @@ public function getDocument(string $collection, string $id): Document // return new Document(); // } - // $document = $this->decode($document); + $document = $this->decode($collection, $document); return $document; } @@ -419,51 +453,48 @@ public function createDocument(string $collection, Document $document): Document /** * Update Document * - * @param array $collection - * @param array $id - * @param array $data + * @param string $collection + * @param array $document * - * @return Document|false + * @return Document * * @throws Exception */ - public function updateDocument(string $collection, string $id, array $data): Document + public function updateDocument(string $collection, Document $document): Document { - if (!isset($data['$id'])) { + if (!$document->getId()) { throw new Exception('Must define $id attribute'); } - $document = $this->getDocument($collection, $id); // TODO make sure user don\'t need read permission for write operations + $old = $this->getDocument($collection, $document->getId()); // TODO make sure user don\'t need read permission for write operations // Make sure reserved keys stay constant - $data['$id'] = $document->getId(); - $data['$collection'] = $document->getCollection(); + // $data['$id'] = $old->getId(); + // $data['$collection'] = $old->getCollection(); - // $validator = new Authorization($document, 'write'); + // $validator = new Authorization($old, 'write'); - // if (!$validator->isValid($document->getPermissions())) { // Check if user has write access to this document + // if (!$validator->isValid($old->getPermissions())) { // Check if user has write access to this document // throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false; // } - $new = new Document($data); - - // if (!$validator->isValid($new->getPermissions())) { // Check if user has write access to this document + // if (!$validator->isValid($document->getPermissions())) { // Check if user has write access to this document // throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false; // } - // $new = $this->encode($new); + // $document = $this->encode($document); // $validator = new Structure($this); - // if (!$validator->isValid($new)) { // Make sure updated structure still apply collection rules (if any) + // if (!$validator->isValid($document)) { // Make sure updated structure still apply collection rules (if any) // throw new StructureException($validator->getDescription()); // var_dump($validator->getDescription()); return false; // } - // $new = new Document($this->adapter->updateDocument($collection, $new->getArrayCopy())); + $document = $this->adapter->updateDocument($collection, $document); // $new = $this->decode($new); - return $new; + return $document; } /** @@ -599,164 +630,130 @@ public function deleteDocument(string $collection, string $id): bool // return $new; // } - // /** - // * @return array - // */ - // public function getDebug() - // { - // return $this->adapter->getDebug(); - // } - - // /** - // * @return int - // */ - // public function getSum() - // { - // $debug = $this->getDebug(); - - // return (isset($debug['sum'])) ? $debug['sum'] : 0; - // } - - // /** - // * Add Attribute Filter - // * - // * @param string $name - // * @param callable $encode - // * @param callable $decode - // * - // * @return void - // */ - // static public function addFilter(string $name, callable $encode, callable $decode): void - // { - // self::$filters[$name] = [ - // 'encode' => $encode, - // 'decode' => $decode, - // ]; - // } - - // public function encode(Document $document):Document - // { - // if($document->getCollection() === null) { - // return $document; - // } - - // $collection = $this->getDocument(self::COLLECTIONS, $document->getCollection(), true , false); - // $rules = $collection->getAttribute('rules', []); - - // foreach ($rules as $key => $rule) { - // $key = $rule->getAttribute('key', null); - // $type = $rule->getAttribute('type', null); - // $array = $rule->getAttribute('array', false); - // $filters = $rule->getAttribute('filter', []); - // $value = $document->getAttribute($key, null); - - // if (($value !== null)) { - // if ($type === self::VAR_DOCUMENT) { - // if($array) { - // $list = []; - // foreach ($value as $child) { - // $list[] = $this->encode($child); - // } - - // $document->setAttribute($key, $list); - // } else { - // $document->setAttribute($key, $this->encode($value)); - // } - // } else { - // foreach ($filters as $filter) { - // $value = $this->encodeAttribute($filter, $value); - // $document->setAttribute($key, $value); - // } - // } - // } - // } + /** + * Add Attribute Filter + * + * @param string $name + * @param callable $encode + * @param callable $decode + * + * @return void + */ + static public function addFilter(string $name, callable $encode, callable $decode): void + { + self::$filters[$name] = [ + 'encode' => $encode, + 'decode' => $decode, + ]; + } - // return $document; - // } + public function encode(Document $collection, Document $document):Document + { + $rules = $collection->getAttribute('rules', []); + + foreach ($rules as $key => $rule) { + $key = $rule->getAttribute('key', null); + $type = $rule->getAttribute('type', null); + $array = $rule->getAttribute('array', false); + $filters = $rule->getAttribute('filter', []); + $value = $document->getAttribute($key, null); + + if (($value !== null)) { + foreach ($filters as $filter) { + $value = $this->encodeAttribute($filter, $value); + $document->setAttribute($key, $value); + } + } + } - // public function decode(Document $document):Document - // { - // if($document->getCollection() === null) { - // return $document; - // } + return $document; + } - // $collection = $this->getDocument(self::COLLECTIONS, $document->getCollection(), true , false); - // $rules = $collection->getAttribute('rules', []); - - // foreach ($rules as $key => $rule) { - // $key = $rule->getAttribute('key', null); - // $type = $rule->getAttribute('type', null); - // $array = $rule->getAttribute('array', false); - // $filters = $rule->getAttribute('filter', []); - // $value = $document->getAttribute($key, null); - - // if (($value !== null)) { - // if ($type === self::VAR_DOCUMENT) { - // if($array) { - // $list = []; - // foreach ($value as $child) { - // $list[] = $this->decode($child); - // } - - // $document->setAttribute($key, $list); - // } else { - // $document->setAttribute($key, $this->decode($value)); - // } - // } else { - // foreach (array_reverse($filters) as $filter) { - // $value = $this->decodeAttribute($filter, $value); - // $document->setAttribute($key, $value); - // } - // } - // } - // } + public function decode(Document $collection, Document $document):Document + { + $rules = $collection->getAttribute('attributes', []); + + foreach ($rules as $rule) { + $key = $rule['$id'] ?? ''; + $type = $rule['type'] ?? ''; + $array = $rule['array'] ?? false; + $filters = $rule['filters'] ?? []; + $value = $document->getAttribute($key, null); + + if($array) { + $value = json_decode($value, true); + } + else { + $value = [$value]; + } + + foreach ($value as &$node) { + switch ($type) { + case self::VAR_BOOLEAN: + $node = (bool)$node; + break; + case self::VAR_INTEGER: + $node = (int)$node; + break; + case self::VAR_FLOAT: + $node = (float)$node; + break; + + default: + # code... + break; + } + } + + $document->setAttribute($key, ($array) ? $value : $value[0]); + } - // return $document; - // } + return $document; + } - // /** - // * Encode Attribute - // * - // * @param string $name - // * @param mixed $value - // */ - // static protected function encodeAttribute(string $name, $value) - // { - // if (!isset(self::$filters[$name])) { - // return $value; - // throw new Exception('Filter not found'); - // } + /** + * Encode Attribute + * + * @param string $name + * @param mixed $value + */ + static protected function encodeAttribute(string $name, $value) + { + if (!isset(self::$filters[$name])) { + return $value; + throw new Exception('Filter not found'); + } - // try { - // $value = self::$filters[$name]['encode']($value); - // } catch (\Throwable $th) { - // $value = null; - // } + try { + $value = self::$filters[$name]['encode']($value); + } catch (\Throwable $th) { + $value = null; + } - // return $value; - // } + return $value; + } - // /** - // * Decode Attribute - // * - // * @param string $name - // * @param mixed $value - // */ - // static protected function decodeAttribute(string $name, $value) - // { - // if (!isset(self::$filters[$name])) { - // return $value; - // throw new Exception('Filter not found'); - // } + /** + * Decode Attribute + * + * @param string $name + * @param mixed $value + */ + static protected function decodeAttribute(string $name, $value) + { + if (!isset(self::$filters[$name])) { + return $value; + throw new Exception('Filter not found'); + } - // try { - // $value = self::$filters[$name]['decode']($value); - // } catch (\Throwable $th) { - // $value = null; - // } + try { + $value = self::$filters[$name]['decode']($value); + } catch (\Throwable $th) { + $value = null; + } - // return $value; - // } + return $value; + } /** * Get 13 Chars Unique ID. From bfe83195b133f1965a82b492223e0426d56473a8 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 19 Apr 2021 08:33:10 +0300 Subject: [PATCH 037/190] Updated Readme --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 9a3c39587..d10a2b474 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,12 @@ require_once __DIR__ . '/../../vendor/autoload.php'; ``` +## TODOS + +- [ ] Validate original document before editing `$id` +- [ ] Test duplicated document exception +- [ ] Delete / Update documents before cleaning cache + ## System Requirements Utopia Framework requires PHP 7.3 or later. We recommend using the latest PHP version whenever possible. @@ -48,6 +54,11 @@ docker-compose exec tests vendor/bin/psalm --show-info=true + [https://twitter.com/eldadfux](https://twitter.com/eldadfux) + [https://github.com/eldadfux](https://github.com/eldadfux) +**Brandon Leckemby** + ++ [https://github.com/kodumbeats](https://github.com/kodumbeats) ++ [blog.leckemby.me](blog.leckemby.me) + ## Copyright and license The MIT License (MIT) [http://www.opensource.org/licenses/mit-license.php](http://www.opensource.org/licenses/mit-license.php) From 64dae609af66ba81090bddaf77f8c1bc01caae62 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 20 Apr 2021 09:30:24 +0300 Subject: [PATCH 038/190] Updated Readme and fixed minor errors --- README.md | 2 ++ src/Database/Adapter/MariaDB.php | 6 ++-- src/Database/Database.php | 51 ++++---------------------------- 3 files changed, 11 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index d10a2b474..e2dabc332 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,9 @@ require_once __DIR__ . '/../../vendor/autoload.php'; - [ ] Validate original document before editing `$id` - [ ] Test duplicated document exception +- [ ] Test no one can overwrite exciting documents/collections without permission - [ ] Delete / Update documents before cleaning cache +- [ ] Test for query timeout limits ## System Requirements diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index ba907d4d5..fac0251ab 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -533,9 +533,9 @@ protected function getSQLType(string $type, int $size, bool $signed = true): str * @param string $type * @param int $size * - * @return string + * @return int */ - protected function getPDOType($value): string + protected function getPDOType($value): int { switch (gettype($value)) { case 'string': @@ -550,7 +550,7 @@ protected function getPDOType($value): string return PDO::PARAM_INT; break; - case 'float': + // case 'float': // (for historical reasons "double" is returned in case of a float, and not simply "float") case 'double': return PDO::PARAM_STR; break; diff --git a/src/Database/Database.php b/src/Database/Database.php index 7f0b7003c..12a41aa8f 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -79,46 +79,6 @@ class Database 'indexes' => [], ]; - // protected $collection = [ - // '$id' => 'collections', - // 'name' => 'collections', - // 'attributes' => [ - // [ - // '$id' => 'type', - // 'type' => self::VAR_STRING, - // 'size' => 64, - // 'signed' => true, - // 'array' => false, - // 'filters' => [], - // ], - // [ - // '$id' => 'size', - // 'type' => self::VAR_INTEGER, - // 'size' => 0, - // 'signed' => true, - // 'array' => false, - // 'filters' => [], - // ], - // [ - // '$id' => 'signed', - // 'type' => self::VAR_BOOLEAN, - // 'size' => 0, - // 'signed' => true, - // 'array' => false, - // 'filters' => [], - // ], - // [ - // '$id' => 'array', - // 'type' => self::VAR_BOOLEAN, - // 'size' => 0, - // 'signed' => true, - // 'array' => false, - // 'filters' => [], - // ] - // ], - // 'indexes' => [], - // ]; - /** * @var array */ @@ -287,7 +247,7 @@ public function createAttribute(string $collection, string $id, string $type, in ]), Document::SET_TYPE_APPEND); if($collection->getId() !== self::COLLECTIONS) { - $this->updateDocument(self::COLLECTIONS, $collection); + $this->updateDocument(self::COLLECTIONS, $collection->getId(), $collection); } switch ($type) { @@ -454,19 +414,20 @@ public function createDocument(string $collection, Document $document): Document * Update Document * * @param string $collection - * @param array $document + * @param string $id + * @param Document $document * * @return Document * * @throws Exception */ - public function updateDocument(string $collection, Document $document): Document + public function updateDocument(string $collection, string $id, Document $document): Document { - if (!$document->getId()) { + if (!$document->getId() || !$id) { throw new Exception('Must define $id attribute'); } - $old = $this->getDocument($collection, $document->getId()); // TODO make sure user don\'t need read permission for write operations + $old = $this->getDocument($collection, $id); // TODO make sure user don\'t need read permission for write operations // Make sure reserved keys stay constant // $data['$id'] = $old->getId(); From 125762e5447e4159b2924c69f54ce25d80cf2434 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 20 Apr 2021 09:43:51 +0300 Subject: [PATCH 039/190] Fixed Psalm errors --- composer.lock | 218 ++++++++++++----------- src/Database/Adapter.php | 2 +- src/Database/Adapter/MariaDB.php | 11 +- src/Database/Database.php | 8 +- src/Database/Validator/Authorization.php | 2 +- src/Database/Validator/DocumentId.php | 8 - 6 files changed, 125 insertions(+), 124 deletions(-) diff --git a/composer.lock b/composer.lock index c07f4982b..4a88115a8 100644 --- a/composer.lock +++ b/composer.lock @@ -81,16 +81,16 @@ }, { "name": "jean85/pretty-package-versions", - "version": "1.5.1", + "version": "1.6.0", "source": { "type": "git", "url": "https://github.com/Jean85/pretty-package-versions.git", - "reference": "a917488320c20057da87f67d0d40543dd9427f7a" + "reference": "1e0104b46f045868f11942aea058cd7186d6c303" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/a917488320c20057da87f67d0d40543dd9427f7a", - "reference": "a917488320c20057da87f67d0d40543dd9427f7a", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/1e0104b46f045868f11942aea058cd7186d6c303", + "reference": "1e0104b46f045868f11942aea058cd7186d6c303", "shasum": "" }, "require": { @@ -130,9 +130,9 @@ ], "support": { "issues": "https://github.com/Jean85/pretty-package-versions/issues", - "source": "https://github.com/Jean85/pretty-package-versions/tree/1.5.1" + "source": "https://github.com/Jean85/pretty-package-versions/tree/1.6.0" }, - "time": "2020-09-14T08:43:34+00:00" + "time": "2021-02-04T16:20:16+00:00" }, { "name": "mongodb/mongodb", @@ -204,7 +204,7 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.22.0", + "version": "v1.22.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", @@ -267,7 +267,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.22.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.22.1" }, "funding": [ { @@ -427,16 +427,16 @@ }, { "name": "amphp/byte-stream", - "version": "v1.8.0", + "version": "v1.8.1", "source": { "type": "git", "url": "https://github.com/amphp/byte-stream.git", - "reference": "f0c20cf598a958ba2aa8c6e5a71c697d652c7088" + "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/byte-stream/zipball/f0c20cf598a958ba2aa8c6e5a71c697d652c7088", - "reference": "f0c20cf598a958ba2aa8c6e5a71c697d652c7088", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/acbd8002b3536485c997c4e019206b3f10ca15bd", + "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd", "shasum": "" }, "require": { @@ -492,9 +492,15 @@ "support": { "irc": "irc://irc.freenode.org/amphp", "issues": "https://github.com/amphp/byte-stream/issues", - "source": "https://github.com/amphp/byte-stream/tree/master" + "source": "https://github.com/amphp/byte-stream/tree/v1.8.1" }, - "time": "2020-06-29T18:35:05+00:00" + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2021-03-30T17:13:30+00:00" }, { "name": "composer/semver", @@ -579,16 +585,16 @@ }, { "name": "composer/xdebug-handler", - "version": "1.4.5", + "version": "1.4.6", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "f28d44c286812c714741478d968104c5e604a1d4" + "reference": "f27e06cd9675801df441b3656569b328e04aa37c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/f28d44c286812c714741478d968104c5e604a1d4", - "reference": "f28d44c286812c714741478d968104c5e604a1d4", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/f27e06cd9675801df441b3656569b328e04aa37c", + "reference": "f27e06cd9675801df441b3656569b328e04aa37c", "shasum": "" }, "require": { @@ -596,7 +602,8 @@ "psr/log": "^1.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8" + "phpstan/phpstan": "^0.12.55", + "symfony/phpunit-bridge": "^4.2 || ^5" }, "type": "library", "autoload": { @@ -622,7 +629,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/xdebug-handler/issues", - "source": "https://github.com/composer/xdebug-handler/tree/1.4.5" + "source": "https://github.com/composer/xdebug-handler/tree/1.4.6" }, "funding": [ { @@ -638,7 +645,7 @@ "type": "tidelift" } ], - "time": "2020-11-13T08:04:11+00:00" + "time": "2021-03-25T17:01:18+00:00" }, { "name": "dnoegel/php-xdg-base-dir", @@ -793,16 +800,16 @@ }, { "name": "felixfbecker/language-server-protocol", - "version": "v1.5.0", + "version": "1.5.1", "source": { "type": "git", "url": "https://github.com/felixfbecker/php-language-server-protocol.git", - "reference": "85e83cacd2ed573238678c6875f8f0d7ec699541" + "reference": "9d846d1f5cf101deee7a61c8ba7caa0a975cd730" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/85e83cacd2ed573238678c6875f8f0d7ec699541", - "reference": "85e83cacd2ed573238678c6875f8f0d7ec699541", + "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/9d846d1f5cf101deee7a61c8ba7caa0a975cd730", + "reference": "9d846d1f5cf101deee7a61c8ba7caa0a975cd730", "shasum": "" }, "require": { @@ -843,9 +850,9 @@ ], "support": { "issues": "https://github.com/felixfbecker/php-language-server-protocol/issues", - "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.0" + "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/1.5.1" }, - "time": "2020-10-23T13:55:30+00:00" + "time": "2021-02-22T14:02:09+00:00" }, { "name": "myclabs/deep-copy", @@ -1127,16 +1134,16 @@ }, { "name": "phar-io/version", - "version": "3.0.4", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/phar-io/version.git", - "reference": "e4782611070e50613683d2b9a57730e9a3ba5451" + "reference": "bae7c545bef187884426f042434e561ab1ddb182" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/e4782611070e50613683d2b9a57730e9a3ba5451", - "reference": "e4782611070e50613683d2b9a57730e9a3ba5451", + "url": "https://api.github.com/repos/phar-io/version/zipball/bae7c545bef187884426f042434e561ab1ddb182", + "reference": "bae7c545bef187884426f042434e561ab1ddb182", "shasum": "" }, "require": { @@ -1172,9 +1179,9 @@ "description": "Library for handling version information and constraints", "support": { "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.0.4" + "source": "https://github.com/phar-io/version/tree/3.1.0" }, - "time": "2020-12-13T23:18:30+00:00" + "time": "2021-02-23T14:00:09+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -1336,16 +1343,16 @@ }, { "name": "phpspec/prophecy", - "version": "1.12.2", + "version": "1.13.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "245710e971a030f42e08f4912863805570f23d39" + "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/245710e971a030f42e08f4912863805570f23d39", - "reference": "245710e971a030f42e08f4912863805570f23d39", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/be1996ed8adc35c3fd795488a653f4b518be70ea", + "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea", "shasum": "" }, "require": { @@ -1397,22 +1404,22 @@ ], "support": { "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/1.12.2" + "source": "https://github.com/phpspec/prophecy/tree/1.13.0" }, - "time": "2020-12-19T10:15:11+00:00" + "time": "2021-03-17T13:42:18+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.5", + "version": "9.2.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "f3e026641cc91909d421802dd3ac7827ebfd97e1" + "reference": "f6293e1b30a2354e8428e004689671b83871edde" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f3e026641cc91909d421802dd3ac7827ebfd97e1", - "reference": "f3e026641cc91909d421802dd3ac7827ebfd97e1", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f6293e1b30a2354e8428e004689671b83871edde", + "reference": "f6293e1b30a2354e8428e004689671b83871edde", "shasum": "" }, "require": { @@ -1468,7 +1475,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.5" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.6" }, "funding": [ { @@ -1476,7 +1483,7 @@ "type": "github" } ], - "time": "2020-11-28T06:44:49+00:00" + "time": "2021-03-28T07:26:59+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1721,16 +1728,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.1", + "version": "9.5.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "e7bdf4085de85a825f4424eae52c99a1cec2f360" + "reference": "c73c6737305e779771147af66c96ca6a7ed8a741" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e7bdf4085de85a825f4424eae52c99a1cec2f360", - "reference": "e7bdf4085de85a825f4424eae52c99a1cec2f360", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c73c6737305e779771147af66c96ca6a7ed8a741", + "reference": "c73c6737305e779771147af66c96ca6a7ed8a741", "shasum": "" }, "require": { @@ -1808,7 +1815,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.1" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.4" }, "funding": [ { @@ -1820,31 +1827,26 @@ "type": "github" } ], - "time": "2021-01-17T07:42:25+00:00" + "time": "2021-03-23T07:16:29+00:00" }, { "name": "psr/container", - "version": "1.0.0", + "version": "1.1.1", "source": { "type": "git", "url": "https://github.com/php-fig/container.git", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=7.2.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, "autoload": { "psr-4": { "Psr\\Container\\": "src/" @@ -1857,7 +1859,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common Container Interface (PHP FIG PSR-11)", @@ -1871,9 +1873,9 @@ ], "support": { "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/master" + "source": "https://github.com/php-fig/container/tree/1.1.1" }, - "time": "2017-02-14T16:28:37+00:00" + "time": "2021-03-05T17:36:06+00:00" }, { "name": "psr/log", @@ -2891,16 +2893,16 @@ }, { "name": "symfony/console", - "version": "v5.2.1", + "version": "v5.2.6", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "47c02526c532fb381374dab26df05e7313978976" + "reference": "35f039df40a3b335ebf310f244cb242b3a83ac8d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/47c02526c532fb381374dab26df05e7313978976", - "reference": "47c02526c532fb381374dab26df05e7313978976", + "url": "https://api.github.com/repos/symfony/console/zipball/35f039df40a3b335ebf310f244cb242b3a83ac8d", + "reference": "35f039df40a3b335ebf310f244cb242b3a83ac8d", "shasum": "" }, "require": { @@ -2959,7 +2961,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Console Component", + "description": "Eases the creation of beautiful and testable command line interfaces", "homepage": "https://symfony.com", "keywords": [ "cli", @@ -2968,7 +2970,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.2.1" + "source": "https://github.com/symfony/console/tree/v5.2.6" }, "funding": [ { @@ -2984,11 +2986,11 @@ "type": "tidelift" } ], - "time": "2020-12-18T08:03:05+00:00" + "time": "2021-03-28T09:42:18+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.22.0", + "version": "v1.22.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -3047,7 +3049,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.22.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.22.1" }, "funding": [ { @@ -3067,16 +3069,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.22.0", + "version": "v1.22.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "267a9adeb8ecb8071040a740930e077cdfb987af" + "reference": "5601e09b69f26c1828b13b6bb87cb07cddba3170" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/267a9adeb8ecb8071040a740930e077cdfb987af", - "reference": "267a9adeb8ecb8071040a740930e077cdfb987af", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/5601e09b69f26c1828b13b6bb87cb07cddba3170", + "reference": "5601e09b69f26c1828b13b6bb87cb07cddba3170", "shasum": "" }, "require": { @@ -3128,7 +3130,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.22.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.22.1" }, "funding": [ { @@ -3144,20 +3146,20 @@ "type": "tidelift" } ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2021-01-22T09:19:47+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.22.0", + "version": "v1.22.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "6e971c891537eb617a00bb07a43d182a6915faba" + "reference": "43a0283138253ed1d48d352ab6d0bdb3f809f248" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/6e971c891537eb617a00bb07a43d182a6915faba", - "reference": "6e971c891537eb617a00bb07a43d182a6915faba", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/43a0283138253ed1d48d352ab6d0bdb3f809f248", + "reference": "43a0283138253ed1d48d352ab6d0bdb3f809f248", "shasum": "" }, "require": { @@ -3212,7 +3214,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.22.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.22.1" }, "funding": [ { @@ -3228,20 +3230,20 @@ "type": "tidelift" } ], - "time": "2021-01-07T17:09:11+00:00" + "time": "2021-01-22T09:19:47+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.22.0", + "version": "v1.22.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "f377a3dd1fde44d37b9831d68dc8dea3ffd28e13" + "reference": "5232de97ee3b75b0360528dae24e73db49566ab1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/f377a3dd1fde44d37b9831d68dc8dea3ffd28e13", - "reference": "f377a3dd1fde44d37b9831d68dc8dea3ffd28e13", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/5232de97ee3b75b0360528dae24e73db49566ab1", + "reference": "5232de97ee3b75b0360528dae24e73db49566ab1", "shasum": "" }, "require": { @@ -3292,7 +3294,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.22.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.22.1" }, "funding": [ { @@ -3308,11 +3310,11 @@ "type": "tidelift" } ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2021-01-22T09:19:47+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.22.0", + "version": "v1.22.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", @@ -3371,7 +3373,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.22.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.22.1" }, "funding": [ { @@ -3391,21 +3393,21 @@ }, { "name": "symfony/service-contracts", - "version": "v2.2.0", + "version": "v2.4.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1" + "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d15da7ba4957ffb8f1747218be9e1a121fd298a1", - "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb", + "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb", "shasum": "" }, "require": { "php": ">=7.2.5", - "psr/container": "^1.0" + "psr/container": "^1.1" }, "suggest": { "symfony/service-implementation": "" @@ -3413,7 +3415,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.2-dev" + "dev-main": "2.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -3450,7 +3452,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/master" + "source": "https://github.com/symfony/service-contracts/tree/v2.4.0" }, "funding": [ { @@ -3466,20 +3468,20 @@ "type": "tidelift" } ], - "time": "2020-09-07T11:33:47+00:00" + "time": "2021-04-01T10:43:52+00:00" }, { "name": "symfony/string", - "version": "v5.2.1", + "version": "v5.2.6", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "5bd67751d2e3f7d6f770c9154b8fbcb2aa05f7ed" + "reference": "ad0bd91bce2054103f5eaa18ebeba8d3bc2a0572" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/5bd67751d2e3f7d6f770c9154b8fbcb2aa05f7ed", - "reference": "5bd67751d2e3f7d6f770c9154b8fbcb2aa05f7ed", + "url": "https://api.github.com/repos/symfony/string/zipball/ad0bd91bce2054103f5eaa18ebeba8d3bc2a0572", + "reference": "ad0bd91bce2054103f5eaa18ebeba8d3bc2a0572", "shasum": "" }, "require": { @@ -3522,7 +3524,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony String component", + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", "homepage": "https://symfony.com", "keywords": [ "grapheme", @@ -3533,7 +3535,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.2.1" + "source": "https://github.com/symfony/string/tree/v5.2.6" }, "funding": [ { @@ -3549,7 +3551,7 @@ "type": "tidelift" } ], - "time": "2020-12-05T07:33:16+00:00" + "time": "2021-03-17T17:12:15+00:00" }, { "name": "theseer/tokenizer", diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index 68e358ac0..e24947e87 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -188,7 +188,7 @@ abstract public function deleteIndex(string $collection, string $id): bool; * @param string $collection * @param string $id * - * @return array + * @return Document */ abstract public function getDocument(string $collection, string $id): Document; diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index fac0251ab..58f014f56 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -189,7 +189,9 @@ public function deleteAttribute(string $collection, string $id, bool $array = fa * @param string $collection * @param string $id * @param string $type - * @param int $size + * @param array $attributes + * @param array $lengths + * @param array $orders * * @return bool */ @@ -237,7 +239,7 @@ public function deleteIndex(string $collection, string $id): bool * @param string $collection * @param string $id * - * @return array + * @return Document */ public function getDocument(string $collection, string $id): Document { @@ -530,12 +532,11 @@ protected function getSQLType(string $type, int $size, bool $signed = true): str /** * Get PDO Type * - * @param string $type - * @param int $size + * @param string $value * * @return int */ - protected function getPDOType($value): int + protected function getPDOType(string $value): int { switch (gettype($value)) { case 'string': diff --git a/src/Database/Database.php b/src/Database/Database.php index 12a41aa8f..17cc49d1c 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -46,6 +46,8 @@ class Database /** * Parent Collection * Defines the structure for both system and custom collections + * + * @var array */ protected $collection = [ '$id' => 'collections', @@ -462,7 +464,7 @@ public function updateDocument(string $collection, string $id, Document $documen * @param string $collection * @param string $id * - * @return Document|false + * @return false * * @throws AuthorizationException */ @@ -677,6 +679,8 @@ public function decode(Document $collection, Document $document):Document * * @param string $name * @param mixed $value + * + * @return mixed */ static protected function encodeAttribute(string $name, $value) { @@ -699,6 +703,8 @@ static protected function encodeAttribute(string $name, $value) * * @param string $name * @param mixed $value + * + * @return mixed */ static protected function decodeAttribute(string $name, $value) { diff --git a/src/Database/Validator/Authorization.php b/src/Database/Validator/Authorization.php index b2d07ce2c..47a7a85b4 100644 --- a/src/Database/Validator/Authorization.php +++ b/src/Database/Validator/Authorization.php @@ -70,7 +70,7 @@ public function isValid($permissions) return false; } - $permission = null; + $permission = ''; foreach ($permissions[$this->action] as $permission) { $permission = \str_replace(':{self}', ':'.$this->document->getId(), $permission); diff --git a/src/Database/Validator/DocumentId.php b/src/Database/Validator/DocumentId.php index 7d69e9be5..40837c1f9 100644 --- a/src/Database/Validator/DocumentId.php +++ b/src/Database/Validator/DocumentId.php @@ -60,14 +60,6 @@ public function isValid($id) { $document = $this->database->getDocument($this->collection, $id); - if (!$document) { - return false; - } - - if (!$document instanceof Document) { - return false; - } - if (!$document->getId()) { return false; } From b40b56b9f5cc2c8677a958388ed55a69a801c35c Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Wed, 21 Apr 2021 07:42:52 +0300 Subject: [PATCH 040/190] Added delete method --- src/Database/Adapter.php | 18 ++++++------- src/Database/Adapter/MariaDB.php | 41 ++++++++++++++++++++++++++-- src/Database/Database.php | 11 +++++--- tests/Database/Base.php | 46 +++++++++++++++++++++++++++++++- 4 files changed, 101 insertions(+), 15 deletions(-) diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index e24947e87..227909401 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -212,15 +212,15 @@ abstract public function createDocument(string $collection, Document $document): */ abstract public function updateDocument(string $collection, Document $document): Document; - // /** - // * Delete Node. - // * - // * @param Document $collection - // * @param string $id - // * - // * @return array - // */ - // abstract public function deleteDocument(Document $collection, string $id): bool; + /** + * Delete Document. + * + * @param string $collection + * @param string $id + * + * @return array + */ + abstract public function deleteDocument(string $collection, string $id): bool; // /** // * Find. diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 58f014f56..959b9b877 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -313,6 +313,7 @@ public function createDocument(string $collection, Document $document): Document } $attribute = $this->filter($attribute); + $value = (is_bool($value)) ? (int)$value : $value; $stmt->bindValue(':' . $attribute, $value, $this->getPDOType($value)); } @@ -355,7 +356,7 @@ public function createDocument(string $collection, Document $document): Document } /** - * Create Document + * Update Document * * @param string $collection * @param Document $document @@ -388,6 +389,7 @@ public function updateDocument(string $collection, Document $document): Document } $attribute = $this->filter($attribute); + $value = (is_bool($value)) ? (int)$value : $value; $stmt->bindValue(':' . $attribute, $value, $this->getPDOType($value)); } @@ -428,6 +430,41 @@ public function updateDocument(string $collection, Document $document): Document return $document; } + /** + * Delete Document + * + * @param string $collection + * @param Document $document + * + * @return Document + */ + public function deleteDocument(string $collection, string $id): bool + { + $name = $this->filter($collection); + + $stmt = $this->getPDO() + ->prepare("DELETE FROM {$this->getNamespace()}.{$name} + WHERE _uid = :_uid LIMIT 1"); + + $stmt->bindValue(':_uid', $id, PDO::PARAM_STR); + + if(!$stmt->execute()) { + throw new Exception('Failed to clean document'); + } + + $stmt = $this->getPDO() + ->prepare("DELETE FROM {$this->getNamespace()}.{$name}_permissions + WHERE _uid = :_uid"); + + $stmt->bindValue(':_uid', $id, PDO::PARAM_STR); + + if(!$stmt->execute()) { + throw new Exception('Failed to clean permissions'); + } + + return true; + } + /** * Get max STRING limit * @@ -544,7 +581,7 @@ protected function getPDOType(string $value): int break; case 'boolean': - return PDO::PARAM_BOOL; + return PDO::PARAM_INT; break; case 'integer': diff --git a/src/Database/Database.php b/src/Database/Database.php index 17cc49d1c..281ee0a06 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -364,12 +364,18 @@ public function getDocument(string $collection, string $id): Document $collection = $this->getDocument(self::COLLECTIONS, $collection); $document = $this->adapter->getDocument($collection->getId(), $id); + $document->setAttribute('$collection', $collection->getId()); + // $validator = new Authorization($document, self::PERMISSION_READ); // if (!$validator->isValid($document->getPermissions())) { // Check if user has read access to this document // return new Document(); // } + if($document->isEmpty()) { + return $document; + } + $document = $this->decode($collection, $document); return $document; @@ -403,6 +409,7 @@ public function createDocument(string $collection, Document $document): Document $document ->setAttribute('$id', empty($document->getId()) ? $this->getId(): $document->getId()) + ->setAttribute('$collection', $collection) ; $document = $this->adapter->createDocument($collection, $document); @@ -478,9 +485,7 @@ public function deleteDocument(string $collection, string $id): bool // throw new AuthorizationException($validator->getDescription()); // } - // return new Document($this->adapter->deleteDocument($collection, $id)); - - return false; + return $this->adapter->deleteDocument($collection, $id); } // /** diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 6a5b3a2a8..2d9d5e118 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -137,7 +137,7 @@ public function testCreateDocument() /** * @depends testCreateDocument */ - public function testGetDocument($document) + public function testGetDocument(Document $document) { $document = static::getDatabase()->getDocument('documents', $document->getId()); @@ -152,6 +152,50 @@ public function testGetDocument($document) $this->assertEquals(true, $document->getAttribute('boolean')); $this->assertIsArray($document->getAttribute('colors')); $this->assertEquals(['pink', 'green', 'blue'], $document->getAttribute('colors')); + + return $document; + } + + /** + * @depends testGetDocument + */ + public function testUpdateDocument(Document $document) + { + $document + ->setAttribute('string', 'text📝 updated') + ->setAttribute('integer', 6) + ->setAttribute('float', 5.56) + ->setAttribute('boolean', false) + ->setAttribute('colors', 'red', Document::SET_TYPE_APPEND) + ; + + $new = $this->getDatabase()->updateDocument($document->getCollection(), $document->getId(), $document); + + $this->assertNotEmpty(true, $new->getId()); + $this->assertIsString($new->getAttribute('string')); + $this->assertEquals('text📝 updated', $new->getAttribute('string')); + $this->assertIsInt($new->getAttribute('integer')); + $this->assertEquals(6, $new->getAttribute('integer')); + $this->assertIsFloat($new->getAttribute('float')); + $this->assertEquals(5.56, $new->getAttribute('float')); + $this->assertIsBool($new->getAttribute('boolean')); + $this->assertEquals(false, $new->getAttribute('boolean')); + $this->assertIsArray($new->getAttribute('colors')); + $this->assertEquals(['pink', 'green', 'blue', 'red'], $new->getAttribute('colors')); + + return $document; + } + + /** + * @depends testUpdateDocument + */ + public function testDeleteDocument(Document $document) + { + $result = $this->getDatabase()->deleteDocument($document->getCollection(), $document->getId()); + $document = $this->getDatabase()->getDocument($document->getCollection(), $document->getId()); + + $this->assertEquals(true, $result); + $this->assertEquals(true, $document->isEmpty()); } // public function testCreateDocument() From 617da888cda0974a33518c2341520116ed48e176 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Wed, 21 Apr 2021 08:28:49 +0300 Subject: [PATCH 041/190] Updated todos + a new dependency --- README.md | 12 ++++++++++- composer.json | 1 + composer.lock | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 66 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e2dabc332..6e33e08ae 100644 --- a/README.md +++ b/README.md @@ -26,11 +26,21 @@ require_once __DIR__ . '/../../vendor/autoload.php'; ## TODOS +- [ ] Updated collection on deletion +- [x] Updated collection on attribute creation +- [ ] Updated collection on attribute deletion +- [ ] Updated collection on index creation +- [ ] Updated collection on index deletion - [ ] Validate original document before editing `$id` -- [ ] Test duplicated document exception +- [ ] Test duplicated document exception is being thrown - [ ] Test no one can overwrite exciting documents/collections without permission - [ ] Delete / Update documents before cleaning cache - [ ] Test for query timeout limits +- [ ] Add strcture validation layer (based on new collection structure) +- [ ] Add autorization validation layer +- [ ] Add cache layer +- [ ] Implement find method +- [ ] Merge new filter syntax parser ## System Requirements diff --git a/composer.json b/composer.json index 60d53994e..085230290 100755 --- a/composer.json +++ b/composer.json @@ -21,6 +21,7 @@ "php": ">=7.1", "ext-pdo": "*", "utopia-php/framework": "0.*.*", + "utopia-php/cache": "0.3.*", "mongodb/mongodb": "1.8.0" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 4a88115a8..3327487b3 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3f6639dbd84b14f560220fff38fcde12", + "content-hash": "dea2465791a58ea901b688ea9eae5b72", "packages": [ { "name": "composer/package-versions-deprecated", @@ -285,6 +285,59 @@ ], "time": "2021-01-07T16:49:33+00:00" }, + { + "name": "utopia-php/cache", + "version": "0.3.0", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/cache.git", + "reference": "e6b8b99d445c4bba5f51857e3d2a3c39a42d73a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/cache/zipball/e6b8b99d445c4bba5f51857e3d2a3c39a42d73a2", + "reference": "e6b8b99d445c4bba5f51857e3d2a3c39a42d73a2", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-redis": "*", + "php": ">=7.4" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "vimeo/psalm": "4.0.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\Cache\\": "src/Cache" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eldad Fux", + "email": "eldad@appwrite.io" + } + ], + "description": "A simple cache library to manage application cache storing, loading and purging", + "keywords": [ + "cache", + "framework", + "php", + "upf", + "utopia" + ], + "support": { + "issues": "https://github.com/utopia-php/cache/issues", + "source": "https://github.com/utopia-php/cache/tree/0.3.0" + }, + "time": "2021-04-20T21:37:21+00:00" + }, { "name": "utopia-php/framework", "version": "0.14.0", From e97579cbe6dbad5dc814d460424148e328211b3a Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 22 Apr 2021 11:56:59 +0300 Subject: [PATCH 042/190] Changed permissions data structure format --- src/Database/Adapter/MariaDB.php | 9 +++-- src/Database/Database.php | 31 ++++++++++----- src/Database/Document.php | 23 +++++++++-- src/Database/Validator/Authorization.php | 10 +---- src/Database/Validator/Permissions.php | 26 +++---------- tests/Database/Base.php | 6 +-- tests/Database/DocumentTest.php | 20 +++------- .../Database/Validator/AuthorizationTest.php | 24 ++++++------ tests/Database/Validator/PermissionsTest.php | 38 +++---------------- 9 files changed, 79 insertions(+), 108 deletions(-) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 959b9b877..0da27edf7 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -256,7 +256,8 @@ public function getDocument(string $collection, string $id): Document $document = $stmt->fetch(); $document['$id'] = $document['_uid']; - $document['$permissions'] = [Database::PERMISSION_READ => [], Database::PERMISSION_WRITE => []]; + $document['$read'] = []; + $document['$write'] = []; unset($document['_id']); unset($document['_uid']); @@ -273,7 +274,7 @@ public function getDocument(string $collection, string $id): Document foreach ($permissions as $permission) { $action = $permission['_action'] ?? ''; $role = $permission['_role'] ?? ''; - $document['$permissions'][$action][] = $role; + $document['$'.$action][] = $role; } return new Document($document); @@ -341,7 +342,7 @@ public function createDocument(string $collection, Document $document): Document $stmt->bindValue(':_uid', $document->getId(), PDO::PARAM_STR); - foreach ($document->getPermissions() as $action => $roles) { + foreach (['read' => $document->getRead(), 'write' => $document->getWrite()] as $action => $roles) { foreach ($roles as $key => $role) { $stmt->bindValue(':_action', $action, PDO::PARAM_STR); $stmt->bindValue(':_role', $role, PDO::PARAM_STR); @@ -416,7 +417,7 @@ public function updateDocument(string $collection, Document $document): Document $stmt->bindValue(':_uid', $document->getId(), PDO::PARAM_STR); - foreach ($document->getPermissions() as $action => $roles) { + foreach (['read' => $document->getRead(), 'write' => $document->getWrite()] as $action => $roles) { foreach ($roles as $key => $role) { $stmt->bindValue(':_action', $action, PDO::PARAM_STR); $stmt->bindValue(':_role', $role, PDO::PARAM_STR); diff --git a/src/Database/Database.php b/src/Database/Database.php index 281ee0a06..a3f7bb4bc 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -181,6 +181,17 @@ public function createCollection(string $id): Document return $this->createDocument(Database::COLLECTIONS, new Document([ '$id' => $id, + '$read' => ['*'], + '$write' => ['*'], + 'name' => $id, + 'attributes' => [], + 'indexes' => [], + ])); + + return $this->createDocument(Database::COLLECTIONS, new Document([ + '$id' => $id, + '$read' => ['*'], + '$write' => ['*'], 'name' => $id, 'attributes' => [], 'indexes' => [], @@ -366,11 +377,11 @@ public function getDocument(string $collection, string $id): Document $document->setAttribute('$collection', $collection->getId()); - // $validator = new Authorization($document, self::PERMISSION_READ); + $validator = new Authorization($document, self::PERMISSION_READ); - // if (!$validator->isValid($document->getPermissions())) { // Check if user has read access to this document - // return new Document(); - // } + if (!$validator->isValid($document->getRead())) { // Check if user has read access to this document + return new Document(); + } if($document->isEmpty()) { return $document; @@ -396,7 +407,7 @@ public function createDocument(string $collection, Document $document): Document { // $validator = new Authorization($document, self::PERMISSION_WRITE); - // if (!$validator->isValid($document->getPermissions())) { // Check if user has write access to this document + // if (!$validator->isValid($document->getWrite())) { // Check if user has write access to this document // throw new AuthorizationException($validator->getDescription()); // } @@ -444,11 +455,11 @@ public function updateDocument(string $collection, string $id, Document $documen // $validator = new Authorization($old, 'write'); - // if (!$validator->isValid($old->getPermissions())) { // Check if user has write access to this document + // if (!$validator->isValid($old->getWrite())) { // Check if user has write access to this document // throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false; // } - // if (!$validator->isValid($document->getPermissions())) { // Check if user has write access to this document + // if (!$validator->isValid($document->getWrite())) { // Check if user has write access to this document // throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false; // } @@ -481,7 +492,7 @@ public function deleteDocument(string $collection, string $id): bool // $validator = new Authorization($document, 'write'); - // if (!$validator->isValid($document->getPermissions())) { // Check if user has write access to this document + // if (!$validator->isValid($document->getWrite())) { // Check if user has write access to this document // throw new AuthorizationException($validator->getDescription()); // } @@ -573,13 +584,13 @@ public function deleteDocument(string $collection, string $id): bool // $validator = new Authorization($document, 'write'); - // if (!$validator->isValid($document->getPermissions())) { // Check if user has write access to this document + // if (!$validator->isValid($document->getWrite())) { // Check if user has write access to this document // throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false; // } // $new = new Document($data); - // if (!$validator->isValid($new->getPermissions())) { // Check if user has write access to this document + // if (!$validator->isValid($new->getWrite())) { // Check if user has write access to this document // throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false; // } diff --git a/src/Database/Document.php b/src/Database/Document.php index 0e77424fe..efed817c7 100644 --- a/src/Database/Document.php +++ b/src/Database/Document.php @@ -3,6 +3,7 @@ namespace Utopia\Database; use ArrayObject; +use Exception; class Document extends ArrayObject { @@ -23,6 +24,14 @@ class Document extends ArrayObject */ public function __construct(array $input = []) { + if(isset($input['$read']) && !is_array($input['$read'])) { + throw new Exception('$read permission must be of type array'); + } + + if(isset($input['$write']) && !is_array($input['$write'])) { + throw new Exception('$write permission must be of type array'); + } + foreach ($input as $key => &$value) { if (\is_array($value)) { if ((isset($value['$id']) || isset($value['$collection']))) { @@ -59,9 +68,17 @@ public function getCollection(): string /** * @return array */ - public function getPermissions(): array + public function getRead(): array + { + return $this->getAttribute('$read', []); + } + + /** + * @return array + */ + public function getWrite(): array { - return $this->getAttribute('$permissions', []); + return $this->getAttribute('$write', []); } /** @@ -74,7 +91,7 @@ public function getAttributes(): array $attributes = []; foreach ($this as $attribute => $value) { - if(array_key_exists($attribute, ['$id' => true, '$collection' => true, '$permissions' => true])) { + if(array_key_exists($attribute, ['$id' => true, '$collection' => true, '$read' => true, '$write' => []])) { continue; } diff --git a/src/Database/Validator/Authorization.php b/src/Database/Validator/Authorization.php index 47a7a85b4..b5ac2876d 100644 --- a/src/Database/Validator/Authorization.php +++ b/src/Database/Validator/Authorization.php @@ -64,15 +64,9 @@ public function isValid($permissions) return true; } - if (!isset($permissions[$this->action])) { - $this->message = 'Missing action key: "'.$this->action.'"'; - - return false; - } - $permission = ''; - foreach ($permissions[$this->action] as $permission) { + foreach ($permissions as $permission) { $permission = \str_replace(':{self}', ':'.$this->document->getId(), $permission); if (\array_key_exists($permission, self::$roles)) { @@ -80,7 +74,7 @@ public function isValid($permissions) } } - $this->message = 'Missing "'.$this->action.'" permission for role "'.$permission.'". Only this scopes "'.\json_encode(self::getRoles()).'" are given and only this are allowed "'.\json_encode($permissions[$this->action]).'".'; + $this->message = 'Missing "'.$this->action.'" permission for role "'.$permission.'". Only this scopes "'.\json_encode(self::getRoles()).'" are given and only this are allowed "'.\json_encode($permissions).'".'; return false; } diff --git a/src/Database/Validator/Permissions.php b/src/Database/Validator/Permissions.php index 4a313ad42..56f11ed5d 100644 --- a/src/Database/Validator/Permissions.php +++ b/src/Database/Validator/Permissions.php @@ -32,33 +32,19 @@ public function getDescription() * * @return bool */ - public function isValid($value) + public function isValid($roles) { - if (!\is_array($value) && !empty($value)) { - $this->message = 'Invalid permissions data structure'; - + if(!is_array($roles)) { + $this->message = 'Permissions roles must be an array of strings'; return false; } - foreach ($value as $action => $roles) { - if (!\in_array($action, ['read', 'write', 'execute'])) { - $this->message = 'Unknown action ("'.$action.'")'; - - return false; - } + foreach ($roles as $role) { + if (!\is_string($role)) { + $this->message = 'Permissions role must be of type string.'; - if(!is_array($roles)) { - $this->message = 'Permissions roles must be an array of strings'; return false; } - - foreach ($roles as $role) { - if (!\is_string($role)) { - $this->message = 'Permissions role must be of type string.'; - - return false; - } - } } return true; diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 2d9d5e118..353ab1861 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -108,10 +108,8 @@ public function testCreateDocument() $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'colors', Database::VAR_STRING, 32, true, true)); $document = static::getDatabase()->createDocument('documents', new Document([ - '$permissions' => [ - 'read' => ['user1', 'user2'], - 'write' => ['user1x', 'user2x'], - ], + '$read' => ['*', 'user1', 'user2'], + '$write' => ['*', 'user1x', 'user2x'], 'string' => 'text📝', 'integer' => 5, 'float' => 5.55, diff --git a/tests/Database/DocumentTest.php b/tests/Database/DocumentTest.php index f175df2a9..2a873e7e5 100644 --- a/tests/Database/DocumentTest.php +++ b/tests/Database/DocumentTest.php @@ -26,11 +26,6 @@ class DocumentTest extends TestCase * @var string */ protected $collection = null; - - /** - * @var string - */ - protected $permissions = []; public function setUp(): void { @@ -38,15 +33,11 @@ public function setUp(): void $this->collection = uniqid(); - $this->permissions = [ - 'read' => ['user:123', 'team:123'], - 'write' => ['*'], - ]; - $this->document = new Document([ '$id' => $this->id, '$collection' => $this->collection, - '$permissions' => $this->permissions, + '$read' => ['user:123', 'team:123'], + '$write' => ['*'], 'title' => 'This is a test.', 'list' => [ 'one' @@ -79,8 +70,8 @@ public function testCollection() public function testPermissions() { - $this->assertEquals($this->permissions, $this->document->getPermissions()); - $this->assertEquals([], $this->empty->getPermissions()); + $this->assertEquals(['user:123', 'team:123'], $this->document->getRead()); + $this->assertEquals(['*'], $this->document->getWrite()); } public function testGetAttributes() @@ -165,7 +156,8 @@ public function testGetArrayCopy() $this->assertEquals([ '$id' => $this->id, '$collection' => $this->collection, - '$permissions' => $this->permissions, + '$read' => ['user:123', 'team:123'], + '$write' => ['*'], 'title' => 'This is a test.', 'list' => [ 'one' diff --git a/tests/Database/Validator/AuthorizationTest.php b/tests/Database/Validator/AuthorizationTest.php index 1b0cfc6f2..4e45254e0 100644 --- a/tests/Database/Validator/AuthorizationTest.php +++ b/tests/Database/Validator/AuthorizationTest.php @@ -23,10 +23,8 @@ public function setUp(): void $this->document = new Document([ '$id' => uniqid(), '$collection' => uniqid(), - '$permissions' => [ - 'read' => ['user:123', 'team:123'], - 'write' => ['*'], - ], + '$read' => ['user:123', 'team:123'], + '$write' => ['*'], ]); $this->object = new Authorization($this->document, 'read'); } @@ -37,7 +35,7 @@ public function tearDown(): void public function testValues() { - $this->assertEquals($this->object->isValid($this->document->getPermissions()), false); + $this->assertEquals($this->object->isValid($this->document->getRead()), false); Authorization::setRole('user:456'); Authorization::setRole('user:123'); @@ -47,37 +45,37 @@ public function testValues() $this->assertEquals(Authorization::isRole(''), false); $this->assertEquals(Authorization::isRole('*'), true); - $this->assertEquals($this->object->isValid($this->document->getPermissions()), true); + $this->assertEquals($this->object->isValid($this->document->getRead()), true); Authorization::cleanRoles(); - $this->assertEquals($this->object->isValid($this->document->getPermissions()), false); + $this->assertEquals($this->object->isValid($this->document->getRead()), false); Authorization::setRole('team:123'); - $this->assertEquals($this->object->isValid($this->document->getPermissions()), true); + $this->assertEquals($this->object->isValid($this->document->getRead()), true); Authorization::cleanRoles(); Authorization::disable(); - $this->assertEquals($this->object->isValid($this->document->getPermissions()), true); + $this->assertEquals($this->object->isValid($this->document->getRead()), true); Authorization::reset(); - $this->assertEquals($this->object->isValid($this->document->getPermissions()), false); + $this->assertEquals($this->object->isValid($this->document->getRead()), false); Authorization::setDefaultStatus(false); Authorization::disable(); - $this->assertEquals($this->object->isValid($this->document->getPermissions()), true); + $this->assertEquals($this->object->isValid($this->document->getRead()), true); Authorization::reset(); - $this->assertEquals($this->object->isValid($this->document->getPermissions()), true); + $this->assertEquals($this->object->isValid($this->document->getRead()), true); Authorization::enable(); - $this->assertEquals($this->object->isValid($this->document->getPermissions()), false); + $this->assertEquals($this->object->isValid($this->document->getRead()), false); Authorization::setRole('textX'); diff --git a/tests/Database/Validator/PermissionsTest.php b/tests/Database/Validator/PermissionsTest.php index 80bd39af7..0fb679bcf 100644 --- a/tests/Database/Validator/PermissionsTest.php +++ b/tests/Database/Validator/PermissionsTest.php @@ -25,46 +25,20 @@ public function testValues() $document = new Document([ '$id' => uniqid(), '$collection' => uniqid(), - '$permissions' => [ - 'read' => ['user:123', 'team:123'], - 'write' => ['*'], - ], + '$read' => ['user:123', 'team:123'], + '$write' => ['*'], ]); - $this->assertEquals($object->isValid($document->getPermissions()), true); + $this->assertEquals($object->isValid($document->getRead()), true); $document = new Document([ '$id' => uniqid(), '$collection' => uniqid(), - '$permissions' => [ - 'read' => ['user:123', 'team:123'], - ], + '$read' => ['user:123', 'team:123'], ]); - $this->assertEquals($object->isValid($document->getPermissions()), true); - - $document = new Document([ - '$id' => uniqid(), - '$collection' => uniqid(), - '$permissions' => [ - 'read' => ['user:123', 'team:123'], - 'write' => ['*'], - 'unknown' => ['*'], - ], - ]); - - $this->assertEquals($object->isValid($document->getPermissions()), false); - - $document = new Document([ - '$id' => uniqid(), - '$collection' => uniqid(), - '$permissions' => [ - 'read' => 'unknown', - 'write' => ['*'], - ], - ]); - - $this->assertEquals($object->isValid($document->getPermissions()), false); + $this->assertEquals($object->isValid($document->getRead()), true); + $this->assertEquals($object->isValid('sting'), false); } } \ No newline at end of file From a5fc0f42a81111769caf225b1973ad7e5ea0c37f Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 22 Apr 2021 14:28:03 +0300 Subject: [PATCH 043/190] Syncing collection metadata --- src/Database/Database.php | 70 ++++++++++++++++++++++++++++++--------- 1 file changed, 54 insertions(+), 16 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index a3f7bb4bc..fd0720ce6 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -178,16 +178,7 @@ public function createCollection(string $id): Document if($id === self::COLLECTIONS) { return new Document($this->collection); } - - return $this->createDocument(Database::COLLECTIONS, new Document([ - '$id' => $id, - '$read' => ['*'], - '$write' => ['*'], - 'name' => $id, - 'attributes' => [], - 'indexes' => [], - ])); - + return $this->createDocument(Database::COLLECTIONS, new Document([ '$id' => $id, '$read' => ['*'], @@ -218,7 +209,8 @@ public function getCollection(string $id): Document */ public function listCollections(): array { - return $this->adapter->listCollections(); + // TODO add a search here + return []; } /** @@ -230,9 +222,9 @@ public function listCollections(): array */ public function deleteCollection(string $id): bool { - // TODO Delete collection document first + $this->adapter->deleteCollection($id); - return $this->adapter->deleteCollection($id); + return $this->deleteDocument(self::COLLECTIONS, $id); } /** @@ -297,7 +289,23 @@ public function createAttribute(string $collection, string $id, string $type, in */ public function deleteAttribute(string $collection, string $id): bool { - return $this->adapter->deleteAttribute($collection, $id); + $collection = $this->getCollection($collection); + + $attributes = $collection->getAttribute('attributes', []); + + foreach ($attributes as $key => $value) { + if(isset($value['$id']) && $value['$id'] === $id) { + unset($attributes[$key]); + } + } + + $collection->setAttribute('attributes', $attributes); + + if($collection->getId() !== self::COLLECTIONS) { + $this->updateDocument(self::COLLECTIONS, $collection->getId(), $collection); + } + + return $this->adapter->deleteAttribute($collection->getId(), $id); } /** @@ -318,6 +326,20 @@ public function createIndex(string $collection, string $id, string $type, array throw new Exception('Missing attributes'); } + $collection = $this->getCollection($collection); + + $collection->setAttribute('indexes', new Document([ + '$id' => $id, + 'type' => $type, + 'attributes' => $attributes, + 'lengths' => $lengths, + 'orders' => $orders, + ]), Document::SET_TYPE_APPEND); + + if($collection->getId() !== self::COLLECTIONS) { + $this->updateDocument(self::COLLECTIONS, $collection->getId(), $collection); + } + switch ($type) { case self::INDEX_KEY: if(!$this->adapter->getSupportForIndex()) { @@ -342,7 +364,7 @@ public function createIndex(string $collection, string $id, string $type, array break; } - return $this->adapter->createIndex($collection, $id, $type, $attributes, $lengths, $orders); + return $this->adapter->createIndex($collection->getId(), $id, $type, $attributes, $lengths, $orders); } /** @@ -355,7 +377,23 @@ public function createIndex(string $collection, string $id, string $type, array */ public function deleteIndex(string $collection, string $id): bool { - return $this->adapter->deleteIndex($collection, $id); + $collection = $this->getCollection($collection); + + $indexes = $collection->getAttribute('indexes', []); + + foreach ($indexes as $key => $value) { + if(isset($value['$id']) && $value['$id'] === $id) { + unset($indexes[$key]); + } + } + + $collection->setAttribute('indexes', $indexes); + + if($collection->getId() !== self::COLLECTIONS) { + $this->updateDocument(self::COLLECTIONS, $collection->getId(), $collection); + } + + return $this->adapter->deleteIndex($collection->getId(), $id); } /** From 03585753b7db0da645d271870727c2fdbd2f3b6a Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 22 Apr 2021 14:35:48 +0300 Subject: [PATCH 044/190] Added test for metadata sync --- tests/Database/Base.php | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 353ab1861..b56a8feff 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -51,12 +51,18 @@ public function testCreateDeleteAttribute() $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'float', Database::VAR_FLOAT, 0)); $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'boolean', Database::VAR_BOOLEAN, 0)); + $collection = static::getDatabase()->getCollection('attributes'); + $this->assertCount(7, $collection->getAttribute('attributes')); + // Array $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'string_list', Database::VAR_STRING, 128, true, true)); $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'integer_list', Database::VAR_INTEGER, 0, true, true)); $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'float_list', Database::VAR_FLOAT, 0, true, true)); $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'boolean_list', Database::VAR_BOOLEAN, 0, true, true)); + $collection = static::getDatabase()->getCollection('attributes'); + $this->assertCount(11, $collection->getAttribute('attributes')); + // Delete $this->assertEquals(true, static::getDatabase()->deleteAttribute('attributes', 'string1')); $this->assertEquals(true, static::getDatabase()->deleteAttribute('attributes', 'string2')); @@ -66,12 +72,18 @@ public function testCreateDeleteAttribute() $this->assertEquals(true, static::getDatabase()->deleteAttribute('attributes', 'float')); $this->assertEquals(true, static::getDatabase()->deleteAttribute('attributes', 'boolean')); + $collection = static::getDatabase()->getCollection('attributes'); + $this->assertCount(4, $collection->getAttribute('attributes')); + // Delete Array $this->assertEquals(true, static::getDatabase()->deleteAttribute('attributes', 'string_list')); $this->assertEquals(true, static::getDatabase()->deleteAttribute('attributes', 'integer_list')); $this->assertEquals(true, static::getDatabase()->deleteAttribute('attributes', 'float_list')); $this->assertEquals(true, static::getDatabase()->deleteAttribute('attributes', 'boolean_list')); + $collection = static::getDatabase()->getCollection('attributes'); + $this->assertCount(0, $collection->getAttribute('attributes')); + static::getDatabase()->deleteCollection('attributes'); } @@ -83,17 +95,23 @@ public function testCreateDeleteIndex() $this->assertEquals(true, static::getDatabase()->createAttribute('indexes', 'integer', Database::VAR_INTEGER, 0)); $this->assertEquals(true, static::getDatabase()->createAttribute('indexes', 'float', Database::VAR_FLOAT, 0)); $this->assertEquals(true, static::getDatabase()->createAttribute('indexes', 'boolean', Database::VAR_BOOLEAN, 0)); - + // Indexes $this->assertEquals(true, static::getDatabase()->createIndex('indexes', 'index1', Database::INDEX_KEY, ['string', 'integer'], [128], [Database::ORDER_ASC])); $this->assertEquals(true, static::getDatabase()->createIndex('indexes', 'index2', Database::INDEX_KEY, ['float', 'integer'], [], [Database::ORDER_ASC, Database::ORDER_DESC])); $this->assertEquals(true, static::getDatabase()->createIndex('indexes', 'index3', Database::INDEX_KEY, ['integer', 'boolean'], [], [Database::ORDER_ASC, Database::ORDER_DESC, Database::ORDER_DESC])); + $collection = static::getDatabase()->getCollection('indexes'); + $this->assertCount(3, $collection->getAttribute('indexes')); + // Delete Indexes $this->assertEquals(true, static::getDatabase()->deleteIndex('indexes', 'index1')); $this->assertEquals(true, static::getDatabase()->deleteIndex('indexes', 'index2')); $this->assertEquals(true, static::getDatabase()->deleteIndex('indexes', 'index3')); + $collection = static::getDatabase()->getCollection('indexes'); + $this->assertCount(0, $collection->getAttribute('indexes')); + static::getDatabase()->deleteCollection('indexes'); } From 3cf58ce52a9e6cc3160186becddd3c6650a227d9 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 22 Apr 2021 14:00:04 -0400 Subject: [PATCH 045/190] Add function to parse query expressions --- src/Database/Database.php | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/Database/Database.php b/src/Database/Database.php index fd0720ce6..848fecd67 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -785,4 +785,34 @@ public function getId(): string { return \uniqid(); } + + /** + * Get attribute key-value from query expression + * + * @param string $expression + * + * @return array + */ + public function parseExpression(string $expression): array + { + //find location of parentheses in expression + $start = mb_strpos($expression, '('); + $end = mb_strpos($expression, ')'); + + //extract the query method + $method = mb_substr($expression, 0, $start); + + //grab everything inside parentheses + $query = mb_substr($expression, + ($start + 1), /* exclude open paren*/ + ($end - $start - 1) /* exclude closed paren*/ + ); + + //strip quotes from queries of type string + $query = str_replace('"', "", $query); + $query = str_replace("'", "", $query); + + return [$method, $query]; + } + } From 514bf70932b81bde15769363aa3ba1e583fbe506 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 22 Apr 2021 14:01:02 -0400 Subject: [PATCH 046/190] Add method to parse custom filter queries --- src/Database/Database.php | 64 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/Database/Database.php b/src/Database/Database.php index 848fecd67..b070b425a 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -815,4 +815,68 @@ public function parseExpression(string $expression): array return [$method, $query]; } + /** + * Parse query filters + * + * @param string $collection Collection name + * @param array $filters Array of document filters + * + * @return array + * */ + public function parseQueries(string $collection, array $filters): array + { + $collection = $this->getCollection($collection); + $schema = $collection->getAttribute('attributes', []); + + $output = []; + foreach ($filters as &$filter) + { + $stanzas = mb_substr_count($filter, ".") + 1; + + switch ($stanzas): + case 2: + $input = explode('.', $filter); + + $attribute = $input[0]; + $expression = $input[1]; + [$method, $query] = $this->parseExpression($expression); + // $attributeType = $schema[\array_search($attribute, $schema)]['type']; + $attributeType = $schema[array_search($attribute, array_column($schema, '$id'))]['type']; + array_push($output, [ + 'collection' => $collection->getId(), + 'attribute' => $attribute, + 'method' => $method, + 'query' => $query, + 'queryType' => $attributeType, + ]); + break; + + // Copypasta code - remove/refactor when collection relations work + // + // case 3: + // $input = explode('.', $filter); + + // $attribute = $input[0]; + // $childAttribute = $input[1]; + // $expression = $input[2]; + // [$method, $query] = pickAttribute($expression); + // $attributeType = checkType($schema, $collection, $attribute, $childAttribute); + // array_push($output, [ + // 'input' => $filter, + // 'output' => [ + // [ + // 'collection' => $collection, + // 'attribute' => $attribute, + // 'childAttribute' => $childAttribute, + // 'method' => $method, + // 'query' => $query, + // 'queryType' => $attributeType, + // ] + // ], + // ]); + // break; + endswitch; + } + return $output; + } } From 80eec648f9edf5e1b91ca3794a3a39c84cdb3fb2 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 22 Apr 2021 14:01:21 -0400 Subject: [PATCH 047/190] Add tests for parsing queries --- tests/Database/Base.php | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/Database/Base.php b/tests/Database/Base.php index b56a8feff..bd786f166 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -1558,4 +1558,32 @@ public function decodeTest() { $this->assertEquals('1', '1'); } + + public function testParseQueries() + { + // Set up mock collections + $this->assertInstanceOf('Utopia\Database\Document', static::getDatabase()->createCollection('Movie')); + // $this->assertInstanceOf('Utopia\Database\Document', static::getDatabase()->createCollection('Director')); + // $this->assertInstanceOf('Utopia\Database\Document', static::getDatabase()->createCollection('Actor')); + $this->assertEquals(true, static::getDatabase()->createAttribute('Movie', 'title', Database::VAR_STRING, 256)); + $this->assertEquals(true, static::getDatabase()->createAttribute('Movie', 'year', Database::VAR_INTEGER, 0)); + $this->assertEquals(true, static::getDatabase()->createAttribute('Movie', 'published', Database::VAR_BOOLEAN, 0)); + $this->assertEquals(true, static::getDatabase()->createAttribute('Movie', 'director', Database::VAR_STRING, 256)); + $this->assertEquals(true, static::getDatabase()->createAttribute('Movie', 'actors', Database::VAR_STRING, 256, true, true)); + + // test parser + $query = static::getDatabase()->parseQueries('Movie', ['title.equal("Iron Man")']); + $this->assertEquals('Movie', $query[0]['collection']); + $this->assertEquals('title', $query[0]['attribute']); + $this->assertEquals('equal', $query[0]['method']); + $this->assertEquals('Iron Man', $query[0]['query']); + $this->assertEquals(Database::VAR_STRING, $query[0]['queryType']); + + $query = static::getDatabase()->parseQueries('Movie', ['year.lesser(2001)']); + $this->assertEquals('Movie', $query[0]['collection']); + $this->assertEquals('year', $query[0]['attribute']); + $this->assertEquals('lesser', $query[0]['method']); + $this->assertEquals(2001, $query[0]['query']); + $this->assertEquals(Database::VAR_INTEGER, $query[0]['queryType']); + } } \ No newline at end of file From 69e6bc766be2e8f9045c1e6e1343563a6b7786fe Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 22 Apr 2021 15:55:20 -0400 Subject: [PATCH 048/190] Add test for parsing query expressions --- tests/Database/Base.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/Database/Base.php b/tests/Database/Base.php index bd786f166..20cb9bc5a 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -1559,6 +1559,7 @@ public function decodeTest() $this->assertEquals('1', '1'); } + public function testParseQueries() { // Set up mock collections @@ -1586,4 +1587,16 @@ public function testParseQueries() $this->assertEquals(2001, $query[0]['query']); $this->assertEquals(Database::VAR_INTEGER, $query[0]['queryType']); } + + public function testParseExpression() + { + [$method, $query] = static::getDatabase()->parseExpression('equal("Spiderman")'); + $this->assertEquals('equal', $method); + $this->assertEquals('Spiderman', $query); + + + [$method, $query] = static::getDatabase()->parseExpression('lesser(2001)'); + $this->assertEquals('lesser', $method); + $this->assertEquals(2001, $query); + } } \ No newline at end of file From c658106fcc1bdc26df25b9ff139c45bea1ea3e31 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 22 Apr 2021 23:45:02 +0300 Subject: [PATCH 049/190] Added read and write authorization --- README.md | 10 +-- src/Database/Adapter/MariaDB.php | 9 ++- src/Database/Database.php | 55 +++++++------ tests/Database/Base.php | 129 ++++++++++++++++++++++++++++++- 4 files changed, 168 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 6e33e08ae..42a7dd7b6 100644 --- a/README.md +++ b/README.md @@ -26,18 +26,18 @@ require_once __DIR__ . '/../../vendor/autoload.php'; ## TODOS -- [ ] Updated collection on deletion +- [x] Updated collection on deletion - [x] Updated collection on attribute creation -- [ ] Updated collection on attribute deletion -- [ ] Updated collection on index creation -- [ ] Updated collection on index deletion +- [x] Updated collection on attribute deletion +- [x] Updated collection on index creation +- [x] Updated collection on index deletion - [ ] Validate original document before editing `$id` - [ ] Test duplicated document exception is being thrown - [ ] Test no one can overwrite exciting documents/collections without permission - [ ] Delete / Update documents before cleaning cache - [ ] Test for query timeout limits +- [x] Add autorization validation layer - [ ] Add strcture validation layer (based on new collection structure) -- [ ] Add autorization validation layer - [ ] Add cache layer - [ ] Implement find method - [ ] Merge new filter syntax parser diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 0da27edf7..78722ab54 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -244,13 +244,14 @@ public function deleteIndex(string $collection, string $id): bool public function getDocument(string $collection, string $id): Document { $name = $this->filter($collection); - + $stmt = $this->getPDO()->prepare("SELECT * FROM {$this->getNamespace()}.{$name} WHERE _uid = :_uid LIMIT 1; "); $stmt->bindValue(':_uid', $id, PDO::PARAM_STR); + $stmt->execute(); $document = $stmt->fetch(); @@ -342,7 +343,7 @@ public function createDocument(string $collection, Document $document): Document $stmt->bindValue(':_uid', $document->getId(), PDO::PARAM_STR); - foreach (['read' => $document->getRead(), 'write' => $document->getWrite()] as $action => $roles) { + foreach (['read' => $document->getRead(), 'write' => $document->getWrite()] as $action => $roles) { // Insert all permissions foreach ($roles as $key => $role) { $stmt->bindValue(':_action', $action, PDO::PARAM_STR); $stmt->bindValue(':_role', $role, PDO::PARAM_STR); @@ -401,7 +402,7 @@ public function updateDocument(string $collection, Document $document): Document /** * Update Permissions */ - $stmt = $this->getPDO() + $stmt = $this->getPDO() // Clean all old permissions to avoid any duplications ->prepare("DELETE FROM {$this->getNamespace()}.{$name}_permissions WHERE _uid = :_uid"); @@ -417,7 +418,7 @@ public function updateDocument(string $collection, Document $document): Document $stmt->bindValue(':_uid', $document->getId(), PDO::PARAM_STR); - foreach (['read' => $document->getRead(), 'write' => $document->getWrite()] as $action => $roles) { + foreach (['read' => $document->getRead(), 'write' => $document->getWrite()] as $action => $roles) { // Insert all permissions foreach ($roles as $key => $role) { $stmt->bindValue(':_action', $action, PDO::PARAM_STR); $stmt->bindValue(':_role', $role, PDO::PARAM_STR); diff --git a/src/Database/Database.php b/src/Database/Database.php index fd0720ce6..b9980488d 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -50,7 +50,8 @@ class Database * @var array */ protected $collection = [ - '$id' => 'collections', + '$id' => self::COLLECTIONS, + '$collection' => self::COLLECTIONS, 'name' => 'collections', 'attributes' => [ [ @@ -410,14 +411,19 @@ public function getDocument(string $collection, string $id): Document return new Document($this->collection); } - $collection = $this->getDocument(self::COLLECTIONS, $collection); + if(empty($collection)) { + throw new Exception('test exception: '.$collection .':'. $id); + } + + $collection = $this->getCollection($collection); + $document = $this->adapter->getDocument($collection->getId(), $id); $document->setAttribute('$collection', $collection->getId()); $validator = new Authorization($document, self::PERMISSION_READ); - if (!$validator->isValid($document->getRead())) { // Check if user has read access to this document + if (!$validator->isValid($document->getRead()) && $collection->getId() !== self::COLLECTIONS) { // Check if user has read access to this document return new Document(); } @@ -443,11 +449,11 @@ public function getDocument(string $collection, string $id): Document */ public function createDocument(string $collection, Document $document): Document { - // $validator = new Authorization($document, self::PERMISSION_WRITE); + $validator = new Authorization($document, self::PERMISSION_WRITE); - // if (!$validator->isValid($document->getWrite())) { // Check if user has write access to this document - // throw new AuthorizationException($validator->getDescription()); - // } + if (!$validator->isValid($document->getWrite())) { // Check if user has write access to this document + throw new AuthorizationException($validator->getDescription()); + } // $document = $this->encode($document); // $validator = new Structure($this); @@ -456,12 +462,11 @@ public function createDocument(string $collection, Document $document): Document // throw new StructureException($validator->getDescription()); // var_dump($validator->getDescription()); return false; // } - $document - ->setAttribute('$id', empty($document->getId()) ? $this->getId(): $document->getId()) - ->setAttribute('$collection', $collection) - ; + $document->setAttribute('$id', empty($document->getId()) ? $this->getId(): $document->getId()); $document = $this->adapter->createDocument($collection, $document); + + $document->setAttribute('$collection', $collection); // $document = $this->decode($document); @@ -491,15 +496,15 @@ public function updateDocument(string $collection, string $id, Document $documen // $data['$id'] = $old->getId(); // $data['$collection'] = $old->getCollection(); - // $validator = new Authorization($old, 'write'); + $validator = new Authorization($old, 'write'); - // if (!$validator->isValid($old->getWrite())) { // Check if user has write access to this document - // throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false; - // } + if (!$validator->isValid($old->getWrite())) { // Check if user has write access to this document + throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false; + } - // if (!$validator->isValid($document->getWrite())) { // Check if user has write access to this document - // throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false; - // } + if (!$validator->isValid($document->getWrite())) { // Check if user has write access to this document + throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false; + } // $document = $this->encode($document); @@ -526,13 +531,13 @@ public function updateDocument(string $collection, string $id, Document $documen */ public function deleteDocument(string $collection, string $id): bool { - // $document = $this->getDocument($collection, $id); + $document = $this->getDocument($collection, $id); - // $validator = new Authorization($document, 'write'); + $validator = new Authorization($document, 'write'); - // if (!$validator->isValid($document->getWrite())) { // Check if user has write access to this document - // throw new AuthorizationException($validator->getDescription()); - // } + if (!$validator->isValid($document->getWrite())) { // Check if user has write access to this document + throw new AuthorizationException($validator->getDescription()); + } return $this->adapter->deleteDocument($collection, $id); } @@ -556,7 +561,7 @@ public function deleteDocument(string $collection, string $id): bool // 'filters' => [], // ], $options); - // $results = $this->adapter->find($this->getDocument(self::COLLECTIONS, $collection), $options); + // $results = $this->adapter->find($this->getCollection($collection), $options); // foreach ($results as &$node) { // $node = $this->decode(new Document($node)); @@ -640,7 +645,7 @@ public function deleteDocument(string $collection, string $id): bool // throw new StructureException($validator->getDescription()); // var_dump($validator->getDescription()); return false; // } - // $new = new Document($this->adapter->updateDocument($this->getDocument(self::COLLECTIONS, $new->getCollection()), $new->getId(), $new->getArrayCopy())); + // $new = new Document($this->adapter->updateDocument($this->getCollection($new->getCollection()), $new->getId(), $new->getArrayCopy())); // $new = $this->decode($new); diff --git a/tests/Database/Base.php b/tests/Database/Base.php index b56a8feff..c5599c52a 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -5,6 +5,7 @@ use PHPUnit\Framework\TestCase; use Utopia\Database\Database; use Utopia\Database\Document; +use Utopia\Database\Exception\Authorization as ExceptionAuthorization; use Utopia\Database\Validator\Authorization; abstract class Base extends TestCase @@ -16,6 +17,7 @@ abstract static protected function getDatabase(): Database; public function setUp(): void { + Authorization::setRole('*'); } public function tearDown(): void @@ -36,7 +38,9 @@ public function testCreateDelete() public function testCreateDeleteCollection() { $this->assertInstanceOf('Utopia\Database\Document', static::getDatabase()->createCollection('actors')); + $this->assertEquals(false, static::getDatabase()->getCollection('actors')->isEmpty()); $this->assertEquals(true, static::getDatabase()->deleteCollection('actors')); + $this->assertEquals(true, static::getDatabase()->getCollection('actors')->isEmpty()); } public function testCreateDeleteAttribute() @@ -124,7 +128,7 @@ public function testCreateDocument() $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'float', Database::VAR_FLOAT, 0)); $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'boolean', Database::VAR_BOOLEAN, 0)); $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'colors', Database::VAR_STRING, 32, true, true)); - + $document = static::getDatabase()->createDocument('documents', new Document([ '$read' => ['*', 'user1', 'user2'], '$write' => ['*', 'user1x', 'user2x'], @@ -172,6 +176,129 @@ public function testGetDocument(Document $document) return $document; } + /** + * @depends testCreateDocument + */ + public function testReadPermissionsSuccess(Document $document) + { + $document = static::getDatabase()->createDocument('documents', new Document([ + '$read' => ['*'], + '$write' => ['*'], + 'string' => 'text📝', + 'integer' => 5, + 'float' => 5.55, + 'boolean' => true, + 'colors' => ['pink', 'green', 'blue'], + ])); + + $this->assertEquals(false, $document->isEmpty()); + + Authorization::cleanRoles(); + + $document = static::getDatabase()->getDocument($document->getCollection(), $document->getId()); + + $this->assertEquals(true, $document->isEmpty()); + + Authorization::setRole('*'); + + return $document; + } + + /** + * @depends testCreateDocument + */ + public function testReadPermissionsFailure(Document $document) + { + $this->expectException(ExceptionAuthorization::class); + + $document = static::getDatabase()->createDocument('documents', new Document([ + '$read' => ['user1'], + '$write' => ['user1'], + 'string' => 'text📝', + 'integer' => 5, + 'float' => 5.55, + 'boolean' => true, + 'colors' => ['pink', 'green', 'blue'], + ])); + + return $document; + } + + /** + * @depends testCreateDocument + */ + public function testWritePermissionsSuccess(Document $document) + { + $document = static::getDatabase()->createDocument('documents', new Document([ + '$read' => ['*'], + '$write' => ['*'], + 'string' => 'text📝', + 'integer' => 5, + 'float' => 5.55, + 'boolean' => true, + 'colors' => ['pink', 'green', 'blue'], + ])); + + $this->assertEquals(false, $document->isEmpty()); + + return $document; + } + + /** + * @depends testCreateDocument + */ + public function testWritePermissionsFailure(Document $document) + { + $this->expectException(ExceptionAuthorization::class); + + Authorization::cleanRoles(); + + $document = static::getDatabase()->createDocument('documents', new Document([ + '$read' => ['*'], + '$write' => ['*'], + 'string' => 'text📝', + 'integer' => 5, + 'float' => 5.55, + 'boolean' => true, + 'colors' => ['pink', 'green', 'blue'], + ])); + + return $document; + } + + /** + * @depends testCreateDocument + */ + public function testWritePermissionsUpdateFailure(Document $document) + { + $this->expectException(ExceptionAuthorization::class); + + $document = static::getDatabase()->createDocument('documents', new Document([ + '$read' => ['*'], + '$write' => ['*'], + 'string' => 'text📝', + 'integer' => 5, + 'float' => 5.55, + 'boolean' => true, + 'colors' => ['pink', 'green', 'blue'], + ])); + + Authorization::cleanRoles(); + + $document = static::getDatabase()->updateDocument('documents', $document->getId(), new Document([ + '$id' => $document->getId(), + '$read' => ['*'], + '$write' => ['*'], + 'string' => 'text📝', + 'integer' => 5, + 'float' => 5.55, + 'boolean' => true, + 'colors' => ['pink', 'green', 'blue'], + ])); + + return $document; + } + /** * @depends testGetDocument */ From 68189e253ae46c8ef28c7cb7187d75913255afef Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 23 Apr 2021 00:06:00 +0300 Subject: [PATCH 050/190] Fixd psalm warnings --- src/Database/Adapter.php | 2 +- src/Database/Adapter/MariaDB.php | 8 ++++---- src/Database/Database.php | 2 +- src/Database/Validator/Permissions.php | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index 227909401..25766af08 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -218,7 +218,7 @@ abstract public function updateDocument(string $collection, Document $document): * @param string $collection * @param string $id * - * @return array + * @return bool */ abstract public function deleteDocument(string $collection, string $id): bool; diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 78722ab54..261f9348e 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -436,9 +436,9 @@ public function updateDocument(string $collection, Document $document): Document * Delete Document * * @param string $collection - * @param Document $document + * @param string $id * - * @return Document + * @return bool */ public function deleteDocument(string $collection, string $id): bool { @@ -571,11 +571,11 @@ protected function getSQLType(string $type, int $size, bool $signed = true): str /** * Get PDO Type * - * @param string $value + * @param mixed $value * * @return int */ - protected function getPDOType(string $value): int + protected function getPDOType($value): int { switch (gettype($value)) { case 'string': diff --git a/src/Database/Database.php b/src/Database/Database.php index b9980488d..1864a3af1 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -525,7 +525,7 @@ public function updateDocument(string $collection, string $id, Document $documen * @param string $collection * @param string $id * - * @return false + * @return bool * * @throws AuthorizationException */ diff --git a/src/Database/Validator/Permissions.php b/src/Database/Validator/Permissions.php index 56f11ed5d..9a2a5eb4a 100644 --- a/src/Database/Validator/Permissions.php +++ b/src/Database/Validator/Permissions.php @@ -28,7 +28,7 @@ public function getDescription() * * Returns true if valid or false if not. * - * @param mixed $value + * @param mixed $roles * * @return bool */ From a86c8fc0233b2a697fad821a76c5f04c31ec65a3 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 23 Apr 2021 00:43:39 +0300 Subject: [PATCH 051/190] Duplicated document error --- README.md | 2 +- tests/Database/Base.php | 263 +++++++++++++++++++++------------------- 2 files changed, 141 insertions(+), 124 deletions(-) diff --git a/README.md b/README.md index 42a7dd7b6..db69aec6d 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ require_once __DIR__ . '/../../vendor/autoload.php'; - [x] Updated collection on index creation - [x] Updated collection on index deletion - [ ] Validate original document before editing `$id` -- [ ] Test duplicated document exception is being thrown +- [x] Test duplicated document exception is being thrown - [ ] Test no one can overwrite exciting documents/collections without permission - [ ] Delete / Update documents before cleaning cache - [ ] Test for query timeout limits diff --git a/tests/Database/Base.php b/tests/Database/Base.php index c5599c52a..b5a93ad35 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -6,6 +6,7 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Exception\Authorization as ExceptionAuthorization; +use Utopia\Database\Exception\Duplicate; use Utopia\Database\Validator\Authorization; abstract class Base extends TestCase @@ -176,129 +177,6 @@ public function testGetDocument(Document $document) return $document; } - /** - * @depends testCreateDocument - */ - public function testReadPermissionsSuccess(Document $document) - { - $document = static::getDatabase()->createDocument('documents', new Document([ - '$read' => ['*'], - '$write' => ['*'], - 'string' => 'text📝', - 'integer' => 5, - 'float' => 5.55, - 'boolean' => true, - 'colors' => ['pink', 'green', 'blue'], - ])); - - $this->assertEquals(false, $document->isEmpty()); - - Authorization::cleanRoles(); - - $document = static::getDatabase()->getDocument($document->getCollection(), $document->getId()); - - $this->assertEquals(true, $document->isEmpty()); - - Authorization::setRole('*'); - - return $document; - } - - /** - * @depends testCreateDocument - */ - public function testReadPermissionsFailure(Document $document) - { - $this->expectException(ExceptionAuthorization::class); - - $document = static::getDatabase()->createDocument('documents', new Document([ - '$read' => ['user1'], - '$write' => ['user1'], - 'string' => 'text📝', - 'integer' => 5, - 'float' => 5.55, - 'boolean' => true, - 'colors' => ['pink', 'green', 'blue'], - ])); - - return $document; - } - - /** - * @depends testCreateDocument - */ - public function testWritePermissionsSuccess(Document $document) - { - $document = static::getDatabase()->createDocument('documents', new Document([ - '$read' => ['*'], - '$write' => ['*'], - 'string' => 'text📝', - 'integer' => 5, - 'float' => 5.55, - 'boolean' => true, - 'colors' => ['pink', 'green', 'blue'], - ])); - - $this->assertEquals(false, $document->isEmpty()); - - return $document; - } - - /** - * @depends testCreateDocument - */ - public function testWritePermissionsFailure(Document $document) - { - $this->expectException(ExceptionAuthorization::class); - - Authorization::cleanRoles(); - - $document = static::getDatabase()->createDocument('documents', new Document([ - '$read' => ['*'], - '$write' => ['*'], - 'string' => 'text📝', - 'integer' => 5, - 'float' => 5.55, - 'boolean' => true, - 'colors' => ['pink', 'green', 'blue'], - ])); - - return $document; - } - - /** - * @depends testCreateDocument - */ - public function testWritePermissionsUpdateFailure(Document $document) - { - $this->expectException(ExceptionAuthorization::class); - - $document = static::getDatabase()->createDocument('documents', new Document([ - '$read' => ['*'], - '$write' => ['*'], - 'string' => 'text📝', - 'integer' => 5, - 'float' => 5.55, - 'boolean' => true, - 'colors' => ['pink', 'green', 'blue'], - ])); - - Authorization::cleanRoles(); - - $document = static::getDatabase()->updateDocument('documents', $document->getId(), new Document([ - '$id' => $document->getId(), - '$read' => ['*'], - '$write' => ['*'], - 'string' => 'text📝', - 'integer' => 5, - 'float' => 5.55, - 'boolean' => true, - 'colors' => ['pink', 'green', 'blue'], - ])); - - return $document; - } - /** * @depends testGetDocument */ @@ -1685,4 +1563,143 @@ public function decodeTest() { $this->assertEquals('1', '1'); } + + + /** + * @depends testCreateDocument + */ + public function testReadPermissionsSuccess(Document $document) + { + $document = static::getDatabase()->createDocument('documents', new Document([ + '$read' => ['*'], + '$write' => ['*'], + 'string' => 'text📝', + 'integer' => 5, + 'float' => 5.55, + 'boolean' => true, + 'colors' => ['pink', 'green', 'blue'], + ])); + + $this->assertEquals(false, $document->isEmpty()); + + Authorization::cleanRoles(); + + $document = static::getDatabase()->getDocument($document->getCollection(), $document->getId()); + + $this->assertEquals(true, $document->isEmpty()); + + Authorization::setRole('*'); + + return $document; + } + + /** + * @depends testCreateDocument + */ + public function testReadPermissionsFailure(Document $document) + { + $this->expectException(ExceptionAuthorization::class); + + $document = static::getDatabase()->createDocument('documents', new Document([ + '$read' => ['user1'], + '$write' => ['user1'], + 'string' => 'text📝', + 'integer' => 5, + 'float' => 5.55, + 'boolean' => true, + 'colors' => ['pink', 'green', 'blue'], + ])); + + return $document; + } + + /** + * @depends testCreateDocument + */ + public function testWritePermissionsSuccess(Document $document) + { + $document = static::getDatabase()->createDocument('documents', new Document([ + '$read' => ['*'], + '$write' => ['*'], + 'string' => 'text📝', + 'integer' => 5, + 'float' => 5.55, + 'boolean' => true, + 'colors' => ['pink', 'green', 'blue'], + ])); + + $this->assertEquals(false, $document->isEmpty()); + + return $document; + } + + /** + * @depends testCreateDocument + */ + public function testWritePermissionsFailure(Document $document) + { + $this->expectException(ExceptionAuthorization::class); + + Authorization::cleanRoles(); + + $document = static::getDatabase()->createDocument('documents', new Document([ + '$read' => ['*'], + '$write' => ['*'], + 'string' => 'text📝', + 'integer' => 5, + 'float' => 5.55, + 'boolean' => true, + 'colors' => ['pink', 'green', 'blue'], + ])); + + return $document; + } + + /** + * @depends testCreateDocument + */ + public function testWritePermissionsUpdateFailure(Document $document) + { + $this->expectException(ExceptionAuthorization::class); + + $document = static::getDatabase()->createDocument('documents', new Document([ + '$read' => ['*'], + '$write' => ['*'], + 'string' => 'text📝', + 'integer' => 5, + 'float' => 5.55, + 'boolean' => true, + 'colors' => ['pink', 'green', 'blue'], + ])); + + Authorization::cleanRoles(); + + $document = static::getDatabase()->updateDocument('documents', $document->getId(), new Document([ + '$id' => $document->getId(), + '$read' => ['*'], + '$write' => ['*'], + 'string' => 'text📝', + 'integer' => 5, + 'float' => 5.55, + 'boolean' => true, + 'colors' => ['pink', 'green', 'blue'], + ])); + + return $document; + } + + /** + * @depends testGetDocument + */ + public function testExceptionDuplicate(Document $document) + { + $this->expectException(Duplicate::class); + + $document->setAttribute('$id', 'duplicated'); + + static::getDatabase()->createDocument($document->getCollection(), $document); + static::getDatabase()->createDocument($document->getCollection(), $document); + + return $document; + } } \ No newline at end of file From 5ab6872e2fd27ea7e9830945b1310229d87e0ab8 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 23 Apr 2021 00:45:31 +0300 Subject: [PATCH 052/190] Updated TODOs --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index db69aec6d..f37848709 100644 --- a/README.md +++ b/README.md @@ -34,12 +34,11 @@ require_once __DIR__ . '/../../vendor/autoload.php'; - [ ] Validate original document before editing `$id` - [x] Test duplicated document exception is being thrown - [ ] Test no one can overwrite exciting documents/collections without permission -- [ ] Delete / Update documents before cleaning cache -- [ ] Test for query timeout limits - [x] Add autorization validation layer - [ ] Add strcture validation layer (based on new collection structure) -- [ ] Add cache layer +- [ ] Add cache layer (Delete / Update documents before cleaning cache) - [ ] Implement find method +- [ ] Test for find timeout limits - [ ] Merge new filter syntax parser ## System Requirements From 9e48eb823cf16e45ebdbc3274bd6a2b87f8e17fe Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 23 Apr 2021 08:00:21 +0300 Subject: [PATCH 053/190] Updated docs --- README.md | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/README.md b/README.md index f37848709..ea0a0aefa 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,77 @@ require_once __DIR__ . '/../../vendor/autoload.php'; ``` +### Concepts + +A list of the utopia/php concepts and their relevant equivalent using the different adapters + +- **Database** - An instance of the utopia/database library that abstracts one of the supported adapters and provides a unified API for CRUD operation and queries on a specific schema or isolated scope inside the underlining database. +- ** Adapter** - An implementation of an underline database engine that this library can support, below is a list of [supported adapters](#supported-adapters) and supported capabilities for each Adapter. +- **Collection** - A set of documents stored on the same adapter scope. For SQL-based adapters, this will be equivalent to a table. For a No-SQL adapter, this will equivalent to a native collection. +- **Document** - A simple JSON object that will be stored in one of the utopia/database collections. For SQL-based adapters, this will be equivalent to a row. For a No-SQL adapter, this will equivalent to a native document. +- **Attribute** A simple document attribute. For SQL-based adapters, this will be equivalent to a column. For a No-SQL adapter, this will equivalent to a native document field. +- **Index** A simple collection index used to improve the performance of your database queries. + +### Examples + +Some examples to help you get started. + +**Creating a database:** + +```php +use PDO; +use Utopia\Database\Database; +use Utopia\Database\Adapter\MariaDB; + +$dbHost = 'mariadb'; +$dbPort = '3306'; +$dbUser = 'root'; +$dbPass = 'password'; + +$pdo = new PDO("mysql:host={$dbHost};port={$dbPort};charset=utf8mb4", $dbUser, $dbPass, [ + PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4', + PDO::ATTR_TIMEOUT => 3, // Seconds + PDO::ATTR_PERSISTENT => true, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, +]); + +$database = new Database(new MariaDB($pdo)); +$database->setNamespace('myscope'); + +$database->create(); // Creates a new schema named `myscope` +``` + +**Creating a collection:** + +```php +$database->createCollection('documents'); + +// Add attributes +$database->createAttribute('documents', 'string', Database::VAR_STRING, 128); +$database->createAttribute('documents', 'integer', Database::VAR_INTEGER, 0); +$database->createAttribute('documents', 'float', Database::VAR_FLOAT, 0); +$database->createAttribute('documents', 'boolean', Database::VAR_BOOLEAN, 0); +$database->createAttribute('documents', 'colors', Database::VAR_STRING, 32, true, true); + +// Create an Index +$database->createIndex('indexes', 'index1', Database::INDEX_KEY, ['string', 'integer'], [128], [Database::ORDER_ASC]); +``` + +**Create a document:** + +```php +$document = static::getDatabase()->createDocument('documents', new Document([ + '$read' => ['*', 'user1', 'user2'], + '$write' => ['*', 'user1', 'user2'], + 'string' => 'text📝', + 'integer' => 5, + 'float' => 5.55, + 'boolean' => true, + 'colors' => ['pink', 'green', 'blue'], +])); +``` + ## TODOS - [x] Updated collection on deletion From bfd80681ae9e83d498f8cf0cdb41d9bced650dfa Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 23 Apr 2021 08:11:17 +0300 Subject: [PATCH 054/190] Updated readme --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index ea0a0aefa..217dd8dd1 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,18 @@ $document = static::getDatabase()->createDocument('documents', new Document([ ])); ``` +### Adapters + +Below is a list of supported adapters, and thier compatibly tested versions alongside a list of supported features and relevant limits. + +| Adapter | Status | Version | | | +|---------|---------|---|---|---| +| MariaDB | ✅ | 10.5 | | | +| MySQL | ✅ | 8.0 | | | +| MongoDB | 🛠 | 3.6 | | | + +` ✅ - supported, 🛠 - work in progress` + ## TODOS - [x] Updated collection on deletion From 26a08c12bb33b14e54e0f78db7b7a813b7081a51 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 23 Apr 2021 08:14:04 +0300 Subject: [PATCH 055/190] Updated support table --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 217dd8dd1..e726785e9 100644 --- a/README.md +++ b/README.md @@ -99,11 +99,13 @@ $document = static::getDatabase()->createDocument('documents', new Document([ Below is a list of supported adapters, and thier compatibly tested versions alongside a list of supported features and relevant limits. -| Adapter | Status | Version | | | -|---------|---------|---|---|---| -| MariaDB | ✅ | 10.5 | | | -| MySQL | ✅ | 8.0 | | | -| MongoDB | 🛠 | 3.6 | | | +| Adapter | Status | Version | +|---------|---------|---| +| MariaDB | ✅ | 10.5 | +| MySQL | ✅ | 8.0 | +| Postgres | 🛠 | 13.0 | +| MongoDB | 🛠 | 3.6 | +| SQLlite | 🛠 | 3.35 | ` ✅ - supported, 🛠 - work in progress` From 087fbe26654689293c9e4a2f90990df7a77c0859 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 23 Apr 2021 08:30:58 +0300 Subject: [PATCH 056/190] Improved docs --- README.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e726785e9..dbd1b6070 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,20 @@ A list of the utopia/php concepts and their relevant equivalent using the differ - ** Adapter** - An implementation of an underline database engine that this library can support, below is a list of [supported adapters](#supported-adapters) and supported capabilities for each Adapter. - **Collection** - A set of documents stored on the same adapter scope. For SQL-based adapters, this will be equivalent to a table. For a No-SQL adapter, this will equivalent to a native collection. - **Document** - A simple JSON object that will be stored in one of the utopia/database collections. For SQL-based adapters, this will be equivalent to a row. For a No-SQL adapter, this will equivalent to a native document. -- **Attribute** A simple document attribute. For SQL-based adapters, this will be equivalent to a column. For a No-SQL adapter, this will equivalent to a native document field. -- **Index** A simple collection index used to improve the performance of your database queries. +- **Attribute** - A simple document attribute. For SQL-based adapters, this will be equivalent to a column. For a No-SQL adapter, this will equivalent to a native document field. +- **Index** - A simple collection index used to improve the performance of your database queries. +- **Permissions** - Using permissions, you can decide which roles will grant read or write access for a specific document. The special attributes `$read` and `$write` are used to store permissions metadata for each document in the collection. A permission role can be any string you want. You can use `Authorization::setRole()` to delegate new roles to your users, once obtained a new role a user would gain read or write access to a relevant document. + +### Reserved Attributes + +- `$id` - the documnet unique ID, you can set your own custom ID or a random UID will be generated by the library. +- `$collection` - an attribute containing the name of the collection the document is stored in. +- `$read` - an attribute containing an array of strings. Each string represent a specific role. If your user obtains that role he will have read access for this document. +- `$write` - an attribute containing an array of strings. Each string represent a specific role. If your user obtains that role he will have write access for this document. + +### Attribute Types + +The database document interface only supports primitives types (`strings`, `integers`, `floats`, and `booleans`) translated to their native database types for each of the relevant database adapters. Complex types like arrays or objects will be encoded to JSON strings when stored and decoded back when fetched from their adapters. ### Examples From 980f168f9f9547892b1dbb68f97a86bdf88c882e Mon Sep 17 00:00:00 2001 From: "Eldad A. Fux" Date: Fri, 23 Apr 2021 18:09:25 +0300 Subject: [PATCH 057/190] Update README.md Co-authored-by: kodumbeats --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dbd1b6070..213991cfd 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ require_once __DIR__ . '/../../vendor/autoload.php'; A list of the utopia/php concepts and their relevant equivalent using the different adapters - **Database** - An instance of the utopia/database library that abstracts one of the supported adapters and provides a unified API for CRUD operation and queries on a specific schema or isolated scope inside the underlining database. -- ** Adapter** - An implementation of an underline database engine that this library can support, below is a list of [supported adapters](#supported-adapters) and supported capabilities for each Adapter. +- **Adapter** - An implementation of an underlying database engine that this library can support - below is a list of [supported adapters](#supported-adapters) and supported capabilities for each Adapter. - **Collection** - A set of documents stored on the same adapter scope. For SQL-based adapters, this will be equivalent to a table. For a No-SQL adapter, this will equivalent to a native collection. - **Document** - A simple JSON object that will be stored in one of the utopia/database collections. For SQL-based adapters, this will be equivalent to a row. For a No-SQL adapter, this will equivalent to a native document. - **Attribute** - A simple document attribute. For SQL-based adapters, this will be equivalent to a column. For a No-SQL adapter, this will equivalent to a native document field. From f474a0d39e60b1c346f63f4b2de9ce72af00e3c3 Mon Sep 17 00:00:00 2001 From: "Eldad A. Fux" Date: Fri, 23 Apr 2021 18:09:34 +0300 Subject: [PATCH 058/190] Update README.md Co-authored-by: kodumbeats --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 213991cfd..0630aaccb 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ $database->createIndex('indexes', 'index1', Database::INDEX_KEY, ['string', 'int **Create a document:** ```php -$document = static::getDatabase()->createDocument('documents', new Document([ +$document = $database->createDocument('documents', new Document([ '$read' => ['*', 'user1', 'user2'], '$write' => ['*', 'user1', 'user2'], 'string' => 'text📝', From cdf52673341f4be5d3be85b59ff0b8b03b1236d9 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 23 Apr 2021 11:21:28 -0400 Subject: [PATCH 059/190] Refactor query into standalone class --- src/Database/Query.php | 103 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 src/Database/Query.php diff --git a/src/Database/Query.php b/src/Database/Query.php new file mode 100644 index 000000000..f8b7e2545 --- /dev/null +++ b/src/Database/Query.php @@ -0,0 +1,103 @@ +attribute = $attribute; + $this->operator = $operator; + $this->operand = $operand; + } + + /** + * Parse query filter + * + * @param string $filter + * + * @return Query + * */ + public static function parse(string $filter) + { + // TODO@kodumbeats handle '.' in expression value + $stanzas = mb_substr_count(mb_substr($filter, ".") + 1; + + // TODO@kodumbeats handle relations between collections, e.g. if($stanzas > 2) + switch ($stanzas): + case 2: + $input = explode('.', $filter); + + $attribute = $input[0]; + [$operator, $operand] = $this->parseExpression($input[1]); + break; + endswitch; + + return new Query($attribute, $operator, $operand); + } + + /** + * Get attribute key-value from query expression + * $expression: string with format 'operator(operand)' + * + * @param string $expression + * + * @return array + */ + protected function parseExpression(string $expression): array + { + //find location of parentheses in expression + $start = mb_strpos($expression, '('); + $end = mb_strpos($expression, ')'); + + //extract the query method + $operator = mb_substr($expression, 0, $start); + + //grab everything inside parentheses + $operand = mb_substr($expression, + ($start + 1), /* exclude open paren*/ + ($end - $start - 1) /* exclude closed paren*/ + ); + + //strip quotes from queries of type string + $operand = str_replace('"', "", $operand); + $operand = str_replace("'", "", $operand); + + return [$operator, $operand]; + } + + // /** + // * Validate query against collection schema + // * + // * @param array $schema Structured array of collection attributes + // * + // * @return bool + // */ + // protected function isValid($schema): bool + // { + // $attributeType = $schema[array_search($attribute, array_column($schema, '$id'))]['type']; + // } +} From 3f75885d914315b7862e98b2da5d4fb61d0a0ad5 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 23 Apr 2021 11:23:32 -0400 Subject: [PATCH 060/190] Add getters for object properties --- src/Database/Query.php | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/Database/Query.php b/src/Database/Query.php index f8b7e2545..6cd909af9 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -34,6 +34,36 @@ public function __construct($attribute, $operator, $operand) $this->operand = $operand; } + /** + * Get attribute + * + * @return string + */ + public function getAttribute() + { + return $this->attribute; + } + + /** + * Get operator + * + * @return string + */ + public function getOperator() + { + return $this->operator; + } + + /** + * Get operand + * + * @return string + */ + public function getOperand() + { + return $this->operand; + } + /** * Parse query filter * From 2a8a5b36a8355e4e4d7f4333f5a7852cbfd533cb Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 23 Apr 2021 11:28:30 -0400 Subject: [PATCH 061/190] Add method to get all query details --- src/Database/Query.php | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Database/Query.php b/src/Database/Query.php index 6cd909af9..9794e4f6d 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -4,7 +4,6 @@ use Utopia\Database\Database; - class Query { /** @@ -64,6 +63,20 @@ public function getOperand() return $this->operand; } + /** + * Get all query details as array + * + * @return array + */ + public function getQuery() + { + return [ + 'attribute' => $this->attribute, + 'operator' => $this->operator, + 'operand' => $this->operand, + ]; + } + /** * Parse query filter * @@ -80,7 +93,6 @@ public static function parse(string $filter) switch ($stanzas): case 2: $input = explode('.', $filter); - $attribute = $input[0]; [$operator, $operand] = $this->parseExpression($input[1]); break; From f763ba4950263754a6ac7b265632415bed16197e Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 23 Apr 2021 12:03:44 -0400 Subject: [PATCH 062/190] Add return typing --- src/Database/Query.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Database/Query.php b/src/Database/Query.php index 9794e4f6d..41d38157e 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -38,7 +38,7 @@ public function __construct($attribute, $operator, $operand) * * @return string */ - public function getAttribute() + public function getAttribute(): string { return $this->attribute; } @@ -48,7 +48,7 @@ public function getAttribute() * * @return string */ - public function getOperator() + public function getOperator(): string { return $this->operator; } @@ -56,7 +56,7 @@ public function getOperator() /** * Get operand * - * @return string + * @return mixed */ public function getOperand() { @@ -68,7 +68,7 @@ public function getOperand() * * @return array */ - public function getQuery() + public function getQuery(): array { return [ 'attribute' => $this->attribute, @@ -84,7 +84,7 @@ public function getQuery() * * @return Query * */ - public static function parse(string $filter) + public static function parse(string $filter): Query { // TODO@kodumbeats handle '.' in expression value $stanzas = mb_substr_count(mb_substr($filter, ".") + 1; From 06f02f74985424dc81a9ff1990c5eb6b88e345b7 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 23 Apr 2021 12:04:10 -0400 Subject: [PATCH 063/190] Clean up code --- src/Database/Query.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Database/Query.php b/src/Database/Query.php index 41d38157e..2a25a1e81 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -2,8 +2,6 @@ namespace Utopia\Database; -use Utopia\Database\Database; - class Query { /** @@ -87,7 +85,7 @@ public function getQuery(): array public static function parse(string $filter): Query { // TODO@kodumbeats handle '.' in expression value - $stanzas = mb_substr_count(mb_substr($filter, ".") + 1; + $stanzas = mb_substr_count($filter, ".") + 1; // TODO@kodumbeats handle relations between collections, e.g. if($stanzas > 2) switch ($stanzas): From e01fcc403f96a8be4a2f4a1abd66f52a936c443e Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 23 Apr 2021 12:04:44 -0400 Subject: [PATCH 064/190] Call parseExpression as a static method --- src/Database/Query.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Database/Query.php b/src/Database/Query.php index 2a25a1e81..2008925c2 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -92,7 +92,7 @@ public static function parse(string $filter): Query case 2: $input = explode('.', $filter); $attribute = $input[0]; - [$operator, $operand] = $this->parseExpression($input[1]); + [$operator, $operand] = Query::parseExpression($input[1]); break; endswitch; @@ -107,7 +107,7 @@ public static function parse(string $filter): Query * * @return array */ - protected function parseExpression(string $expression): array + public static function parseExpression(string $expression): array { //find location of parentheses in expression $start = mb_strpos($expression, '('); From 65a2166889461e146039e5a672709b61f2fec04e Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 23 Apr 2021 12:05:05 -0400 Subject: [PATCH 065/190] Add tests for query class --- tests/Database/QueryTest.php | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/Database/QueryTest.php diff --git a/tests/Database/QueryTest.php b/tests/Database/QueryTest.php new file mode 100644 index 000000000..1e12e9b83 --- /dev/null +++ b/tests/Database/QueryTest.php @@ -0,0 +1,33 @@ +assertEquals('title', $query->getAttribute()); + $this->assertEquals('equal', $query->getOperator()); + $this->assertEquals('Iron Man', $query->getOperand()); + + $query = Query::parse('year.lesser(2001)'); + $this->assertEquals('year', $query->getAttribute()); + $this->assertEquals('lesser', $query->getOperator()); + $this->assertEquals(2001, $query->getOperand()); + } + +} \ No newline at end of file From 1bb848d04d6d2de6b9e399dd5fdbaef4b2d218e5 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 23 Apr 2021 12:09:48 -0400 Subject: [PATCH 066/190] Refactor parsing code into query class and tests --- src/Database/Database.php | 94 ------------------------------------ tests/Database/Base.php | 40 --------------- tests/Database/QueryTest.php | 14 +++++- 3 files changed, 13 insertions(+), 135 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index b070b425a..fd0720ce6 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -785,98 +785,4 @@ public function getId(): string { return \uniqid(); } - - /** - * Get attribute key-value from query expression - * - * @param string $expression - * - * @return array - */ - public function parseExpression(string $expression): array - { - //find location of parentheses in expression - $start = mb_strpos($expression, '('); - $end = mb_strpos($expression, ')'); - - //extract the query method - $method = mb_substr($expression, 0, $start); - - //grab everything inside parentheses - $query = mb_substr($expression, - ($start + 1), /* exclude open paren*/ - ($end - $start - 1) /* exclude closed paren*/ - ); - - //strip quotes from queries of type string - $query = str_replace('"', "", $query); - $query = str_replace("'", "", $query); - - return [$method, $query]; - } - - /** - * Parse query filters - * - * @param string $collection Collection name - * @param array $filters Array of document filters - * - * @return array - * */ - public function parseQueries(string $collection, array $filters): array - { - $collection = $this->getCollection($collection); - $schema = $collection->getAttribute('attributes', []); - - $output = []; - foreach ($filters as &$filter) - { - $stanzas = mb_substr_count($filter, ".") + 1; - - switch ($stanzas): - case 2: - $input = explode('.', $filter); - - $attribute = $input[0]; - $expression = $input[1]; - [$method, $query] = $this->parseExpression($expression); - // $attributeType = $schema[\array_search($attribute, $schema)]['type']; - $attributeType = $schema[array_search($attribute, array_column($schema, '$id'))]['type']; - array_push($output, [ - 'collection' => $collection->getId(), - 'attribute' => $attribute, - 'method' => $method, - 'query' => $query, - 'queryType' => $attributeType, - ]); - break; - - // Copypasta code - remove/refactor when collection relations work - // - // case 3: - // $input = explode('.', $filter); - - // $attribute = $input[0]; - // $childAttribute = $input[1]; - // $expression = $input[2]; - // [$method, $query] = pickAttribute($expression); - // $attributeType = checkType($schema, $collection, $attribute, $childAttribute); - // array_push($output, [ - // 'input' => $filter, - // 'output' => [ - // [ - // 'collection' => $collection, - // 'attribute' => $attribute, - // 'childAttribute' => $childAttribute, - // 'method' => $method, - // 'query' => $query, - // 'queryType' => $attributeType, - // ] - // ], - // ]); - // break; - endswitch; - } - return $output; - } } diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 20cb9bc5a..5fc3e0993 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -1559,44 +1559,4 @@ public function decodeTest() $this->assertEquals('1', '1'); } - - public function testParseQueries() - { - // Set up mock collections - $this->assertInstanceOf('Utopia\Database\Document', static::getDatabase()->createCollection('Movie')); - // $this->assertInstanceOf('Utopia\Database\Document', static::getDatabase()->createCollection('Director')); - // $this->assertInstanceOf('Utopia\Database\Document', static::getDatabase()->createCollection('Actor')); - $this->assertEquals(true, static::getDatabase()->createAttribute('Movie', 'title', Database::VAR_STRING, 256)); - $this->assertEquals(true, static::getDatabase()->createAttribute('Movie', 'year', Database::VAR_INTEGER, 0)); - $this->assertEquals(true, static::getDatabase()->createAttribute('Movie', 'published', Database::VAR_BOOLEAN, 0)); - $this->assertEquals(true, static::getDatabase()->createAttribute('Movie', 'director', Database::VAR_STRING, 256)); - $this->assertEquals(true, static::getDatabase()->createAttribute('Movie', 'actors', Database::VAR_STRING, 256, true, true)); - - // test parser - $query = static::getDatabase()->parseQueries('Movie', ['title.equal("Iron Man")']); - $this->assertEquals('Movie', $query[0]['collection']); - $this->assertEquals('title', $query[0]['attribute']); - $this->assertEquals('equal', $query[0]['method']); - $this->assertEquals('Iron Man', $query[0]['query']); - $this->assertEquals(Database::VAR_STRING, $query[0]['queryType']); - - $query = static::getDatabase()->parseQueries('Movie', ['year.lesser(2001)']); - $this->assertEquals('Movie', $query[0]['collection']); - $this->assertEquals('year', $query[0]['attribute']); - $this->assertEquals('lesser', $query[0]['method']); - $this->assertEquals(2001, $query[0]['query']); - $this->assertEquals(Database::VAR_INTEGER, $query[0]['queryType']); - } - - public function testParseExpression() - { - [$method, $query] = static::getDatabase()->parseExpression('equal("Spiderman")'); - $this->assertEquals('equal', $method); - $this->assertEquals('Spiderman', $query); - - - [$method, $query] = static::getDatabase()->parseExpression('lesser(2001)'); - $this->assertEquals('lesser', $method); - $this->assertEquals(2001, $query); - } } \ No newline at end of file diff --git a/tests/Database/QueryTest.php b/tests/Database/QueryTest.php index 1e12e9b83..d74726bb4 100644 --- a/tests/Database/QueryTest.php +++ b/tests/Database/QueryTest.php @@ -16,7 +16,7 @@ public function tearDown(): void { } - public function testCreate() + public function testParse() { $query = Query::parse('title.equal("Iron Man")'); @@ -30,4 +30,16 @@ public function testCreate() $this->assertEquals(2001, $query->getOperand()); } + public function testParseExpression() + { + [$operator, $operand] = Query::parseExpression('equal("Spiderman")'); + $this->assertEquals('equal', $operator); + $this->assertEquals('Spiderman', $operand); + + + [$operator, $operand] = Query::parseExpression('lesser(2001)'); + $this->assertEquals('lesser', $operator); + $this->assertEquals(2001, $operand); + } + } \ No newline at end of file From e8021531558efb209428aa9be1b49a3c304ce802 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 23 Apr 2021 12:26:07 -0400 Subject: [PATCH 067/190] Add type hinting --- src/Database/Query.php | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Database/Query.php b/src/Database/Query.php index 2008925c2..b0924c8d8 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -23,8 +23,12 @@ class Query * Construct. * * Construct a new query object + * + * @param string $attribute + * @param string $operator + * @param mixed $operand */ - public function __construct($attribute, $operator, $operand) + public function __construct($attribute, $operator, $operand) { $this->attribute = $attribute; $this->operator = $operator; @@ -105,18 +109,25 @@ public static function parse(string $filter): Query * * @param string $expression * - * @return array + * @return (string|mixed)[] */ public static function parseExpression(string $expression): array { //find location of parentheses in expression + + /** @var int */ $start = mb_strpos($expression, '('); + /** @var int */ $end = mb_strpos($expression, ')'); //extract the query method + + /** @var string */ $operator = mb_substr($expression, 0, $start); //grab everything inside parentheses + + /** @var mixed */ $operand = mb_substr($expression, ($start + 1), /* exclude open paren*/ ($end - $start - 1) /* exclude closed paren*/ From c18b485e540e6ac7476e42e44084ea2d74080fa6 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 23 Apr 2021 12:48:50 -0400 Subject: [PATCH 068/190] Add types to constructor params --- src/Database/Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Database/Query.php b/src/Database/Query.php index b0924c8d8..ab51a691d 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -28,7 +28,7 @@ class Query * @param string $operator * @param mixed $operand */ - public function __construct($attribute, $operator, $operand) + public function __construct(string $attribute, string $operator, $operand) { $this->attribute = $attribute; $this->operator = $operator; From b2f6603dd7f099a7e54af60f52ab516a05abf077 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 23 Apr 2021 12:54:14 -0400 Subject: [PATCH 069/190] Rename operand to value --- src/Database/Query.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Database/Query.php b/src/Database/Query.php index ab51a691d..3bb7f0b5d 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -17,7 +17,7 @@ class Query /** * @var mixed */ - protected $operand; + protected $value; /** * Construct. @@ -26,13 +26,13 @@ class Query * * @param string $attribute * @param string $operator - * @param mixed $operand + * @param mixed $value */ - public function __construct(string $attribute, string $operator, $operand) + public function __construct(string $attribute, string $operator, $value) { $this->attribute = $attribute; $this->operator = $operator; - $this->operand = $operand; + $this->value = $value; } /** @@ -96,11 +96,11 @@ public static function parse(string $filter): Query case 2: $input = explode('.', $filter); $attribute = $input[0]; - [$operator, $operand] = Query::parseExpression($input[1]); + [$operator, $value] = Query::parseExpression($input[1]); break; endswitch; - return new Query($attribute, $operator, $operand); + return new Query($attribute, $operator, $value); } /** @@ -128,16 +128,16 @@ public static function parseExpression(string $expression): array //grab everything inside parentheses /** @var mixed */ - $operand = mb_substr($expression, + $value = mb_substr($expression, ($start + 1), /* exclude open paren*/ ($end - $start - 1) /* exclude closed paren*/ ); //strip quotes from queries of type string - $operand = str_replace('"', "", $operand); - $operand = str_replace("'", "", $operand); + $value = str_replace('"', "", $value); + $value = str_replace("'", "", $value); - return [$operator, $operand]; + return [$operator, $value]; } // /** From f581824aed527bd2214eed598d60a944515fe3f1 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 23 Apr 2021 12:55:09 -0400 Subject: [PATCH 070/190] Change to protected method --- src/Database/Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Database/Query.php b/src/Database/Query.php index 3bb7f0b5d..2487f438a 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -111,7 +111,7 @@ public static function parse(string $filter): Query * * @return (string|mixed)[] */ - public static function parseExpression(string $expression): array + protected static function parseExpression(string $expression): array { //find location of parentheses in expression From fe6becbf4389902dfb0e202c14b4672124b858d6 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 23 Apr 2021 12:57:39 -0400 Subject: [PATCH 071/190] Use bracket syntax for switch --- src/Database/Query.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Database/Query.php b/src/Database/Query.php index 2487f438a..ca7017279 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -92,13 +92,13 @@ public static function parse(string $filter): Query $stanzas = mb_substr_count($filter, ".") + 1; // TODO@kodumbeats handle relations between collections, e.g. if($stanzas > 2) - switch ($stanzas): + switch ($stanzas) { case 2: $input = explode('.', $filter); $attribute = $input[0]; [$operator, $value] = Query::parseExpression($input[1]); break; - endswitch; + } return new Query($attribute, $operator, $value); } From 1c6e60c20f7afcd8639a55b8fb56c4acfb0cdf7d Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 23 Apr 2021 13:08:46 -0400 Subject: [PATCH 072/190] Treat query expression as kv pair --- src/Database/Query.php | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/Database/Query.php b/src/Database/Query.php index ca7017279..9e04034d0 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -25,14 +25,13 @@ class Query * Construct a new query object * * @param string $attribute - * @param string $operator + * @param array $expression * @param mixed $value */ - public function __construct(string $attribute, string $operator, $value) + public function __construct(string $attribute, array $expression) { $this->attribute = $attribute; - $this->operator = $operator; - $this->value = $value; + [$this->operator, $this->value] = $expression; } /** @@ -60,9 +59,9 @@ public function getOperator(): string * * @return mixed */ - public function getOperand() + public function getValue() { - return $this->operand; + return $this->value; } /** @@ -96,11 +95,11 @@ public static function parse(string $filter): Query case 2: $input = explode('.', $filter); $attribute = $input[0]; - [$operator, $value] = Query::parseExpression($input[1]); + $expression = Query::parseExpression($input[1]); break; } - return new Query($attribute, $operator, $value); + return new Query($attribute, $expression); } /** @@ -137,7 +136,7 @@ protected static function parseExpression(string $expression): array $value = str_replace('"', "", $value); $value = str_replace("'", "", $value); - return [$operator, $value]; + return [$operator => $value]; } // /** From 179a0d51da3db2313a9c7662ebcc593b68d0c1f0 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 23 Apr 2021 14:51:23 -0400 Subject: [PATCH 073/190] Create query validator --- src/Database/Query.php | 13 +---- src/Database/Validator/Query.php | 88 ++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 12 deletions(-) create mode 100644 src/Database/Validator/Query.php diff --git a/src/Database/Query.php b/src/Database/Query.php index 9e04034d0..6b188ee6e 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -105,6 +105,7 @@ public static function parse(string $filter): Query /** * Get attribute key-value from query expression * $expression: string with format 'operator(operand)' + * $expression: string with format 'operator(value)' * * @param string $expression * @@ -138,16 +139,4 @@ protected static function parseExpression(string $expression): array return [$operator => $value]; } - - // /** - // * Validate query against collection schema - // * - // * @param array $schema Structured array of collection attributes - // * - // * @return bool - // */ - // protected function isValid($schema): bool - // { - // $attributeType = $schema[array_search($attribute, array_column($schema, '$id'))]['type']; - // } } diff --git a/src/Database/Validator/Query.php b/src/Database/Validator/Query.php new file mode 100644 index 000000000..6bc38b909 --- /dev/null +++ b/src/Database/Validator/Query.php @@ -0,0 +1,88 @@ +schema = $schema; + } + + /** + * Get Description. + * + * Returns validator description + * + * @return string + */ + public function getDescription() + { + return $this->message; + } + + /** + * Is valid. + * + * Returns true if query typed according to schema. + * + * @param Query $query + * + * @return bool + */ + public function isValid(Query $query) + { + // Extract the type of desired attribute from collection $schema + $attributeType = $this->schema[array_search($query->getAttribute(), array_column($this->schema, '$id'))]['type']; + + if ($attributeType === gettype($query->getValue())) { + return true; + } + + return false; + + } + /** + * Is array + * + * Function will return true if object is array. + * + * @return bool + */ + public function isArray(): bool + { + return true; + } + + /** + * Get Type + * + * Returns validator type. + * + * @return string + */ + public function getType(): string + { + return self::TYPE_OBJECT; + } +} From 8ada10e25b6d2045262796f785f6539e41598bfe Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 23 Apr 2021 14:53:45 -0400 Subject: [PATCH 074/190] Remove references to old var --- src/Database/Query.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Database/Query.php b/src/Database/Query.php index 6b188ee6e..b6bfaf90b 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -74,7 +74,7 @@ public function getQuery(): array return [ 'attribute' => $this->attribute, 'operator' => $this->operator, - 'operand' => $this->operand, + 'operand' => $this->value, ]; } @@ -104,7 +104,6 @@ public static function parse(string $filter): Query /** * Get attribute key-value from query expression - * $expression: string with format 'operator(operand)' * $expression: string with format 'operator(value)' * * @param string $expression From 8934ce72169d79b4071d24d0bcbf97d401edc219 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 23 Apr 2021 14:54:18 -0400 Subject: [PATCH 075/190] Revert "Change to protected method" This reverts commit f581824aed527bd2214eed598d60a944515fe3f1. --- src/Database/Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Database/Query.php b/src/Database/Query.php index b6bfaf90b..b7665d25b 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -110,7 +110,7 @@ public static function parse(string $filter): Query * * @return (string|mixed)[] */ - protected static function parseExpression(string $expression): array + public static function parseExpression(string $expression): array { //find location of parentheses in expression From 38b02cf39c1d8cdc934edb34749f7c6db6758580 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 23 Apr 2021 15:15:52 -0400 Subject: [PATCH 076/190] Revert "Treat query expression as kv pair" This reverts commit 1c6e60c20f7afcd8639a55b8fb56c4acfb0cdf7d. --- src/Database/Query.php | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Database/Query.php b/src/Database/Query.php index b7665d25b..2ade8464a 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -25,13 +25,14 @@ class Query * Construct a new query object * * @param string $attribute - * @param array $expression + * @param string $operator * @param mixed $value */ - public function __construct(string $attribute, array $expression) + public function __construct(string $attribute, string $operator, $value) { $this->attribute = $attribute; - [$this->operator, $this->value] = $expression; + $this->operator = $operator; + $this->value = $value; } /** @@ -59,9 +60,9 @@ public function getOperator(): string * * @return mixed */ - public function getValue() + public function getOperand() { - return $this->value; + return $this->operand; } /** @@ -95,11 +96,11 @@ public static function parse(string $filter): Query case 2: $input = explode('.', $filter); $attribute = $input[0]; - $expression = Query::parseExpression($input[1]); + [$operator, $value] = Query::parseExpression($input[1]); break; } - return new Query($attribute, $expression); + return new Query($attribute, $operator, $value); } /** @@ -136,6 +137,6 @@ public static function parseExpression(string $expression): array $value = str_replace('"', "", $value); $value = str_replace("'", "", $value); - return [$operator => $value]; + return [$operator, $value]; } } From c2b9dc268a3034a3055404a5ad31c736192f00bd Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 23 Apr 2021 15:18:52 -0400 Subject: [PATCH 077/190] Use value instead of operand --- src/Database/Query.php | 4 ++-- tests/Database/QueryTest.php | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Database/Query.php b/src/Database/Query.php index 2ade8464a..5cfb46b6e 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -60,9 +60,9 @@ public function getOperator(): string * * @return mixed */ - public function getOperand() + public function getValue() { - return $this->operand; + return $this->value; } /** diff --git a/tests/Database/QueryTest.php b/tests/Database/QueryTest.php index d74726bb4..e326c146f 100644 --- a/tests/Database/QueryTest.php +++ b/tests/Database/QueryTest.php @@ -22,12 +22,12 @@ public function testParse() $this->assertEquals('title', $query->getAttribute()); $this->assertEquals('equal', $query->getOperator()); - $this->assertEquals('Iron Man', $query->getOperand()); + $this->assertEquals('Iron Man', $query->getValue()); $query = Query::parse('year.lesser(2001)'); $this->assertEquals('year', $query->getAttribute()); $this->assertEquals('lesser', $query->getOperator()); - $this->assertEquals(2001, $query->getOperand()); + $this->assertEquals(2001, $query->getValue()); } public function testParseExpression() @@ -37,9 +37,9 @@ public function testParseExpression() $this->assertEquals('Spiderman', $operand); - [$operator, $operand] = Query::parseExpression('lesser(2001)'); + [$operator, $value] = Query::parseExpression('lesser(2001)'); $this->assertEquals('lesser', $operator); - $this->assertEquals(2001, $operand); + $this->assertEquals(2001, $value); } } \ No newline at end of file From 70719234c8757775bcdc8ffc1fa0e1d84ab88d88 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 23 Apr 2021 15:57:28 -0400 Subject: [PATCH 078/190] Clarify intended behavior for parseExpression() --- src/Database/Query.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Database/Query.php b/src/Database/Query.php index 5cfb46b6e..d6ba49437 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -96,7 +96,8 @@ public static function parse(string $filter): Query case 2: $input = explode('.', $filter); $attribute = $input[0]; - [$operator, $value] = Query::parseExpression($input[1]); + $expression = $input[1]; + [$operator, $value] = self::parseExpression($expression); break; } @@ -111,7 +112,7 @@ public static function parse(string $filter): Query * * @return (string|mixed)[] */ - public static function parseExpression(string $expression): array + protected static function parseExpression(string $expression): array { //find location of parentheses in expression From 0ec83e0529716be2f8e80c50e3b5ef1e19380724 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 23 Apr 2021 16:37:23 -0400 Subject: [PATCH 079/190] Add and fix query tests --- src/Database/Query.php | 4 ++-- tests/Database/QueryTest.php | 35 +++++++++++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/Database/Query.php b/src/Database/Query.php index d6ba49437..16ebaca83 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -75,7 +75,7 @@ public function getQuery(): array return [ 'attribute' => $this->attribute, 'operator' => $this->operator, - 'operand' => $this->value, + 'value' => $this->value, ]; } @@ -112,7 +112,7 @@ public static function parse(string $filter): Query * * @return (string|mixed)[] */ - protected static function parseExpression(string $expression): array + public static function parseExpression(string $expression): array { //find location of parentheses in expression diff --git a/tests/Database/QueryTest.php b/tests/Database/QueryTest.php index e326c146f..c03c8aa12 100644 --- a/tests/Database/QueryTest.php +++ b/tests/Database/QueryTest.php @@ -32,9 +32,9 @@ public function testParse() public function testParseExpression() { - [$operator, $operand] = Query::parseExpression('equal("Spiderman")'); + [$operator, $value] = Query::parseExpression('equal("Spiderman")'); $this->assertEquals('equal', $operator); - $this->assertEquals('Spiderman', $operand); + $this->assertEquals('Spiderman', $value); [$operator, $value] = Query::parseExpression('lesser(2001)'); @@ -42,4 +42,35 @@ public function testParseExpression() $this->assertEquals(2001, $value); } + public function testGetAttribute() + { + $query = Query::parse('title.equal("Iron Man")'); + + $this->assertEquals('title', $query->getAttribute()); + } + + public function testGetOperator() + { + $query = Query::parse('title.equal("Iron Man")'); + + $this->assertEquals('equal', $query->getOperator()); + } + + public function testGetValue() + { + $query = Query::parse('title.equal("Iron Man")'); + + $this->assertEquals('Iron Man', $query->getValue()); + } + + public function testGetQuery() + { + $parsed = Query::parse('title.equal("Iron Man")'); + $query = $parsed->getQuery(); + + $this->assertEquals('title', $query['attribute']); + $this->assertEquals('equal', $query['operator']); + $this->assertEquals('Iron Man', $query['value']); + } + } \ No newline at end of file From e3f1efb43942518b2d8a3eb59a3c41e7ca0411cc Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 23 Apr 2021 17:11:39 -0400 Subject: [PATCH 080/190] Constructor expects array of $values by default --- src/Database/Query.php | 28 +++++++++++++++++----------- tests/Database/QueryTest.php | 16 ++++++++-------- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/Database/Query.php b/src/Database/Query.php index 16ebaca83..78e930bab 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -15,9 +15,9 @@ class Query protected $operator = ''; /** - * @var mixed + * @var (mixed)[] */ - protected $value; + protected $values; /** * Construct. @@ -26,13 +26,13 @@ class Query * * @param string $attribute * @param string $operator - * @param mixed $value + * @param array $values */ - public function __construct(string $attribute, string $operator, $value) + public function __construct(string $attribute, string $operator, array $values) { $this->attribute = $attribute; $this->operator = $operator; - $this->value = $value; + $this->values = $values; } /** @@ -60,9 +60,9 @@ public function getOperator(): string * * @return mixed */ - public function getValue() + public function getValues() { - return $this->value; + return $this->values; } /** @@ -75,7 +75,7 @@ public function getQuery(): array return [ 'attribute' => $this->attribute, 'operator' => $this->operator, - 'value' => $this->value, + 'values' => $this->values, ]; } @@ -97,11 +97,11 @@ public static function parse(string $filter): Query $input = explode('.', $filter); $attribute = $input[0]; $expression = $input[1]; - [$operator, $value] = self::parseExpression($expression); + [$operator, $values] = self::parseExpression($expression); break; } - return new Query($attribute, $operator, $value); + return new Query($attribute, $operator, $values); } /** @@ -110,7 +110,7 @@ public static function parse(string $filter): Query * * @param string $expression * - * @return (string|mixed)[] + * @return (string|array)[] */ public static function parseExpression(string $expression): array { @@ -138,6 +138,12 @@ public static function parseExpression(string $expression): array $value = str_replace('"', "", $value); $value = str_replace("'", "", $value); + // if $value is not array, return array with single $value + // TODO@kodumbeats appropriately cast type of ints, floats, bools + if (gettype($value) !== 'array') { + $value = [$value]; + } + return [$operator, $value]; } } diff --git a/tests/Database/QueryTest.php b/tests/Database/QueryTest.php index c03c8aa12..f4f5dc1f3 100644 --- a/tests/Database/QueryTest.php +++ b/tests/Database/QueryTest.php @@ -22,24 +22,24 @@ public function testParse() $this->assertEquals('title', $query->getAttribute()); $this->assertEquals('equal', $query->getOperator()); - $this->assertEquals('Iron Man', $query->getValue()); + $this->assertContains('Iron Man', $query->getValues()); $query = Query::parse('year.lesser(2001)'); $this->assertEquals('year', $query->getAttribute()); $this->assertEquals('lesser', $query->getOperator()); - $this->assertEquals(2001, $query->getValue()); + $this->assertContains('2001', $query->getValues()); } public function testParseExpression() { - [$operator, $value] = Query::parseExpression('equal("Spiderman")'); + [$operator, $values] = Query::parseExpression('equal("Spiderman")'); $this->assertEquals('equal', $operator); - $this->assertEquals('Spiderman', $value); + $this->assertContains('Spiderman', $values); - [$operator, $value] = Query::parseExpression('lesser(2001)'); + [$operator, $values] = Query::parseExpression('lesser(2001)'); $this->assertEquals('lesser', $operator); - $this->assertEquals(2001, $value); + $this->assertContains('2001', $values); } public function testGetAttribute() @@ -60,7 +60,7 @@ public function testGetValue() { $query = Query::parse('title.equal("Iron Man")'); - $this->assertEquals('Iron Man', $query->getValue()); + $this->assertContains('Iron Man', $query->getValues()); } public function testGetQuery() @@ -70,7 +70,7 @@ public function testGetQuery() $this->assertEquals('title', $query['attribute']); $this->assertEquals('equal', $query['operator']); - $this->assertEquals('Iron Man', $query['value']); + $this->assertContains('Iron Man', $query['values']); } } \ No newline at end of file From 0659fe8787a8e12b46a62a1da56584b5ba4de524 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 23 Apr 2021 17:20:47 -0400 Subject: [PATCH 081/190] Properly scope parseExpression --- src/Database/Query.php | 2 +- tests/Database/QueryTest.php | 12 ------------ 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/src/Database/Query.php b/src/Database/Query.php index 78e930bab..3b9403de3 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -112,7 +112,7 @@ public static function parse(string $filter): Query * * @return (string|array)[] */ - public static function parseExpression(string $expression): array + protected static function parseExpression(string $expression): array { //find location of parentheses in expression diff --git a/tests/Database/QueryTest.php b/tests/Database/QueryTest.php index f4f5dc1f3..1b97e4847 100644 --- a/tests/Database/QueryTest.php +++ b/tests/Database/QueryTest.php @@ -30,18 +30,6 @@ public function testParse() $this->assertContains('2001', $query->getValues()); } - public function testParseExpression() - { - [$operator, $values] = Query::parseExpression('equal("Spiderman")'); - $this->assertEquals('equal', $operator); - $this->assertContains('Spiderman', $values); - - - [$operator, $values] = Query::parseExpression('lesser(2001)'); - $this->assertEquals('lesser', $operator); - $this->assertContains('2001', $values); - } - public function testGetAttribute() { $query = Query::parse('title.equal("Iron Man")'); From cd664537d82370d162f53ab16209a08673eb31a7 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 24 Apr 2021 01:56:38 +0300 Subject: [PATCH 082/190] Added a space :) --- src/Database/Validator/Key.php | 1 + src/Database/Validator/Permissions.php | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Database/Validator/Key.php b/src/Database/Validator/Key.php index e6bb3b426..7bb42f0c1 100644 --- a/src/Database/Validator/Key.php +++ b/src/Database/Validator/Key.php @@ -52,6 +52,7 @@ public function isValid($value) return true; } + /** * Is array * diff --git a/src/Database/Validator/Permissions.php b/src/Database/Validator/Permissions.php index 9a2a5eb4a..829ee70ec 100644 --- a/src/Database/Validator/Permissions.php +++ b/src/Database/Validator/Permissions.php @@ -49,6 +49,7 @@ public function isValid($roles) return true; } + /** * Is array * From 0e079a8788ea56477f26e0208422d31bc9e0efea Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 25 Apr 2021 01:58:51 +0300 Subject: [PATCH 083/190] Work in progress structure validator --- src/Database/Adapter.php | 2 - src/Database/Adapter/MariaDB.php | 3 +- src/Database/Validator/Structure.php | 545 ++++++++++----------- tests/Database/Validator/StructureTest.php | 141 ++++++ 4 files changed, 396 insertions(+), 295 deletions(-) create mode 100644 tests/Database/Validator/StructureTest.php diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index 25766af08..0457b6a90 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -6,8 +6,6 @@ abstract class Adapter { - const METADATA = 'metadata'; - /** * @var string */ diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 261f9348e..f1bd52f30 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -521,7 +521,7 @@ public function getSupportForFulltextIndex(): bool * Get SQL Type * * @param string $type - * @param int $size + * @param int $size in chars * * @return string */ @@ -529,6 +529,7 @@ protected function getSQLType(string $type, int $size, bool $signed = true): str { switch ($type) { case Database::VAR_STRING: + // $size = $size * 4; // Convert utf8mb4 size to bytes if($size > 16777215) { return 'LONGTEXT'; } diff --git a/src/Database/Validator/Structure.php b/src/Database/Validator/Structure.php index 7ada96b19..bdf022545 100644 --- a/src/Database/Validator/Structure.php +++ b/src/Database/Validator/Structure.php @@ -1,294 +1,255 @@ '$id', -// '$collection' => Database::COLLECTION_RULES, -// 'key' => '$id', -// 'type' => Database::VAR_KEY, -// 'default' => null, -// 'required' => false, -// 'array' => false, -// ], -// [ -// 'label' => '$collection', -// '$collection' => Database::COLLECTION_RULES, -// 'key' => '$collection', -// 'type' => Database::VAR_KEY, -// 'default' => null, -// 'required' => true, -// 'array' => false, -// ], -// [ -// 'label' => '$permissions', -// '$collection' => Database::COLLECTION_RULES, -// 'key' => '$permissions', -// 'type' => 'permissions', -// 'default' => null, -// 'required' => true, -// 'array' => false, -// ], -// [ -// 'label' => '$createdAt', -// '$collection' => Database::COLLECTION_RULES, -// 'key' => '$createdAt', -// 'type' => Database::VAR_INTEGER, -// 'default' => null, -// 'required' => false, -// 'array' => false, -// ], -// [ -// 'label' => '$updatedAt', -// '$collection' => Database::COLLECTION_RULES, -// 'key' => '$updatedAt', -// 'type' => Database::VAR_INTEGER, -// 'default' => null, -// 'required' => false, -// 'array' => false, -// ], -// ]; - -// /** -// * @var string -// */ -// protected $message = 'General Error'; - -// /** -// * Structure constructor. -// * -// * @param Database $database -// */ -// public function __construct(Database $database) -// { -// $this->database = $database; -// } - -// /** -// * Get Description. -// * -// * Returns validator description -// * -// * @return string -// */ -// public function getDescription() -// { -// return 'Invalid document structure: '.$this->message; -// } - -// /** -// * Is valid. -// * -// * Returns true if valid or false if not. -// * -// * @param mixed $document -// * -// * @return bool -// */ -// public function isValid($document) -// { -// $document = (\is_array($document)) ? new Document($document) : $document; - -// $this->id = $document->getId(); - -// if (\is_null($document->getCollection())) { -// $this->message = 'Missing collection attribute $collection'; - -// return false; -// } - -// $collection = $this->getCollection(Database::COLLECTIONS, $document->getCollection()); - -// if (\is_null($collection->getId()) || Database::COLLECTIONS != $collection->getCollection()) { -// $this->message = 'Collection "'.$collection->getCollection().'" not found'; -// return false; -// } - -// $array = $document->getArrayCopy(); -// $rules = \array_merge($this->rules, $collection->getAttribute('rules', [])); - -// foreach ($rules as $rule) { // Check all required keys are set -// if (isset($rule['key']) && !isset($array[$rule['key']]) -// && isset($rule['required']) && true == $rule['required']) { -// $this->message = 'Missing required key "'.$rule['key'].'"'; - -// return false; -// } -// } - -// foreach ($array as $key => $value) { -// $rule = $collection->search('key', $key, $rules); - -// if (!$rule) { -// continue; -// } - -// $ruleType = $rule['type'] ?? ''; -// $ruleRequired = $rule['required'] ?? true; -// $ruleArray = $rule['array'] ?? false; -// $validator = null; - -// switch ($ruleType) { -// case self::RULE_TYPE_PERMISSIONS: -// $validator = new Permissions(); -// break; -// case Database::VAR_KEY: -// $validator = new Key(); -// break; -// case Database::VAR_STRING: -// case self::RULE_TYPE_MARKDOWN: -// $validator = new Validator\Text(0); -// break; -// case Database::VAR_NUMERIC: -// $validator = new Validator\Numeric(); -// break; -// case Database::VAR_INTEGER: -// $validator = new Validator\Integer(); -// break; -// case Database::VAR_FLOAT: -// $validator = new Validator\FloatValidator(); -// break; -// case Database::VAR_BOOLEAN: -// $validator = new Validator\Boolean(); -// break; -// case Database::VAR_EMAIL: -// $validator = new Validator\Email(); -// break; -// case Database::VAR_URL: -// $validator = new Validator\URL(); -// break; -// case self::RULE_TYPE_IP: -// $validator = new Validator\IP(); -// break; -// case Database::VAR_IPV4: -// $validator = new Validator\IP(Validator\IP::V4); -// break; -// case Database::VAR_IPV6: -// $validator = new Validator\IP(Validator\IP::V6); -// break; -// case Database::VAR_DOCUMENT: -// $validator = new Collection($this->database, (isset($rule['list'])) ? $rule['list'] : []); -// $value = $document->getAttribute($key); -// break; -// case self::RULE_TYPE_DOCUMENTID: -// $validator = new DocumentId($this->database, (isset($rule['list']) && isset($rule['list'][0])) ? $rule['list'][0] : ''); -// $value = $document->getAttribute($key); -// break; -// case self::RULE_TYPE_FILEID: -// $validator = new DocumentId($this->database, Database::COLLECTION_FILES); -// $value = $document->getAttribute($key); -// break; -// } - -// if (empty($validator)) { // Error creating validator for property -// $this->message = 'Unknown rule type "'.$ruleType.'" for property "'.\htmlspecialchars($key, ENT_QUOTES, 'UTF-8').'"'; - -// if (empty($ruleType)) { -// $this->message = 'Unknown property "'.$key.'" type'. -// '. Make sure to follow '.\strtolower($collection->getAttribute('name', 'unknown')).' collection structure'; -// } - -// return false; -// } - -// if ($ruleRequired && ('' === $value || null === $value)) { -// $this->message = 'Required property "'.$key.'" has no value'; - -// return false; -// } - -// if (!$ruleRequired && empty($value)) { -// unset($array[$key]); -// unset($rule); - -// continue; -// } - -// if ($ruleArray) { // Array of values validation -// if (!\is_array($value)) { -// $this->message = 'Property "'.$key.'" must be an array'; - -// return false; -// } - -// // TODO add is required check here - -// foreach ($value as $node) { -// if (!$validator->isValid($node)) { // Check if property is valid, if not required can also be empty -// $this->message = 'Property "'.$key.'" has invalid input. '.$validator->getDescription(); - -// return false; -// } -// } -// } else { // Single value validation -// if ((!$validator->isValid($value)) && !('' === $value && !$ruleRequired)) { // Error when value is not valid, and is not optional and empty -// $this->message = 'Property "'.$key.'" has invalid input. '.$validator->getDescription(); - -// return false; -// } -// } - -// unset($array[$key]); -// unset($rule); -// } - -// if (!empty($array)) { // No fields should be left unvalidated -// $this->message = 'Unknown properties are not allowed ('.\implode(', ', \array_keys($array)).') for this collection'. -// '. Make sure to follow '.\strtolower($collection->getAttribute('name', 'unknown')).' collection structure'; - -// return false; -// } - -// return true; -// } - -// /** -// * Get Collection -// * -// * Get Collection by unique ID -// * -// * @return Document -// */ -// protected function getCollection(string $collection, string $id): Document -// { -// return $this->database->getDocument($collection, $id); -// } -// } +namespace Utopia\Database\Validator; + +use Utopia\Database\Database; +use Utopia\Database\Document; +use Utopia\Validator; +use Utopia\Validator\Boolean; +use Utopia\Validator\FloatValidator; +use Utopia\Validator\Integer; +use Utopia\Validator\Text; + +class Structure extends Validator +{ + /** + * @var Document + */ + protected $collection; + + /** + * @var array + */ + protected $attributes = [ + [ + '$id' => '$id', + 'type' => Database::VAR_STRING, + 'size' => 64, + 'required' => false, + 'signed' => true, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => '$collection', + 'type' => Database::VAR_STRING, + 'size' => 64, + 'required' => true, + 'signed' => true, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => '$read', + 'type' => Database::VAR_STRING, + 'size' => 64, + 'required' => false, + 'signed' => true, + 'array' => true, + 'filters' => [], + ], + [ + '$id' => '$write', + 'type' => Database::VAR_STRING, + 'size' => 64, + 'required' => false, + 'signed' => true, + 'array' => true, + 'filters' => [], + ], + ]; + + /** + * @var Validator[] + */ + static protected $validators = []; + + /** + * @var string + */ + protected $message = 'General Error'; + + /** + * Structure constructor. + * + * @param Document $collection + */ + public function __construct(Document $collection) + { + $this->collection = $collection; + } + + /** + * Remove a Validator + * + * @param string $name + */ + static public function getValidators() + { + return self::$validators; + } + + /** + * Add a new Validator + * + * @param string $name + * @param Validator $validator + * @param string $type + */ + static public function addValidator(string $name, Validator $validator, string $type) + { + self::$validators[$name] = [ + 'validator' => $validator, + 'type' => $type, + ]; + } + + /** + * Remove a Validator + * + * @param string $name + */ + static public function removeValidator(string $name) + { + unset(self::$validators[$name]); + } + + /** + * Get Description. + * + * Returns validator description + * + * @return string + */ + public function getDescription() + { + return 'Invalid document structure: '.$this->message; + } + + /** + * Is valid. + * + * Returns true if valid or false if not. + * + * @param Document $document + * + * @return bool + */ + public function isValid($document) + { + if(!$document instanceof Document) { + $this->message = 'Value must be an instance of Document'; + return false; + } + + if (empty($document->getCollection())) { + $this->message = 'Missing collection attribute $collection'; + return false; + } + + if (empty($this->collection->getId()) || Database::COLLECTIONS !== $this->collection->getCollection()) { + $this->message = 'Collection "'.$this->collection->getCollection().'" not found'; + return false; + } + + $keys = []; + $structure = $document->getArrayCopy(); + $attributes = \array_merge($this->attributes, $this->collection->getAttribute('attributes', [])); + + foreach ($attributes as $key => $attribute) { // Check all required attributes are set + $name = $attribute['$id'] ?? ''; + $required = $attribute['required'] ?? false; + + $keys[$name] = $attribute; // List of allowed attributes to help find unknown ones + + if($required && !isset($structure[$name])) { + $this->message = 'Missing required attribute "'.$name.'"'; + return false; + } + } + + foreach ($structure as $key => $value) { + if(!array_key_exists($key, $keys)) { // Check no unknown attributes are set + $this->message = 'Unknown attribute: "'. '"'.$key.'"'; + return false; + } + + $attribute = $keys[$key] ?? []; + $type = $attribute['type'] ?? ''; + $array = $attribute['array'] ?? false; + + switch ($type) { + case Database::VAR_STRING: + $validator = new Text(0); + break; + + case Database::VAR_INTEGER: + $validator = new Integer(); + break; + + case Database::VAR_FLOAT: + $validator = new FloatValidator(); + break; + + + case Database::VAR_BOOLEAN: + $validator = new Boolean(); + break; + + default: + $this->message = 'Unknown attribute type "'.$type.'"'; + return false; + break; + } + + if($array) { // Validate attribute type + if(!is_array($value)) { + $this->message = 'Attribute "'.$key.'" must be an array'; + return false; + } + + foreach ($value as $x => $child) { + if(!$validator->isValid($child)) { + $this->message = 'Attribute "'.$key.'"["'.$x.'"] has invalid type. '.$validator->getDescription(); + return false; + } + } + } + else { + if(!$validator->isValid($value)) { + $this->message = 'Attribute "'.$key.'" has invalid type. '.$validator->getDescription(); + return false; + } + } + + // TODO check for length / size + // TODO check for specific validation + } + + return true; + } + + /** + * Is array + * + * Function will return true if object is array. + * + * @return bool + */ + public function isArray(): bool + { + return false; + } + + /** + * Get Type + * + * Returns validator type. + * + * @return string + */ + public function getType(): string + { + return self::TYPE_ARRAY; + } +} diff --git a/tests/Database/Validator/StructureTest.php b/tests/Database/Validator/StructureTest.php new file mode 100644 index 000000000..4b8420ab9 --- /dev/null +++ b/tests/Database/Validator/StructureTest.php @@ -0,0 +1,141 @@ + Database::COLLECTIONS, + '$collection' => Database::COLLECTIONS, + 'name' => 'collections', + 'attributes' => [ + [ + '$id' => 'title', + 'type' => Database::VAR_STRING, + 'size' => 256, + 'required' => true, + 'signed' => true, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'description', + 'type' => Database::VAR_STRING, + 'size' => 1000000, + 'required' => true, + 'signed' => true, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'rating', + 'type' => Database::VAR_INTEGER, + 'size' => 5, + 'required' => true, + 'signed' => true, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'published', + 'type' => Database::VAR_BOOLEAN, + 'size' => 5, + 'required' => true, + 'signed' => true, + 'array' => false, + 'filters' => [], + ], + ], + 'indexes' => [], + ]; + + public function setUp(): void + { + } + + public function tearDown(): void + { + } + + public function testDocumentInstance() + { + $validator = new Structure(new Document($this->collection)); + + $this->assertEquals(false, $validator->isValid('string')); + $this->assertEquals(false, $validator->isValid(null)); + $this->assertEquals(false, $validator->isValid(false)); + $this->assertEquals(false, $validator->isValid(1)); + + $this->assertEquals('Invalid document structure: Value must be an instance of Document', $validator->getDescription()); + } + + public function testCollectionAttribute() + { + $validator = new Structure(new Document()); + + $this->assertEquals(false, $validator->isValid(new Document())); + + $this->assertEquals('Invalid document structure: Missing collection attribute $collection', $validator->getDescription()); + } + + public function testCollection() + { + $validator = new Structure(new Document()); + + $this->assertEquals(false, $validator->isValid(new Document([ + '$collection' => 'posts', + 'title' => 'Demo Title', + 'description' => 'Demo description', + 'rating' => 5, + 'published' => true, + ]))); + + $this->assertEquals('Invalid document structure: Collection "" not found', $validator->getDescription()); + } + + public function testReuiredKeys() + { + $validator = new Structure(new Document($this->collection)); + + $this->assertEquals(false, $validator->isValid(new Document([ + '$collection' => 'posts', + 'description' => 'Demo description', + 'rating' => 5, + 'published' => true, + ]))); + + $this->assertEquals('Invalid document structure: Missing required key "title"', $validator->getDescription()); + } + + public function testUnknownKeys() + { + $validator = new Structure(new Document($this->collection)); + + $this->assertEquals(false, $validator->isValid(new Document([ + '$collection' => 'posts', + 'title' => 'Demo Title', + 'titlex' => 'Unknown Attribute', + 'description' => 'Demo description', + 'rating' => 5, + 'published' => true, + ]))); + + $this->assertEquals('Invalid document structure: Unknown property: ""titlex"', $validator->getDescription()); + + $this->assertEquals(true, $validator->isValid(new Document([ + '$collection' => 'posts', + 'title' => 'Demo Title', + 'description' => 'Demo description', + 'rating' => 5, + 'published' => true, + ]))); + } +} From f4445cea12647edcaadc8fde731888905623471f Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 25 Apr 2021 02:41:30 +0300 Subject: [PATCH 084/190] Structure and filters - work in progress. --- src/Database/Database.php | 194 +++++++++++++++++++++++++++----------- tests/Database/Base.php | 32 +++---- 2 files changed, 155 insertions(+), 71 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 1864a3af1..f9b176b9c 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -10,8 +10,6 @@ class Database { - const METADATA = 'metadata'; - // Simple Types const VAR_STRING = 'string'; const VAR_INTEGER = 'integer'; @@ -58,6 +56,7 @@ class Database '$id' => 'name', 'type' => self::VAR_STRING, 'size' => 256, + 'required' => true, 'signed' => true, 'array' => false, 'filters' => [], @@ -66,17 +65,19 @@ class Database '$id' => 'attributes', 'type' => self::VAR_STRING, 'size' => 1000000, + 'required' => false, 'signed' => true, 'array' => true, - 'filters' => [], + 'filters' => ['json'], ], [ '$id' => 'indexes', 'type' => self::VAR_STRING, 'size' => 1000000, + 'required' => false, 'signed' => true, 'array' => true, - 'filters' => [], + 'filters' => ['json'], ], ], 'indexes' => [], @@ -85,7 +86,7 @@ class Database /** * @var array */ - static public $filters = []; + static protected $filters = []; /** * @param Adapter $adapter @@ -93,6 +94,25 @@ class Database public function __construct(Adapter $adapter) { $this->adapter = $adapter; + + self::addFilter('json', + function($value) { + $value = ($value instanceof Document) ? $value->getArrayCopy() : $value; + + if(!is_array($value)) { + throw new Exception('Can\'t encode to JSON'); + } + + return json_encode($value); + }, + function($value) { + if(!is_string($value)) { + throw new Exception('Can\'t decode from JSON'); + } + + return json_decode($value, true); + } + ); } /** @@ -137,9 +157,9 @@ public function create(): bool $this->adapter->create(); $this->createCollection(self::COLLECTIONS); - $this->createAttribute(self::COLLECTIONS, 'name', self::VAR_STRING, 128); - $this->createAttribute(self::COLLECTIONS, 'attributes', self::VAR_STRING, 8064); - $this->createAttribute(self::COLLECTIONS, 'indexes', self::VAR_STRING, 8064); + $this->createAttribute(self::COLLECTIONS, 'name', self::VAR_STRING, 128, true); + $this->createAttribute(self::COLLECTIONS, 'attributes', self::VAR_STRING, 8064, false); + $this->createAttribute(self::COLLECTIONS, 'indexes', self::VAR_STRING, 8064, false); $this->createIndex(self::COLLECTIONS, '_key_1', self::INDEX_UNIQUE, ['name']); return true; @@ -234,12 +254,15 @@ public function deleteCollection(string $id): bool * @param string $collection * @param string $id * @param string $type - * @param int $size + * @param int $size utf8mb4 chars length + * @param bool $required + * @param bool $signed * @param bool $array + * @param array $filters * * @return bool */ - public function createAttribute(string $collection, string $id, string $type, int $size, bool $signed = true, bool $array = false, array $filters = []): bool + public function createAttribute(string $collection, string $id, string $type, int $size, bool $required, bool $signed = true, bool $array = false, array $filters = []): bool { $collection = $this->getCollection($collection); @@ -247,6 +270,7 @@ public function createAttribute(string $collection, string $id, string $type, in '$id' => $id, 'type' => $type, 'size' => $size, + 'required' => $required, 'signed' => $signed, 'array' => $array, 'filters' => $filters, @@ -416,7 +440,6 @@ public function getDocument(string $collection, string $id): Document } $collection = $this->getCollection($collection); - $document = $this->adapter->getDocument($collection->getId(), $id); $document->setAttribute('$collection', $collection->getId()); @@ -431,6 +454,7 @@ public function getDocument(string $collection, string $id): Document return $document; } + $document = $this->casting($collection, $document); $document = $this->decode($collection, $document); return $document; @@ -455,20 +479,24 @@ public function createDocument(string $collection, Document $document): Document throw new AuthorizationException($validator->getDescription()); } - // $document = $this->encode($document); - // $validator = new Structure($this); + $collection = $this->getCollection($collection); + + $document + ->setAttribute('$id', empty($document->getId()) ? $this->getId(): $document->getId()) + ->setAttribute('$collection', $collection->getId()) + ; - // if (!$validator->isValid($document)) { - // throw new StructureException($validator->getDescription()); // var_dump($validator->getDescription()); return false; - // } + $document = $this->encode($collection, $document); - $document->setAttribute('$id', empty($document->getId()) ? $this->getId(): $document->getId()); - - $document = $this->adapter->createDocument($collection, $document); + $validator = new Structure($collection); + + if (!$validator->isValid($document)) { + throw new StructureException($validator->getDescription()); + } - $document->setAttribute('$collection', $collection); + $document = $this->adapter->createDocument($collection->getId(), $document); - // $document = $this->decode($document); + $document = $this->decode($collection, $document); return $document; } @@ -491,6 +519,7 @@ public function updateDocument(string $collection, string $id, Document $documen } $old = $this->getDocument($collection, $id); // TODO make sure user don\'t need read permission for write operations + $collection = $this->getCollection($collection); // Make sure reserved keys stay constant // $data['$id'] = $old->getId(); @@ -499,24 +528,24 @@ public function updateDocument(string $collection, string $id, Document $documen $validator = new Authorization($old, 'write'); if (!$validator->isValid($old->getWrite())) { // Check if user has write access to this document - throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false; + throw new AuthorizationException($validator->getDescription()); } if (!$validator->isValid($document->getWrite())) { // Check if user has write access to this document - throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false; + throw new AuthorizationException($validator->getDescription()); } - // $document = $this->encode($document); + $document = $this->encode($collection, $document); - // $validator = new Structure($this); + $validator = new Structure($collection); - // if (!$validator->isValid($document)) { // Make sure updated structure still apply collection rules (if any) - // throw new StructureException($validator->getDescription()); // var_dump($validator->getDescription()); return false; - // } + if (!$validator->isValid($document)) { // Make sure updated structure still apply collection rules (if any) + throw new StructureException($validator->getDescription()); + } - $document = $this->adapter->updateDocument($collection, $document); + $document = $this->adapter->updateDocument($collection->getId(), $document); - // $new = $this->decode($new); + $document = $this->decode($collection, $document); return $document; } @@ -628,13 +657,13 @@ public function deleteDocument(string $collection, string $id): bool // $validator = new Authorization($document, 'write'); // if (!$validator->isValid($document->getWrite())) { // Check if user has write access to this document - // throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false; + // throw new AuthorizationException($validator->getDescription()); // } // $new = new Document($data); // if (!$validator->isValid($new->getWrite())) { // Check if user has write access to this document - // throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false; + // throw new AuthorizationException($validator->getDescription()); // } // $new = $this->encode($new); @@ -642,7 +671,7 @@ public function deleteDocument(string $collection, string $id): bool // $validator = new Structure($this); // if (!$validator->isValid($new)) { // Make sure updated structure still apply collection rules (if any) - // throw new StructureException($validator->getDescription()); // var_dump($validator->getDescription()); return false; + // throw new StructureException($validator->getDescription()); // } // $new = new Document($this->adapter->updateDocument($this->getCollection($new->getCollection()), $new->getId(), $new->getArrayCopy())); @@ -669,37 +698,94 @@ static public function addFilter(string $name, callable $encode, callable $decod ]; } + /** + * Encode Document + * + * @param Document $collection + * @param Document $document + * + * @return Document + */ public function encode(Document $collection, Document $document):Document { - $rules = $collection->getAttribute('rules', []); + $attributes = $collection->getAttribute('attributes', []); - foreach ($rules as $key => $rule) { - $key = $rule->getAttribute('key', null); - $type = $rule->getAttribute('type', null); - $array = $rule->getAttribute('array', false); - $filters = $rule->getAttribute('filter', []); + foreach ($attributes as $attribute) { + $key = $attribute['$id'] ?? ''; + $array = $attribute['array'] ?? false; + $filters = $attribute['filters'] ?? []; $value = $document->getAttribute($key, null); - if (($value !== null)) { - foreach ($filters as $filter) { - $value = $this->encodeAttribute($filter, $value); - $document->setAttribute($key, $value); + $value = ($array) ? $value : [$value]; + + foreach ($value as &$node) { + if (($node !== null)) { + foreach ($filters as $filter) { + $node = $this->encodeAttribute($filter, $node); + } } } + + if(!$array) { + $value = $value[0]; + } + + $document->setAttribute($key, $value); } return $document; } + /** + * Decode Document + * + * @param Document $collection + * @param Document $document + * + * @return Document + */ public function decode(Document $collection, Document $document):Document { - $rules = $collection->getAttribute('attributes', []); + $attributes = $collection->getAttribute('attributes', []); + + foreach ($attributes as $attribute) { + $key = $attribute['$id'] ?? ''; + $array = $attribute['array'] ?? false; + $filters = $attribute['filters'] ?? []; + $value = $document->getAttribute($key, null); + + $value = ($array) ? $value : [$value]; + + foreach ($value as &$node) { + if (($node !== null)) { + foreach ($filters as $filter) { + $node = $this->decodeAttribute($filter, $node); + } + } + } + + $document->setAttribute($key, ($array) ? $value : $value[0]); + } + + return $document; + } + + /** + * Casting + * + * @param Document $collection + * @param Document $document + * + * @return Document + */ + public function casting(Document $collection, Document $document):Document + { + $attributes = $collection->getAttribute('attributes', []); - foreach ($rules as $rule) { - $key = $rule['$id'] ?? ''; - $type = $rule['type'] ?? ''; - $array = $rule['array'] ?? false; - $filters = $rule['filters'] ?? []; + foreach ($attributes as $attribute) { + $key = $attribute['$id'] ?? ''; + $type = $attribute['type'] ?? ''; + $array = $attribute['array'] ?? false; $value = $document->getAttribute($key, null); if($array) { @@ -741,17 +827,16 @@ public function decode(Document $collection, Document $document):Document * * @return mixed */ - static protected function encodeAttribute(string $name, $value) + protected function encodeAttribute(string $name, $value) { if (!isset(self::$filters[$name])) { - return $value; throw new Exception('Filter not found'); } try { $value = self::$filters[$name]['encode']($value); } catch (\Throwable $th) { - $value = null; + throw $th; } return $value; @@ -765,17 +850,16 @@ static protected function encodeAttribute(string $name, $value) * * @return mixed */ - static protected function decodeAttribute(string $name, $value) + protected function decodeAttribute(string $name, $value) { if (!isset(self::$filters[$name])) { - return $value; throw new Exception('Filter not found'); } try { $value = self::$filters[$name]['decode']($value); } catch (\Throwable $th) { - $value = null; + throw $th; } return $value; diff --git a/tests/Database/Base.php b/tests/Database/Base.php index b5a93ad35..eec66da14 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -48,13 +48,13 @@ public function testCreateDeleteAttribute() { static::getDatabase()->createCollection('attributes'); - $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'string1', Database::VAR_STRING, 128)); - $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'string2', Database::VAR_STRING, 16383+1)); - $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'string3', Database::VAR_STRING, 65535+1)); - $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'string4', Database::VAR_STRING, 16777215+1)); - $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'integer', Database::VAR_INTEGER, 0)); - $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'float', Database::VAR_FLOAT, 0)); - $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'boolean', Database::VAR_BOOLEAN, 0)); + $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'string1', Database::VAR_STRING, 128, true)); + $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'string2', Database::VAR_STRING, 16383+1, true)); + $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'string3', Database::VAR_STRING, 65535+1, true)); + $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'string4', Database::VAR_STRING, 16777215+1, true)); + $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'integer', Database::VAR_INTEGER, 0, true)); + $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'float', Database::VAR_FLOAT, 0, true)); + $this->assertEquals(true, static::getDatabase()->createAttribute('attributes', 'boolean', Database::VAR_BOOLEAN, 0, true)); $collection = static::getDatabase()->getCollection('attributes'); $this->assertCount(7, $collection->getAttribute('attributes')); @@ -96,10 +96,10 @@ public function testCreateDeleteIndex() { static::getDatabase()->createCollection('indexes'); - $this->assertEquals(true, static::getDatabase()->createAttribute('indexes', 'string', Database::VAR_STRING, 128)); - $this->assertEquals(true, static::getDatabase()->createAttribute('indexes', 'integer', Database::VAR_INTEGER, 0)); - $this->assertEquals(true, static::getDatabase()->createAttribute('indexes', 'float', Database::VAR_FLOAT, 0)); - $this->assertEquals(true, static::getDatabase()->createAttribute('indexes', 'boolean', Database::VAR_BOOLEAN, 0)); + $this->assertEquals(true, static::getDatabase()->createAttribute('indexes', 'string', Database::VAR_STRING, 128, true)); + $this->assertEquals(true, static::getDatabase()->createAttribute('indexes', 'integer', Database::VAR_INTEGER, 0, true)); + $this->assertEquals(true, static::getDatabase()->createAttribute('indexes', 'float', Database::VAR_FLOAT, 0, true)); + $this->assertEquals(true, static::getDatabase()->createAttribute('indexes', 'boolean', Database::VAR_BOOLEAN, 0, true)); // Indexes $this->assertEquals(true, static::getDatabase()->createIndex('indexes', 'index1', Database::INDEX_KEY, ['string', 'integer'], [128], [Database::ORDER_ASC])); @@ -124,11 +124,11 @@ public function testCreateDocument() { static::getDatabase()->createCollection('documents'); - $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'string', Database::VAR_STRING, 128)); - $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'integer', Database::VAR_INTEGER, 0)); - $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'float', Database::VAR_FLOAT, 0)); - $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'boolean', Database::VAR_BOOLEAN, 0)); - $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'colors', Database::VAR_STRING, 32, true, true)); + $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'string', Database::VAR_STRING, 128, true)); + $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'integer', Database::VAR_INTEGER, 0, true)); + $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'float', Database::VAR_FLOAT, 0, true)); + $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'boolean', Database::VAR_BOOLEAN, 0, true)); + $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'colors', Database::VAR_STRING, 32, true, true, true)); $document = static::getDatabase()->createDocument('documents', new Document([ '$read' => ['*', 'user1', 'user2'], From 8c771d664129b96ca20a17e27ff1f240581e2bd6 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 25 Apr 2021 02:41:55 +0300 Subject: [PATCH 085/190] Use query not filter --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0630aaccb..2b3a91064 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ Below is a list of supported adapters, and thier compatibly tested versions alon - [ ] Add cache layer (Delete / Update documents before cleaning cache) - [ ] Implement find method - [ ] Test for find timeout limits -- [ ] Merge new filter syntax parser +- [ ] Merge new query syntax parser ## System Requirements From ece4105b34137ffdaaa50c38eac4daed216fcb66 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 25 Apr 2021 19:04:01 +0300 Subject: [PATCH 086/190] Added missing tests --- src/Database/Validator/Structure.php | 2 +- tests/Database/Validator/StructureTest.php | 179 ++++++++++++++++++++- 2 files changed, 178 insertions(+), 3 deletions(-) diff --git a/src/Database/Validator/Structure.php b/src/Database/Validator/Structure.php index bdf022545..c749d008e 100644 --- a/src/Database/Validator/Structure.php +++ b/src/Database/Validator/Structure.php @@ -210,7 +210,7 @@ public function isValid($document) foreach ($value as $x => $child) { if(!$validator->isValid($child)) { - $this->message = 'Attribute "'.$key.'"["'.$x.'"] has invalid type. '.$validator->getDescription(); + $this->message = 'Attribute "'.$key.'[\''.$x.'\']" has invalid type. '.$validator->getDescription(); return false; } } diff --git a/tests/Database/Validator/StructureTest.php b/tests/Database/Validator/StructureTest.php index 4b8420ab9..da6d36511 100644 --- a/tests/Database/Validator/StructureTest.php +++ b/tests/Database/Validator/StructureTest.php @@ -44,6 +44,15 @@ class StructureTest extends TestCase 'array' => false, 'filters' => [], ], + [ + '$id' => 'price', + 'type' => Database::VAR_FLOAT, + 'size' => 5, + 'required' => true, + 'signed' => true, + 'array' => false, + 'filters' => [], + ], [ '$id' => 'published', 'type' => Database::VAR_BOOLEAN, @@ -53,6 +62,15 @@ class StructureTest extends TestCase 'array' => false, 'filters' => [], ], + [ + '$id' => 'tags', + 'type' => Database::VAR_STRING, + 'size' => 55, + 'required' => true, + 'signed' => true, + 'array' => true, + 'filters' => [], + ], ], 'indexes' => [], ]; @@ -95,7 +113,9 @@ public function testCollection() 'title' => 'Demo Title', 'description' => 'Demo description', 'rating' => 5, + 'price' => 1.99, 'published' => true, + 'tags' => ['dog', 'cat', 'mouse'], ]))); $this->assertEquals('Invalid document structure: Collection "" not found', $validator->getDescription()); @@ -109,10 +129,12 @@ public function testReuiredKeys() '$collection' => 'posts', 'description' => 'Demo description', 'rating' => 5, + 'price' => 1.99, 'published' => true, + 'tags' => ['dog', 'cat', 'mouse'], ]))); - $this->assertEquals('Invalid document structure: Missing required key "title"', $validator->getDescription()); + $this->assertEquals('Invalid document structure: Missing required attribute "title"', $validator->getDescription()); } public function testUnknownKeys() @@ -125,17 +147,170 @@ public function testUnknownKeys() 'titlex' => 'Unknown Attribute', 'description' => 'Demo description', 'rating' => 5, + 'price' => 1.99, 'published' => true, + 'tags' => ['dog', 'cat', 'mouse'], ]))); - $this->assertEquals('Invalid document structure: Unknown property: ""titlex"', $validator->getDescription()); + $this->assertEquals('Invalid document structure: Unknown attribute: ""titlex"', $validator->getDescription()); + } + + public function testValidDocument() + { + $validator = new Structure(new Document($this->collection)); $this->assertEquals(true, $validator->isValid(new Document([ '$collection' => 'posts', 'title' => 'Demo Title', 'description' => 'Demo description', 'rating' => 5, + 'price' => 1.99, 'published' => true, + 'tags' => ['dog', 'cat', 'mouse'], ]))); + + } + + public function testStringValidation() + { + $validator = new Structure(new Document($this->collection)); + + $this->assertEquals(false, $validator->isValid(new Document([ + '$collection' => 'posts', + 'title' => 5, + 'description' => 'Demo description', + 'rating' => 5, + 'price' => 1.99, + 'published' => true, + 'tags' => ['dog', 'cat', 'mouse'], + ]))); + + $this->assertEquals('Invalid document structure: Attribute "title" has invalid type. Value must be a string', $validator->getDescription()); + } + + public function testArrayOfStringsValidation() + { + $validator = new Structure(new Document($this->collection)); + + $this->assertEquals(false, $validator->isValid(new Document([ + '$collection' => 'posts', + 'title' => 'string', + 'description' => 'Demo description', + 'rating' => 5, + 'price' => 1.99, + 'published' => true, + 'tags' => [1, 'cat', 'mouse'], + ]))); + + $this->assertEquals('Invalid document structure: Attribute "tags[\'0\']" has invalid type. Value must be a string', $validator->getDescription()); + + $this->assertEquals(false, $validator->isValid(new Document([ + '$collection' => 'posts', + 'title' => 'string', + 'description' => 'Demo description', + 'rating' => 5, + 'price' => 1.99, + 'published' => true, + 'tags' => [true], + ]))); + + $this->assertEquals('Invalid document structure: Attribute "tags[\'0\']" has invalid type. Value must be a string', $validator->getDescription()); + + $this->assertEquals(true, $validator->isValid(new Document([ + '$collection' => 'posts', + 'title' => 'string', + 'description' => 'Demo description', + 'rating' => 5, + 'price' => 1.99, + 'published' => true, + 'tags' => [], + ]))); + } + + public function testIntegerValidation() + { + $validator = new Structure(new Document($this->collection)); + + $this->assertEquals(false, $validator->isValid(new Document([ + '$collection' => 'posts', + 'title' => 'string', + 'description' => 'Demo description', + 'rating' => true, + 'price' => 1.99, + 'published' => false, + 'tags' => ['dog', 'cat', 'mouse'], + ]))); + + $this->assertEquals('Invalid document structure: Attribute "rating" has invalid type. Value must be a valid integer', $validator->getDescription()); + + $this->assertEquals(false, $validator->isValid(new Document([ + '$collection' => 'posts', + 'title' => 'string', + 'description' => 'Demo description', + 'rating' => '', + 'price' => 1.99, + 'published' => false, + 'tags' => ['dog', 'cat', 'mouse'], + ]))); + + $this->assertEquals('Invalid document structure: Attribute "rating" has invalid type. Value must be a valid integer', $validator->getDescription()); + } + + public function testFloatValidation() + { + $validator = new Structure(new Document($this->collection)); + + $this->assertEquals(false, $validator->isValid(new Document([ + '$collection' => 'posts', + 'title' => 'string', + 'description' => 'Demo description', + 'rating' => 5, + 'price' => 2, + 'published' => false, + 'tags' => ['dog', 'cat', 'mouse'], + ]))); + + $this->assertEquals('Invalid document structure: Attribute "price" has invalid type. Value must be a valid float', $validator->getDescription()); + + $this->assertEquals(false, $validator->isValid(new Document([ + '$collection' => 'posts', + 'title' => 'string', + 'description' => 'Demo description', + 'rating' => 5, + 'price' => '', + 'published' => false, + 'tags' => ['dog', 'cat', 'mouse'], + ]))); + + $this->assertEquals('Invalid document structure: Attribute "price" has invalid type. Value must be a valid float', $validator->getDescription()); + } + + public function testBooleanValidation() + { + $validator = new Structure(new Document($this->collection)); + + $this->assertEquals(false, $validator->isValid(new Document([ + '$collection' => 'posts', + 'title' => 'string', + 'description' => 'Demo description', + 'rating' => 5, + 'price' => 1.99, + 'published' => 1, + 'tags' => ['dog', 'cat', 'mouse'], + ]))); + + $this->assertEquals('Invalid document structure: Attribute "published" has invalid type. Value must be a boolean', $validator->getDescription()); + + $this->assertEquals(false, $validator->isValid(new Document([ + '$collection' => 'posts', + 'title' => 'string', + 'description' => 'Demo description', + 'rating' => 5, + 'price' => 1.99, + 'published' => '', + 'tags' => ['dog', 'cat', 'mouse'], + ]))); + + $this->assertEquals('Invalid document structure: Attribute "published" has invalid type. Value must be a boolean', $validator->getDescription()); } } From 6b3bcd2cdf1918f16ecdfea788946c75986da079 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 26 Apr 2021 11:41:47 -0400 Subject: [PATCH 087/190] Typecast expression values and handle comma-separated values --- src/Database/Query.php | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/Database/Query.php b/src/Database/Query.php index 3b9403de3..11605af19 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -134,16 +134,32 @@ protected static function parseExpression(string $expression): array ($end - $start - 1) /* exclude closed paren*/ ); - //strip quotes from queries of type string - $value = str_replace('"', "", $value); - $value = str_replace("'", "", $value); - - // if $value is not array, return array with single $value - // TODO@kodumbeats appropriately cast type of ints, floats, bools - if (gettype($value) !== 'array') { - $value = [$value]; - } + // Explode comma-separated values and trim whitespace + + $values = array_map('trim', explode(',', $value)); + + // strip quotes from queries of type string + + $values = array_map(function ($str) { + return str_replace(['"',"'"], "", $str); + }, $values); + + // type cast ints, floats, and bools + + $values = array_map(function ($value) { + // type casted to int or float by "+" operator + if (is_numeric($value)) { + return $value + 0; + // since (bool)"false" returns true, check bools manually + } elseif ($value === 'true') { + return true; + } elseif ($value === 'false') { + return false; + } else { + return $value; + } + }, $values); - return [$operator, $value]; + return [$operator, $values]; } } From a1b472d39271c1175af307d7ada6d607d289d9ed Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 26 Apr 2021 11:42:52 -0400 Subject: [PATCH 088/190] Test with integer param --- tests/Database/QueryTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Database/QueryTest.php b/tests/Database/QueryTest.php index 1b97e4847..02757a84b 100644 --- a/tests/Database/QueryTest.php +++ b/tests/Database/QueryTest.php @@ -27,7 +27,7 @@ public function testParse() $query = Query::parse('year.lesser(2001)'); $this->assertEquals('year', $query->getAttribute()); $this->assertEquals('lesser', $query->getOperator()); - $this->assertContains('2001', $query->getValues()); + $this->assertContains(2001, $query->getValues()); } public function testGetAttribute() From e74da85bf7fb0776af289f004a2c6e125849f0c2 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 26 Apr 2021 11:45:35 -0400 Subject: [PATCH 089/190] Test for typecasted bool expressions --- tests/Database/QueryTest.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/Database/QueryTest.php b/tests/Database/QueryTest.php index 02757a84b..81beb1ea8 100644 --- a/tests/Database/QueryTest.php +++ b/tests/Database/QueryTest.php @@ -28,6 +28,16 @@ public function testParse() $this->assertEquals('year', $query->getAttribute()); $this->assertEquals('lesser', $query->getOperator()); $this->assertContains(2001, $query->getValues()); + + $query = Query::parse('published.equal(true)'); + $this->assertEquals('published', $query->getAttribute()); + $this->assertEquals('equal', $query->getOperator()); + $this->assertContains(true, $query->getValues()); + + $query = Query::parse('published.equal(false)'); + $this->assertEquals('published', $query->getAttribute()); + $this->assertEquals('equal', $query->getOperator()); + $this->assertContains(false, $query->getValues()); } public function testGetAttribute() From f7e86b6b6f3a0d6a070e663fa85fdc661a122d72 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 26 Apr 2021 11:46:50 -0400 Subject: [PATCH 090/190] Test multiple query values --- tests/Database/QueryTest.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/Database/QueryTest.php b/tests/Database/QueryTest.php index 81beb1ea8..010767b88 100644 --- a/tests/Database/QueryTest.php +++ b/tests/Database/QueryTest.php @@ -38,6 +38,13 @@ public function testParse() $this->assertEquals('published', $query->getAttribute()); $this->assertEquals('equal', $query->getOperator()); $this->assertContains(false, $query->getValues()); + + $query = Query::parse('actors.equal("Brad Pitt", "Johnny Depp")'); + + $this->assertEquals('actors', $query->getAttribute()); + $this->assertEquals('equal', $query->getOperator()); + $this->assertContains('Brad Pitt', $query->getValues()); + $this->assertContains('Johnny Depp', $query->getValues()); } public function testGetAttribute() From a14a73046080fbe9d96fe290fa90667648b437fb Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 26 Apr 2021 11:47:20 -0400 Subject: [PATCH 091/190] Cleanup for readability --- tests/Database/QueryTest.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/Database/QueryTest.php b/tests/Database/QueryTest.php index 010767b88..24c6b903c 100644 --- a/tests/Database/QueryTest.php +++ b/tests/Database/QueryTest.php @@ -25,16 +25,19 @@ public function testParse() $this->assertContains('Iron Man', $query->getValues()); $query = Query::parse('year.lesser(2001)'); + $this->assertEquals('year', $query->getAttribute()); $this->assertEquals('lesser', $query->getOperator()); $this->assertContains(2001, $query->getValues()); $query = Query::parse('published.equal(true)'); + $this->assertEquals('published', $query->getAttribute()); $this->assertEquals('equal', $query->getOperator()); $this->assertContains(true, $query->getValues()); $query = Query::parse('published.equal(false)'); + $this->assertEquals('published', $query->getAttribute()); $this->assertEquals('equal', $query->getOperator()); $this->assertContains(false, $query->getValues()); @@ -70,8 +73,7 @@ public function testGetValue() public function testGetQuery() { - $parsed = Query::parse('title.equal("Iron Man")'); - $query = $parsed->getQuery(); + $query = Query::parse('title.equal("Iron Man")')->getQuery(); $this->assertEquals('title', $query['attribute']); $this->assertEquals('equal', $query['operator']); From 631ecfd0a0447ae148185300100a644b9ce327ee Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 26 Apr 2021 11:51:36 -0400 Subject: [PATCH 092/190] Reserve query validator for separate PR --- src/Database/Validator/Query.php | 88 -------------------------------- 1 file changed, 88 deletions(-) delete mode 100644 src/Database/Validator/Query.php diff --git a/src/Database/Validator/Query.php b/src/Database/Validator/Query.php deleted file mode 100644 index 6bc38b909..000000000 --- a/src/Database/Validator/Query.php +++ /dev/null @@ -1,88 +0,0 @@ -schema = $schema; - } - - /** - * Get Description. - * - * Returns validator description - * - * @return string - */ - public function getDescription() - { - return $this->message; - } - - /** - * Is valid. - * - * Returns true if query typed according to schema. - * - * @param Query $query - * - * @return bool - */ - public function isValid(Query $query) - { - // Extract the type of desired attribute from collection $schema - $attributeType = $this->schema[array_search($query->getAttribute(), array_column($this->schema, '$id'))]['type']; - - if ($attributeType === gettype($query->getValue())) { - return true; - } - - return false; - - } - /** - * Is array - * - * Function will return true if object is array. - * - * @return bool - */ - public function isArray(): bool - { - return true; - } - - /** - * Get Type - * - * Returns validator type. - * - * @return string - */ - public function getType(): string - { - return self::TYPE_OBJECT; - } -} From 4d6189be45acf4a356b9054b1ac404c2bdc8f85c Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 26 Apr 2021 13:53:50 -0400 Subject: [PATCH 093/190] Only consider periods oudside parentheses --- src/Database/Query.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Database/Query.php b/src/Database/Query.php index 11605af19..260c34b0d 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -88,13 +88,16 @@ public function getQuery(): array * */ public static function parse(string $filter): Query { - // TODO@kodumbeats handle '.' in expression value - $stanzas = mb_substr_count($filter, ".") + 1; + // get index of open parentheses + $end = mb_strpos($filter, '('); + // count stanzas by only counting '.' that come before open parentheses + $stanzas = mb_substr_count(mb_substr($filter, 0, $end), ".") + 1; // TODO@kodumbeats handle relations between collections, e.g. if($stanzas > 2) switch ($stanzas) { case 2: - $input = explode('.', $filter); + // use limit param to ignore '.' in $expression + $input = explode('.', $filter, $stanzas); $attribute = $input[0]; $expression = $input[1]; [$operator, $values] = self::parseExpression($expression); From 1f4c64f322ce98f7bc27ad492adf5f71d40afb3e Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 26 Apr 2021 14:53:26 -0400 Subject: [PATCH 094/190] Test for float values --- tests/Database/QueryTest.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/Database/QueryTest.php b/tests/Database/QueryTest.php index 24c6b903c..28f47d25a 100644 --- a/tests/Database/QueryTest.php +++ b/tests/Database/QueryTest.php @@ -48,6 +48,12 @@ public function testParse() $this->assertEquals('equal', $query->getOperator()); $this->assertContains('Brad Pitt', $query->getValues()); $this->assertContains('Johnny Depp', $query->getValues()); + + $query = Query::parse('score.greater(8.5)'); + + $this->assertEquals('score', $query->getAttribute()); + $this->assertEquals('greater', $query->getOperator()); + $this->assertContains(8.5, $query->getValues()); } public function testGetAttribute() From fa2828610c2313fda94cc92c1a5a2bf459fd733a Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 26 Apr 2021 14:53:54 -0400 Subject: [PATCH 095/190] Handle null as value --- src/Database/Query.php | 20 ++++++++++---------- tests/Database/QueryTest.php | 12 ++++++++++++ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/Database/Query.php b/src/Database/Query.php index 260c34b0d..12d3faecc 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -141,25 +141,25 @@ protected static function parseExpression(string $expression): array $values = array_map('trim', explode(',', $value)); - // strip quotes from queries of type string - - $values = array_map(function ($str) { - return str_replace(['"',"'"], "", $str); - }, $values); - - // type cast ints, floats, and bools + // type cast ints, floats, bools, and null $values = array_map(function ($value) { - // type casted to int or float by "+" operator if (is_numeric($value)) { + // type casted to int or float by "+" operator return $value + 0; - // since (bool)"false" returns true, check bools manually } elseif ($value === 'true') { + // since (bool)"false" returns true, check bools manually return true; } elseif ($value === 'false') { return false; + } elseif ($value === '"null"') { + // need special case to handle null with or without quotes + return 'null'; + } elseif ($value === 'null') { + return null; } else { - return $value; + // strip quotes from queries of type string + return str_replace(['"',"'"], "", $value); } }, $values); diff --git a/tests/Database/QueryTest.php b/tests/Database/QueryTest.php index 28f47d25a..c258dc356 100644 --- a/tests/Database/QueryTest.php +++ b/tests/Database/QueryTest.php @@ -54,6 +54,18 @@ public function testParse() $this->assertEquals('score', $query->getAttribute()); $this->assertEquals('greater', $query->getOperator()); $this->assertContains(8.5, $query->getValues()); + + $query = Query::parse('director.notEqual("null")'); + + $this->assertEquals('director', $query->getAttribute()); + $this->assertEquals('notEqual', $query->getOperator()); + $this->assertContains('null', $query->getValues()); + + $query = Query::parse('director.notEqual(null)'); + + $this->assertEquals('director', $query->getAttribute()); + $this->assertEquals('notEqual', $query->getOperator()); + $this->assertContains(null, $query->getValues()); } public function testGetAttribute() From 2ec2fc83b35c26b840b94b9f175a0525f17a6c64 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 26 Apr 2021 15:21:57 -0400 Subject: [PATCH 096/190] Refactor elseifs into switch --- src/Database/Query.php | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/Database/Query.php b/src/Database/Query.php index 12d3faecc..ecf092462 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -141,25 +141,31 @@ protected static function parseExpression(string $expression): array $values = array_map('trim', explode(',', $value)); - // type cast ints, floats, bools, and null + // Cast $value type $values = array_map(function ($value) { - if (is_numeric($value)) { + switch (true) { // type casted to int or float by "+" operator - return $value + 0; - } elseif ($value === 'true') { + case is_numeric($value): + return $value + 0; + // since (bool)"false" returns true, check bools manually - return true; - } elseif ($value === 'false') { - return false; - } elseif ($value === '"null"') { + case $value === 'true': + return true; + + case $value === 'false': + return false; + // need special case to handle null with or without quotes - return 'null'; - } elseif ($value === 'null') { - return null; - } else { + case $value === '"null"': + return 'null'; + + case $value === 'null': + return null; + // strip quotes from queries of type string - return str_replace(['"',"'"], "", $value); + default: + return str_replace(['"',"'"], "", $value); } }, $values); From ab2a6b7d55fe1c3deaafd8affa52064877890a8f Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 26 Apr 2021 15:25:12 -0400 Subject: [PATCH 097/190] Use single array_map to trim whitespace and typecast --- src/Database/Query.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Database/Query.php b/src/Database/Query.php index ecf092462..1964de374 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -137,13 +137,16 @@ protected static function parseExpression(string $expression): array ($end - $start - 1) /* exclude closed paren*/ ); - // Explode comma-separated values and trim whitespace + // Explode comma-separated values - $values = array_map('trim', explode(',', $value)); + $values = explode(',', $value); // Cast $value type $values = array_map(function ($value) { + // trim whitespace + $value = trim($value); + switch (true) { // type casted to int or float by "+" operator case is_numeric($value): @@ -156,10 +159,10 @@ protected static function parseExpression(string $expression): array case $value === 'false': return false; - // need special case to handle null with or without quotes case $value === '"null"': return 'null'; + // need special case to handle null with or without quotes case $value === 'null': return null; @@ -167,6 +170,7 @@ protected static function parseExpression(string $expression): array default: return str_replace(['"',"'"], "", $value); } + }, $values); return [$operator, $values]; From b01d9dddcf32be6cdc4211a89426d005165573bf Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 26 Apr 2021 15:29:46 -0400 Subject: [PATCH 098/190] Remove unneeded switch case --- src/Database/Query.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Database/Query.php b/src/Database/Query.php index 1964de374..c9eee8a32 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -159,9 +159,6 @@ protected static function parseExpression(string $expression): array case $value === 'false': return false; - case $value === '"null"': - return 'null'; - // need special case to handle null with or without quotes case $value === 'null': return null; From 7db5c8fc28ee5c16d06e508cee98c5d446cd56e6 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 26 Apr 2021 15:52:25 -0400 Subject: [PATCH 099/190] Trim whitespace and remove escape slashes --- src/Database/Query.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Database/Query.php b/src/Database/Query.php index c9eee8a32..3ee13974f 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -144,8 +144,6 @@ protected static function parseExpression(string $expression): array // Cast $value type $values = array_map(function ($value) { - // trim whitespace - $value = trim($value); switch (true) { // type casted to int or float by "+" operator @@ -163,9 +161,11 @@ protected static function parseExpression(string $expression): array case $value === 'null': return null; - // strip quotes from queries of type string default: - return str_replace(['"',"'"], "", $value); + // strip escape characters + $value = stripslashes($value); + // trim leading and tailing quotes and whitespace + return trim($value, '\'" '); } }, $values); From 40cd42c84f6340a3bf13f6761d3f28a0cfe4c6e3 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 26 Apr 2021 15:52:42 -0400 Subject: [PATCH 100/190] Test for whitespace and escaped character --- tests/Database/QueryTest.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/Database/QueryTest.php b/tests/Database/QueryTest.php index c258dc356..89230b56a 100644 --- a/tests/Database/QueryTest.php +++ b/tests/Database/QueryTest.php @@ -42,6 +42,12 @@ public function testParse() $this->assertEquals('equal', $query->getOperator()); $this->assertContains(false, $query->getValues()); + $query = Query::parse('actors.notContains(" Johnny Depp ")'); + + $this->assertEquals('actors', $query->getAttribute()); + $this->assertEquals('notContains', $query->getOperator()); + $this->assertContains('Johnny Depp', $query->getValues()); + $query = Query::parse('actors.equal("Brad Pitt", "Johnny Depp")'); $this->assertEquals('actors', $query->getAttribute()); @@ -49,6 +55,12 @@ public function testParse() $this->assertContains('Brad Pitt', $query->getValues()); $this->assertContains('Johnny Depp', $query->getValues()); + $query = Query::parse('writers.contains("Tim O\'Reilly")'); + + $this->assertEquals('writers', $query->getAttribute()); + $this->assertEquals('contains', $query->getOperator()); + $this->assertContains("Tim O'Reilly", $query->getValues()); + $query = Query::parse('score.greater(8.5)'); $this->assertEquals('score', $query->getAttribute()); From 53cf8dc9d29dbdac1c3c72bec5e074f44464f00e Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 26 Apr 2021 16:29:48 -0400 Subject: [PATCH 101/190] Use strrpos to find last occurrence of parenthesis --- src/Database/Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Database/Query.php b/src/Database/Query.php index 3ee13974f..e976e71ea 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -122,7 +122,7 @@ protected static function parseExpression(string $expression): array /** @var int */ $start = mb_strpos($expression, '('); /** @var int */ - $end = mb_strpos($expression, ')'); + $end = mb_strrpos($expression, ')'); //extract the query method From ba852c2263494037e545fb4b4abc61d8149d1c12 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 26 Apr 2021 16:30:02 -0400 Subject: [PATCH 102/190] Correct comments --- src/Database/Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Database/Query.php b/src/Database/Query.php index e976e71ea..e73f56ae6 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -157,7 +157,7 @@ protected static function parseExpression(string $expression): array case $value === 'false': return false; - // need special case to handle null with or without quotes + // need special case to cast (null) as null, not string case $value === 'null': return null; From 87dd5b30ba2f8fd38917ee82cd206ac3af4d7b99 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 26 Apr 2021 16:36:22 -0400 Subject: [PATCH 103/190] Test constructor function --- tests/Database/QueryTest.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/Database/QueryTest.php b/tests/Database/QueryTest.php index 89230b56a..402925341 100644 --- a/tests/Database/QueryTest.php +++ b/tests/Database/QueryTest.php @@ -16,6 +16,15 @@ public function tearDown(): void { } + public function testCreate(): void + { + $query = new Query('title', 'equal', ['Iron Man']); + + $this->assertEquals('title', $query->getAttribute()); + $this->assertEquals('equal', $query->getOperator()); + $this->assertContains('Iron Man', $query->getValues()); + } + public function testParse() { $query = Query::parse('title.equal("Iron Man")'); From 1522284cf0fcc6c73031ee25547030ed33c8c890 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 26 Apr 2021 17:39:59 -0400 Subject: [PATCH 104/190] Properly handle whitespace between values --- src/Database/Query.php | 8 ++++++-- tests/Database/QueryTest.php | 6 ++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Database/Query.php b/src/Database/Query.php index e73f56ae6..00a56e8f7 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -145,6 +145,10 @@ protected static function parseExpression(string $expression): array $values = array_map(function ($value) { + // Trim whitespace from around $value + + $value = trim($value); + switch (true) { // type casted to int or float by "+" operator case is_numeric($value): @@ -164,8 +168,8 @@ protected static function parseExpression(string $expression): array default: // strip escape characters $value = stripslashes($value); - // trim leading and tailing quotes and whitespace - return trim($value, '\'" '); + // trim leading and tailing quotes + return trim($value, '\'"'); } }, $values); diff --git a/tests/Database/QueryTest.php b/tests/Database/QueryTest.php index 402925341..f4811e92d 100644 --- a/tests/Database/QueryTest.php +++ b/tests/Database/QueryTest.php @@ -51,11 +51,13 @@ public function testParse() $this->assertEquals('equal', $query->getOperator()); $this->assertContains(false, $query->getValues()); - $query = Query::parse('actors.notContains(" Johnny Depp ")'); + $query = Query::parse('actors.notContains( " Johnny Depp ", " Brad Pitt" , "Al Pacino")'); $this->assertEquals('actors', $query->getAttribute()); $this->assertEquals('notContains', $query->getOperator()); - $this->assertContains('Johnny Depp', $query->getValues()); + $this->assertContains(' Johnny Depp ', $query->getValues()); + $this->assertContains(' Brad Pitt', $query->getValues()); + $this->assertContains('Al Pacino', $query->getValues()); $query = Query::parse('actors.equal("Brad Pitt", "Johnny Depp")'); From ae17f4d1b5d1949a907d0c89d011c717fe32fd1d Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 26 Apr 2021 17:57:10 -0400 Subject: [PATCH 105/190] Fix typo --- tests/Database/Adapter/MariaDBTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Database/Adapter/MariaDBTest.php b/tests/Database/Adapter/MariaDBTest.php index 4a01997c5..042c1ae57 100644 --- a/tests/Database/Adapter/MariaDBTest.php +++ b/tests/Database/Adapter/MariaDBTest.php @@ -15,7 +15,7 @@ class MariaDBTest extends Base static $database = null; /** - * @reture Adapter + * @return Adapter */ static function getDatabase(): Database { From a4e720adcece40b729648bde94283207a3d89606 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 26 Apr 2021 17:57:38 -0400 Subject: [PATCH 106/190] Upgrade utopia-php/cache to 0.4.0 --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 085230290..870f363d5 100755 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "php": ">=7.1", "ext-pdo": "*", "utopia-php/framework": "0.*.*", - "utopia-php/cache": "0.3.*", + "utopia-php/cache": "0.4.0", "mongodb/mongodb": "1.8.0" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 3327487b3..2bf15be73 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "dea2465791a58ea901b688ea9eae5b72", + "content-hash": "fa7d9c6d206e196d739c246ce67aac67", "packages": [ { "name": "composer/package-versions-deprecated", @@ -287,16 +287,16 @@ }, { "name": "utopia-php/cache", - "version": "0.3.0", + "version": "0.4.0", "source": { "type": "git", "url": "https://github.com/utopia-php/cache.git", - "reference": "e6b8b99d445c4bba5f51857e3d2a3c39a42d73a2" + "reference": "81c7806e13091a9d585ad9bba57fae625a2cf26c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cache/zipball/e6b8b99d445c4bba5f51857e3d2a3c39a42d73a2", - "reference": "e6b8b99d445c4bba5f51857e3d2a3c39a42d73a2", + "url": "https://api.github.com/repos/utopia-php/cache/zipball/81c7806e13091a9d585ad9bba57fae625a2cf26c", + "reference": "81c7806e13091a9d585ad9bba57fae625a2cf26c", "shasum": "" }, "require": { @@ -334,9 +334,9 @@ ], "support": { "issues": "https://github.com/utopia-php/cache/issues", - "source": "https://github.com/utopia-php/cache/tree/0.3.0" + "source": "https://github.com/utopia-php/cache/tree/0.4.0" }, - "time": "2021-04-20T21:37:21+00:00" + "time": "2021-04-22T14:28:14+00:00" }, { "name": "utopia-php/framework", From aab22016e1be23a4d1e15b471fe2acc23686746b Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 26 Apr 2021 18:06:39 -0400 Subject: [PATCH 107/190] Use redis cache in testing --- docker-compose.yml | 6 ++++++ tests/Database/Adapter/MariaDBTest.php | 9 ++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index fb1bdc1ed..71e36278c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -67,5 +67,11 @@ services: MYSQL_PASSWORD: password MYSQL_TCP_PORT: 3307 + redis: + image: redis:6.0-alpine + container_name: redis + networks: + - database + networks: database: \ No newline at end of file diff --git a/tests/Database/Adapter/MariaDBTest.php b/tests/Database/Adapter/MariaDBTest.php index 042c1ae57..85b7a791e 100644 --- a/tests/Database/Adapter/MariaDBTest.php +++ b/tests/Database/Adapter/MariaDBTest.php @@ -3,8 +3,11 @@ namespace Utopia\Tests\Adapter; use PDO; +use Redis; use Utopia\Database\Database; use Utopia\Database\Adapter\MariaDB; +use Utopia\Cache\Cache; +use Utopia\Cache\Adapter\Redis as RedisAdapter; use Utopia\Tests\Base; class MariaDBTest extends Base @@ -36,7 +39,11 @@ static function getDatabase(): Database PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, ]); - $database = new Database(new MariaDB($pdo)); + $redis = new Redis(); + $redis->connect('redis', 6379); + $cache = new Cache(new RedisAdapter($redis)); + + $database = new Database(new MariaDB($pdo), $cache); $database->setNamespace('myapp_'.uniqid()); return self::$database = $database; From 3eed65fbf86fef92ae492feea42149b2080d79a5 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 26 Apr 2021 18:08:03 -0400 Subject: [PATCH 108/190] Check cache first on getDocument --- src/Database/Database.php | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index f9b176b9c..ec855c7e4 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -7,6 +7,7 @@ use Utopia\Database\Validator\Structure; use Utopia\Database\Exception\Authorization as AuthorizationException; use Utopia\Database\Exception\Structure as StructureException; +use Utopia\Cache\Cache; class Database { @@ -36,11 +37,19 @@ class Database // Collections const COLLECTIONS = 'collections'; + // Cache + const TTL = 60 * 60 * 24; // 24 hours + /** * @var Adapter */ protected $adapter; + /** + * @var Cache + */ + protected $cache; + /** * Parent Collection * Defines the structure for both system and custom collections @@ -90,10 +99,12 @@ class Database /** * @param Adapter $adapter + * @param Cache $cache (optional) */ - public function __construct(Adapter $adapter) + public function __construct(Adapter $adapter, Cache $cache = null) { $this->adapter = $adapter; + $this->cache = $cache; self::addFilter('json', function($value) { @@ -440,7 +451,16 @@ public function getDocument(string $collection, string $id): Document } $collection = $this->getCollection($collection); - $document = $this->adapter->getDocument($collection->getId(), $id); + + if ($cache) { + $document = $this->cache->load($id, self::TTL); + } + + if (!$document) { + $document = $this->adapter->getDocument($collection->getId(), $id); + + $this->cache->save($id, $document); // save to cache after fetching from db + } $document->setAttribute('$collection', $collection->getId()); From 23d23f29d995c4796176814dd7d2a27ad6fc0264 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Tue, 27 Apr 2021 07:38:02 -0400 Subject: [PATCH 109/190] Update cache on document update or delete --- src/Database/Database.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index ec855c7e4..e9a819e35 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -452,7 +452,7 @@ public function getDocument(string $collection, string $id): Document $collection = $this->getCollection($collection); - if ($cache) { + if ($this->cache) { $document = $this->cache->load($id, self::TTL); } @@ -564,6 +564,11 @@ public function updateDocument(string $collection, string $id, Document $documen } $document = $this->adapter->updateDocument($collection->getId(), $document); + + if ($this->cache) { + $this->cache->purge($id); + $this->cache->save($id, $document); + } $document = $this->decode($collection, $document); @@ -588,6 +593,10 @@ public function deleteDocument(string $collection, string $id): bool throw new AuthorizationException($validator->getDescription()); } + if ($this->cache) { + $this->cache->purge($id); + } + return $this->adapter->deleteDocument($collection, $id); } From d70b393c28baf26473924d396bed60bf77b5c1d9 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Tue, 27 Apr 2021 08:26:25 -0400 Subject: [PATCH 110/190] Install redis lib in testing container --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index f3f281f43..44df6dba4 100755 --- a/Dockerfile +++ b/Dockerfile @@ -20,8 +20,8 @@ RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone RUN \ apk update \ && apk add --no-cache postgresql-libs postgresql-dev make automake autoconf gcc g++ \ - && pecl install mongodb \ - && docker-php-ext-enable mongodb \ + && pecl install mongodb redis \ + && docker-php-ext-enable mongodb redis \ && docker-php-ext-install opcache pgsql pdo_mysql pdo_pgsql \ && rm -rf /var/cache/apk/* From bf3dd6ccf387686f49128e4638e17d71bc3140e1 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Tue, 27 Apr 2021 08:33:45 -0400 Subject: [PATCH 111/190] Properly assign $document --- src/Database/Database.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Database/Database.php b/src/Database/Database.php index e9a819e35..acfc169ab 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -451,6 +451,7 @@ public function getDocument(string $collection, string $id): Document } $collection = $this->getCollection($collection); + $document = null; if ($this->cache) { $document = $this->cache->load($id, self::TTL); From 71119bfd8cec3fc89b7d0e746f69ca63e4ab0c3e Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Tue, 27 Apr 2021 10:10:21 -0400 Subject: [PATCH 112/190] Hydrate document string to Document object --- src/Database/Database.php | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index acfc169ab..91d4df287 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -452,17 +452,22 @@ public function getDocument(string $collection, string $id): Document $collection = $this->getCollection($collection); $document = null; + $cache = null; if ($this->cache) { - $document = $this->cache->load($id, self::TTL); + $cache = json_decode($this->cache->load($id, self::TTL), true); + $document = ($cache) ? new Document() : null; + + if (!$document) { + foreach ($cache as $key => $value) $document->{$key} = $value; + } } if (!$document) { - $document = $this->adapter->getDocument($collection->getId(), $id); - - $this->cache->save($id, $document); // save to cache after fetching from db + $document = $this->adapter->getDocument($collection->getId(), $id); } + // $document = $this->adapter->getDocument($collection->getId(), $id); $document->setAttribute('$collection', $collection->getId()); $validator = new Authorization($document, self::PERMISSION_READ); @@ -475,6 +480,10 @@ public function getDocument(string $collection, string $id): Document return $document; } + if($cache) { + $this->cache->save($id, json_encode($document)); // save to cache after fetching from db + } + $document = $this->casting($collection, $document); $document = $this->decode($collection, $document); @@ -565,13 +574,12 @@ public function updateDocument(string $collection, string $id, Document $documen } $document = $this->adapter->updateDocument($collection->getId(), $document); - + $document = $this->decode($collection, $document); + if ($this->cache) { $this->cache->purge($id); - $this->cache->save($id, $document); + $this->cache->save($id, json_encode($document)); } - - $document = $this->decode($collection, $document); return $document; } From bb055abdbd67d931d457092fa834360758c0df27 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Tue, 27 Apr 2021 11:38:44 -0400 Subject: [PATCH 113/190] Fix call to adapter --- src/Database/Database.php | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 91d4df287..1d02e9a68 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -456,18 +456,13 @@ public function getDocument(string $collection, string $id): Document if ($this->cache) { $cache = json_decode($this->cache->load($id, self::TTL), true); - $document = ($cache) ? new Document() : null; - - if (!$document) { - foreach ($cache as $key => $value) $document->{$key} = $value; - } + $document = new Document($cache ?? []); } - if (!$document) { + if ($document->isEmpty()) { $document = $this->adapter->getDocument($collection->getId(), $id); } - // $document = $this->adapter->getDocument($collection->getId(), $id); $document->setAttribute('$collection', $collection->getId()); $validator = new Authorization($document, self::PERMISSION_READ); From 4df301e7150a8014dbca491daa1b446b1e47ac88 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Tue, 27 Apr 2021 11:45:27 -0400 Subject: [PATCH 114/190] Save array copy of Document object --- src/Database/Database.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 1d02e9a68..34a77b46f 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -476,7 +476,7 @@ public function getDocument(string $collection, string $id): Document } if($cache) { - $this->cache->save($id, json_encode($document)); // save to cache after fetching from db + $this->cache->save($id, json_encode($document->getArrayCopy())); // save to cache after fetching from db } $document = $this->casting($collection, $document); @@ -573,7 +573,7 @@ public function updateDocument(string $collection, string $id, Document $documen if ($this->cache) { $this->cache->purge($id); - $this->cache->save($id, json_encode($document)); + $this->cache->save($id, json_encode($document->getArrayCopy())); } return $document; From 286a74b21901293a247cbbf3091a3e6891b4ca42 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Tue, 27 Apr 2021 15:26:31 -0400 Subject: [PATCH 115/190] Require cache as contructor param --- src/Database/Database.php | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 34a77b46f..8678ef06a 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -99,9 +99,9 @@ class Database /** * @param Adapter $adapter - * @param Cache $cache (optional) + * @param Cache $cache */ - public function __construct(Adapter $adapter, Cache $cache = null) + public function __construct(Adapter $adapter, Cache $cache) { $this->adapter = $adapter; $this->cache = $cache; @@ -571,10 +571,8 @@ public function updateDocument(string $collection, string $id, Document $documen $document = $this->adapter->updateDocument($collection->getId(), $document); $document = $this->decode($collection, $document); - if ($this->cache) { - $this->cache->purge($id); - $this->cache->save($id, json_encode($document->getArrayCopy())); - } + // $this->cache->purge($id); + // $this->cache->save($id, $document->getArrayCopy()); return $document; } @@ -597,9 +595,7 @@ public function deleteDocument(string $collection, string $id): bool throw new AuthorizationException($validator->getDescription()); } - if ($this->cache) { - $this->cache->purge($id); - } + // $this->cache->purge($id); return $this->adapter->deleteDocument($collection, $id); } From 3f30840fba75f09b6860c79bb830c7e01561ae57 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Tue, 27 Apr 2021 15:29:44 -0400 Subject: [PATCH 116/190] Ensure cache is empty before testing --- tests/Database/Adapter/MariaDBTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Database/Adapter/MariaDBTest.php b/tests/Database/Adapter/MariaDBTest.php index 85b7a791e..27dac6fb5 100644 --- a/tests/Database/Adapter/MariaDBTest.php +++ b/tests/Database/Adapter/MariaDBTest.php @@ -41,6 +41,7 @@ static function getDatabase(): Database $redis = new Redis(); $redis->connect('redis', 6379); + $redis->flushAll(); $cache = new Cache(new RedisAdapter($redis)); $database = new Database(new MariaDB($pdo), $cache); From cf18aa1ab2e3b5dadffd910233bb5c8b98504768 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Tue, 27 Apr 2021 15:38:06 -0400 Subject: [PATCH 117/190] Ensure tests pass without saving to cache --- src/Database/Database.php | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 8678ef06a..d71f87b2b 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -454,12 +454,12 @@ public function getDocument(string $collection, string $id): Document $document = null; $cache = null; - if ($this->cache) { - $cache = json_decode($this->cache->load($id, self::TTL), true); - $document = new Document($cache ?? []); + // TODO@kodumbeats Check if returned cache id matches request + if ($cache = $this->cache->load($id, self::TTL)) { + $document = new Document($cache); } - if ($document->isEmpty()) { + if (!$document) { $document = $this->adapter->getDocument($collection->getId(), $id); } @@ -475,9 +475,7 @@ public function getDocument(string $collection, string $id): Document return $document; } - if($cache) { - $this->cache->save($id, json_encode($document->getArrayCopy())); // save to cache after fetching from db - } + // $this->cache->save($id, $document->getArrayCopy()); // save to cache after fetching from db $document = $this->casting($collection, $document); $document = $this->decode($collection, $document); From 66c9f7069fb9b311587cde6c41632cb18e035f6a Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Tue, 27 Apr 2021 16:18:35 -0400 Subject: [PATCH 118/190] Debugging getArrayCopy method --- src/Database/Database.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Database/Database.php b/src/Database/Database.php index d71f87b2b..27568ca81 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -480,6 +480,8 @@ public function getDocument(string $collection, string $id): Document $document = $this->casting($collection, $document); $document = $this->decode($collection, $document); + // var_dump($document, new Document($document->getArrayCopy())); + return $document; } From c173d233497a7f62c22516c268c0446b423a3fee Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Wed, 28 Apr 2021 15:00:21 +0300 Subject: [PATCH 119/190] Find method - work in progress --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2b3a91064..32ef256c3 100644 --- a/README.md +++ b/README.md @@ -132,11 +132,13 @@ Below is a list of supported adapters, and thier compatibly tested versions alon - [x] Test duplicated document exception is being thrown - [ ] Test no one can overwrite exciting documents/collections without permission - [x] Add autorization validation layer -- [ ] Add strcture validation layer (based on new collection structure) +- [x] Add strcture validation layer (based on new collection structure) - [ ] Add cache layer (Delete / Update documents before cleaning cache) -- [ ] Implement find method +- [x] Implement find method +- [ ] Implement or conditions for the find method - [ ] Test for find timeout limits - [ ] Merge new query syntax parser +- [ ] Limit queries to indexed attaributes only? ## System Requirements From 57be09e34939a1f48fe27f47203878db74f3f8b5 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Wed, 28 Apr 2021 12:38:07 -0400 Subject: [PATCH 120/190] Legacy code that considers arrays as nested documents --- src/Database/Document.php | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Database/Document.php b/src/Database/Document.php index efed817c7..69127fab7 100644 --- a/src/Database/Document.php +++ b/src/Database/Document.php @@ -32,19 +32,19 @@ public function __construct(array $input = []) throw new Exception('$write permission must be of type array'); } - foreach ($input as $key => &$value) { - if (\is_array($value)) { - if ((isset($value['$id']) || isset($value['$collection']))) { - $input[$key] = new self($value); - } else { - foreach ($value as $childKey => $child) { - if ((isset($child['$id']) || isset($child['$collection'])) && (!$child instanceof self)) { - $value[$childKey] = new self($child); - } - } - } - } - } + // foreach ($input as $key => &$value) { + // if (\is_array($value)) { + // if ((isset($value['$id']) || isset($value['$collection']))) { + // $input[$key] = new self($value); + // } else { + // foreach ($value as $childKey => $child) { + // if ((isset($child['$id']) || isset($child['$collection'])) && (!$child instanceof self)) { + // $value[$childKey] = new self($child); + // } + // } + // } + // } + // } parent::__construct($input); } From 53d521c9917e7cd4a91f3d6cf3103dacc5e54572 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Wed, 28 Apr 2021 12:45:26 -0400 Subject: [PATCH 121/190] Validate cache read access with authorization validator --- src/Database/Database.php | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 27568ca81..98fdcf268 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -457,12 +457,21 @@ public function getDocument(string $collection, string $id): Document // TODO@kodumbeats Check if returned cache id matches request if ($cache = $this->cache->load($id, self::TTL)) { $document = new Document($cache); - } + $validator = new Authorization($document, self::PERMISSION_READ); + + if (!$validator->isValid($document->getRead()) && $collection->getId() !== self::COLLECTIONS) { // Check if user has read access to this document + return new Document(); + } + + if($document->isEmpty()) { + return $document; + } - if (!$document) { - $document = $this->adapter->getDocument($collection->getId(), $id); + return $document; } + $document = $this->adapter->getDocument($collection->getId(), $id); + $document->setAttribute('$collection', $collection->getId()); $validator = new Authorization($document, self::PERMISSION_READ); @@ -475,12 +484,10 @@ public function getDocument(string $collection, string $id): Document return $document; } - // $this->cache->save($id, $document->getArrayCopy()); // save to cache after fetching from db - $document = $this->casting($collection, $document); $document = $this->decode($collection, $document); - // var_dump($document, new Document($document->getArrayCopy())); + $this->cache->save($id, $document->getArrayCopy()); // save to cache after fetching from db return $document; } @@ -571,8 +578,8 @@ public function updateDocument(string $collection, string $id, Document $documen $document = $this->adapter->updateDocument($collection->getId(), $document); $document = $this->decode($collection, $document); - // $this->cache->purge($id); - // $this->cache->save($id, $document->getArrayCopy()); + $this->cache->purge($id); + $this->cache->save($id, $document->getArrayCopy()); return $document; } @@ -595,7 +602,7 @@ public function deleteDocument(string $collection, string $id): bool throw new AuthorizationException($validator->getDescription()); } - // $this->cache->purge($id); + $this->cache->purge($id); return $this->adapter->deleteDocument($collection, $id); } From f2cdd98bc2da7658f83e03bd413dde4e6229793c Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Wed, 28 Apr 2021 13:21:03 -0400 Subject: [PATCH 122/190] Add cache to readme example --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2b3a91064..71486a7ce 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,8 @@ Some examples to help you get started. use PDO; use Utopia\Database\Database; use Utopia\Database\Adapter\MariaDB; +use Utopia\Cache\Cache; +use Utopia\Cache\Adapter\None as NoCache; $dbHost = 'mariadb'; $dbPort = '3306'; @@ -71,7 +73,9 @@ $pdo = new PDO("mysql:host={$dbHost};port={$dbPort};charset=utf8mb4", $dbUser, $ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, ]); -$database = new Database(new MariaDB($pdo)); +$cache = new Cache(new NoCache()); + +$database = new Database(new MariaDB($pdo), $cache); $database->setNamespace('myscope'); $database->create(); // Creates a new schema named `myscope` From 2549ca2e16b06ff23076c7e60520dfd46e473346 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Wed, 28 Apr 2021 16:19:14 -0400 Subject: [PATCH 123/190] Create query validator --- src/Database/Validator/Query.php | 124 +++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 src/Database/Validator/Query.php diff --git a/src/Database/Validator/Query.php b/src/Database/Validator/Query.php new file mode 100644 index 000000000..3ee6e690c --- /dev/null +++ b/src/Database/Validator/Query.php @@ -0,0 +1,124 @@ +schema = $schema; + } + + /** + * Get Description. + * + * Returns validator description + * + * @return string + */ + public function getDescription() + { + return $this->message; + } + + /** + * Is valid. + * + * Returns true if query typed according to schema. + * + * @param Query $query + * + * @return bool + */ + public function isValid(Query $query) + { + // Validate operator + if (!in_array($query->getOperator(), $this->operators)) { + $this->message = 'Query operator invalid'; + return false; + } + + // Validate attribute by name and type + $attributes = $this->schema['attributes']; + + if (!in_array($query->getAttribute(), $attributes)) { + $this->message = 'Attribute not found in schema'; + return false; + } + + // Extract the type of desired attribute from collection $schema + $attributeType = $this->schema[array_search($query->getAttribute(), array_column($this->schema, '$id'))]['type']; + + if ($attributeType !== gettype($query->getValue())) { + $this->message = 'Query type does not match schema'; + return false; + } + + // Ensure values are array + if (is_array($query->getValues())) { + return true; + } + + return false; + } + /** + * Is array + * + * Function will return true if object is array. + * + * @return bool + */ + public function isArray(): bool + { + return false; + } + + /** + * Get Type + * + * Returns validator type. + * + * @return string + */ + public function getType(): string + { + return self::TYPE_OBJECT; + } +} From e16e3981c3c2734d38f308cf05e9fe4709c2ba53 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Wed, 28 Apr 2021 16:21:55 -0400 Subject: [PATCH 124/190] Remove unused import --- src/Database/Validator/Query.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Database/Validator/Query.php b/src/Database/Validator/Query.php index 3ee6e690c..f9e99d47d 100644 --- a/src/Database/Validator/Query.php +++ b/src/Database/Validator/Query.php @@ -3,7 +3,6 @@ namespace Utopia\Database\Validator; use Utopia\Validator; -use Utopia\Database\Database; use Utopia\Database\Query; class Query extends Validator From 1ebfacb466b9c4bf9b510d2eb3e965bc12e177d4 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Wed, 28 Apr 2021 17:28:50 -0400 Subject: [PATCH 125/190] Rename to avoid namespace conflict --- .../{Query.php => QueryValidator.php} | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) rename src/Database/Validator/{Query.php => QueryValidator.php} (74%) diff --git a/src/Database/Validator/Query.php b/src/Database/Validator/QueryValidator.php similarity index 74% rename from src/Database/Validator/Query.php rename to src/Database/Validator/QueryValidator.php index f9e99d47d..9f65f986e 100644 --- a/src/Database/Validator/Query.php +++ b/src/Database/Validator/QueryValidator.php @@ -5,7 +5,7 @@ use Utopia\Validator; use Utopia\Database\Query; -class Query extends Validator +class QueryValidator extends Validator { /** * @var string @@ -62,11 +62,11 @@ public function getDescription() * * Returns true if query typed according to schema. * - * @param Query $query + * @param $query * * @return bool */ - public function isValid(Query $query) + public function isValid($query) { // Validate operator if (!in_array($query->getOperator(), $this->operators)) { @@ -74,28 +74,30 @@ public function isValid(Query $query) return false; } - // Validate attribute by name and type - $attributes = $this->schema['attributes']; + // Search for attribute in schema + $attributeIndex = array_search($query->getAttribute(), array_column($this->schema, '$id')); - if (!in_array($query->getAttribute(), $attributes)) { - $this->message = 'Attribute not found in schema'; + if ($attributeIndex === false) { + $this->message = 'Attribute not found in schema: ' . $query->getAttribute(); return false; } // Extract the type of desired attribute from collection $schema $attributeType = $this->schema[array_search($query->getAttribute(), array_column($this->schema, '$id'))]['type']; - if ($attributeType !== gettype($query->getValue())) { - $this->message = 'Query type does not match schema'; - return false; + foreach ($query->getValues() as $value) { + if (gettype($value) ==! $attributeType) { + $this->message = 'Query type does not match expected' . $attributeType; + return false; + } } // Ensure values are array - if (is_array($query->getValues())) { - return true; + if (!is_array($query->getValues())) { + return false; } - return false; + return true; } /** * Is array From 78ed2c00686fef5ff38f31671820da3124c84d75 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Wed, 28 Apr 2021 17:29:12 -0400 Subject: [PATCH 126/190] Test query validator --- .../Database/Validator/QueryValidatorTest.php | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 tests/Database/Validator/QueryValidatorTest.php diff --git a/tests/Database/Validator/QueryValidatorTest.php b/tests/Database/Validator/QueryValidatorTest.php new file mode 100644 index 000000000..775045450 --- /dev/null +++ b/tests/Database/Validator/QueryValidatorTest.php @@ -0,0 +1,90 @@ + 'title', + 'type' => Database::VAR_STRING, + 'size' => 256, + 'required' => true, + 'signed' => true, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'description', + 'type' => Database::VAR_STRING, + 'size' => 1000000, + 'required' => true, + 'signed' => true, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'rating', + 'type' => Database::VAR_INTEGER, + 'size' => 5, + 'required' => true, + 'signed' => true, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'price', + 'type' => Database::VAR_FLOAT, + 'size' => 5, + 'required' => true, + 'signed' => true, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'published', + 'type' => Database::VAR_BOOLEAN, + 'size' => 5, + 'required' => true, + 'signed' => true, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'tags', + 'type' => Database::VAR_STRING, + 'size' => 55, + 'required' => true, + 'signed' => true, + 'array' => true, + 'filters' => [], + ], + ]; + + public function setUp(): void + { + } + + public function tearDown(): void + { + } + + public function testQuery() + { + $validator = new QueryValidator($this->schema); + + $query = Query::parse('title.equal("Iron Man")'); + var_dump($query); + + $this->assertEquals(true, $validator->isValid($query)); + } + +} From 8b3d3cb7f714db14030a60f4bc5cbcb91722eba5 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Wed, 28 Apr 2021 17:29:51 -0400 Subject: [PATCH 127/190] Remove debugging code --- tests/Database/Validator/QueryValidatorTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Database/Validator/QueryValidatorTest.php b/tests/Database/Validator/QueryValidatorTest.php index 775045450..a9d08a19e 100644 --- a/tests/Database/Validator/QueryValidatorTest.php +++ b/tests/Database/Validator/QueryValidatorTest.php @@ -82,7 +82,6 @@ public function testQuery() $validator = new QueryValidator($this->schema); $query = Query::parse('title.equal("Iron Man")'); - var_dump($query); $this->assertEquals(true, $validator->isValid($query)); } From 767c225c52523fcdfd79790ef5a55614f6eb17f3 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Wed, 28 Apr 2021 17:51:40 -0400 Subject: [PATCH 128/190] Remove unnecessary validation --- src/Database/Validator/QueryValidator.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Database/Validator/QueryValidator.php b/src/Database/Validator/QueryValidator.php index 9f65f986e..f98f25346 100644 --- a/src/Database/Validator/QueryValidator.php +++ b/src/Database/Validator/QueryValidator.php @@ -92,11 +92,6 @@ public function isValid($query) } } - // Ensure values are array - if (!is_array($query->getValues())) { - return false; - } - return true; } /** From 949c9d390e4e4d35c51caec3d0625fbd3501fcbe Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Wed, 28 Apr 2021 17:54:30 -0400 Subject: [PATCH 129/190] Test error handling --- src/Database/Validator/QueryValidator.php | 6 ++--- .../Database/Validator/QueryValidatorTest.php | 25 +++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/Database/Validator/QueryValidator.php b/src/Database/Validator/QueryValidator.php index f98f25346..e3246a1b1 100644 --- a/src/Database/Validator/QueryValidator.php +++ b/src/Database/Validator/QueryValidator.php @@ -70,7 +70,7 @@ public function isValid($query) { // Validate operator if (!in_array($query->getOperator(), $this->operators)) { - $this->message = 'Query operator invalid'; + $this->message = 'Query operator invalid: ' . $query->getOperator(); return false; } @@ -86,8 +86,8 @@ public function isValid($query) $attributeType = $this->schema[array_search($query->getAttribute(), array_column($this->schema, '$id'))]['type']; foreach ($query->getValues() as $value) { - if (gettype($value) ==! $attributeType) { - $this->message = 'Query type does not match expected' . $attributeType; + if (gettype($value) !== $attributeType) { + $this->message = 'Query type does not match expected: ' . $attributeType; return false; } } diff --git a/tests/Database/Validator/QueryValidatorTest.php b/tests/Database/Validator/QueryValidatorTest.php index a9d08a19e..6c62c67b4 100644 --- a/tests/Database/Validator/QueryValidatorTest.php +++ b/tests/Database/Validator/QueryValidatorTest.php @@ -84,6 +84,31 @@ public function testQuery() $query = Query::parse('title.equal("Iron Man")'); $this->assertEquals(true, $validator->isValid($query)); + public function testInvalidOperator() + { + $validator = new QueryValidator($this->schema); + + $validator->isValid(Query::parse('title.eqqual("Iron Man")')); + + $this->assertEquals('Query operator invalid: eqqual', $validator->getDescription()); } + public function testAttributeNotFound() + { + $validator = new QueryValidator($this->schema); + + $validator->isValid(Query::parse('name.equal("Iron Man")')); + + $this->assertEquals('Attribute not found in schema: name', $validator->getDescription()); + } + + public function testAttributeWrongType() + { + $validator = new QueryValidator($this->schema); + + $query = Query::parse('title.equal(1776)'); + $validator->isValid($query); + + $this->assertEquals('Query type does not match expected: string', $validator->getDescription()); + } } From 49dba960989a1f15b39d2b261c703e33aa6885b5 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Wed, 28 Apr 2021 17:54:37 -0400 Subject: [PATCH 130/190] Test for success --- tests/Database/Validator/QueryValidatorTest.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/Database/Validator/QueryValidatorTest.php b/tests/Database/Validator/QueryValidatorTest.php index 6c62c67b4..fbf3f3a37 100644 --- a/tests/Database/Validator/QueryValidatorTest.php +++ b/tests/Database/Validator/QueryValidatorTest.php @@ -81,9 +81,11 @@ public function testQuery() { $validator = new QueryValidator($this->schema); - $query = Query::parse('title.equal("Iron Man")'); + $this->assertEquals(true, $validator->isValid(Query::parse('title.equal("Iron Man")'))); + $this->assertEquals(true, $validator->isValid(Query::parse('title.equal("Iron Man", "Ant Man")'))); + $this->assertEquals(true, $validator->isValid(Query::parse('rating.greater(4)'))); + } - $this->assertEquals(true, $validator->isValid($query)); public function testInvalidOperator() { $validator = new QueryValidator($this->schema); From 7366f853a529301145d8c6d9d84f58b81bfcea9b Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Wed, 28 Apr 2021 18:05:08 -0400 Subject: [PATCH 131/190] Test various types of queries --- tests/Database/Validator/QueryValidatorTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Database/Validator/QueryValidatorTest.php b/tests/Database/Validator/QueryValidatorTest.php index fbf3f3a37..0d1d646eb 100644 --- a/tests/Database/Validator/QueryValidatorTest.php +++ b/tests/Database/Validator/QueryValidatorTest.php @@ -81,8 +81,8 @@ public function testQuery() { $validator = new QueryValidator($this->schema); - $this->assertEquals(true, $validator->isValid(Query::parse('title.equal("Iron Man")'))); - $this->assertEquals(true, $validator->isValid(Query::parse('title.equal("Iron Man", "Ant Man")'))); + $this->assertEquals(true, $validator->isValid(Query::parse('title.notEqual("Iron Man", "Ant Man")'))); + $this->assertEquals(true, $validator->isValid(Query::parse('description.equal("Best movie ever")'))); $this->assertEquals(true, $validator->isValid(Query::parse('rating.greater(4)'))); } From ea0c62237fea1b66c238a9477c32810a7de711a2 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Wed, 28 Apr 2021 18:08:20 -0400 Subject: [PATCH 132/190] Test floats - gettype returns double for legacy reasons --- src/Database/Database.php | 2 +- tests/Database/Validator/QueryValidatorTest.php | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index f9b176b9c..c087a6f93 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -13,7 +13,7 @@ class Database // Simple Types const VAR_STRING = 'string'; const VAR_INTEGER = 'integer'; - const VAR_FLOAT = 'float'; + const VAR_FLOAT = 'double'; const VAR_BOOLEAN = 'boolean'; // Relationships Types diff --git a/tests/Database/Validator/QueryValidatorTest.php b/tests/Database/Validator/QueryValidatorTest.php index 0d1d646eb..4769f1752 100644 --- a/tests/Database/Validator/QueryValidatorTest.php +++ b/tests/Database/Validator/QueryValidatorTest.php @@ -84,6 +84,13 @@ public function testQuery() $this->assertEquals(true, $validator->isValid(Query::parse('title.notEqual("Iron Man", "Ant Man")'))); $this->assertEquals(true, $validator->isValid(Query::parse('description.equal("Best movie ever")'))); $this->assertEquals(true, $validator->isValid(Query::parse('rating.greater(4)'))); + + // Debugging floats + // $query = Query::parse('price.lesserEqual(6.50)'); + // $validator->isValid(Query::parse('price.lesserEqual(6.50)')); + // var_dump($validator->getDescription()); + + $this->assertEquals(true, $validator->isValid(Query::parse('price.lesserEqual(6.50)'))); } public function testInvalidOperator() From e5a698903f08500e06c03e90b1d9bcb0c90b8a35 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 29 Apr 2021 16:50:15 +0300 Subject: [PATCH 133/190] Updated docs --- README.md | 28 ++++++++++++---------------- src/Database/Adapter.php | 30 +++++++++++++++++------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 32ef256c3..e98cd74ab 100644 --- a/README.md +++ b/README.md @@ -123,22 +123,18 @@ Below is a list of supported adapters, and thier compatibly tested versions alon ## TODOS -- [x] Updated collection on deletion -- [x] Updated collection on attribute creation -- [x] Updated collection on attribute deletion -- [x] Updated collection on index creation -- [x] Updated collection on index deletion -- [ ] Validate original document before editing `$id` -- [x] Test duplicated document exception is being thrown -- [ ] Test no one can overwrite exciting documents/collections without permission -- [x] Add autorization validation layer -- [x] Add strcture validation layer (based on new collection structure) -- [ ] Add cache layer (Delete / Update documents before cleaning cache) -- [x] Implement find method -- [ ] Implement or conditions for the find method -- [ ] Test for find timeout limits -- [ ] Merge new query syntax parser -- [ ] Limit queries to indexed attaributes only? +- [ ] CRUD: Updated databases list method +- [ ] CRUD: Updated collection list method +- [ ] CRUD: Add format validation on top of type validation +- [ ] CRUD: Validate original document before editing `$id` +- [ ] CRUD: Test no one can overwrite exciting documents/collections without permission +- [ ] CRUD: Add cache layer (Delete / Update documents before cleaning cache) +- [ ] FIND: Implement or conditions for the find method +- [ ] FIND: Implement or conditions with floats +- [ ] FIND: Test for find timeout limits +- [ ] FIND: Limit queries to indexed attaributes only? +- [ ] FIND: Add a query validator +- [ ] FIND: Add support for more operators (>,>=,<,<=,search/match/like) ## System Requirements diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index 0457b6a90..79cec5b84 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -201,7 +201,7 @@ abstract public function getDocument(string $collection, string $id): Document; abstract public function createDocument(string $collection, Document $document): Document; /** - * Update Document. + * Update Document * * @param string $collection * @param Document $document @@ -211,7 +211,7 @@ abstract public function createDocument(string $collection, Document $document): abstract public function updateDocument(string $collection, Document $document): Document; /** - * Delete Document. + * Delete Document * * @param string $collection * @param string $id @@ -220,17 +220,21 @@ abstract public function updateDocument(string $collection, Document $document): */ abstract public function deleteDocument(string $collection, string $id): bool; - // /** - // * Find. - // * - // * Find data sets using chosen queries - // * - // * @param Document $collection - // * @param array $options - // * - // * @return array - // */ - // abstract public function find(Document $collection, array $options); + /** + * Find Documents + * + * Find data sets using chosen queries + * + * @param string $collection + * @param \Utopia\Database\Query[] $queries + * @param int $limit + * @param int $offset + * @param array $orderAttributes + * @param array $orderTypes + * + * @return Document[] + */ + abstract public function find(string $collection, array $queries = [], $limit = 25, $offset = 0, $orderAttributes = [], $orderTypes = []): array; // /** // * @param array $options From 19486f4a2fab6d1cd8b6676191cecda34b229068 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 29 Apr 2021 16:50:22 +0300 Subject: [PATCH 134/190] Find method - work in progress --- src/Database/Adapter/MariaDB.php | 90 +- src/Database/Database.php | 46 +- src/Database/Query.php | 3 + tests/Database/Base.php | 1460 +++--------------------------- 4 files changed, 256 insertions(+), 1343 deletions(-) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index f1bd52f30..eb7f9cb99 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -9,6 +9,8 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Exception\Duplicate; +use Utopia\Database\Query; +use Utopia\Database\Validator\Authorization; class MariaDB extends Adapter { @@ -467,6 +469,60 @@ public function deleteDocument(string $collection, string $id): bool return true; } + /** + * Find Documents + * + * Find data sets using chosen queries + * + * @param string $collection + * @param \Utopia\Database\Query[] $queries + * @param int $limit + * @param int $offset + * @param array $orderAttributes + * @param array $orderTypes + * + * @return Document[] + */ + public function find(string $collection, array $queries = [], $limit = 25, $offset = 0, $orderAttributes = [], $orderTypes = []): array + { + $name = $this->filter($collection); + $roles = Authorization::getRoles(); + $where = ['1=1']; + + foreach($roles as &$role) { + $role = $this->getPDO()->quote($role, PDO::PARAM_STR); + } + + foreach($queries as $query) { + $where[] = 'table_main.'.$query->getAttribute().$this->getSQLOperator($query->getOperator()).':attribute_'.$query->getAttribute(); // Using `attrubute_` to avoid conflicts with custom names + } + + $stmt = $this->getPDO()->prepare("SELECT table_main.* FROM {$this->getNamespace()}.{$name} table_main + INNER JOIN {$this->getNamespace()}.{$name}_permissions as table_permissions + ON table_main._uid = table_permissions._uid + AND table_permissions._action = 'read' + AND table_permissions._role IN (".implode(',', $roles).") + WHERE ".implode(' AND ', $where)." + LIMIT :offset, :limit; + "); + + foreach($queries as $query) { + $stmt->bindValue(':attribute_'.$query->getAttribute(), $query->getValues()[0], $this->getPDOType($query->getValues()[0])); + } + + $stmt->bindValue(':limit', $limit, PDO::PARAM_INT); + $stmt->bindValue(':offset', $offset, PDO::PARAM_INT); + $stmt->execute(); + + $results = $stmt->fetchAll(); + + foreach ($results as &$value) { + $value = new Document($value); + } + + return $results; + } + /** * Get max STRING limit * @@ -569,6 +625,30 @@ protected function getSQLType(string $type, int $size, bool $signed = true): str } } + /** + * Get SQL Operator + * + * @param string $operator + * + * @return string + */ + protected function getSQLOperator(string $operator): string + { + switch ($operator) { + case Query::TYPE_EQUAL: + return '='; + break; + + case Query::TYPE_NOTEQUAL: + return '!='; + break; + + default: + throw new Exception('Unknown Operator:' . $operator); + break; + } + } + /** * Get PDO Type * @@ -587,14 +667,14 @@ protected function getPDOType($value): int return PDO::PARAM_INT; break; - case 'integer': - return PDO::PARAM_INT; - break; - - // case 'float': // (for historical reasons "double" is returned in case of a float, and not simply "float") + case 'float': // (for historical reasons "double" is returned in case of a float, and not simply "float") case 'double': return PDO::PARAM_STR; break; + + case 'integer': + return PDO::PARAM_INT; + break; default: throw new Exception('Unknown PDO Type for ' . gettype($value)); diff --git a/src/Database/Database.php b/src/Database/Database.php index f9b176b9c..9367e2d58 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -571,33 +571,31 @@ public function deleteDocument(string $collection, string $id): bool return $this->adapter->deleteDocument($collection, $id); } - // /** - // * @param string $collection - // * @param array $options - // * - // * @return Document[] - // */ - // public function find(string $collection, array $options) - // { - // $options = \array_merge([ - // 'offset' => 0, - // 'limit' => 15, - // 'search' => '', - // 'relations' => true, - // 'orderField' => '', - // 'orderType' => 'ASC', - // 'orderCast' => 'int', - // 'filters' => [], - // ], $options); + /** + * Find Documents + * + * @param string $collection + * @param Query[] $queries + * @param int $limit + * @param int $offset + * @param array $orderAttributes + * @param array $orderTypes + * + * @return Document[] + */ + public function find(string $collection, array $queries = [], $limit = 25, $offset = 0, $orderAttributes = [], $orderTypes = []): array + { + $collection = $this->getCollection($collection); - // $results = $this->adapter->find($this->getCollection($collection), $options); + $results = $this->adapter->find($collection->getId(), $queries, $limit, $offset, $orderAttributes, $orderTypes); - // foreach ($results as &$node) { - // $node = $this->decode(new Document($node)); - // } + foreach ($results as &$node) { + $node = $this->casting($collection, $node); + $node = $this->decode($collection, $node); + } - // return $results; - // } + return $results; + } // /** // * @param string $collection diff --git a/src/Database/Query.php b/src/Database/Query.php index 00a56e8f7..bab2cd512 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -4,6 +4,9 @@ class Query { + const TYPE_EQUAL = 'equal'; + const TYPE_NOTEQUAL = 'notEqual'; + /** * @var string */ diff --git a/tests/Database/Base.php b/tests/Database/Base.php index b54893362..47c2ecb5f 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -7,6 +7,7 @@ use Utopia\Database\Document; use Utopia\Database\Exception\Authorization as ExceptionAuthorization; use Utopia\Database\Exception\Duplicate; +use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; abstract class Base extends TestCase @@ -219,1320 +220,151 @@ public function testDeleteDocument(Document $document) $this->assertEquals(true, $document->isEmpty()); } - // public function testCreateDocument() - // { - // $collection1 = self::$database->createDocument(Database::COLLECTIONS, [ - // '$collection' => Database::COLLECTIONS, - // '$permissions' => ['read' => ['*']], - // 'name' => 'Create Documents', - // 'rules' => [ - // [ - // '$collection' => Database::COLLECTION_RULES, - // '$permissions' => ['read' => ['*']], - // 'label' => 'Name', - // 'key' => 'name', - // 'type' => Database::VAR_STRING, - // 'default' => '', - // 'required' => true, - // 'array' => false, - // ], - // [ - // '$collection' => Database::COLLECTION_RULES, - // '$permissions' => ['read' => ['*']], - // 'label' => 'Links', - // 'key' => 'links', - // 'type' => Database::VAR_STRING, - // 'default' => '', - // 'required' => true, - // 'array' => true, - // ], - // ] - // ]); - - // $this->assertEquals(true, self::$database->createCollection($collection1->getId(), [], [])); - - // $document0 = new Document([ - // '$collection' => $collection1->getId(), - // '$permissions' => [ - // 'read' => ['*'], - // 'write' => ['user:123'], - // ], - // 'name' => 'Task #0', - // 'links' => [ - // 'http://example.com/link-1', - // 'http://example.com/link-2', - // 'http://example.com/link-3', - // 'http://example.com/link-4', - // ], - // ]); - - // $document1 = self::$database->createDocument($collection1->getId(), [ - // '$collection' => $collection1->getId(), - // '$permissions' => [ - // 'read' => ['*'], - // 'write' => ['user:123'], - // ], - // 'name' => 'Task #1️⃣', - // 'links' => [ - // 'http://example.com/link-5', - // 'http://example.com/link-6', - // 'http://example.com/link-7', - // 'http://example.com/link-8', - // ], - // ]); - - // $this->assertNotEmpty($document1->getId()); - // $this->assertIsArray($document1->getPermissions()); - // $this->assertArrayHasKey('read', $document1->getPermissions()); - // $this->assertArrayHasKey('write', $document1->getPermissions()); - // $this->assertEquals('Task #1️⃣', $document1->getAttribute('name')); - // $this->assertCount(4, $document1->getAttribute('links')); - // $this->assertEquals('http://example.com/link-5', $document1->getAttribute('links')[0]); - // $this->assertEquals('http://example.com/link-6', $document1->getAttribute('links')[1]); - // $this->assertEquals('http://example.com/link-7', $document1->getAttribute('links')[2]); - // $this->assertEquals('http://example.com/link-8', $document1->getAttribute('links')[3]); - - // $document2 = self::$database->createDocument(Database::COLLECTION_USERS, [ - // '$collection' => Database::COLLECTION_USERS, - // '$permissions' => [ - // 'read' => ['*'], - // 'write' => ['user:123'], - // ], - // 'email' => 'test@appwrite.io', - // 'emailVerification' => false, - // 'status' => 0, - // 'password' => 'secrethash', - // 'password-update' => \time(), - // 'registration' => \time(), - // 'reset' => false, - // 'name' => 'Test', - // ]); - - // $this->assertNotEmpty($document2->getId()); - // $this->assertIsArray($document2->getPermissions()); - // $this->assertArrayHasKey('read', $document2->getPermissions()); - // $this->assertArrayHasKey('write', $document2->getPermissions()); - // $this->assertEquals('test@appwrite.io', $document2->getAttribute('email')); - // $this->assertIsString($document2->getAttribute('email')); - // $this->assertEquals(0, $document2->getAttribute('status')); - // $this->assertIsInt($document2->getAttribute('status')); - // $this->assertEquals(false, $document2->getAttribute('emailVerification')); - // $this->assertIsBool($document2->getAttribute('emailVerification')); - - // $document2 = self::$database->getDocument(Database::COLLECTION_USERS, $document2->getId()); - - // $this->assertNotEmpty($document2->getId()); - // $this->assertIsArray($document2->getPermissions()); - // $this->assertArrayHasKey('read', $document2->getPermissions()); - // $this->assertArrayHasKey('write', $document2->getPermissions()); - // $this->assertEquals('test@appwrite.io', $document2->getAttribute('email')); - // $this->assertIsString($document2->getAttribute('email')); - // $this->assertEquals(0, $document2->getAttribute('status')); - // $this->assertIsInt($document2->getAttribute('status')); - // $this->assertEquals(false, $document2->getAttribute('emailVerification')); - // $this->assertIsBool($document2->getAttribute('emailVerification')); - - // $types = [ - // Database::VAR_STRING, - // Database::VAR_NUMBER, - // Database::VAR_BOOLEAN, - // Database::VAR_DOCUMENT, - // ]; - - // $rules = []; - - // foreach($types as $type) { - // $rules[] = [ - // '$collection' => Database::COLLECTION_RULES, - // '$permissions' => ['read' => ['*']], - // 'label' => ucfirst($type), - // 'key' => $type, - // 'type' => $type, - // 'default' => null, - // 'required' => true, - // 'array' => false, - // 'list' => ($type === Database::VAR_DOCUMENT) ? [$collection1->getId()] : [], - // ]; - - // $rules[] = [ - // '$collection' => Database::COLLECTION_RULES, - // '$permissions' => ['read' => ['*']], - // 'label' => ucfirst($type), - // 'key' => $type.'s', - // 'type' => $type, - // 'default' => null, - // 'required' => true, - // 'array' => true, - // 'list' => ($type === Database::VAR_DOCUMENT) ? [$collection1->getId()] : [], - // ]; - // } - - // $rules[] = [ - // '$collection' => Database::COLLECTION_RULES, - // '$permissions' => ['read' => ['*']], - // 'label' => 'document2', - // 'key' => 'document2', - // 'type' => Database::VAR_DOCUMENT, - // 'default' => null, - // 'required' => true, - // 'array' => false, - // 'list' => [$collection1->getId()], - // ]; - - // $rules[] = [ - // '$collection' => Database::COLLECTION_RULES, - // '$permissions' => ['read' => ['*']], - // 'label' => 'documents2', - // 'key' => 'documents2', - // 'type' => Database::VAR_DOCUMENT, - // 'default' => null, - // 'required' => true, - // 'array' => true, - // 'list' => [$collection1->getId()], - // ]; - - // $collection2 = self::$database->createDocument(Database::COLLECTIONS, [ - // '$collection' => Database::COLLECTIONS, - // '$permissions' => ['read' => ['*']], - // 'name' => 'Create Documents', - // 'rules' => $rules, - // ]); - - // $this->assertEquals(true, self::$database->createCollection($collection2->getId(), [], [])); - - // $document3 = self::$database->createDocument($collection2->getId(), [ - // '$collection' => $collection2->getId(), - // '$permissions' => [ - // 'read' => ['*'], - // 'write' => ['user:123'], - // ], - // 'text' => 'Hello World', - // 'texts' => ['Hello World 1', 'Hello World 2'], - // // 'document' => $document0, - // // 'documents' => [$document0], - // 'document' => $document0, - // 'documents' => [$document1, $document0], - // 'document2' => $document1, - // 'documents2' => [$document0, $document1], - // 'integer' => 1, - // 'integers' => [5, 3, 4], - // 'float' => 2.22, - // 'floats' => [1.13, 4.33, 8.9999], - // 'numeric' => 1, - // 'numerics' => [1, 5, 7.77], - // 'boolean' => true, - // 'booleans' => [true, false, true], - // 'email' => 'test@appwrite.io', - // 'emails' => [ - // 'test4@appwrite.io', - // 'test3@appwrite.io', - // 'test2@appwrite.io', - // 'test1@appwrite.io' - // ], - // 'url' => 'http://example.com/welcome', - // 'urls' => [ - // 'http://example.com/welcome-1', - // 'http://example.com/welcome-2', - // 'http://example.com/welcome-3' - // ], - // 'ipv4' => '172.16.254.1', - // 'ipv4s' => [ - // '172.16.254.1', - // '172.16.254.5' - // ], - // 'ipv6' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334', - // 'ipv6s' => [ - // '2001:0db8:85a3:0000:0000:8a2e:0370:7334', - // '2001:0db8:85a3:0000:0000:8a2e:0370:7337' - // ], - // 'key' => uniqid(), - // 'keys' => [uniqid(), uniqid(), uniqid()], - // ]); - - // $document3 = self::$database->getDocument($collection2->getId(), $document3->getId()); - - // $this->assertIsString($document3->getId()); - // $this->assertIsString($document3->getCollection()); - // $this->assertEquals([ - // 'read' => ['*'], - // 'write' => ['user:123'], - // ], $document3->getPermissions()); - // $this->assertEquals('Hello World', $document3->getAttribute('text')); - // $this->assertCount(2, $document3->getAttribute('texts')); - - // $this->assertIsString($document3->getAttribute('text')); - // $this->assertEquals('Hello World', $document3->getAttribute('text')); - // $this->assertEquals(['Hello World 1', 'Hello World 2'], $document3->getAttribute('texts')); - // $this->assertCount(2, $document3->getAttribute('texts')); - - // $this->assertInstanceOf(Document::class, $document3->getAttribute('document')); - // $this->assertIsString($document3->getAttribute('document')->getId()); - // $this->assertNotEmpty($document3->getAttribute('document')->getId()); - // $this->assertIsArray($document3->getAttribute('document')->getPermissions()); - // $this->assertArrayHasKey('read', $document3->getAttribute('document')->getPermissions()); - // $this->assertArrayHasKey('write', $document3->getAttribute('document')->getPermissions()); - // $this->assertEquals('Task #0', $document3->getAttribute('document')->getAttribute('name')); - // $this->assertCount(4, $document3->getAttribute('document')->getAttribute('links')); - // $this->assertEquals('http://example.com/link-1', $document3->getAttribute('document')->getAttribute('links')[0]); - // $this->assertEquals('http://example.com/link-2', $document3->getAttribute('document')->getAttribute('links')[1]); - // $this->assertEquals('http://example.com/link-3', $document3->getAttribute('document')->getAttribute('links')[2]); - // $this->assertEquals('http://example.com/link-4', $document3->getAttribute('document')->getAttribute('links')[3]); - - // $this->assertInstanceOf(Document::class, $document3->getAttribute('documents')[0]); - // $this->assertIsString($document3->getAttribute('documents')[0]->getId()); - // $this->assertNotEmpty($document3->getAttribute('documents')[0]->getId()); - // $this->assertIsArray($document3->getAttribute('documents')[0]->getPermissions()); - // $this->assertArrayHasKey('read', $document3->getAttribute('documents')[0]->getPermissions()); - // $this->assertArrayHasKey('write', $document3->getAttribute('documents')[0]->getPermissions()); - // $this->assertEquals('Task #1️⃣', $document3->getAttribute('documents')[0]->getAttribute('name')); - // $this->assertCount(4, $document3->getAttribute('documents')[0]->getAttribute('links')); - // $this->assertEquals('http://example.com/link-5', $document3->getAttribute('documents')[0]->getAttribute('links')[0]); - // $this->assertEquals('http://example.com/link-6', $document3->getAttribute('documents')[0]->getAttribute('links')[1]); - // $this->assertEquals('http://example.com/link-7', $document3->getAttribute('documents')[0]->getAttribute('links')[2]); - // $this->assertEquals('http://example.com/link-8', $document3->getAttribute('documents')[0]->getAttribute('links')[3]); - - // $this->assertInstanceOf(Document::class, $document3->getAttribute('documents')[1]); - // $this->assertIsString($document3->getAttribute('documents')[1]->getId()); - // $this->assertNotEmpty($document3->getAttribute('documents')[1]->getId()); - // $this->assertIsArray($document3->getAttribute('documents')[1]->getPermissions()); - // $this->assertArrayHasKey('read', $document3->getAttribute('documents')[1]->getPermissions()); - // $this->assertArrayHasKey('write', $document3->getAttribute('documents')[1]->getPermissions()); - // $this->assertEquals('Task #0', $document3->getAttribute('documents')[1]->getAttribute('name')); - // $this->assertCount(4, $document3->getAttribute('documents')[1]->getAttribute('links')); - // $this->assertEquals('http://example.com/link-1', $document3->getAttribute('documents')[1]->getAttribute('links')[0]); - // $this->assertEquals('http://example.com/link-2', $document3->getAttribute('documents')[1]->getAttribute('links')[1]); - // $this->assertEquals('http://example.com/link-3', $document3->getAttribute('documents')[1]->getAttribute('links')[2]); - // $this->assertEquals('http://example.com/link-4', $document3->getAttribute('documents')[1]->getAttribute('links')[3]); - - // $this->assertInstanceOf(Document::class, $document3->getAttribute('document2')); - // $this->assertIsString($document3->getAttribute('document2')->getId()); - // $this->assertNotEmpty($document3->getAttribute('document2')->getId()); - // $this->assertIsArray($document3->getAttribute('document2')->getPermissions()); - // $this->assertArrayHasKey('read', $document3->getAttribute('document2')->getPermissions()); - // $this->assertArrayHasKey('write', $document3->getAttribute('document2')->getPermissions()); - // $this->assertEquals('Task #1️⃣', $document3->getAttribute('document2')->getAttribute('name')); - // $this->assertCount(4, $document3->getAttribute('document2')->getAttribute('links')); - // $this->assertEquals('http://example.com/link-5', $document3->getAttribute('document2')->getAttribute('links')[0]); - // $this->assertEquals('http://example.com/link-6', $document3->getAttribute('document2')->getAttribute('links')[1]); - // $this->assertEquals('http://example.com/link-7', $document3->getAttribute('document2')->getAttribute('links')[2]); - // $this->assertEquals('http://example.com/link-8', $document3->getAttribute('document2')->getAttribute('links')[3]); - - // $this->assertInstanceOf(Document::class, $document3->getAttribute('documents2')[0]); - // $this->assertIsString($document3->getAttribute('documents2')[0]->getId()); - // $this->assertNotEmpty($document3->getAttribute('documents2')[0]->getId()); - // $this->assertIsArray($document3->getAttribute('documents2')[0]->getPermissions()); - // $this->assertArrayHasKey('read', $document3->getAttribute('documents2')[0]->getPermissions()); - // $this->assertArrayHasKey('write', $document3->getAttribute('documents2')[0]->getPermissions()); - // $this->assertEquals('Task #0', $document3->getAttribute('documents2')[0]->getAttribute('name')); - // $this->assertCount(4, $document3->getAttribute('documents2')[0]->getAttribute('links')); - // $this->assertEquals('http://example.com/link-1', $document3->getAttribute('documents2')[0]->getAttribute('links')[0]); - // $this->assertEquals('http://example.com/link-2', $document3->getAttribute('documents2')[0]->getAttribute('links')[1]); - // $this->assertEquals('http://example.com/link-3', $document3->getAttribute('documents2')[0]->getAttribute('links')[2]); - // $this->assertEquals('http://example.com/link-4', $document3->getAttribute('documents2')[0]->getAttribute('links')[3]); - - // $this->assertInstanceOf(Document::class, $document3->getAttribute('documents2')[1]); - // $this->assertIsString($document3->getAttribute('documents2')[1]->getId()); - // $this->assertNotEmpty($document3->getAttribute('documents2')[1]->getId()); - // $this->assertIsArray($document3->getAttribute('documents2')[1]->getPermissions()); - // $this->assertArrayHasKey('read', $document3->getAttribute('documents2')[1]->getPermissions()); - // $this->assertArrayHasKey('write', $document3->getAttribute('documents2')[1]->getPermissions()); - // $this->assertEquals('Task #1️⃣', $document3->getAttribute('documents2')[1]->getAttribute('name')); - // $this->assertCount(4, $document3->getAttribute('documents2')[1]->getAttribute('links')); - // $this->assertEquals('http://example.com/link-5', $document3->getAttribute('documents2')[1]->getAttribute('links')[0]); - // $this->assertEquals('http://example.com/link-6', $document3->getAttribute('documents2')[1]->getAttribute('links')[1]); - // $this->assertEquals('http://example.com/link-7', $document3->getAttribute('documents2')[1]->getAttribute('links')[2]); - // $this->assertEquals('http://example.com/link-8', $document3->getAttribute('documents2')[1]->getAttribute('links')[3]); - - // $this->assertIsInt($document3->getAttribute('integer')); - // $this->assertEquals(1, $document3->getAttribute('integer')); - // $this->assertIsInt($document3->getAttribute('integers')[0]); - // $this->assertIsInt($document3->getAttribute('integers')[1]); - // $this->assertIsInt($document3->getAttribute('integers')[2]); - // $this->assertEquals([5, 3, 4], $document3->getAttribute('integers')); - // $this->assertCount(3, $document3->getAttribute('integers')); - - // $this->assertIsFloat($document3->getAttribute('float')); - // $this->assertEquals(2.22, $document3->getAttribute('float')); - // $this->assertIsFloat($document3->getAttribute('floats')[0]); - // $this->assertIsFloat($document3->getAttribute('floats')[1]); - // $this->assertIsFloat($document3->getAttribute('floats')[2]); - // $this->assertEquals([1.13, 4.33, 8.9999], $document3->getAttribute('floats')); - // $this->assertCount(3, $document3->getAttribute('floats')); - - // $this->assertIsBool($document3->getAttribute('boolean')); - // $this->assertEquals(true, $document3->getAttribute('boolean')); - // $this->assertIsBool($document3->getAttribute('booleans')[0]); - // $this->assertIsBool($document3->getAttribute('booleans')[1]); - // $this->assertIsBool($document3->getAttribute('booleans')[2]); - // $this->assertEquals([true, false, true], $document3->getAttribute('booleans')); - // $this->assertCount(3, $document3->getAttribute('booleans')); - - // $this->assertIsString($document3->getAttribute('email')); - // $this->assertEquals('test@appwrite.io', $document3->getAttribute('email')); - // $this->assertIsString($document3->getAttribute('emails')[0]); - // $this->assertIsString($document3->getAttribute('emails')[1]); - // $this->assertIsString($document3->getAttribute('emails')[2]); - // $this->assertIsString($document3->getAttribute('emails')[3]); - // $this->assertEquals([ - // 'test4@appwrite.io', - // 'test3@appwrite.io', - // 'test2@appwrite.io', - // 'test1@appwrite.io' - // ], $document3->getAttribute('emails')); - // $this->assertCount(4, $document3->getAttribute('emails')); - - // $this->assertIsString($document3->getAttribute('url')); - // $this->assertEquals('http://example.com/welcome', $document3->getAttribute('url')); - // $this->assertIsString($document3->getAttribute('urls')[0]); - // $this->assertIsString($document3->getAttribute('urls')[1]); - // $this->assertIsString($document3->getAttribute('urls')[2]); - // $this->assertEquals([ - // 'http://example.com/welcome-1', - // 'http://example.com/welcome-2', - // 'http://example.com/welcome-3' - // ], $document3->getAttribute('urls')); - // $this->assertCount(3, $document3->getAttribute('urls')); - - // $this->assertIsString($document3->getAttribute('ipv4')); - // $this->assertEquals('172.16.254.1', $document3->getAttribute('ipv4')); - // $this->assertIsString($document3->getAttribute('ipv4s')[0]); - // $this->assertIsString($document3->getAttribute('ipv4s')[1]); - // $this->assertEquals([ - // '172.16.254.1', - // '172.16.254.5' - // ], $document3->getAttribute('ipv4s')); - // $this->assertCount(2, $document3->getAttribute('ipv4s')); - - // $this->assertIsString($document3->getAttribute('ipv6')); - // $this->assertEquals('2001:0db8:85a3:0000:0000:8a2e:0370:7334', $document3->getAttribute('ipv6')); - // $this->assertIsString($document3->getAttribute('ipv6s')[0]); - // $this->assertIsString($document3->getAttribute('ipv6s')[1]); - // $this->assertEquals([ - // '2001:0db8:85a3:0000:0000:8a2e:0370:7334', - // '2001:0db8:85a3:0000:0000:8a2e:0370:7337' - // ], $document3->getAttribute('ipv6s')); - // $this->assertCount(2, $document3->getAttribute('ipv6s')); - - // $this->assertEquals('2001:0db8:85a3:0000:0000:8a2e:0370:7334', $document3->getAttribute('ipv6')); - // $this->assertEquals([ - // '2001:0db8:85a3:0000:0000:8a2e:0370:7334', - // '2001:0db8:85a3:0000:0000:8a2e:0370:7337' - // ], $document3->getAttribute('ipv6s')); - // $this->assertCount(2, $document3->getAttribute('ipv6s')); - - // $this->assertIsString($document3->getAttribute('key')); - // $this->assertCount(3, $document3->getAttribute('keys')); - // } - - // public function testGetDocument() - // { - // // Mocked document - // $document = self::$database->getDocument(Database::COLLECTIONS, Database::COLLECTION_USERS); - - // $this->assertEquals(Database::COLLECTION_USERS, $document->getId()); - // $this->assertEquals(Database::COLLECTIONS, $document->getCollection()); - - // $collection1 = self::$database->createDocument(Database::COLLECTIONS, [ - // '$collection' => Database::COLLECTIONS, - // '$permissions' => ['read' => ['*']], - // 'name' => 'Create Documents', - // 'rules' => [ - // [ - // '$collection' => Database::COLLECTION_RULES, - // '$permissions' => ['read' => ['*']], - // 'label' => 'Name', - // 'key' => 'name', - // 'type' => Database::VAR_STRING, - // 'default' => '', - // 'required' => true, - // 'array' => false, - // ], - // [ - // '$collection' => Database::COLLECTION_RULES, - // '$permissions' => ['read' => ['*']], - // 'label' => 'Links', - // 'key' => 'links', - // 'type' => Database::VAR_STRING, - // 'default' => '', - // 'required' => true, - // 'array' => true, - // ], - // ] - // ]); - - // $this->assertEquals(true, self::$database->createCollection($collection1->getId(), [], [])); - - // $document1 = self::$database->createDocument($collection1->getId(), [ - // '$collection' => $collection1->getId(), - // '$permissions' => [ - // 'read' => ['*'], - // 'write' => ['user:123'], - // ], - // 'name' => 'Task #1️⃣', - // 'links' => [ - // 'http://example.com/link-5', - // 'http://example.com/link-6', - // 'http://example.com/link-7', - // 'http://example.com/link-8', - // ], - // ]); - - // $document1 = self::$database->getDocument($collection1->getId(), $document1->getId()); - - // $this->assertFalse($document1->isEmpty()); - // $this->assertNotEmpty($document1->getId()); - // $this->assertIsArray($document1->getPermissions()); - // $this->assertArrayHasKey('read', $document1->getPermissions()); - // $this->assertArrayHasKey('write', $document1->getPermissions()); - // $this->assertEquals('Task #1️⃣', $document1->getAttribute('name')); - // $this->assertCount(4, $document1->getAttribute('links')); - // $this->assertEquals('http://example.com/link-5', $document1->getAttribute('links')[0]); - // $this->assertEquals('http://example.com/link-6', $document1->getAttribute('links')[1]); - // $this->assertEquals('http://example.com/link-7', $document1->getAttribute('links')[2]); - // $this->assertEquals('http://example.com/link-8', $document1->getAttribute('links')[3]); - - // $document1 = self::$database->getDocument($collection1->getId(), $document1->getId().'x'); - - // $this->assertTrue($document1->isEmpty()); - // $this->assertEmpty($document1->getId()); - // $this->assertEmpty($document1->getCollection()); - // $this->assertIsArray($document1->getPermissions()); - // $this->assertEmpty($document1->getPermissions()); - // } - - // public function testUpdateDocument() - // { - // $collection1 = self::$database->createDocument(Database::COLLECTIONS, [ - // '$collection' => Database::COLLECTIONS, - // '$permissions' => ['read' => ['*']], - // 'name' => 'Create Documents', - // 'rules' => [ - // [ - // '$collection' => Database::COLLECTION_RULES, - // '$permissions' => ['read' => ['*']], - // 'label' => 'Name', - // 'key' => 'name', - // 'type' => Database::VAR_STRING, - // 'default' => '', - // 'required' => true, - // 'array' => false, - // ], - // [ - // '$collection' => Database::COLLECTION_RULES, - // '$permissions' => ['read' => ['*']], - // 'label' => 'Links', - // 'key' => 'links', - // 'type' => Database::VAR_STRING, - // 'default' => '', - // 'required' => true, - // 'array' => true, - // ], - // ] - // ]); - - // $this->assertEquals(true, self::$database->createCollection($collection1->getId(), [], [])); - - // $document0 = new Document([ - // '$collection' => $collection1->getId(), - // '$permissions' => [ - // 'read' => ['*'], - // 'write' => ['user:123'], - // ], - // 'name' => 'Task #0', - // 'links' => [ - // 'http://example.com/link-1', - // 'http://example.com/link-2', - // 'http://example.com/link-3', - // 'http://example.com/link-4', - // ], - // ]); - - // $document1 = self::$database->createDocument($collection1->getId(), [ - // '$collection' => $collection1->getId(), - // '$permissions' => [ - // 'read' => ['*'], - // 'write' => ['user:123'], - // ], - // 'name' => 'Task #1️⃣', - // 'links' => [ - // 'http://example.com/link-5', - // 'http://example.com/link-6', - // 'http://example.com/link-7', - // 'http://example.com/link-8', - // ], - // ]); - - // $this->assertNotEmpty($document1->getId()); - // $this->assertIsArray($document1->getPermissions()); - // $this->assertArrayHasKey('read', $document1->getPermissions()); - // $this->assertArrayHasKey('write', $document1->getPermissions()); - // $this->assertEquals('Task #1️⃣', $document1->getAttribute('name')); - // $this->assertCount(4, $document1->getAttribute('links')); - // $this->assertEquals('http://example.com/link-5', $document1->getAttribute('links')[0]); - // $this->assertEquals('http://example.com/link-6', $document1->getAttribute('links')[1]); - // $this->assertEquals('http://example.com/link-7', $document1->getAttribute('links')[2]); - // $this->assertEquals('http://example.com/link-8', $document1->getAttribute('links')[3]); - - // $document1 = self::$database->getDocument($collection1->getId(), $document1->getId()); - - // $this->assertNotEmpty($document1->getId()); - // $this->assertIsArray($document1->getPermissions()); - // $this->assertArrayHasKey('read', $document1->getPermissions()); - // $this->assertArrayHasKey('write', $document1->getPermissions()); - // $this->assertEquals('Task #1️⃣', $document1->getAttribute('name')); - // $this->assertCount(4, $document1->getAttribute('links')); - // $this->assertEquals('http://example.com/link-5', $document1->getAttribute('links')[0]); - // $this->assertEquals('http://example.com/link-6', $document1->getAttribute('links')[1]); - // $this->assertEquals('http://example.com/link-7', $document1->getAttribute('links')[2]); - // $this->assertEquals('http://example.com/link-8', $document1->getAttribute('links')[3]); - - // $document1 = self::$database->updateDocument($collection1->getId(), $document1->getId(), [ - // '$id' => $document1->getId(), - // '$collection' => $collection1->getId(), - // '$permissions' => [ - // 'read' => ['user:1234'], - // 'write' => ['user:1234'], - // ], - // 'name' => 'Task #1x', - // 'links' => [ - // 'http://example.com/link-5x', - // 'http://example.com/link-6x', - // 'http://example.com/link-7x', - // 'http://example.com/link-8x', - // ], - // ]); - - // $this->assertNotEmpty($document1->getId()); - // $this->assertIsArray($document1->getPermissions()); - // $this->assertArrayHasKey('read', $document1->getPermissions()); - // $this->assertArrayHasKey('write', $document1->getPermissions()); - // $this->assertEquals('Task #1x', $document1->getAttribute('name')); - // $this->assertCount(4, $document1->getAttribute('links')); - // $this->assertEquals('http://example.com/link-5x', $document1->getAttribute('links')[0]); - // $this->assertEquals('http://example.com/link-6x', $document1->getAttribute('links')[1]); - // $this->assertEquals('http://example.com/link-7x', $document1->getAttribute('links')[2]); - // $this->assertEquals('http://example.com/link-8x', $document1->getAttribute('links')[3]); - - // $document1 = self::$database->getDocument($collection1->getId(), $document1->getId()); - - // $this->assertNotEmpty($document1->getId()); - // $this->assertIsArray($document1->getPermissions()); - // $this->assertArrayHasKey('read', $document1->getPermissions()); - // $this->assertArrayHasKey('write', $document1->getPermissions()); - // $this->assertEquals('Task #1x', $document1->getAttribute('name')); - // $this->assertCount(4, $document1->getAttribute('links')); - // $this->assertEquals('http://example.com/link-5x', $document1->getAttribute('links')[0]); - // $this->assertEquals('http://example.com/link-6x', $document1->getAttribute('links')[1]); - // $this->assertEquals('http://example.com/link-7x', $document1->getAttribute('links')[2]); - // $this->assertEquals('http://example.com/link-8x', $document1->getAttribute('links')[3]); - - // $document2 = self::$database->createDocument(Database::COLLECTION_USERS, [ - // '$collection' => Database::COLLECTION_USERS, - // '$permissions' => [ - // 'read' => ['*'], - // 'write' => ['user:123'], - // ], - // 'email' => 'test5@appwrite.io', - // 'emailVerification' => false, - // 'status' => 0, - // 'password' => 'secrethash', - // 'password-update' => \time(), - // 'registration' => \time(), - // 'reset' => false, - // 'name' => 'Test', - // ]); - - // $this->assertNotEmpty($document2->getId()); - // $this->assertIsArray($document2->getPermissions()); - // $this->assertArrayHasKey('read', $document2->getPermissions()); - // $this->assertArrayHasKey('write', $document2->getPermissions()); - // $this->assertEquals('test5@appwrite.io', $document2->getAttribute('email')); - // $this->assertIsString($document2->getAttribute('email')); - // $this->assertEquals(0, $document2->getAttribute('status')); - // $this->assertIsInt($document2->getAttribute('status')); - // $this->assertEquals(false, $document2->getAttribute('emailVerification')); - // $this->assertIsBool($document2->getAttribute('emailVerification')); - - // $document2 = self::$database->getDocument(Database::COLLECTION_USERS, $document2->getId()); - - // $this->assertNotEmpty($document2->getId()); - // $this->assertIsArray($document2->getPermissions()); - // $this->assertArrayHasKey('read', $document2->getPermissions()); - // $this->assertArrayHasKey('write', $document2->getPermissions()); - // $this->assertEquals('test5@appwrite.io', $document2->getAttribute('email')); - // $this->assertIsString($document2->getAttribute('email')); - // $this->assertEquals(0, $document2->getAttribute('status')); - // $this->assertIsInt($document2->getAttribute('status')); - // $this->assertEquals(false, $document2->getAttribute('emailVerification')); - // $this->assertIsBool($document2->getAttribute('emailVerification')); - - // $document2 = self::$database->updateDocument(Database::COLLECTION_USERS, $document2->getId(), [ - // '$id' => $document2->getId(), - // '$collection' => Database::COLLECTION_USERS, - // '$permissions' => [ - // 'read' => ['user:1234'], - // 'write' => ['user:1234'], - // ], - // 'email' => 'test5x@appwrite.io', - // 'emailVerification' => true, - // 'status' => 1, - // 'password' => 'secrethashx', - // 'password-update' => \time(), - // 'registration' => \time(), - // 'reset' => true, - // 'name' => 'Testx', - // ]); - - // $this->assertNotEmpty($document2->getId()); - // $this->assertIsArray($document2->getPermissions()); - // $this->assertArrayHasKey('read', $document2->getPermissions()); - // $this->assertArrayHasKey('write', $document2->getPermissions()); - // $this->assertEquals('test5x@appwrite.io', $document2->getAttribute('email')); - // $this->assertIsString($document2->getAttribute('email')); - // $this->assertEquals(1, $document2->getAttribute('status')); - // $this->assertIsInt($document2->getAttribute('status')); - // $this->assertEquals(true, $document2->getAttribute('emailVerification')); - // $this->assertIsBool($document2->getAttribute('emailVerification')); - - // $document2 = self::$database->getDocument(Database::COLLECTION_USERS, $document2->getId()); - - // $this->assertNotEmpty($document2->getId()); - // $this->assertIsArray($document2->getPermissions()); - // $this->assertArrayHasKey('read', $document2->getPermissions()); - // $this->assertArrayHasKey('write', $document2->getPermissions()); - // $this->assertEquals('test5x@appwrite.io', $document2->getAttribute('email')); - // $this->assertIsString($document2->getAttribute('email')); - // $this->assertEquals(1, $document2->getAttribute('status')); - // $this->assertIsInt($document2->getAttribute('status')); - // $this->assertEquals(true, $document2->getAttribute('emailVerification')); - // $this->assertIsBool($document2->getAttribute('emailVerification')); - - // $types = [ - // Database::VAR_STRING, - // Database::VAR_NUMBER, - // Database::VAR_BOOLEAN, - // Database::VAR_DOCUMENT, - // ]; - - // $rules = []; - - // foreach($types as $type) { - // $rules[] = [ - // '$collection' => Database::COLLECTION_RULES, - // '$permissions' => ['read' => ['*']], - // 'label' => ucfirst($type), - // 'key' => $type, - // 'type' => $type, - // 'default' => null, - // 'required' => true, - // 'array' => false, - // 'list' => ($type === Database::VAR_DOCUMENT) ? [$collection1->getId()] : [], - // ]; - - // $rules[] = [ - // '$collection' => Database::COLLECTION_RULES, - // '$permissions' => ['read' => ['*']], - // 'label' => ucfirst($type), - // 'key' => $type.'s', - // 'type' => $type, - // 'default' => null, - // 'required' => true, - // 'array' => true, - // 'list' => ($type === Database::VAR_DOCUMENT) ? [$collection1->getId()] : [], - // ]; - // } - - // $rules[] = [ - // '$collection' => Database::COLLECTION_RULES, - // '$permissions' => ['read' => ['*']], - // 'label' => 'document2', - // 'key' => 'document2', - // 'type' => Database::VAR_DOCUMENT, - // 'default' => null, - // 'required' => true, - // 'array' => false, - // 'list' => [$collection1->getId()], - // ]; - - // $rules[] = [ - // '$collection' => Database::COLLECTION_RULES, - // '$permissions' => ['read' => ['*']], - // 'label' => 'documents2', - // 'key' => 'documents2', - // 'type' => Database::VAR_DOCUMENT, - // 'default' => null, - // 'required' => true, - // 'array' => true, - // 'list' => [$collection1->getId()], - // ]; - - // $collection2 = self::$database->createDocument(Database::COLLECTIONS, [ - // '$collection' => Database::COLLECTIONS, - // '$permissions' => ['read' => ['*']], - // 'name' => 'Create Documents', - // 'rules' => $rules, - // ]); - - // $this->assertEquals(true, self::$database->createCollection($collection2->getId(), [], [])); - - // $document3 = self::$database->createDocument($collection2->getId(), [ - // '$collection' => $collection2->getId(), - // '$permissions' => [ - // 'read' => ['*'], - // 'write' => ['user:123'], - // ], - // 'text' => 'Hello World', - // 'texts' => ['Hello World 1', 'Hello World 2'], - // // 'document' => $document0, - // // 'documents' => [$document0], - // 'document' => $document0, - // 'documents' => [$document1, $document0], - // 'document2' => $document1, - // 'documents2' => [$document0, $document1], - // 'integer' => 1, - // 'integers' => [5, 3, 4], - // 'float' => 2.22, - // 'floats' => [1.13, 4.33, 8.9999], - // 'numeric' => 1, - // 'numerics' => [1, 5, 7.77], - // 'boolean' => true, - // 'booleans' => [true, false, true], - // 'email' => 'test@appwrite.io', - // 'emails' => [ - // 'test4@appwrite.io', - // 'test3@appwrite.io', - // 'test2@appwrite.io', - // 'test1@appwrite.io' - // ], - // 'url' => 'http://example.com/welcome', - // 'urls' => [ - // 'http://example.com/welcome-1', - // 'http://example.com/welcome-2', - // 'http://example.com/welcome-3' - // ], - // 'ipv4' => '172.16.254.1', - // 'ipv4s' => [ - // '172.16.254.1', - // '172.16.254.5' - // ], - // 'ipv6' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334', - // 'ipv6s' => [ - // '2001:0db8:85a3:0000:0000:8a2e:0370:7334', - // '2001:0db8:85a3:0000:0000:8a2e:0370:7337' - // ], - // 'key' => uniqid(), - // 'keys' => [uniqid(), uniqid(), uniqid()], - // ]); - - // $document3 = self::$database->getDocument($collection2->getId(), $document3->getId()); - - // $this->assertIsString($document3->getId()); - // $this->assertIsString($document3->getCollection()); - // $this->assertEquals([ - // 'read' => ['*'], - // 'write' => ['user:123'], - // ], $document3->getPermissions()); - // $this->assertEquals('Hello World', $document3->getAttribute('text')); - // $this->assertCount(2, $document3->getAttribute('texts')); - - // $this->assertIsString($document3->getAttribute('text')); - // $this->assertEquals('Hello World', $document3->getAttribute('text')); - // $this->assertEquals(['Hello World 1', 'Hello World 2'], $document3->getAttribute('texts')); - // $this->assertCount(2, $document3->getAttribute('texts')); - - // $this->assertInstanceOf(Document::class, $document3->getAttribute('document')); - // $this->assertIsString($document3->getAttribute('document')->getId()); - // $this->assertNotEmpty($document3->getAttribute('document')->getId()); - // $this->assertIsArray($document3->getAttribute('document')->getPermissions()); - // $this->assertArrayHasKey('read', $document3->getAttribute('document')->getPermissions()); - // $this->assertArrayHasKey('write', $document3->getAttribute('document')->getPermissions()); - // $this->assertEquals('Task #0', $document3->getAttribute('document')->getAttribute('name')); - // $this->assertCount(4, $document3->getAttribute('document')->getAttribute('links')); - // $this->assertEquals('http://example.com/link-1', $document3->getAttribute('document')->getAttribute('links')[0]); - // $this->assertEquals('http://example.com/link-2', $document3->getAttribute('document')->getAttribute('links')[1]); - // $this->assertEquals('http://example.com/link-3', $document3->getAttribute('document')->getAttribute('links')[2]); - // $this->assertEquals('http://example.com/link-4', $document3->getAttribute('document')->getAttribute('links')[3]); - - // $this->assertInstanceOf(Document::class, $document3->getAttribute('documents')[0]); - // $this->assertIsString($document3->getAttribute('documents')[0]->getId()); - // $this->assertNotEmpty($document3->getAttribute('documents')[0]->getId()); - // $this->assertIsArray($document3->getAttribute('documents')[0]->getPermissions()); - // $this->assertArrayHasKey('read', $document3->getAttribute('documents')[0]->getPermissions()); - // $this->assertArrayHasKey('write', $document3->getAttribute('documents')[0]->getPermissions()); - // $this->assertEquals('Task #1x', $document3->getAttribute('documents')[0]->getAttribute('name')); - // $this->assertCount(4, $document3->getAttribute('documents')[0]->getAttribute('links')); - // $this->assertEquals('http://example.com/link-5x', $document3->getAttribute('documents')[0]->getAttribute('links')[0]); - // $this->assertEquals('http://example.com/link-6x', $document3->getAttribute('documents')[0]->getAttribute('links')[1]); - // $this->assertEquals('http://example.com/link-7x', $document3->getAttribute('documents')[0]->getAttribute('links')[2]); - // $this->assertEquals('http://example.com/link-8x', $document3->getAttribute('documents')[0]->getAttribute('links')[3]); - - // $this->assertInstanceOf(Document::class, $document3->getAttribute('documents')[1]); - // $this->assertIsString($document3->getAttribute('documents')[1]->getId()); - // $this->assertNotEmpty($document3->getAttribute('documents')[1]->getId()); - // $this->assertIsArray($document3->getAttribute('documents')[1]->getPermissions()); - // $this->assertArrayHasKey('read', $document3->getAttribute('documents')[1]->getPermissions()); - // $this->assertArrayHasKey('write', $document3->getAttribute('documents')[1]->getPermissions()); - // $this->assertEquals('Task #0', $document3->getAttribute('documents')[1]->getAttribute('name')); - // $this->assertCount(4, $document3->getAttribute('documents')[1]->getAttribute('links')); - // $this->assertEquals('http://example.com/link-1', $document3->getAttribute('documents')[1]->getAttribute('links')[0]); - // $this->assertEquals('http://example.com/link-2', $document3->getAttribute('documents')[1]->getAttribute('links')[1]); - // $this->assertEquals('http://example.com/link-3', $document3->getAttribute('documents')[1]->getAttribute('links')[2]); - // $this->assertEquals('http://example.com/link-4', $document3->getAttribute('documents')[1]->getAttribute('links')[3]); - - // $this->assertInstanceOf(Document::class, $document3->getAttribute('document2')); - // $this->assertIsString($document3->getAttribute('document2')->getId()); - // $this->assertNotEmpty($document3->getAttribute('document2')->getId()); - // $this->assertIsArray($document3->getAttribute('document2')->getPermissions()); - // $this->assertArrayHasKey('read', $document3->getAttribute('document2')->getPermissions()); - // $this->assertArrayHasKey('write', $document3->getAttribute('document2')->getPermissions()); - // $this->assertEquals('Task #1x', $document3->getAttribute('document2')->getAttribute('name')); - // $this->assertCount(4, $document3->getAttribute('document2')->getAttribute('links')); - // $this->assertEquals('http://example.com/link-5x', $document3->getAttribute('document2')->getAttribute('links')[0]); - // $this->assertEquals('http://example.com/link-6x', $document3->getAttribute('document2')->getAttribute('links')[1]); - // $this->assertEquals('http://example.com/link-7x', $document3->getAttribute('document2')->getAttribute('links')[2]); - // $this->assertEquals('http://example.com/link-8x', $document3->getAttribute('document2')->getAttribute('links')[3]); - - // $this->assertInstanceOf(Document::class, $document3->getAttribute('documents2')[0]); - // $this->assertIsString($document3->getAttribute('documents2')[0]->getId()); - // $this->assertNotEmpty($document3->getAttribute('documents2')[0]->getId()); - // $this->assertIsArray($document3->getAttribute('documents2')[0]->getPermissions()); - // $this->assertArrayHasKey('read', $document3->getAttribute('documents2')[0]->getPermissions()); - // $this->assertArrayHasKey('write', $document3->getAttribute('documents2')[0]->getPermissions()); - // $this->assertEquals('Task #0', $document3->getAttribute('documents2')[0]->getAttribute('name')); - // $this->assertCount(4, $document3->getAttribute('documents2')[0]->getAttribute('links')); - // $this->assertEquals('http://example.com/link-1', $document3->getAttribute('documents2')[0]->getAttribute('links')[0]); - // $this->assertEquals('http://example.com/link-2', $document3->getAttribute('documents2')[0]->getAttribute('links')[1]); - // $this->assertEquals('http://example.com/link-3', $document3->getAttribute('documents2')[0]->getAttribute('links')[2]); - // $this->assertEquals('http://example.com/link-4', $document3->getAttribute('documents2')[0]->getAttribute('links')[3]); - - // $this->assertInstanceOf(Document::class, $document3->getAttribute('documents2')[1]); - // $this->assertIsString($document3->getAttribute('documents2')[1]->getId()); - // $this->assertNotEmpty($document3->getAttribute('documents2')[1]->getId()); - // $this->assertIsArray($document3->getAttribute('documents2')[1]->getPermissions()); - // $this->assertArrayHasKey('read', $document3->getAttribute('documents2')[1]->getPermissions()); - // $this->assertArrayHasKey('write', $document3->getAttribute('documents2')[1]->getPermissions()); - // $this->assertEquals('Task #1x', $document3->getAttribute('documents2')[1]->getAttribute('name')); - // $this->assertCount(4, $document3->getAttribute('documents2')[1]->getAttribute('links')); - // $this->assertEquals('http://example.com/link-5x', $document3->getAttribute('documents2')[1]->getAttribute('links')[0]); - // $this->assertEquals('http://example.com/link-6x', $document3->getAttribute('documents2')[1]->getAttribute('links')[1]); - // $this->assertEquals('http://example.com/link-7x', $document3->getAttribute('documents2')[1]->getAttribute('links')[2]); - // $this->assertEquals('http://example.com/link-8x', $document3->getAttribute('documents2')[1]->getAttribute('links')[3]); - - // $this->assertIsInt($document3->getAttribute('integer')); - // $this->assertEquals(1, $document3->getAttribute('integer')); - // $this->assertIsInt($document3->getAttribute('integers')[0]); - // $this->assertIsInt($document3->getAttribute('integers')[1]); - // $this->assertIsInt($document3->getAttribute('integers')[2]); - // $this->assertEquals([5, 3, 4], $document3->getAttribute('integers')); - // $this->assertCount(3, $document3->getAttribute('integers')); - - // $this->assertIsFloat($document3->getAttribute('float')); - // $this->assertEquals(2.22, $document3->getAttribute('float')); - // $this->assertIsFloat($document3->getAttribute('floats')[0]); - // $this->assertIsFloat($document3->getAttribute('floats')[1]); - // $this->assertIsFloat($document3->getAttribute('floats')[2]); - // $this->assertEquals([1.13, 4.33, 8.9999], $document3->getAttribute('floats')); - // $this->assertCount(3, $document3->getAttribute('floats')); - - // $this->assertIsBool($document3->getAttribute('boolean')); - // $this->assertEquals(true, $document3->getAttribute('boolean')); - // $this->assertIsBool($document3->getAttribute('booleans')[0]); - // $this->assertIsBool($document3->getAttribute('booleans')[1]); - // $this->assertIsBool($document3->getAttribute('booleans')[2]); - // $this->assertEquals([true, false, true], $document3->getAttribute('booleans')); - // $this->assertCount(3, $document3->getAttribute('booleans')); - - // $this->assertIsString($document3->getAttribute('email')); - // $this->assertEquals('test@appwrite.io', $document3->getAttribute('email')); - // $this->assertIsString($document3->getAttribute('emails')[0]); - // $this->assertIsString($document3->getAttribute('emails')[1]); - // $this->assertIsString($document3->getAttribute('emails')[2]); - // $this->assertIsString($document3->getAttribute('emails')[3]); - // $this->assertEquals([ - // 'test4@appwrite.io', - // 'test3@appwrite.io', - // 'test2@appwrite.io', - // 'test1@appwrite.io' - // ], $document3->getAttribute('emails')); - // $this->assertCount(4, $document3->getAttribute('emails')); - - // $this->assertIsString($document3->getAttribute('url')); - // $this->assertEquals('http://example.com/welcome', $document3->getAttribute('url')); - // $this->assertIsString($document3->getAttribute('urls')[0]); - // $this->assertIsString($document3->getAttribute('urls')[1]); - // $this->assertIsString($document3->getAttribute('urls')[2]); - // $this->assertEquals([ - // 'http://example.com/welcome-1', - // 'http://example.com/welcome-2', - // 'http://example.com/welcome-3' - // ], $document3->getAttribute('urls')); - // $this->assertCount(3, $document3->getAttribute('urls')); - - // $this->assertIsString($document3->getAttribute('ipv4')); - // $this->assertEquals('172.16.254.1', $document3->getAttribute('ipv4')); - // $this->assertIsString($document3->getAttribute('ipv4s')[0]); - // $this->assertIsString($document3->getAttribute('ipv4s')[1]); - // $this->assertEquals([ - // '172.16.254.1', - // '172.16.254.5' - // ], $document3->getAttribute('ipv4s')); - // $this->assertCount(2, $document3->getAttribute('ipv4s')); - - // $this->assertIsString($document3->getAttribute('ipv6')); - // $this->assertEquals('2001:0db8:85a3:0000:0000:8a2e:0370:7334', $document3->getAttribute('ipv6')); - // $this->assertIsString($document3->getAttribute('ipv6s')[0]); - // $this->assertIsString($document3->getAttribute('ipv6s')[1]); - // $this->assertEquals([ - // '2001:0db8:85a3:0000:0000:8a2e:0370:7334', - // '2001:0db8:85a3:0000:0000:8a2e:0370:7337' - // ], $document3->getAttribute('ipv6s')); - // $this->assertCount(2, $document3->getAttribute('ipv6s')); - - // $this->assertEquals('2001:0db8:85a3:0000:0000:8a2e:0370:7334', $document3->getAttribute('ipv6')); - // $this->assertEquals([ - // '2001:0db8:85a3:0000:0000:8a2e:0370:7334', - // '2001:0db8:85a3:0000:0000:8a2e:0370:7337' - // ], $document3->getAttribute('ipv6s')); - // $this->assertCount(2, $document3->getAttribute('ipv6s')); - - // $this->assertIsString($document3->getAttribute('key')); - // $this->assertCount(3, $document3->getAttribute('keys')); - - // // Update - - // $document3 = self::$database->updateDocument($collection2->getId(), $document3->getId(), [ - // '$id' => $document3->getId(), - // '$collection' => $collection2->getId(), - // '$permissions' => [ - // 'read' => ['user:1234'], - // 'write' => ['user:1234'], - // ], - // 'text' => 'Hello Worldx', - // 'texts' => ['Hello World 1x', 'Hello World 2x'], - // 'document' => $document0, - // 'documents' => [$document1, $document0], - // 'document2' => $document1, - // 'documents2' => [$document0, $document1], - // 'integer' => 2, - // 'integers' => [6, 4, 5], - // 'float' => 3.22, - // 'floats' => [2.13, 5.33, 9.9999], - // 'numeric' => 2, - // 'numerics' => [2, 6, 8.77], - // 'boolean' => false, - // 'booleans' => [false, true, false], - // 'email' => 'testx@appwrite.io', - // 'emails' => [ - // 'test4x@appwrite.io', - // 'test3x@appwrite.io', - // 'test2x@appwrite.io', - // 'test1x@appwrite.io' - // ], - // 'url' => 'http://example.com/welcomex', - // 'urls' => [ - // 'http://example.com/welcome-1x', - // 'http://example.com/welcome-2x', - // 'http://example.com/welcome-3x' - // ], - // 'ipv4' => '172.16.254.2', - // 'ipv4s' => [ - // '172.16.254.2', - // '172.16.254.6' - // ], - // 'ipv6' => '2001:0db8:85a3:0000:0000:8a2e:0370:7335', - // 'ipv6s' => [ - // '2001:0db8:85a3:0000:0000:8a2e:0370:7335', - // '2001:0db8:85a3:0000:0000:8a2e:0370:7338' - // ], - // 'key' => uniqid().'x', - // 'keys' => [uniqid().'x', uniqid().'x', uniqid().'x'], - // ]); - - // $document3 = self::$database->getDocument($collection2->getId(), $document3->getId()); - - // $this->assertIsString($document3->getId()); - // $this->assertIsString($document3->getCollection()); - // $this->assertEquals([ - // 'read' => ['user:1234'], - // 'write' => ['user:1234'], - // ], $document3->getPermissions()); - // $this->assertEquals('Hello Worldx', $document3->getAttribute('text')); - // $this->assertCount(2, $document3->getAttribute('texts')); - - // $this->assertIsString($document3->getAttribute('text')); - // $this->assertEquals('Hello Worldx', $document3->getAttribute('text')); - // $this->assertEquals(['Hello World 1x', 'Hello World 2x'], $document3->getAttribute('texts')); - // $this->assertCount(2, $document3->getAttribute('texts')); - - // $this->assertInstanceOf(Document::class, $document3->getAttribute('document')); - // $this->assertIsString($document3->getAttribute('document')->getId()); - // $this->assertNotEmpty($document3->getAttribute('document')->getId()); - // $this->assertIsArray($document3->getAttribute('document')->getPermissions()); - // $this->assertArrayHasKey('read', $document3->getAttribute('document')->getPermissions()); - // $this->assertArrayHasKey('write', $document3->getAttribute('document')->getPermissions()); - // $this->assertEquals('Task #0', $document3->getAttribute('document')->getAttribute('name')); - // $this->assertCount(4, $document3->getAttribute('document')->getAttribute('links')); - // $this->assertEquals('http://example.com/link-1', $document3->getAttribute('document')->getAttribute('links')[0]); - // $this->assertEquals('http://example.com/link-2', $document3->getAttribute('document')->getAttribute('links')[1]); - // $this->assertEquals('http://example.com/link-3', $document3->getAttribute('document')->getAttribute('links')[2]); - // $this->assertEquals('http://example.com/link-4', $document3->getAttribute('document')->getAttribute('links')[3]); - - // $this->assertInstanceOf(Document::class, $document3->getAttribute('documents')[0]); - // $this->assertIsString($document3->getAttribute('documents')[0]->getId()); - // $this->assertNotEmpty($document3->getAttribute('documents')[0]->getId()); - // $this->assertIsArray($document3->getAttribute('documents')[0]->getPermissions()); - // $this->assertArrayHasKey('read', $document3->getAttribute('documents')[0]->getPermissions()); - // $this->assertArrayHasKey('write', $document3->getAttribute('documents')[0]->getPermissions()); - // $this->assertEquals('Task #1x', $document3->getAttribute('documents')[0]->getAttribute('name')); - // $this->assertCount(4, $document3->getAttribute('documents')[0]->getAttribute('links')); - // $this->assertEquals('http://example.com/link-5x', $document3->getAttribute('documents')[0]->getAttribute('links')[0]); - // $this->assertEquals('http://example.com/link-6x', $document3->getAttribute('documents')[0]->getAttribute('links')[1]); - // $this->assertEquals('http://example.com/link-7x', $document3->getAttribute('documents')[0]->getAttribute('links')[2]); - // $this->assertEquals('http://example.com/link-8x', $document3->getAttribute('documents')[0]->getAttribute('links')[3]); - - // $this->assertInstanceOf(Document::class, $document3->getAttribute('documents')[1]); - // $this->assertIsString($document3->getAttribute('documents')[1]->getId()); - // $this->assertNotEmpty($document3->getAttribute('documents')[1]->getId()); - // $this->assertIsArray($document3->getAttribute('documents')[1]->getPermissions()); - // $this->assertArrayHasKey('read', $document3->getAttribute('documents')[1]->getPermissions()); - // $this->assertArrayHasKey('write', $document3->getAttribute('documents')[1]->getPermissions()); - // $this->assertEquals('Task #0', $document3->getAttribute('documents')[1]->getAttribute('name')); - // $this->assertCount(4, $document3->getAttribute('documents')[1]->getAttribute('links')); - // $this->assertEquals('http://example.com/link-1', $document3->getAttribute('documents')[1]->getAttribute('links')[0]); - // $this->assertEquals('http://example.com/link-2', $document3->getAttribute('documents')[1]->getAttribute('links')[1]); - // $this->assertEquals('http://example.com/link-3', $document3->getAttribute('documents')[1]->getAttribute('links')[2]); - // $this->assertEquals('http://example.com/link-4', $document3->getAttribute('documents')[1]->getAttribute('links')[3]); - - // $this->assertInstanceOf(Document::class, $document3->getAttribute('document2')); - // $this->assertIsString($document3->getAttribute('document2')->getId()); - // $this->assertNotEmpty($document3->getAttribute('document2')->getId()); - // $this->assertIsArray($document3->getAttribute('document2')->getPermissions()); - // $this->assertArrayHasKey('read', $document3->getAttribute('document2')->getPermissions()); - // $this->assertArrayHasKey('write', $document3->getAttribute('document2')->getPermissions()); - // $this->assertEquals('Task #1x', $document3->getAttribute('document2')->getAttribute('name')); - // $this->assertCount(4, $document3->getAttribute('document2')->getAttribute('links')); - // $this->assertEquals('http://example.com/link-5x', $document3->getAttribute('document2')->getAttribute('links')[0]); - // $this->assertEquals('http://example.com/link-6x', $document3->getAttribute('document2')->getAttribute('links')[1]); - // $this->assertEquals('http://example.com/link-7x', $document3->getAttribute('document2')->getAttribute('links')[2]); - // $this->assertEquals('http://example.com/link-8x', $document3->getAttribute('document2')->getAttribute('links')[3]); - - // $this->assertInstanceOf(Document::class, $document3->getAttribute('documents2')[0]); - // $this->assertIsString($document3->getAttribute('documents2')[0]->getId()); - // $this->assertNotEmpty($document3->getAttribute('documents2')[0]->getId()); - // $this->assertIsArray($document3->getAttribute('documents2')[0]->getPermissions()); - // $this->assertArrayHasKey('read', $document3->getAttribute('documents2')[0]->getPermissions()); - // $this->assertArrayHasKey('write', $document3->getAttribute('documents2')[0]->getPermissions()); - // $this->assertEquals('Task #0', $document3->getAttribute('documents2')[0]->getAttribute('name')); - // $this->assertCount(4, $document3->getAttribute('documents2')[0]->getAttribute('links')); - // $this->assertEquals('http://example.com/link-1', $document3->getAttribute('documents2')[0]->getAttribute('links')[0]); - // $this->assertEquals('http://example.com/link-2', $document3->getAttribute('documents2')[0]->getAttribute('links')[1]); - // $this->assertEquals('http://example.com/link-3', $document3->getAttribute('documents2')[0]->getAttribute('links')[2]); - // $this->assertEquals('http://example.com/link-4', $document3->getAttribute('documents2')[0]->getAttribute('links')[3]); - - // $this->assertInstanceOf(Document::class, $document3->getAttribute('documents2')[1]); - // $this->assertIsString($document3->getAttribute('documents2')[1]->getId()); - // $this->assertNotEmpty($document3->getAttribute('documents2')[1]->getId()); - // $this->assertIsArray($document3->getAttribute('documents2')[1]->getPermissions()); - // $this->assertArrayHasKey('read', $document3->getAttribute('documents2')[1]->getPermissions()); - // $this->assertArrayHasKey('write', $document3->getAttribute('documents2')[1]->getPermissions()); - // $this->assertEquals('Task #1x', $document3->getAttribute('documents2')[1]->getAttribute('name')); - // $this->assertCount(4, $document3->getAttribute('documents2')[1]->getAttribute('links')); - // $this->assertEquals('http://example.com/link-5x', $document3->getAttribute('documents2')[1]->getAttribute('links')[0]); - // $this->assertEquals('http://example.com/link-6x', $document3->getAttribute('documents2')[1]->getAttribute('links')[1]); - // $this->assertEquals('http://example.com/link-7x', $document3->getAttribute('documents2')[1]->getAttribute('links')[2]); - // $this->assertEquals('http://example.com/link-8x', $document3->getAttribute('documents2')[1]->getAttribute('links')[3]); - - // $this->assertIsInt($document3->getAttribute('integer')); - // $this->assertEquals(2, $document3->getAttribute('integer')); - // $this->assertIsInt($document3->getAttribute('integers')[0]); - // $this->assertIsInt($document3->getAttribute('integers')[1]); - // $this->assertIsInt($document3->getAttribute('integers')[2]); - // $this->assertEquals([6, 4, 5], $document3->getAttribute('integers')); - // $this->assertCount(3, $document3->getAttribute('integers')); - - // $this->assertIsFloat($document3->getAttribute('float')); - // $this->assertEquals(3.22, $document3->getAttribute('float')); - // $this->assertIsFloat($document3->getAttribute('floats')[0]); - // $this->assertIsFloat($document3->getAttribute('floats')[1]); - // $this->assertIsFloat($document3->getAttribute('floats')[2]); - // $this->assertEquals([2.13, 5.33, 9.9999], $document3->getAttribute('floats')); - // $this->assertCount(3, $document3->getAttribute('floats')); - - // $this->assertIsBool($document3->getAttribute('boolean')); - // $this->assertEquals(false, $document3->getAttribute('boolean')); - // $this->assertIsBool($document3->getAttribute('booleans')[0]); - // $this->assertIsBool($document3->getAttribute('booleans')[1]); - // $this->assertIsBool($document3->getAttribute('booleans')[2]); - // $this->assertEquals([false, true, false], $document3->getAttribute('booleans')); - // $this->assertCount(3, $document3->getAttribute('booleans')); - - // $this->assertIsString($document3->getAttribute('email')); - // $this->assertEquals('testx@appwrite.io', $document3->getAttribute('email')); - // $this->assertIsString($document3->getAttribute('emails')[0]); - // $this->assertIsString($document3->getAttribute('emails')[1]); - // $this->assertIsString($document3->getAttribute('emails')[2]); - // $this->assertIsString($document3->getAttribute('emails')[3]); - // $this->assertEquals([ - // 'test4x@appwrite.io', - // 'test3x@appwrite.io', - // 'test2x@appwrite.io', - // 'test1x@appwrite.io' - // ], $document3->getAttribute('emails')); - // $this->assertCount(4, $document3->getAttribute('emails')); - - // $this->assertIsString($document3->getAttribute('url')); - // $this->assertEquals('http://example.com/welcomex', $document3->getAttribute('url')); - // $this->assertIsString($document3->getAttribute('urls')[0]); - // $this->assertIsString($document3->getAttribute('urls')[1]); - // $this->assertIsString($document3->getAttribute('urls')[2]); - // $this->assertEquals([ - // 'http://example.com/welcome-1x', - // 'http://example.com/welcome-2x', - // 'http://example.com/welcome-3x' - // ], $document3->getAttribute('urls')); - // $this->assertCount(3, $document3->getAttribute('urls')); - - // $this->assertIsString($document3->getAttribute('ipv4')); - // $this->assertEquals('172.16.254.2', $document3->getAttribute('ipv4')); - // $this->assertIsString($document3->getAttribute('ipv4s')[0]); - // $this->assertIsString($document3->getAttribute('ipv4s')[1]); - // $this->assertEquals([ - // '172.16.254.2', - // '172.16.254.6' - // ], $document3->getAttribute('ipv4s')); - // $this->assertCount(2, $document3->getAttribute('ipv4s')); - - // $this->assertIsString($document3->getAttribute('ipv6')); - // $this->assertEquals('2001:0db8:85a3:0000:0000:8a2e:0370:7335', $document3->getAttribute('ipv6')); - // $this->assertIsString($document3->getAttribute('ipv6s')[0]); - // $this->assertIsString($document3->getAttribute('ipv6s')[1]); - // $this->assertEquals([ - // '2001:0db8:85a3:0000:0000:8a2e:0370:7335', - // '2001:0db8:85a3:0000:0000:8a2e:0370:7338' - // ], $document3->getAttribute('ipv6s')); - // $this->assertCount(2, $document3->getAttribute('ipv6s')); - - // $this->assertEquals('2001:0db8:85a3:0000:0000:8a2e:0370:7335', $document3->getAttribute('ipv6')); - // $this->assertEquals([ - // '2001:0db8:85a3:0000:0000:8a2e:0370:7335', - // '2001:0db8:85a3:0000:0000:8a2e:0370:7338' - // ], $document3->getAttribute('ipv6s')); - // $this->assertCount(2, $document3->getAttribute('ipv6s')); - - // $this->assertIsString($document3->getAttribute('key')); - // $this->assertCount(3, $document3->getAttribute('keys')); - // } - - // public function testDeleteDocument() - // { - // $collection1 = self::$database->createDocument(Database::COLLECTIONS, [ - // '$collection' => Database::COLLECTIONS, - // '$permissions' => ['read' => ['*']], - // 'name' => 'Create Documents', - // 'rules' => [ - // [ - // '$collection' => Database::COLLECTION_RULES, - // '$permissions' => ['read' => ['*']], - // 'label' => 'Name', - // 'key' => 'name', - // 'type' => Database::VAR_STRING, - // 'default' => '', - // 'required' => true, - // 'array' => false, - // ], - // [ - // '$collection' => Database::COLLECTION_RULES, - // '$permissions' => ['read' => ['*']], - // 'label' => 'Links', - // 'key' => 'links', - // 'type' => Database::VAR_STRING, - // 'default' => '', - // 'required' => true, - // 'array' => true, - // ], - // ] - // ]); - - // $this->assertEquals(true, self::$database->createCollection($collection1->getId(), [], [])); - - // $document1 = self::$database->createDocument($collection1->getId(), [ - // '$collection' => $collection1->getId(), - // '$permissions' => [ - // 'read' => ['*'], - // 'write' => ['user:123'], - // ], - // 'name' => 'Task #1️⃣', - // 'links' => [ - // 'http://example.com/link-5', - // 'http://example.com/link-6', - // 'http://example.com/link-7', - // 'http://example.com/link-8', - // ], - // ]); - - // $this->assertNotEmpty($document1->getId()); - // $this->assertIsArray($document1->getPermissions()); - // $this->assertArrayHasKey('read', $document1->getPermissions()); - // $this->assertArrayHasKey('write', $document1->getPermissions()); - // $this->assertEquals('Task #1️⃣', $document1->getAttribute('name')); - // $this->assertCount(4, $document1->getAttribute('links')); - // $this->assertEquals('http://example.com/link-5', $document1->getAttribute('links')[0]); - // $this->assertEquals('http://example.com/link-6', $document1->getAttribute('links')[1]); - // $this->assertEquals('http://example.com/link-7', $document1->getAttribute('links')[2]); - // $this->assertEquals('http://example.com/link-8', $document1->getAttribute('links')[3]); - - // $document1 = self::$database->getDocument($collection1->getId(), $document1->getId()); - - // $this->assertNotEmpty($document1->getId()); - // $this->assertIsArray($document1->getPermissions()); - // $this->assertArrayHasKey('read', $document1->getPermissions()); - // $this->assertArrayHasKey('write', $document1->getPermissions()); - // $this->assertEquals('Task #1️⃣', $document1->getAttribute('name')); - // $this->assertCount(4, $document1->getAttribute('links')); - // $this->assertEquals('http://example.com/link-5', $document1->getAttribute('links')[0]); - // $this->assertEquals('http://example.com/link-6', $document1->getAttribute('links')[1]); - // $this->assertEquals('http://example.com/link-7', $document1->getAttribute('links')[2]); - // $this->assertEquals('http://example.com/link-8', $document1->getAttribute('links')[3]); - - // self::$database->deleteDocument($collection1->getId(), $document1->getId()); - - // $document1 = self::$database->getDocument($collection1->getId(), $document1->getId()); - - // $this->assertTrue($document1->isEmpty()); - // $this->assertEmpty($document1->getId()); - // $this->assertEmpty($document1->getCollection()); - // $this->assertIsArray($document1->getPermissions()); - // $this->assertEmpty($document1->getPermissions()); - // } - - // public function testFind() - // { - // $data = include __DIR__.'/../../resources/database/movies.php'; - - // $collections = $data['collections']; - // $movies = $data['movies']; - - // foreach ($collections as $key => &$collection) { - // $collection = self::$database->createDocument(Database::COLLECTIONS, $collection); - // self::$database->createCollection($collection->getId(), [], []); - // } - - // foreach ($movies as $key => &$movie) { - // $movie['$collection'] = $collection->getId(); - // $movie['$permissions'] = []; - // $movie = self::$database->createDocument($collection->getId(), $movie); - // } - - // self::$database->find($collection->getId(), [ - // 'limit' => 5, - // 'filters' => [ - // 'name=Hello World', - // 'releaseYear=1999', - // 'langauges=English', - // ], - // ]); - // $this->assertEquals('1', '1'); - // } + /** + * @depends testUpdateDocument + */ + public function testFind(Document $document) + { + static::getDatabase()->createCollection('movies'); + + $this->assertEquals(true, static::getDatabase()->createAttribute('movies', 'name', Database::VAR_STRING, 128, true)); + $this->assertEquals(true, static::getDatabase()->createAttribute('movies', 'director', Database::VAR_STRING, 128, true)); + $this->assertEquals(true, static::getDatabase()->createAttribute('movies', 'year', Database::VAR_INTEGER, 0, true)); + $this->assertEquals(true, static::getDatabase()->createAttribute('movies', 'price', Database::VAR_FLOAT, 0, true)); + $this->assertEquals(true, static::getDatabase()->createAttribute('movies', 'active', Database::VAR_BOOLEAN, 0, true)); + $this->assertEquals(true, static::getDatabase()->createAttribute('movies', 'generes', Database::VAR_STRING, 32, true, true, true)); + + static::getDatabase()->createDocument('movies', new Document([ + '$read' => ['*', 'user1', 'user2'], + '$write' => ['*', 'user1x', 'user2x'], + 'name' => 'Frozen', + 'director' => 'Chris Buck & Jennifer Lee', + 'year' => 2013, + 'price' => 39.50, + 'active' => true, + 'generes' => ['animation', 'kids'], + ])); + + static::getDatabase()->createDocument('movies', new Document([ + '$read' => ['*', 'user1', 'user2'], + '$write' => ['*', 'user1x', 'user2x'], + 'name' => 'Frozen II', + 'director' => 'Chris Buck & Jennifer Lee', + 'year' => 2019, + 'price' => 39.50, + 'active' => true, + 'generes' => ['animation', 'kids'], + ])); + + static::getDatabase()->createDocument('movies', new Document([ + '$read' => ['*', 'user1', 'user2'], + '$write' => ['*', 'user1x', 'user2x'], + 'name' => 'Captain America: The First Avenger', + 'director' => 'Joe Johnston', + 'year' => 2011, + 'price' => 25.94, + 'active' => true, + 'generes' => ['science fiction', 'action', 'comics'], + ])); + + static::getDatabase()->createDocument('movies', new Document([ + '$read' => ['*', 'user1', 'user2'], + '$write' => ['*', 'user1x', 'user2x'], + 'name' => 'Captain Marvel', + 'director' => 'Anna Boden & Ryan Fleck', + 'year' => 2019, + 'price' => 25.99, + 'active' => true, + 'generes' => ['science fiction', 'action', 'comics'], + ])); + + static::getDatabase()->createDocument('movies', new Document([ + '$read' => ['*', 'user1', 'user2'], + '$write' => ['*', 'user1x', 'user2x'], + 'name' => 'Work in Progress', + 'director' => 'TBD', + 'year' => 2025, + 'price' => 0.0, + 'active' => false, + 'generes' => [], + ])); + + static::getDatabase()->createDocument('movies', new Document([ + '$read' => ['userx'], + '$write' => ['*', 'user1x', 'user2x'], + 'name' => 'Work in Progress 2', + 'director' => 'TBD', + 'year' => 2026, + 'price' => 0.0, + 'active' => false, + 'generes' => [], + ])); + + /** + * Check Basic + */ + $documents = static::getDatabase()->find('movies'); + + $this->assertEquals(5, count($documents)); + + /** + * Check Permissions + */ + Authorization::setRole('userx'); + + $documents = static::getDatabase()->find('movies'); + + $this->assertEquals(6, count($documents)); + + /** + * Check an Integer condition + */ + $documents = static::getDatabase()->find('movies', [ + new Query('year', Query::TYPE_EQUAL, [2019]), + ]); + + $this->assertEquals(2, count($documents)); + $this->assertEquals('Frozen II', $documents[0]['name']); + $this->assertEquals('Captain Marvel', $documents[1]['name']); + + /** + * Boolean condition + */ + $documents = static::getDatabase()->find('movies', [ + new Query('active', Query::TYPE_EQUAL, [true]), + ]); + + $this->assertEquals(4, count($documents)); + + /** + * String condition + */ + $documents = static::getDatabase()->find('movies', [ + new Query('director', Query::TYPE_EQUAL, ['TBD']), + ]); + + $this->assertEquals(2, count($documents)); + + /** + * Float condition + */ + // $documents = static::getDatabase()->find('movies', [ + // new Query('price', Query::TYPE_EQUAL, [25.99]), + // ]); + + // $this->assertEquals(1, count($documents)); + // $this->assertEquals('Captain Marvel', $documents[0]['name']); + + /** + * Multiple conditions + */ + $documents = static::getDatabase()->find('movies', [ + new Query('director', Query::TYPE_EQUAL, ['TBD']), + new Query('year', Query::TYPE_EQUAL, [2026]), + ]); + + $this->assertEquals(1, count($documents)); + } public function testFindFirst() { From 2350e5c3312e848114efea3bd319e5e43aaabfc9 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 29 Apr 2021 11:02:33 -0400 Subject: [PATCH 135/190] Cache updated documentId on updateDocument --- src/Database/Database.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 98fdcf268..ad0d43bfc 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -579,7 +579,7 @@ public function updateDocument(string $collection, string $id, Document $documen $document = $this->decode($collection, $document); $this->cache->purge($id); - $this->cache->save($id, $document->getArrayCopy()); + $this->cache->save($document->getId(), $document->getArrayCopy()); return $document; } From 25aed1bcbad7857adcc121c0a90f46bafdeab161 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 29 Apr 2021 11:14:51 -0400 Subject: [PATCH 136/190] Add assertions for validator response --- tests/Database/Validator/QueryValidatorTest.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/Database/Validator/QueryValidatorTest.php b/tests/Database/Validator/QueryValidatorTest.php index 4769f1752..39a345848 100644 --- a/tests/Database/Validator/QueryValidatorTest.php +++ b/tests/Database/Validator/QueryValidatorTest.php @@ -97,8 +97,9 @@ public function testInvalidOperator() { $validator = new QueryValidator($this->schema); - $validator->isValid(Query::parse('title.eqqual("Iron Man")')); + $response = $validator->isValid(Query::parse('title.eqqual("Iron Man")')); + $this->assertEquals(false, $response); $this->assertEquals('Query operator invalid: eqqual', $validator->getDescription()); } @@ -106,8 +107,9 @@ public function testAttributeNotFound() { $validator = new QueryValidator($this->schema); - $validator->isValid(Query::parse('name.equal("Iron Man")')); + $response = $validator->isValid(Query::parse('name.equal("Iron Man")')); + $this->assertEquals(false, $response); $this->assertEquals('Attribute not found in schema: name', $validator->getDescription()); } @@ -115,9 +117,9 @@ public function testAttributeWrongType() { $validator = new QueryValidator($this->schema); - $query = Query::parse('title.equal(1776)'); - $validator->isValid($query); + $response = $validator->isValid(Query::parse('title.equal(1776)')); + $this->assertEquals(false, $response); $this->assertEquals('Query type does not match expected: string', $validator->getDescription()); } } From 1c6daa33488a501653759c43aa5092e3e56fbbe7 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 29 Apr 2021 11:22:51 -0400 Subject: [PATCH 137/190] Remove debugging code --- tests/Database/Validator/QueryValidatorTest.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/Database/Validator/QueryValidatorTest.php b/tests/Database/Validator/QueryValidatorTest.php index 39a345848..4ad863158 100644 --- a/tests/Database/Validator/QueryValidatorTest.php +++ b/tests/Database/Validator/QueryValidatorTest.php @@ -85,11 +85,6 @@ public function testQuery() $this->assertEquals(true, $validator->isValid(Query::parse('description.equal("Best movie ever")'))); $this->assertEquals(true, $validator->isValid(Query::parse('rating.greater(4)'))); - // Debugging floats - // $query = Query::parse('price.lesserEqual(6.50)'); - // $validator->isValid(Query::parse('price.lesserEqual(6.50)')); - // var_dump($validator->getDescription()); - $this->assertEquals(true, $validator->isValid(Query::parse('price.lesserEqual(6.50)'))); } From 88bc6c5cfa47331439f16837ffb35a746a9ba524 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 29 Apr 2021 19:53:20 +0300 Subject: [PATCH 138/190] Updated composer.json --- composer.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/composer.json b/composer.json index 870f363d5..2ebba1939 100755 --- a/composer.json +++ b/composer.json @@ -9,6 +9,10 @@ { "name": "Eldad Fux", "email": "eldad@appwrite.io" + }, + { + "name": "Brandon Leckemby", + "email": "brandon@appwrite.io" } ], "autoload": { @@ -20,6 +24,8 @@ "require": { "php": ">=7.1", "ext-pdo": "*", + "ext-redis": "*", + "ext-mongodb": "*", "utopia-php/framework": "0.*.*", "utopia-php/cache": "0.4.0", "mongodb/mongodb": "1.8.0" From 3e6fab67bf2dc2b4b5b2ca58377606f1845497cc Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 29 Apr 2021 19:53:31 +0300 Subject: [PATCH 139/190] Updated names to avoid conflicts --- docker-compose.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 71e36278c..566ecf2e6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,7 +13,7 @@ services: postgres: image: postgres:13 - container_name: postgres + container_name: utopia-postgres networks: - database ports: @@ -24,7 +24,7 @@ services: cockroach: image: cockroachdb/cockroach:v20.2.0 - container_name: cockroach + container_name: utopia-cockroach command: start-single-node --insecure --logtostderr networks: - database @@ -34,7 +34,7 @@ services: mariadb: image: mariadb:10.5 - container_name: mariadb + container_name: utopia-mariadb networks: - database ports: @@ -44,7 +44,7 @@ services: mongo: image: mongo:3.6 - container_name: mongo + container_name: utopia-mongo networks: - database ports: @@ -55,7 +55,7 @@ services: mysql: image: mysql:8.0 - container_name: mysql + container_name: utopia-mysql networks: - database ports: @@ -69,7 +69,7 @@ services: redis: image: redis:6.0-alpine - container_name: redis + container_name: utopia-redis networks: - database From 0d87b575c939377fb820ea9a38be1ce66632a47b Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 29 Apr 2021 19:53:42 +0300 Subject: [PATCH 140/190] Commented mongo tests --- tests/Database/Adapter/MongoDBTest.php | 62 +++++++++++++------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/tests/Database/Adapter/MongoDBTest.php b/tests/Database/Adapter/MongoDBTest.php index 7eb314f50..7664d3c4a 100644 --- a/tests/Database/Adapter/MongoDBTest.php +++ b/tests/Database/Adapter/MongoDBTest.php @@ -1,39 +1,39 @@ 'root', - 'password' => 'example', - ], - ); +// $client = new Client('mongodb://mongo/', +// [ +// 'username' => 'root', +// 'password' => 'example', +// ], +// ); - $database = new Database(new MongoDB($client)); - $database->setNamespace('myapp_'.uniqid()); +// $database = new Database(new MongoDB($client)); +// $database->setNamespace('myapp_'.uniqid()); - return self::$database = $database; - } -} \ No newline at end of file +// return self::$database = $database; +// } +// } \ No newline at end of file From 57231e2a323aa2e610e937e5188224616f42ec49 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 29 Apr 2021 19:53:53 +0300 Subject: [PATCH 141/190] Commented postgres tests --- tests/Database/Adapter/PostgresTest.php | 84 ++++++++++++------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/tests/Database/Adapter/PostgresTest.php b/tests/Database/Adapter/PostgresTest.php index 2e8d8d080..9a152c7a5 100644 --- a/tests/Database/Adapter/PostgresTest.php +++ b/tests/Database/Adapter/PostgresTest.php @@ -1,44 +1,44 @@ 'SET NAMES utf8mb4', - PDO::ATTR_TIMEOUT => 3, // Seconds - PDO::ATTR_PERSISTENT => true, - PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, - ]); - - $database = new Database(new Postgres($pdo)); - $database->setNamespace('myapp_'.uniqid()); - - return self::$database = $database; - } -} \ No newline at end of file +// namespace Utopia\Tests\Adapter; + +// use PDO; +// use Utopia\Database\Database; +// use Utopia\Database\Adapter\Postgres; +// use Utopia\Tests\Base; + +// class PostgresTest extends Base +// { +// /** +// * @var Database +// */ +// static $database = null; + +// /** +// * @reture Adapter +// */ +// static function getDatabase(): Database +// { +// if(!is_null(self::$database)) { +// return self::$database; +// } + +// $dbHost = 'postgres'; +// $dbPort = '5432'; +// $dbUser = 'root'; +// $dbPass = 'password'; + +// $pdo = new PDO("pgsql:host={$dbHost};port={$dbPort};", $dbUser, $dbPass, [ +// PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4', +// PDO::ATTR_TIMEOUT => 3, // Seconds +// PDO::ATTR_PERSISTENT => true, +// PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, +// PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, +// ]); + +// $database = new Database(new Postgres($pdo)); +// $database->setNamespace('myapp_'.uniqid()); + +// return self::$database = $database; +// } +// } \ No newline at end of file From 4eced761f221e4723c45f1c252d6c7d40da31de7 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 29 Apr 2021 19:55:39 +0300 Subject: [PATCH 142/190] Fixed MySQL tests --- tests/Database/Adapter/MySQLTest.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/Database/Adapter/MySQLTest.php b/tests/Database/Adapter/MySQLTest.php index 0b7bfbdd8..4d3800d9f 100644 --- a/tests/Database/Adapter/MySQLTest.php +++ b/tests/Database/Adapter/MySQLTest.php @@ -3,8 +3,11 @@ namespace Utopia\Tests\Adapter; use PDO; +use Redis; +use Utopia\Cache\Cache; use Utopia\Database\Database; use Utopia\Database\Adapter\MySQL; +use Utopia\Cache\Adapter\Redis as RedisAdapter; use Utopia\Tests\Base; class MySQLTest extends Base @@ -38,7 +41,12 @@ static function getDatabase(): Database $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); // Return arrays $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // Handle all errors with exceptions - $database = new Database(new MySQL($pdo)); + $redis = new Redis(); + $redis->connect('redis', 6379); + $redis->flushAll(); + $cache = new Cache(new RedisAdapter($redis)); + + $database = new Database(new MySQL($pdo), $cache); $database->setNamespace('myapp_'.uniqid()); return self::$database = $database; From 82c343584073f15219993e0f39346f80fe806663 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 29 Apr 2021 20:35:45 +0300 Subject: [PATCH 143/190] Fixed Psalm errors and warnings --- src/Database/Adapter/Cockroach.php | 8 +- src/Database/Adapter/MariaDB.php | 2 +- src/Database/Adapter/MongoDB.php | 258 +++++++++++++-------------- src/Database/Adapter/Postgres.php | 250 +++++++++++++------------- src/Database/Database.php | 16 +- src/Database/Validator/Structure.php | 12 +- 6 files changed, 269 insertions(+), 277 deletions(-) diff --git a/src/Database/Adapter/Cockroach.php b/src/Database/Adapter/Cockroach.php index 15f9abcef..9f1b6176a 100644 --- a/src/Database/Adapter/Cockroach.php +++ b/src/Database/Adapter/Cockroach.php @@ -1,8 +1,8 @@ client = $client; - } - - /** - * Create Database - * - * @return bool - */ - public function create(): bool - { - $namespace = $this->getNamespace(); - return (!!$this->client->$namespace); - } - - /** - * List Databases - * - * @return array - */ - public function list(): array - { - $list = []; - - foreach ($this->client->listDatabaseNames() as $key => $value) { - $list[] = $value; - } +// namespace Utopia\Database\Adapter; + +// use Exception; +// use Utopia\Database\Adapter; +// use MongoDB\Client; +// use MongoDB\Database; + +// class MongoDB extends Adapter +// { +// /** +// * @var Client +// */ +// protected $client; + +// /** +// * @var Database|null +// */ +// protected $database; + +// /** +// * Constructor. +// * +// * Set connection and settings +// * +// * @param Client $client +// */ +// public function __construct(Client $client) +// { +// $this->client = $client; +// } + +// /** +// * Create Database +// * +// * @return bool +// */ +// public function create(): bool +// { +// $namespace = $this->getNamespace(); +// return (!!$this->client->$namespace); +// } + +// /** +// * List Databases +// * +// * @return array +// */ +// public function list(): array +// { +// $list = []; + +// foreach ($this->client->listDatabaseNames() as $key => $value) { +// $list[] = $value; +// } - return $list; - } - - /** - * Delete Database - * - * @return bool - */ - public function delete(): bool - { - return (!!$this->getDatabase()->dropCollection($this->getNamespace())); - } - - /** - * Create Collection - * - * @param string $id - * @return bool - */ - public function createCollection(string $id): bool - { - return (!!$this->getDatabase()->createCollection($id)); - } - - /** - * List Collections - * - * @return array - */ - public function listCollections(): array - { - $list = []; - - foreach ($this->getDatabase()->listCollectionNames() as $key => $value) { - $list[] = $value; - } +// return $list; +// } + +// /** +// * Delete Database +// * +// * @return bool +// */ +// public function delete(): bool +// { +// return (!!$this->getDatabase()->dropCollection($this->getNamespace())); +// } + +// /** +// * Create Collection +// * +// * @param string $id +// * @return bool +// */ +// public function createCollection(string $id): bool +// { +// return (!!$this->getDatabase()->createCollection($id)); +// } + +// /** +// * List Collections +// * +// * @return array +// */ +// public function listCollections(): array +// { +// $list = []; + +// foreach ($this->getDatabase()->listCollectionNames() as $key => $value) { +// $list[] = $value; +// } - return $list; - } - - /** - * Delete Collection - * - * @param string $id - * @return bool - */ - public function deleteCollection(string $id): bool - { - return (!!$this->getDatabase()->dropCollection($id)); - } - - /** - * @return Database - * - * @throws Exception - */ - protected function getDatabase() - { - if($this->database) { - return $this->database; - } - - $namespace = $this->getNamespace(); +// return $list; +// } + +// /** +// * Delete Collection +// * +// * @param string $id +// * @return bool +// */ +// public function deleteCollection(string $id): bool +// { +// return (!!$this->getDatabase()->dropCollection($id)); +// } + +// /** +// * @return Database +// * +// * @throws Exception +// */ +// protected function getDatabase() +// { +// if($this->database) { +// return $this->database; +// } + +// $namespace = $this->getNamespace(); - return $this->client->$namespace; - } - - /** - * @return Client - * - * @throws Exception - */ - protected function getClient() - { - return $this->client; - } -} \ No newline at end of file +// return $this->client->$namespace; +// } + +// /** +// * @return Client +// * +// * @throws Exception +// */ +// protected function getClient() +// { +// return $this->client; +// } +// } \ No newline at end of file diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php index 141b14666..318cabf8d 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -1,129 +1,129 @@ pdo = $pdo; - } +// namespace Utopia\Database\Adapter; + +// use PDO; +// use Exception; +// use phpDocumentor\Reflection\DocBlock\Tags\Var_; +// use Utopia\Database\Adapter; + +// class Postgres extends Adapter +// { +// /** +// * @var PDO +// */ +// protected $pdo; + +// /** +// * Constructor. +// * +// * Set connection and settings +// * +// * @param PDO $pdo +// */ +// public function __construct(PDO $pdo) +// { +// $this->pdo = $pdo; +// } - /** - * Create Database - * - * @return bool - */ - public function create(): bool - { - $name = $this->getNamespace(); - - return $this->getPDO() - ->prepare("CREATE SCHEMA {$name} /*!40100 DEFAULT CHARACTER SET utf8mb4 */;") - ->execute(); - } - - /** - * List Databases - * - * @return array - */ - public function list(): array - { - $stmt = $this->getPDO() - ->prepare("SELECT datname FROM pg_database;"); - - $stmt->execute(); +// /** +// * Create Database +// * +// * @return bool +// */ +// public function create(): bool +// { +// $name = $this->getNamespace(); + +// return $this->getPDO() +// ->prepare("CREATE SCHEMA {$name} /*!40100 DEFAULT CHARACTER SET utf8mb4 */;") +// ->execute(); +// } + +// /** +// * List Databases +// * +// * @return array +// */ +// public function list(): array +// { +// $stmt = $this->getPDO() +// ->prepare("SELECT datname FROM pg_database;"); + +// $stmt->execute(); - $list = []; - - foreach ($stmt->fetchAll() as $key => $value) { - $list[] = $value['datname'] ?? ''; - } - - return $list; - } - - /** - * Delete Database - * - * @return bool - */ - public function delete(): bool - { - $name = $this->getNamespace(); - - return $this->getPDO() - ->prepare("DROP SCHEMA {$name};") - ->execute(); - } - - /** - * Create Collection - * - * @param string $id - * @return bool - */ - public function createCollection(string $id): bool - { - $name = $this->filter($id).'_documents'; - - return $this->getPDO() - ->prepare("CREATE TABLE {$this->getNamespace()}.{$name}( - _id INT PRIMARY KEY NOT NULL, - _uid CHAR(13) NOT NULL - );") - ->execute(); - } - - /** - * List Collections - * - * @return array - */ - public function listCollections(): array - { - } - - /** - * Delete Collection - * - * @param string $id - * @return bool - */ - public function deleteCollection(string $id): bool - { - $name = $this->filter($id).'_documents'; - - return $this->getPDO() - ->prepare("DROP TABLE {$this->getNamespace()}.{$name};") - ->execute(); - } - - /** - * @return PDO - * - * @throws Exception - */ - protected function getPDO() - { - return $this->pdo; - } -} \ No newline at end of file +// $list = []; + +// foreach ($stmt->fetchAll() as $key => $value) { +// $list[] = $value['datname'] ?? ''; +// } + +// return $list; +// } + +// /** +// * Delete Database +// * +// * @return bool +// */ +// public function delete(): bool +// { +// $name = $this->getNamespace(); + +// return $this->getPDO() +// ->prepare("DROP SCHEMA {$name};") +// ->execute(); +// } + +// /** +// * Create Collection +// * +// * @param string $id +// * @return bool +// */ +// public function createCollection(string $id): bool +// { +// $name = $this->filter($id).'_documents'; + +// return $this->getPDO() +// ->prepare("CREATE TABLE {$this->getNamespace()}.{$name}( +// _id INT PRIMARY KEY NOT NULL, +// _uid CHAR(13) NOT NULL +// );") +// ->execute(); +// } + +// /** +// * List Collections +// * +// * @return array +// */ +// public function listCollections(): array +// { +// } + +// /** +// * Delete Collection +// * +// * @param string $id +// * @return bool +// */ +// public function deleteCollection(string $id): bool +// { +// $name = $this->filter($id).'_documents'; + +// return $this->getPDO() +// ->prepare("DROP TABLE {$this->getNamespace()}.{$name};") +// ->execute(); +// } + +// /** +// * @return PDO +// * +// * @throws Exception +// */ +// protected function getPDO() +// { +// return $this->pdo; +// } +// } \ No newline at end of file diff --git a/src/Database/Database.php b/src/Database/Database.php index 23ff55f2f..1d337c80d 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -107,20 +107,10 @@ public function __construct(Adapter $adapter, Cache $cache) $this->cache = $cache; self::addFilter('json', - function($value) { - $value = ($value instanceof Document) ? $value->getArrayCopy() : $value; - - if(!is_array($value)) { - throw new Exception('Can\'t encode to JSON'); - } - - return json_encode($value); + function(Document $value): string { + return json_encode($value->getArrayCopy()); }, - function($value) { - if(!is_string($value)) { - throw new Exception('Can\'t decode from JSON'); - } - + function(string $value): array { return json_decode($value, true); } ); diff --git a/src/Database/Validator/Structure.php b/src/Database/Validator/Structure.php index c749d008e..c1a738c35 100644 --- a/src/Database/Validator/Structure.php +++ b/src/Database/Validator/Structure.php @@ -60,7 +60,7 @@ class Structure extends Validator ]; /** - * @var Validator[] + * @var array */ static protected $validators = []; @@ -83,8 +83,10 @@ public function __construct(Document $collection) * Remove a Validator * * @param string $name + * + * @return array */ - static public function getValidators() + static public function getValidators(): array { return self::$validators; } @@ -96,7 +98,7 @@ static public function getValidators() * @param Validator $validator * @param string $type */ - static public function addValidator(string $name, Validator $validator, string $type) + static public function addValidator(string $name, Validator $validator, string $type): void { self::$validators[$name] = [ 'validator' => $validator, @@ -109,7 +111,7 @@ static public function addValidator(string $name, Validator $validator, string $ * * @param string $name */ - static public function removeValidator(string $name) + static public function removeValidator(string $name): void { unset(self::$validators[$name]); } @@ -131,7 +133,7 @@ public function getDescription() * * Returns true if valid or false if not. * - * @param Document $document + * @param mixed $document * * @return bool */ From db25e1a9011c8c5d6589f6d0825642626035ebb8 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 29 Apr 2021 20:57:48 +0300 Subject: [PATCH 144/190] Updated CI --- .travis.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.travis.yml b/.travis.yml index 58a7e5fe5..6c5474450 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,12 @@ notifications: before_script: docker run --rm --interactive --tty --volume "$(pwd)":/app composer update --ignore-platform-reqs --optimize-autoloader --no-plugins --no-scripts --prefer-dist +before_install: +- > + if [ ! -z "${DOCKERHUB_PULL_USERNAME:-}" ]; then + echo "${DOCKERHUB_PULL_PASSWORD}" | docker login --username "${DOCKERHUB_PULL_USERNAME}" --password-stdin + fi + install: - docker-compose up -d - sleep 10 From ba2190dfe3b261d8d05107fffda7c6cdfce84bb9 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 29 Apr 2021 21:38:31 +0300 Subject: [PATCH 145/190] Fixed DB errors --- src/Database/Database.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 1d337c80d..e2e628a1d 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -107,10 +107,18 @@ public function __construct(Adapter $adapter, Cache $cache) $this->cache = $cache; self::addFilter('json', - function(Document $value): string { - return json_encode($value->getArrayCopy()); + /** + * @param mixed $value + * @return string + */ + function($value): string { + return json_encode($value); }, - function(string $value): array { + /** + * @param mixed $value + * @return array + */ + function($value): array { return json_decode($value, true); } ); From a40d4d77f507f8c71a721d86db50d99b57eb6f00 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 29 Apr 2021 15:32:29 -0400 Subject: [PATCH 146/190] Cast and hint to appropriate types for psalm --- composer.json | 2 +- composer.lock | 18 ++++++++++-------- src/Database/Query.php | 9 ++++++++- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/composer.json b/composer.json index 2ebba1939..779cde85c 100755 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ "ext-redis": "*", "ext-mongodb": "*", "utopia-php/framework": "0.*.*", - "utopia-php/cache": "0.4.0", + "utopia-php/cache": "0.4.*", "mongodb/mongodb": "1.8.0" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 2bf15be73..9dc980eac 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "fa7d9c6d206e196d739c246ce67aac67", + "content-hash": "dd2546d5cd02cf55391c4a5cd4768bd0", "packages": [ { "name": "composer/package-versions-deprecated", @@ -287,16 +287,16 @@ }, { "name": "utopia-php/cache", - "version": "0.4.0", + "version": "0.4.1", "source": { "type": "git", "url": "https://github.com/utopia-php/cache.git", - "reference": "81c7806e13091a9d585ad9bba57fae625a2cf26c" + "reference": "8c48eff73219c8c1ac2807909f0a38f3480c8938" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cache/zipball/81c7806e13091a9d585ad9bba57fae625a2cf26c", - "reference": "81c7806e13091a9d585ad9bba57fae625a2cf26c", + "url": "https://api.github.com/repos/utopia-php/cache/zipball/8c48eff73219c8c1ac2807909f0a38f3480c8938", + "reference": "8c48eff73219c8c1ac2807909f0a38f3480c8938", "shasum": "" }, "require": { @@ -334,9 +334,9 @@ ], "support": { "issues": "https://github.com/utopia-php/cache/issues", - "source": "https://github.com/utopia-php/cache/tree/0.4.0" + "source": "https://github.com/utopia-php/cache/tree/0.4.1" }, - "time": "2021-04-22T14:28:14+00:00" + "time": "2021-04-29T18:41:43+00:00" }, { "name": "utopia-php/framework", @@ -3922,7 +3922,9 @@ "prefer-lowest": false, "platform": { "php": ">=7.1", - "ext-pdo": "*" + "ext-pdo": "*", + "ext-redis": "*", + "ext-mongodb": "*" }, "platform-dev": [], "plugin-api-version": "2.0.0" diff --git a/src/Database/Query.php b/src/Database/Query.php index bab2cd512..a5b6144b7 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -91,9 +91,16 @@ public function getQuery(): array * */ public static function parse(string $filter): Query { + $attribute = ''; + $operator = ''; + $values = []; + // get index of open parentheses + /** @var int */ $end = mb_strpos($filter, '('); + // count stanzas by only counting '.' that come before open parentheses + /** @var int */ $stanzas = mb_substr_count(mb_substr($filter, 0, $end), ".") + 1; // TODO@kodumbeats handle relations between collections, e.g. if($stanzas > 2) @@ -103,7 +110,7 @@ public static function parse(string $filter): Query $input = explode('.', $filter, $stanzas); $attribute = $input[0]; $expression = $input[1]; - [$operator, $values] = self::parseExpression($expression); + [(string)$operator, (array)$values] = self::parseExpression($expression); break; } From 7f38155011b85122cc982b38f2b17ccb81ceaa3e Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 29 Apr 2021 15:50:32 -0400 Subject: [PATCH 147/190] Correct type declaration for return array --- src/Database/Query.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Database/Query.php b/src/Database/Query.php index a5b6144b7..56b06f2e5 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -110,7 +110,7 @@ public static function parse(string $filter): Query $input = explode('.', $filter, $stanzas); $attribute = $input[0]; $expression = $input[1]; - [(string)$operator, (array)$values] = self::parseExpression($expression); + [$operator, $values] = self::parseExpression($expression); break; } @@ -123,7 +123,7 @@ public static function parse(string $filter): Query * * @param string $expression * - * @return (string|array)[] + * @return array */ protected static function parseExpression(string $expression): array { @@ -153,6 +153,7 @@ protected static function parseExpression(string $expression): array // Cast $value type + /** @var mixed */ $values = array_map(function ($value) { // Trim whitespace from around $value From 2bf2c2200a07395c6575d7d7332168973de4a28e Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 29 Apr 2021 15:53:17 -0400 Subject: [PATCH 148/190] Add more specific typehint --- src/Database/Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Database/Query.php b/src/Database/Query.php index 56b06f2e5..3a3beed54 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -153,7 +153,7 @@ protected static function parseExpression(string $expression): array // Cast $value type - /** @var mixed */ + /** @var array */ $values = array_map(function ($value) { // Trim whitespace from around $value From 9a4047c3534ca3dd27f526d0eddbea8ba08bf18c Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 30 Apr 2021 15:12:15 +0300 Subject: [PATCH 149/190] Update README --- README.md | 46 ++++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index a767e2053..11f934c28 100644 --- a/README.md +++ b/README.md @@ -73,44 +73,54 @@ $pdo = new PDO("mysql:host={$dbHost};port={$dbPort};charset=utf8mb4", $dbUser, $ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, ]); -$cache = new Cache(new NoCache()); +$cache = new Cache(new NoCache()); // or use any cache adapter you wish $database = new Database(new MariaDB($pdo), $cache); -$database->setNamespace('myscope'); +$database->setNamespace('mydb'); -$database->create(); // Creates a new schema named `myscope` +$database->create(); // Creates a new schema named `mydb` ``` **Creating a collection:** ```php -$database->createCollection('documents'); +$database->createCollection('movies'); // Add attributes -$database->createAttribute('documents', 'string', Database::VAR_STRING, 128); -$database->createAttribute('documents', 'integer', Database::VAR_INTEGER, 0); -$database->createAttribute('documents', 'float', Database::VAR_FLOAT, 0); -$database->createAttribute('documents', 'boolean', Database::VAR_BOOLEAN, 0); -$database->createAttribute('documents', 'colors', Database::VAR_STRING, 32, true, true); +$database->createAttribute('movies', 'name', Database::VAR_STRING, 128, true); +$database->createAttribute('movies', 'director', Database::VAR_STRING, 128, true); +$database->createAttribute('movies', 'year', Database::VAR_INTEGER, 0, true); +$database->createAttribute('movies', 'price', Database::VAR_FLOAT, 0, true); +$database->createAttribute('movies', 'active', Database::VAR_BOOLEAN, 0, true); +$database->createAttribute('movies', 'generes', Database::VAR_STRING, 32, true, true, true); // Create an Index -$database->createIndex('indexes', 'index1', Database::INDEX_KEY, ['string', 'integer'], [128], [Database::ORDER_ASC]); +$database->createIndex('movies', 'index1', Database::INDEX_KEY, ['year'], [128], [Database::ORDER_ASC]); ``` **Create a document:** ```php -$document = $database->createDocument('documents', new Document([ +static::getDatabase()->createDocument('movies', new Document([ '$read' => ['*', 'user1', 'user2'], - '$write' => ['*', 'user1', 'user2'], - 'string' => 'text📝', - 'integer' => 5, - 'float' => 5.55, - 'boolean' => true, - 'colors' => ['pink', 'green', 'blue'], + '$write' => ['*', 'user1x', 'user2x'], + 'name' => 'Captain Marvel', + 'director' => 'Anna Boden & Ryan Fleck', + 'year' => 2019, + 'price' => 25.99, + 'active' => true, + 'generes' => ['science fiction', 'action', 'comics'], ])); ``` +**Find:** + +```php +$documents = static::getDatabase()->find('movies', [ + new Query('year', Query::TYPE_EQUAL, [2019]), +]); +``` + ### Adapters Below is a list of supported adapters, and thier compatibly tested versions alongside a list of supported features and relevant limits. @@ -132,13 +142,13 @@ Below is a list of supported adapters, and thier compatibly tested versions alon - [ ] CRUD: Add format validation on top of type validation - [ ] CRUD: Validate original document before editing `$id` - [ ] CRUD: Test no one can overwrite exciting documents/collections without permission -- [ ] CRUD: Add cache layer (Delete / Update documents before cleaning cache) - [ ] FIND: Implement or conditions for the find method - [ ] FIND: Implement or conditions with floats - [ ] FIND: Test for find timeout limits - [ ] FIND: Limit queries to indexed attaributes only? - [ ] FIND: Add a query validator - [ ] FIND: Add support for more operators (>,>=,<,<=,search/match/like) +- [ ] TEST: Missing Collection, DocumentId validators tests ## System Requirements From 7a903b54f5be8654b23ac991b49aee929e0ddb90 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 30 Apr 2021 23:27:21 +0300 Subject: [PATCH 150/190] Added support for 'OR' conditions and new operators --- src/Database/Adapter/MariaDB.php | 54 +++++++++++++++++++------------- src/Database/Query.php | 4 +++ 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 0081bf11c..53bf0a401 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -107,18 +107,6 @@ public function createCollection(string $id): bool ->execute(); } - /** - * List Collections - * - * @return array - */ - public function listCollections(): array - { - $list = []; - - return $list; - } - /** * Delete Collection * @@ -493,21 +481,29 @@ public function find(string $collection, array $queries = [], $limit = 25, $offs $role = $this->getPDO()->quote($role, PDO::PARAM_STR); } - foreach($queries as $query) { - $where[] = 'table_main.'.$query->getAttribute().$this->getSQLOperator($query->getOperator()).':attribute_'.$query->getAttribute(); // Using `attrubute_` to avoid conflicts with custom names + $permissions = (Authorization::$status) ? "INNER JOIN {$this->getNamespace()}.{$name}_permissions as table_permissions + ON table_main._uid = table_permissions._uid + AND table_permissions._action = 'read' AND table_permissions._role IN (".implode(',', $roles).")" : ''; + + foreach($queries as $i => $query) { + $conditions = []; + foreach ($query->getValues() as $key => $value) { + $conditions[] = 'table_main.'.$query->getAttribute().' '.$this->getSQLOperator($query->getOperator()).' :attribute_'.$i.'_'.$key.'_'.$query->getAttribute(); // Using `attrubute_` to avoid conflicts with custom names + } + + $where[] = implode(' OR ', $conditions); } $stmt = $this->getPDO()->prepare("SELECT table_main.* FROM {$this->getNamespace()}.{$name} table_main - INNER JOIN {$this->getNamespace()}.{$name}_permissions as table_permissions - ON table_main._uid = table_permissions._uid - AND table_permissions._action = 'read' - AND table_permissions._role IN (".implode(',', $roles).") + {$permissions} WHERE ".implode(' AND ', $where)." LIMIT :offset, :limit; "); - - foreach($queries as $query) { - $stmt->bindValue(':attribute_'.$query->getAttribute(), $query->getValues()[0], $this->getPDOType($query->getValues()[0])); + + foreach($queries as $i => $query) { + foreach($query->getValues() as $key => $value) { + $stmt->bindValue(':attribute_'.$i.'_'.$key.'_'.$query->getAttribute(), $value, $this->getPDOType($value)); + } } $stmt->bindValue(':limit', $limit, PDO::PARAM_INT); @@ -643,6 +639,22 @@ protected function getSQLOperator(string $operator): string return '!='; break; + case Query::TYPE_LESSER: + return '<'; + break; + + case Query::TYPE_LESSEREQUAL: + return '<='; + break; + + case Query::TYPE_GREATER: + return '>'; + break; + + case Query::TYPE_GREATEREQUAL: + return '>='; + break; + default: throw new Exception('Unknown Operator:' . $operator); break; diff --git a/src/Database/Query.php b/src/Database/Query.php index 3a3beed54..163347054 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -6,6 +6,10 @@ class Query { const TYPE_EQUAL = 'equal'; const TYPE_NOTEQUAL = 'notEqual'; + const TYPE_LESSER = 'lesser'; + const TYPE_LESSEREQUAL = 'lesserEqual'; + const TYPE_GREATER = 'greater'; + const TYPE_GREATEREQUAL = 'greaterEqual'; /** * @var string From d0b6220adfb9940ea49de93611ca5451dd2de06f Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 30 Apr 2021 23:27:36 +0300 Subject: [PATCH 151/190] Added support for list collections --- README.md | 18 ++++++++++++------ src/Database/Adapter.php | 7 ------- src/Database/Database.php | 14 +++++++++++--- tests/Database/Base.php | 27 +++++++++++++++++++++------ 4 files changed, 44 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 11f934c28..cc507cee0 100644 --- a/README.md +++ b/README.md @@ -138,18 +138,24 @@ Below is a list of supported adapters, and thier compatibly tested versions alon ## TODOS - [ ] CRUD: Updated databases list method -- [ ] CRUD: Updated collection list method +- [x] CRUD: Updated collection list method - [ ] CRUD: Add format validation on top of type validation - [ ] CRUD: Validate original document before editing `$id` - [ ] CRUD: Test no one can overwrite exciting documents/collections without permission -- [ ] FIND: Implement or conditions for the find method -- [ ] FIND: Implement or conditions with floats +- [x] FIND: Implement or conditions for the find method +- [x] FIND: Implement or conditions with floats - [ ] FIND: Test for find timeout limits -- [ ] FIND: Limit queries to indexed attaributes only? -- [ ] FIND: Add a query validator -- [ ] FIND: Add support for more operators (>,>=,<,<=,search/match/like) +- [ ] FIND: Add a query validator (Limit queries to indexed attaributes only?) +- [ ] FIND: Add support for more operators (search/match/like) +- [ ] TEST: Find Limit & Offset values +- [ ] TEST: Find Order by (+multiple attributes) - [ ] TEST: Missing Collection, DocumentId validators tests +## Open Issues + +- Lazy index creation, maybe add a queue attribute to populate before creating the index? +- In queries for arrays, should we create a dedicated index? + ## System Requirements Utopia Framework requires PHP 7.3 or later. We recommend using the latest PHP version whenever possible. diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index 79cec5b84..9ca385e95 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -117,13 +117,6 @@ abstract public function delete(): bool; */ abstract public function createCollection(string $name): bool; - /** - * List Collections - * - * @return array - */ - abstract public function listCollections(): array; - /** * Delete Collection * diff --git a/src/Database/Database.php b/src/Database/Database.php index e2e628a1d..fd7706cdc 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -235,12 +235,20 @@ public function getCollection(string $id): Document /** * List Collections * + * @param int $offset + * @param int $limit + * * @return array */ - public function listCollections(): array + public function listCollections($limit = 25, $offset = 0): array { - // TODO add a search here - return []; + Authorization::disable(); + + $result = $this->find(self::COLLECTIONS, [], $limit, $offset); + + Authorization::reset(); + + return $result; } /** diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 47c2ecb5f..1e4ee98cf 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -37,9 +37,12 @@ public function testCreateDelete() /** * @depends testCreateDelete */ - public function testCreateDeleteCollection() + public function testCreateListDeleteCollection() { $this->assertInstanceOf('Utopia\Database\Document', static::getDatabase()->createCollection('actors')); + + $this->assertCount(1, static::getDatabase()->listCollections()); + $this->assertEquals(false, static::getDatabase()->getCollection('actors')->isEmpty()); $this->assertEquals(true, static::getDatabase()->deleteCollection('actors')); $this->assertEquals(true, static::getDatabase()->getCollection('actors')->isEmpty()); @@ -348,12 +351,13 @@ public function testFind(Document $document) /** * Float condition */ - // $documents = static::getDatabase()->find('movies', [ - // new Query('price', Query::TYPE_EQUAL, [25.99]), - // ]); + $documents = static::getDatabase()->find('movies', [ + new Query('price', Query::TYPE_LESSER, [26.00]), + new Query('price', Query::TYPE_GREATER, [25.98]), + ]); - // $this->assertEquals(1, count($documents)); - // $this->assertEquals('Captain Marvel', $documents[0]['name']); + $this->assertEquals(1, count($documents)); + $this->assertEquals('Captain Marvel', $documents[0]['name']); /** * Multiple conditions @@ -364,6 +368,17 @@ public function testFind(Document $document) ]); $this->assertEquals(1, count($documents)); + + /** + * Multiple conditions and OR values + */ + $documents = static::getDatabase()->find('movies', [ + new Query('name', Query::TYPE_EQUAL, ['Frozen II', 'Captain Marvel']), + ]); + + $this->assertEquals(2, count($documents)); + $this->assertEquals('Frozen II', $documents[0]['name']); + $this->assertEquals('Captain Marvel', $documents[1]['name']); } public function testFindFirst() From aacf09cad6b67f51252e9af17dcb682cda06ae01 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 1 May 2021 09:07:24 +0300 Subject: [PATCH 152/190] Added state managment methods --- src/Database/Adapter/MariaDB.php | 6 +- src/Database/Database.php | 158 ++++++++++++++++++++- src/Database/Validator/Structure.php | 14 +- tests/Database/Base.php | 76 ++++++++++ tests/Database/Validator/StructureTest.php | 6 + 5 files changed, 247 insertions(+), 13 deletions(-) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 53bf0a401..d9e670718 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -88,7 +88,7 @@ public function createCollection(string $id): bool $this->getPDO() ->prepare("CREATE TABLE IF NOT EXISTS {$this->getNamespace()}.{$id}_permissions ( `_id` int(11) unsigned NOT NULL AUTO_INCREMENT, - `_uid` CHAR(13) NOT NULL, + `_uid` CHAR(255) NOT NULL, `_action` CHAR(128) NOT NULL, `_role` CHAR(128) NOT NULL, PRIMARY KEY (`_id`), @@ -100,7 +100,7 @@ public function createCollection(string $id): bool return $this->getPDO() ->prepare("CREATE TABLE IF NOT EXISTS {$this->getNamespace()}.{$id} ( `_id` int(11) unsigned NOT NULL AUTO_INCREMENT, - `_uid` CHAR(13) NOT NULL, + `_uid` CHAR(255) NOT NULL, PRIMARY KEY (`_id`), UNIQUE KEY `_index1` (`_uid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;") @@ -612,7 +612,7 @@ protected function getSQLType(string $type, int $size, bool $signed = true): str break; case Database::VAR_DOCUMENT: - return 'CHAR(13)'; + return 'CHAR(255)'; break; default: diff --git a/src/Database/Database.php b/src/Database/Database.php index fd7706cdc..54c1f46b4 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -88,8 +88,28 @@ class Database 'array' => true, 'filters' => ['json'], ], + [ + '$id' => 'attributesInQueue', + 'type' => self::VAR_STRING, + 'size' => 1000000, + 'required' => false, + 'signed' => true, + 'array' => true, + 'filters' => ['json'], + ], + [ + '$id' => 'indexesInQueue', + 'type' => self::VAR_STRING, + 'size' => 1000000, + 'required' => false, + 'signed' => true, + 'array' => true, + 'filters' => ['json'], + ], ], 'indexes' => [], + 'attributesInQueue' => [], + 'indexesInQueue' => [], ]; /** @@ -166,9 +186,11 @@ public function create(): bool $this->adapter->create(); $this->createCollection(self::COLLECTIONS); - $this->createAttribute(self::COLLECTIONS, 'name', self::VAR_STRING, 128, true); - $this->createAttribute(self::COLLECTIONS, 'attributes', self::VAR_STRING, 8064, false); - $this->createAttribute(self::COLLECTIONS, 'indexes', self::VAR_STRING, 8064, false); + $this->createAttribute(self::COLLECTIONS, 'name', self::VAR_STRING, 512, true); + $this->createAttribute(self::COLLECTIONS, 'attributes', self::VAR_STRING, 1000000, false); + $this->createAttribute(self::COLLECTIONS, 'indexes', self::VAR_STRING, 1000000, false); + $this->createAttribute(self::COLLECTIONS, 'attributesInQueue', self::VAR_STRING, 1000000, false); + $this->createAttribute(self::COLLECTIONS, 'indexesInQueue', self::VAR_STRING, 1000000, false); $this->createIndex(self::COLLECTIONS, '_key_1', self::INDEX_UNIQUE, ['name']); return true; @@ -216,6 +238,8 @@ public function createCollection(string $id): Document 'name' => $id, 'attributes' => [], 'indexes' => [], + 'attributesInQueue' => [], + 'indexesInQueue' => [], ])); } @@ -350,6 +374,70 @@ public function deleteAttribute(string $collection, string $id): bool return $this->adapter->deleteAttribute($collection->getId(), $id); } + /** + * Add Attribute in Queue + * + * @param string $collection + * @param string $id + * @param string $type + * @param int $size utf8mb4 chars length + * @param bool $required + * @param bool $signed + * @param bool $array + * @param array $filters + * + * @return bool + */ + public function addAttributeInQueue(string $collection, string $id, string $type, int $size, bool $required, bool $signed = true, bool $array = false, array $filters = []): bool + { + $collection = $this->getCollection($collection); + + $collection->setAttribute('attributesInQueue', new Document([ + '$id' => $id, + 'type' => $type, + 'size' => $size, + 'required' => $required, + 'signed' => $signed, + 'array' => $array, + 'filters' => $filters, + ]), Document::SET_TYPE_APPEND); + + if($collection->getId() !== self::COLLECTIONS) { + $this->updateDocument(self::COLLECTIONS, $collection->getId(), $collection); + } + + return true; + } + + /** + * Remove Attribute in Queue + * + * @param string $collection + * @param string $id + * + * @return bool + */ + public function removeAttributeInQueue(string $collection, string $id): bool + { + $collection = $this->getCollection($collection); + + $attributes = $collection->getAttribute('attributesInQueue', []); + + foreach ($attributes as $key => $value) { + if(isset($value['$id']) && $value['$id'] === $id) { + unset($attributes[$key]); + } + } + + $collection->setAttribute('attributesInQueue', $attributes); + + if($collection->getId() !== self::COLLECTIONS) { + $this->updateDocument(self::COLLECTIONS, $collection->getId(), $collection); + } + + return true; + } + /** * Create Index * @@ -438,6 +526,70 @@ public function deleteIndex(string $collection, string $id): bool return $this->adapter->deleteIndex($collection->getId(), $id); } + /** + * Add Index in Queue + * + * @param string $collection + * @param string $id + * @param string $type + * @param array $attributes + * @param array $lengths + * @param array $orders + * + * @return bool + */ + public function addIndexInQueue(string $collection, string $id, string $type, array $attributes, array $lengths = [], array $orders = []): bool + { + if(empty($attributes)) { + throw new Exception('Missing attributes'); + } + + $collection = $this->getCollection($collection); + + $collection->setAttribute('indexesInQueue', new Document([ + '$id' => $id, + 'type' => $type, + 'attributes' => $attributes, + 'lengths' => $lengths, + 'orders' => $orders, + ]), Document::SET_TYPE_APPEND); + + if($collection->getId() !== self::COLLECTIONS) { + $this->updateDocument(self::COLLECTIONS, $collection->getId(), $collection); + } + + return true; + } + + /** + * Remove Index in Queue + * + * @param string $collection + * @param string $id + * + * @return bool + */ + public function removeIndexInQueue(string $collection, string $id): bool + { + $collection = $this->getCollection($collection); + + $indexes = $collection->getAttribute('indexesInQueue', []); + + foreach ($indexes as $key => $value) { + if(isset($value['$id']) && $value['$id'] === $id) { + unset($indexes[$key]); + } + } + + $collection->setAttribute('indexesInQueue', $indexes); + + if($collection->getId() !== self::COLLECTIONS) { + $this->updateDocument(self::COLLECTIONS, $collection->getId(), $collection); + } + + return true; + } + /** * Get Document * diff --git a/src/Database/Validator/Structure.php b/src/Database/Validator/Structure.php index c1a738c35..256604f25 100644 --- a/src/Database/Validator/Structure.php +++ b/src/Database/Validator/Structure.php @@ -62,7 +62,7 @@ class Structure extends Validator /** * @var array */ - static protected $validators = []; + static protected $formats = []; /** * @var string @@ -86,9 +86,9 @@ public function __construct(Document $collection) * * @return array */ - static public function getValidators(): array + static public function getFormats(): array { - return self::$validators; + return self::$formats; } /** @@ -98,9 +98,9 @@ static public function getValidators(): array * @param Validator $validator * @param string $type */ - static public function addValidator(string $name, Validator $validator, string $type): void + static public function addFormat(string $name, Validator $validator, string $type): void { - self::$validators[$name] = [ + self::$formats[$name] = [ 'validator' => $validator, 'type' => $type, ]; @@ -111,9 +111,9 @@ static public function addValidator(string $name, Validator $validator, string $ * * @param string $name */ - static public function removeValidator(string $name): void + static public function removeFormat(string $name): void { - unset(self::$validators[$name]); + unset(self::$formats[$name]); } /** diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 1e4ee98cf..59d3c69c0 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -96,6 +96,54 @@ public function testCreateDeleteAttribute() static::getDatabase()->deleteCollection('attributes'); } + public function testAddRemoveAttribute() + { + static::getDatabase()->createCollection('attributesInQueue'); + + $this->assertEquals(true, static::getDatabase()->addAttributeInQueue('attributesInQueue', 'string1', Database::VAR_STRING, 128, true)); + $this->assertEquals(true, static::getDatabase()->addAttributeInQueue('attributesInQueue', 'string2', Database::VAR_STRING, 16383+1, true)); + $this->assertEquals(true, static::getDatabase()->addAttributeInQueue('attributesInQueue', 'string3', Database::VAR_STRING, 65535+1, true)); + $this->assertEquals(true, static::getDatabase()->addAttributeInQueue('attributesInQueue', 'string4', Database::VAR_STRING, 16777215+1, true)); + $this->assertEquals(true, static::getDatabase()->addAttributeInQueue('attributesInQueue', 'integer', Database::VAR_INTEGER, 0, true)); + $this->assertEquals(true, static::getDatabase()->addAttributeInQueue('attributesInQueue', 'float', Database::VAR_FLOAT, 0, true)); + $this->assertEquals(true, static::getDatabase()->addAttributeInQueue('attributesInQueue', 'boolean', Database::VAR_BOOLEAN, 0, true)); + + $collection = static::getDatabase()->getCollection('attributesInQueue'); + $this->assertCount(7, $collection->getAttribute('attributesInQueue')); + + // Array + $this->assertEquals(true, static::getDatabase()->addAttributeInQueue('attributesInQueue', 'string_list', Database::VAR_STRING, 128, true, true)); + $this->assertEquals(true, static::getDatabase()->addAttributeInQueue('attributesInQueue', 'integer_list', Database::VAR_INTEGER, 0, true, true)); + $this->assertEquals(true, static::getDatabase()->addAttributeInQueue('attributesInQueue', 'float_list', Database::VAR_FLOAT, 0, true, true)); + $this->assertEquals(true, static::getDatabase()->addAttributeInQueue('attributesInQueue', 'boolean_list', Database::VAR_BOOLEAN, 0, true, true)); + + $collection = static::getDatabase()->getCollection('attributesInQueue'); + $this->assertCount(11, $collection->getAttribute('attributesInQueue')); + + // Delete + $this->assertEquals(true, static::getDatabase()->removeAttributeInQueue('attributesInQueue', 'string1')); + $this->assertEquals(true, static::getDatabase()->removeAttributeInQueue('attributesInQueue', 'string2')); + $this->assertEquals(true, static::getDatabase()->removeAttributeInQueue('attributesInQueue', 'string3')); + $this->assertEquals(true, static::getDatabase()->removeAttributeInQueue('attributesInQueue', 'string4')); + $this->assertEquals(true, static::getDatabase()->removeAttributeInQueue('attributesInQueue', 'integer')); + $this->assertEquals(true, static::getDatabase()->removeAttributeInQueue('attributesInQueue', 'float')); + $this->assertEquals(true, static::getDatabase()->removeAttributeInQueue('attributesInQueue', 'boolean')); + + $collection = static::getDatabase()->getCollection('attributesInQueue'); + $this->assertCount(4, $collection->getAttribute('attributesInQueue')); + + // Delete Array + $this->assertEquals(true, static::getDatabase()->removeAttributeInQueue('attributesInQueue', 'string_list')); + $this->assertEquals(true, static::getDatabase()->removeAttributeInQueue('attributesInQueue', 'integer_list')); + $this->assertEquals(true, static::getDatabase()->removeAttributeInQueue('attributesInQueue', 'float_list')); + $this->assertEquals(true, static::getDatabase()->removeAttributeInQueue('attributesInQueue', 'boolean_list')); + + $collection = static::getDatabase()->getCollection('attributesInQueue'); + $this->assertCount(0, $collection->getAttribute('attributesInQueue')); + + static::getDatabase()->deleteCollection('attributesInQueue'); + } + public function testCreateDeleteIndex() { static::getDatabase()->createCollection('indexes'); @@ -124,6 +172,34 @@ public function testCreateDeleteIndex() static::getDatabase()->deleteCollection('indexes'); } + public function testAddRemoveIndexInQueue() + { + static::getDatabase()->createCollection('indexesInQueue'); + + $this->assertEquals(true, static::getDatabase()->createAttribute('indexesInQueue', 'string', Database::VAR_STRING, 128, true)); + $this->assertEquals(true, static::getDatabase()->createAttribute('indexesInQueue', 'integer', Database::VAR_INTEGER, 0, true)); + $this->assertEquals(true, static::getDatabase()->createAttribute('indexesInQueue', 'float', Database::VAR_FLOAT, 0, true)); + $this->assertEquals(true, static::getDatabase()->createAttribute('indexesInQueue', 'boolean', Database::VAR_BOOLEAN, 0, true)); + + // Indexes + $this->assertEquals(true, static::getDatabase()->addIndexInQueue('indexesInQueue', 'index1', Database::INDEX_KEY, ['string', 'integer'], [128], [Database::ORDER_ASC])); + $this->assertEquals(true, static::getDatabase()->addIndexInQueue('indexesInQueue', 'index2', Database::INDEX_KEY, ['float', 'integer'], [], [Database::ORDER_ASC, Database::ORDER_DESC])); + $this->assertEquals(true, static::getDatabase()->addIndexInQueue('indexesInQueue', 'index3', Database::INDEX_KEY, ['integer', 'boolean'], [], [Database::ORDER_ASC, Database::ORDER_DESC, Database::ORDER_DESC])); + + $collection = static::getDatabase()->getCollection('indexesInQueue'); + $this->assertCount(3, $collection->getAttribute('indexesInQueue')); + + // Delete Indexes + $this->assertEquals(true, static::getDatabase()->removeIndexInQueue('indexesInQueue', 'index1')); + $this->assertEquals(true, static::getDatabase()->removeIndexInQueue('indexesInQueue', 'index2')); + $this->assertEquals(true, static::getDatabase()->removeIndexInQueue('indexesInQueue', 'index3')); + + $collection = static::getDatabase()->getCollection('indexesInQueue'); + $this->assertCount(0, $collection->getAttribute('indexesInQueue')); + + static::getDatabase()->deleteCollection('indexesInQueue'); + } + public function testCreateDocument() { static::getDatabase()->createCollection('documents'); diff --git a/tests/Database/Validator/StructureTest.php b/tests/Database/Validator/StructureTest.php index da6d36511..974d7c7c6 100644 --- a/tests/Database/Validator/StructureTest.php +++ b/tests/Database/Validator/StructureTest.php @@ -20,6 +20,7 @@ class StructureTest extends TestCase [ '$id' => 'title', 'type' => Database::VAR_STRING, + 'format' => '', 'size' => 256, 'required' => true, 'signed' => true, @@ -29,6 +30,7 @@ class StructureTest extends TestCase [ '$id' => 'description', 'type' => Database::VAR_STRING, + 'format' => '', 'size' => 1000000, 'required' => true, 'signed' => true, @@ -38,6 +40,7 @@ class StructureTest extends TestCase [ '$id' => 'rating', 'type' => Database::VAR_INTEGER, + 'format' => '', 'size' => 5, 'required' => true, 'signed' => true, @@ -47,6 +50,7 @@ class StructureTest extends TestCase [ '$id' => 'price', 'type' => Database::VAR_FLOAT, + 'format' => '', 'size' => 5, 'required' => true, 'signed' => true, @@ -56,6 +60,7 @@ class StructureTest extends TestCase [ '$id' => 'published', 'type' => Database::VAR_BOOLEAN, + 'format' => '', 'size' => 5, 'required' => true, 'signed' => true, @@ -65,6 +70,7 @@ class StructureTest extends TestCase [ '$id' => 'tags', 'type' => Database::VAR_STRING, + 'format' => '', 'size' => 55, 'required' => true, 'signed' => true, From 48d414440d54fc26f5d67001110a2eb944aa072c Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 1 May 2021 09:12:20 +0300 Subject: [PATCH 153/190] Fixed typo --- tests/Database/Validator/StructureTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Database/Validator/StructureTest.php b/tests/Database/Validator/StructureTest.php index 974d7c7c6..7d5f4cee0 100644 --- a/tests/Database/Validator/StructureTest.php +++ b/tests/Database/Validator/StructureTest.php @@ -127,7 +127,7 @@ public function testCollection() $this->assertEquals('Invalid document structure: Collection "" not found', $validator->getDescription()); } - public function testReuiredKeys() + public function testRequiredKeys() { $validator = new Structure(new Document($this->collection)); From 5f0a80e780337359dfa97bfd8c9fc9d52527d348 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 1 May 2021 13:33:52 +0300 Subject: [PATCH 154/190] Work in progress: limits section --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index cc507cee0..316271b90 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,15 @@ Below is a list of supported adapters, and thier compatibly tested versions alon - Lazy index creation, maybe add a queue attribute to populate before creating the index? - In queries for arrays, should we create a dedicated index? +## Limitations (to be completed per adapter) + +- ID max size can be 255 bytes +- ID can only contain [^A-Za-z0-9] +- Document max size is x bytes +- Collection can have a max of x attributes +- Collection can have a max of x indexes +- Index value max size is x bytes. Values over x bytes are truncated + ## System Requirements Utopia Framework requires PHP 7.3 or later. We recommend using the latest PHP version whenever possible. From 5000c9c4065ec97fd20de1a9e996051fe55113d1 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 1 May 2021 13:54:27 +0300 Subject: [PATCH 155/190] Updated TODO list --- src/Database/Adapter/MariaDB.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index d9e670718..410cc7dff 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -483,7 +483,7 @@ public function find(string $collection, array $queries = [], $limit = 25, $offs $permissions = (Authorization::$status) ? "INNER JOIN {$this->getNamespace()}.{$name}_permissions as table_permissions ON table_main._uid = table_permissions._uid - AND table_permissions._action = 'read' AND table_permissions._role IN (".implode(',', $roles).")" : ''; + AND table_permissions._action = 'read' AND table_permissions._role IN (".implode(',', $roles).")" : ''; // Disable join when no authorization required foreach($queries as $i => $query) { $conditions = []; @@ -497,6 +497,7 @@ public function find(string $collection, array $queries = [], $limit = 25, $offs $stmt = $this->getPDO()->prepare("SELECT table_main.* FROM {$this->getNamespace()}.{$name} table_main {$permissions} WHERE ".implode(' AND ', $where)." + ORDER BY LIMIT :offset, :limit; "); From 9f4353453b4b73215debf672497ca60f1e9e4c74 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 1 May 2021 17:25:03 +0300 Subject: [PATCH 156/190] Added support for find order attributes --- README.md | 8 +++----- src/Database/Adapter/MariaDB.php | 11 ++++++++++- src/Database/Database.php | 10 ++++++++++ tests/Database/Base.php | 26 ++++++++++++++++++++++++++ 4 files changed, 49 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 316271b90..4edacefe3 100644 --- a/README.md +++ b/README.md @@ -138,17 +138,15 @@ Below is a list of supported adapters, and thier compatibly tested versions alon ## TODOS - [ ] CRUD: Updated databases list method -- [x] CRUD: Updated collection list method -- [ ] CRUD: Add format validation on top of type validation +- [ ] * CRUD: Add format validation on top of type validation - [ ] CRUD: Validate original document before editing `$id` - [ ] CRUD: Test no one can overwrite exciting documents/collections without permission -- [x] FIND: Implement or conditions for the find method -- [x] FIND: Implement or conditions with floats - [ ] FIND: Test for find timeout limits - [ ] FIND: Add a query validator (Limit queries to indexed attaributes only?) - [ ] FIND: Add support for more operators (search/match/like) +- [ ] * FIND: Missing returned data for $read & $write permissions (denormalize data?) - [ ] TEST: Find Limit & Offset values -- [ ] TEST: Find Order by (+multiple attributes) +- [ ] * TEST: Find Order by (+multiple attributes) - [ ] TEST: Missing Collection, DocumentId validators tests ## Open Issues diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 410cc7dff..cbc19b2e4 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -476,11 +476,18 @@ public function find(string $collection, array $queries = [], $limit = 25, $offs $name = $this->filter($collection); $roles = Authorization::getRoles(); $where = ['1=1']; + $orders = []; foreach($roles as &$role) { $role = $this->getPDO()->quote($role, PDO::PARAM_STR); } + foreach($orderAttributes as $i => $attribute) { + $attribute = $this->filter($attribute); + $orderType = $this->filter($orderTypes[$i] ?? Database::ORDER_ASC); + $orders[] = $attribute.' '.$orderType; + } + $permissions = (Authorization::$status) ? "INNER JOIN {$this->getNamespace()}.{$name}_permissions as table_permissions ON table_main._uid = table_permissions._uid AND table_permissions._action = 'read' AND table_permissions._role IN (".implode(',', $roles).")" : ''; // Disable join when no authorization required @@ -494,10 +501,12 @@ public function find(string $collection, array $queries = [], $limit = 25, $offs $where[] = implode(' OR ', $conditions); } + $order = (!empty($orders)) ? 'ORDER BY '.implode(', ', $orders) : ''; + $stmt = $this->getPDO()->prepare("SELECT table_main.* FROM {$this->getNamespace()}.{$name} table_main {$permissions} WHERE ".implode(' AND ', $where)." - ORDER BY + {$order} LIMIT :offset, :limit; "); diff --git a/src/Database/Database.php b/src/Database/Database.php index 54c1f46b4..e4cbd831e 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -50,6 +50,16 @@ class Database */ protected $cache; + /** + * @var array + */ + protected $primitives = [ + self::VAR_STRING => true, + self::VAR_INTEGER => true, + self::VAR_FLOAT => true, + self::VAR_BOOLEAN => true, + ]; + /** * Parent Collection * Defines the structure for both system and custom collections diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 59d3c69c0..7ca1a7799 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -455,6 +455,32 @@ public function testFind(Document $document) $this->assertEquals(2, count($documents)); $this->assertEquals('Frozen II', $documents[0]['name']); $this->assertEquals('Captain Marvel', $documents[1]['name']); + + /** + * ORDER BY + */ + $documents = static::getDatabase()->find('movies', [], 25, 0, ['price'], [Database::ORDER_DESC]); + + $this->assertEquals(6, count($documents)); + $this->assertEquals('Frozen', $documents[0]['name']); + $this->assertEquals('Frozen II', $documents[1]['name']); + $this->assertEquals('Captain Marvel', $documents[2]['name']); + $this->assertEquals('Captain America: The First Avenger', $documents[3]['name']); + $this->assertEquals('Work in Progress', $documents[4]['name']); + $this->assertEquals('Work in Progress 2', $documents[5]['name']); + + /** + * ORDER BY - Multiple attributes + */ + $documents = static::getDatabase()->find('movies', [], 25, 0, ['price', 'name'], [Database::ORDER_DESC, Database::ORDER_DESC]); + + $this->assertEquals(6, count($documents)); + $this->assertEquals('Frozen II', $documents[0]['name']); + $this->assertEquals('Frozen', $documents[1]['name']); + $this->assertEquals('Captain Marvel', $documents[2]['name']); + $this->assertEquals('Captain America: The First Avenger', $documents[3]['name']); + $this->assertEquals('Work in Progress 2', $documents[4]['name']); + $this->assertEquals('Work in Progress', $documents[5]['name']); } public function testFindFirst() From bd9756f048c014bc1199328b6395e836e2b74099 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 1 May 2021 17:28:35 +0300 Subject: [PATCH 157/190] Added tests for limit + offset options --- tests/Database/Base.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 7ca1a7799..57fe5d411 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -481,6 +481,28 @@ public function testFind(Document $document) $this->assertEquals('Captain America: The First Avenger', $documents[3]['name']); $this->assertEquals('Work in Progress 2', $documents[4]['name']); $this->assertEquals('Work in Progress', $documents[5]['name']); + + /** + * Limit + */ + $documents = static::getDatabase()->find('movies', [], 4, 0); + + $this->assertEquals(4, count($documents)); + $this->assertEquals('Frozen', $documents[0]['name']); + $this->assertEquals('Frozen II', $documents[1]['name']); + $this->assertEquals('Captain America: The First Avenger', $documents[2]['name']); + $this->assertEquals('Captain Marvel', $documents[3]['name']); + + /** + * Limit + Offset + */ + $documents = static::getDatabase()->find('movies', [], 4, 2); + + $this->assertEquals(4, count($documents)); + $this->assertEquals('Captain America: The First Avenger', $documents[0]['name']); + $this->assertEquals('Captain Marvel', $documents[1]['name']); + $this->assertEquals('Work in Progress', $documents[2]['name']); + $this->assertEquals('Work in Progress 2', $documents[3]['name']); } public function testFindFirst() From 78db6a0ccd8893dd1d13ab770afd6f0bab91a726 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 1 May 2021 17:32:37 +0300 Subject: [PATCH 158/190] Updated TODO list --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 4edacefe3..a80421825 100644 --- a/README.md +++ b/README.md @@ -145,8 +145,6 @@ Below is a list of supported adapters, and thier compatibly tested versions alon - [ ] FIND: Add a query validator (Limit queries to indexed attaributes only?) - [ ] FIND: Add support for more operators (search/match/like) - [ ] * FIND: Missing returned data for $read & $write permissions (denormalize data?) -- [ ] TEST: Find Limit & Offset values -- [ ] * TEST: Find Order by (+multiple attributes) - [ ] TEST: Missing Collection, DocumentId validators tests ## Open Issues From 2e2069149b2cfe83d1852c422b7766a9f86a202d Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 1 May 2021 20:05:25 +0300 Subject: [PATCH 159/190] Denormalized data for faster reads --- src/Database/Adapter/MariaDB.php | 37 +++++++++++++++----------------- tests/Database/Base.php | 14 ++++++++++++ 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index cbc19b2e4..e0168c9a9 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -101,6 +101,7 @@ public function createCollection(string $id): bool ->prepare("CREATE TABLE IF NOT EXISTS {$this->getNamespace()}.{$id} ( `_id` int(11) unsigned NOT NULL AUTO_INCREMENT, `_uid` CHAR(255) NOT NULL, + `_permissions` TEXT NOT NULL, PRIMARY KEY (`_id`), UNIQUE KEY `_index1` (`_uid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;") @@ -246,27 +247,13 @@ public function getDocument(string $collection, string $id): Document $document = $stmt->fetch(); + $permissions = (isset($document['_permissions'])) ? json_decode($document['_permissions'], true) : []; $document['$id'] = $document['_uid']; - $document['$read'] = []; - $document['$write'] = []; + $document['$read'] = $permissions[Database::PERMISSION_READ] ?? []; + $document['$write'] = $permissions[Database::PERMISSION_WRITE] ?? []; unset($document['_id']); unset($document['_uid']); - - $stmt = $this->getPDO()->prepare("SELECT * FROM {$this->getNamespace()}.{$name}_permissions - WHERE _uid = :_uid - LIMIT 100; - "); - - $stmt->bindValue(':_uid', $id, PDO::PARAM_STR); - $stmt->execute(); - - $permissions = $stmt->fetchAll(); - - foreach ($permissions as $permission) { - $action = $permission['_action'] ?? ''; - $role = $permission['_role'] ?? ''; - $document['$'.$action][] = $role; - } + unset($document['_permissions']); return new Document($document); } @@ -295,9 +282,10 @@ public function createDocument(string $collection, Document $document): Document $stmt = $this->getPDO() ->prepare("INSERT INTO {$this->getNamespace()}.{$name} - SET {$columns} _uid = :_uid"); + SET {$columns} _uid = :_uid, _permissions = :_permissions"); $stmt->bindValue(':_uid', $document->getId(), PDO::PARAM_STR); + $stmt->bindValue(':_permissions', json_encode([Database::PERMISSION_READ => $document->getRead(), Database::PERMISSION_WRITE => $document->getWrite()]), PDO::PARAM_STR); foreach ($attributes as $attribute => $value) { if(is_array($value)) { // arrays & objects should be saved as strings @@ -371,9 +359,10 @@ public function updateDocument(string $collection, Document $document): Document $stmt = $this->getPDO() ->prepare("UPDATE {$this->getNamespace()}.{$name} - SET {$columns} _uid = :_uid WHERE _uid = :_uid"); + SET {$columns} _uid = :_uid, _permissions = :_permissions WHERE _uid = :_uid"); $stmt->bindValue(':_uid', $document->getId(), PDO::PARAM_STR); + $stmt->bindValue(':_permissions', json_encode([Database::PERMISSION_READ => $document->getRead(), Database::PERMISSION_WRITE => $document->getWrite()]), PDO::PARAM_STR); foreach ($attributes as $attribute => $value) { if(is_array($value)) { // arrays & objects should be saved as strings @@ -523,6 +512,14 @@ public function find(string $collection, array $queries = [], $limit = 25, $offs $results = $stmt->fetchAll(); foreach ($results as &$value) { + $permissions = (isset($value['_permissions'])) ? json_decode($value['_permissions'], true) : []; + $value['$id'] = $value['_uid']; + $value['$read'] = $permissions[Database::PERMISSION_READ] ?? []; + $value['$write'] = $permissions[Database::PERMISSION_WRITE] ?? []; + unset($value['_id']); + unset($value['_uid']); + unset($value['_permissions']); + $value = new Document($value); } diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 57fe5d411..7aa7d46d9 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -385,6 +385,20 @@ public function testFind(Document $document) $documents = static::getDatabase()->find('movies'); $this->assertEquals(5, count($documents)); + $this->assertNotEmpty($documents[0]->getId()); + $this->assertEquals(['*', 'user1', 'user2'], $documents[0]->getRead()); + $this->assertEquals(['*', 'user1x', 'user2x'], $documents[0]->getWrite()); + $this->assertEquals('Frozen', $documents[0]->getAttribute('name')); + $this->assertEquals('Chris Buck & Jennifer Lee', $documents[0]->getAttribute('director')); + $this->assertIsString($documents[0]->getAttribute('director')); + $this->assertEquals(2013, $documents[0]->getAttribute('year')); + $this->assertIsInt($documents[0]->getAttribute('year')); + $this->assertEquals(39.50, $documents[0]->getAttribute('price')); + $this->assertIsFloat($documents[0]->getAttribute('price')); + $this->assertEquals(true, $documents[0]->getAttribute('active')); + $this->assertIsBool($documents[0]->getAttribute('active')); + $this->assertEquals(['animation', 'kids'], $documents[0]->getAttribute('generes')); + $this->assertIsArray($documents[0]->getAttribute('generes')); /** * Check Permissions From 4d7c15c17f160f3798fbbc4225ac90908298d67c Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 1 May 2021 23:25:27 +0300 Subject: [PATCH 160/190] Added swoole setup --- Dockerfile | 13 ++++++++++++- README.md | 1 - 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 44df6dba4..d1e970f35 100755 --- a/Dockerfile +++ b/Dockerfile @@ -15,18 +15,29 @@ FROM php:7.4-cli-alpine as final LABEL maintainer="team@appwrite.io" +ENV PHP_SWOOLE_VERSION=v4.6.6 + RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone RUN \ apk update \ - && apk add --no-cache postgresql-libs postgresql-dev make automake autoconf gcc g++ \ + && apk add --no-cache postgresql-libs postgresql-dev make automake autoconf gcc g++ git brotli-dev \ && pecl install mongodb redis \ && docker-php-ext-enable mongodb redis \ && docker-php-ext-install opcache pgsql pdo_mysql pdo_pgsql \ + ## Swoole Extension + && git clone --depth 1 --branch $PHP_SWOOLE_VERSION https://github.com/swoole/swoole-src.git \ + && cd swoole-src \ + && phpize \ + && ./configure --enable-http2 \ + && make && make install \ + && cd .. \ && rm -rf /var/cache/apk/* WORKDIR /usr/src/code +RUN echo extension=swoole.so >> /usr/local/etc/php/conf.d/swoole.ini + COPY --from=step0 /usr/local/src/vendor /usr/src/code/vendor # Add Source Code diff --git a/README.md b/README.md index a80421825..e72beeec0 100644 --- a/README.md +++ b/README.md @@ -144,7 +144,6 @@ Below is a list of supported adapters, and thier compatibly tested versions alon - [ ] FIND: Test for find timeout limits - [ ] FIND: Add a query validator (Limit queries to indexed attaributes only?) - [ ] FIND: Add support for more operators (search/match/like) -- [ ] * FIND: Missing returned data for $read & $write permissions (denormalize data?) - [ ] TEST: Missing Collection, DocumentId validators tests ## Open Issues From 888ecb21fe1e46a2a3991e4be10bcd6c1f40ec68 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 1 May 2021 23:52:17 +0300 Subject: [PATCH 161/190] Added format layer for structure validation --- src/Database/Validator/Structure.php | 32 ++++++++++++++- tests/Database/Format.php | 45 ++++++++++++++++++++++ tests/Database/Validator/StructureTest.php | 45 +++++++++++++++++++++- 3 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 tests/Database/Format.php diff --git a/src/Database/Validator/Structure.php b/src/Database/Validator/Structure.php index 256604f25..76aed4bab 100644 --- a/src/Database/Validator/Structure.php +++ b/src/Database/Validator/Structure.php @@ -2,6 +2,7 @@ namespace Utopia\Database\Validator; +use Exception; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Validator; @@ -106,6 +107,26 @@ static public function addFormat(string $name, Validator $validator, string $typ ]; } + /** + * Get a Validator + * + * @param string $name + * + * @return Validator + */ + static public function getFormat(string $name, string $type): Validator + { + if(isset(self::$formats[$name])) { + if(self::$formats[$name]['type'] !== $type) { + throw new Exception('Format ("'.$name.'") not available for this attribute type ("'.$type.'")'); + } + + return self::$formats[$name]['validator']; + } + + throw new Exception('Unknown format validator: "'.$name.'"'); + } + /** * Remove a Validator * @@ -179,6 +200,7 @@ public function isValid($document) $attribute = $keys[$key] ?? []; $type = $attribute['type'] ?? ''; $array = $attribute['array'] ?? false; + $format = $attribute['format'] ?? ''; switch ($type) { case Database::VAR_STRING: @@ -224,8 +246,16 @@ public function isValid($document) } } + if($format) { + $validator = self::getFormat($format, $type); + + if(!$validator->isValid($value)) { + $this->message = 'Attribute "'.$key.'" has invalid format. '.$validator->getDescription(); + return false; + } + } + // TODO check for length / size - // TODO check for specific validation } return true; diff --git a/tests/Database/Format.php b/tests/Database/Format.php new file mode 100644 index 000000000..7b0899ba8 --- /dev/null +++ b/tests/Database/Format.php @@ -0,0 +1,45 @@ + true, 'filters' => [], ], + [ + '$id' => 'feedback', + 'type' => Database::VAR_STRING, + 'format' => 'email', + 'size' => 55, + 'required' => true, + 'signed' => true, + 'array' => false, + 'filters' => [], + ], ], 'indexes' => [], ]; public function setUp(): void { + Structure::addFormat('email', new Format(0), Database::VAR_STRING); } public function tearDown(): void @@ -122,6 +134,7 @@ public function testCollection() 'price' => 1.99, 'published' => true, 'tags' => ['dog', 'cat', 'mouse'], + 'feedback' => 'team@appwrite.io', ]))); $this->assertEquals('Invalid document structure: Collection "" not found', $validator->getDescription()); @@ -138,6 +151,7 @@ public function testRequiredKeys() 'price' => 1.99, 'published' => true, 'tags' => ['dog', 'cat', 'mouse'], + 'feedback' => 'team@appwrite.io', ]))); $this->assertEquals('Invalid document structure: Missing required attribute "title"', $validator->getDescription()); @@ -156,6 +170,7 @@ public function testUnknownKeys() 'price' => 1.99, 'published' => true, 'tags' => ['dog', 'cat', 'mouse'], + 'feedback' => 'team@appwrite.io', ]))); $this->assertEquals('Invalid document structure: Unknown attribute: ""titlex"', $validator->getDescription()); @@ -173,8 +188,8 @@ public function testValidDocument() 'price' => 1.99, 'published' => true, 'tags' => ['dog', 'cat', 'mouse'], + 'feedback' => 'team@appwrite.io', ]))); - } public function testStringValidation() @@ -189,6 +204,7 @@ public function testStringValidation() 'price' => 1.99, 'published' => true, 'tags' => ['dog', 'cat', 'mouse'], + 'feedback' => 'team@appwrite.io', ]))); $this->assertEquals('Invalid document structure: Attribute "title" has invalid type. Value must be a string', $validator->getDescription()); @@ -206,6 +222,7 @@ public function testArrayOfStringsValidation() 'price' => 1.99, 'published' => true, 'tags' => [1, 'cat', 'mouse'], + 'feedback' => 'team@appwrite.io', ]))); $this->assertEquals('Invalid document structure: Attribute "tags[\'0\']" has invalid type. Value must be a string', $validator->getDescription()); @@ -218,6 +235,7 @@ public function testArrayOfStringsValidation() 'price' => 1.99, 'published' => true, 'tags' => [true], + 'feedback' => 'team@appwrite.io', ]))); $this->assertEquals('Invalid document structure: Attribute "tags[\'0\']" has invalid type. Value must be a string', $validator->getDescription()); @@ -230,6 +248,7 @@ public function testArrayOfStringsValidation() 'price' => 1.99, 'published' => true, 'tags' => [], + 'feedback' => 'team@appwrite.io', ]))); } @@ -245,6 +264,7 @@ public function testIntegerValidation() 'price' => 1.99, 'published' => false, 'tags' => ['dog', 'cat', 'mouse'], + 'feedback' => 'team@appwrite.io', ]))); $this->assertEquals('Invalid document structure: Attribute "rating" has invalid type. Value must be a valid integer', $validator->getDescription()); @@ -257,6 +277,7 @@ public function testIntegerValidation() 'price' => 1.99, 'published' => false, 'tags' => ['dog', 'cat', 'mouse'], + 'feedback' => 'team@appwrite.io', ]))); $this->assertEquals('Invalid document structure: Attribute "rating" has invalid type. Value must be a valid integer', $validator->getDescription()); @@ -274,6 +295,7 @@ public function testFloatValidation() 'price' => 2, 'published' => false, 'tags' => ['dog', 'cat', 'mouse'], + 'feedback' => 'team@appwrite.io', ]))); $this->assertEquals('Invalid document structure: Attribute "price" has invalid type. Value must be a valid float', $validator->getDescription()); @@ -286,6 +308,7 @@ public function testFloatValidation() 'price' => '', 'published' => false, 'tags' => ['dog', 'cat', 'mouse'], + 'feedback' => 'team@appwrite.io', ]))); $this->assertEquals('Invalid document structure: Attribute "price" has invalid type. Value must be a valid float', $validator->getDescription()); @@ -303,6 +326,7 @@ public function testBooleanValidation() 'price' => 1.99, 'published' => 1, 'tags' => ['dog', 'cat', 'mouse'], + 'feedback' => 'team@appwrite.io', ]))); $this->assertEquals('Invalid document structure: Attribute "published" has invalid type. Value must be a boolean', $validator->getDescription()); @@ -315,8 +339,27 @@ public function testBooleanValidation() 'price' => 1.99, 'published' => '', 'tags' => ['dog', 'cat', 'mouse'], + 'feedback' => 'team@appwrite.io', ]))); $this->assertEquals('Invalid document structure: Attribute "published" has invalid type. Value must be a boolean', $validator->getDescription()); } + + public function testFormatValidation() + { + $validator = new Structure(new Document($this->collection)); + + $this->assertEquals(false, $validator->isValid(new Document([ + '$collection' => 'posts', + 'title' => 'string', + 'description' => 'Demo description', + 'rating' => 5, + 'price' => 1.99, + 'published' => true, + 'tags' => ['dog', 'cat', 'mouse'], + 'feedback' => 'team_appwrite.io', + ]))); + + $this->assertEquals('Invalid document structure: Attribute "feedback" has invalid format. Value must be a valid email address', $validator->getDescription()); + } } From 82ffc79b6ef188d268a7eb96b9c084a407dd3d78 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 1 May 2021 23:53:27 +0300 Subject: [PATCH 162/190] Update TODO list --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index e72beeec0..8b28fb77f 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,6 @@ Below is a list of supported adapters, and thier compatibly tested versions alon ## TODOS - [ ] CRUD: Updated databases list method -- [ ] * CRUD: Add format validation on top of type validation - [ ] CRUD: Validate original document before editing `$id` - [ ] CRUD: Test no one can overwrite exciting documents/collections without permission - [ ] FIND: Test for find timeout limits From 518265a14702f61a35b95d4ae25c8b23e7c2b478 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 2 May 2021 21:26:42 +0300 Subject: [PATCH 163/190] Added constant --- src/Database/Database.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Database/Database.php b/src/Database/Database.php index e4cbd831e..6342e2c71 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -37,6 +37,10 @@ class Database // Collections const COLLECTIONS = 'collections'; + // Lengths + + const LENGTH_KEY = 255; + // Cache const TTL = 60 * 60 * 24; // 24 hours From 2348065ef9e32719b50183e049d36b158283837c Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 3 May 2021 12:25:08 +0300 Subject: [PATCH 164/190] Added count method (with optional limit) --- .gitignore | 7 +++- src/Database/Adapter.php | 20 ++++++---- src/Database/Adapter/MariaDB.php | 63 +++++++++++++++++++++++++++++--- src/Database/Database.php | 35 +++++++++--------- tests/Database/Base.php | 38 +++++++++++-------- 5 files changed, 114 insertions(+), 49 deletions(-) diff --git a/.gitignore b/.gitignore index e244eda0b..162774c65 100755 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,7 @@ /vendor/ -/.idea/ \ No newline at end of file +/.idea/ +.DS_Store +mock.json +data-tests.php +loader.php +.phpunit.result.cache \ No newline at end of file diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index 9ca385e95..85655d759 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -227,14 +227,18 @@ abstract public function deleteDocument(string $collection, string $id): bool; * * @return Document[] */ - abstract public function find(string $collection, array $queries = [], $limit = 25, $offset = 0, $orderAttributes = [], $orderTypes = []): array; - - // /** - // * @param array $options - // * - // * @return int - // */ - // abstract public function count(array $options); + abstract public function find(string $collection, array $queries = [], int $limit = 25, int $offset = 0, array $orderAttributes = [], array $orderTypes = []): array; + + /** + * Count Documents + * + * @param string $collection + * @param Query[] $queries + * @param int $max + * + * @return int + */ + abstract public function count(string $collection, array $queries = [], int $max = 0): int; /** * Get max STRING limit diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index e0168c9a9..486cd6f83 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -19,11 +19,6 @@ class MariaDB extends Adapter */ protected $pdo; - /** - * @var bool - */ - protected $transaction = false; - /** * Constructor. * @@ -457,10 +452,11 @@ public function deleteDocument(string $collection, string $id): bool * @param int $offset * @param array $orderAttributes * @param array $orderTypes + * @param bool $count * * @return Document[] */ - public function find(string $collection, array $queries = [], $limit = 25, $offset = 0, $orderAttributes = [], $orderTypes = []): array + public function find(string $collection, array $queries = [], int $limit = 25, int $offset = 0, array $orderAttributes = [], array $orderTypes = []): array { $name = $this->filter($collection); $roles = Authorization::getRoles(); @@ -526,6 +522,61 @@ public function find(string $collection, array $queries = [], $limit = 25, $offs return $results; } + /** + * Cound Documents + * + * Count data set size using chosen queries + * + * @param string $collection + * @param \Utopia\Database\Query[] $queries + * @param int $max + * + * @return int + */ + public function count(string $collection, array $queries = [], int $max = 0): int + { + $name = $this->filter($collection); + $roles = Authorization::getRoles(); + $where = ['1=1']; + $limit = ($max === 0) ? '' : 'LIMIT :max;'; + + foreach($roles as &$role) { + $role = $this->getPDO()->quote($role, PDO::PARAM_STR); + } + + $permissions = (Authorization::$status) ? "INNER JOIN {$this->getNamespace()}.{$name}_permissions as table_permissions + ON table_main._uid = table_permissions._uid + AND table_permissions._action = 'read' AND table_permissions._role IN (".implode(',', $roles).")" : ''; // Disable join when no authorization required + + foreach($queries as $i => $query) { + $conditions = []; + foreach ($query->getValues() as $key => $value) { + $conditions[] = 'table_main.'.$query->getAttribute().' '.$this->getSQLOperator($query->getOperator()).' :attribute_'.$i.'_'.$key.'_'.$query->getAttribute(); // Using `attrubute_` to avoid conflicts with custom names + } + + $where[] = implode(' OR ', $conditions); + } + + $stmt = $this->getPDO()->prepare("SELECT COUNT(table_main.`_uid`) FROM {$this->getNamespace()}.{$name} table_main + {$permissions} + WHERE ".implode(' AND ', $where)." + {$limit} + "); + + foreach($queries as $i => $query) { + foreach($query->getValues() as $key => $value) { + $stmt->bindValue(':attribute_'.$i.'_'.$key.'_'.$query->getAttribute(), $value, $this->getPDOType($value)); + } + } + + $stmt->bindValue(':max', $max, PDO::PARAM_INT); + $stmt->execute(); + + $result = $stmt->fetch(); + + return $result['sum'] ?? 0; + } + /** * Get max STRING limit * diff --git a/src/Database/Database.php b/src/Database/Database.php index 6342e2c71..1daeebff1 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -38,7 +38,6 @@ class Database const COLLECTIONS = 'collections'; // Lengths - const LENGTH_KEY = 255; // Cache @@ -791,7 +790,7 @@ public function deleteDocument(string $collection, string $id): bool * * @return Document[] */ - public function find(string $collection, array $queries = [], $limit = 25, $offset = 0, $orderAttributes = [], $orderTypes = []): array + public function find(string $collection, array $queries = [], int $limit = 25, int $offset = 0, array $orderAttributes = [], array $orderTypes = []): array { $collection = $this->getCollection($collection); @@ -805,6 +804,22 @@ public function find(string $collection, array $queries = [], $limit = 25, $offs return $results; } + /** + * Count Documents + * + * @param string $collection + * @param Query[] $queries + * @param int $max + * + * @return int + */ + public function count(string $collection, array $queries = [], int $max = 0): int + { + $count = $this->adapter->count($collection, $queries, $max); + + return $count; + } + // /** // * @param string $collection // * @param array $options @@ -829,22 +844,6 @@ public function find(string $collection, array $queries = [], $limit = 25, $offs // return \end($results); // } - // /** - // * @param array $options - // * - // * @return int - // */ - // public function count(array $options) - // { - // $options = \array_merge([ - // 'filters' => [], - // ], $options); - - // $results = $this->adapter->count($options); - - // return $results; - // } - // /** // * @param array $data // * diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 7aa7d46d9..07c013b9e 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -529,24 +529,30 @@ public function testFindLast() $this->assertEquals('1', '1'); } + /** + * @depends testFind + */ public function countTest() { - $this->assertEquals('1', '1'); - } - - public function addFilterTest() - { - $this->assertEquals('1', '1'); - } - - public function encodeTest() - { - $this->assertEquals('1', '1'); - } - - public function decodeTest() - { - $this->assertEquals('1', '1'); + $count = static::getDatabase()->count('movies'); + $this->assertEquals(6, $count); + + $count = static::getDatabase()->count('movies', [new Query('year', Query::TYPE_EQUAL, [2019]),]); + $this->assertEquals(2, $count); + + Authorization::unsetRole('userx'); + $count = static::getDatabase()->count('movies'); + $this->assertEquals(5, $count); + + Authorization::disable(); + $count = static::getDatabase()->count('movies'); + $this->assertEquals(6, $count); + Authorization::reset(); + + Authorization::disable(); + $count = static::getDatabase()->count('movies', [], 3); + $this->assertEquals(3, $count); + Authorization::reset(); } /** From 81a16f851c712f5857c044c0e71c0abc698cd619 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 3 May 2021 12:59:14 +0300 Subject: [PATCH 165/190] Fixed limit tests --- src/Database/Adapter/MariaDB.php | 11 +++++++---- tests/Database/Base.php | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 486cd6f83..78e7a2513 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -538,7 +538,7 @@ public function count(string $collection, array $queries = [], int $max = 0): in $name = $this->filter($collection); $roles = Authorization::getRoles(); $where = ['1=1']; - $limit = ($max === 0) ? '' : 'LIMIT :max;'; + $limit = ($max === 0) ? '' : 'LIMIT :max'; foreach($roles as &$role) { $role = $this->getPDO()->quote($role, PDO::PARAM_STR); @@ -557,10 +557,10 @@ public function count(string $collection, array $queries = [], int $max = 0): in $where[] = implode(' OR ', $conditions); } - $stmt = $this->getPDO()->prepare("SELECT COUNT(table_main.`_uid`) FROM {$this->getNamespace()}.{$name} table_main + $stmt = $this->getPDO()->prepare("SELECT COUNT(1) as sum FROM (SELECT 1 FROM {$this->getNamespace()}.{$name} table_main {$permissions} WHERE ".implode(' AND ', $where)." - {$limit} + {$limit}) table_count "); foreach($queries as $i => $query) { @@ -569,7 +569,10 @@ public function count(string $collection, array $queries = [], int $max = 0): in } } - $stmt->bindValue(':max', $max, PDO::PARAM_INT); + if($max !== 0) { + $stmt->bindValue(':max', $max, PDO::PARAM_INT); + } + $stmt->execute(); $result = $stmt->fetch(); diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 07c013b9e..ebe2e384d 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -532,7 +532,7 @@ public function testFindLast() /** * @depends testFind */ - public function countTest() + public function testCount() { $count = static::getDatabase()->count('movies'); $this->assertEquals(6, $count); From 43744d220c41048d677643c70a0aa3ab90652e13 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 3 May 2021 14:07:15 +0300 Subject: [PATCH 166/190] Updated cache key --- src/Database/Database.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 1daeebff1..c9c80d96d 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -626,7 +626,7 @@ public function getDocument(string $collection, string $id): Document $cache = null; // TODO@kodumbeats Check if returned cache id matches request - if ($cache = $this->cache->load($id, self::TTL)) { + if ($cache = $this->cache->load('cache-'.$this->getNamespace().'-'.$collection->getId().'-'.$id, self::TTL)) { $document = new Document($cache); $validator = new Authorization($document, self::PERMISSION_READ); @@ -658,7 +658,7 @@ public function getDocument(string $collection, string $id): Document $document = $this->casting($collection, $document); $document = $this->decode($collection, $document); - $this->cache->save($id, $document->getArrayCopy()); // save to cache after fetching from db + $this->cache->save('cache-'.$this->getNamespace().'-'.$collection->getId().'-'.$id, $document->getArrayCopy()); // save to cache after fetching from db return $document; } @@ -749,8 +749,8 @@ public function updateDocument(string $collection, string $id, Document $documen $document = $this->adapter->updateDocument($collection->getId(), $document); $document = $this->decode($collection, $document); - $this->cache->purge($id); - $this->cache->save($document->getId(), $document->getArrayCopy()); + $this->cache->purge('cache-'.$this->getNamespace().'-'.$collection->getId().'-'.$id); + $this->cache->save('cache-'.$this->getNamespace().'-'.$collection->getId().'-'.$id, $document->getArrayCopy()); return $document; } @@ -773,7 +773,7 @@ public function deleteDocument(string $collection, string $id): bool throw new AuthorizationException($validator->getDescription()); } - $this->cache->purge($id); + $this->cache->purge('cache-'.$this->getNamespace().'-'.$collection.'-'.$id); return $this->adapter->deleteDocument($collection, $id); } From 8dde2a35a397cdb4cc9c2b67c1ade15f18aa38a9 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 3 May 2021 22:12:26 +0300 Subject: [PATCH 167/190] Fixed array param --- src/Database/Validator/Structure.php | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/Database/Validator/Structure.php b/src/Database/Validator/Structure.php index 76aed4bab..d5e1ec2d7 100644 --- a/src/Database/Validator/Structure.php +++ b/src/Database/Validator/Structure.php @@ -249,9 +249,24 @@ public function isValid($document) if($format) { $validator = self::getFormat($format, $type); - if(!$validator->isValid($value)) { - $this->message = 'Attribute "'.$key.'" has invalid format. '.$validator->getDescription(); - return false; + if($array) { // Validate attribute type + if(!is_array($value)) { + $this->message = 'Attribute "'.$key.'" must be an array'; + return false; + } + + foreach ($value as $x => $child) { + if(!$validator->isValid($child)) { + $this->message = 'Attribute "'.$key.'[\''.$x.'\']" has invalid format. '.$validator->getDescription(); + return false; + } + } + } + else { + if(!$validator->isValid($value)) { + $this->message = 'Attribute "'.$key.'" has invalid format. '.$validator->getDescription(); + return false; + } } } From dd598a2c9f67e86252e7bc9c03ae5e10f9c3966c Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 4 May 2021 01:35:26 +0300 Subject: [PATCH 168/190] Added exists method --- src/Database/Adapter.php | 7 +++++++ src/Database/Adapter/MariaDB.php | 23 +++++++++++++++++++++++ src/Database/Database.php | 10 ++++++++++ tests/Database/Base.php | 7 +++++-- 4 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index 85655d759..121c09ae8 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -94,6 +94,13 @@ public function getNamespace(): string */ abstract public function create(): bool; + /** + * Check if database exists + * + * @return bool + */ + abstract public function exists(): bool; + /** * List Databases * diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 78e7a2513..d6062e728 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -45,6 +45,29 @@ public function create(): bool ->execute(); } + /** + * Check if database exists + * + * @return bool + */ + public function exists(): bool + { + $name = $this->getNamespace(); + + $stmt = $this->getPDO() + ->prepare("SELECT SCHEMA_NAME + FROM INFORMATION_SCHEMA.SCHEMATA + WHERE SCHEMA_NAME = :schema;"); + + $stmt->bindValue(':schema', $name, PDO::PARAM_STR); + + $stmt->execute(); + + $document = $stmt->fetch(); + + return ($document['SCHEMA_NAME'] == $name); + } + /** * List Databases * diff --git a/src/Database/Database.php b/src/Database/Database.php index c9c80d96d..4b8d3c1cc 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -209,6 +209,16 @@ public function create(): bool return true; } + /** + * Check if database exists + * + * @return bool + */ + public function exists(): bool + { + return $this->adapter->exists(); + } + /** * List Databases * diff --git a/tests/Database/Base.php b/tests/Database/Base.php index ebe2e384d..19b577020 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -27,15 +27,18 @@ public function tearDown(): void Authorization::reset(); } - public function testCreateDelete() + public function testCreateExistsDelete() { + $this->assertEquals(false, static::getDatabase()->exists()); $this->assertEquals(true, static::getDatabase()->create()); + $this->assertEquals(true, static::getDatabase()->exists()); $this->assertEquals(true, static::getDatabase()->delete()); + $this->assertEquals(false, static::getDatabase()->exists()); $this->assertEquals(true, static::getDatabase()->create()); } /** - * @depends testCreateDelete + * @depends testCreateExistsDelete */ public function testCreateListDeleteCollection() { From 2299091c63aec2315a4f40bc8dd16968c5bec6bf Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 4 May 2021 01:49:00 +0300 Subject: [PATCH 169/190] Null safety --- src/Database/Adapter/MariaDB.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index d6062e728..1c47a000b 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -65,7 +65,7 @@ public function exists(): bool $document = $stmt->fetch(); - return ($document['SCHEMA_NAME'] == $name); + return (($document['SCHEMA_NAME'] ?? '') == $name); } /** From 0ab68d0e8672da85b62aeda7a100a4cec54066e1 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 4 May 2021 16:56:25 +0300 Subject: [PATCH 170/190] Removed cache file --- .phpunit.result.cache | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .phpunit.result.cache diff --git a/.phpunit.result.cache b/.phpunit.result.cache deleted file mode 100644 index 64f3ea4f7..000000000 --- a/.phpunit.result.cache +++ /dev/null @@ -1 +0,0 @@ -C:37:"PHPUnit\Runner\DefaultTestResultCache":8050:{a:2:{s:7:"defects";a:56:{s:42:"Utopia\Tests\AuthorizationTest::testValues";i:4;s:32:"Utopia\Tests\KeyTest::testValues";i:4;s:40:"Utopia\Tests\PermissionsTest::testValues";i:4;s:32:"Utopia\Tests\UIDTest::testValues";i:4;s:42:"Utopia\Tests\DocumentTest::testPermissions";i:4;s:33:"Utopia\Tests\DocumentTest::testId";i:4;s:41:"Utopia\Tests\DocumentTest::testCollection";i:4;s:43:"Utopia\Tests\DocumentTest::testGetAttribute";i:4;s:43:"Utopia\Tests\DocumentTest::testSetAttribute";i:3;s:37:"Utopia\Tests\DocumentTest::testSearch";i:3;s:36:"Utopia\Tests\DocumentTest::testIsSet";i:3;s:43:"Utopia\Tests\DocumentTest::testGetArrayCopy";i:3;s:54:"Utopia\Tests\Adapter\MariaDBTest::testCreateCollection";i:1;s:54:"Utopia\Tests\Adapter\MariaDBTest::testDeleteCollection";i:1;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAttribute";i:4;s:53:"Utopia\Tests\Adapter\MariaDBTest::testDeleteAttribute";i:4;s:49:"Utopia\Tests\Adapter\MariaDBTest::testCreateIndex";i:4;s:49:"Utopia\Tests\Adapter\MariaDBTest::testDeleteIndex";i:4;s:47:"Utopia\Tests\Adapter\MariaDBTest::testFindFirst";i:4;s:46:"Utopia\Tests\Adapter\MariaDBTest::testFindLast";i:4;s:44:"Utopia\Tests\Adapter\MariaDBTest::testCreate";i:4;s:44:"Utopia\Tests\Adapter\MariaDBTest::testDelete";i:4;s:45:"Utopia\Tests\Adapter\PostgresTest::testCreate";i:4;s:45:"Utopia\Tests\Adapter\PostgresTest::testDelete";i:4;s:44:"Utopia\Tests\Adapter\MongoDBTest::testCreate";i:4;s:44:"Utopia\Tests\Adapter\MongoDBTest::testDelete";i:4;s:54:"Utopia\Tests\Adapter\MongoDBTest::testCreateCollection";i:1;s:54:"Utopia\Tests\Adapter\MongoDBTest::testDeleteCollection";i:1;s:55:"Utopia\Tests\Adapter\PostgresTest::testCreateCollection";i:1;s:55:"Utopia\Tests\Adapter\PostgresTest::testDeleteCollection";i:1;s:55:"Utopia\Tests\Adapter\CockroachTest::testCreateAndDelete";i:4;s:56:"Utopia\Tests\Adapter\CockroachTest::testCreateCollection";i:1;s:56:"Utopia\Tests\Adapter\CockroachTest::testDeleteCollection";i:1;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAndDelete";i:4;s:53:"Utopia\Tests\Adapter\MongoDBTest::testCreateAndDelete";i:4;s:51:"Utopia\Tests\Adapter\MySQLTest::testCreateAndDelete";i:4;s:52:"Utopia\Tests\Adapter\MySQLTest::testCreateCollection";i:1;s:52:"Utopia\Tests\Adapter\MySQLTest::testDeleteCollection";i:1;s:54:"Utopia\Tests\Adapter\PostgresTest::testCreateAndDelete";i:4;s:42:"Utopia\Tests\Adapter\MariaDBTest::testList";i:3;s:42:"Utopia\Tests\Adapter\MongoDBTest::testList";i:3;s:40:"Utopia\Tests\Adapter\MySQLTest::testList";i:3;s:43:"Utopia\Tests\Adapter\PostgresTest::testList";i:3;s:53:"Utopia\Tests\Adapter\MariaDBTest::testListCollections";i:3;s:53:"Utopia\Tests\Adapter\MongoDBTest::testListCollections";i:3;s:51:"Utopia\Tests\Adapter\MySQLTest::testListCollections";i:3;s:54:"Utopia\Tests\Adapter\PostgresTest::testListCollections";i:3;s:51:"Utopia\Tests\Adapter\MySQLTest::testCreateAttribute";i:4;s:59:"Utopia\Tests\Adapter\MariaDBTest::testCreateDeleteAttribute";i:4;s:55:"Utopia\Tests\Adapter\MariaDBTest::testCreateDeleteIndex";i:4;s:50:"Utopia\Tests\Adapter\MySQLTest::testCreateDocument";i:3;s:44:"Utopia\Tests\DocumentTest::testGetAttributes";i:3;s:52:"Utopia\Tests\Adapter\MariaDBTest::testCreateDocument";i:4;s:50:"Utopia\Tests\Adapter\MariaDBTest::testCreateDelete";i:4;s:60:"Utopia\Tests\Adapter\MariaDBTest::testCreateDeleteCollection";i:1;s:49:"Utopia\Tests\Adapter\MariaDBTest::testGetDocument";i:3;}s:5:"times";a:74:{s:42:"Utopia\Tests\AuthorizationTest::testValues";d:0.003;s:32:"Utopia\Tests\KeyTest::testValues";d:0.001;s:40:"Utopia\Tests\PermissionsTest::testValues";d:0.001;s:32:"Utopia\Tests\UIDTest::testValues";d:0.001;s:33:"Utopia\Tests\DocumentTest::testId";d:0.032;s:41:"Utopia\Tests\DocumentTest::testCollection";d:0;s:42:"Utopia\Tests\DocumentTest::testPermissions";d:0;s:43:"Utopia\Tests\DocumentTest::testGetAttribute";d:0;s:43:"Utopia\Tests\DocumentTest::testSetAttribute";d:0;s:46:"Utopia\Tests\DocumentTest::testRemoveAttribute";d:0;s:37:"Utopia\Tests\DocumentTest::testSearch";d:0;s:38:"Utopia\Tests\DocumentTest::testIsEmpty";d:0;s:36:"Utopia\Tests\DocumentTest::testIsSet";d:0;s:43:"Utopia\Tests\DocumentTest::testGetArrayCopy";d:0;s:52:"Utopia\Tests\Validator\AuthorizationTest::testValues";d:0.004;s:42:"Utopia\Tests\Validator\KeyTest::testValues";d:0.004;s:50:"Utopia\Tests\Validator\PermissionsTest::testValues";d:0.002;s:42:"Utopia\Tests\Validator\UIDTest::testValues";d:0.002;s:54:"Utopia\Tests\Adapter\MariaDBTest::testCreateCollection";d:0.015;s:54:"Utopia\Tests\Adapter\MariaDBTest::testDeleteCollection";d:0.005;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAttribute";d:0.117;s:53:"Utopia\Tests\Adapter\MariaDBTest::testDeleteAttribute";d:0.002;s:49:"Utopia\Tests\Adapter\MariaDBTest::testCreateIndex";d:0.001;s:49:"Utopia\Tests\Adapter\MariaDBTest::testDeleteIndex";d:0.002;s:47:"Utopia\Tests\Adapter\MariaDBTest::testFindFirst";d:0;s:46:"Utopia\Tests\Adapter\MariaDBTest::testFindLast";d:0;s:44:"Utopia\Tests\Adapter\MariaDBTest::testCreate";d:0.038;s:44:"Utopia\Tests\Adapter\MariaDBTest::testDelete";d:0.002;s:45:"Utopia\Tests\Adapter\PostgresTest::testCreate";d:0.108;s:45:"Utopia\Tests\Adapter\PostgresTest::testDelete";d:0.017;s:48:"Utopia\Tests\Adapter\PostgresTest::testFindFirst";d:0;s:47:"Utopia\Tests\Adapter\PostgresTest::testFindLast";d:0;s:44:"Utopia\Tests\Adapter\MongoDBTest::testCreate";d:0.008;s:44:"Utopia\Tests\Adapter\MongoDBTest::testDelete";d:0.032;s:47:"Utopia\Tests\Adapter\MongoDBTest::testFindFirst";d:0;s:46:"Utopia\Tests\Adapter\MongoDBTest::testFindLast";d:0;s:54:"Utopia\Tests\Adapter\MongoDBTest::testCreateCollection";d:0.016;s:54:"Utopia\Tests\Adapter\MongoDBTest::testDeleteCollection";d:0.003;s:55:"Utopia\Tests\Adapter\PostgresTest::testCreateCollection";d:0.008;s:55:"Utopia\Tests\Adapter\PostgresTest::testDeleteCollection";d:0.003;s:53:"Utopia\Tests\Adapter\MariaDBTest::testCreateAndDelete";d:0.311;s:53:"Utopia\Tests\Adapter\MongoDBTest::testCreateAndDelete";d:0.031;s:54:"Utopia\Tests\Adapter\PostgresTest::testCreateAndDelete";d:0.015;s:51:"Utopia\Tests\Adapter\MySQLTest::testCreateAndDelete";d:1.54;s:52:"Utopia\Tests\Adapter\MySQLTest::testCreateCollection";d:0.026;s:52:"Utopia\Tests\Adapter\MySQLTest::testDeleteCollection";d:0.016;s:45:"Utopia\Tests\Adapter\MySQLTest::testFindFirst";d:0.001;s:44:"Utopia\Tests\Adapter\MySQLTest::testFindLast";d:0;s:55:"Utopia\Tests\Adapter\CockroachTest::testCreateAndDelete";d:0.01;s:56:"Utopia\Tests\Adapter\CockroachTest::testCreateCollection";d:0;s:56:"Utopia\Tests\Adapter\CockroachTest::testDeleteCollection";d:0;s:49:"Utopia\Tests\Adapter\CockroachTest::testFindFirst";d:0.001;s:48:"Utopia\Tests\Adapter\CockroachTest::testFindLast";d:0;s:42:"Utopia\Tests\Adapter\MariaDBTest::testList";d:0.007;s:42:"Utopia\Tests\Adapter\MongoDBTest::testList";d:0.003;s:40:"Utopia\Tests\Adapter\MySQLTest::testList";d:0.003;s:43:"Utopia\Tests\Adapter\PostgresTest::testList";d:0.002;s:53:"Utopia\Tests\Adapter\MariaDBTest::testListCollections";d:0.034;s:53:"Utopia\Tests\Adapter\MongoDBTest::testListCollections";d:0.007;s:51:"Utopia\Tests\Adapter\MySQLTest::testListCollections";d:0.003;s:54:"Utopia\Tests\Adapter\PostgresTest::testListCollections";d:0.005;s:51:"Utopia\Tests\Adapter\MySQLTest::testCreateAttribute";d:0.406;s:59:"Utopia\Tests\Adapter\MariaDBTest::testCreateDeleteAttribute";d:0.182;s:55:"Utopia\Tests\Adapter\MariaDBTest::testCreateDeleteIndex";d:0.099;s:60:"Utopia\Tests\Adapter\MariaDBTest::testCreateDeleteCollection";d:0.047;s:58:"Utopia\Tests\Adapter\MySQLTest::testCreateDeleteCollection";d:0.067;s:57:"Utopia\Tests\Adapter\MySQLTest::testCreateDeleteAttribute";d:0.874;s:53:"Utopia\Tests\Adapter\MySQLTest::testCreateDeleteIndex";d:0.242;s:48:"Utopia\Tests\Adapter\MySQLTest::testCreateDelete";d:1.016;s:50:"Utopia\Tests\Adapter\MySQLTest::testCreateDocument";d:0.126;s:44:"Utopia\Tests\DocumentTest::testGetAttributes";d:0.003;s:50:"Utopia\Tests\Adapter\MariaDBTest::testCreateDelete";d:0.256;s:52:"Utopia\Tests\Adapter\MariaDBTest::testCreateDocument";d:0.074;s:49:"Utopia\Tests\Adapter\MariaDBTest::testGetDocument";d:0.008;}}} \ No newline at end of file From 19f605f4059d7d4976b095f5d11f6cb7626a7b79 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 4 May 2021 22:53:35 +0300 Subject: [PATCH 171/190] Fix for filters --- Dockerfile | 2 +- src/Database/Database.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index d1e970f35..692ba1ac7 100755 --- a/Dockerfile +++ b/Dockerfile @@ -41,6 +41,6 @@ RUN echo extension=swoole.so >> /usr/local/etc/php/conf.d/swoole.ini COPY --from=step0 /usr/local/src/vendor /usr/src/code/vendor # Add Source Code -COPY ./ /usr/src/code +COPY . /usr/src/code CMD [ "tail", "-f", "/dev/null" ] diff --git a/src/Database/Database.php b/src/Database/Database.php index 4b8d3c1cc..38606e9d7 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -973,7 +973,7 @@ public function decode(Document $collection, Document $document):Document foreach ($value as &$node) { if (($node !== null)) { - foreach ($filters as $filter) { + foreach (array_reverse($filters) as $filter) { $node = $this->decodeAttribute($filter, $node); } } From 52c8cacf1d7930c7ae4cad034666a4322e3c56b0 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Wed, 5 May 2021 00:14:11 +0300 Subject: [PATCH 172/190] Fixed empty doc warning --- src/Database/Adapter/MariaDB.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 1c47a000b..1b3ff1c08 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -265,6 +265,10 @@ public function getDocument(string $collection, string $id): Document $document = $stmt->fetch(); + if(empty($document)) { + return new Document([]); + } + $permissions = (isset($document['_permissions'])) ? json_decode($document['_permissions'], true) : []; $document['$id'] = $document['_uid']; $document['$read'] = $permissions[Database::PERMISSION_READ] ?? []; From 4cdea0eb17438fc2d5ef9de62e8b35a71b6828a9 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Wed, 5 May 2021 15:12:13 +0300 Subject: [PATCH 173/190] Added support for unique index --- README.md | 1 + docker-compose.yml | 2 ++ src/Database/Adapter/MariaDB.php | 27 ++++++++++++++++++++++++++- 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8b28fb77f..61318abce 100644 --- a/README.md +++ b/README.md @@ -144,6 +144,7 @@ Below is a list of supported adapters, and thier compatibly tested versions alon - [ ] FIND: Add a query validator (Limit queries to indexed attaributes only?) - [ ] FIND: Add support for more operators (search/match/like) - [ ] TEST: Missing Collection, DocumentId validators tests +- [ ] TEST: Validate row size is not larger than allowed by adapter (MySQL/MariaDB ~16k) ## Open Issues diff --git a/docker-compose.yml b/docker-compose.yml index 566ecf2e6..78a08479b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -66,6 +66,8 @@ services: MYSQL_USER: user MYSQL_PASSWORD: password MYSQL_TCP_PORT: 3307 + cap_add: + - SYS_NICE redis: image: redis:6.0-alpine diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 1b3ff1c08..aa4a41899 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -208,6 +208,7 @@ public function createIndex(string $collection, string $id, string $type, array { $name = $this->filter($collection); $id = $this->filter($id); + $type = $this->getSQLIndex($type); foreach($attributes as $key => &$attribute) { $length = $lengths[$key] ?? ''; @@ -219,7 +220,7 @@ public function createIndex(string $collection, string $id, string $type, array } return $this->getPDO() - ->prepare("CREATE INDEX `{$id}` ON {$this->getNamespace()}.{$name} (".implode(', ', $attributes).");") + ->prepare("CREATE {$type} `{$id}` ON {$this->getNamespace()}.{$name} (".implode(', ', $attributes).");") ->execute(); } @@ -749,6 +750,30 @@ protected function getSQLOperator(string $operator): string } } + /** + * Get SQL Index + * + * @param string $operator + * + * @return string + */ + protected function getSQLIndex(string $type): string + { + switch ($type) { + case Database::INDEX_KEY: + return 'INDEX'; + break; + + case Database::INDEX_UNIQUE: + return 'UNIQUE INDEX'; + break; + + default: + throw new Exception('Unknown Index Type:' . $type); + break; + } + } + /** * Get PDO Type * From 64c1eba43dcbddb914cf0df383c3248a3d3b1803 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Wed, 5 May 2021 16:41:23 +0300 Subject: [PATCH 174/190] Added find first and find last methods --- README.md | 1 + src/Database/Database.php | 48 +++++++++++++++++++-------------------- tests/Database/Base.php | 12 ++++++++-- 3 files changed, 35 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 61318abce..e9e62c1e3 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,7 @@ Below is a list of supported adapters, and thier compatibly tested versions alon - [ ] FIND: Add support for more operators (search/match/like) - [ ] TEST: Missing Collection, DocumentId validators tests - [ ] TEST: Validate row size is not larger than allowed by adapter (MySQL/MariaDB ~16k) +- [ ] TEST: Add test for creation of a unique index ## Open Issues diff --git a/src/Database/Database.php b/src/Database/Database.php index 38606e9d7..9a732f100 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -814,6 +814,30 @@ public function find(string $collection, array $queries = [], int $limit = 25, i return $results; } + /** + * @param string $collection + * @param array $options + * + * @return Document|bool + */ + public function findFirst(string $collection, array $queries = [], int $limit = 25, int $offset = 0, array $orderAttributes = [], array $orderTypes = []) + { + $results = $this->find($collection, $queries, $limit, $offset, $orderAttributes, $orderTypes); + return \reset($results); + } + + /** + * @param string $collection + * @param array $options + * + * @return Document|false + */ + public function findLast(string $collection, array $queries = [], int $limit = 25, int $offset = 0, array $orderAttributes = [], array $orderTypes = []) + { + $results = $this->find($collection, $queries, $limit, $offset, $orderAttributes, $orderTypes); + return \end($results); + } + /** * Count Documents * @@ -830,30 +854,6 @@ public function count(string $collection, array $queries = [], int $max = 0): in return $count; } - // /** - // * @param string $collection - // * @param array $options - // * - // * @return Document - // */ - // public function findFirst(string $collection, array $options) - // { - // $results = $this->find($collection, $options); - // return \reset($results); - // } - - // /** - // * @param string $collection - // * @param array $options - // * - // * @return Document - // */ - // public function findLast(string $collection, array $options) - // { - // $results = $this->find($collection, $options); - // return \end($results); - // } - // /** // * @param array $data // * diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 19b577020..698889164 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -522,14 +522,22 @@ public function testFind(Document $document) $this->assertEquals('Work in Progress 2', $documents[3]['name']); } + /** + * @depends testFind + */ public function testFindFirst() { - $this->assertEquals('1', '1'); + $document = static::getDatabase()->findFirst('movies', [], 4, 2); + $this->assertEquals('Captain America: The First Avenger', $document['name']); } + /** + * @depends testFind + */ public function testFindLast() { - $this->assertEquals('1', '1'); + $document = static::getDatabase()->findLast('movies', [], 4, 2); + $this->assertEquals('Work in Progress 2', $document['name']); } /** From a41bbe4674c2fea3ce84942e40f7c57b4903a918 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Wed, 5 May 2021 16:43:40 +0300 Subject: [PATCH 175/190] added failure tests --- tests/Database/Base.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 698889164..1aa1acc0f 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -529,6 +529,9 @@ public function testFindFirst() { $document = static::getDatabase()->findFirst('movies', [], 4, 2); $this->assertEquals('Captain America: The First Avenger', $document['name']); + + $document = static::getDatabase()->findFirst('movies', [], 4, 10); + $this->assertEquals(false, $document); } /** @@ -538,6 +541,9 @@ public function testFindLast() { $document = static::getDatabase()->findLast('movies', [], 4, 2); $this->assertEquals('Work in Progress 2', $document['name']); + + $document = static::getDatabase()->findLast('movies', [], 4, 10); + $this->assertEquals(false, $document); } /** From 40a0d4025f2a22defa11f010f0adaffdf425fdf4 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Wed, 5 May 2021 23:49:59 +0300 Subject: [PATCH 176/190] Test object nesting --- src/Database/Document.php | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Database/Document.php b/src/Database/Document.php index 69127fab7..efed817c7 100644 --- a/src/Database/Document.php +++ b/src/Database/Document.php @@ -32,19 +32,19 @@ public function __construct(array $input = []) throw new Exception('$write permission must be of type array'); } - // foreach ($input as $key => &$value) { - // if (\is_array($value)) { - // if ((isset($value['$id']) || isset($value['$collection']))) { - // $input[$key] = new self($value); - // } else { - // foreach ($value as $childKey => $child) { - // if ((isset($child['$id']) || isset($child['$collection'])) && (!$child instanceof self)) { - // $value[$childKey] = new self($child); - // } - // } - // } - // } - // } + foreach ($input as $key => &$value) { + if (\is_array($value)) { + if ((isset($value['$id']) || isset($value['$collection']))) { + $input[$key] = new self($value); + } else { + foreach ($value as $childKey => $child) { + if ((isset($child['$id']) || isset($child['$collection'])) && (!$child instanceof self)) { + $value[$childKey] = new self($child); + } + } + } + } + } parent::__construct($input); } From 392c40a4fbf75594609200f7f3edd4089713436d Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 6 May 2021 09:27:08 +0300 Subject: [PATCH 177/190] Added test for filters --- tests/Database/Base.php | 195 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 1aa1acc0f..9d2b356e1 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -3,6 +3,7 @@ namespace Utopia\Tests; use PHPUnit\Framework\TestCase; +use stdClass; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Exception\Authorization as ExceptionAuthorization; @@ -572,6 +573,200 @@ public function testCount() Authorization::reset(); } + public function testEncodeDecode() + { + $collection = new Document([ + '$collection' => Database::COLLECTIONS, + '$id' => 'users', + 'name' => 'Users', + 'attributes' => [ + [ + '$id' => 'name', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 256, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'email', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 1024, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'status', + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'password', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'passwordUpdate', + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'registration', + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'emailVerification', + 'type' => Database::VAR_BOOLEAN, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'reset', + 'type' => Database::VAR_BOOLEAN, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'prefs', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => ['json'] + ], + [ + '$id' => 'sessions', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => ['json'], + ], + [ + '$id' => 'tokens', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => ['json'], + ], + [ + '$id' => 'memberships', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'array' => false, + 'filters' => ['json'], + ], + ], + 'indexes' => [ + [ + '$id' => '_key_email', + 'type' => Database::INDEX_UNIQUE, + 'attributes' => ['email'], + 'lengths' => [1024], + 'orders' => [Database::ORDER_ASC], + ] + ], + ]); + + $document = new Document([ + '$id' => '608fdbe51361a', + '$read' => ['*'], + '$write' => ['user:608fdbe51361a'], + 'email' => 'test@example.com', + 'emailVerification' => false, + 'status' => 1, + 'password' => 'randomhash', + 'passwordUpdate' => 1234, + 'registration' => 1234, + 'reset' => false, + 'name' => 'My Name', + 'prefs' => new stdClass, + 'sessions' => [], + 'tokens' => [], + 'memberships' => [], + ]); + + $result = static::getDatabase()->encode($collection, $document); + + $this->assertEquals('608fdbe51361a', $result->getAttribute('$id')); + $this->assertEquals(['*'], $result->getAttribute('$read')); + $this->assertEquals(['user:608fdbe51361a'], $result->getAttribute('$write')); + $this->assertEquals('test@example.com', $result->getAttribute('email')); + $this->assertEquals(false, $result->getAttribute('emailVerification')); + $this->assertEquals(1, $result->getAttribute('status')); + $this->assertEquals('randomhash', $result->getAttribute('password')); + $this->assertEquals(1234, $result->getAttribute('passwordUpdate')); + $this->assertEquals(1234, $result->getAttribute('registration')); + $this->assertEquals(false, $result->getAttribute('reset')); + $this->assertEquals('My Name', $result->getAttribute('name')); + $this->assertEquals('{}', $result->getAttribute('prefs')); + $this->assertEquals('[]', $result->getAttribute('sessions')); + $this->assertEquals('[]', $result->getAttribute('tokens')); + $this->assertEquals('[]', $result->getAttribute('memberships')); + + $result = static::getDatabase()->decode($collection, $document); + + $this->assertEquals('608fdbe51361a', $result->getAttribute('$id')); + $this->assertEquals(['*'], $result->getAttribute('$read')); + $this->assertEquals(['user:608fdbe51361a'], $result->getAttribute('$write')); + $this->assertEquals('test@example.com', $result->getAttribute('email')); + $this->assertEquals(false, $result->getAttribute('emailVerification')); + $this->assertEquals(1, $result->getAttribute('status')); + $this->assertEquals('randomhash', $result->getAttribute('password')); + $this->assertEquals(1234, $result->getAttribute('passwordUpdate')); + $this->assertEquals(1234, $result->getAttribute('registration')); + $this->assertEquals(false, $result->getAttribute('reset')); + $this->assertEquals('My Name', $result->getAttribute('name')); + $this->assertEquals([], $result->getAttribute('prefs')); + $this->assertEquals([], $result->getAttribute('sessions')); + $this->assertEquals([], $result->getAttribute('tokens')); + $this->assertEquals([], $result->getAttribute('memberships')); + } + /** * @depends testCreateDocument */ From 1664bdac9163916ae7e91f1eb2077a883a2d176d Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 6 May 2021 09:42:59 +0300 Subject: [PATCH 178/190] Added casting and filtering status --- src/Database/Document.php | 56 +++++++++++++++++++++++++++++++++ tests/Database/DocumentTest.php | 14 +++++++++ 2 files changed, 70 insertions(+) diff --git a/src/Database/Document.php b/src/Database/Document.php index efed817c7..ef3d3ffc3 100644 --- a/src/Database/Document.php +++ b/src/Database/Document.php @@ -11,6 +11,16 @@ class Document extends ArrayObject const SET_TYPE_PREPEND = 'prepend'; const SET_TYPE_APPEND = 'append'; + /** + * @var bool + */ + protected $filter = false; + + /** + * @var bool + */ + protected $casting = false; + /** * Construct. * @@ -240,6 +250,52 @@ public function isSet($key): bool return isset($this[$key]); } + /** + * Get Document Filter Status + * + * @return bool + */ + public function getFilterStatus(): bool + { + return $this->filter; + } + + /** + * Set Document Filter Status + * + * @param bool $status + * + * @return self + */ + public function setFilterStatus(bool $status): self + { + $this->filter = $status; + return $this; + } + + /** + * Get Document Casting Status + * + * @return bool + */ + public function getCastingStatus(): bool + { + return $this->casting; + } + + /** + * Set Document Casting Status + * + * @param bool $status + * + * @return self + */ + public function setCastingStatus(bool $status): self + { + $this->casting = $status; + return $this; + } + /** * Get Array Copy. * diff --git a/tests/Database/DocumentTest.php b/tests/Database/DocumentTest.php index 2a873e7e5..96b241810 100644 --- a/tests/Database/DocumentTest.php +++ b/tests/Database/DocumentTest.php @@ -151,6 +151,20 @@ public function testIsSet() $this->assertEquals(true, $this->document->isSet('title')); } + public function testFilterStatus() + { + $this->assertEquals(false, $this->document->getFilterStatus()); + $this->assertEquals($this->document, $this->document->setFilterStatus(true)); + $this->assertEquals(true, $this->document->getFilterStatus()); + } + + public function testCastingStatus() + { + $this->assertEquals(false, $this->document->getCastingStatus()); + $this->assertEquals($this->document, $this->document->setCastingStatus(true)); + $this->assertEquals(true, $this->document->getCastingStatus()); + } + public function testGetArrayCopy() { $this->assertEquals([ From 5fb88388b8b1a46a8aec87bd0f21dc6f64429797 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 6 May 2021 10:33:53 +0300 Subject: [PATCH 179/190] Updated filters logic --- src/Database/Database.php | 24 +++++++++----- src/Database/Document.php | 56 --------------------------------- tests/Database/DocumentTest.php | 14 --------- 3 files changed, 16 insertions(+), 78 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 9a732f100..e64f65619 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -89,7 +89,7 @@ class Database 'size' => 1000000, 'required' => false, 'signed' => true, - 'array' => true, + 'array' => false, 'filters' => ['json'], ], [ @@ -98,7 +98,7 @@ class Database 'size' => 1000000, 'required' => false, 'signed' => true, - 'array' => true, + 'array' => false, 'filters' => ['json'], ], [ @@ -107,7 +107,7 @@ class Database 'size' => 1000000, 'required' => false, 'signed' => true, - 'array' => true, + 'array' => false, 'filters' => ['json'], ], [ @@ -116,7 +116,7 @@ class Database 'size' => 1000000, 'required' => false, 'signed' => true, - 'array' => true, + 'array' => false, 'filters' => ['json'], ], ], @@ -142,16 +142,24 @@ public function __construct(Adapter $adapter, Cache $cache) self::addFilter('json', /** * @param mixed $value - * @return string + * @return mixed */ - function($value): string { + function($value) { + if(!is_array($value) && !$value instanceof \stdClass) { + return $value; + } + return json_encode($value); }, /** * @param mixed $value - * @return array + * @return mixed */ - function($value): array { + function($value) { + if(!is_string($value)) { + return $value; + } + return json_decode($value, true); } ); diff --git a/src/Database/Document.php b/src/Database/Document.php index ef3d3ffc3..efed817c7 100644 --- a/src/Database/Document.php +++ b/src/Database/Document.php @@ -11,16 +11,6 @@ class Document extends ArrayObject const SET_TYPE_PREPEND = 'prepend'; const SET_TYPE_APPEND = 'append'; - /** - * @var bool - */ - protected $filter = false; - - /** - * @var bool - */ - protected $casting = false; - /** * Construct. * @@ -250,52 +240,6 @@ public function isSet($key): bool return isset($this[$key]); } - /** - * Get Document Filter Status - * - * @return bool - */ - public function getFilterStatus(): bool - { - return $this->filter; - } - - /** - * Set Document Filter Status - * - * @param bool $status - * - * @return self - */ - public function setFilterStatus(bool $status): self - { - $this->filter = $status; - return $this; - } - - /** - * Get Document Casting Status - * - * @return bool - */ - public function getCastingStatus(): bool - { - return $this->casting; - } - - /** - * Set Document Casting Status - * - * @param bool $status - * - * @return self - */ - public function setCastingStatus(bool $status): self - { - $this->casting = $status; - return $this; - } - /** * Get Array Copy. * diff --git a/tests/Database/DocumentTest.php b/tests/Database/DocumentTest.php index 96b241810..2a873e7e5 100644 --- a/tests/Database/DocumentTest.php +++ b/tests/Database/DocumentTest.php @@ -151,20 +151,6 @@ public function testIsSet() $this->assertEquals(true, $this->document->isSet('title')); } - public function testFilterStatus() - { - $this->assertEquals(false, $this->document->getFilterStatus()); - $this->assertEquals($this->document, $this->document->setFilterStatus(true)); - $this->assertEquals(true, $this->document->getFilterStatus()); - } - - public function testCastingStatus() - { - $this->assertEquals(false, $this->document->getCastingStatus()); - $this->assertEquals($this->document, $this->document->setCastingStatus(true)); - $this->assertEquals(true, $this->document->getCastingStatus()); - } - public function testGetArrayCopy() { $this->assertEquals([ From 04c4a09756526f4d83c03dae9915c6de9f3ccfeb Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 6 May 2021 12:22:14 +0300 Subject: [PATCH 180/190] Added tests for array filters --- src/Database/Database.php | 10 ++++++++++ tests/Database/Base.php | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/src/Database/Database.php b/src/Database/Database.php index e64f65619..901776340 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -145,6 +145,8 @@ public function __construct(Adapter $adapter, Cache $cache) * @return mixed */ function($value) { + $value = ($value instanceof Document) ? $value->getArrayCopy() : $value; + if(!is_array($value) && !$value instanceof \stdClass) { return $value; } @@ -939,6 +941,10 @@ public function encode(Document $collection, Document $document):Document $filters = $attribute['filters'] ?? []; $value = $document->getAttribute($key, null); + if(is_null($value)) { + continue; + } + $value = ($array) ? $value : [$value]; foreach ($value as &$node) { @@ -977,6 +983,10 @@ public function decode(Document $collection, Document $document):Document $filters = $attribute['filters'] ?? []; $value = $document->getAttribute($key, null); + if(is_null($value)) { + continue; + } + $value = ($array) ? $value : [$value]; foreach ($value as &$node) { diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 9d2b356e1..e744577df 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -700,6 +700,26 @@ public function testEncodeDecode() 'array' => false, 'filters' => ['json'], ], + [ + '$id' => 'roles', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 128, + 'signed' => true, + 'required' => false, + 'array' => true, + 'filters' => [], + ], + [ + '$id' => 'tags', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 128, + 'signed' => true, + 'required' => false, + 'array' => true, + 'filters' => ['json'], + ], ], 'indexes' => [ [ @@ -728,6 +748,16 @@ public function testEncodeDecode() 'sessions' => [], 'tokens' => [], 'memberships' => [], + 'roles' => [ + 'admin', + 'developer', + 'tester', + ], + 'tags' => [ + ['$id' => '1', 'label' => 'x'], + ['$id' => '2', 'label' => 'y'], + ['$id' => '3', 'label' => 'z'], + ], ]); $result = static::getDatabase()->encode($collection, $document); @@ -747,6 +777,8 @@ public function testEncodeDecode() $this->assertEquals('[]', $result->getAttribute('sessions')); $this->assertEquals('[]', $result->getAttribute('tokens')); $this->assertEquals('[]', $result->getAttribute('memberships')); + $this->assertEquals(['admin','developer','tester',], $result->getAttribute('roles')); + $this->assertEquals(['{"$id":"1","label":"x"}','{"$id":"2","label":"y"}','{"$id":"3","label":"z"}',], $result->getAttribute('tags')); $result = static::getDatabase()->decode($collection, $document); @@ -765,6 +797,12 @@ public function testEncodeDecode() $this->assertEquals([], $result->getAttribute('sessions')); $this->assertEquals([], $result->getAttribute('tokens')); $this->assertEquals([], $result->getAttribute('memberships')); + $this->assertEquals(['admin','developer','tester',], $result->getAttribute('roles')); + $this->assertEquals([ + ['$id' => '1', 'label' => 'x'], + ['$id' => '2', 'label' => 'y'], + ['$id' => '3', 'label' => 'z'], + ], $result->getAttribute('tags')); } /** From 54927448766780d5c864f84757de5252bdc807b3 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 6 May 2021 14:08:54 +0300 Subject: [PATCH 181/190] Minor fix/test --- src/Database/Database.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 901776340..ce47209da 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -146,7 +146,7 @@ public function __construct(Adapter $adapter, Cache $cache) */ function($value) { $value = ($value instanceof Document) ? $value->getArrayCopy() : $value; - + if(!is_array($value) && !$value instanceof \stdClass) { return $value; } @@ -1022,7 +1022,7 @@ public function casting(Document $collection, Document $document):Document $value = $document->getAttribute($key, null); if($array) { - $value = json_decode($value, true); + $value = (!is_string($value)) ? $value : json_decode($value, true); } else { $value = [$value]; From 2bb3db211d13f4028d05189b3313e2b2f1cce884 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 6 May 2021 20:16:47 +0300 Subject: [PATCH 182/190] Updated tests --- src/Database/Database.php | 1 + tests/Database/Base.php | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Database/Database.php b/src/Database/Database.php index ce47209da..b5272ec15 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -819,6 +819,7 @@ public function find(string $collection, array $queries = [], int $limit = 25, i foreach ($results as &$node) { $node = $this->casting($collection, $node); $node = $this->decode($collection, $node); + $node->setAttribute('$collection', $collection->getId()); } return $results; diff --git a/tests/Database/Base.php b/tests/Database/Base.php index e744577df..06284d5c3 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -390,6 +390,7 @@ public function testFind(Document $document) $this->assertEquals(5, count($documents)); $this->assertNotEmpty($documents[0]->getId()); + $this->assertEquals('movies', $documents[0]->getCollection()); $this->assertEquals(['*', 'user1', 'user2'], $documents[0]->getRead()); $this->assertEquals(['*', 'user1x', 'user2x'], $documents[0]->getWrite()); $this->assertEquals('Frozen', $documents[0]->getAttribute('name')); From aeabf0f88d2f1c15476fee19eee3a863ddbf7050 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 6 May 2021 23:03:19 +0300 Subject: [PATCH 183/190] More tests --- tests/Database/Base.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 06284d5c3..7d60bbe63 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -213,6 +213,7 @@ public function testCreateDocument() $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'float', Database::VAR_FLOAT, 0, true)); $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'boolean', Database::VAR_BOOLEAN, 0, true)); $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'colors', Database::VAR_STRING, 32, true, true, true)); + $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'empty', Database::VAR_STRING, 32, false, true, true)); $document = static::getDatabase()->createDocument('documents', new Document([ '$read' => ['*', 'user1', 'user2'], @@ -222,6 +223,7 @@ public function testCreateDocument() 'float' => 5.55, 'boolean' => true, 'colors' => ['pink', 'green', 'blue'], + 'empty' => [], ])); $this->assertNotEmpty(true, $document->getId()); @@ -235,6 +237,7 @@ public function testCreateDocument() $this->assertEquals(true, $document->getAttribute('boolean')); $this->assertIsArray($document->getAttribute('colors')); $this->assertEquals(['pink', 'green', 'blue'], $document->getAttribute('colors')); + $this->assertEquals([], $document->getAttribute('empty')); return $document; } From 11a149d2fd04b73c9fddffb93e96efc78d4a6039 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Thu, 6 May 2021 23:03:31 +0300 Subject: [PATCH 184/190] Better error message --- src/Database/Adapter/MariaDB.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index aa4a41899..77144af42 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -326,7 +326,7 @@ public function createDocument(string $collection, Document $document): Document switch ($e->getCode()) { case 1062: case 23000: - throw new Duplicate(); // TODO add test for catching this exception + throw new Duplicate('Duplicated document: '.$e->getMessage()); // TODO add test for catching this exception break; default: From ee26734fb9bab9e46e5bf398fb658fa0e5a4b31e Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 8 May 2021 22:00:56 +0300 Subject: [PATCH 185/190] Allow null value for optional attrubutes --- src/Database/Validator/Structure.php | 9 +++++++ tests/Database/Validator/StructureTest.php | 31 ++++++++++++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/Database/Validator/Structure.php b/src/Database/Validator/Structure.php index d5e1ec2d7..7824adbdc 100644 --- a/src/Database/Validator/Structure.php +++ b/src/Database/Validator/Structure.php @@ -201,6 +201,7 @@ public function isValid($document) $type = $attribute['type'] ?? ''; $array = $attribute['array'] ?? false; $format = $attribute['format'] ?? ''; + $required = $attribute['required'] ?? false; switch ($type) { case Database::VAR_STRING: @@ -233,6 +234,10 @@ public function isValid($document) } foreach ($value as $x => $child) { + if($required == false && is_null($child)) { // Allow null value to optional params + continue; + } + if(!$validator->isValid($child)) { $this->message = 'Attribute "'.$key.'[\''.$x.'\']" has invalid type. '.$validator->getDescription(); return false; @@ -240,6 +245,10 @@ public function isValid($document) } } else { + if($required == false && is_null($value)) { // Allow null value to optional params + continue; + } + if(!$validator->isValid($value)) { $this->message = 'Attribute "'.$key.'" has invalid type. '.$validator->getDescription(); return false; diff --git a/tests/Database/Validator/StructureTest.php b/tests/Database/Validator/StructureTest.php index 0b7e9802a..927bf5cd8 100644 --- a/tests/Database/Validator/StructureTest.php +++ b/tests/Database/Validator/StructureTest.php @@ -33,7 +33,7 @@ class StructureTest extends TestCase 'type' => Database::VAR_STRING, 'format' => '', 'size' => 1000000, - 'required' => true, + 'required' => false, 'signed' => true, 'array' => false, 'filters' => [], @@ -73,7 +73,7 @@ class StructureTest extends TestCase 'type' => Database::VAR_STRING, 'format' => '', 'size' => 55, - 'required' => true, + 'required' => false, 'signed' => true, 'array' => true, 'filters' => [], @@ -156,6 +156,33 @@ public function testRequiredKeys() $this->assertEquals('Invalid document structure: Missing required attribute "title"', $validator->getDescription()); } + + public function testNullValues() + { + $validator = new Structure(new Document($this->collection)); + + $this->assertEquals(true, $validator->isValid(new Document([ + '$collection' => 'posts', + 'title' => 'My Title', + 'description' => null, + 'rating' => 5, + 'price' => 1.99, + 'published' => true, + 'tags' => ['dog', 'cat', 'mouse'], + 'feedback' => 'team@appwrite.io', + ]))); + + $this->assertEquals(true, $validator->isValid(new Document([ + '$collection' => 'posts', + 'title' => 'My Title', + 'description' => null, + 'rating' => 5, + 'price' => 1.99, + 'published' => true, + 'tags' => ['dog', null, 'mouse'], + 'feedback' => 'team@appwrite.io', + ]))); + } public function testUnknownKeys() { From 0f4aecd6fbf708ecd512aea331c11bbbf6abd3d4 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 9 May 2021 01:05:26 +0300 Subject: [PATCH 186/190] Allow null values --- src/Database/Adapter/MariaDB.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 77144af42..0178e9616 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -800,6 +800,10 @@ protected function getPDOType($value): int case 'integer': return PDO::PARAM_INT; break; + + case 'null': + return PDO::PARAM_NULL; + break; default: throw new Exception('Unknown PDO Type for ' . gettype($value)); From 20d189c6687ebfe82c3148794a01f51ebc6efb05 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 9 May 2021 01:11:07 +0300 Subject: [PATCH 187/190] Allow null values --- src/Database/Adapter/MariaDB.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 0178e9616..0de7faf6e 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -801,7 +801,7 @@ protected function getPDOType($value): int return PDO::PARAM_INT; break; - case 'null': + case 'NULL': return PDO::PARAM_NULL; break; From 6813402ce968b449578def5a14c5f1ebf7cbfc34 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 9 May 2021 02:21:00 +0300 Subject: [PATCH 188/190] Updated auth validator --- src/Database/Validator/Authorization.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Database/Validator/Authorization.php b/src/Database/Validator/Authorization.php index b5ac2876d..a6336d5e2 100644 --- a/src/Database/Validator/Authorization.php +++ b/src/Database/Validator/Authorization.php @@ -64,11 +64,9 @@ public function isValid($permissions) return true; } - $permission = ''; + $permission = '-'; foreach ($permissions as $permission) { - $permission = \str_replace(':{self}', ':'.$this->document->getId(), $permission); - if (\array_key_exists($permission, self::$roles)) { return true; } From 8afa1108320c3197fa6f63c83a0c63f9ce47abf7 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 9 May 2021 02:25:55 +0300 Subject: [PATCH 189/190] Updated tests --- src/Database/Validator/Authorization.php | 5 +++++ tests/Database/Validator/AuthorizationTest.php | 3 +++ 2 files changed, 8 insertions(+) diff --git a/src/Database/Validator/Authorization.php b/src/Database/Validator/Authorization.php index a6336d5e2..90bbec295 100644 --- a/src/Database/Validator/Authorization.php +++ b/src/Database/Validator/Authorization.php @@ -64,6 +64,11 @@ public function isValid($permissions) return true; } + if(empty($permissions)) { + $this->message = 'No permissions provided for action \''.$this->action.'\''; + return false; + } + $permission = '-'; foreach ($permissions as $permission) { diff --git a/tests/Database/Validator/AuthorizationTest.php b/tests/Database/Validator/AuthorizationTest.php index 4e45254e0..ca7ee547b 100644 --- a/tests/Database/Validator/AuthorizationTest.php +++ b/tests/Database/Validator/AuthorizationTest.php @@ -36,6 +36,9 @@ public function tearDown(): void public function testValues() { $this->assertEquals($this->object->isValid($this->document->getRead()), false); + $this->assertEquals($this->object->isValid(''), false); + $this->assertEquals($this->object->isValid([]), false); + $this->assertEquals($this->object->getDescription(), 'No permissions provided for action \'read\''); Authorization::setRole('user:456'); Authorization::setRole('user:123'); From d54bbae93fea9f60df535e4f669146e24d87761b Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 9 May 2021 16:42:23 +0300 Subject: [PATCH 190/190] Fix for find method --- src/Database/Adapter/MariaDB.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 0de7faf6e..00d80c7ee 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -519,6 +519,7 @@ public function find(string $collection, array $queries = [], int $limit = 25, i $stmt = $this->getPDO()->prepare("SELECT table_main.* FROM {$this->getNamespace()}.{$name} table_main {$permissions} WHERE ".implode(' AND ', $where)." + GROUP BY table_main._uid {$order} LIMIT :offset, :limit; "); @@ -551,7 +552,7 @@ public function find(string $collection, array $queries = [], int $limit = 25, i } /** - * Cound Documents + * Count Documents * * Count data set size using chosen queries * @@ -588,6 +589,7 @@ public function count(string $collection, array $queries = [], int $max = 0): in $stmt = $this->getPDO()->prepare("SELECT COUNT(1) as sum FROM (SELECT 1 FROM {$this->getNamespace()}.{$name} table_main {$permissions} WHERE ".implode(' AND ', $where)." + GROUP BY table_main._uid {$limit}) table_count ");