]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - lib/pgsqlschema.php
quick syntax fix
[quix0rs-gnu-social.git] / lib / pgsqlschema.php
1 <?php
2 /**
3  * StatusNet, the distributed open-source microblogging tool
4  *
5  * Database schema utilities
6  *
7  * PHP version 5
8  *
9  * LICENCE: This program is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU Affero General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU Affero General Public License for more details.
18  *
19  * You should have received a copy of the GNU Affero General Public License
20  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21  *
22  * @category  Database
23  * @package   StatusNet
24  * @author    Evan Prodromou <evan@status.net>
25  * @copyright 2009 StatusNet, Inc.
26  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
27  * @link      http://status.net/
28  */
29
30 if (!defined('STATUSNET')) {
31     exit(1);
32 }
33
34 /**
35  * Class representing the database schema
36  *
37  * A class representing the database schema. Can be used to
38  * manipulate the schema -- especially for plugins and upgrade
39  * utilities.
40  *
41  * @category Database
42  * @package  StatusNet
43  * @author   Evan Prodromou <evan@status.net>
44  * @author   Brenda Wallace <shiny@cpan.org>
45  * @author   Brion Vibber <brion@status.net>
46  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
47  * @link     http://status.net/
48  */
49
50 class PgsqlSchema extends Schema
51 {
52
53     /**
54      * Returns a table definition array for the table
55      * in the schema with the given name.
56      *
57      * Throws an exception if the table is not found.
58      *
59      * @param string $table Name of the table to get
60      *
61      * @return array tabledef for that table.
62      */
63
64     public function getTableDef($table)
65     {
66         $def = array();
67         $hasKeys = false;
68
69         // Pull column data from INFORMATION_SCHEMA
70         $columns = $this->fetchMetaInfo($table, 'columns', 'ordinal_position');
71         if (count($columns) == 0) {
72             throw new SchemaTableMissingException("No such table: $table");
73         }
74
75         foreach ($columns as $row) {
76
77             $name = $row['column_name'];
78             $field = array();
79
80             // ??
81             list($type, $size) = $this->reverseMapType($row['udt_name']);
82             $field['type'] = $type;
83             if ($size !== null) {
84                 $field['size'] = $size;
85             }
86
87             if ($type == 'char' || $type == 'varchar') {
88                 if ($row['character_maximum_length'] !== null) {
89                     $field['length'] = intval($row['character_maximum_length']);
90                 }
91             }
92             if ($type == 'numeric') {
93                 // Other int types may report these values, but they're irrelevant.
94                 // Just ignore them!
95                 if ($row['numeric_precision'] !== null) {
96                     $field['precision'] = intval($row['numeric_precision']);
97                 }
98                 if ($row['numeric_scale'] !== null) {
99                     $field['scale'] = intval($row['numeric_scale']);
100                 }
101             }
102             if ($row['is_nullable'] == 'NO') {
103                 $field['not null'] = true;
104             }
105             if ($row['column_default'] !== null) {
106                 $field['default'] = $row['column_default'];
107                 if ($this->isNumericType($type)) {
108                     $field['default'] = intval($field['default']);
109                 }
110             }
111
112             $def['fields'][$name] = $field;
113         }
114
115         // Pull constraint data from INFORMATION_SCHEMA
116         // @fixme also find multi-val indexes
117         // @fixme distinguish the primary key
118         // @fixme pull foreign key references
119         $keyColumns = $this->fetchMetaInfo($table, 'key_column_usage', 'constraint_name,ordinal_position');
120         $keys = array();
121
122         foreach ($keyColumns as $row) {
123             $keyName = $row['constraint_name'];
124             $keyCol = $row['column_name'];
125             if (!isset($keys[$keyName])) {
126                 $keys[$keyName] = array();
127             }
128             $keys[$keyName][] = $keyCol;
129         }
130
131         foreach ($keys as $keyName => $cols) {
132             $def['unique indexes'][$keyName] = $cols;
133         }
134         return $def;
135     }
136
137     /**
138      * Pull some INFORMATION.SCHEMA data for the given table.
139      *
140      * @param string $table
141      * @return array of arrays
142      */
143     function fetchMetaInfo($table, $infoTable, $orderBy=null)
144     {
145         $query = "SELECT * FROM information_schema.%s " .
146                  "WHERE table_name='%s'";
147         $sql = sprintf($query, $infoTable, $table);
148         if ($orderBy) {
149             $sql .= ' ORDER BY ' . $orderBy;
150         }
151         return $this->fetchQueryData($sql);
152     }
153
154     /**
155      * Creates a table with the given names and columns.
156      *
157      * @param string $name    Name of the table
158      * @param array  $columns Array of ColumnDef objects
159      *                        for new table.
160      *
161      * @return boolean success flag
162      */
163
164     public function createTable($name, $columns)
165     {
166         $uniques = array();
167         $primary = array();
168         $indices = array();
169         $onupdate = array();
170
171         $sql = "CREATE TABLE $name (\n";
172
173         for ($i = 0; $i < count($columns); $i++) {
174
175             $cd =& $columns[$i];
176
177             if ($i > 0) {
178                 $sql .= ",\n";
179             }
180
181             $sql .= $this->_columnSql($cd);
182             switch ($cd->key) {
183             case 'UNI':
184                 $uniques[] = $cd->name;
185                 break;
186             case 'PRI':
187                 $primary[] = $cd->name;
188                 break;
189             case 'MUL':
190                 $indices[] = $cd->name;
191                 break;
192             }
193         }
194
195         if (count($primary) > 0) { // it really should be...
196             $sql .= ",\n PRIMARY KEY (" . implode(',', $primary) . ")";
197         }
198
199         $sql .= "); ";
200
201
202         foreach ($uniques as $u) {
203             $sql .= "\n CREATE index {$name}_{$u}_idx ON {$name} ($u); ";
204         }
205
206         foreach ($indices as $i) {
207             $sql .= "CREATE index {$name}_{$i}_idx ON {$name} ($i)";
208         }
209         $res = $this->conn->query($sql);
210
211         if (PEAR::isError($res)) {
212             throw new Exception($res->getMessage(). ' SQL was '. $sql);
213         }
214
215         return true;
216     }
217
218     /**
219      * Translate the (mostly) mysql-ish column types into somethings more standard
220      * @param string column type
221      *
222      * @return string postgres happy column type
223      */
224     private function _columnTypeTranslation($type) {
225       $map = array(
226       'datetime' => 'timestamp',
227       );
228       if(!empty($map[$type])) {
229         return $map[$type];
230       }
231       return $type;
232     }
233
234     /**
235      * Modifies a column in the schema.
236      *
237      * The name must match an existing column and table.
238      *
239      * @param string    $table     name of the table
240      * @param ColumnDef $columndef new definition of the column.
241      *
242      * @return boolean success flag
243      */
244
245     public function modifyColumn($table, $columndef)
246     {
247         $sql = "ALTER TABLE $table ALTER COLUMN TYPE " .
248           $this->_columnSql($columndef);
249
250         $res = $this->conn->query($sql);
251
252         if (PEAR::isError($res)) {
253             throw new Exception($res->getMessage());
254         }
255
256         return true;
257     }
258
259     /**
260      * Return the proper SQL for creating or
261      * altering a column.
262      *
263      * Appropriate for use in CREATE TABLE or
264      * ALTER TABLE statements.
265      *
266      * @param string $tableName
267      * @param array $tableDef
268      * @param string $columnName
269      * @param array $cd column to create
270      *
271      * @return string correct SQL for that column
272      */
273
274     function columnSql($name, array $cd)
275     {
276         $line = array();
277         $line[] = parent::_columnSql($cd);
278
279         if ($table['foreign keys'][$name]) {
280             foreach ($table['foreign keys'][$name] as $foreignTable => $foreignColumn) {
281                 $line[] = 'references';
282                 $line[] = $this->quoteId($foreignTable);
283                 $line[] = '(' . $this->quoteId($foreignColumn) . ')';
284             }
285         }
286
287         return implode(' ', $line);
288     }
289
290     /**
291      * Append phrase(s) to an array of partial ALTER TABLE chunks in order
292      * to alter the given column from its old state to a new one.
293      *
294      * @param array $phrase
295      * @param string $columnName
296      * @param array $old previous column definition as found in DB
297      * @param array $cd current column definition
298      */
299     function appendAlterModifyColumn(array &$phrase, $columnName, array $old, array $cd)
300     {
301         $prefix = 'ALTER COLUMN ' . $this->quoteIdentifier($columnName) . ' ';
302
303         $oldType = $this->mapType($old);
304         $newType = $this->mapType($cd);
305         if ($oldType != $newType) {
306             $phrase[] = $prefix . 'TYPE ' . $newType;
307         }
308
309         if (!empty($old['not null']) && empty($cd['not null'])) {
310             $phrase[] = $prefix . 'DROP NOT NULL';
311         } else if (empty($old['not null']) && !empty($cd['not null'])) {
312             $phrase[] = $prefix . 'SET NOT NULL';
313         }
314
315         if (isset($old['default']) && !isset($cd['default'])) {
316             $phrase[] = $prefix . 'DROP DEFAULT';
317         } else if (!isset($old['default']) && isset($cd['default'])) {
318             $phrase[] = $prefix . 'SET DEFAULT ' . $this->quoteDefaultValue($cd);
319         }
320     }
321
322     /**
323      * Quote a db/table/column identifier if necessary.
324      *
325      * @param string $name
326      * @return string
327      */
328     function quoteIdentifier($name)
329     {
330         return '"' . $name . '"';
331     }
332
333     function mapType($column)
334     {
335         $map = array('serial' => 'bigserial', // FIXME: creates the wrong name for the sequence for some internal sequence-lookup function, so better fix this to do the real 'create sequence' dance.
336                      'numeric' => 'decimal',
337                      'datetime' => 'timestamp',
338                      'blob' => 'bytea');
339
340         $type = $column['type'];
341         if (isset($map[$type])) {
342             $type = $map[$type];
343         }
344
345         if (!empty($column['size'])) {
346             $size = $column['size'];
347             if ($type == 'integer' &&
348                        in_array($size, array('small', 'big'))) {
349                 $type = $size . 'int';
350             }
351         }
352
353         return $type;
354     }
355
356     // @fixme need name... :P
357     function typeAndSize($column)
358     {
359         if ($column['type'] == 'enum') {
360             $vals = array_map(array($this, 'quote'), $column['enum']);
361             return "text check ($name in " . implode(',', $vals) . ')';
362         } else {
363             return parent::typeAndSize($column);
364         }
365     }
366
367     /**
368      * Map a native type back to an independent type + size
369      *
370      * @param string $type
371      * @return array ($type, $size) -- $size may be null
372      */
373     protected function reverseMapType($type)
374     {
375         $type = strtolower($type);
376         $map = array(
377             'int4' => array('int', null),
378             'int8' => array('int', 'big'),
379             'bytea' => array('blob', null),
380         );
381         if (isset($map[$type])) {
382             return $map[$type];
383         } else {
384             return array($type, null);
385         }
386     }
387
388 }