xref: /plugin/combo/ComboStrap/Meta/Store/MetadataDbStore.php (revision 04fd306c7c155fa133ebb3669986875d65988276)
1*04fd306cSNickeau<?php
2*04fd306cSNickeau
3*04fd306cSNickeau
4*04fd306cSNickeaunamespace ComboStrap\Meta\Store;
5*04fd306cSNickeau
6*04fd306cSNickeau
7*04fd306cSNickeauuse ComboStrap\DatabasePageRow;
8*04fd306cSNickeauuse ComboStrap\ExceptionBadArgument;
9*04fd306cSNickeauuse ComboStrap\ExceptionCompile;
10*04fd306cSNickeauuse ComboStrap\ExceptionNotExists;
11*04fd306cSNickeauuse ComboStrap\ExceptionNotFound;
12*04fd306cSNickeauuse ComboStrap\ExceptionRuntime;
13*04fd306cSNickeauuse ComboStrap\ExceptionRuntimeInternal;
14*04fd306cSNickeauuse ComboStrap\ExceptionSqliteNotAvailable;
15*04fd306cSNickeauuse ComboStrap\LogUtility;
16*04fd306cSNickeauuse ComboStrap\MarkupPath;
17*04fd306cSNickeauuse ComboStrap\Meta\Api\Metadata;
18*04fd306cSNickeauuse ComboStrap\Meta\Store\MetadataDokuWikiStore;
19*04fd306cSNickeauuse ComboStrap\Meta\Api\MetadataStore;
20*04fd306cSNickeauuse ComboStrap\Meta\Api\MetadataStoreAbs;
21*04fd306cSNickeauuse ComboStrap\Meta\Api\MetadataTabular;
22*04fd306cSNickeauuse ComboStrap\ResourceCombo;
23*04fd306cSNickeauuse ComboStrap\Sqlite;
24*04fd306cSNickeau
25*04fd306cSNickeau/**
26*04fd306cSNickeau * Class MetadataDbStore
27*04fd306cSNickeau * @package ComboStrap
28*04fd306cSNickeau * The database store
29*04fd306cSNickeau * TODO: {@link DatabasePageRow} should be integrated into MetadataDbStore
30*04fd306cSNickeau *   A tabular metadata should be created to get all {@link DatabasePageRow::getMetaRecord()}
31*04fd306cSNickeau */
32*04fd306cSNickeauclass MetadataDbStore extends MetadataStoreAbs implements MetadataStore
33*04fd306cSNickeau{
34*04fd306cSNickeau    const CANONICAL = "database";
35*04fd306cSNickeau
36*04fd306cSNickeau    /**
37*04fd306cSNickeau     * @var DatabasePageRow[]
38*04fd306cSNickeau     */
39*04fd306cSNickeau    private static array $dbRows = [];
40*04fd306cSNickeau    private Sqlite $sqlite;
41*04fd306cSNickeau
42*04fd306cSNickeau    /**
43*04fd306cSNickeau     * @var Metadata - the uid metadata
44*04fd306cSNickeau     * They are here to throw at construct time
45*04fd306cSNickeau     */
46*04fd306cSNickeau    private Metadata $resourceUidMeta;
47*04fd306cSNickeau    /**
48*04fd306cSNickeau     * @var mixed - the uid metadata value
49*04fd306cSNickeau     * They are here to throw at construct time
50*04fd306cSNickeau     */
51*04fd306cSNickeau    private $resourceUidMetaValue;
52*04fd306cSNickeau
53*04fd306cSNickeau    /**
54*04fd306cSNickeau     * @throws ExceptionSqliteNotAvailable
55*04fd306cSNickeau     * @throws ExceptionNotExists - if the resource does not exist in the database
56*04fd306cSNickeau     */
57*04fd306cSNickeau    public function __construct(ResourceCombo $resource)
58*04fd306cSNickeau    {
59*04fd306cSNickeau        // sqlite in the constructor to handle only one sqlite exception
60*04fd306cSNickeau        $this->sqlite = Sqlite::createOrGetSqlite();
61*04fd306cSNickeau
62*04fd306cSNickeau        // uid of the resoure (the old page id)
63*04fd306cSNickeau        $this->resourceUidMeta = $resource->getUid();
64*04fd306cSNickeau        $persistentName = $this->resourceUidMeta::getPersistentName();
65*04fd306cSNickeau        /**
66*04fd306cSNickeau         * If  uid is null, it's not yet in the database
67*04fd306cSNickeau         * and returns the default or null, or empty array
68*04fd306cSNickeau         */
69*04fd306cSNickeau        $this->resourceUidMetaValue = MetadataDokuWikiStore::getOrCreateFromResource($resource)
70*04fd306cSNickeau            ->getFromName($persistentName);
71*04fd306cSNickeau
72*04fd306cSNickeau        parent::__construct($resource);
73*04fd306cSNickeau    }
74*04fd306cSNickeau
75*04fd306cSNickeau
76*04fd306cSNickeau    /**
77*04fd306cSNickeau     * @throws ExceptionNotExists
78*04fd306cSNickeau     * @throws ExceptionSqliteNotAvailable
79*04fd306cSNickeau     */
80*04fd306cSNickeau    static function getOrCreateFromResource(ResourceCombo $resourceCombo): MetadataStore
81*04fd306cSNickeau    {
82*04fd306cSNickeau        return new MetadataDbStore($resourceCombo);
83*04fd306cSNickeau    }
84*04fd306cSNickeau
85*04fd306cSNickeau    public static function resetAll()
86*04fd306cSNickeau    {
87*04fd306cSNickeau        self::$dbRows = [];
88*04fd306cSNickeau    }
89*04fd306cSNickeau
90*04fd306cSNickeau    public function set(Metadata $metadata)
91*04fd306cSNickeau    {
92*04fd306cSNickeau        if ($metadata instanceof MetadataTabular) {
93*04fd306cSNickeau
94*04fd306cSNickeau            $this->syncTabular($metadata);
95*04fd306cSNickeau            return;
96*04fd306cSNickeau        }
97*04fd306cSNickeau
98*04fd306cSNickeau        throw new ExceptionRuntime("The metadata ($metadata) is not yet supported on set", self::CANONICAL);
99*04fd306cSNickeau
100*04fd306cSNickeau    }
101*04fd306cSNickeau
102*04fd306cSNickeau    public function get(Metadata $metadata, $default = null)
103*04fd306cSNickeau    {
104*04fd306cSNickeau
105*04fd306cSNickeau        $resource = $metadata->getResource();
106*04fd306cSNickeau        if (!($resource instanceof MarkupPath)) {
107*04fd306cSNickeau            throw new ExceptionRuntime("The resource type ({$resource->getType()}) is not yet supported for the database metadata store", self::CANONICAL);
108*04fd306cSNickeau        }
109*04fd306cSNickeau
110*04fd306cSNickeau        if ($metadata instanceof MetadataTabular) {
111*04fd306cSNickeau
112*04fd306cSNickeau            return $this->getDbTabularData($metadata);
113*04fd306cSNickeau
114*04fd306cSNickeau        } else {
115*04fd306cSNickeau
116*04fd306cSNickeau            $pageMetaFromFileSystem = MarkupPath::createPageFromAbsoluteId($resource->getPathObject()->toAbsoluteId());
117*04fd306cSNickeau            $fsStore = MetadataDokuWikiStore::getOrCreateFromResource($pageMetaFromFileSystem);
118*04fd306cSNickeau            $pageMetaFromFileSystem->setReadStore($fsStore);
119*04fd306cSNickeau
120*04fd306cSNickeau            $database = DatabasePageRow::getOrCreateFromPageObject($pageMetaFromFileSystem);
121*04fd306cSNickeau            if (!$database->exists()) {
122*04fd306cSNickeau                return null;
123*04fd306cSNickeau            }
124*04fd306cSNickeau            return $database->getFromRow($metadata->getName());
125*04fd306cSNickeau
126*04fd306cSNickeau        }
127*04fd306cSNickeau    }
128*04fd306cSNickeau
129*04fd306cSNickeau    /**
130*04fd306cSNickeau     *
131*04fd306cSNickeau     */
132*04fd306cSNickeau    private function syncTabular(MetadataTabular $metadata)
133*04fd306cSNickeau    {
134*04fd306cSNickeau
135*04fd306cSNickeau        try {
136*04fd306cSNickeau            $uid = $metadata->getUidObject();
137*04fd306cSNickeau        } catch (ExceptionBadArgument $e) {
138*04fd306cSNickeau            throw new ExceptionRuntimeInternal("The uid class should be defined for the metadata ($metadata)");
139*04fd306cSNickeau        }
140*04fd306cSNickeau
141*04fd306cSNickeau        $sourceRows = $metadata->toStoreValue();
142*04fd306cSNickeau        if ($sourceRows === null) {
143*04fd306cSNickeau            return;
144*04fd306cSNickeau        }
145*04fd306cSNickeau
146*04fd306cSNickeau        $targetRows = $this->getDbTabularData($metadata);
147*04fd306cSNickeau        foreach ($targetRows as $targetRow) {
148*04fd306cSNickeau            $targetRowId = $targetRow[$uid::getPersistentName()];
149*04fd306cSNickeau            if (isset($sourceRows[$targetRowId])) {
150*04fd306cSNickeau                unset($sourceRows[$targetRowId]);
151*04fd306cSNickeau            } else {
152*04fd306cSNickeau                $this->deleteRow($targetRow, $metadata);
153*04fd306cSNickeau            }
154*04fd306cSNickeau        }
155*04fd306cSNickeau
156*04fd306cSNickeau        foreach ($sourceRows as $sourceRow) {
157*04fd306cSNickeau            $this->addRow($sourceRow, $metadata);
158*04fd306cSNickeau        }
159*04fd306cSNickeau
160*04fd306cSNickeau    }
161*04fd306cSNickeau
162*04fd306cSNickeau
163*04fd306cSNickeau    /**
164*04fd306cSNickeau     * @param array $row
165*04fd306cSNickeau     * @param Metadata $metadata
166*04fd306cSNickeau     * @return void
167*04fd306cSNickeau     */
168*04fd306cSNickeau    private function addRow(array $row, Metadata $metadata): void
169*04fd306cSNickeau    {
170*04fd306cSNickeau
171*04fd306cSNickeau        /**
172*04fd306cSNickeau         * Add the id
173*04fd306cSNickeau         */
174*04fd306cSNickeau        $resourceCombo = $metadata->getResource();
175*04fd306cSNickeau        $resourceUidObject = $resourceCombo->getUidObject();
176*04fd306cSNickeau        try {
177*04fd306cSNickeau            $uidValue = $resourceUidObject->getValue();
178*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
179*04fd306cSNickeau            // not yet in db
180*04fd306cSNickeau            return;
181*04fd306cSNickeau        }
182*04fd306cSNickeau
183*04fd306cSNickeau        $row[$resourceUidObject::getPersistentName()] = $uidValue;
184*04fd306cSNickeau
185*04fd306cSNickeau        $tableName = $this->getTableName($metadata);
186*04fd306cSNickeau        $request = $this->sqlite
187*04fd306cSNickeau            ->createRequest()
188*04fd306cSNickeau            ->setTableRow($tableName, $row);
189*04fd306cSNickeau        try {
190*04fd306cSNickeau            $request->execute();
191*04fd306cSNickeau        } catch (ExceptionCompile $e) {
192*04fd306cSNickeau            LogUtility::msg("There was a problem during rows insertion for the table ($tableName)" . $e->getMessage());
193*04fd306cSNickeau        } finally {
194*04fd306cSNickeau            $request->close();
195*04fd306cSNickeau        }
196*04fd306cSNickeau
197*04fd306cSNickeau
198*04fd306cSNickeau    }
199*04fd306cSNickeau
200*04fd306cSNickeau    /**
201*04fd306cSNickeau     * @param array $row
202*04fd306cSNickeau     * @param Metadata $metadata
203*04fd306cSNickeau     */
204*04fd306cSNickeau    private function deleteRow(array $row, Metadata $metadata): void
205*04fd306cSNickeau    {
206*04fd306cSNickeau        $tableName = $this->getTableName($metadata);
207*04fd306cSNickeau        $resourceIdAttribute = $metadata->getResource()->getUidObject()::getPersistentName();
208*04fd306cSNickeau        $metadataIdAttribute = $metadata->getUidObject()::getPersistentName();
209*04fd306cSNickeau        $delete = <<<EOF
210*04fd306cSNickeaudelete from $tableName where $resourceIdAttribute = ? and $metadataIdAttribute = ?
211*04fd306cSNickeauEOF;
212*04fd306cSNickeau
213*04fd306cSNickeau        $row = [
214*04fd306cSNickeau            $resourceIdAttribute => $row[$resourceIdAttribute],
215*04fd306cSNickeau            $metadataIdAttribute => $row[$metadataIdAttribute]
216*04fd306cSNickeau        ];
217*04fd306cSNickeau        $request = Sqlite::createOrGetSqlite()
218*04fd306cSNickeau            ->createRequest()
219*04fd306cSNickeau            ->setQueryParametrized($delete, $row);
220*04fd306cSNickeau        try {
221*04fd306cSNickeau            $request->execute();
222*04fd306cSNickeau        } catch (ExceptionCompile $e) {
223*04fd306cSNickeau            LogUtility::msg("There was a problem during the row delete of $tableName. Message: {$e->getMessage()}");
224*04fd306cSNickeau            return;
225*04fd306cSNickeau        } finally {
226*04fd306cSNickeau            $request->close();
227*04fd306cSNickeau        }
228*04fd306cSNickeau
229*04fd306cSNickeau
230*04fd306cSNickeau    }
231*04fd306cSNickeau
232*04fd306cSNickeau
233*04fd306cSNickeau    /**
234*04fd306cSNickeau     * @return array - the rows
235*04fd306cSNickeau     * @var Metadata $metadata
236*04fd306cSNickeau     */
237*04fd306cSNickeau    private function getDbTabularData(Metadata $metadata): array
238*04fd306cSNickeau    {
239*04fd306cSNickeau
240*04fd306cSNickeau        $uid = $this->resourceUidMeta;
241*04fd306cSNickeau        $uidValue = $this->resourceUidMetaValue;
242*04fd306cSNickeau        if ($uidValue === null) {
243*04fd306cSNickeau            // no yet in the db
244*04fd306cSNickeau            return [];
245*04fd306cSNickeau        }
246*04fd306cSNickeau
247*04fd306cSNickeau        $uidAttribute = $uid::getPersistentName();
248*04fd306cSNickeau        $children = $metadata->getChildrenObject();
249*04fd306cSNickeau        if ($children === null) {
250*04fd306cSNickeau            throw new ExceptionRuntimeInternal("The children of the tabular metadata ($metadata) should be set to synchronize into the database");
251*04fd306cSNickeau        }
252*04fd306cSNickeau        $attributes = [];
253*04fd306cSNickeau        foreach ($children as $child) {
254*04fd306cSNickeau            $attributes[] = $child::getPersistentName();
255*04fd306cSNickeau        }
256*04fd306cSNickeau        $tableName = $this->getTableName($metadata);
257*04fd306cSNickeau        $query = Sqlite::createSelectFromTableAndColumns($tableName, $attributes);
258*04fd306cSNickeau        $query = "$query where $uidAttribute = ? ";
259*04fd306cSNickeau        $res = $this->sqlite
260*04fd306cSNickeau            ->createRequest()
261*04fd306cSNickeau            ->setQueryParametrized($query, [$uidValue]);
262*04fd306cSNickeau        $rows = [];
263*04fd306cSNickeau        try {
264*04fd306cSNickeau            $rows = $res
265*04fd306cSNickeau                ->execute()
266*04fd306cSNickeau                ->getRows();
267*04fd306cSNickeau        } catch (ExceptionCompile $e) {
268*04fd306cSNickeau            throw new ExceptionRuntimeInternal("An exception has occurred with the $tableName ({$metadata->getResource()}) selection query. Message: {$e->getMessage()}, Query: ($query", self::CANONICAL, 1, $e);
269*04fd306cSNickeau        } finally {
270*04fd306cSNickeau            $res->close();
271*04fd306cSNickeau        }
272*04fd306cSNickeau        return $rows;
273*04fd306cSNickeau
274*04fd306cSNickeau    }
275*04fd306cSNickeau
276*04fd306cSNickeau    public function persist()
277*04fd306cSNickeau    {
278*04fd306cSNickeau        // there is no notion of commit in the sqlite plugin
279*04fd306cSNickeau    }
280*04fd306cSNickeau
281*04fd306cSNickeau    public function isHierarchicalTextBased(): bool
282*04fd306cSNickeau    {
283*04fd306cSNickeau        return false;
284*04fd306cSNickeau    }
285*04fd306cSNickeau
286*04fd306cSNickeau    public function reset()
287*04fd306cSNickeau    {
288*04fd306cSNickeau        throw new ExceptionRuntime("To implement");
289*04fd306cSNickeau    }
290*04fd306cSNickeau
291*04fd306cSNickeau    public function getFromName(string $name, $default = null)
292*04fd306cSNickeau    {
293*04fd306cSNickeau
294*04fd306cSNickeau        if ($this->resourceUidMetaValue === null) {
295*04fd306cSNickeau            // not yet in the db
296*04fd306cSNickeau            return $default;
297*04fd306cSNickeau        }
298*04fd306cSNickeau
299*04fd306cSNickeau        $row = $this->getDatabaseRow();
300*04fd306cSNickeau        $value = $row->getFromRow($name);
301*04fd306cSNickeau        if ($value !== null) {
302*04fd306cSNickeau            return $value;
303*04fd306cSNickeau        }
304*04fd306cSNickeau        return $default;
305*04fd306cSNickeau    }
306*04fd306cSNickeau
307*04fd306cSNickeau    public function setFromPersistentName(string $name, $value, $default = null)
308*04fd306cSNickeau    {
309*04fd306cSNickeau        throw new ExceptionRuntime("Not implemented");
310*04fd306cSNickeau    }
311*04fd306cSNickeau
312*04fd306cSNickeau
313*04fd306cSNickeau    private function getTableName(Metadata $metadata): string
314*04fd306cSNickeau    {
315*04fd306cSNickeau        return $metadata->getResource()->getType() . "_" . $metadata::getPersistentName();
316*04fd306cSNickeau
317*04fd306cSNickeau    }
318*04fd306cSNickeau
319*04fd306cSNickeau    public function getCanonical(): string
320*04fd306cSNickeau    {
321*04fd306cSNickeau        return self::CANONICAL;
322*04fd306cSNickeau    }
323*04fd306cSNickeau
324*04fd306cSNickeau    private function getDatabaseRow(): DatabasePageRow
325*04fd306cSNickeau    {
326*04fd306cSNickeau        $mapKey = $this->getResource()->getPathObject()->toAbsoluteId();
327*04fd306cSNickeau        $row = self::$dbRows[$mapKey];
328*04fd306cSNickeau        if ($row === null) {
329*04fd306cSNickeau            $page = $this->getResource();
330*04fd306cSNickeau            $row = DatabasePageRow::getFromPageObject($page);
331*04fd306cSNickeau            self::$dbRows[$mapKey] = $row;
332*04fd306cSNickeau        }
333*04fd306cSNickeau        return $row;
334*04fd306cSNickeau    }
335*04fd306cSNickeau
336*04fd306cSNickeau
337*04fd306cSNickeau}
338