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