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