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