xref: /plugin/struct/meta/SchemaBuilder.php (revision 351ea4eaf542017b6e75f78ae82602c3c5d47778)
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
67        // create or update the data table
68        if($this->oldschema->getId()) {
69            $ok = $this->updateDataTable();
70        } else {
71            $ok = $this->newDataTable();
72        }
73        if(!$ok) return false;
74
75        // create a new schema
76        if(!$this->newSchema()) return false;
77
78        // update column info
79        if(!$this->updateColumns()) return false;
80        if(!$this->addColumns()) return false;
81
82        $this->sqlite->query('COMMIT TRANSACTION');
83
84        return $this->newschemaid;
85    }
86
87    /**
88     * Creates a new schema
89     *
90     * @todo use checksum or other heuristic to see if we really need a new schema OTOH we probably need one nearly always!?
91     */
92    protected function newSchema() {
93        $sql = "INSERT INTO schemas (tbl, ts) VALUES (?, ?)";
94        $this->sqlite->query($sql, $this->table, time());
95        $res = $this->sqlite->query('SELECT last_insert_rowid()');
96        $this->newschemaid = $this->sqlite->res2single($res);
97        $this->sqlite->res_close($res);
98        if(!$this->newschemaid) return false;
99        return true;
100    }
101
102    /**
103     * Updates all the existing column infos and adds them to the new schema
104     */
105    protected function updateColumns() {
106        foreach($this->oldschema->getColumns() as $column) {
107            $oldEntry = $column->getType()->getAsEntry();
108            $oldTid   = $column->getTid();
109            $newEntry = $oldEntry;
110            $newTid   = $oldTid;
111            $enabled  = true;
112            $sort = $column->getSort();
113            if(isset($this->data['cols'][$column->getColref()])){
114                // todo I'm not too happy with this hardcoded here - we should probably have a list of fields at one place
115                $newEntry['config'] = $this->data['cols'][$column->getColref()]['config'];
116                $newEntry['label'] = $this->data['cols'][$column->getColref()]['label'];
117                $newEntry['ismulti'] = $this->data['cols'][$column->getColref()]['multi'];
118                $newEntry['class'] = $this->data['cols'][$column->getColref()]['class'];
119                $sort = $this->data['cols'][$column->getColref()]['sort'];
120
121                // when the type definition has changed, we create a new one
122                if(array_diff_assoc($oldEntry, $newEntry)) {
123                    $ok = $this->sqlite->storeEntry('types', $newEntry);
124                    if(!$ok) return false;
125                    $res = $this->sqlite->query('SELECT last_insert_rowid()');
126                    if(!$res) return false;
127                    $newTid = $this->sqlite->res2single($res);
128                    $this->sqlite->res_close($res);
129                }
130            } else {
131                $enabled = false; // no longer there FIXME this assumes we remove the entry from the form completely. We might not want to do that
132            }
133
134            // add this type to the schema columns
135            $schemaEntry = array(
136                'sid' => $this->newschemaid,
137                'colref' => $column->getColref(),
138                'enabled' => $enabled,
139                'tid' => $newTid,
140                'sort' => $sort
141            );
142            $ok = $this->sqlite->storeEntry('schema_cols', $schemaEntry);
143            if(!$ok) return false;
144        }
145        return true;
146    }
147
148    /**
149     * Adds new columns to the new schema
150     *
151     * @return bool
152     */
153    protected function addColumns() {
154        if(!isset($this->data['new'])) return true;
155
156        $colref = count($this->oldschema->getColumns())+1;
157
158        foreach($this->data['new'] as $column) {
159            // todo this duplicates the hardcoding as in  the function above
160            $newEntry = array();
161            $newEntry['config'] = $column['config'];
162            $newEntry['label'] = $column['label'];
163            $newEntry['ismulti'] = $column['multi'];
164            $newEntry['class'] = $column['class'];
165            $sort = $column['sort'];
166            $enabled = true;
167
168            // save the type
169            $ok = $this->sqlite->storeEntry('types', $newEntry);
170            if(!$ok) return false;
171            $res = $this->sqlite->query('SELECT last_insert_rowid()');
172            if(!$res) return false;
173            $newTid = $this->sqlite->res2single($res);
174            $this->sqlite->res_close($res);
175
176
177            // add this type to the schema columns
178            $schemaEntry = array(
179                'sid' => $this->newschemaid,
180                'colref' => $colref,
181                'enabled' => $enabled,
182                'tid' => $newTid,
183                'sort' => $sort
184            );
185            $ok = $this->sqlite->storeEntry('schema_cols', $schemaEntry);
186            if(!$ok) return false;
187            $colref++;
188        }
189
190        return true;
191    }
192
193    /**
194     * Create a completely new data table
195     *
196     * @todo how do we want to handle indexes?
197     * @return bool
198     */
199    protected function newDataTable() {
200        $tbl = 'data_' . $this->table;
201        $cols = count($this->data['new']); // number of columns in the schema
202
203        $sql = "CREATE TABLE $tbl (
204                    pid NOT NULL,
205                    rev INTEGER NOT NULL,\n";
206        for($i = 1; $i <= $cols; $i++) {
207            $sql .= "col$i DEFAULT '',\n";
208        }
209        $sql .= "PRIMARY KEY(pid, rev) )";
210
211        return (bool) $this->sqlite->query($sql);
212    }
213
214    /**
215     * Add additional columns to an existing data table
216     *
217     * @return bool
218     */
219    protected function updateDataTable() {
220        $tbl = 'data_' . $this->table;
221        $oldcols = count($this->oldschema->getColumns()); // number of columns in the old schema
222        $newcols = count($this->data['new']); // number of *new* columns in the schema
223
224        for($i = $oldcols+1; $i <= $oldcols + $newcols; $i++) {
225            $sql = " ALTER TABLE $tbl ADD COLUMN col$i DEFAULT ''";
226            if(! $this->sqlite->query($sql)) {
227                return false;
228            }
229        }
230
231        return true;
232    }
233
234}
235