included the changes by fgsl (#4660)
[tine20] / tine20 / Setup / Backend / Pgsql.php
1 <?php
2 /**
3  * Tine 2.0
4  *
5  * @package     Setup
6  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
7  * @author      Fl├ívio Gomes da Silva Lisboa <flavio.lisboa@serpro.gov.br>
8  * @copyright   Copyright (c) 2011-2011 Metaways Infosystems GmbH (http://www.metaways.de)
9  *
10  */
11
12 /**
13  * setup backend class for PostgreSQL 8.3 +
14  * based on class Setup_Backend_Mysql
15  * @package     Setup
16  */
17 class Setup_Backend_Pgsql extends Setup_Backend_Abstract
18 {
19     /**
20      * Define how database agnostic data types get mapped to postgresql data types
21      * @todo reviews data type
22      * @var array
23      */
24     protected $_typeMappings = array(
25         'integer' => array(
26             'lengthTypes' => array(
27                 4 => 'smallint',
28                 19 => 'integer',
29                 64 => 'bigint'),
30             'defaultType' => 'integer',
31             'defaultLength' => self::INTEGER_DEFAULT_LENGTH),            
32         'boolean' => array(
33             'defaultType' => 'NUMERIC',
34             'defaultScale' => 0,
35             'defaultLength' => 1),
36         'text' => array(
37             'lengthTypes' => array(
38                 256 => 'character varying', //@todo this should be 255 indeed but we have 256 in our setup.xml files
39                 65535 => 'character varying',
40                 16777215 => 'character varying',
41                 4294967295 => 'character varying'),
42             'defaultType' => 'text',
43             'defaultLength' => null),
44         'float' => array(
45             'defaultType' => 'double precision',
46             'defaultLength' => null ),
47         'decimal' => array(
48             'defaultType' => 'numeric',
49             'defaultLength' => null ),
50         'datetime' => array(
51             'defaultType' => 'timestamp with time zone',
52             'defaultLength' => null ),
53         'time' => array(
54             'defaultType' => 'time with timezone',
55             'defaultLength' => null ),
56         'date' => array(
57             'defaultType' => 'date',
58             'defaultLength' => null ),
59         'blob' => array(
60             'defaultType' => 'text',
61             'defaultLength' => null ),
62         'clob' => array(
63             'defaultType' => 'text',
64             'defaultLength' => null ),
65         'enum' => array(
66             'defaultType' => 'enum',
67             'defaultLength' => null     )
68     );  
69     
70     /**
71      * Generates an SQL CREATE STATEMENT
72      * @param Setup_Backend_Schema_Table_Abstract $_table
73      * @return array CREATE TABLE statement, sequence, indexes
74      * @throws Setup_Exception_NotFound
75      */
76     public function getCreateStatement(Setup_Backend_Schema_Table_Abstract  $_table)
77     {
78         
79         $enums = array();
80         $statement = "CREATE TABLE " . SQL_TABLE_PREFIX . $_table->name . " (\n";
81         $statementSnippets = array();       
82
83         foreach ($_table->fields as $field) {
84             if (isset($field->name)) {
85                 // getFieldDeclarations() adds 'unsigned' that it doesn't exist in PortgreSQL
86                 if (isset($field->unsigned)) $field->unsigned = false;
87                 $fieldDeclarations = $this->getFieldDeclarations($field,$_table->name);
88                 // removes length of integer between parenthesis 
89                 $fieldDeclarations = preg_replace('/integer\([0-9][0-9]\)/', 'integer', $fieldDeclarations);
90                 $fieldDeclarations = preg_replace('/smallint\([0-9][0-9]\)/', 'smallint', $fieldDeclarations);
91                 $fieldDeclarations = preg_replace('/bigint\([0-9][0-9]\)/', 'bigint', $fieldDeclarations);
92                 // replaces integer auto_increment with serial
93                 $fieldDeclarations = str_replace('integer NOT NULL auto_increment', "serial NOT NULL", $fieldDeclarations);
94                 $statementSnippets[] = $fieldDeclarations;
95             }
96         }
97
98         $createIndexStatement = '';
99         
100         foreach ($_table->indices as $index) {
101             if ($index->foreign) {
102                 $statementSnippets[] = $this->getForeignKeyDeclarations($index);
103             } else {
104                 $statementSnippet = $this->getIndexDeclarations($index,$_table->name);
105                 if (strpos($statementSnippet, 'CREATE INDEX')!==false)
106                 {
107                     $createIndexStatement = $statementSnippet;
108                 }
109                 else
110                 {
111                     $statementSnippets[] = $statementSnippet;
112                 }
113             }
114         }
115
116         $statement .= implode(",\n", $statementSnippets) . "\n)";
117
118         Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . "\n" .  $statement . "\n" . $createIndexStatement);
119
120         return array('table'=>$statement,'index'=>$createIndexStatement);
121     }   
122     
123
124     /**
125      * Get schema of existing table
126      *
127      * @param String $_tableName
128      *
129      * @return Setup_Backend_Schema_Table_Pgsql
130      */
131     public function getExistingSchema($_tableName)
132     {
133         // Get common table information
134         $select = $this->_db->select()
135         ->from('information_schema.tables')
136         ->where($this->_db->quoteIdentifier('TABLE_SCHEMA') . ' = ?', $this->_config->database->dbname)
137         ->where($this->_db->quoteIdentifier('TABLE_NAME') . ' = ?',  SQL_TABLE_PREFIX . $_tableName);
138
139
140         $stmt = $select->query();
141         $tableInfo = $stmt->fetchObject();
142
143         //$existingTable = new Setup_Backend_Schema_Table($tableInfo);
144         $existingTable = Setup_Backend_Schema_Table_Factory::factory('Pgsql', $tableInfo);
145         // get field informations
146         $select = $this->_db->select()
147         ->from('information_schema.COLUMNS')
148         ->where($this->_db->quoteIdentifier('TABLE_NAME') . ' = ?', SQL_TABLE_PREFIX .  $_tableName);
149
150         $stmt = $select->query();
151         $tableColumns = $stmt->fetchAll();
152
153         foreach ($tableColumns as $tableColumn) {
154             $field = Setup_Backend_Schema_Field_Factory::factory('Pgsql', $tableColumn);
155             $existingTable->addField($field);
156
157             if ($field->primary === 'true' || $field->unique === 'true' || $field->mul === 'true') {
158                 $index = Setup_Backend_Schema_Index_Factory::factory('Pgsql', $tableColumn);
159
160                 // get foreign keys
161                 $select = $this->_db->select()
162                 ->from('information_schema.KEY_COLUMN_USAGE')
163                 ->where($this->_db->quoteIdentifier('TABLE_NAME') . ' = ?', SQL_TABLE_PREFIX .  $_tableName)
164                 ->where($this->_db->quoteIdentifier('COLUMN_NAME') . ' = ?', $tableColumn['COLUMN_NAME']);
165
166                 $stmt = $select->query();
167                 $keyUsage = $stmt->fetchAll();
168
169                 foreach ($keyUsage as $keyUse) {
170                     if ($keyUse['REFERENCED_TABLE_NAME'] != NULL) {
171                         $index->setForeignKey($keyUse);
172                     }
173                 }
174                 $existingTable->addIndex($index);
175             }
176         }
177
178         return $existingTable;
179     }
180
181     /**
182      * add column/field to database table
183      *
184      * @param string tableName
185      * @param Setup_Backend_Schema_Field_Abstract declaration
186      * @param int position of future column
187      */
188     public function addCol($_tableName, Setup_Backend_Schema_Field_Abstract $_declaration, $_position = NULL)
189     {
190         $statement = "ALTER TABLE '" . SQL_TABLE_PREFIX . $_tableName . "' ADD COLUMN " ;
191
192         $statement .= $this->getFieldDeclarations($_declaration);
193
194         if ($_position !== NULL) {
195             if ($_position == 0) {
196                 $statement .= ' FIRST ';
197             } else {
198                 $before = $this->execQuery('DESCRIBE \'' . SQL_TABLE_PREFIX . $_tableName . '\' ');
199                 $statement .= ' AFTER \'' . $before[$_position]['Field'] . '\'';
200             }
201         }
202
203         $this->execQueryVoid($statement);
204     }
205
206     /**
207      * rename or redefines column/field in database table
208      *
209      * @param string tableName
210      * @param Setup_Backend_Schema_Field_Abstract declaration
211      * @param string old column/field name
212      */
213     public function alterCol($_tableName, Setup_Backend_Schema_Field_Abstract $_declaration, $_oldName = NULL)
214     {
215         $statement = "ALTER TABLE '" . SQL_TABLE_PREFIX . $_tableName . "' CHANGE COLUMN " ;
216
217         if ($_oldName === NULL) {
218             $oldName = $_declaration->name;
219         } else {
220             $oldName = $_oldName;
221         }
222
223         $statement .= " '" . $oldName .  "' " . $this->getFieldDeclarations($_declaration);
224         $this->execQueryVoid($statement);
225     }
226
227     /**
228      * add a key to database table
229      *
230      * @param string tableName
231      * @param Setup_Backend_Schema_Index_Abstract declaration
232      */
233     public function addIndex($_tableName ,  Setup_Backend_Schema_Index_Abstract $_declaration)
234     {
235         $statement = "ALTER TABLE '" . SQL_TABLE_PREFIX . $_tableName . "' ADD "
236         . $this->getIndexDeclarations($_declaration);
237         $this->execQueryVoid($statement);
238     }
239
240     /**
241      * create the right pgsql-statement-snippet for keys.
242      * return constraints to add to create table statement or
243      * return create index statement
244      * @param   Setup_Backend_Schema_Index_Abstract $_key
245      * @param String | optional $_tableName [is not used in this Backend (PgSQL)]
246      * @return  string
247      * @throws  Setup_Exception_NotFound
248      */
249     public function getIndexDeclarations(Setup_Backend_Schema_Index_Abstract $_key, $_tableName = '')
250     {
251         $isNotIndex = false;
252         
253         $keys = array();
254
255         $indexes = str_replace('-', ',', $_key->name);
256         $snippet = 'CREATE INDEX  ' . $_tableName . '_' . $_key->name . ' ON ' . SQL_TABLE_PREFIX . $_tableName . "($indexes);";
257         $snippet = str_replace('-', '_', $snippet);
258         if (!empty($_key->primary)) {
259             $pkey = $_tableName . '_pkey';
260             $pkey = str_replace('-', '_', $pkey);
261             $snippet = " CONSTRAINT $pkey PRIMARY KEY ";
262             $isNotIndex = true;
263         } else if (!empty($_key->unique)) {
264             $unique = $_tableName . '_' . $_key->name . '_' . 'key';
265             $unique = str_replace('-', '_', $unique);
266             $snippet = "CONSTRAINT $unique UNIQUE " ;
267             $isNotIndex = true;
268         }
269
270         foreach ($_key->field as $keyfield) {
271             $key = (string)$keyfield;
272             $keys[] = $key;
273         }
274
275         if (empty($keys)) {
276             throw new Setup_Exception_NotFound('no keys for index found');
277         }
278
279         if ($isNotIndex)
280         {
281             $snippet .= ' (' . implode(",", $keys) . ')';
282         }
283
284         return $snippet;
285     }
286
287     /**
288      *  create the right mysql-statement-snippet for foreign keys
289      *
290      * @param object $_key the xml index definition
291      * @return string
292      */
293     public function getForeignKeyDeclarations(Setup_Backend_Schema_Index_Abstract $_key)
294     {
295         $snippet = '  CONSTRAINT ' . SQL_TABLE_PREFIX . $_key->referenceTable . '_' . $_key->field . ' FOREIGN KEY ';
296         $snippet .= '(' . $_key->field . ") REFERENCES " . SQL_TABLE_PREFIX
297         . $_key->referenceTable .
298                     " (" . $_key->referenceField . ")";
299
300         if (!empty($_key->referenceOnDelete)) {
301             $snippet .= " ON DELETE " . strtoupper($_key->referenceOnDelete);
302         }
303         if (!empty($_key->referenceOnUpdate)) {
304             $snippet .= " ON UPDATE " . strtoupper($_key->referenceOnUpdate);
305         }       
306         
307         return $snippet;
308     }
309
310     /**
311      * enable/disabled foreign key checks
312      *
313      * @param integer|string|boolean $_value
314      */
315     public function setForeignKeyChecks($_value)
316     {
317         if ($_value == 0 || $_value == 1) {
318             $this->_db->query("SET FOREIGN_KEY_CHECKS=" . $_value);
319         }
320     }
321     
322     /**
323      * takes the xml stream and creates a table
324      *
325      * @param object $_table xml stream
326      */ 
327     public function createTable(Setup_Backend_Schema_Table_Abstract  $_table)
328     {
329         // receives an array with CREATE TABLE and CREATE INDEX statements 
330         $statements = $this->getCreateStatement($_table);
331         
332         try {
333             // creates table
334             $this->execQueryVoid($statements['table']);
335             
336             // creates indexes
337             if (!empty($statements['index'])) $this->execQueryVoid($statements['index']);           
338         }
339         catch (Exception $e)
340         {
341             Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Exception: ' . $e->getMessage() . ' Trace: ' . $e->getTraceAsString());
342         }
343     }
344         
345 }