cede0c5e04da324bbdf3b5ade733cdddf3c1a8fb
[tine20] / tine20 / Setup / Backend / Mysql.php
1 <?php
2 /**
3  * Tine 2.0
4  *
5  * @package     Setup
6  * @subpackage  Backend
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @author      Lars Kneschke <l.kneschke@metaways.de>
9  * @copyright   Copyright (c) 2008-2011 Metaways Infosystems GmbH (http://www.metaways.de)
10  *
11  */
12
13 /**
14  * setup backend class for MySQL 5.0 +
15  *
16  * @package     Setup
17  * @subpackage  Backend
18  */
19 class Setup_Backend_Mysql extends Setup_Backend_Abstract
20 {
21     /**
22      * Define how database agnostic data types get mapped to mysql data types
23      * 
24      * @var array
25      */
26     protected $_typeMappings = array(
27         'integer' => array(
28             'lengthTypes' => array(
29                 4 => 'tinyint',
30                 19 => 'int',
31                 64 => 'bigint'),
32             'defaultType' => 'int',
33             'defaultLength' => self::INTEGER_DEFAULT_LENGTH),
34         'boolean' => array(
35             'defaultType' => 'tinyint',
36             'defaultLength' => 1),
37         'text' => array(
38             'lengthTypes' => array(
39                 256 => 'varchar', //@todo this should be 255 indeed but we have 256 in our setup.xml files
40                 65535 => 'text',
41                 16777215 => 'mediumtext',
42                 4294967295 => 'longtext'),
43             'defaultType' => 'text',
44             'defaultLength' => null),
45         'float' => array(
46             'defaultType' => 'double'),
47         'decimal' => array(
48             'lengthTypes' => array(
49                 65 => 'decimal'),
50             'defaultType' => 'decimal',
51             'defaultScale' => '0'),
52         'datetime' => array(
53             'defaultType' => 'datetime'),
54         'time' => array(
55             'defaultType' => 'time'),
56         'date' => array(
57             'defaultType' => 'date'),
58         'blob' => array(
59             'defaultType' => 'longblob'),
60         'clob' => array(
61             'defaultType' => 'longtext'),
62         'enum' => array(
63             'defaultType' => 'enum')
64     );
65  
66     /**
67      * get create table statement
68      * 
69      * @param Setup_Backend_Schema_Table_Abstract $_table
70      * @return string
71      */
72     public function getCreateStatement(Setup_Backend_Schema_Table_Abstract  $_table)
73     {
74         $statement = "CREATE TABLE IF NOT EXISTS `" . SQL_TABLE_PREFIX . $_table->name . "` (\n";
75         $statementSnippets = array();
76      
77         foreach ($_table->fields as $field) {
78             if (isset($field->name)) {
79                $statementSnippets[] = $this->getFieldDeclarations($field);
80             }
81         }
82
83         foreach ($_table->indices as $index) {
84             if ($index->foreign) {
85                $statementSnippets[] = $this->getForeignKeyDeclarations($index);
86             } else {
87                $statementSnippets[] = $this->getIndexDeclarations($index);
88             }
89         }
90
91         $statement .= implode(",\n", $statementSnippets) . "\n)";
92
93         if (isset($_table->engine)) {
94             $statement .= " ENGINE=" . $_table->engine . " DEFAULT CHARSET=" . $_table->charset;
95         } else {
96             $statement .= " ENGINE=InnoDB DEFAULT CHARSET=utf8 ";
97         }
98
99         if (isset($_table->comment)) {
100             $statement .= " COMMENT='" . $_table->comment . "'";
101         }
102         
103         return $statement;
104     }
105     
106     /**
107      * (non-PHPdoc)
108      * @see Setup_Backend_Interface::getExistingForeignKeys()
109      */
110     public function getExistingForeignKeys($tableName)
111     {
112         $select = $this->_db->select()
113             ->from(array('table_constraints' => 'INFORMATION_SCHEMA.TABLE_CONSTRAINTS'), array('TABLE_NAME', 'CONSTRAINT_NAME'))
114             ->join(
115                 array('key_column_usage' => 'INFORMATION_SCHEMA.KEY_COLUMN_USAGE'), 
116                 $this->_db->quoteIdentifier('table_constraints.CONSTRAINT_NAME') . '=' . $this->_db->quoteIdentifier('key_column_usage.CONSTRAINT_NAME'),
117                 array()
118             )
119             ->where($this->_db->quoteIdentifier('table_constraints.CONSTRAINT_SCHEMA')    . ' = ?', $this->_config->database->dbname)
120             ->where($this->_db->quoteIdentifier('table_constraints.CONSTRAINT_TYPE')      . ' = ?', 'FOREIGN KEY')
121             ->where($this->_db->quoteIdentifier('key_column_usage.REFERENCED_TABLE_NAME') . ' = ?', SQL_TABLE_PREFIX . $tableName);
122         
123         $foreignKeyNames = array();
124         
125         $stmt = $select->query();
126         while ($row = $stmt->fetch()) {
127             $foreignKeyNames[$row['CONSTRAINT_NAME']] = array(
128                 'table_name'      => str_replace(SQL_TABLE_PREFIX, '', $row['TABLE_NAME']), 
129                 'constraint_name' => str_replace(SQL_TABLE_PREFIX, '', $row['CONSTRAINT_NAME']));
130         }
131         
132         return $foreignKeyNames;
133     }
134     
135     /**
136      * Get schema of existing table
137      * 
138      * @param String $_tableName
139      * 
140      * @return Setup_Backend_Schema_Table_Mysql
141      */
142     public function getExistingSchema($_tableName)
143     {
144         // Get common table information
145         $select = $this->_db->select()
146             ->from('information_schema.tables')
147             ->where($this->_db->quoteIdentifier('TABLE_SCHEMA') . ' = ?', $this->_config->database->dbname)
148             ->where($this->_db->quoteIdentifier('TABLE_NAME') . ' = ?',  SQL_TABLE_PREFIX . $_tableName);
149           
150           
151         $stmt = $select->query();
152         $tableInfo = $stmt->fetchObject();
153         
154         //$existingTable = new Setup_Backend_Schema_Table($tableInfo);
155         $existingTable = Setup_Backend_Schema_Table_Factory::factory('Mysql', $tableInfo);
156        // get field informations
157         $select = $this->_db->select()
158             ->from('information_schema.COLUMNS')
159             ->where($this->_db->quoteIdentifier('TABLE_NAME') . ' = ?', SQL_TABLE_PREFIX .  $_tableName);
160
161         $stmt = $select->query();
162         $tableColumns = $stmt->fetchAll();
163
164         foreach ($tableColumns as $tableColumn) {
165             $field = Setup_Backend_Schema_Field_Factory::factory('Mysql', $tableColumn);
166             $existingTable->addField($field);
167             
168             if ($field->primary === 'true' || $field->unique === 'true' || $field->mul === 'true') {
169                 $index = Setup_Backend_Schema_Index_Factory::factory('Mysql', $tableColumn);
170                         
171                 // get foreign keys
172                 $select = $this->_db->select()
173                     ->from('information_schema.KEY_COLUMN_USAGE')
174                     ->where($this->_db->quoteIdentifier('TABLE_NAME') . ' = ?', SQL_TABLE_PREFIX .  $_tableName)
175                     ->where($this->_db->quoteIdentifier('COLUMN_NAME') . ' = ?', $tableColumn['COLUMN_NAME']);
176
177                 $stmt = $select->query();
178                 $keyUsage = $stmt->fetchAll();
179
180                 foreach ($keyUsage as $keyUse) {
181                     if ($keyUse['REFERENCED_TABLE_NAME'] != NULL) {
182                         $index->setForeignKey($keyUse);
183                     }
184                 }
185                 $existingTable->addIndex($index);
186             }
187         }
188         
189         return $existingTable;
190     }
191
192     /**
193      * add column/field to database table
194      * 
195      * @param string tableName
196      * @param Setup_Backend_Schema_Field_Abstract declaration
197      * @param int position of future column
198      */    
199     public function addCol($_tableName, Setup_Backend_Schema_Field_Abstract $_declaration, $_position = NULL)
200     {
201         $statement = "ALTER TABLE `" . SQL_TABLE_PREFIX . $_tableName . "` ADD COLUMN " ;
202         
203         $statement .= $this->getFieldDeclarations($_declaration);
204         
205         if ($_position !== NULL) {
206             if ($_position == 0) {
207                 $statement .= ' FIRST ';
208             } else {
209                 $before = $this->execQuery('DESCRIBE `' . SQL_TABLE_PREFIX . $_tableName . '` ');
210                 $statement .= ' AFTER `' . $before[$_position]['Field'] . '`';
211             }
212         }
213
214         $this->execQueryVoid($statement);
215     }
216     
217     /**
218      * rename or redefines column/field in database table
219      * 
220      * @param string tableName
221      * @param Setup_Backend_Schema_Field_Abstract declaration
222      * @param string old column/field name 
223      */    
224     public function alterCol($_tableName, Setup_Backend_Schema_Field_Abstract $_declaration, $_oldName = NULL)
225     {
226         $statement = "ALTER TABLE `" . SQL_TABLE_PREFIX . $_tableName . "` CHANGE COLUMN " ;
227         
228         if ($_oldName === NULL) {
229             $oldName = $_declaration->name;
230         } else {
231             $oldName = $_oldName;
232         }
233         
234         $statement .= " `" . $oldName .  "` " . $this->getFieldDeclarations($_declaration);
235         $this->execQueryVoid($statement);
236     }
237  
238     /**
239      * add a key to database table
240      * 
241      * @param string tableName 
242      * @param Setup_Backend_Schema_Index_Abstract declaration
243      */     
244     public function addIndex($_tableName ,  Setup_Backend_Schema_Index_Abstract $_declaration)
245     {
246         $statement = "ALTER TABLE `" . SQL_TABLE_PREFIX . $_tableName . "` ADD "
247                     . $this->getIndexDeclarations($_declaration);
248         $this->execQueryVoid($statement);
249     }
250
251     /**
252      * create the right mysql-statement-snippet for keys
253      *
254      * @param   Setup_Backend_Schema_Index_Abstract $_key
255      * @param String | optional $_tableName [is not used in this Backend (MySQL)]
256      * @return  string
257      * @throws  Setup_Exception_NotFound
258      */
259     public function getIndexDeclarations(Setup_Backend_Schema_Index_Abstract $_key, $_tableName = '')
260     {
261         $keys = array();
262
263         $snippet = "  KEY `" . $_key->name . "`";
264         if (!empty($_key->primary)) {
265             $snippet = '  PRIMARY KEY ';
266         } else if (!empty($_key->unique)) {
267             $snippet = "  UNIQUE KEY `" . $_key->name . "`" ;
268         }
269         
270         foreach ($_key->field as $keyfield) {
271             $key = '`' . (string)$keyfield . '`';
272             if ($_key->length !== NULL) {
273                 $key .= ' (' . $_key->length . ')';
274             }
275             else if ((isset($_key->fieldLength[(string)$keyfield]) || array_key_exists((string)$keyfield, $_key->fieldLength))) {
276                 $key .= ' (' . $_key->fieldLength[(string)$keyfield] . ')';
277             }
278             $keys[] = $key;
279         }
280
281         if (empty($keys)) {
282             throw new Setup_Exception_NotFound('no keys for index found');
283         }
284
285         $snippet .= ' (' . implode(",", $keys) . ')';
286         
287         return $snippet;
288     }
289
290     /**
291      *  create the right mysql-statement-snippet for foreign keys
292      *
293      * @param object $_key the xml index definition
294      * @return string
295      */
296     public function getForeignKeyDeclarations(Setup_Backend_Schema_Index_Abstract $_key)
297     {
298         $snippet = '  CONSTRAINT `' . SQL_TABLE_PREFIX . $_key->name . '` FOREIGN KEY ';
299         $snippet .= '(`' . $_key->field . "`) REFERENCES `" . SQL_TABLE_PREFIX
300                     . $_key->referenceTable . 
301                     "` (`" . $_key->referenceField . "`)";
302
303         if (!empty($_key->referenceOnDelete)) {
304             $snippet .= " ON DELETE " . strtoupper($_key->referenceOnDelete);
305         }
306         if (!empty($_key->referenceOnUpdate)) {
307             $snippet .= " ON UPDATE " . strtoupper($_key->referenceOnUpdate);
308         }
309         return $snippet;
310     }
311     
312     /**
313      * enable/disabled foreign key checks
314      *
315      * @param integer|string|boolean $_value
316      */
317     public function setForeignKeyChecks($_value)
318     {
319         if ($_value == 0 || $_value == 1) {
320             $this->_db->query("SET FOREIGN_KEY_CHECKS=" . $_value);
321         }
322     }
323 }