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