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