Db::dropTable()
last analyzed

Size

Total Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
nc 1
dl 0
loc 1
ccs 0
cts 0
cp 0
c 0
b 0
f 0
1
<?php
2
/**
3
 * @author Todd Burry <[email protected]>
4
 * @copyright 2009-2014 Vanilla Forums Inc.
5
 * @license MIT
6
 */
7
8
namespace Garden\Db;
9
10
/**
11
 * Defines a standard set of methods that all database drivers must conform to.
12
 */
13
abstract class Db {
14
    /// Constants ///
15
16
    const QUERY_DEFINE = 'define';
17
    const QUERY_READ = 'read';
18
    const QUERY_WRITE = 'write';
19
20
    const INDEX_PK = 'primary';
21
    const INDEX_IX = 'index';
22
    const INDEX_UNIQUE = 'unique';
23
24
    const OPTION_REPLACE = 'replace';
25
    const OPTION_IGNORE = 'ignore';
26
    const OPTION_UPSERT = 'upsert';
27
    const OPTION_TRUNCATE = 'truncate';
28
    const OPTION_DROP = 'drop';
29
    const OPTION_MODE = 'mode';
30
31
    const OP_EQ = '=';
32
    const OP_GT = '>';
33
    const OP_GTE = '>=';
34
    const OP_IN = 'in';
35
    const OP_LIKE = 'like';
36
    const OP_LT = '<';
37
    const OP_LTE = '<=';
38
    const OP_NE = '<>';
39
40
    const OP_AND = 'and';
41
    const OP_OR = 'or';
42
43
    const ORDER_ASC = 'asc';
44
    const ORDER_DESC = 'desc';
45
46
    const FETCH_TABLENAMES = 0x1;
47
    const FETCH_COLUMNS = 0x2;
48
    const FETCH_INDEXES = 0x4;
49
50
    const MODE_EXEC = 0x1;
51
    const MODE_ECHO = 0x2;
52
    const MODE_SQL = 0x4;
53
    const MODE_PDO = 0x8;
54
55
    /// Properties ///
56
57
    /**
58
     * @var string The database prefix.
59
     */
60
    protected $px = 'gdn_';
61
62
    /**
63
     * @var int The query execution mode.
64
     */
65
    protected $mode = Db::MODE_EXEC;
66
67
    /**
68
     * @var array A cached copy of the table schemas.
69
     */
70
    protected $tables = [];
71
72
    /**
73
     * @var int Whether or not all the tables have been fetched.
74
     */
75
    protected $allTablesFetched = 0;
76
77
    /**
78
     * @var int The number of rows that were affected by the last query.
79
     */
80
    protected $rowCount;
81
82
    /// Methods ///
83
84
    /**
85
     * Create the appropriate db driver given a config.
86
     *
87
     * @param array $config The configuration used to initialize the object.
88
     * The config must have a driver key which names the db class to create.
89
     * @throws \Exception Throws an exception when the config isn't complete.
90
     */
91
    public static function create($config) {
92
        $driver = val('driver', $config);
93
        if (!$driver) {
94
            throw new \Exception('You must specify a driver.', 500);
95
        }
96
97
        if (strpos($driver, '\\') === false) {
98
            $class = '\Garden\Db\\'.$driver;
99
        } else {
100
            // TODO: Check against a white list of db drivers.
101
            $class = $driver;
102
        }
103
104
        if (!class_exists($class)) {
105
            throw new \Exception("Class $class does not exist.", 500);
106
        }
107
108
        $db = new $class($config);
109
        return $db;
110
    }
111
112
    /**
113
     * Add a table to the database.
114
     *
115
     * @param string $tablename The name of the table.
116
     * @param array $tabledef The table definition.
117
     * @param array $options An array of additional options when adding the table.
118
     */
119
    abstract protected function createTable($tablename, array $tabledef, array $options = []);
120
121
    /**
122
     * Alter a table in the database.
123
     *
124
     * When altering a table you pass an array with three optional keys: add, drop, and alter.
125
     * Each value is consists of a table definition in a format that would be passed to {@link Db::setTableDef()}.
126
     *
127
     * @param string $tablename The name of the table.
128
     * @param array $alterdef The alter definition.
129
     * @param array $options An array of additional options when adding the table.
130
     */
131
    abstract protected function alterTable($tablename, array $alterdef, array $options = []);
132
133
    /**
134
     * Drop a table.
135
     *
136
     * @param string $tablename The name of the table to drop.
137
     * @param array $options An array of additional options when adding the table.
138
     */
139
    abstract public function dropTable($tablename, array $options = []);
140
141
    /**
142
     * Get a table definition.
143
     *
144
     * @param string $tablename The name of the table.
145
     * @return array|null Returns the table definition or null if the table does not exist.
146
     */
147 44
    public function getTableDef($tablename) {
148 44
        $ltablename = strtolower($tablename);
149
150
        // Check to see if the table isn't in the cache first.
151 44
        if ($this->allTablesFetched & Db::FETCH_TABLENAMES &&
152 44
            !isset($this->tables[$ltablename])) {
153 10
            return null;
154
        }
155
156
        if (
157 39
            isset($this->tables[$ltablename]) &&
158 39
            is_array($this->tables[$ltablename]) &&
159 39
            isset($this->tables[$ltablename]['columns'], $this->tables[$ltablename]['indexes'])
160 39
        ) {
161 39
            return $this->tables[$ltablename];
162
        }
163 8
        return [];
164
    }
165
166
    /**
167
     * Get all of the tables in the database.
168
     *
169
     * @param bool $withDefs Whether or not to return the full table definitions or just the table names.
170
     * @return array Returns an array of either the table definitions or the table names.
171
     */
172 2
    public function getAllTables($withDefs = false) {
173 2
        if ($withDefs && ($this->allTablesFetched & Db::FETCH_COLUMNS)) {
174
            return $this->tables;
175 2
        } elseif (!$withDefs && ($this->allTablesFetched & Db::FETCH_TABLENAMES)) {
176 2
            return array_keys($this->tables);
177
        } else {
178 2
            return null;
179
        }
180
    }
181
182
    /**
183
     * Set a table definition to the database.
184
     *
185
     * @param string $tablename The name of the table.
186
     * @param array $tableDef The table definition.
187
     * @param array $options An array of additional options when adding the table.
188
     */
189 44
    public function setTableDef($tablename, array $tableDef, array $options = []) {
190 44
        $ltablename = strtolower($tablename);
191 44
        $tableDef['name'] = $tablename;
192 44
        $drop = val(Db::OPTION_DROP, $options, false);
193 44
        $curTable = $this->getTableDef($tablename);
194
195 44
        $this->fixIndexes($tablename, $tableDef, $curTable);
196
197 44
        if (!$curTable) {
198 16
            $this->createTable($tablename, $tableDef, $options);
199 16
            $this->tables[$ltablename] = $tableDef;
200 16
            return;
201
        }
202
        // This is the alter statement.
203 36
        $alterDef = [];
204
205
        // Figure out the columns that have changed.
206 36
        $curColumns = (array)val('columns', $curTable, []);
207 36
        $newColumns = (array)val('columns', $tableDef, []);
208
209 36
        $alterDef['add']['columns'] = array_diff_key($newColumns, $curColumns);
210 36
        $alterDef['alter']['columns'] = array_uintersect_assoc($newColumns, $curColumns, function ($new, $curr) {
211
            // Return 0 if the values are different, not the same.
212 36
            if (val('type', $curr) !== val('type', $new) ||
213 36
                val('required', $curr) !== val('required', $new) ||
214 36
                val('default', $curr) !== val('required', $new)) {
215 8
                return 0;
216
            }
217 28
            return 1;
218 36
        });
219
220
        // Figure out the indexes that have changed.
221 36
        $curIndexes = (array)val('indexes', $curTable, []);
222 36
        $newIndexes = (array)val('indexes', $tableDef, []);
223
224 36
        $alterDef['add']['indexes'] = array_udiff($newIndexes, $curIndexes, [$this, 'indexCompare']);
225
226 36
        $dropIndexes = array_udiff($curIndexes, $newIndexes, [$this, 'indexCompare']);
227 36
        if ($drop) {
228 2
            $alterDef['drop']['columns'] = array_diff_key($curColumns, $newColumns);
229 2
            $alterDef['drop']['indexes'] = $dropIndexes;
230 2
        } else {
231 34
            $alterDef['drop']['columns'] = [];
232 34
            $alterDef['drop']['indexes'] = [];
233
234
            // If the primary key has changed then the old one needs to be dropped.
235 34
            if (isset($dropIndexes[Db::INDEX_PK])) {
236 4
                $alterDef['drop']['indexes'][Db::INDEX_PK] = $dropIndexes[Db::INDEX_PK];
237 4
            }
238
        }
239
240
        // Check to see if any alterations at all need to be made.
241 36
        if (empty($alterDef['add']['columns']) && empty($alterDef['add']['indexes']) &&
242 36
            empty($alterDef['drop']['columns']) && empty($alterDef['drop']['indexes']) &&
243 36
            empty($alterDef['alter']['columns'])) {
244 28
            return;
245
        }
246
247 8
        $alterDef['def'] = $tableDef;
248
249
        // Alter the table.
250 8
        $this->alterTable($tablename, $alterDef, $options);
251
252
        // Update the cached schema.
253 8
        $tableDef['name'] = $tablename;
254 8
        $this->tables[$ltablename] = $tableDef;
255 8
    }
256
257
    /**
258
     * Move the primary key index into the correct place for database drivers.
259
     *
260
     * @param string $tablename The name of the table.
261
     * @param array &$tableDef The table definition.
262
     * @param array|null $curTableDef The current database table def used to resolve conflicts in some names.
263
     * @throws \Exception Throws an exception when there is a mismatch between the primary index and the primary key
264
     * defined on the columns themselves.
265
     */
266 44
    protected function fixIndexes($tablename, array &$tableDef, $curTableDef = null) {
267
        // Loop through the columns and add get the primary key index.
268 44
        $primaryColumns = [];
269 44
        foreach ($tableDef['columns'] as $cname => $cdef) {
270 44
            if (val('primary', $cdef)) {
271 4
                $primaryColumns[] = $cname;
272 4
            }
273 44
        }
274
275
        // Massage the primary key index.
276 44
        $primaryFound = false;
277 44
        array_touch('indexes', $tableDef, []);
278 44
        foreach ($tableDef['indexes'] as &$indexDef) {
279 44
            array_touch('name', $indexDef, $this->buildIndexName($tablename, $indexDef));
280
281 44
            if (val('type', $indexDef) === Db::INDEX_PK) {
282 8
                $primaryFound = true;
283
284 8
                if (empty($primaryColumns)) {
285 6
                    foreach ($indexDef['columns'] as $cname) {
286 6
                        $tableDef['columns'][$cname]['primary'] = true;
287 6
                    }
288 8
                } elseif (array_diff($primaryColumns, $indexDef['columns'])) {
289
                    throw new \Exception("There is a mismatch in the primary key index and primary key columns.", 500);
290
                }
291 44
            } elseif (isset($curTableDef['indexes'])) {
292 32
                $curIndexDef = array_usearch($indexDef, $curTableDef['indexes'], [$this, 'indexCompare']);
293 32
                if ($curIndexDef && isset($curIndexDef['name'])) {
294 30
                    $indexDef['name'] = $curIndexDef['name'];
295 30
                }
296 32
            }
297 44
        }
298
299 44
        if (!$primaryFound && !empty($primaryColumns)) {
300 2
            $tableDef['indexes'][db::INDEX_PK] = [
301 2
                'columns' => $primaryColumns,
302
                'type' => Db::INDEX_PK
303 2
            ];
304 2
        }
305 44
    }
306
307
    /**
308
     * Get the database prefix.
309
     *
310
     * @return string Returns the current db prefix.
311
     */
312
    public function getPx() {
313
        return $this->px;
314
    }
315
316
    /**
317
     * Set the database prefix.
318
     *
319
     * @param string $px The new database prefix.
320
     */
321
    public function setPx($px) {
322
        $this->px = $px;
323
    }
324
325
    /**
326
     * Compare two index definitions to see if they have the same columns and same type.
327
     *
328
     * @param array $a The first index.
329
     * @param array $b The second index.
330
     * @return int Returns an integer less than, equal to, or greater than zero if {@link $a} is
331
     * considered to be respectively less than, equal to, or greater than {@link $b}.
332
     */
333 36
    public function indexCompare(array $a, array $b) {
334 36
        if ($a['columns'] > $b['columns']) {
335 6
            return 1;
336 36
        } elseif ($a['columns'] < $b['columns']) {
337 6
            return -1;
338
        }
339
340 30
        return strcmp(val('type', $a, ''), val('type', $b, ''));
341
    }
342
343
    /**
344
     * Get data from the database.
345
     *
346
     * @param string $tablename The name of the table to get the data from.
347
     * @param array $where An array of where conditions.
348
     * @param array $options An array of additional options.
349
     * @return mixed Returns the result set.
350
     */
351
    abstract public function get($tablename, array $where, array $options = []);
352
353
    /**
354
     * Get a single row from the database.
355
     *
356
     * This is a conveinience method that calls {@link Db::get()} and shifts off the first row.
357
     *
358
     * @param string $tablename The name of the table to get the data from.
359
     * @param array $where An array of where conditions.
360
     * @param array $options An array of additional options.
361
     * @return array|false Returns the row or false if there is no row.
362
     */
363 12
    public function getOne($tablename, array $where, array $options = []) {
364 12
        $options['limit'] = 1;
365 12
        $rows = $this->get($tablename, $where, $options);
366 12
        return array_shift($rows);
367
    }
368
369
    /**
370
     * Insert a row into a table.
371
     *
372
     * @param string $tablename The name of the table to insert into.
373
     * @param array $row The row of data to insert.
374
     * @param array $options An array of options for the insert.
375
     *
376
     * Db::OPTION_IGNORE
377
     * : Whether or not to ignore inserts that lead to a duplicate key. *default false*
378
     * Db::OPTION_REPLACE
379
     * : Whether or not to replace duplicate keys. *default false*
380
     * Db::OPTION_UPSERT
381
     * : Whether or not to update the existing data when duplicate keys exist.
382
     *
383
     * @return mixed Should return the id of the inserted record.
384
     * @see Db::load()
385
     */
386
    abstract public function insert($tablename, array $row, array $options = []);
387
388
    /**
389
     * Load many rows into a table.
390
     *
391
     * @param string $tablename The name of the table to insert into.
392
     * @param \Traversable|array $rows A dataset to insert.
393
     * Note that all rows must contain the same columns.
394
     * The first row will be looked at for the structure of the insert and the rest of the rows will use this structure.
395
     * @param array $options An array of options for the inserts. See {@link Db::insert()} for details.
396
     * @return mixed
397
     * @see Db::insert()
398
     */
399
    abstract public function load($tablename, $rows, array $options = []);
400
401
402
    /**
403
     * Update a row or rows in a table.
404
     *
405
     * @param string $tablename The name of the table to update.
406
     * @param array $set The values to set.
407
     * @param array $where The where filter for the update.
408
     * @param array $options An array of options for the update.
409
     * @return mixed
410
     */
411
    abstract public function update($tablename, array $set, array $where, array $options = []);
412
413
    /**
414
     * Delete rows from a table.
415
     *
416
     * @param string $tablename The name of the table to delete from.
417
     * @param array $where The where filter of the delete.
418
     * @param array $options An array of options.
419
     *
420
     * Db:OPTION_TRUNCATE
421
     * : Truncate the table instead of deleting rows. In this case {@link $where} must be blank.
422
     * @return mixed
423
     */
424
    abstract public function delete($tablename, array $where, array $options = []);
425
426
    /**
427
     * Reset the internal table definition cache.
428
     *
429
     * @return Db Returns $this for fluent calls.
430
     */
431 8
    public function reset() {
432 8
        $this->tables = [];
433 8
        $this->allTablesFetched = 0;
434 8
        $this->rowCount = 0;
435 8
        return $this;
436
    }
437
438
    /**
439
     * Build a standardized index name from an index definition.
440
     *
441
     * @param string $tablename The name of the table the index is in.
442
     * @param array $indexDef The index definition.
443
     * @return string Returns the index name.
444
     */
445 44
    protected function buildIndexName($tablename, array $indexDef) {
446 44
        $type = val('type', $indexDef, Db::INDEX_IX);
447
448 44
        if ($type === Db::INDEX_PK) {
449 9
            return 'primary';
450
        }
451 38
        $px = val($type, [Db::INDEX_IX => 'ix_', Db::INDEX_UNIQUE => 'ux_'], 'ix_');
452 38
        $sx = val('suffix', $indexDef);
453 38
        $result = $px.$tablename.'_'.($sx ?: implode('', $indexDef['columns']));
454 38
        return $result;
455
    }
456
}
457