1*1c502704SAndreas Gohr<?php 2*1c502704SAndreas Gohr 3*1c502704SAndreas Gohrnamespace plugin\struct\meta; 4*1c502704SAndreas Gohr 5*1c502704SAndreas Gohrclass SchemaBuilder { 6*1c502704SAndreas Gohr 7*1c502704SAndreas Gohr /** 8*1c502704SAndreas Gohr * @var array The posted new data for the schema 9*1c502704SAndreas Gohr */ 10*1c502704SAndreas Gohr protected $data = array(); 11*1c502704SAndreas Gohr 12*1c502704SAndreas Gohr /** 13*1c502704SAndreas Gohr * @var string The table name associated with the schema 14*1c502704SAndreas Gohr */ 15*1c502704SAndreas Gohr protected $table = ''; 16*1c502704SAndreas Gohr 17*1c502704SAndreas Gohr /** 18*1c502704SAndreas Gohr * @var Schema the previously valid schema for this table 19*1c502704SAndreas Gohr */ 20*1c502704SAndreas Gohr protected $oldschema; 21*1c502704SAndreas Gohr 22*1c502704SAndreas Gohr /** @var int the ID of the newly created schema */ 23*1c502704SAndreas Gohr protected $newschemaid = 0; 24*1c502704SAndreas Gohr 25*1c502704SAndreas Gohr /** @var \helper_plugin_sqlite|null */ 26*1c502704SAndreas Gohr protected $sqlite; 27*1c502704SAndreas Gohr 28*1c502704SAndreas Gohr /** 29*1c502704SAndreas Gohr * SchemaBuilder constructor. 30*1c502704SAndreas Gohr * @param string $table 31*1c502704SAndreas Gohr * @param array $data 32*1c502704SAndreas Gohr */ 33*1c502704SAndreas Gohr public function __construct($table, $data) { 34*1c502704SAndreas Gohr $this->table = $table; 35*1c502704SAndreas Gohr $this->data = $data; 36*1c502704SAndreas Gohr $this->oldschema = new Schema($table); 37*1c502704SAndreas Gohr 38*1c502704SAndreas Gohr /** @var \helper_plugin_struct_db $helper */ 39*1c502704SAndreas Gohr $helper = plugin_load('helper', 'struct_db'); 40*1c502704SAndreas Gohr $this->sqlite = $helper->getDB(); 41*1c502704SAndreas Gohr } 42*1c502704SAndreas Gohr 43*1c502704SAndreas Gohr /** 44*1c502704SAndreas Gohr * Create the new schema 45*1c502704SAndreas Gohr * 46*1c502704SAndreas Gohr * @return bool|int the new schema id on success 47*1c502704SAndreas Gohr */ 48*1c502704SAndreas Gohr public function build() { 49*1c502704SAndreas Gohr $this->sqlite->query('BEGIN TRANSACTION'); 50*1c502704SAndreas Gohr 51*1c502704SAndreas Gohr // create or update the data table 52*1c502704SAndreas Gohr if($this->oldschema->getId()) { 53*1c502704SAndreas Gohr $ok = $this->updateDataTable(); 54*1c502704SAndreas Gohr } else { 55*1c502704SAndreas Gohr $ok = $this->newDataTable(); 56*1c502704SAndreas Gohr } 57*1c502704SAndreas Gohr if(!$ok) return false; 58*1c502704SAndreas Gohr 59*1c502704SAndreas Gohr // create a new schema 60*1c502704SAndreas Gohr if(!$this->newSchema()) return false; 61*1c502704SAndreas Gohr 62*1c502704SAndreas Gohr // update column info 63*1c502704SAndreas Gohr if(!$this->updateColumns()) return false; 64*1c502704SAndreas Gohr if(!$this->addColumns()) return false; 65*1c502704SAndreas Gohr 66*1c502704SAndreas Gohr $this->sqlite->query('COMMIT TRANSACTION'); 67*1c502704SAndreas Gohr 68*1c502704SAndreas Gohr return $this->newschemaid; 69*1c502704SAndreas Gohr } 70*1c502704SAndreas Gohr 71*1c502704SAndreas Gohr /** 72*1c502704SAndreas Gohr * Creates a new schema 73*1c502704SAndreas Gohr * 74*1c502704SAndreas Gohr * @todo use checksum or other heuristic to see if we really need a new schema OTOH we probably need one nearly always!? 75*1c502704SAndreas Gohr */ 76*1c502704SAndreas Gohr protected function newSchema() { 77*1c502704SAndreas Gohr $sql = "INSERT INTO schemas (tbl, ts) VALUES (?, ?)"; 78*1c502704SAndreas Gohr $this->sqlite->query($sql, $this->table, time()); 79*1c502704SAndreas Gohr $res = $this->sqlite->query('SELECT last_insert_rowid()'); 80*1c502704SAndreas Gohr $this->newschemaid = $this->sqlite->res2single($res); 81*1c502704SAndreas Gohr $this->sqlite->res_close($res); 82*1c502704SAndreas Gohr if(!$this->newschemaid) return false; 83*1c502704SAndreas Gohr return true; 84*1c502704SAndreas Gohr } 85*1c502704SAndreas Gohr 86*1c502704SAndreas Gohr /** 87*1c502704SAndreas Gohr * Updates all the existing column infos and adds them to the new schema 88*1c502704SAndreas Gohr */ 89*1c502704SAndreas Gohr protected function updateColumns() { 90*1c502704SAndreas Gohr foreach($this->oldschema->getColumns() as $column) { 91*1c502704SAndreas Gohr $oldEntry = $column->getType()->getAsEntry(); 92*1c502704SAndreas Gohr $oldTid = $column->getTid(); 93*1c502704SAndreas Gohr $newEntry = $oldEntry; 94*1c502704SAndreas Gohr $newTid = $oldTid; 95*1c502704SAndreas Gohr $enabled = true; 96*1c502704SAndreas Gohr $sort = $column->getSort(); 97*1c502704SAndreas Gohr if(isset($this->data['cols'][$column->getColref()])){ 98*1c502704SAndreas Gohr // todo I'm not too happy with this hardcoded here - we should probably have a list of fields at one place 99*1c502704SAndreas Gohr $newEntry['config'] = $this->data['cols'][$column->getColref()]['config']; 100*1c502704SAndreas Gohr $newEntry['label'] = $this->data['cols'][$column->getColref()]['label']; 101*1c502704SAndreas Gohr $newEntry['ismulti'] = $this->data['cols'][$column->getColref()]['multi']; 102*1c502704SAndreas Gohr $newEntry['class'] = $this->data['cols'][$column->getColref()]['class']; 103*1c502704SAndreas Gohr $sort = $this->data['cols'][$column->getColref()]['sort']; 104*1c502704SAndreas Gohr 105*1c502704SAndreas Gohr // when the type definition has changed, we create a new one 106*1c502704SAndreas Gohr if(array_diff_assoc($oldEntry, $newEntry)) { 107*1c502704SAndreas Gohr $ok = $this->sqlite->storeEntry('types', $newEntry); 108*1c502704SAndreas Gohr if(!$ok) return false; 109*1c502704SAndreas Gohr $res = $this->sqlite->query('SELECT last_insert_rowid()'); 110*1c502704SAndreas Gohr if(!$res) return false; 111*1c502704SAndreas Gohr $newTid = $this->sqlite->res2single($res); 112*1c502704SAndreas Gohr $this->sqlite->res_close($res); 113*1c502704SAndreas Gohr } 114*1c502704SAndreas Gohr } else { 115*1c502704SAndreas Gohr $enabled = false; // no longer there FIXME this assumes we remove the entry from the from completely. We might not want to do that 116*1c502704SAndreas Gohr } 117*1c502704SAndreas Gohr 118*1c502704SAndreas Gohr // add this type to the schema columns 119*1c502704SAndreas Gohr $schemaEntry = array( 120*1c502704SAndreas Gohr 'sid' => $this->newschemaid, 121*1c502704SAndreas Gohr 'colref' => $column->getColref(), 122*1c502704SAndreas Gohr 'enabled' => $enabled, 123*1c502704SAndreas Gohr 'tid' => $newTid, 124*1c502704SAndreas Gohr 'sort' => $sort 125*1c502704SAndreas Gohr ); 126*1c502704SAndreas Gohr $ok = $this->sqlite->storeEntry('schema_cols', $schemaEntry); 127*1c502704SAndreas Gohr if(!$ok) return false; 128*1c502704SAndreas Gohr } 129*1c502704SAndreas Gohr return true; 130*1c502704SAndreas Gohr } 131*1c502704SAndreas Gohr 132*1c502704SAndreas Gohr /** 133*1c502704SAndreas Gohr * Adds new columns to the new schema 134*1c502704SAndreas Gohr * 135*1c502704SAndreas Gohr * @return bool 136*1c502704SAndreas Gohr */ 137*1c502704SAndreas Gohr protected function addColumns() { 138*1c502704SAndreas Gohr if(!isset($this->data['new'])) return true; 139*1c502704SAndreas Gohr 140*1c502704SAndreas Gohr $colref = count($this->oldschema->getColumns())+1; 141*1c502704SAndreas Gohr 142*1c502704SAndreas Gohr foreach($this->data['new'] as $column) { 143*1c502704SAndreas Gohr // todo this duplicates the hardcoding as in the function above 144*1c502704SAndreas Gohr $newEntry = array(); 145*1c502704SAndreas Gohr $newEntry['config'] = $column['config']; 146*1c502704SAndreas Gohr $newEntry['label'] = $column['label']; 147*1c502704SAndreas Gohr $newEntry['ismulti'] = $column['multi']; 148*1c502704SAndreas Gohr $newEntry['class'] = $column['class']; 149*1c502704SAndreas Gohr $sort = $column['sort']; 150*1c502704SAndreas Gohr $enabled = true; 151*1c502704SAndreas Gohr 152*1c502704SAndreas Gohr // save the type 153*1c502704SAndreas Gohr $ok = $this->sqlite->storeEntry('types', $newEntry); 154*1c502704SAndreas Gohr if(!$ok) return false; 155*1c502704SAndreas Gohr $res = $this->sqlite->query('SELECT last_insert_rowid()'); 156*1c502704SAndreas Gohr if(!$res) return false; 157*1c502704SAndreas Gohr $newTid = $this->sqlite->res2single($res); 158*1c502704SAndreas Gohr $this->sqlite->res_close($res); 159*1c502704SAndreas Gohr 160*1c502704SAndreas Gohr 161*1c502704SAndreas Gohr // add this type to the schema columns 162*1c502704SAndreas Gohr $schemaEntry = array( 163*1c502704SAndreas Gohr 'sid' => $this->newschemaid, 164*1c502704SAndreas Gohr 'colref' => $colref, 165*1c502704SAndreas Gohr 'enabled' => $enabled, 166*1c502704SAndreas Gohr 'tid' => $newTid, 167*1c502704SAndreas Gohr 'sort' => $sort 168*1c502704SAndreas Gohr ); 169*1c502704SAndreas Gohr $ok = $this->sqlite->storeEntry('schema_cols', $schemaEntry); 170*1c502704SAndreas Gohr if(!$ok) return false; 171*1c502704SAndreas Gohr $colref++; 172*1c502704SAndreas Gohr } 173*1c502704SAndreas Gohr 174*1c502704SAndreas Gohr return true; 175*1c502704SAndreas Gohr } 176*1c502704SAndreas Gohr 177*1c502704SAndreas Gohr /** 178*1c502704SAndreas Gohr * Create a completely new data table 179*1c502704SAndreas Gohr * 180*1c502704SAndreas Gohr * @todo how do we want to handle indexes? 181*1c502704SAndreas Gohr * @return bool 182*1c502704SAndreas Gohr */ 183*1c502704SAndreas Gohr protected function newDataTable() { 184*1c502704SAndreas Gohr $tbl = 'data_' . $this->table; 185*1c502704SAndreas Gohr $cols = count($this->data['new']); // number of columns in the schema 186*1c502704SAndreas Gohr 187*1c502704SAndreas Gohr $sql = "CREATE TABLE $tbl ( 188*1c502704SAndreas Gohr pid NOT NULL, 189*1c502704SAndreas Gohr rev INTEGER NOT NULL,\n"; 190*1c502704SAndreas Gohr for($i = 1; $i <= $cols; $i++) { 191*1c502704SAndreas Gohr $sql .= "col$i DEFAULT '',\n"; 192*1c502704SAndreas Gohr } 193*1c502704SAndreas Gohr $sql .= "PRIMARY KEY(pid, rev) )"; 194*1c502704SAndreas Gohr 195*1c502704SAndreas Gohr return (bool) $this->sqlite->query($sql); 196*1c502704SAndreas Gohr } 197*1c502704SAndreas Gohr 198*1c502704SAndreas Gohr /** 199*1c502704SAndreas Gohr * Add additional columns to an existing data table 200*1c502704SAndreas Gohr * 201*1c502704SAndreas Gohr * @return bool 202*1c502704SAndreas Gohr */ 203*1c502704SAndreas Gohr protected function updateDataTable() { 204*1c502704SAndreas Gohr $tbl = 'data_' . $this->table; 205*1c502704SAndreas Gohr $oldcols = count($this->oldschema->getColumns()); // number of columns in the old schema 206*1c502704SAndreas Gohr $newcols = count($this->data['new']); // number of *new* columns in the schema 207*1c502704SAndreas Gohr 208*1c502704SAndreas Gohr for($i = $oldcols+1; $i <= $oldcols + $newcols; $i++) { 209*1c502704SAndreas Gohr $sql = " ALTER TABLE $tbl ADD COLUMN col$i DEFAULT ''"; 210*1c502704SAndreas Gohr if(! $this->sqlite->query($sql)) { 211*1c502704SAndreas Gohr return false; 212*1c502704SAndreas Gohr } 213*1c502704SAndreas Gohr } 214*1c502704SAndreas Gohr 215*1c502704SAndreas Gohr return true; 216*1c502704SAndreas Gohr } 217*1c502704SAndreas Gohr 218*1c502704SAndreas Gohr} 219