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