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