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('schemaentryelement.php');
0009 require_once('utils.php');
0010 
0011 /** Represents a product schema entry. */
0012 class SchemaEntry
0013 {
0014     public $name;
0015     public $type;
0016     public $elements = array();
0017 
0018     private $entryId = -1;
0019     private $m_product = null;
0020 
0021     const SCALAR_TPYE = 'scalar';
0022     const LIST_TYPE = 'list';
0023     const MAP_TYPE = 'map';
0024 
0025     public function __construct(Product $product)
0026     {
0027         $this->m_product = &$product;
0028     }
0029 
0030     /** Checks if this is a schema entry. */
0031     public function isValid()
0032     {
0033         if ($this->type != self::SCALAR_TPYE && $this->type != self::LIST_TYPE && $this->type != self::MAP_TYPE)
0034             return false;
0035         if (!Utils::isValidIdentifier($this->name))
0036             return false;
0037         return true;
0038     }
0039 
0040     /** Checks if this is a scalar type, ie. samples go into the primary data table. */
0041     public function isScalar()
0042     {
0043         return $this->type === self::SCALAR_TPYE;
0044     }
0045 
0046     /** Returns the product this entry belongs to. */
0047     public function product()
0048     {
0049         return $this->m_product;
0050     }
0051 
0052     /** Load product schema from storage. */
0053     static public function loadSchema(Datastore $db, Product &$product)
0054     {
0055         $stmt = $db->prepare('SELECT
0056                 tbl_schema.col_id, tbl_schema.col_name, tbl_schema.col_type, tbl_schema_element.col_name, tbl_schema_element.col_type
0057             FROM tbl_schema_element JOIN tbl_schema ON (tbl_schema.col_id = tbl_schema_element.col_schema_id)
0058             WHERE tbl_schema.col_product_id = :productId
0059             ORDER BY tbl_schema.col_id
0060         ');
0061         $stmt->bindValue(':productId', $product->id(), PDO::PARAM_INT);
0062         $db->execute($stmt);
0063         $schema = array();
0064         $entry = new SchemaEntry($product);
0065         foreach ($stmt as $row) {
0066             if ($entry->entryId != $row[0]) {
0067                 if ($entry->isValid()) {
0068                     array_push($schema, $entry);
0069                     $entry = new SchemaEntry($product);
0070                 }
0071                 $entry->entryId = $row[0];
0072                 $entry->name = $row[1];
0073                 $entry->type = $row[2];
0074             }
0075             $elem = new SchemaEntryElement($entry);
0076             $elem->name = $row[3];
0077             $elem->type = $row[4];
0078             array_push($entry->elements, $elem);
0079         }
0080         if ($entry->isValid())
0081             array_push($schema, $entry);
0082 
0083         return $schema;
0084     }
0085 
0086     /** Insert a new schema entry into storage. */
0087     public function insert(Datastore $db, $productId)
0088     {
0089         $stmt = $db->prepare('INSERT INTO
0090             tbl_schema (col_product_id, col_name, col_type)
0091             VALUES (:productId, :name, :type)
0092         ');
0093         $stmt->bindValue(':productId', $productId, PDO::PARAM_INT);
0094         $stmt->bindValue(':name', $this->name, PDO::PARAM_STR);
0095         $stmt->bindValue(':type', $this->type, PDO::PARAM_STR);
0096         $db->execute($stmt);
0097         $this->entryId = $db->pdoHandle()->lastInsertId();
0098 
0099         // add secondary data tables for non-scalars
0100         switch ($this->type) {
0101             case self::LIST_TYPE:
0102                 $this->createListDataTable($db);
0103                 break;
0104             case self::MAP_TYPE:
0105                 $this->createMapDataTable($db);
0106                 break;
0107         }
0108 
0109         // add elements (which will create data table columns, so this needs to be last)
0110         foreach ($this->elements as $elem)
0111             $elem->insert($db, $this->entryId);
0112     }
0113 
0114     /** Update this schema entry in storage. */
0115     public function update(Datastore $db, SchemaEntry $newEntry)
0116     {
0117         // TODO reject type changes
0118 
0119         // update elements
0120         $oldElements = array();
0121         foreach ($this->elements as $oldElem)
0122             $oldElements[$oldElem->name] = $oldElem;
0123 
0124         foreach ($newEntry->elements as $newElem) {
0125             if (array_key_exists($newElem->name, $oldElements)) {
0126                 // update
0127                 // TODO this would require type conversion of the data!?
0128             } else {
0129                 // insert
0130                 $newElem->insert($db, $this->entryId);
0131             }
0132             unset($oldElements[$newElem->name]);
0133         }
0134 
0135         // delete whatever is left
0136         foreach($oldElements as $elem) {
0137             $elem->dropDataColumn($db);
0138             $elem->delete($db, $this->entryId);
0139         }
0140     }
0141 
0142     /** Delete this schema entry from storage. */
0143     public function delete(Datastore $db, $productId)
0144     {
0145         // delete data
0146         if ($this->isScalar()) {
0147             foreach ($this->elements as $elem)
0148                 $elem->dropDataColumn($db);
0149         } else {
0150             $this->dropDataTable($db);
0151         }
0152 
0153         // delete elements
0154         $stmt = $db->prepare('DELETE FROM tbl_schema_element WHERE col_schema_id = :id');
0155         $stmt->bindValue(':id', $this->entryId, PDO::PARAM_INT);
0156         $db->execute($stmt);
0157 
0158         // delete entry
0159         $stmt = $db->prepare('DELETE FROM tbl_schema WHERE col_id = :id');
0160         $stmt->bindValue(':id', $this->entryId, PDO::PARAM_INT);
0161         $db->execute($stmt);
0162     }
0163 
0164     /** Convert a JSON object into an array of SchemaEntry instances. */
0165     static public function fromJson($jsonArray, Product &$product)
0166     {
0167         $entries = array();
0168         foreach ($jsonArray as $jsonObj) {
0169             if (!property_exists($jsonObj, 'name') || !property_exists($jsonObj, 'type'))
0170                 throw new RESTException('Incomplete schema entry object.', 400);
0171             $e = new SchemaEntry($product);
0172             $e->name = strval($jsonObj->name);
0173             $e->type = strval($jsonObj->type);
0174             if (property_exists($jsonObj, 'elements'))
0175                 $e->elements = SchemaEntryElement::fromJson($jsonObj->elements, $e);
0176             if (!$e->isValid())
0177                 throw new RESTException('Invalid schema entry.', 400);
0178             array_push($entries, $e);
0179         }
0180         return $entries;
0181     }
0182 
0183     /** Data table name for secondary data tables. */
0184     public function dataTableName()
0185     {
0186         $tableName = 'pd2_' . Utils::normalizeString($this->product()->name)
0187             . '__' . Utils::normalizeString($this->name);
0188         return strtolower($tableName);
0189     }
0190 
0191 
0192     /** Create secondary data tables for list types. */
0193     private function createListDataTable(Datastore $db)
0194     {
0195         $stmt = $db->prepare('CREATE TABLE ' . $this->dataTableName(). ' ('
0196             . Utils::primaryKeyColumnDeclaration($db->driver(), 'col_id') . ', '
0197             . 'col_sample_id INTEGER REFERENCES ' . $this->product()->dataTableName() . '(col_id) ON DELETE CASCADE)'
0198         );
0199         $db->execute($stmt);
0200     }
0201 
0202     /** Create secondary data tables for map types. */
0203     private function createMapDataTable(Datastore $db)
0204     {
0205         $stmt = $db->prepare('CREATE TABLE ' . $this->dataTableName(). ' ('
0206             . Utils::primaryKeyColumnDeclaration($db->driver(), 'col_id') . ', '
0207             . 'col_sample_id INTEGER REFERENCES ' . $this->product()->dataTableName() . '(col_id) ON DELETE CASCADE, '
0208             . 'col_key ' . Utils::sqlStringType($db->driver()) . ' NOT NULL)'
0209         );
0210         $db->execute($stmt);
0211     }
0212 
0213     /** Drop secondary data tables for non-scalar types. */
0214     private function dropDataTable(Datastore $db)
0215     {
0216         $stmt = $db->prepare('DROP TABLE ' . $this->dataTableName());
0217         $db->execute($stmt);
0218     }
0219 }
0220 
0221 ?>