File indexing completed on 2024-12-15 03:45:06

0001 <?php
0002 /*
0003     SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
0004 
0005     SPDX-License-Identifier: MIT
0006 */
0007 
0008 require_once('restexception.php');
0009 require_once('utils.php');
0010 
0011 /** Represents a product schema entry element. */
0012 class SchemaEntryElement
0013 {
0014     public $name;
0015     public $type;
0016 
0017     private $schemaEntry = null;
0018 
0019     const STRING_TYPE = 'string';
0020     const INT_TYPE = 'int';
0021     const NUMBER_TYPE = 'number';
0022     const BOOL_TYPE = 'bool';
0023 
0024     public function __construct(SchemaEntry $entry)
0025     {
0026         $this->schemaEntry = &$entry;
0027     }
0028 
0029     /** Checks if this schema entry element is valid. */
0030     public function isValid()
0031     {
0032         if ($this->type != self::STRING_TYPE && $this->type != self::INT_TYPE && $this->type != self::NUMBER_TYPE && $this->type != self::BOOL_TYPE)
0033             return false;
0034         if (!Utils::isValidIdentifier($this->name))
0035             return false;
0036         return true;
0037     }
0038 
0039     /** Column name in the data table. */
0040     public function dataColumnName()
0041     {
0042         $colName = 'col_data_' . Utils::normalizeString($this->schemaEntry->name) . '_' . Utils::normalizeString($this->name);
0043         return strtolower($colName);
0044     }
0045 
0046     /** Insert this element into storage. */
0047     public function insert(Datastore $db, $entryId)
0048     {
0049         $stmt = $db->prepare('INSERT INTO
0050             tbl_schema_element (col_schema_id, col_name, col_type)
0051             VALUES (:schemaId, :name, :type)
0052         ');
0053         $stmt->bindValue(':schemaId', $entryId, PDO::PARAM_INT);
0054         $stmt->bindValue(':name', $this->name, PDO::PARAM_STR);
0055         $stmt->bindValue(':type', $this->type, PDO::PARAM_STR);
0056         $db->execute($stmt);
0057 
0058         if ($this->schemaEntry->isScalar())
0059             $this->createScalarDataTableColumn($db);
0060         else
0061             $this->createNonScalarDataTableColumn($db);
0062     }
0063 
0064     /** Delete this element from storage. */
0065     public function delete(Datastore $db, $entryId)
0066     {
0067         $stmt = $db->prepare('
0068             DELETE FROM tbl_schema_element
0069             WHERE col_schema_id = :schemaId AND col_name = :name
0070         ');
0071         $stmt->bindValue(':schemaId', $entryId, PDO::PARAM_INT);
0072         $stmt->bindValue(':name', $this->name, PDO::PARAM_STR);
0073         $db->execute($stmt);
0074     }
0075 
0076     /** Drop data table column for this element. */
0077     public function dropDataColumn(Datastore $db)
0078     {
0079         if ($db->driver() == 'sqlite') {
0080             error_log('Sqlite does not support dropping columns.');
0081             return;
0082         }
0083 
0084         try {
0085             if ($this->schemaEntry->isScalar()) {
0086                 $stmt = $db->prepare('ALTER TABLE ' . $this->schemaEntry->product()->dataTableName()
0087                     . ' DROP COLUMN ' . $this->dataColumnName());
0088                 $db->execute($stmt);
0089             } else {
0090                 $stmt = $db->prepare('ALTER TABLE ' . $this->schemaEntry->dataTableName()
0091                     . ' DROP COLUMN ' . $this->dataColumnName());
0092                 $db->execute($stmt);
0093             }
0094         } catch (RESTException $e) {
0095             // don't fail hard on column removal, this can leave the db and our schema description in an inconsistent state
0096             // or block product deletion entirely
0097             error_log($e->getMessage());
0098             // restart transaction so this doesn't block subsequent queries
0099             $db->rollBack();
0100             $db->beginTransaction();
0101         }
0102     }
0103 
0104     /** Convert a JSON array into an array of SchemaEntryElement instances. */
0105     static public function fromJson($jsonArray, SchemaEntry &$entry)
0106     {
0107         $elems = array();
0108         foreach ($jsonArray as $jsonObj) {
0109             if (!property_exists($jsonObj, 'name') || !property_exists($jsonObj, 'type'))
0110                 throw new RESTException('Incomplete schema entry element.', 400);
0111             $e = new SchemaEntryElement($entry);
0112             $e->name = $jsonObj->name;
0113             $e->type = $jsonObj->type;
0114             if (!$e->isValid())
0115                 throw new RESTException('Invalid schema entry element.', 400);
0116             array_push($elems, $e);
0117         }
0118         return $elems;
0119     }
0120 
0121 
0122     /** SQL type for this element. */
0123     private function sqlType(Datastore $db)
0124     {
0125         switch ($this->type) {
0126             case self::STRING_TYPE: return Utils::sqlStringType($db->driver());
0127             case self::INT_TYPE: return 'INTEGER';
0128             case self::NUMBER_TYPE: return 'REAL';
0129             case self::BOOL_TYPE: return 'BOOLEAN';
0130         }
0131         throw new RESTException('Unsupported schema entry element type.', 400);
0132     }
0133 
0134     /** Creates a data table entry for scalar elements. */
0135     private function createScalarDataTableColumn(Datastore $db)
0136     {
0137         $stmt = $db->prepare('ALTER TABLE ' . $this->schemaEntry->product()->dataTableName()
0138             . ' ADD COLUMN ' . $this->dataColumnName() . ' ' . $this->sqlType($db));
0139         $db->execute($stmt);
0140     }
0141 
0142     /** Creates a data table entry for non-scalar elements. */
0143     private function createNonScalarDataTableColumn(Datastore $db)
0144     {
0145         $stmt = $db->prepare('ALTER TABLE ' . $this->schemaEntry->dataTableName()
0146             . ' ADD COLUMN ' . $this->dataColumnName() . ' ' . $this->sqlType($db));
0147         $db->execute($stmt);
0148     }
0149 }
0150 
0151 ?>