adapter->getCell('SELECT DATABASE()'); $table = $this->esc( $type, TRUE ); $keys = $this->adapter->get(' SELECT information_schema.key_column_usage.constraint_name AS `name`, information_schema.key_column_usage.referenced_table_name AS `table`, information_schema.key_column_usage.column_name AS `from`, information_schema.key_column_usage.referenced_column_name AS `to`, information_schema.referential_constraints.update_rule AS `on_update`, information_schema.referential_constraints.delete_rule AS `on_delete` FROM information_schema.key_column_usage INNER JOIN information_schema.referential_constraints ON information_schema.referential_constraints.constraint_name = information_schema.key_column_usage.constraint_name WHERE information_schema.key_column_usage.table_schema = :database AND information_schema.referential_constraints.constraint_schema = :database AND information_schema.key_column_usage.constraint_schema = :database AND information_schema.key_column_usage.table_name = :table AND information_schema.key_column_usage.constraint_name != \'PRIMARY\' AND information_schema.key_column_usage.referenced_table_name IS NOT NULL ', array( ':database' => $databaseName, ':table' => $table ) ); $keyInfoList = array(); foreach ( $keys as $k ) { $label = $this->makeFKLabel( $k['from'], $k['table'], $k['to'] ); $keyInfoList[$label] = array( 'name' => $k['name'], 'from' => $k['from'], 'table' => $k['table'], 'to' => $k['to'], 'on_update' => $k['on_update'], 'on_delete' => $k['on_delete'] ); } return $keyInfoList; } /** * Constructor * * @param Adapter $adapter Database Adapter */ public function __construct( Adapter $adapter ) { $this->typeno_sqltype = array( MySQL::C_DATATYPE_BOOL => ' TINYINT(1) UNSIGNED ', MySQL::C_DATATYPE_UINT32 => ' INT(11) UNSIGNED ', MySQL::C_DATATYPE_DOUBLE => ' DOUBLE ', MySQL::C_DATATYPE_TEXT7 => ' VARCHAR(191) ', MYSQL::C_DATATYPE_TEXT8 => ' VARCHAR(255) ', MySQL::C_DATATYPE_TEXT16 => ' TEXT ', MySQL::C_DATATYPE_TEXT32 => ' LONGTEXT ', MySQL::C_DATATYPE_SPECIAL_DATE => ' DATE ', MySQL::C_DATATYPE_SPECIAL_DATETIME => ' DATETIME ', MySQL::C_DATATYPE_SPECIAL_POINT => ' POINT ', MySQL::C_DATATYPE_SPECIAL_LINESTRING => ' LINESTRING ', MySQL::C_DATATYPE_SPECIAL_POLYGON => ' POLYGON ', MySQL::C_DATATYPE_SPECIAL_MONEY => ' DECIMAL(10,2) ' ); $this->sqltype_typeno = array(); foreach ( $this->typeno_sqltype as $k => $v ) { $this->sqltype_typeno[trim( strtolower( $v ) )] = $k; } $this->adapter = $adapter; $this->encoding = $this->adapter->getDatabase()->getMysqlEncoding(); } /** * This method returns the datatype to be used for primary key IDS and * foreign keys. Returns one if the data type constants. * * @return integer */ public function getTypeForID() { return self::C_DATATYPE_UINT32; } /** * @see QueryWriter::getTables */ public function getTables() { return $this->adapter->getCol( 'show tables' ); } /** * @see QueryWriter::createTable */ public function createTable( $table ) { $table = $this->esc( $table ); $encoding = $this->adapter->getDatabase()->getMysqlEncoding(); $sql = "CREATE TABLE $table (id INT( 11 ) UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY ( id )) ENGINE = InnoDB DEFAULT CHARSET={$encoding} COLLATE={$encoding}_unicode_ci "; $this->adapter->exec( $sql ); } /** * @see QueryWriter::getColumns */ public function getColumns( $table ) { $columnsRaw = $this->adapter->get( "DESCRIBE " . $this->esc( $table ) ); $columns = array(); foreach ( $columnsRaw as $r ) { $columns[$r['Field']] = $r['Type']; } return $columns; } /** * @see QueryWriter::scanType */ public function scanType( $value, $flagSpecial = FALSE ) { $this->svalue = $value; if ( is_null( $value ) ) return MySQL::C_DATATYPE_BOOL; if ( $value === INF ) return MySQL::C_DATATYPE_TEXT7; if ( $flagSpecial ) { if ( preg_match( '/^-?\d+\.\d{2}$/', $value ) ) { return MySQL::C_DATATYPE_SPECIAL_MONEY; } if ( preg_match( '/^\d{4}\-\d\d-\d\d$/', $value ) ) { return MySQL::C_DATATYPE_SPECIAL_DATE; } if ( preg_match( '/^\d{4}\-\d\d-\d\d\s\d\d:\d\d:\d\d$/', $value ) ) { return MySQL::C_DATATYPE_SPECIAL_DATETIME; } if ( preg_match( '/^POINT\(/', $value ) ) { return MySQL::C_DATATYPE_SPECIAL_POINT; } if ( preg_match( '/^LINESTRING\(/', $value ) ) { return MySQL::C_DATATYPE_SPECIAL_LINESTRING; } if ( preg_match( '/^POLYGON\(/', $value ) ) { return MySQL::C_DATATYPE_SPECIAL_POLYGON; } } //setter turns TRUE FALSE into 0 and 1 because database has no real bools (TRUE and FALSE only for test?). if ( $value === FALSE || $value === TRUE || $value === '0' || $value === '1' ) { return MySQL::C_DATATYPE_BOOL; } if ( is_float( $value ) ) return self::C_DATATYPE_DOUBLE; if ( !$this->startsWithZeros( $value ) ) { if ( is_numeric( $value ) && ( floor( $value ) == $value ) && $value >= 0 && $value <= 4294967295 ) { return MySQL::C_DATATYPE_UINT32; } if ( is_numeric( $value ) ) { return MySQL::C_DATATYPE_DOUBLE; } } if ( mb_strlen( $value, 'UTF-8' ) <= 191 ) { return MySQL::C_DATATYPE_TEXT7; } if ( mb_strlen( $value, 'UTF-8' ) <= 255 ) { return MySQL::C_DATATYPE_TEXT8; } if ( mb_strlen( $value, 'UTF-8' ) <= 65535 ) { return MySQL::C_DATATYPE_TEXT16; } return MySQL::C_DATATYPE_TEXT32; } /** * @see QueryWriter::code */ public function code( $typedescription, $includeSpecials = FALSE ) { if ( isset( $this->sqltype_typeno[$typedescription] ) ) { $r = $this->sqltype_typeno[$typedescription]; } else { $r = self::C_DATATYPE_SPECIFIED; } if ( $includeSpecials ) { return $r; } if ( $r >= QueryWriter::C_DATATYPE_RANGE_SPECIAL ) { return self::C_DATATYPE_SPECIFIED; } return $r; } /** * @see QueryWriter::addUniqueIndex */ public function addUniqueConstraint( $type, $properties ) { $tableNoQ = $this->esc( $type, TRUE ); $columns = array(); foreach( $properties as $key => $column ) $columns[$key] = $this->esc( $column ); $table = $this->esc( $type ); sort( $columns ); // Else we get multiple indexes due to order-effects $name = 'UQ_' . sha1( implode( ',', $columns ) ); try { $sql = "ALTER TABLE $table ADD UNIQUE INDEX $name (" . implode( ',', $columns ) . ")"; $this->adapter->exec( $sql ); } catch ( SQLException $e ) { //do nothing, dont use alter table ignore, this will delete duplicate records in 3-ways! return FALSE; } return TRUE; } /** * @see QueryWriter::addIndex */ public function addIndex( $type, $name, $property ) { try { $table = $this->esc( $type ); $name = preg_replace( '/\W/', '', $name ); $column = $this->esc( $property ); $this->adapter->exec( "CREATE INDEX $name ON $table ($column) " ); return TRUE; } catch ( SQLException $e ) { return FALSE; } } /** * @see QueryWriter::addFK * @return bool */ public function addFK( $type, $targetType, $property, $targetProperty, $isDependent = FALSE ) { $table = $this->esc( $type ); $targetTable = $this->esc( $targetType ); $targetTableNoQ = $this->esc( $targetType, TRUE ); $field = $this->esc( $property ); $fieldNoQ = $this->esc( $property, TRUE ); $targetField = $this->esc( $targetProperty ); $targetFieldNoQ = $this->esc( $targetProperty, TRUE ); $tableNoQ = $this->esc( $type, TRUE ); $fieldNoQ = $this->esc( $property, TRUE ); if ( !is_null( $this->getForeignKeyForTypeProperty( $tableNoQ, $fieldNoQ ) ) ) return FALSE; //Widen the column if it's incapable of representing a foreign key (at least INT). $columns = $this->getColumns( $tableNoQ ); $idType = $this->getTypeForID(); if ( $this->code( $columns[$fieldNoQ] ) !== $idType ) { $this->widenColumn( $type, $property, $idType ); } $fkName = 'fk_'.($tableNoQ.'_'.$fieldNoQ); $cName = 'c_'.$fkName; try { $this->adapter->exec( " ALTER TABLE {$table} ADD CONSTRAINT $cName FOREIGN KEY $fkName ( `{$fieldNoQ}` ) REFERENCES `{$targetTableNoQ}` (`{$targetFieldNoQ}`) ON DELETE " . ( $isDependent ? 'CASCADE' : 'SET NULL' ) . ' ON UPDATE '.( $isDependent ? 'CASCADE' : 'SET NULL' ).';'); } catch ( SQLException $e ) { // Failure of fk-constraints is not a problem } return true; } /** * @see QueryWriter::sqlStateIn */ public function sqlStateIn( $state, $list ) { $stateMap = array( '42S02' => QueryWriter::C_SQLSTATE_NO_SUCH_TABLE, '42S22' => QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN, '23000' => QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION ); return in_array( ( isset( $stateMap[$state] ) ? $stateMap[$state] : '0' ), $list ); } /** * @see QueryWriter::wipeAll */ public function wipeAll() { $this->adapter->exec( 'SET FOREIGN_KEY_CHECKS = 0;' ); foreach ( $this->getTables() as $t ) { try { $this->adapter->exec( "DROP TABLE IF EXISTS `$t`" ); } catch ( SQLException $e ) { } try { $this->adapter->exec( "DROP VIEW IF EXISTS `$t`" ); } catch ( SQLException $e ) { } } $this->adapter->exec( 'SET FOREIGN_KEY_CHECKS = 1;' ); } }