11c502704SAndreas Gohr<?php 21c502704SAndreas Gohr 31c502704SAndreas Gohrnamespace plugin\struct\meta; 41c502704SAndreas Gohr 57182938bSAndreas Gohr/** 67182938bSAndreas Gohr * Class SchemaBuilder 77182938bSAndreas Gohr * 87182938bSAndreas Gohr * This class builds and updates the schema definitions for our tables. This includes CREATEing and ALTERing 97182938bSAndreas Gohr * the actual data tables as well as updating the meta information in our meta data tables. 107182938bSAndreas Gohr * 117182938bSAndreas Gohr * To use, simply instantiate a new object of the Builder and run the build() method on it. 127182938bSAndreas Gohr * 137182938bSAndreas Gohr * Note: even though data tables use a data_ prefix in the database, this prefix is internal only and should 147182938bSAndreas Gohr * never be passed as $table anywhere! 157182938bSAndreas Gohr * 167182938bSAndreas Gohr * @package plugin\struct\meta 177182938bSAndreas Gohr */ 181c502704SAndreas Gohrclass SchemaBuilder { 191c502704SAndreas Gohr 201c502704SAndreas Gohr /** 211c502704SAndreas Gohr * @var array The posted new data for the schema 227182938bSAndreas Gohr * @see Schema::AdminEditor() 231c502704SAndreas Gohr */ 241c502704SAndreas Gohr protected $data = array(); 251c502704SAndreas Gohr 261c502704SAndreas Gohr /** 271c502704SAndreas Gohr * @var string The table name associated with the schema 281c502704SAndreas Gohr */ 291c502704SAndreas Gohr protected $table = ''; 301c502704SAndreas Gohr 311c502704SAndreas Gohr /** 321c502704SAndreas Gohr * @var Schema the previously valid schema for this table 331c502704SAndreas Gohr */ 341c502704SAndreas Gohr protected $oldschema; 351c502704SAndreas Gohr 361c502704SAndreas Gohr /** @var int the ID of the newly created schema */ 371c502704SAndreas Gohr protected $newschemaid = 0; 381c502704SAndreas Gohr 391c502704SAndreas Gohr /** @var \helper_plugin_sqlite|null */ 401c502704SAndreas Gohr protected $sqlite; 411c502704SAndreas Gohr 421c502704SAndreas Gohr /** 431c502704SAndreas Gohr * SchemaBuilder constructor. 447182938bSAndreas Gohr * 457182938bSAndreas Gohr * @param string $table The table's name 467182938bSAndreas Gohr * @param array $data The defining of the table (basically what get's posted in the schema editor form) 477182938bSAndreas Gohr * @see Schema::AdminEditor() 481c502704SAndreas Gohr */ 491c502704SAndreas Gohr public function __construct($table, $data) { 501c502704SAndreas Gohr $this->table = $table; 511c502704SAndreas Gohr $this->data = $data; 521c502704SAndreas Gohr $this->oldschema = new Schema($table); 531c502704SAndreas Gohr 541c502704SAndreas Gohr /** @var \helper_plugin_struct_db $helper */ 551c502704SAndreas Gohr $helper = plugin_load('helper', 'struct_db'); 561c502704SAndreas Gohr $this->sqlite = $helper->getDB(); 571c502704SAndreas Gohr } 581c502704SAndreas Gohr 591c502704SAndreas Gohr /** 601c502704SAndreas Gohr * Create the new schema 611c502704SAndreas Gohr * 621c502704SAndreas Gohr * @return bool|int the new schema id on success 631c502704SAndreas Gohr */ 641c502704SAndreas Gohr public function build() { 651c502704SAndreas Gohr $this->sqlite->query('BEGIN TRANSACTION'); 666390a534SMichael Große $ok = true; 676684d78dSAndreas Gohr // create the data table if new schema 686684d78dSAndreas Gohr if(!$this->oldschema->getId()) { 691c502704SAndreas Gohr $ok = $this->newDataTable(); 706684d78dSAndreas Gohr } 711c502704SAndreas Gohr 721c502704SAndreas Gohr // create a new schema 736390a534SMichael Große $ok = $ok && $this->newSchema(); 741c502704SAndreas Gohr 751c502704SAndreas Gohr // update column info 766390a534SMichael Große $ok = $ok && $this->updateColumns(); 776390a534SMichael Große $ok = $ok && $this->addColumns(); 781c502704SAndreas Gohr 796390a534SMichael Große if (!$ok) { 806390a534SMichael Große $this->sqlite->query('ROLLBACK TRANSACTION'); 816390a534SMichael Große return false; 826390a534SMichael Große } 831c502704SAndreas Gohr $this->sqlite->query('COMMIT TRANSACTION'); 841c502704SAndreas Gohr 851c502704SAndreas Gohr return $this->newschemaid; 861c502704SAndreas Gohr } 871c502704SAndreas Gohr 881c502704SAndreas Gohr /** 891c502704SAndreas Gohr * Creates a new schema 901c502704SAndreas Gohr * 911c502704SAndreas Gohr * @todo use checksum or other heuristic to see if we really need a new schema OTOH we probably need one nearly always!? 921c502704SAndreas Gohr */ 931c502704SAndreas Gohr protected function newSchema() { 941c502704SAndreas Gohr $sql = "INSERT INTO schemas (tbl, ts) VALUES (?, ?)"; 951c502704SAndreas Gohr $this->sqlite->query($sql, $this->table, time()); 961c502704SAndreas Gohr $res = $this->sqlite->query('SELECT last_insert_rowid()'); 971c502704SAndreas Gohr $this->newschemaid = $this->sqlite->res2single($res); 981c502704SAndreas Gohr $this->sqlite->res_close($res); 991c502704SAndreas Gohr if(!$this->newschemaid) return false; 1001c502704SAndreas Gohr return true; 1011c502704SAndreas Gohr } 1021c502704SAndreas Gohr 1031c502704SAndreas Gohr /** 1041c502704SAndreas Gohr * Updates all the existing column infos and adds them to the new schema 1051c502704SAndreas Gohr */ 1061c502704SAndreas Gohr protected function updateColumns() { 1071c502704SAndreas Gohr foreach($this->oldschema->getColumns() as $column) { 1081c502704SAndreas Gohr $oldEntry = $column->getType()->getAsEntry(); 1091c502704SAndreas Gohr $oldTid = $column->getTid(); 1101c502704SAndreas Gohr $newEntry = $oldEntry; 1111c502704SAndreas Gohr $newTid = $oldTid; 1121c502704SAndreas Gohr $enabled = true; 1131c502704SAndreas Gohr $sort = $column->getSort(); 1141c502704SAndreas Gohr if(isset($this->data['cols'][$column->getColref()])){ 1151c502704SAndreas Gohr // todo I'm not too happy with this hardcoded here - we should probably have a list of fields at one place 1161c502704SAndreas Gohr $newEntry['config'] = $this->data['cols'][$column->getColref()]['config']; 1171c502704SAndreas Gohr $newEntry['label'] = $this->data['cols'][$column->getColref()]['label']; 118b7a5ee93SMichael Große $newEntry['ismulti'] = $this->data['cols'][$column->getColref()]['ismulti']; 1191c502704SAndreas Gohr $newEntry['class'] = $this->data['cols'][$column->getColref()]['class']; 1201c502704SAndreas Gohr $sort = $this->data['cols'][$column->getColref()]['sort']; 1211c502704SAndreas Gohr 1221c502704SAndreas Gohr // when the type definition has changed, we create a new one 1231c502704SAndreas Gohr if(array_diff_assoc($oldEntry, $newEntry)) { 1241c502704SAndreas Gohr $ok = $this->sqlite->storeEntry('types', $newEntry); 1251c502704SAndreas Gohr if(!$ok) return false; 1261c502704SAndreas Gohr $res = $this->sqlite->query('SELECT last_insert_rowid()'); 1271c502704SAndreas Gohr if(!$res) return false; 1281c502704SAndreas Gohr $newTid = $this->sqlite->res2single($res); 1291c502704SAndreas Gohr $this->sqlite->res_close($res); 1301c502704SAndreas Gohr } 1311c502704SAndreas Gohr } else { 1327182938bSAndreas Gohr $enabled = false; // no longer there FIXME this assumes we remove the entry from the form completely. We might not want to do that 1331c502704SAndreas Gohr } 1341c502704SAndreas Gohr 1351c502704SAndreas Gohr // add this type to the schema columns 1361c502704SAndreas Gohr $schemaEntry = array( 1371c502704SAndreas Gohr 'sid' => $this->newschemaid, 1381c502704SAndreas Gohr 'colref' => $column->getColref(), 1391c502704SAndreas Gohr 'enabled' => $enabled, 1401c502704SAndreas Gohr 'tid' => $newTid, 1411c502704SAndreas Gohr 'sort' => $sort 1421c502704SAndreas Gohr ); 1431c502704SAndreas Gohr $ok = $this->sqlite->storeEntry('schema_cols', $schemaEntry); 1441c502704SAndreas Gohr if(!$ok) return false; 1451c502704SAndreas Gohr } 1461c502704SAndreas Gohr return true; 1471c502704SAndreas Gohr } 1481c502704SAndreas Gohr 1491c502704SAndreas Gohr /** 1501c502704SAndreas Gohr * Adds new columns to the new schema 1511c502704SAndreas Gohr * 1521c502704SAndreas Gohr * @return bool 1531c502704SAndreas Gohr */ 1541c502704SAndreas Gohr protected function addColumns() { 1551c502704SAndreas Gohr if(!isset($this->data['new'])) return true; 1561c502704SAndreas Gohr 1571c502704SAndreas Gohr $colref = count($this->oldschema->getColumns())+1; 1581c502704SAndreas Gohr 1591c502704SAndreas Gohr foreach($this->data['new'] as $column) { 1601c502704SAndreas Gohr // todo this duplicates the hardcoding as in the function above 1611c502704SAndreas Gohr $newEntry = array(); 1621c502704SAndreas Gohr $newEntry['config'] = $column['config']; 1631c502704SAndreas Gohr $newEntry['label'] = $column['label']; 164b7a5ee93SMichael Große $newEntry['ismulti'] = $column['ismulti']; 1651c502704SAndreas Gohr $newEntry['class'] = $column['class']; 1661c502704SAndreas Gohr $sort = $column['sort']; 1671c502704SAndreas Gohr $enabled = true; 1681c502704SAndreas Gohr 1696684d78dSAndreas Gohr // only save if the column got a name 1706684d78dSAndreas Gohr if(!$newEntry['label']) continue; 1716684d78dSAndreas Gohr 1726684d78dSAndreas Gohr // add new column to the data table 1736684d78dSAndreas Gohr if(!$this->addDataTableColumn($colref)) { 1746684d78dSAndreas Gohr return false; 1756684d78dSAndreas Gohr } 1766684d78dSAndreas Gohr 1771c502704SAndreas Gohr // save the type 1781c502704SAndreas Gohr $ok = $this->sqlite->storeEntry('types', $newEntry); 1791c502704SAndreas Gohr if(!$ok) return false; 1801c502704SAndreas Gohr $res = $this->sqlite->query('SELECT last_insert_rowid()'); 1811c502704SAndreas Gohr if(!$res) return false; 1821c502704SAndreas Gohr $newTid = $this->sqlite->res2single($res); 1831c502704SAndreas Gohr $this->sqlite->res_close($res); 1841c502704SAndreas Gohr 1851c502704SAndreas Gohr 1861c502704SAndreas Gohr // add this type to the schema columns 1871c502704SAndreas Gohr $schemaEntry = array( 1881c502704SAndreas Gohr 'sid' => $this->newschemaid, 1891c502704SAndreas Gohr 'colref' => $colref, 1901c502704SAndreas Gohr 'enabled' => $enabled, 1911c502704SAndreas Gohr 'tid' => $newTid, 1921c502704SAndreas Gohr 'sort' => $sort 1931c502704SAndreas Gohr ); 1941c502704SAndreas Gohr $ok = $this->sqlite->storeEntry('schema_cols', $schemaEntry); 1951c502704SAndreas Gohr if(!$ok) return false; 1961c502704SAndreas Gohr $colref++; 1971c502704SAndreas Gohr } 1981c502704SAndreas Gohr 1991c502704SAndreas Gohr return true; 2001c502704SAndreas Gohr } 2011c502704SAndreas Gohr 2021c502704SAndreas Gohr /** 2036684d78dSAndreas Gohr * Create a completely new data table with columns yet 2041c502704SAndreas Gohr * 2051c502704SAndreas Gohr * @todo how do we want to handle indexes? 2061c502704SAndreas Gohr * @return bool 2071c502704SAndreas Gohr */ 2081c502704SAndreas Gohr protected function newDataTable() { 2091c502704SAndreas Gohr $tbl = 'data_' . $this->table; 2101c502704SAndreas Gohr 2111c502704SAndreas Gohr $sql = "CREATE TABLE $tbl ( 2121c502704SAndreas Gohr pid NOT NULL, 2136684d78dSAndreas Gohr rev INTEGER NOT NULL, 214*7059e7e1SAndreas Gohr latest BOOLEAN NOT NULL DEFAULT 0, 2156684d78dSAndreas Gohr PRIMARY KEY(pid, rev) 2166684d78dSAndreas Gohr )"; 2171c502704SAndreas Gohr 2181c502704SAndreas Gohr return (bool) $this->sqlite->query($sql); 2191c502704SAndreas Gohr } 2201c502704SAndreas Gohr 2211c502704SAndreas Gohr /** 2226684d78dSAndreas Gohr * Add an additional column to the existing data table 2231c502704SAndreas Gohr * 2246684d78dSAndreas Gohr * @param int $index the new column index to add 2251c502704SAndreas Gohr * @return bool 2261c502704SAndreas Gohr */ 2276684d78dSAndreas Gohr protected function addDataTableColumn($index) { 2281c502704SAndreas Gohr $tbl = 'data_' . $this->table; 2296684d78dSAndreas Gohr $sql = " ALTER TABLE $tbl ADD COLUMN col$index DEFAULT ''"; 2301c502704SAndreas Gohr if(! $this->sqlite->query($sql)) { 2311c502704SAndreas Gohr return false; 2321c502704SAndreas Gohr } 2331c502704SAndreas Gohr return true; 2341c502704SAndreas Gohr } 2351c502704SAndreas Gohr 2361c502704SAndreas Gohr} 237