11c502704SAndreas Gohr<?php 21c502704SAndreas Gohr 31c502704SAndreas Gohrnamespace plugin\struct\meta; 41c502704SAndreas Gohr 5*7182938bSAndreas Gohr/** 6*7182938bSAndreas Gohr * Class SchemaBuilder 7*7182938bSAndreas Gohr * 8*7182938bSAndreas Gohr * This class builds and updates the schema definitions for our tables. This includes CREATEing and ALTERing 9*7182938bSAndreas Gohr * the actual data tables as well as updating the meta information in our meta data tables. 10*7182938bSAndreas Gohr * 11*7182938bSAndreas Gohr * To use, simply instantiate a new object of the Builder and run the build() method on it. 12*7182938bSAndreas Gohr * 13*7182938bSAndreas Gohr * Note: even though data tables use a data_ prefix in the database, this prefix is internal only and should 14*7182938bSAndreas Gohr * never be passed as $table anywhere! 15*7182938bSAndreas Gohr * 16*7182938bSAndreas Gohr * @package plugin\struct\meta 17*7182938bSAndreas Gohr */ 181c502704SAndreas Gohrclass SchemaBuilder { 191c502704SAndreas Gohr 201c502704SAndreas Gohr /** 211c502704SAndreas Gohr * @var array The posted new data for the schema 22*7182938bSAndreas 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. 44*7182938bSAndreas Gohr * 45*7182938bSAndreas Gohr * @param string $table The table's name 46*7182938bSAndreas Gohr * @param array $data The defining of the table (basically what get's posted in the schema editor form) 47*7182938bSAndreas 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'); 661c502704SAndreas Gohr 671c502704SAndreas Gohr // create or update the data table 681c502704SAndreas Gohr if($this->oldschema->getId()) { 691c502704SAndreas Gohr $ok = $this->updateDataTable(); 701c502704SAndreas Gohr } else { 711c502704SAndreas Gohr $ok = $this->newDataTable(); 721c502704SAndreas Gohr } 731c502704SAndreas Gohr if(!$ok) return false; 741c502704SAndreas Gohr 751c502704SAndreas Gohr // create a new schema 761c502704SAndreas Gohr if(!$this->newSchema()) return false; 771c502704SAndreas Gohr 781c502704SAndreas Gohr // update column info 791c502704SAndreas Gohr if(!$this->updateColumns()) return false; 801c502704SAndreas Gohr if(!$this->addColumns()) return false; 811c502704SAndreas Gohr 821c502704SAndreas Gohr $this->sqlite->query('COMMIT TRANSACTION'); 831c502704SAndreas Gohr 841c502704SAndreas Gohr return $this->newschemaid; 851c502704SAndreas Gohr } 861c502704SAndreas Gohr 871c502704SAndreas Gohr /** 881c502704SAndreas Gohr * Creates a new schema 891c502704SAndreas Gohr * 901c502704SAndreas Gohr * @todo use checksum or other heuristic to see if we really need a new schema OTOH we probably need one nearly always!? 911c502704SAndreas Gohr */ 921c502704SAndreas Gohr protected function newSchema() { 931c502704SAndreas Gohr $sql = "INSERT INTO schemas (tbl, ts) VALUES (?, ?)"; 941c502704SAndreas Gohr $this->sqlite->query($sql, $this->table, time()); 951c502704SAndreas Gohr $res = $this->sqlite->query('SELECT last_insert_rowid()'); 961c502704SAndreas Gohr $this->newschemaid = $this->sqlite->res2single($res); 971c502704SAndreas Gohr $this->sqlite->res_close($res); 981c502704SAndreas Gohr if(!$this->newschemaid) return false; 991c502704SAndreas Gohr return true; 1001c502704SAndreas Gohr } 1011c502704SAndreas Gohr 1021c502704SAndreas Gohr /** 1031c502704SAndreas Gohr * Updates all the existing column infos and adds them to the new schema 1041c502704SAndreas Gohr */ 1051c502704SAndreas Gohr protected function updateColumns() { 1061c502704SAndreas Gohr foreach($this->oldschema->getColumns() as $column) { 1071c502704SAndreas Gohr $oldEntry = $column->getType()->getAsEntry(); 1081c502704SAndreas Gohr $oldTid = $column->getTid(); 1091c502704SAndreas Gohr $newEntry = $oldEntry; 1101c502704SAndreas Gohr $newTid = $oldTid; 1111c502704SAndreas Gohr $enabled = true; 1121c502704SAndreas Gohr $sort = $column->getSort(); 1131c502704SAndreas Gohr if(isset($this->data['cols'][$column->getColref()])){ 1141c502704SAndreas Gohr // todo I'm not too happy with this hardcoded here - we should probably have a list of fields at one place 1151c502704SAndreas Gohr $newEntry['config'] = $this->data['cols'][$column->getColref()]['config']; 1161c502704SAndreas Gohr $newEntry['label'] = $this->data['cols'][$column->getColref()]['label']; 1171c502704SAndreas Gohr $newEntry['ismulti'] = $this->data['cols'][$column->getColref()]['multi']; 1181c502704SAndreas Gohr $newEntry['class'] = $this->data['cols'][$column->getColref()]['class']; 1191c502704SAndreas Gohr $sort = $this->data['cols'][$column->getColref()]['sort']; 1201c502704SAndreas Gohr 1211c502704SAndreas Gohr // when the type definition has changed, we create a new one 1221c502704SAndreas Gohr if(array_diff_assoc($oldEntry, $newEntry)) { 1231c502704SAndreas Gohr $ok = $this->sqlite->storeEntry('types', $newEntry); 1241c502704SAndreas Gohr if(!$ok) return false; 1251c502704SAndreas Gohr $res = $this->sqlite->query('SELECT last_insert_rowid()'); 1261c502704SAndreas Gohr if(!$res) return false; 1271c502704SAndreas Gohr $newTid = $this->sqlite->res2single($res); 1281c502704SAndreas Gohr $this->sqlite->res_close($res); 1291c502704SAndreas Gohr } 1301c502704SAndreas Gohr } else { 131*7182938bSAndreas Gohr $enabled = false; // no longer there FIXME this assumes we remove the entry from the form completely. We might not want to do that 1321c502704SAndreas Gohr } 1331c502704SAndreas Gohr 1341c502704SAndreas Gohr // add this type to the schema columns 1351c502704SAndreas Gohr $schemaEntry = array( 1361c502704SAndreas Gohr 'sid' => $this->newschemaid, 1371c502704SAndreas Gohr 'colref' => $column->getColref(), 1381c502704SAndreas Gohr 'enabled' => $enabled, 1391c502704SAndreas Gohr 'tid' => $newTid, 1401c502704SAndreas Gohr 'sort' => $sort 1411c502704SAndreas Gohr ); 1421c502704SAndreas Gohr $ok = $this->sqlite->storeEntry('schema_cols', $schemaEntry); 1431c502704SAndreas Gohr if(!$ok) return false; 1441c502704SAndreas Gohr } 1451c502704SAndreas Gohr return true; 1461c502704SAndreas Gohr } 1471c502704SAndreas Gohr 1481c502704SAndreas Gohr /** 1491c502704SAndreas Gohr * Adds new columns to the new schema 1501c502704SAndreas Gohr * 1511c502704SAndreas Gohr * @return bool 1521c502704SAndreas Gohr */ 1531c502704SAndreas Gohr protected function addColumns() { 1541c502704SAndreas Gohr if(!isset($this->data['new'])) return true; 1551c502704SAndreas Gohr 1561c502704SAndreas Gohr $colref = count($this->oldschema->getColumns())+1; 1571c502704SAndreas Gohr 1581c502704SAndreas Gohr foreach($this->data['new'] as $column) { 1591c502704SAndreas Gohr // todo this duplicates the hardcoding as in the function above 1601c502704SAndreas Gohr $newEntry = array(); 1611c502704SAndreas Gohr $newEntry['config'] = $column['config']; 1621c502704SAndreas Gohr $newEntry['label'] = $column['label']; 1631c502704SAndreas Gohr $newEntry['ismulti'] = $column['multi']; 1641c502704SAndreas Gohr $newEntry['class'] = $column['class']; 1651c502704SAndreas Gohr $sort = $column['sort']; 1661c502704SAndreas Gohr $enabled = true; 1671c502704SAndreas Gohr 1681c502704SAndreas Gohr // save the type 1691c502704SAndreas Gohr $ok = $this->sqlite->storeEntry('types', $newEntry); 1701c502704SAndreas Gohr if(!$ok) return false; 1711c502704SAndreas Gohr $res = $this->sqlite->query('SELECT last_insert_rowid()'); 1721c502704SAndreas Gohr if(!$res) return false; 1731c502704SAndreas Gohr $newTid = $this->sqlite->res2single($res); 1741c502704SAndreas Gohr $this->sqlite->res_close($res); 1751c502704SAndreas Gohr 1761c502704SAndreas Gohr 1771c502704SAndreas Gohr // add this type to the schema columns 1781c502704SAndreas Gohr $schemaEntry = array( 1791c502704SAndreas Gohr 'sid' => $this->newschemaid, 1801c502704SAndreas Gohr 'colref' => $colref, 1811c502704SAndreas Gohr 'enabled' => $enabled, 1821c502704SAndreas Gohr 'tid' => $newTid, 1831c502704SAndreas Gohr 'sort' => $sort 1841c502704SAndreas Gohr ); 1851c502704SAndreas Gohr $ok = $this->sqlite->storeEntry('schema_cols', $schemaEntry); 1861c502704SAndreas Gohr if(!$ok) return false; 1871c502704SAndreas Gohr $colref++; 1881c502704SAndreas Gohr } 1891c502704SAndreas Gohr 1901c502704SAndreas Gohr return true; 1911c502704SAndreas Gohr } 1921c502704SAndreas Gohr 1931c502704SAndreas Gohr /** 1941c502704SAndreas Gohr * Create a completely new data table 1951c502704SAndreas Gohr * 1961c502704SAndreas Gohr * @todo how do we want to handle indexes? 1971c502704SAndreas Gohr * @return bool 1981c502704SAndreas Gohr */ 1991c502704SAndreas Gohr protected function newDataTable() { 2001c502704SAndreas Gohr $tbl = 'data_' . $this->table; 2011c502704SAndreas Gohr $cols = count($this->data['new']); // number of columns in the schema 2021c502704SAndreas Gohr 2031c502704SAndreas Gohr $sql = "CREATE TABLE $tbl ( 2041c502704SAndreas Gohr pid NOT NULL, 2051c502704SAndreas Gohr rev INTEGER NOT NULL,\n"; 2061c502704SAndreas Gohr for($i = 1; $i <= $cols; $i++) { 2071c502704SAndreas Gohr $sql .= "col$i DEFAULT '',\n"; 2081c502704SAndreas Gohr } 2091c502704SAndreas Gohr $sql .= "PRIMARY KEY(pid, rev) )"; 2101c502704SAndreas Gohr 2111c502704SAndreas Gohr return (bool) $this->sqlite->query($sql); 2121c502704SAndreas Gohr } 2131c502704SAndreas Gohr 2141c502704SAndreas Gohr /** 2151c502704SAndreas Gohr * Add additional columns to an existing data table 2161c502704SAndreas Gohr * 2171c502704SAndreas Gohr * @return bool 2181c502704SAndreas Gohr */ 2191c502704SAndreas Gohr protected function updateDataTable() { 2201c502704SAndreas Gohr $tbl = 'data_' . $this->table; 2211c502704SAndreas Gohr $oldcols = count($this->oldschema->getColumns()); // number of columns in the old schema 2221c502704SAndreas Gohr $newcols = count($this->data['new']); // number of *new* columns in the schema 2231c502704SAndreas Gohr 2241c502704SAndreas Gohr for($i = $oldcols+1; $i <= $oldcols + $newcols; $i++) { 2251c502704SAndreas Gohr $sql = " ALTER TABLE $tbl ADD COLUMN col$i DEFAULT ''"; 2261c502704SAndreas Gohr if(! $this->sqlite->query($sql)) { 2271c502704SAndreas Gohr return false; 2281c502704SAndreas Gohr } 2291c502704SAndreas Gohr } 2301c502704SAndreas Gohr 2311c502704SAndreas Gohr return true; 2321c502704SAndreas Gohr } 2331c502704SAndreas Gohr 2341c502704SAndreas Gohr} 235