diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index decfd7db4..422b62bd4 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -22,6 +22,18 @@ abstract class Adapter */ protected array $debug = []; + /** + * @var array> + */ + protected array $transformations = [ + '*' => [], + ]; + + /** + * @var array + */ + protected array $metadata = []; + protected static ?int $timeout = null; /** @@ -135,6 +147,81 @@ public function getDefaultDatabase(): string return $this->defaultDatabase; } + /** + * Set metadata for query comments + * + * @param string $key + * @param mixed $value + * @return $this + */ + public function setMetadata(string $key, mixed $value): self + { + $this->metadata[$key] = $value; + + $output = ''; + foreach ($this->metadata as $key => $value) { + $output .= "-- {$key}: {$value}\n"; + } + + $this->before(Database::EVENT_ALL, 'metadata', function ($query) use ($output) { + return $output . $query; + }); + + return $this; + } + + /** + * Get metadata + * + * @return array + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * Clear existing metadata + * + * @return $this + */ + public function resetMetadata(): self + { + $this->metadata = []; + + return $this; + } + + /** + * Apply a transformation to a query before an event occurs + * + * @param string $event + * @param string $name + * @param ?callable $callback + * @return self + */ + public function before(string $event, string $name = '', ?callable $callback = null): self + { + if (!isset($this->transformations[$event])) { + $this->transformations[$event] = []; + } + $this->transformations[$event][$name] = $callback; + + return $this; + } + + protected function trigger(string $event, mixed $query): mixed + { + foreach ($this->transformations[Database::EVENT_ALL] as $callback) { + $query = $callback($query); + } + foreach (($this->transformations[$event] ?? []) as $callback) { + $query = $callback($query); + } + + return $query; + } + /** * Ping Database * diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 49a6533ce..1babd3d13 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -27,8 +27,12 @@ public function create(string $name): bool { $name = $this->filter($name); + $sql = "CREATE DATABASE IF NOT EXISTS `{$name}` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;"; + + $sql = $this->trigger(Database::EVENT_DATABASE_CREATE, $sql); + return $this->getPDO() - ->prepare("CREATE DATABASE IF NOT EXISTS `{$name}` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;") + ->prepare($sql) ->execute(); } @@ -44,8 +48,12 @@ public function delete(string $name): bool { $name = $this->filter($name); + $sql = "DROP DATABASE `{$name}`;"; + + $sql = $this->trigger(Database::EVENT_DATABASE_DELETE, $sql); + return $this->getPDO() - ->prepare("DROP DATABASE `{$name}`;") + ->prepare($sql) ->execute(); } @@ -103,33 +111,45 @@ public function createCollection(string $name, array $attributes = [], array $in $indexStrings[$key] = "{$indexType} `{$indexId}` (" . \implode(", ", $indexAttributes) . " ),"; } + $sql = " + CREATE TABLE IF NOT EXISTS `{$database}`.`{$namespace}_{$id}` ( + `_id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `_uid` VARCHAR(255) NOT NULL, + `_createdAt` datetime(3) DEFAULT NULL, + `_updatedAt` datetime(3) DEFAULT NULL, + `_permissions` MEDIUMTEXT DEFAULT NULL, + " . \implode(' ', $attributeStrings) . " + PRIMARY KEY (`_id`), + " . \implode(' ', $indexStrings) . " + UNIQUE KEY `_uid` (`_uid`), + KEY `_created_at` (`_createdAt`), + KEY `_updated_at` (`_updatedAt`) + ) + "; + + $sql = $this->trigger(Database::EVENT_COLLECTION_CREATE, $sql); + try { $this->getPDO() - ->prepare("CREATE TABLE IF NOT EXISTS `{$database}`.`{$namespace}_{$id}` ( - `_id` int(11) unsigned NOT NULL AUTO_INCREMENT, - `_uid` VARCHAR(255) NOT NULL, - `_createdAt` datetime(3) DEFAULT NULL, - `_updatedAt` datetime(3) DEFAULT NULL, - `_permissions` MEDIUMTEXT DEFAULT NULL, - " . \implode(' ', $attributeStrings) . " - PRIMARY KEY (`_id`), - " . \implode(' ', $indexStrings) . " - UNIQUE KEY `_uid` (`_uid`), - KEY `_created_at` (`_createdAt`), - KEY `_updated_at` (`_updatedAt`) - )") + ->prepare($sql) ->execute(); + $sql = " + CREATE TABLE IF NOT EXISTS `{$database}`.`{$namespace}_{$id}_perms` ( + `_id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `_type` VARCHAR(12) NOT NULL, + `_permission` VARCHAR(255) NOT NULL, + `_document` VARCHAR(255) NOT NULL, + PRIMARY KEY (`_id`), + UNIQUE INDEX `_index1` (`_document`,`_type`,`_permission`), + INDEX `_permission` (`_permission`,`_type`,`_document`) + ) + "; + + $sql = $this->trigger(Database::EVENT_COLLECTION_CREATE, $sql); + $this->getPDO() - ->prepare("CREATE TABLE IF NOT EXISTS `{$database}`.`{$namespace}_{$id}_perms` ( - `_id` int(11) unsigned NOT NULL AUTO_INCREMENT, - `_type` VARCHAR(12) NOT NULL, - `_permission` VARCHAR(255) NOT NULL, - `_document` VARCHAR(255) NOT NULL, - PRIMARY KEY (`_id`), - UNIQUE INDEX `_index1` (`_document`,`_type`,`_permission`), - INDEX `_permission` (`_permission`,`_type`,`_document`) - )") + ->prepare($sql) ->execute(); } catch (\Exception $th) { $this->getPDO() @@ -138,7 +158,6 @@ public function createCollection(string $name, array $attributes = [], array $in throw $th; } - // Update $this->getCountOfIndexes when adding another default index return true; } @@ -193,8 +212,12 @@ public function deleteCollection(string $id): bool { $id = $this->filter($id); + $sql = "DROP TABLE {$this->getSQLTable($id)}, {$this->getSQLTable($id . '_perms')};"; + + $sql = $this->trigger(Database::EVENT_COLLECTION_DELETE, $sql); + return $this->getPDO() - ->prepare("DROP TABLE {$this->getSQLTable($id)}, {$this->getSQLTable($id . '_perms')};") + ->prepare($sql) ->execute(); } @@ -221,8 +244,11 @@ public function createAttribute(string $collection, string $id, string $type, in $type = 'LONGTEXT'; } + $sql = "ALTER TABLE {$this->getSQLTable($name)} ADD COLUMN `{$id}` {$type};"; + $sql = $this->trigger(Database::EVENT_ATTRIBUTE_CREATE, $sql); + return $this->getPDO() - ->prepare("ALTER TABLE {$this->getSQLTable($name)} ADD COLUMN `{$id}` {$type};") + ->prepare($sql) ->execute(); } @@ -249,8 +275,12 @@ public function updateAttribute(string $collection, string $id, string $type, in $type = 'LONGTEXT'; } + $sql = "ALTER TABLE {$this->getSQLTable($name)} MODIFY `{$id}` {$type};"; + + $sql = $this->trigger(Database::EVENT_ATTRIBUTE_UPDATE, $sql); + return $this->getPDO() - ->prepare("ALTER TABLE {$this->getSQLTable($name)} MODIFY `{$id}` {$type};") + ->prepare($sql) ->execute(); } @@ -269,8 +299,12 @@ public function deleteAttribute(string $collection, string $id, bool $array = fa $name = $this->filter($collection); $id = $this->filter($id); + $sql = "ALTER TABLE {$this->getSQLTable($name)} DROP COLUMN `{$id}`;"; + + $sql = $this->trigger(Database::EVENT_ATTRIBUTE_DELETE, $sql); + return $this->getPDO() - ->prepare("ALTER TABLE {$this->getSQLTable($name)} DROP COLUMN `{$id}`;") + ->prepare($sql) ->execute(); } @@ -290,8 +324,12 @@ public function renameAttribute(string $collection, string $old, string $new): b $old = $this->filter($old); $new = $this->filter($new); + $sql = "ALTER TABLE {$this->getSQLTable($collection)} RENAME COLUMN `{$old}` TO `{$new}`;"; + + $sql = $this->trigger(Database::EVENT_ATTRIBUTE_UPDATE, $sql); + return $this->getPDO() - ->prepare("ALTER TABLE {$this->getSQLTable($collection)} RENAME COLUMN `{$old}` TO `{$new}`;") + ->prepare($sql) ->execute(); } @@ -341,6 +379,8 @@ public function createRelationship( throw new DatabaseException('Invalid relationship type'); } + $sql = $this->trigger(Database::EVENT_ATTRIBUTE_CREATE, $sql); + return $this->getPDO() ->prepare($sql) ->execute(); @@ -424,6 +464,8 @@ public function updateRelationship( return true; } + $sql = $this->trigger(Database::EVENT_ATTRIBUTE_UPDATE, $sql); + return $this->getPDO() ->prepare($sql) ->execute(); @@ -490,6 +532,8 @@ public function deleteRelationship( return true; } + $sql = $this->trigger(Database::EVENT_ATTRIBUTE_DELETE, $sql); + return $this->getPDO() ->prepare($sql) ->execute(); @@ -510,8 +554,12 @@ public function renameIndex(string $collection, string $old, string $new): bool $old = $this->filter($old); $new = $this->filter($new); + $sql = "ALTER TABLE {$this->getSQLTable($collection)} RENAME INDEX `{$old}` TO `{$new}`;"; + + $sql = $this->trigger(Database::EVENT_INDEX_RENAME, $sql); + return $this->getPDO() - ->prepare("ALTER TABLE {$this->getSQLTable($collection)} RENAME INDEX `{$old}` TO `{$new}`;") + ->prepare($sql) ->execute(); } @@ -553,8 +601,12 @@ public function createIndex(string $collection, string $id, string $type, array $attributes[$key] = "`{$attribute}`{$length} {$order}"; } + $sql = $this->getSQLIndex($name, $id, $type, $attributes); + + $sql = $this->trigger(Database::EVENT_INDEX_CREATE, $sql); + return $this->getPDO() - ->prepare($this->getSQLIndex($name, $id, $type, $attributes)) + ->prepare($sql) ->execute(); } @@ -572,8 +624,12 @@ public function deleteIndex(string $collection, string $id): bool $name = $this->filter($collection); $id = $this->filter($id); + $sql = "ALTER TABLE {$this->getSQLTable($name)} DROP INDEX `{$id}`;"; + + $sql = $this->trigger(Database::EVENT_INDEX_DELETE, $sql); + return $this->getPDO() - ->prepare("ALTER TABLE {$this->getSQLTable($name)} DROP INDEX `{$id}`;") + ->prepare($sql) ->execute(); } @@ -619,9 +675,14 @@ public function createDocument(string $collection, Document $document): Document $columnNames .= ':' . $bindKey . ', '; } - $stmt = $this->getPDO() - ->prepare("INSERT INTO {$this->getSQLTable($name)} - ({$columns}_uid) VALUES ({$columnNames}:_uid)"); + $sql = " + INSERT INTO {$this->getSQLTable($name)}({$columns} _uid) + VALUES ({$columnNames} :_uid) + "; + + $sql = $this->trigger(Database::EVENT_DOCUMENT_CREATE, $sql); + + $stmt = $this->getPDO()->prepare($sql); $stmt->bindValue(':_uid', $document->getId(), PDO::PARAM_STR); @@ -652,8 +713,14 @@ public function createDocument(string $collection, Document $document): Document } if (!empty($permissions)) { - $queryPermissions = "INSERT INTO {$this->getSQLTable($name . '_perms')} (_type, _permission, _document) VALUES " . implode(', ', $permissions); - $stmtPermissions = $this->getPDO()->prepare($queryPermissions); + $strPermissions = \implode(', ', $permissions); + + $sqlPermissions = " + INSERT INTO {$this->getSQLTable($name . '_perms')} (_type, _permission, _document) + VALUES {$strPermissions} + "; + $sqlPermissions = $this->trigger(Database::EVENT_PERMISSIONS_CREATE, $sqlPermissions); + $stmtPermissions = $this->getPDO()->prepare($sqlPermissions); } try { @@ -703,18 +770,22 @@ public function updateDocument(string $collection, Document $document): Document $name = $this->filter($collection); $columns = ''; + $sql = " + SELECT _type, _permission + FROM {$this->getSQLTable($name . '_perms')} p + WHERE p._document = :_uid + "; + + $sql = $this->trigger(Database::EVENT_PERMISSIONS_READ, $sql); + /** * Get current permissions from the database */ - $permissionsStmt = $this->getPDO()->prepare(" - SELECT _type, _permission - FROM {$this->getSQLTable($name . '_perms')} p - WHERE p._document = :_uid - "); - $permissionsStmt->bindValue(':_uid', $document->getId()); - $permissionsStmt->execute(); - $permissions = $permissionsStmt->fetchAll(); - $permissionsStmt->closeCursor(); + $sqlPermissions = $this->getPDO()->prepare($sql); + $sqlPermissions->bindValue(':_uid', $document->getId()); + $sqlPermissions->execute(); + $permissions = $sqlPermissions->fetchAll(); + $sqlPermissions->closeCursor(); $initial = []; foreach (Database::PERMISSIONS as $type) { @@ -769,14 +840,17 @@ public function updateDocument(string $collection, Document $document): Document } if (!empty($removeQuery)) { $removeQuery .= ')'; - $stmtRemovePermissions = $this->getPDO() - ->prepare(" - DELETE + $removeQuery = " + DELETE FROM {$this->getSQLTable($name . '_perms')} WHERE _document = :_uid {$removeQuery} - "); + "; + + $removeQuery = $this->trigger(Database::EVENT_PERMISSIONS_DELETE, $removeQuery); + + $stmtRemovePermissions = $this->getPDO()->prepare($removeQuery); $stmtRemovePermissions->bindValue(':_uid', $document->getId()); foreach ($removals as $type => $permissions) { @@ -797,12 +871,14 @@ public function updateDocument(string $collection, Document $document): Document } } - $stmtAddPermissions = $this->getPDO() - ->prepare( - " - INSERT INTO {$this->getSQLTable($name . '_perms')} - (_document, _type, _permission) VALUES " . \implode(', ', $values) - ); + $sql = " + INSERT INTO {$this->getSQLTable($name . '_perms')} + (_document, _type, _permission) VALUES " . \implode(', ', $values) + ; + + $sql = $this->trigger(Database::EVENT_PERMISSIONS_CREATE, $sql); + + $stmtAddPermissions = $this->getPDO()->prepare($sql); $stmtAddPermissions->bindValue(":_uid", $document->getId()); foreach ($additions as $type => $permissions) { @@ -824,9 +900,15 @@ public function updateDocument(string $collection, Document $document): Document $bindIndex++; } - $stmt = $this->getPDO() - ->prepare("UPDATE {$this->getSQLTable($name)} - SET {$columns} _uid = :_uid WHERE _uid = :_uid"); + $sql = " + UPDATE {$this->getSQLTable($name)} + SET {$columns} _uid = :_uid + WHERE _uid = :_uid + "; + + $sql = $this->trigger(Database::EVENT_DOCUMENT_UPDATE, $sql); + + $stmt = $this->getPDO()->prepare($sql); $stmt->bindValue(':_uid', $document->getId()); @@ -845,6 +927,7 @@ public function updateDocument(string $collection, Document $document): Document try { $stmt->execute(); + if (isset($stmtRemovePermissions)) { $stmtRemovePermissions->execute(); } @@ -887,10 +970,20 @@ public function increaseDocumentAttribute(string $collection, string $id, string $name = $this->filter($collection); $attribute = $this->filter($attribute); - $sqlMax = $max ? " AND `{$attribute}` <= {$max}" : ""; - $sqlMin = $min ? " AND `{$attribute}` >= {$min}" : ""; + $sqlMax = $max ? " AND `{$attribute}` <= {$max}" : ''; + $sqlMin = $min ? " AND `{$attribute}` >= {$min}" : ''; + + $sql = " + UPDATE {$this->getSQLTable($name)} + SET `{$attribute}` = `{$attribute}` + :val + WHERE + _uid = :_uid + {$sqlMax} + {$sqlMin} + "; + + $sql = $this->trigger(Database::EVENT_DOCUMENT_UPDATE, $sql); - $sql = "UPDATE {$this->getSQLTable($name)} SET `{$attribute}` = `{$attribute}` + :val WHERE _uid = :_uid" . $sqlMax . $sqlMin; $stmt = $this->getPDO()->prepare($sql); $stmt->bindValue(':_uid', $id); $stmt->bindValue(':val', $value); @@ -914,15 +1007,34 @@ public function deleteDocument(string $collection, string $id): bool $this->getPDO()->beginTransaction(); - $stmt = $this->getPDO()->prepare("DELETE FROM {$this->getSQLTable($name)} WHERE _uid = :_uid"); + $sql = " + DELETE FROM {$this->getSQLTable($name)} + WHERE _uid = :_uid + "; + + $sql = $this->trigger(Database::EVENT_DOCUMENT_DELETE, $sql); + + $stmt = $this->getPDO()->prepare($sql); + $stmt->bindValue(':_uid', $id); - $stmtPermissions = $this->getPDO()->prepare("DELETE FROM {$this->getSQLTable($name . '_perms')} WHERE _document = :_uid"); + $sql = " + DELETE FROM {$this->getSQLTable($name . '_perms')} + WHERE _document = :_uid + "; + + $sql = $this->trigger(Database::EVENT_PERMISSIONS_DELETE, $sql); + + $stmtPermissions = $this->getPDO()->prepare($sql); $stmtPermissions->bindValue(':_uid', $id); try { - $stmt->execute() || throw new DatabaseException('Failed to delete document'); - $stmtPermissions->execute() || throw new DatabaseException('Failed to clean permissions'); + if (!$stmt->execute()) { + throw new DatabaseException('Failed to delete document'); + } + if (!$stmtPermissions->execute()) { + throw new DatabaseException('Failed to clean permissions'); + } } catch (\Throwable $th) { $this->getPDO()->rollBack(); throw new DatabaseException($th->getMessage()); @@ -1059,6 +1171,8 @@ public function find(string $collection, array $queries = [], ?int $limit = 25, {$sqlLimit}; "; + $sql = $this->trigger(Database::EVENT_DOCUMENT_FIND, $sql); + if ($timeout || static::$timeout) { $sql = $this->setTimeoutForQuery($sql, $timeout ? $timeout : static::$timeout); } @@ -1079,7 +1193,7 @@ public function find(string $collection, array $queries = [], ?int $limit = 25, default => $attribute }; - if (is_null($cursor[$attribute] ?? null)) { + if (\is_null($cursor[$attribute] ?? null)) { throw new DatabaseException("Order attribute '{$attribute}' is empty"); } $stmt->bindValue(':cursor', $cursor[$attribute], $this->getPDOType($cursor[$attribute])); @@ -1127,7 +1241,7 @@ public function find(string $collection, array $queries = [], ?int $limit = 25, } if ($cursorDirection === Database::CURSOR_BEFORE) { - $results = array_reverse($results); + $results = \array_reverse($results); } return $results; @@ -1158,16 +1272,21 @@ public function count(string $collection, array $queries = [], ?int $max = null, $where[] = $this->getSQLPermissionsCondition($name, $roles); } - $sqlWhere = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : ''; - $sql = "SELECT COUNT(1) as sum - FROM - ( - SELECT 1 - FROM {$this->getSQLTable($name)} table_main - " . $sqlWhere . " - {$limit} - ) table_count + $sqlWhere = !empty($where) + ? 'WHERE ' . \implode(' AND ', $where) + : ''; + + $sql = " + SELECT COUNT(1) as sum FROM ( + SELECT 1 + FROM {$this->getSQLTable($name)} table_main + " . $sqlWhere . " + {$limit} + ) table_count "; + + $sql = $this->trigger(Database::EVENT_DOCUMENT_COUNT, $sql); + if ($timeout || self::$timeout) { $sql = $this->setTimeoutForQuery($sql, $timeout ? $timeout : self::$timeout); } @@ -1218,16 +1337,21 @@ public function sum(string $collection, string $attribute, array $queries = [], $where[] = $this->getSQLPermissionsCondition($name, $roles); } - $sqlWhere = !empty($where) ? 'where ' . implode(' AND ', $where) : ''; - $sql = "SELECT SUM({$attribute}) as sum - FROM - ( - SELECT {$attribute} - FROM {$this->getSQLTable($name)} table_main - " . $sqlWhere . " - {$limit} - ) table_count + $sqlWhere = !empty($where) + ? 'WHERE ' . \implode(' AND ', $where) + : ''; + + $sql = " + SELECT SUM({$attribute}) as sum FROM ( + SELECT {$attribute} + FROM {$this->getSQLTable($name)} table_main + {$sqlWhere} + {$limit} + ) table_count "; + + $sql = $this->trigger(Database::EVENT_DOCUMENT_SUM, $sql); + if ($timeout || self::$timeout) { $sql = $this->setTimeoutForQuery($sql, $timeout ? $timeout : self::$timeout); } diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php index 06dfeef9f..97b600123 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -36,8 +36,11 @@ public function create(string $name): bool { $name = $this->filter($name); + $sql = "CREATE SCHEMA IF NOT EXISTS \"{$name}\""; + $sql = $this->trigger(Database::EVENT_DATABASE_CREATE, $sql); + return $this->getPDO() - ->prepare("CREATE SCHEMA IF NOT EXISTS \"{$name}\"") + ->prepare($sql) ->execute(); } @@ -52,8 +55,12 @@ public function create(string $name): bool public function delete(string $name): bool { $name = $this->filter($name); + + $sql = "DROP SCHEMA IF EXISTS \"{$name}\" CASCADE"; + $sql = $this->trigger(Database::EVENT_DATABASE_DELETE, $sql); + return $this->getPDO() - ->prepare("DROP SCHEMA \"{$name}\" CASCADE;") + ->prepare($sql) ->execute(); } @@ -87,7 +94,7 @@ public function createCollection(string $name, array $attributes = [], array $in /** * @var array $attributes */ - $stmt = $this->getPDO()->prepare(" + $sql = " CREATE TABLE IF NOT EXISTS {$this->getSQLTable($id)} ( \"_id\" SERIAL NOT NULL, \"_uid\" VARCHAR(255) NOT NULL, @@ -97,29 +104,37 @@ public function createCollection(string $name, array $attributes = [], array $in " . \implode(' ', $attributes) . " PRIMARY KEY (\"_id\") ); - "); - - $stmtIndex = $this->getPDO()->prepare(" - CREATE UNIQUE INDEX \"{$namespace}_{$id}_uid\" on {$this->getSQLTable($id)} (LOWER(\"_uid\")); + + CREATE UNIQUE INDEX \"{$namespace}_{$id}_uid\" on {$this->getSQLTable($id)} (LOWER(\"_uid\")); CREATE INDEX \"{$namespace}_{$id}_created\" ON {$this->getSQLTable($id)} (\"_createdAt\"); CREATE INDEX \"{$namespace}_{$id}_updated\" ON {$this->getSQLTable($id)} (\"_updatedAt\"); - "); + "; + + $sql = $this->trigger(Database::EVENT_COLLECTION_CREATE, $sql); + + $stmt = $this->getPDO()->prepare($sql); try { $stmt->execute(); - $stmtIndex->execute(); + + $sql = " + CREATE TABLE IF NOT EXISTS {$this->getSQLTable($id . '_perms')} ( + \"_id\" SERIAL NOT NULL, + \"_type\" VARCHAR(12) NOT NULL, + \"_permission\" VARCHAR(255) NOT NULL, + \"_document\" VARCHAR(255) NOT NULL, + PRIMARY KEY (\"_id\") + ); + CREATE UNIQUE INDEX \"index_{$namespace}_{$id}_ukey\" + ON {$this->getSQLTable($id. '_perms')} USING btree (\"_document\",\"_type\",\"_permission\"); + CREATE INDEX \"index_{$namespace}_{$id}_permission\" + ON {$this->getSQLTable($id. '_perms')} USING btree (\"_permission\",\"_type\",\"_document\"); + "; + + $sql = $this->trigger(Database::EVENT_COLLECTION_CREATE, $sql); $this->getPDO() - ->prepare("CREATE TABLE IF NOT EXISTS {$this->getSQLTable($id . '_perms')} ( - \"_id\" SERIAL NOT NULL, - \"_type\" VARCHAR(12) NOT NULL, - \"_permission\" VARCHAR(255) NOT NULL, - \"_document\" VARCHAR(255) NOT NULL, - PRIMARY KEY (\"_id\") - ); - CREATE UNIQUE INDEX \"index_{$namespace}_{$id}_ukey\" ON {$this->getSQLTable($id. '_perms')} USING btree (\"_document\",\"_type\",\"_permission\"); - CREATE INDEX \"index_{$namespace}_{$id}_permission\" ON {$this->getSQLTable($id. '_perms')} USING btree (\"_permission\",\"_type\",\"_document\"); - ") + ->prepare($sql) ->execute(); foreach ($indexes as $index) { @@ -146,8 +161,6 @@ public function createCollection(string $name, array $attributes = [], array $in throw new DatabaseException('Failed to commit transaction'); } - // Update $this->getIndexCount when adding another default index - return true; } @@ -196,8 +209,11 @@ public function deleteCollection(string $id): bool { $id = $this->filter($id); + $sql = "DROP TABLE {$this->getSQLTable($id)}, {$this->getSQLTable($id . '_perms')}"; + $sql = $this->trigger(Database::EVENT_COLLECTION_DELETE, $sql); + return $this->getPDO() - ->prepare("DROP TABLE {$this->getSQLTable($id)}, {$this->getSQLTable($id . '_perms')};") + ->prepare($sql) ->execute(); } @@ -222,9 +238,15 @@ public function createAttribute(string $collection, string $id, string $type, in $type = 'TEXT'; } + $sql = " + ALTER TABLE {$this->getSQLTable($name)} + ADD COLUMN \"{$id}\" {$type} + "; + + $sql = $this->trigger(Database::EVENT_ATTRIBUTE_CREATE, $sql); + return $this->getPDO() - ->prepare("ALTER TABLE {$this->getSQLTable($name)} - ADD COLUMN \"{$id}\" {$type};") + ->prepare($sql) ->execute(); } @@ -242,9 +264,15 @@ public function deleteAttribute(string $collection, string $id, bool $array = fa $name = $this->filter($collection); $id = $this->filter($id); + $sql = " + ALTER TABLE {$this->getSQLTable($name)} + DROP COLUMN \"{$id}\"; + "; + + $sql = $this->trigger(Database::EVENT_ATTRIBUTE_DELETE, $sql); + return $this->getPDO() - ->prepare("ALTER TABLE {$this->getSQLTable($name)} - DROP COLUMN \"{$id}\";") + ->prepare($sql) ->execute(); } @@ -264,11 +292,15 @@ public function renameAttribute(string $collection, string $old, string $new): b $old = $this->filter($old); $new = $this->filter($new); + $sql = " + ALTER TABLE {$this->getSQLTable($collection)} + RENAME COLUMN \"{$old}\" TO \"{$new}\" + "; + + $sql = $this->trigger(Database::EVENT_ATTRIBUTE_UPDATE, $sql); + return $this->getPDO() - ->prepare("ALTER TABLE {$this->getSQLTable($collection)} RENAME COLUMN - \"{$old}\" - TO - \"{$new}\";") + ->prepare($sql) ->execute(); } @@ -299,9 +331,15 @@ public function updateAttribute(string $collection, string $id, string $type, in $type = "TIMESTAMP(3) without time zone USING TO_TIMESTAMP(\"$id\", 'YYYY-MM-DD HH24:MI:SS.MS')"; } + $sql = " + ALTER TABLE {$this->getSQLTable($name)} + ALTER COLUMN \"{$id}\" TYPE {$type} + "; + + $sql = $this->trigger(Database::EVENT_ATTRIBUTE_UPDATE, $sql); + return $this->getPDO() - ->prepare("ALTER TABLE {$this->getSQLTable($name)} - ALTER COLUMN \"{$id}\" TYPE {$type};") + ->prepare($sql) ->execute(); } @@ -351,6 +389,8 @@ public function createRelationship( throw new DatabaseException('Invalid relationship type'); } + $sql = $this->trigger(Database::EVENT_ATTRIBUTE_CREATE, $sql); + return $this->getPDO() ->prepare($sql) ->execute(); @@ -434,6 +474,8 @@ public function updateRelationship( return true; } + $sql = $this->trigger(Database::EVENT_ATTRIBUTE_UPDATE, $sql); + return $this->getPDO() ->prepare($sql) ->execute(); @@ -493,6 +535,8 @@ public function deleteRelationship( throw new DatabaseException('Invalid relationship type'); } + $sql = $this->trigger(Database::EVENT_ATTRIBUTE_DELETE, $sql); + return $this->getPDO() ->prepare($sql) ->execute(); @@ -539,8 +583,11 @@ public function createIndex(string $collection, string $id, string $type, array } } + $sql = $this->getSQLIndex($name, $id, $type, $attributes); + $sql = $this->trigger(Database::EVENT_INDEX_CREATE, $sql); + return $this->getPDO() - ->prepare($this->getSQLIndex($name, $id, $type, $attributes)) + ->prepare($sql) ->execute(); } @@ -559,8 +606,11 @@ public function deleteIndex(string $collection, string $id): bool $id = $this->filter($id); $schemaName = $this->getDefaultDatabase(); + $sql = "DROP INDEX IF EXISTS \"{$schemaName}\".{$id}"; + $sql = $this->trigger(Database::EVENT_INDEX_DELETE, $sql); + return $this->getPDO() - ->prepare("DROP INDEX IF EXISTS \"{$schemaName}\".{$id};") + ->prepare($sql) ->execute(); } @@ -583,8 +633,11 @@ public function renameIndex(string $collection, string $old, string $new): bool $oldIndexName = $collection . "_" . $old; $newIndexName = $namespace . $collection . "_" . $new; + $sql = "ALTER INDEX {$this->getSQLTable($oldIndexName)} RENAME TO \"{$newIndexName}\""; + $sql = $this->trigger(Database::EVENT_INDEX_RENAME, $sql); + return $this->getPDO() - ->prepare("ALTER INDEX {$this->getSQLTable($oldIndexName)} RENAME TO \"{$newIndexName}\";") + ->prepare($sql) ->execute(); } @@ -629,9 +682,14 @@ public function createDocument(string $collection, Document $document): Document $bindIndex++; } - $stmt = $this->getPDO() - ->prepare("INSERT INTO {$this->getSQLTable($name)} - ({$columns}\"_uid\") VALUES ({$columnNames}:_uid) RETURNING _id;"); + $sql = " + INSERT INTO {$this->getSQLTable($name)} ({$columns}\"_uid\") + VALUES ({$columnNames}:_uid) RETURNING _id + "; + + $sql = $this->trigger(Database::EVENT_DOCUMENT_CREATE, $sql); + + $stmt = $this->getPDO()->prepare($sql); $stmt->bindValue(':_uid', $document->getId(), PDO::PARAM_STR); @@ -663,8 +721,12 @@ public function createDocument(string $collection, Document $document): Document if (!empty($permissions)) { - $queryPermissions = "INSERT INTO {$this->getSQLTable($name . '_perms')} - (_type, _permission, _document) VALUES " . implode(', ', $permissions); + $queryPermissions = " + INSERT INTO {$this->getSQLTable($name . '_perms')} + (_type, _permission, _document) VALUES " . implode(', ', $permissions); + + $queryPermissions = $this->trigger(Database::EVENT_PERMISSIONS_CREATE, $queryPermissions); + $stmtPermissions = $this->getPDO()->prepare($queryPermissions); } @@ -712,14 +774,18 @@ public function updateDocument(string $collection, Document $document): Document $name = $this->filter($collection); $columns = ''; + $sql = " + SELECT _type, _permission + FROM {$this->getSQLTable($name . '_perms')} p + WHERE p._document = :_uid + "; + + $sql = $this->trigger(Database::EVENT_PERMISSIONS_READ, $sql); + /** * Get current permissions from the database */ - $permissionsStmt = $this->getPDO()->prepare(" - SELECT _type, _permission - FROM {$this->getSQLTable($name . '_perms')} p - WHERE p._document = :_uid - "); + $permissionsStmt = $this->getPDO()->prepare($sql); $permissionsStmt->bindValue(':_uid', $document->getId()); $permissionsStmt->execute(); $permissions = $permissionsStmt->fetchAll(); @@ -778,14 +844,15 @@ public function updateDocument(string $collection, Document $document): Document } if (!empty($removeQuery)) { $removeQuery .= ')'; - $stmtRemovePermissions = $this->getPDO() - ->prepare(" - DELETE + $removeQuery = " + DELETE FROM {$this->getSQLTable($name . '_perms')} WHERE _document = :_uid {$removeQuery} - "); + "; + $removeQuery = $this->trigger(Database::EVENT_PERMISSIONS_DELETE, $removeQuery); + $stmtRemovePermissions = $this->getPDO()->prepare($removeQuery); $stmtRemovePermissions->bindValue(':_uid', $document->getId()); foreach ($removals as $type => $permissions) { @@ -806,11 +873,13 @@ public function updateDocument(string $collection, Document $document): Document } } - $stmtAddPermissions = $this->getPDO() - ->prepare( - "INSERT INTO {$this->getSQLTable($name . '_perms')} - (_document, _type, _permission) VALUES" . \implode(', ', $values) - ); + $sql = " + INSERT INTO {$this->getSQLTable($name . '_perms')} + (_document, _type, _permission) VALUES" . \implode(', ', $values); + + $sql = $this->trigger(Database::EVENT_PERMISSIONS_CREATE, $sql); + + $stmtAddPermissions = $this->getPDO()->prepare($sql); $stmtAddPermissions->bindValue(":_uid", $document->getId()); foreach ($additions as $type => $permissions) { @@ -832,9 +901,14 @@ public function updateDocument(string $collection, Document $document): Document $bindIndex++; } - $stmt = $this->getPDO() - ->prepare("UPDATE {$this->getSQLTable($name)} - SET {$columns} _uid = :_uid WHERE _uid = :_uid"); + $sql = " + UPDATE {$this->getSQLTable($name)} + SET {$columns} _uid = :_uid WHERE _uid = :_uid + "; + + $sql = $this->trigger(Database::EVENT_DOCUMENT_UPDATE, $sql); + + $stmt = $this->getPDO()->prepare($sql); $stmt->bindValue(':_uid', $document->getId()); @@ -898,7 +972,17 @@ public function increaseDocumentAttribute(string $collection, string $id, string $sqlMax = $max ? " AND \"{$attribute}\" <= {$max}" : ""; $sqlMin = $min ? " AND \"{$attribute}\" >= {$min}" : ""; - $sql = "UPDATE {$this->getSQLTable($name)} SET \"{$attribute}\" = \"{$attribute}\" + :val WHERE _uid = :_uid" . $sqlMax . $sqlMin; + $sql = " + UPDATE {$this->getSQLTable($name)} + SET \"{$attribute}\" = \"{$attribute}\" + :val + WHERE + _uid = :_uid + {$sqlMax} + {$sqlMin} + "; + + $sql = $this->trigger(Database::EVENT_DOCUMENT_UPDATE, $sql); + $stmt = $this->getPDO()->prepare($sql); $stmt->bindValue(':_uid', $id); $stmt->bindValue(':val', $value); @@ -921,17 +1005,34 @@ public function deleteDocument(string $collection, string $id): bool $this->getPDO()->beginTransaction(); - $stmt = $this->getPDO() - ->prepare("DELETE FROM {$this->getSQLTable($name)} WHERE _uid = :_uid"); + $sql = " + DELETE FROM {$this->getSQLTable($name)} + WHERE _uid = :_uid + "; + + $sql = $this->trigger(Database::EVENT_DOCUMENT_DELETE, $sql); + + $stmt = $this->getPDO()->prepare($sql); $stmt->bindValue(':_uid', $id, PDO::PARAM_STR); - $stmtPermissions = $this->getPDO()->prepare("DELETE FROM {$this->getSQLTable($name . '_perms')} WHERE _document = :_uid"); + $sql = " + DELETE FROM {$this->getSQLTable($name . '_perms')} + WHERE _document = :_uid + "; + + $sql = $this->trigger(Database::EVENT_PERMISSIONS_DELETE, $sql); + + $stmtPermissions = $this->getPDO()->prepare($sql); $stmtPermissions->bindValue(':_uid', $id); try { - $stmt->execute() || throw new DatabaseException('Failed to delete document'); - $stmtPermissions->execute() || throw new DatabaseException('Failed to clean permissions'); + if (!$stmt->execute()) { + throw new DatabaseException('Failed to delete document'); + } + if (!$stmtPermissions->execute()) { + throw new DatabaseException('Failed to clean permissions'); + } } catch (\Throwable $th) { $this->getPDO()->rollBack(); throw new DatabaseException($th->getMessage()); @@ -1065,11 +1166,14 @@ public function find(string $collection, array $queries = [], ?int $limit = 25, {$sqlLimit}; "; + $sql = $this->trigger(Database::EVENT_DOCUMENT_FIND, $sql); + if ($timeout || self::$timeout) { - $sql = $this->setTimeoutForQuery($sql, $timeout ? $timeout : self::$timeout); + $sql = $this->setTimeoutForQuery($sql, $timeout ?: self::$timeout); } $stmt = $this->getPDO()->prepare($sql); + foreach ($queries as $query) { $this->bindConditionValue($stmt, $query); } @@ -1085,7 +1189,7 @@ public function find(string $collection, array $queries = [], ?int $limit = 25, default => $attribute }; - if (is_null($cursor[$attribute] ?? null)) { + if (\is_null($cursor[$attribute] ?? null)) { throw new DatabaseException("Order attribute '{$attribute}' is empty."); } $stmt->bindValue(':cursor', $cursor[$attribute], $this->getPDOType($cursor[$attribute])); @@ -1166,21 +1270,23 @@ public function count(string $collection, array $queries = [], ?int $max = null, } $sqlWhere = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : ''; - $sql = "SELECT COUNT(1) as sum - FROM - ( - SELECT 1 - FROM {$this->getSQLTable($name)} table_main - " . $sqlWhere . " - {$limit} - ) table_count + $sql = " + SELECT COUNT(1) as sum FROM ( + SELECT 1 + FROM {$this->getSQLTable($name)} table_main + " . $sqlWhere . " + {$limit} + ) table_count "; + $sql = $this->trigger(Database::EVENT_DOCUMENT_COUNT, $sql); + if ($timeout || self::$timeout) { - $sql = $this->setTimeoutForQuery($sql, $timeout ? $timeout : self::$timeout); + $sql = $this->setTimeoutForQuery($sql, $timeout ?: self::$timeout); } $stmt = $this->getPDO()->prepare($sql); + foreach ($queries as $query) { $this->bindConditionValue($stmt, $query); } @@ -1225,16 +1331,17 @@ public function sum(string $collection, string $attribute, array $queries = [], $where[] = $this->getSQLPermissionsCondition($name, $roles); } - $sql = "SELECT SUM({$attribute}) as sum - FROM - ( - SELECT {$attribute} - FROM {$this->getSQLTable($name)} table_main - WHERE {$permissions} AND " . implode(' AND ', $where) . " - {$limit} - ) table_count + $sql = " + SELECT SUM({$attribute}) as sum FROM ( + SELECT {$attribute} + FROM {$this->getSQLTable($name)} table_main + WHERE {$permissions} AND " . implode(' AND ', $where) . " + {$limit} + ) table_count "; + $sql = $this->trigger(Database::EVENT_DOCUMENT_SUM, $sql); + if ($timeout || self::$timeout) { $sql = $this->setTimeoutForQuery($sql, $timeout ? $timeout : self::$timeout); } diff --git a/src/Database/Adapter/SQLite.php b/src/Database/Adapter/SQLite.php index a1ee719b9..f50c66815 100644 --- a/src/Database/Adapter/SQLite.php +++ b/src/Database/Adapter/SQLite.php @@ -47,7 +47,14 @@ public function exists(string $database, ?string $collection): bool $collection = $this->filter($collection); - $stmt = $this->getPDO()->prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = :table"); + $sql = " + SELECT name FROM sqlite_master + WHERE type='table' AND name = :table + "; + + $sql = $this->trigger(Database::EVENT_DATABASE_CREATE, $sql); + + $stmt = $this->getPDO()->prepare($sql); $stmt->bindValue(':table', "{$this->getNamespace()}_{$collection}", PDO::PARAM_STR); @@ -119,15 +126,21 @@ public function createCollection(string $name, array $attributes = [], array $in $attributeStrings[$key] = "`{$attrId}` {$attrType}, "; } + $sql = " + CREATE TABLE IF NOT EXISTS `{$namespace}_{$id}` ( + `_id` INTEGER PRIMARY KEY AUTOINCREMENT, + `_uid` CHAR(255) NOT NULL, + `_createdAt` datetime(3) DEFAULT NULL, + `_updatedAt` datetime(3) DEFAULT NULL, + `_permissions` MEDIUMTEXT DEFAULT NULL".(!empty($attributes) ? ',' : '')." + " . \substr(\implode(' ', $attributeStrings), 0, -2) . " + ) + "; + + $sql = $this->trigger(Database::EVENT_COLLECTION_CREATE, $sql); + $this->getPDO() - ->prepare("CREATE TABLE IF NOT EXISTS `{$namespace}_{$id}` ( - `_id` INTEGER PRIMARY KEY AUTOINCREMENT, - `_uid` CHAR(255) NOT NULL, - `_createdAt` datetime(3) DEFAULT NULL, - `_updatedAt` datetime(3) DEFAULT NULL, - `_permissions` MEDIUMTEXT DEFAULT NULL".((!empty($attributes)) ? ',' : '')." - " . substr(\implode(' ', $attributeStrings), 0, -2) . " - )") + ->prepare($sql) ->execute(); $this->createIndex($id, '_index1', Database::INDEX_UNIQUE, ['_uid'], [], []); @@ -144,13 +157,19 @@ public function createCollection(string $name, array $attributes = [], array $in $this->createIndex($id, $indexId, $indexType, $indexAttributes, $indexLengths, $indexOrders); } + $sql = " + CREATE TABLE IF NOT EXISTS `{$namespace}_{$id}_perms` ( + `_id` INTEGER PRIMARY KEY AUTOINCREMENT, + `_type` VARCHAR(12) NOT NULL, + `_permission` VARCHAR(255) NOT NULL, + `_document` VARCHAR(255) NOT NULL + ) + "; + + $sql = $this->trigger(Database::EVENT_COLLECTION_CREATE, $sql); + $this->getPDO() - ->prepare("CREATE TABLE IF NOT EXISTS `{$namespace}_{$id}_perms` ( - `_id` INTEGER PRIMARY KEY AUTOINCREMENT, - `_type` VARCHAR(12) NOT NULL, - `_permission` VARCHAR(255) NOT NULL, - `_document` VARCHAR(255) NOT NULL - )") + ->prepare($sql) ->execute(); $this->createIndex("{$id}_perms", '_index_1', Database::INDEX_UNIQUE, ['_document', '_type', '_permission'], [], []); @@ -211,12 +230,18 @@ public function deleteCollection(string $id): bool $this->getPDO()->beginTransaction(); + $sql = "DROP TABLE `{$this->getNamespace()}_{$id}`"; + $sql = $this->trigger(Database::EVENT_COLLECTION_DELETE, $sql); + $this->getPDO() - ->prepare("DROP TABLE `{$this->getNamespace()}_{$id}`;") + ->prepare($sql) ->execute(); + $sql = "DROP TABLE `{$this->getNamespace()}_{$id}_perms`"; + $sql = $this->trigger(Database::EVENT_COLLECTION_DELETE, $sql); + $this->getPDO() - ->prepare("DROP TABLE `{$this->getNamespace()}_{$id}_perms`;") + ->prepare($sql) ->execute(); $this->getPDO()->commit(); @@ -275,9 +300,15 @@ public function deleteAttribute(string $collection, string $id, bool $array = fa } } + $sql = " + ALTER TABLE {$this->getSQLTable($name)} + DROP COLUMN `{$id}` + "; + + $sql = $this->trigger(Database::EVENT_COLLECTION_DELETE, $sql); + return $this->getPDO() - ->prepare("ALTER TABLE {$this->getSQLTable($name)} - DROP COLUMN `{$id}`;") + ->prepare($sql) ->execute(); } @@ -341,8 +372,12 @@ public function createIndex(string $collection, string $id, string $type, array $name = $this->filter($collection); $id = $this->filter($id); + $sql = $this->getSQLIndex($name, $id, $type, $attributes); + + $sql = $this->trigger(Database::EVENT_INDEX_CREATE, $sql); + return $this->getPDO() - ->prepare($this->getSQLIndex($name, $id, $type, $attributes)) + ->prepare($sql) ->execute(); } @@ -360,8 +395,11 @@ public function deleteIndex(string $collection, string $id): bool $name = $this->filter($collection); $id = $this->filter($id); + $sql = "DROP INDEX `{$this->getNamespace()}_{$name}_{$id}`"; + $sql = $this->trigger(Database::EVENT_INDEX_DELETE, $sql); + return $this->getPDO() - ->prepare("DROP INDEX `{$this->getNamespace()}_{$name}_{$id}`;") + ->prepare($sql) ->execute(); } @@ -405,9 +443,14 @@ public function createDocument(string $collection, Document $document): Document $columns[] = "_id"; } - $stmt = $this->getPDO() - ->prepare("INSERT INTO `{$this->getNamespace()}_{$name}` - (".implode(', ', $columns).") VALUES (:".implode(', :', $values).");"); + $sql = " + INSERT INTO `{$this->getNamespace()}_{$name}` (".\implode(', ', $columns).") + VALUES (:".\implode(', :', $values)."); + "; + + $sql = $this->trigger(Database::EVENT_DOCUMENT_CREATE, $sql); + + $stmt = $this->getPDO()->prepare($sql); $stmt->bindValue(':_uid', $document->getId(), PDO::PARAM_STR); @@ -438,14 +481,21 @@ public function createDocument(string $collection, Document $document): Document } if (!empty($permissions)) { - $queryPermissions = "INSERT INTO `{$this->getNamespace()}_{$name}_perms` (_type, _permission, _document) VALUES " . implode(', ', $permissions); + $queryPermissions = " + INSERT INTO `{$this->getNamespace()}_{$name}_perms` (_type, _permission, _document) + VALUES " . \implode( + ', ', + $permissions + ); + $queryPermissions = $this->trigger(Database::EVENT_PERMISSIONS_CREATE, $queryPermissions); + $stmtPermissions = $this->getPDO()->prepare($queryPermissions); } try { $stmt->execute(); - $statment = $this->getPDO()->prepare("select last_insert_rowid() as id"); + $statment = $this->getPDO()->prepare("SELECT last_insert_rowid() AS id"); $statment->execute(); $last = $statment->fetch(); $document['$internalId'] = $last['id']; @@ -488,14 +538,19 @@ public function updateDocument(string $collection, Document $document): Document $name = $this->filter($collection); $columns = ''; + + $sql = " + SELECT _type, _permission + FROM `{$this->getNamespace()}_{$name}_perms` + WHERE _document = :_uid + "; + + $sql = $this->trigger(Database::EVENT_PERMISSIONS_READ, $sql); + /** * Get current permissions from the database */ - $permissionsStmt = $this->getPDO()->prepare(" - SELECT _type, _permission - FROM `{$this->getNamespace()}_{$name}_perms` p - WHERE p._document = :_uid - "); + $permissionsStmt = $this->getPDO()->prepare($sql); $permissionsStmt->bindValue(':_uid', $document->getId()); $permissionsStmt->execute(); $permissions = $permissionsStmt->fetchAll(); @@ -554,14 +609,16 @@ public function updateDocument(string $collection, Document $document): Document } if (!empty($removeQuery)) { $removeQuery .= ')'; - $stmtRemovePermissions = $this->getPDO() - ->prepare(" - DELETE + $removeQuery = " + DELETE FROM `{$this->getNamespace()}_{$name}_perms` WHERE _document = :_uid {$removeQuery} - "); + "; + $removeQuery = $this->trigger(Database::EVENT_PERMISSIONS_DELETE, $removeQuery); + + $stmtRemovePermissions = $this->getPDO()->prepare($removeQuery); $stmtRemovePermissions->bindValue(':_uid', $document->getId()); foreach ($removals as $type => $permissions) { @@ -582,12 +639,13 @@ public function updateDocument(string $collection, Document $document): Document } } - $stmtAddPermissions = $this->getPDO() - ->prepare( - " - INSERT INTO `{$this->getNamespace()}_{$name}_perms` - (_document, _type, _permission) VALUES " . \implode(', ', $values) - ); + $sql = " + INSERT INTO `{$this->getNamespace()}_{$name}_perms` + (_document, _type, _permission) VALUES " . \implode(', ', $values); + + $sql = $this->trigger(Database::EVENT_PERMISSIONS_CREATE, $sql); + + $stmtAddPermissions = $this->getPDO()->prepare($sql); $stmtAddPermissions->bindValue(":_uid", $document->getId()); foreach ($additions as $type => $permissions) { @@ -600,7 +658,6 @@ public function updateDocument(string $collection, Document $document): Document /** * Update Attributes */ - $bindIndex = 0; foreach ($attributes as $attribute => $value) { $column = $this->filter($attribute); @@ -609,9 +666,14 @@ public function updateDocument(string $collection, Document $document): Document $bindIndex++; } - $stmt = $this->getPDO() - ->prepare("UPDATE `{$this->getNamespace()}_{$name}` - SET {$columns} _uid = :_uid WHERE _uid = :_uid"); + $sql = " + UPDATE `{$this->getNamespace()}_{$name}` + SET {$columns} _uid = :_uid WHERE _uid = :_uid + "; + + $sql = $this->trigger(Database::EVENT_DOCUMENT_UPDATE, $sql); + + $stmt = $this->getPDO()->prepare($sql); $stmt->bindValue(':_uid', $document->getId()); diff --git a/src/Database/Database.php b/src/Database/Database.php index c530af9d5..8fc1c3dbe 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -115,6 +115,10 @@ class Database public const EVENT_DOCUMENT_INCREASE = 'document_increase'; public const EVENT_DOCUMENT_DECREASE = 'document_decrease'; + public const EVENT_PERMISSIONS_CREATE = 'permissions_create'; + public const EVENT_PERMISSIONS_READ = 'permissions_read'; + public const EVENT_PERMISSIONS_DELETE = 'permissions_delete'; + public const EVENT_ATTRIBUTE_CREATE = 'attribute_create'; public const EVENT_ATTRIBUTE_UPDATE = 'attribute_update'; public const EVENT_ATTRIBUTE_DELETE = 'attribute_delete'; @@ -392,6 +396,7 @@ function (?string $value) { * Add listener to events * * @param string $event + * @param string $name * @param callable $callback * @return self */ @@ -401,6 +406,22 @@ public function on(string $event, string $name, callable $callback): self $this->listeners[$event] = []; } $this->listeners[$event][$name] = $callback; + + return $this; + } + + /** + * Add a transformation to be applied to a query string before an event occurs + * + * @param string $event + * @param string $name + * @param callable $callback + * @return $this + */ + public function before(string $event, string $name, callable $callback): self + { + $this->adapter->before($event, $name, $callback); + return $this; } @@ -468,14 +489,14 @@ protected function trigger(string $event, mixed $args = null): void if (isset($this->silentListeners[$name])) { continue; } - call_user_func($callback, $event, $args); + $callback($event, $args); } foreach (($this->listeners[$event] ?? []) as $name => $callback) { if (isset($this->silentListeners[$name])) { continue; } - call_user_func($callback, $event, $args); + $callback($event, $args); } } @@ -559,6 +580,63 @@ public function getDefaultDatabase(): string return $this->adapter->getDefaultDatabase(); } + /** + * Set a metadata value to be printed in the query comments + * + * @param string $key + * @param mixed $value + * @return self + */ + public function setMetadata(string $key, mixed $value): self + { + $this->adapter->setMetadata($key, $value); + + return $this; + } + + /** + * Get metadata + * + * @return array + */ + public function getMetadata(): array + { + return $this->adapter->getMetadata(); + } + + /** + * Clear metadata + * + * @return void + */ + public function resetMetadata(): void + { + $this->adapter->resetMetadata(); + } + + /** + * Set maximum query execution time + * + * @param int $milliseconds + * @return void + * @throws Exception + */ + public function setTimeout(int $milliseconds): void + { + $this->adapter->setTimeout($milliseconds); + } + + /** + * Clear maximum query execution time + * + * @return void + * @throws Exception + */ + public function clearTimeout(): void + { + $this->adapter->clearTimeout(); + } + /** * Ping Database * @@ -4301,15 +4379,6 @@ public function sum(string $collection, string $attribute, array $queries = [], return $sum; } - public function setTimeout(int $milliseconds): void - { - $this->adapter->setTimeout($milliseconds); - } - - public function clearTimeout(): void - { - $this->adapter->clearTimeout(); - } /** * Add Attribute Filter * diff --git a/tests/Database/Base.php b/tests/Database/Base.php index afb537ee9..99e7b41d0 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -12332,6 +12332,19 @@ public function testLabels(): void $this->assertCount(1, $documents); } + public function testMetadata(): void + { + static::getDatabase()->setMetadata('key', 'value'); + + static::getDatabase()->createCollection('testers'); + + $this->assertEquals(['key' => 'value'], static::getDatabase()->getMetadata()); + + static::getDatabase()->resetMetadata(); + + $this->assertEquals([], static::getDatabase()->getMetadata()); + } + public function testEmptyOperatorValues(): void { try { @@ -12415,6 +12428,31 @@ public function testEmptyOperatorValues(): void } } + public function testTransformations(): void + { + static::getDatabase()->createCollection('docs', attributes: [ + new Document([ + '$id' => 'name', + 'type' => Database::VAR_STRING, + 'size' => 767, + 'required' => true, + ]) + ]); + + static::getDatabase()->createDocument('docs', new Document([ + '$id' => 'doc1', + 'name' => 'value1', + ])); + + static::getDatabase()->before(Database::EVENT_DOCUMENT_READ, 'test', function (string $query) { + return "SELECT 1"; + }); + + $result = static::getDatabase()->getDocument('docs', 'doc1'); + + $this->assertTrue($result->isEmpty()); + } + public function testEvents(): void { Authorization::skip(function () {