xref: /plugin/struct/meta/SchemaBuilder.php (revision 7182938b8bb5a6371b51b072741e1a471cc88144)
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