xref: /plugin/combo/ComboStrap/Meta/Store/MetadataDbStore.php (revision 70bbd7f1f72440223cc13f3495efdcb2b0a11514)
104fd306cSNickeau<?php
204fd306cSNickeau
304fd306cSNickeau
404fd306cSNickeaunamespace ComboStrap\Meta\Store;
504fd306cSNickeau
604fd306cSNickeau
704fd306cSNickeauuse ComboStrap\DatabasePageRow;
804fd306cSNickeauuse ComboStrap\ExceptionBadArgument;
904fd306cSNickeauuse ComboStrap\ExceptionCompile;
1004fd306cSNickeauuse ComboStrap\ExceptionNotExists;
1104fd306cSNickeauuse ComboStrap\ExceptionNotFound;
1204fd306cSNickeauuse ComboStrap\ExceptionRuntime;
1304fd306cSNickeauuse ComboStrap\ExceptionRuntimeInternal;
1404fd306cSNickeauuse ComboStrap\ExceptionSqliteNotAvailable;
1504fd306cSNickeauuse ComboStrap\LogUtility;
1604fd306cSNickeauuse ComboStrap\MarkupPath;
1704fd306cSNickeauuse ComboStrap\Meta\Api\Metadata;
1804fd306cSNickeauuse ComboStrap\Meta\Store\MetadataDokuWikiStore;
1904fd306cSNickeauuse ComboStrap\Meta\Api\MetadataStore;
2004fd306cSNickeauuse ComboStrap\Meta\Api\MetadataStoreAbs;
2104fd306cSNickeauuse ComboStrap\Meta\Api\MetadataTabular;
2204fd306cSNickeauuse ComboStrap\ResourceCombo;
2304fd306cSNickeauuse ComboStrap\Sqlite;
2404fd306cSNickeau
2504fd306cSNickeau/**
2604fd306cSNickeau * Class MetadataDbStore
2704fd306cSNickeau * @package ComboStrap
2804fd306cSNickeau * The database store
2904fd306cSNickeau * TODO: {@link DatabasePageRow} should be integrated into MetadataDbStore
3004fd306cSNickeau *   A tabular metadata should be created to get all {@link DatabasePageRow::getMetaRecord()}
3104fd306cSNickeau */
3204fd306cSNickeauclass MetadataDbStore extends MetadataStoreAbs implements MetadataStore
3304fd306cSNickeau{
3404fd306cSNickeau    const CANONICAL = "database";
3504fd306cSNickeau
3604fd306cSNickeau    /**
3704fd306cSNickeau     * @var DatabasePageRow[]
3804fd306cSNickeau     */
3904fd306cSNickeau    private static array $dbRows = [];
4004fd306cSNickeau    private Sqlite $sqlite;
4104fd306cSNickeau
4204fd306cSNickeau    /**
4304fd306cSNickeau     * @var Metadata - the uid metadata
4404fd306cSNickeau     * They are here to throw at construct time
4504fd306cSNickeau     */
4604fd306cSNickeau    private Metadata $resourceUidMeta;
4704fd306cSNickeau    /**
4804fd306cSNickeau     * @var mixed - the uid metadata value
4904fd306cSNickeau     * They are here to throw at construct time
5004fd306cSNickeau     */
5104fd306cSNickeau    private $resourceUidMetaValue;
5204fd306cSNickeau
5304fd306cSNickeau    /**
5404fd306cSNickeau     * @throws ExceptionSqliteNotAvailable
5504fd306cSNickeau     * @throws ExceptionNotExists - if the resource does not exist in the database
5604fd306cSNickeau     */
5704fd306cSNickeau    public function __construct(ResourceCombo $resource)
5804fd306cSNickeau    {
5904fd306cSNickeau        // sqlite in the constructor to handle only one sqlite exception
6004fd306cSNickeau        $this->sqlite = Sqlite::createOrGetSqlite();
6104fd306cSNickeau
6204fd306cSNickeau        // uid of the resoure (the old page id)
6304fd306cSNickeau        $this->resourceUidMeta = $resource->getUid();
6404fd306cSNickeau        $persistentName = $this->resourceUidMeta::getPersistentName();
6504fd306cSNickeau        /**
6604fd306cSNickeau         * If  uid is null, it's not yet in the database
6704fd306cSNickeau         * and returns the default or null, or empty array
6804fd306cSNickeau         */
6904fd306cSNickeau        $this->resourceUidMetaValue = MetadataDokuWikiStore::getOrCreateFromResource($resource)
7004fd306cSNickeau            ->getFromName($persistentName);
7104fd306cSNickeau
7204fd306cSNickeau        parent::__construct($resource);
7304fd306cSNickeau    }
7404fd306cSNickeau
7504fd306cSNickeau
7604fd306cSNickeau    /**
7704fd306cSNickeau     * @throws ExceptionNotExists
7804fd306cSNickeau     * @throws ExceptionSqliteNotAvailable
7904fd306cSNickeau     */
8004fd306cSNickeau    static function getOrCreateFromResource(ResourceCombo $resourceCombo): MetadataStore
8104fd306cSNickeau    {
8204fd306cSNickeau        return new MetadataDbStore($resourceCombo);
8304fd306cSNickeau    }
8404fd306cSNickeau
8504fd306cSNickeau    public static function resetAll()
8604fd306cSNickeau    {
8704fd306cSNickeau        self::$dbRows = [];
8804fd306cSNickeau    }
8904fd306cSNickeau
9004fd306cSNickeau    public function set(Metadata $metadata)
9104fd306cSNickeau    {
9204fd306cSNickeau        if ($metadata instanceof MetadataTabular) {
9304fd306cSNickeau
9404fd306cSNickeau            $this->syncTabular($metadata);
9504fd306cSNickeau            return;
9604fd306cSNickeau        }
9704fd306cSNickeau
9804fd306cSNickeau        throw new ExceptionRuntime("The metadata ($metadata) is not yet supported on set", self::CANONICAL);
9904fd306cSNickeau
10004fd306cSNickeau    }
10104fd306cSNickeau
10204fd306cSNickeau    public function get(Metadata $metadata, $default = null)
10304fd306cSNickeau    {
10404fd306cSNickeau
10504fd306cSNickeau        $resource = $metadata->getResource();
10604fd306cSNickeau        if (!($resource instanceof MarkupPath)) {
10704fd306cSNickeau            throw new ExceptionRuntime("The resource type ({$resource->getType()}) is not yet supported for the database metadata store", self::CANONICAL);
10804fd306cSNickeau        }
10904fd306cSNickeau
11004fd306cSNickeau        if ($metadata instanceof MetadataTabular) {
11104fd306cSNickeau
11204fd306cSNickeau            return $this->getDbTabularData($metadata);
11304fd306cSNickeau
11404fd306cSNickeau        } else {
11504fd306cSNickeau
11604fd306cSNickeau            $pageMetaFromFileSystem = MarkupPath::createPageFromAbsoluteId($resource->getPathObject()->toAbsoluteId());
11704fd306cSNickeau            $fsStore = MetadataDokuWikiStore::getOrCreateFromResource($pageMetaFromFileSystem);
11804fd306cSNickeau            $pageMetaFromFileSystem->setReadStore($fsStore);
11904fd306cSNickeau
12004fd306cSNickeau            $database = DatabasePageRow::getOrCreateFromPageObject($pageMetaFromFileSystem);
12104fd306cSNickeau            if (!$database->exists()) {
12204fd306cSNickeau                return null;
12304fd306cSNickeau            }
12404fd306cSNickeau            return $database->getFromRow($metadata->getName());
12504fd306cSNickeau
12604fd306cSNickeau        }
12704fd306cSNickeau    }
12804fd306cSNickeau
12904fd306cSNickeau    /**
13004fd306cSNickeau     *
13104fd306cSNickeau     */
13204fd306cSNickeau    private function syncTabular(MetadataTabular $metadata)
13304fd306cSNickeau    {
13404fd306cSNickeau
13504fd306cSNickeau        try {
13604fd306cSNickeau            $uid = $metadata->getUidObject();
13704fd306cSNickeau        } catch (ExceptionBadArgument $e) {
13804fd306cSNickeau            throw new ExceptionRuntimeInternal("The uid class should be defined for the metadata ($metadata)");
13904fd306cSNickeau        }
14004fd306cSNickeau
14104fd306cSNickeau        $sourceRows = $metadata->toStoreValue();
14204fd306cSNickeau        if ($sourceRows === null) {
14304fd306cSNickeau            return;
14404fd306cSNickeau        }
14504fd306cSNickeau
14604fd306cSNickeau        $targetRows = $this->getDbTabularData($metadata);
14704fd306cSNickeau        foreach ($targetRows as $targetRow) {
14804fd306cSNickeau            $targetRowId = $targetRow[$uid::getPersistentName()];
14904fd306cSNickeau            if (isset($sourceRows[$targetRowId])) {
15004fd306cSNickeau                unset($sourceRows[$targetRowId]);
15104fd306cSNickeau            } else {
15204fd306cSNickeau                $this->deleteRow($targetRow, $metadata);
15304fd306cSNickeau            }
15404fd306cSNickeau        }
15504fd306cSNickeau
15604fd306cSNickeau        foreach ($sourceRows as $sourceRow) {
15704fd306cSNickeau            $this->addRow($sourceRow, $metadata);
15804fd306cSNickeau        }
15904fd306cSNickeau
16004fd306cSNickeau    }
16104fd306cSNickeau
16204fd306cSNickeau
16304fd306cSNickeau    /**
16404fd306cSNickeau     * @param array $row
16504fd306cSNickeau     * @param Metadata $metadata
16604fd306cSNickeau     * @return void
16704fd306cSNickeau     */
16804fd306cSNickeau    private function addRow(array $row, Metadata $metadata): void
16904fd306cSNickeau    {
17004fd306cSNickeau
17104fd306cSNickeau        /**
17204fd306cSNickeau         * Add the id
17304fd306cSNickeau         */
17404fd306cSNickeau        $resourceCombo = $metadata->getResource();
17504fd306cSNickeau        $resourceUidObject = $resourceCombo->getUidObject();
17604fd306cSNickeau        try {
17704fd306cSNickeau            $uidValue = $resourceUidObject->getValue();
17804fd306cSNickeau        } catch (ExceptionNotFound $e) {
17904fd306cSNickeau            // not yet in db
18004fd306cSNickeau            return;
18104fd306cSNickeau        }
18204fd306cSNickeau
18304fd306cSNickeau        $row[$resourceUidObject::getPersistentName()] = $uidValue;
18404fd306cSNickeau
18504fd306cSNickeau        $tableName = $this->getTableName($metadata);
18604fd306cSNickeau        $request = $this->sqlite
18704fd306cSNickeau            ->createRequest()
18804fd306cSNickeau            ->setTableRow($tableName, $row);
18904fd306cSNickeau        try {
19004fd306cSNickeau            $request->execute();
19104fd306cSNickeau        } catch (ExceptionCompile $e) {
19204fd306cSNickeau            LogUtility::msg("There was a problem during rows insertion for the table ($tableName)" . $e->getMessage());
19304fd306cSNickeau        } finally {
19404fd306cSNickeau            $request->close();
19504fd306cSNickeau        }
19604fd306cSNickeau
19704fd306cSNickeau
19804fd306cSNickeau    }
19904fd306cSNickeau
20004fd306cSNickeau    /**
20104fd306cSNickeau     * @param array $row
20204fd306cSNickeau     * @param Metadata $metadata
20304fd306cSNickeau     */
20404fd306cSNickeau    private function deleteRow(array $row, Metadata $metadata): void
20504fd306cSNickeau    {
20604fd306cSNickeau        $tableName = $this->getTableName($metadata);
20704fd306cSNickeau        $resourceIdAttribute = $metadata->getResource()->getUidObject()::getPersistentName();
20804fd306cSNickeau        $metadataIdAttribute = $metadata->getUidObject()::getPersistentName();
20904fd306cSNickeau        $delete = <<<EOF
21004fd306cSNickeaudelete from $tableName where $resourceIdAttribute = ? and $metadataIdAttribute = ?
21104fd306cSNickeauEOF;
21204fd306cSNickeau
21304fd306cSNickeau        $row = [
214*70bbd7f1Sgerardnico            $row[$resourceIdAttribute] ?? null,
215*70bbd7f1Sgerardnico            $row[$metadataIdAttribute] ?? null
21604fd306cSNickeau        ];
21704fd306cSNickeau        $request = Sqlite::createOrGetSqlite()
21804fd306cSNickeau            ->createRequest()
21904fd306cSNickeau            ->setQueryParametrized($delete, $row);
22004fd306cSNickeau        try {
22104fd306cSNickeau            $request->execute();
22204fd306cSNickeau        } catch (ExceptionCompile $e) {
22304fd306cSNickeau            LogUtility::msg("There was a problem during the row delete of $tableName. Message: {$e->getMessage()}");
22404fd306cSNickeau            return;
22504fd306cSNickeau        } finally {
22604fd306cSNickeau            $request->close();
22704fd306cSNickeau        }
22804fd306cSNickeau
22904fd306cSNickeau
23004fd306cSNickeau    }
23104fd306cSNickeau
23204fd306cSNickeau
23304fd306cSNickeau    /**
23404fd306cSNickeau     * @return array - the rows
23504fd306cSNickeau     * @var Metadata $metadata
23604fd306cSNickeau     */
23704fd306cSNickeau    private function getDbTabularData(Metadata $metadata): array
23804fd306cSNickeau    {
23904fd306cSNickeau
24004fd306cSNickeau        $uid = $this->resourceUidMeta;
24104fd306cSNickeau        $uidValue = $this->resourceUidMetaValue;
24204fd306cSNickeau        if ($uidValue === null) {
24304fd306cSNickeau            // no yet in the db
24404fd306cSNickeau            return [];
24504fd306cSNickeau        }
24604fd306cSNickeau
24704fd306cSNickeau        $uidAttribute = $uid::getPersistentName();
24804fd306cSNickeau        $children = $metadata->getChildrenObject();
24904fd306cSNickeau        if ($children === null) {
25004fd306cSNickeau            throw new ExceptionRuntimeInternal("The children of the tabular metadata ($metadata) should be set to synchronize into the database");
25104fd306cSNickeau        }
25204fd306cSNickeau        $attributes = [];
25304fd306cSNickeau        foreach ($children as $child) {
25404fd306cSNickeau            $attributes[] = $child::getPersistentName();
25504fd306cSNickeau        }
25604fd306cSNickeau        $tableName = $this->getTableName($metadata);
25704fd306cSNickeau        $query = Sqlite::createSelectFromTableAndColumns($tableName, $attributes);
25804fd306cSNickeau        $query = "$query where $uidAttribute = ? ";
25904fd306cSNickeau        $res = $this->sqlite
26004fd306cSNickeau            ->createRequest()
26104fd306cSNickeau            ->setQueryParametrized($query, [$uidValue]);
26204fd306cSNickeau        $rows = [];
26304fd306cSNickeau        try {
26404fd306cSNickeau            $rows = $res
26504fd306cSNickeau                ->execute()
26604fd306cSNickeau                ->getRows();
26704fd306cSNickeau        } catch (ExceptionCompile $e) {
26804fd306cSNickeau            throw new ExceptionRuntimeInternal("An exception has occurred with the $tableName ({$metadata->getResource()}) selection query. Message: {$e->getMessage()}, Query: ($query", self::CANONICAL, 1, $e);
26904fd306cSNickeau        } finally {
27004fd306cSNickeau            $res->close();
27104fd306cSNickeau        }
27204fd306cSNickeau        return $rows;
27304fd306cSNickeau
27404fd306cSNickeau    }
27504fd306cSNickeau
27604fd306cSNickeau    public function persist()
27704fd306cSNickeau    {
27804fd306cSNickeau        // there is no notion of commit in the sqlite plugin
27904fd306cSNickeau    }
28004fd306cSNickeau
28104fd306cSNickeau    public function isHierarchicalTextBased(): bool
28204fd306cSNickeau    {
28304fd306cSNickeau        return false;
28404fd306cSNickeau    }
28504fd306cSNickeau
28604fd306cSNickeau    public function reset()
28704fd306cSNickeau    {
28804fd306cSNickeau        throw new ExceptionRuntime("To implement");
28904fd306cSNickeau    }
29004fd306cSNickeau
29104fd306cSNickeau    public function getFromName(string $name, $default = null)
29204fd306cSNickeau    {
29304fd306cSNickeau
29404fd306cSNickeau        if ($this->resourceUidMetaValue === null) {
29504fd306cSNickeau            // not yet in the db
29604fd306cSNickeau            return $default;
29704fd306cSNickeau        }
29804fd306cSNickeau
29904fd306cSNickeau        $row = $this->getDatabaseRow();
30004fd306cSNickeau        $value = $row->getFromRow($name);
30104fd306cSNickeau        if ($value !== null) {
30204fd306cSNickeau            return $value;
30304fd306cSNickeau        }
30404fd306cSNickeau        return $default;
30504fd306cSNickeau    }
30604fd306cSNickeau
30704fd306cSNickeau    public function setFromPersistentName(string $name, $value, $default = null)
30804fd306cSNickeau    {
30904fd306cSNickeau        throw new ExceptionRuntime("Not implemented");
31004fd306cSNickeau    }
31104fd306cSNickeau
31204fd306cSNickeau
31304fd306cSNickeau    private function getTableName(Metadata $metadata): string
31404fd306cSNickeau    {
31504fd306cSNickeau        return $metadata->getResource()->getType() . "_" . $metadata::getPersistentName();
31604fd306cSNickeau
31704fd306cSNickeau    }
31804fd306cSNickeau
31904fd306cSNickeau    public function getCanonical(): string
32004fd306cSNickeau    {
32104fd306cSNickeau        return self::CANONICAL;
32204fd306cSNickeau    }
32304fd306cSNickeau
32404fd306cSNickeau    private function getDatabaseRow(): DatabasePageRow
32504fd306cSNickeau    {
32604fd306cSNickeau        $mapKey = $this->getResource()->getPathObject()->toAbsoluteId();
32704fd306cSNickeau        $row = self::$dbRows[$mapKey];
32804fd306cSNickeau        if ($row === null) {
32904fd306cSNickeau            $page = $this->getResource();
33004fd306cSNickeau            $row = DatabasePageRow::getFromPageObject($page);
33104fd306cSNickeau            self::$dbRows[$mapKey] = $row;
33204fd306cSNickeau        }
33304fd306cSNickeau        return $row;
33404fd306cSNickeau    }
33504fd306cSNickeau
33604fd306cSNickeau
33704fd306cSNickeau}
338