set explicit lenght for index
[tine20] / tine20 / Setup / Backend / Oracle.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      Lars Kneschke <l.kneschke@metaways.de>
8  * @copyright   Copyright (c) 2008 Metaways Infosystems GmbH (http://www.metaways.de)
9  *
10  */
11
12 /**
13  * setup backend class for Oracle
14  *
15  * @package     Setup
16  */
17 class Setup_Backend_Oracle extends Setup_Backend_Abstract
18 {
19     /**
20      * Define how database agnostic data types get mapped to oracle data types
21      * 
22      * @var array
23      */
24     protected $_typeMappings = array(
25         'integer' => array( //integer in oracle is NUMBER with a scale of 0
26             'lengthTypes' => array(
27                 38 => 'NUMBER'),
28             'defaultScale' => 0,
29             'defaultType' => 'NUMBER',
30             'defaultLength' => self::INTEGER_DEFAULT_LENGTH),
31         'boolean' => array(
32             'defaultType' => 'NUMBER',
33             'defaultScale' => 0,
34             'defaultLength' => 1),
35         'text' => array(
36             'lengthTypes' => array(
37                 4000 => 'VARCHAR2',  
38                 4294967295 => 'CLOB'),
39             'defaultType' => 'CLOB',
40             'defaultLength' => null),
41         'float' => array( //float in oracle is NUMBER without precision and scale options
42             'defaultType' => 'NUMBER'),
43         'decimal' => array( //decimal in oracle is NUMBER with length (precision) and scale options
44             'lengthTypes' => array(
45                 38 => 'NUMBER'),
46             'defaultType' => 'NUMBER',
47             'defaultScale' => '0'),
48         'datetime' => array(
49             'defaultType' => 'date'),
50         'date' => array(
51             'defaultType' => 'date'),
52         'time' => array(
53             'defaultType' => 'VARCHAR2',
54             'defaultLength' => 10),
55         'blob' => array(
56             'defaultType' => 'BLOB'),
57         'clob' => array(
58             //'defaultType' => 'CLOB'),
59             'defaultType' => 'VARCHAR2',
60             'defaultLength' => 4000), 
61         'enum' => array(
62             'defaultType' => 'VARCHAR2',
63             'declarationMethod' => '_getSpecialFieldDeclarationEnum')
64     );
65  
66     CONST CONSTRAINT_TYPE_PRIMARY   = 'P';
67     CONST CONSTRAINT_TYPE_FOREIGN   = 'R';
68     CONST CONSTRAINT_TYPE_CHECK     = 'C';
69     CONST CONSTRAINT_TYPE_UNIQUE    = 'U';
70     
71     protected $_table ='';
72     
73     protected $_autoincrementId = '';
74     
75     protected static $_sequence_postfix = '_s';
76     
77     /**
78      * takes the xml stream and creates a table
79      *
80      * @param object $_table xml stream
81      */
82     public function createTable(Setup_Backend_Schema_Table_Abstract $_table)
83     {
84         $this->_table = $_table->name;
85         
86         parent::createTable($_table);
87         
88         if (!empty($this->_autoincrementId)) {
89             $statement = $this->getIncrementSequence($_table->name);
90             $this->execQueryVoid($statement);
91             $statement = $this->getIncrementTrigger($_table->name);
92             $this->execQueryVoid($statement);
93             
94
95             unset($this->_autoincrementId);
96         }
97         
98        foreach ($_table->indices as $index) {
99             if (empty($index->primary) && empty($index->unique) && !$index->foreign) {
100                $this->addIndex($_table->name, $index);
101             }
102         }  
103
104         foreach ($_table->fields as $field) {
105             if (isset($field->comment)) {
106                 $this->setFieldComment($_table->name, $field->name, $field->comment);
107             }         
108         }
109         
110
111     }
112     
113     protected function _getIncrementSequenceName($_tableName)
114     {
115         return SQL_TABLE_PREFIX . substr($_tableName, 0, 25) . self::$_sequence_postfix;
116     }
117     
118     public function getIncrementSequence($_tableName) 
119     {
120         $statement = 'CREATE SEQUENCE ' . $this->_db->quoteIdentifier($this->_getIncrementSequenceName($_tableName)) . ' 
121             MINVALUE 1
122             MAXVALUE 999999999999999999999999999 
123             INCREMENT BY 1
124             START WITH 1 
125             NOCACHE  
126             NOORDER  
127             NOCYCLE
128         ';
129             
130         return $statement;
131     }
132
133     
134     protected function _getIncrementTriggerName($_tableName) 
135     {
136         return SQL_TABLE_PREFIX . substr($_tableName, 0, 25) . '_t';
137     }
138     
139     public function getIncrementTrigger($_tableName) 
140     {
141         $statement = 'CREATE OR REPLACE TRIGGER ' . $this->_db->quoteIdentifier($this->_getIncrementTriggerName($_tableName)) . '
142             BEFORE INSERT ON "' .  SQL_TABLE_PREFIX . $_tableName . '"
143             FOR EACH ROW
144             BEGIN
145             SELECT "' . SQL_TABLE_PREFIX .  substr($_tableName, 0, 25) . self::$_sequence_postfix .'".NEXTVAL INTO :NEW."' . $this->_autoincrementId .'" FROM DUAL;
146             END;
147         ';
148     
149         return $statement;
150     }
151     
152     /**
153      * get create table statement
154      * 
155      *
156      * @param Setup_Backend_Schema_Table_Abstract $_table
157      * @return string
158      */
159     public function getCreateStatement(Setup_Backend_Schema_Table_Abstract $_table)
160     {
161      
162         $statement = 'CREATE TABLE "' . SQL_TABLE_PREFIX . $_table->name . "\" (\n";
163         $statementSnippets = array();
164      
165         foreach ($_table->fields as $field) {
166            $statementSnippets[] = $this->getFieldDeclarations($field, $_table->name);
167         }
168
169         foreach ($_table->indices as $index) {
170             if ($index->foreign) {
171                $statementSnippets[] = $this->getForeignKeyDeclarations($index, $_table->name);
172             } else if ($index->primary || $index->unique) {
173                $statementSnippets[] = $this->getIndexDeclarations($index, $_table->name);
174             }
175         }
176         
177         if (isset($_table->comment)) {
178             //@todo support comments
179             //Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . '  ignoring comment because comments are currently not supported by oracle adapter.');
180         }
181         
182         $statement .= implode(",\n", $statementSnippets) . "\n)";
183         // remove ON DELETE RESTRICT, which is on default
184         $statement = str_replace('ON DELETE RESTRICT', '', $statement);
185         // auto shutup by cweiss: echo "<pre>$statement</pre>";
186         
187         return $statement;
188     }
189     
190     /**
191      * (non-PHPdoc)
192      * @see Setup_Backend_Interface::getExistingForeignKeys()
193      * @todo implement Oracle specific logic
194      */
195     public function getExistingForeignKeys($tableName)
196     {
197         return array();
198         
199         $select = $this->_db->select()
200             ->from('information_schema.table_constraints', array('constraint_name'))
201             ->where($this->_db->quoteIdentifier('constraint_catalog') . ' = ?', $this->_config->database->dbname)
202             ->where($this->_db->quoteIdentifier('constraint_type') . ' = ?', 'FOREIGN KEY')
203             ->where($this->_db->quoteIdentifier('table_name') . ' = ?', $tableName);
204         
205         $stmt = $select->query();
206         $foreignKeyNames = $stmt->fetchColumn();
207         
208         Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . 
209             ' existing foreign keys: ' . print_r($foreignKeyNames, true));
210         
211         return $foreignKeyNames;
212     }
213     
214     
215     public function getExistingSchema($_tableName)
216     {
217         $tableInfo = $this->_getTableInfo($_tableName);
218         $existingTable = Setup_Backend_Schema_Table_Factory::factory('Oracle', $tableInfo);
219         foreach ($tableInfo as $index => $tableColumn) {
220             $field = Setup_Backend_Schema_Field_Factory::factory('Oracle', $tableColumn);
221             $existingTable->addField($field);
222             if ($field->primary === 'true' || $field->unique === 'true' || $field->mul === 'true') {
223                 $index = Setup_Backend_Schema_Index_Factory::factory('Oracle', $tableColumn);
224                 $existingTable->addIndex($index);
225             }
226         }
227         
228         $foreignKeys = $this->getConstraintsForTable($_tableName, Setup_Backend_Oracle::CONSTRAINT_TYPE_FOREIGN, true);
229         foreach ($foreignKeys as $foreignKey) {
230             $index = Setup_Backend_Schema_Index_Factory::factory('Oracle', $tableColumn);
231             $index->setForeignKey($foreignKey);
232             $existingTable->addIndex($index);
233         }
234
235         return $existingTable;
236     }
237     
238     protected function _getTableInfo($_tableName)
239     {
240         $tableName = SQL_TABLE_PREFIX . $_tableName;
241         $tableInfo = Tinebase_Db_Table::getTableDescriptionFromCache($tableName, $this->_db);
242
243         $trigger = $this->_db->fetchRow("SELECT * FROM USER_TRIGGERS WHERE TRIGGER_NAME=?", array($this->_getIncrementTriggerName($_tableName)));
244         $fieldComments = $this->_getFieldComments($_tableName);
245         
246         foreach ($tableInfo as $index => $field) {
247             $field['COLUMN_COMMENT'] = isset($fieldComments[$field['COLUMN_NAME']]) ? $fieldComments[$field['COLUMN_NAME']] : null;
248          
249             switch ($field['DATA_TYPE']) {
250                 case 'VARCHAR2':
251                     $constraint = $this->_db->fetchOne("SELECT SEARCH_CONDITION FROM USER_CONSTRAINTS WHERE CONSTRAINT_NAME=?", array($this->_getConstraintEnumName($_tableName, $field['COLUMN_NAME'])));
252                     if ($constraint) {
253                         $field['DATA_TYPE'] = 'enum';
254                         //extract allowed enum values to $field['TYPE_SPECIAL']
255                         preg_match('/.* IN \((.*)\)$/', $constraint, $matches);
256                         $field['TYPE_SPECIAL'] = $matches[1];
257                     }
258                     break;
259             }
260             
261             $field['EXTRA'] = '';
262             if (isset($trigger['TRIGGER_BODY']) &&
263                 strstr($trigger['TRIGGER_BODY'], ':NEW.' . $this->_db->quoteIdentifier($field['COLUMN_NAME'])))
264                {
265                 $field['EXTRA'] = 'auto_increment';
266             }
267             //@todo aggregate more information like auto_increment, indices, constraints etc. that have not been returned by describeTable
268             
269             $tableInfo[$index] = $field;
270         }
271         
272         
273
274         
275  
276         return $tableInfo;
277     }
278     
279     protected function _sequenceExists($_tableName)
280     {
281         return (bool)$this->_db->fetchOne("SELECT SEQUENCE_NAME FROM USER_SEQUENCES WHERE SEQUENCE_NAME=?", array($this->_getIncrementSequenceName($_tableName)));
282     }
283    
284     /**
285      * Get a list of index names belonging to the given {@param $_tableName}
286      * 
287      * @param String $_tableName
288      * @return Array
289      */
290     public function getIndexesForTable($_tableName)
291     {
292         $tableName = SQL_TABLE_PREFIX . $_tableName;
293         $sql = 'SELECT INDEX_NAME FROM ' . $this->_db->quoteIdentifier('ALL_INDEXES') . ' WHERE TABLE_NAME=:tableName';
294         return $this->_db->fetchCol($sql, array('tableName' => $tableName));
295     }
296     
297     /**
298      * Get a list of constraints belonging to the given {@param $_tableName}
299      * 
300      * @param String $_tableName
301      * @param String | optional [restrict returned constraints to this type]
302      * @return Array
303      */
304     public function getConstraintsForTable($_tableName, $_constraintType = null)
305     {
306         $select = $this->_db->select();
307         $select->from('ALL_CONSTRAINTS')->where('TABLE_NAME=?', SQL_TABLE_PREFIX . $_tableName);
308         if ($_constraintType) {
309             $select->where('CONSTRAINT_TYPE=?', $_constraintType);
310         }
311         return $this->_db->fetchAll($select);
312     }
313
314     /**
315      * add column/field to database table
316      * 
317      * @param string tableName
318      * @param Setup_Backend_Schema_Field declaration
319      * @param int position of future column
320      */    
321     public function addCol($_tableName, Setup_Backend_Schema_Field_Abstract $_declaration, $_position = NULL)
322     {
323         if ($_position != NULL) {
324             throw new Setup_Backend_Exception_NotImplemented(__METHOD__ . ' parameter "$_position" is not supported in Oracle adapter');
325         }
326
327         if ($_declaration->autoincrement) {
328             throw new Setup_Backend_Exception_NotImplemented('Add column autoincrement option is not implemented in Orcale adapter');
329         }
330      
331         $statement = "ALTER TABLE " . $this->_db->quoteIdentifier(SQL_TABLE_PREFIX . $_tableName) . " ADD (" ;
332         
333         $statement .= $this->getFieldDeclarations($_declaration, $_tableName);
334         
335         $statement .= ")";
336         
337         $this->execQueryVoid($statement);
338         
339         if (isset($_declaration->comment)) {
340             $this->setFieldComment($_tableName, $_declaration->name, $_declaration->comment);
341         }
342     }
343     
344     public function setFieldComment($_tableName, $_fieldName, $_comment)
345     {
346         $statement = "COMMENT ON COLUMN " . $this->_db->quoteIdentifier(SQL_TABLE_PREFIX . $_tableName) . "." . $this->_db->quoteIdentifier($_fieldName) . " IS " . $this->_db->quote($_comment);
347         $this->execQueryVoid($statement);
348     }
349     
350     public function getFieldComment($_tableName, $_fieldName)
351     {
352         return $this->_db->fetchOne("SELECT COMMENTS FROM USER_COL_COMMENTS WHERE TABLE_NAME=:table_name AND COLUMN_NAME=:column_name", 
353             array(
354                 'table_name' => SQL_TABLE_PREFIX . $_tableName,
355                 'column_name' => $_fieldName
356             )
357         );
358     }
359     
360     protected function _getFieldComments($_tableName)
361     {
362         $fieldComments = array();
363         $fieldCommentsRaw = $this->_db->fetchAll("SELECT COLUMN_NAME, COMMENTS FROM USER_COL_COMMENTS WHERE TABLE_NAME = :table_name", array('table_name' => SQL_TABLE_PREFIX . $_tableName));
364         foreach ($fieldCommentsRaw as $fieldComment) {
365             if (!empty($fieldComment['COMMENTS'])) {
366                 $fieldComments[$fieldComment['COLUMN_NAME']] = $fieldComment['COMMENTS'];
367             }
368         }
369         return $fieldComments;
370     }
371     
372     /**
373      * rename or redefines column/field in database table
374      * 
375      * @param string tableName
376      * @param Setup_Backend_Schema_Field declaration
377      * @param string old column/field name 
378      */    
379     public function alterCol($_tableName, Setup_Backend_Schema_Field_Abstract $_declaration, $_oldName = NULL)
380     {
381         if (isset($_oldName) && $_oldName != $_declaration->name) {
382             $this->_renameCol($_tableName, $_oldName, $_declaration->name);
383         }
384
385         $statement = "ALTER TABLE " . $this->_db->quoteIdentifier(SQL_TABLE_PREFIX . $_tableName) . " MODIFY " ;
386         $oldName = $_oldName ;
387         
388         if ($_oldName == NULL) {
389             $oldName = SQL_TABLE_PREFIX . $_declaration->name;
390         }
391         
392         $statement .= $this->getFieldDeclarations($_declaration, $_tableName);
393         $this->execQueryVoid($statement);
394     }
395     
396     /**
397      * rename column/field in database table
398      * 
399      * @param string $_tableName
400      * @param string $_oldName
401      * @param string $_newName 
402      */    
403     protected function _renameCol($_tableName, $_oldName, $_newName)
404     {
405         $statement = "ALTER TABLE " . $this->_db->quoteIdentifier(SQL_TABLE_PREFIX . $_tableName) . " RENAME  COLUMN " . $this->_db->quoteIdentifier($_oldName) . ' TO ' . $this->_db->quoteIdentifier($_newName);
406         $this->execQueryVoid($statement);
407     }
408     
409     /**
410      * removes table from database
411      * 
412      * @param string tableName
413      */
414     public function dropTable($_tableName, $_applicationId = NULL)
415     {
416
417         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Dropping table ' . $_tableName);
418         $statement = "DROP TABLE " . $this->_db->quoteIdentifier(SQL_TABLE_PREFIX . $_tableName);
419         $this->execQueryVoid($statement);
420         
421         if ($_applicationId !== NULL) {
422             Tinebase_Application::getInstance()->removeApplicationTable($_applicationId, $_tableName);
423         }
424
425         try {
426             $statement = 'DROP SEQUENCE ' . $this->_db->quoteIdentifier($this->_getIncrementSequenceName($_tableName));
427             $this->execQueryVoid($statement);
428         } catch (Zend_Db_Statement_Exception $e) {
429             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " An exception was thrown while dropping sequence for table {$_tableName}: " . $e->getMessage() . "; This might be OK if the table had no sequencer.");
430         }
431     }
432  
433     /**
434      * add a key to database table
435      * 
436      * @param string tableName 
437      * @param Setup_Backend_Schema_Index_Abstract declaration
438      */     
439     public function addIndex($_tableName, Setup_Backend_Schema_Index_Abstract $_declaration)
440     {
441        $statement = $this->getIndexDeclarations($_declaration, $_tableName);
442        if (!empty($_declaration->primary) || !empty($_declaration->unique)) {
443             $statement = "ALTER TABLE " . $this->_db->quoteIdentifier(SQL_TABLE_PREFIX . $_tableName) . " ADD " . $statement;
444         }
445         
446         $this->execQueryVoid($statement);
447     }
448     
449     protected function _getConstraintEnumName($_tableName, $_fieldName)
450     {
451         $tableName = SQL_TABLE_PREFIX . $_tableName;
452         return $this->_sanititzeName('cons_' . $tableName . "_" . $_fieldName . '_enum');
453     }
454     
455     /**
456      * create the right mysql-statement-snippet for columns/fields
457      *
458      * @param Setup_Backend_Schema_Field field / column
459      * @param String $_tableName [required in this backend (Oracle)]
460      * @todo how gets unsigned handled
461      * @return string
462      */
463     public function getFieldDeclarations(Setup_Backend_Schema_Field_Abstract $_field, $_tableName = '')
464     {
465         if (empty($_tableName)) {
466             throw new Tinebase_Exception_InvalidArgument('Missing required argument $_tableName');
467         }
468         
469         return parent::getFieldDeclarations($_field, $_tableName);
470     }
471     
472     /**
473      * Override method: unsigned option is not supported by oracle backend
474      * @see tine20/Setup/Backend/Setup_Backend_Abstract#_addDeclarationUnsigned($_buffer, $_field)
475      */
476     protected function _addDeclarationUnsigned(array $_buffer, Setup_Backend_Schema_Field_Abstract $_field)
477     {
478         if (isset($_field->unsigned) && $_field->unsigned === true) {
479             //Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' $_field has property unsgined set which is currently not supported by oracle adapter; unsigned property is ignored.');
480         }
481         return $_buffer;
482     }
483     
484     /**
485      * Override method: default value option has to be handled differently for enum data type
486      * @see tine20/Setup/Backend/Setup_Backend_Abstract#_addDeclarationDefaultValue($_buffer, $_field)
487      */
488     protected function _addDeclarationDefaultValue(array $_buffer, Setup_Backend_Schema_Field_Abstract $_field)
489     {
490         if ($_field->type == 'enum') {
491             return $_buffer;
492         }
493         return parent::_addDeclarationDefaultValue($_buffer, $_field);
494     }
495     
496     /**
497      * Override method: not null option has to be handled differently for enum data type
498      * @see tine20/Setup/Backend/Setup_Backend_Abstract#_addDeclarationNotNull($_buffer, $_field)
499      */
500     protected function _addDeclarationNotNull(array $_buffer, Setup_Backend_Schema_Field_Abstract $_field)
501     {
502         if ($_field->type == 'enum') {
503             return $_buffer;
504         }
505         return parent::_addDeclarationNotNull($_buffer, $_field);
506     }
507     
508     /**
509      * Override method: autoincrementation is set up on table creation in oracle {@see createTable()}
510      * => store the name of the autoincrement field in {@see $_autoincrementId} 
511      * @see tine20/Setup/Backend/Setup_Backend_Abstract#_addDeclarationAutoincrement($_buffer, $_field)
512      */
513     protected function _addDeclarationAutoincrement(array $_buffer, Setup_Backend_Schema_Field_Abstract $_field)
514     {
515         if (isset($_field->autoincrement)) {
516             $this->_autoincrementId = $_field->name;
517         }
518         return $_buffer;
519     }
520     
521     /**
522      * Override method: comments are added after creating/aletering the table in {@see addCol()} and {@see createTable()}.
523      * 
524      * @see tine20/Setup/Backend/Setup_Backend_Abstract#_addDeclarationAutoincrement($_buffer, $_field)
525      */
526     protected function _addDeclarationComment(array $_buffer, Setup_Backend_Schema_Field_Abstract $_field)
527     {
528         return $_buffer;
529     }
530     
531     /**
532      * enum datatype is not supported by oracle so we have to emulate the behaviour using constraint checks
533      * 
534      * @param Setup_Backend_Schema_Field_Abstract $_field
535      * @param $_tableName
536      * @return array
537      */
538     protected function _getSpecialFieldDeclarationEnum(Setup_Backend_Schema_Field_Abstract $_field, $_tableName)
539     {
540         $buffer = array();
541
542         $length = 0;
543         foreach ($_field->value as $value) {
544             $values[] = $value;
545             $tempLength = strlen($value);
546             if ($tempLength > $length) {
547                 $length = $tempLength;
548             }
549         }
550         
551         $buffer[] = 'VARCHAR2(' . $length . ')';
552         
553         $additional = '';
554         if ($_field->notnull === true) {
555             $additional .= ' NOT NULL ';
556         }
557         if (isset($_field->default)) {
558             if($_field->default === NULL) {
559                 $buffer[] = "DEFAULT NULL" ;
560             } else {
561                 $buffer[] = $this->_db->quoteInto("DEFAULT ?", $_field->default) ;
562             }
563         }    
564         
565         $buffer[] = $additional . ', CONSTRAINT ' . $this->_db->quoteIdentifier($this->_getConstraintEnumName($_tableName, $_field->name)) . ' CHECK ("'. $_field->name . "\" IN ('" . implode("','", $values) . "'))";
566
567         return $buffer;
568     }
569
570     /**
571      * create the right mysql-statement-snippet for keys
572      *
573      * @param   Setup_Backend_Schema_Index_Abstract key
574      * @param   String $_tableName [parameter is required in this (Oracle) Backend. It is used to create unique index names spanning all tables of the database] 
575      * @return  String
576      * @throws  Setup_Exception_NotFound
577      */
578     public function getIndexDeclarations(Setup_Backend_Schema_Index_Abstract $_key, $_tableName = '')
579     {
580         if (empty($_tableName)) {
581             throw new Tinebase_Exception_InvalidArgument('Missing required argument $_tableName');
582         }
583
584         $keys = array();
585         if (!empty($_key->primary)) {
586             $name = $this->_sanititzeName(SQL_TABLE_PREFIX . 'pk_' . $_tableName);
587             $snippet = '  CONSTRAINT ' . $this->_db->quoteIdentifier($name) . " PRIMARY KEY";
588         } else if (!empty($_key->unique)) {
589             $name = $this->_sanititzeName(SQL_TABLE_PREFIX . "uni_" . $_tableName . "_" . $_key->name);
590             $snippet = '  CONSTRAINT ' . $this->_db->quoteIdentifier($name) . " UNIQUE";
591         } else {
592             $name = $this->_sanititzeName(SQL_TABLE_PREFIX . 'idx_' . $_tableName . "_" . $_key->name);
593             $snippet = '  CREATE INDEX ' . $this->_db->quoteIdentifier($name) . ' ON ' . $this->_db->quoteIdentifier(SQL_TABLE_PREFIX . $_tableName);
594         }        
595
596         foreach ($_key->field as $keyfield) {
597             $key = '"' . (string)$keyfield . '"';
598             if (!empty($keyfield->length)) {
599                 $key .= ' (' . $keyfield->length . ')';
600             }
601             // added
602             else if (array_key_exists((string)$keyfield, $_key->fieldLength)) {
603                 $key .= ' (' . $_key->fieldLength[(string)$keyfield] . ')';
604             }
605             $keys[] = $key;
606         }
607
608         if (empty($keys)) {
609             throw new Setup_Exception_NotFound('No keys for index found.');
610         }
611
612         $snippet .= ' (' . implode(",", $keys) . ')';
613         return $snippet;
614     }
615
616     /**
617      *  create the right mysql-statement-snippet for foreign keys
618      *
619      * @param object $_key the xml index definition
620      * @param String $_tableName [required in this backend (Oracle)]
621      * @return string
622      */
623     public function getForeignKeyDeclarations(Setup_Backend_Schema_Index_Abstract $_key, $_tableName = '')
624     {
625         if (empty($_tableName)) {
626             throw new Tinebase_Exception_InvalidArgument('Missing required argument $_tableName');
627         }
628
629         if (!empty($_key->referenceOnUpdate)) {
630             //$snippet .= " ON UPDATE " . strtoupper($_key->referenceOnUpdate);
631             // comment for now, because we can't install if we throw exception (what ca we do with ON UPDATE?)
632             //throw new Setup_Backend_Exception_NotImplemented('ON UPDATE CONSTRAINTS are not supported by Oracle adapter');
633         }
634         
635         $constraintName = $this->_sanititzeName(SQL_TABLE_PREFIX . 'fk_' . $_tableName . "_" . $_key->field);
636         $snippet = '  CONSTRAINT ' . $this->_db->quoteIdentifier($constraintName) . ' FOREIGN KEY ';
637         $snippet .= '("' . $_key->field . '") REFERENCES ' . $this->_db->quoteIdentifier(SQL_TABLE_PREFIX . $_key->referenceTable) . ' ("' . $_key->referenceField . '")';
638
639         if (!empty($_key->referenceOnDelete)) {
640             $snippet .= " ON DELETE " . strtoupper($_key->referenceOnDelete);
641         }
642         
643         return $snippet;
644     }
645
646 }