* @package Mammut\DB\Dialect */ class MysqlDdl extends \Mammut\DB\Sql\Dialect\Mysql implements iDialect, iDdlDialect { use DefaultDropTable; use DefaultDropView; use DefaultRenameTable; use DefaultDropColumn; public function getDdl() { return $this; } public static function createCollateSQL($charset, $collate) { $sql = ''; switch($charset) { case Charset::CS_ASCII: $sql .= ' CHARACTER SET ascii'; switch($collate) { case Charset::COL_BIN: $sql = ' COLLATE ascii_bin'; break; case Charset::COL_GEN_CI: $sql = ' COLLATE ascii_general_ci'; break; case Charset::COL_GEN_CS: throw new \InvalidArgumentException('unsupported collation'); break; case Charset::COL_UNI: throw new \InvalidArgumentException('unsupported collation'); break; } break; case Charset::CS_BINARY: $sql .= ' CHARACTER SET binary'; if(!empty($collate)) throw new \InvalidArgumentException('binary dosen\'t supported collation'); break; case Charset::CS_LATIN1: $sql .= ' CHARACTER SET latin1'; switch($collate) { case Charset::COL_BIN: $sql .= ' COLLATE latin1_bin'; break; case Charset::COL_GEN_CI: $sql .= ' COLLATE latin1_general_ci'; break; case Charset::COL_GEN_CS: $sql .= ' COLLATE latin1_general_cs'; break; case Charset::COL_UNI: throw new \InvalidArgumentException('unsupported collation'); break; } break; case Charset::CS_UTF8: $sql .= ' CHARACTER SET utf8'; switch($collate) { case Charset::COL_BIN: $sql .= ' COLLATE utf8_bin'; break; case Charset::COL_GEN_CI: $sql .= ' COLLATE utf8_general_ci'; break; case Charset::COL_GEN_CS: throw new \InvalidArgumentException('unsupported collation'); break; case Charset::COL_UNI: $sql .= ' COLLATE utf8_unicode_ci'; break; } break; default: break; } return $sql; } protected function getCreateColumnSQLFromInfo(ColumnInfo $column, $primary) { $sql = $this->quoteIdent($column->getName()); $type = $column->getType(); switch($type) { case Types::TYPE_BLOB: case Types::TYPE_DATE: case Types::TYPE_DATETIME: case Types::TYPE_BOOLEAN: $sql .= ' '.$this->getNativeType($type); break; case Types::TYPE_TINY: case Types::TYPE_SHORT: case Types::TYPE_INT: case Types::TYPE_LONG: $sql .= ' '.$this->getNativeType($type); $size = $column->getSize(); if(!is_int($size)) throw new \InvalidArgumentException("size has to be a integer for type {$type}"); $size = (int) $size; switch($type) { case Types::TYPE_TINY: if($size > 4) throw new \OverflowException("size bigger than maximum size"); break; case Types::TYPE_SHORT: if($size > 6) throw new \OverflowException("size bigger than maximum size"); break; case Types::TYPE_INT: if($size > 11) throw new \OverflowException("size bigger than maximum size"); break; } if($size > 0) $sql .= '(' . $size . ')'; $sql .= $column->isUnsigned() ? ' UNSIGNED' : ''; if($column->isAutoNum()) { if(!in_array($column->getName(), $primary)) throw new \InvalidArgumentException("serial fields needs to be in the primary key"); $sql .= ' AUTO_INCREMENT'; } break; case Types::TYPE_FLOAT: case Types::TYPE_DOUBLE: $sql .= ' '.$this->getNativeType($type); $size = $column->getSize(); if(!is_int($size)) throw new \InvalidArgumentException("size has to be a integer for type {$type}"); $sql .= $column->isUnsigned() ? ' UNSIGNED' : ''; break; case Types::TYPE_DECIMAL: $sql .= ' '.$this->getNativeType($type); $size = $column->getSize(); if(!is_array($size)) throw new \InvalidArgumentException("size has to be an array for type {$type}"); if(count($size) != 2) throw new \RangeException("size array has to containt 2 integers"); reset($size); $m = (int) current($size); next($size); $d = (int) current($size); if($m < 0 || $d < 0) throw new \UnexpectedValueException("integers of the size array needs to be positive"); $sql .= '(' . $m . ',' . $d . ')'; $sql .= $column->isUnsigned() ? ' UNSIGNED' : ''; break; case Types::TYPE_CHAR: case Types::TYPE_VCHAR: case Types::TYPE_BINARY: $sql .= ' '.$this->getNativeType($type); $size = $column->getSize(); if(!is_int($size)) throw new \InvalidArgumentException("size has to be a integer for type {$type}"); $size = (int) $size; if($size > 0) $sql .= '(' . $size . ')'; else $sql .= '(255)'; if ($type != Types::TYPE_BINARY) $sql .= ' ' . self::createCollateSQL($column->getCharset(), $column->getCollate()); break; case Types::TYPE_TEXT: $sql .= ' '.$this->getNativeType($type); $sql .= ' ' . self::createCollateSQL($column->getCharset(), $column->getCollate()); break; case Types::TYPE_UTS: $sql .= ' '.$this->getNativeType($type); $sql .= $column->isUnsigned() ? ' UNSIGNED' : ''; break; case Types::TYPE_ENUM: $sql .= ' '.$this->getNativeType($type); $size = $column->getSize(); $values = array(); foreach($column->getParam() as $e) { if(preg_match('#values\(([a-zA-Z0-9_,]*)\)#', $e, $match)) { $values = explode(',', $match[1]); break; } } $sql .= '(\'' . implode('\',\'', $values) . '\')'; break; default: throw new \InvalidArgumentException('unknown datatype: ' . $type); } $sql .= $column->isNullAllowed() ? ' NULL' : ' NOT NULL'; if(!is_null($column->getDefault())) $sql .= ' DEFAULT ' . $this->quoteValue($column->getDefault()); switch($type) { case Types::TYPE_BOOLEAN: $sql .= ' COMMENT \'boolean\''; break; case Types::TYPE_UTS: $sql .= ' COMMENT \'uts\''; break; } return $sql; } public function getCreateTableSQL($param) { if ($param instanceof TableInfo) return $this->getCreateTableSQLFromInfo($param); throw new ImplementationException('not supported'); } protected function getCreateTableSQLFromInfo(TableInfo $info) { $sql = 'CREATE TABLE ' . $this->quoteIdent($info->getName()) . " (\n"; $next = false; $primary = $info->getPrimary(); $indexes = $info->getIndexes(); $refs = $info->getRefs(); for($i = 0; $i < $info->getColumnCount(); $i++) { if($next) $sql .= ",\n"; $column = $info->getColumnInfo((int) $i); $sql .= $this->getCreateColumnSQLFromInfo($column, $primary); /* if (isset($refs[$column->getName()])) { $targetObj = $this->quoteIdent($refs[$column->getName()]['object']); $targetCol = $this->quoteIdent($refs[$column->getName()]['column']); $sql .= ' REFERENCES '.$targetObj.' ('.$targetCol.')'; unset($refs[$column->getName()]); }*/ $next = true; } if(count($primary) != 0) { $sql .= ', PRIMARY KEY(' . implode(',', array_map(array($this,'quoteIdent'), $primary)) . ')'; } if(count($indexes) != 0) { foreach($indexes as $name=>$values) { foreach($values as $id=>$val) { if(preg_match('#([a-zA-Z0-9_ ]*)\\(([0-9]+)\\)#', $val, $match)) $values[$id] = $this->quoteIdent($match[1]) . '(' . $match[2] . ')'; else $values[$id] = $this->quoteIdent($val); } $sql .= ', INDEX ' . $this->quoteIdent($name) . ' (' . implode(',', $values) . ')'; } } if(count($refs) != 0) { /** * @var $ref RefInfo */ foreach($refs as $name=>$ref) { if(substr_count($name, ",") > 0) { $fields = explode(",", $name); foreach($fields as &$f) $f = $this->quoteIdent($f); $field = implode(',', $fields); } else $field = $this->quoteIdent($name); $targetObj = $this->quoteIdent($ref->getRefTable()); if(substr_count($ref->getRefColumns(), ",") > 0) { $fields = explode(",", $ref->getRefColumns()); foreach($fields as &$f) $f = $this->quoteIdent($f); $targetCol = implode(',', $fields); } else $targetCol = $this->quoteIdent($ref->getRefColumns()); $del = "ON DELETE CASCADE "; $up = "ON UPDATE CASCADE "; if($ref->getDelete() == 'null') $del = "ON DELETE SET NULL "; if($ref->getUpdate() == 'null') $up = "ON UPDATE SET NULL "; $sql .= ",FOREIGN KEY ({$field}) REFERENCES {$targetObj} ({$targetCol}) {$del} {$up}"; } } $sql .= "\n)"; if(!empty($cluster)) // TODO: reenable this $sql .= ' ENGINE=NDB DEFAULT CHARSET=utf8'; else $sql .= ' ENGINE=InnoDB DEFAULT CHARSET=utf8'; $sql .= "\n"; // echo $sql; return $sql; } public function getAlterTableSQL($param) { throw new ImplementationException('not supported'); } public function getCreateViewSQL($param) { if ($param instanceof ViewInfo) return $this->getCreateViewSQLFromInfo($param); throw new ImplementationException('not supported'); } protected function getCreateViewSQLFromInfo(ViewInfo $info) { $sql = 'CREATE OR REPLACE VIEW ' . $this->quoteIdent($info->getName()) . " AS \n"; $next = false; /* $primary = $info->getPrimary(); $indexes = $info->getIndexes(); $refs = $info->getRefs();*/ $sql .= "SELECT \n"; $tables = array($info->getBase()); for($i = 0; $i < $info->getColumnCount(); $i++) { if($next) $sql .= ",\n"; $column = $info->getColumnInfo($i); $base = $this->quoteIdent($column['base']); $col = $this->quoteIdent($column['basecolumn']); // $tables[] = $column['base']; $sql .= $base . "." . $col; $next = true; } $tables = array_unique($tables); $sql .= " FROM \n"; $next = false; foreach($tables as $t) { if($next) $sql .= ",\n"; $sql .= $this->quoteIdent($t); $next = true; } $joins = $info->getJoinObjects(); foreach($joins as $j) { $jInfo = $info->getJoinInfo($j); switch($jInfo['type']) { case 'inner': $obj = $this->quoteIdent($j); $sql .= " JOIN {$obj}\n"; $cons = array(); foreach($jInfo['cond'] as $con) { $leftObjs = explode('.', $con['left']); if(count($leftObjs) == 2) { $left = $this->quoteIdent($leftObjs[0]) . '.' . $this->quoteIdent($leftObjs[1]); } $rightObjs = explode('.', $con['right']); if(count($rightObjs) == 2) { $right = $this->quoteIdent($rightObjs[0]) . '.' . $this->quoteIdent($rightObjs[1]); } switch($con['operator']) { case '=': $operator = '='; break; default: throw new \Mammut\DB\DBException("unsupported operator: {$con['operator']}"); } $cons[] = $left . $operator . $right; unset($left, $operator, $right, $leftObj, $rightObjs); } if(count($cons) > 0) { $sql .= 'ON ' . implode(' AND ', $cons); } break; default: throw new \Mammut\DB\DBException("unsupported join type: {$jInfo['type']}"); } // var_dump($jInfo); //$sql .= 'AND '.$basea.'.'.$cola.'='.$baseb.'.'.$colb; } $sql .= ";\n"; //echo $sql; return $sql; } }