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 ?>