Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Fixed issues related to MSSQL #1431

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/Propel/Generator/Model/Index.php
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,11 @@ public function addColumn($data)
if (isset($data['size']) && $data['size'] > 0) {
$this->columnsSize[$name] = $data['size'];
}
if ($this->getTable()) {
$this->columnObjects[] = $this->getTable()->getColumn($name);
if ($this->getTable() && $column = $this->getTable()->getColumn($name)) {
$this->columnObjects[] = $column;
if (!isset($data['size']) && $column->getSize()) {
$this->columnsSize[$name] = $column->getSize();
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Propel/Generator/Model/Table.php
Original file line number Diff line number Diff line change
Expand Up @@ -786,7 +786,7 @@ public function setupReferrers($throwErrors = false)

// check for incomplete foreign key references when foreign table
// has a composite primary key
if ($foreignTable->hasCompositePrimaryKey()) {
if (!is_null($foreignTable) && $foreignTable->hasCompositePrimaryKey()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 as it fixes this specific case

// get composite foreign key's keys
$foreignPrimaryKeys = $foreignTable->getPrimaryKey();
// check all keys are referenced in foreign key
Expand Down
269 changes: 266 additions & 3 deletions src/Propel/Generator/Platform/MssqlPlatform.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,14 @@
namespace Propel\Generator\Platform;

use Propel\Generator\Model\Database;
use Propel\Generator\Model\Diff\ColumnDiff;
use Propel\Generator\Model\Domain;
use Propel\Generator\Model\ForeignKey;
use Propel\Generator\Model\Index;
use Propel\Generator\Model\PropelTypes;
use Propel\Generator\Model\Table;
use Propel\Generator\Model\Unique;
use Propel\Generator\Model\Column;

/**
* MS SQL PlatformInterface implementation.
Expand Down Expand Up @@ -103,6 +106,34 @@ public function getAddTablesDDL(Database $database)
return $ret;
}

/**
* Builds the DDL SQL for a Column object AFTER a mutation
* This is required since MSSQL doesnt support the same column definition
* when mutating a column vs creating a column
*
* @return string
*/
public function getChangeColumnDDL(Column $col)
{
$domain = $col->getDomain();

$ddl = [$this->quoteIdentifier($col->getName())];
$sqlType = $domain->getSqlType();
if ($this->hasSize($sqlType) && $col->isDefaultSqlType($this)) {
$ddl[] = $sqlType . $col->getSizeDefinition();
} else {
$ddl[] = $sqlType;
}
if ($notNull = $this->getNullString($col->isNotNull())) {
$ddl[] = $notNull;
}
if ($autoIncrement = $col->getAutoIncrementString()) {
$ddl[] = $autoIncrement;
}

return implode(' ', $ddl);
}

public function getDropTableDDL(Table $table)
{
$ret = '';
Expand Down Expand Up @@ -202,16 +233,195 @@ public function getForeignKeyDDL(ForeignKey $fk)
$this->quoteIdentifier($fk->getForeignTableName()),
$this->getColumnListDDL($fk->getForeignColumnObjects())
);
if ($fk->hasOnUpdate() && $fk->getOnUpdate() != ForeignKey::SETNULL) {
if ($fk->hasOnUpdate() && $fk->getOnUpdate()) {
$script .= ' ON UPDATE ' . $fk->getOnUpdate();
}
if ($fk->hasOnDelete() && $fk->getOnDelete() != ForeignKey::SETNULL) {
if ($fk->hasOnDelete() && $fk->getOnDelete()) {
$script .= ' ON DELETE '. $fk->getOnDelete();
}

return $script;
}

/**
* Builds the DDL SQL to drop a foreign key.
*
* @param ForeignKey $fk
* @return string
*/
public function getDropForeignKeyDDL(ForeignKey $fk)
{
if ($fk->isSkipSql() || $fk->isPolymorphic()) {
return;
}

$sql = "
IF EXISTS (SELECT 1 FROM sysobjects WHERE name='" . $fk->getName() . "')
ALTER TABLE " . $this->quoteIdentifier($fk->getTable()->getName()) . " DROP CONSTRAINT " . $this->quoteIdentifier($fk->getName()) . ";
";

return $sql;
}

/**
* Returns the DDL SQL to add an Index.
*
* @param Index $index
* @return string
*/
public function getAddIndexDDL(Index $index)
{
// Unique indexes must be treated as constraints
if ($index->isUnique()) {
return "\nALTER TABLE " . $this->quoteIdentifier($index->getTable()->getName()) . " ADD " . $this->getUniqueDDL($index).";\n";

} else {
$pattern = "
CREATE INDEX %s ON %s (%s);
";

return sprintf($pattern,
$this->quoteIdentifier($index->getName()),
$this->quoteIdentifier($index->getTable()->getName()),
$this->getColumnListDDL($index->getColumnObjects())
);
}
}

/**
* Builds the DDL SQL to drop an Index.
*
* @param Index $index
* @return string
*/
public function getDropIndexDDL(Index $index)
{
// Unique indexes must be treated as constraints
if ($index->isUnique()) {
$sql = "
IF EXISTS (SELECT 1 FROM sysobjects WHERE name='" . $index->getFQName() . "')
ALTER TABLE " . $this->quoteIdentifier($index->getTable()->getName()) . " DROP CONSTRAINT " . $this->quoteIdentifier($index->getFQName()) . ";
";

return $sql;
}

$pattern = "\nDROP INDEX %s ON %s;\n";

return sprintf($pattern,
$this->quoteIdentifier($index->getFQName()),
$this->quoteIdentifier($index->getTable()->getName())
);

}

/**
* Builds the DDL SQL to rename a column
*
* @return string
*/
public function getRenameColumnDDL(Column $fromColumn, Column $toColumn)
{
$fromColumnName = $this->quoteIdentifier($fromColumn->getTable()->getName().".".$fromColumn->getName());
$toColumnName = $this->quoteIdentifier($toColumn->getName());

$script = "
EXEC sp_rename $fromColumnName, $toColumnName, 'COLUMN';
";

return $script;
}

/**
* Builds the DDL SQL to add a list of columns
*
* @param Column[] $columns
* @return string
*/
public function getAddColumnsDDL($columns)
{
$lines = [];
$table = null;
foreach ($columns as $column) {
if (null === $table) {
$table = $column->getTable();
}
$lines[] = $this->getColumnDDL($column);
}

$sep = ",\n";

$pattern = "\nALTER TABLE %s ADD %s;";

return sprintf($pattern,
$this->quoteIdentifier($table->getName()),
implode($sep, $lines)
);
}

/**
* Builds the DDL SQL to modify a column
*
* @return string
*/
public function getModifyColumnDDL(ColumnDiff $columnDiff)
{
$script = '';

// default value changes requires default value constraint dropping
if ($this->alterColumnRequiresDropDefaultConstraint($columnDiff)) {
$dropDefaultContraintSql = $this->getDropDefaultConstraintDDL($columnDiff->getFromColumn());
$script .= $dropDefaultContraintSql;
}

$toColumn = $columnDiff->getToColumn();
$changed = $columnDiff->getChangedProperties();

// default value changed
if (isset($changed['defaultValueValue']) || isset($changed['defaultValueType'])) {
$defaultValueForColumnDDL = $this->getColumnDefaultValueDDL($toColumn);
if (strlen(trim($defaultValueForColumnDDL)) > 0) {
$pattern = "ALTER TABLE %s ADD $defaultValueForColumnDDL FOR %s;\n";
$script .= sprintf(
$pattern,
$this->quoteIdentifier($toColumn->getTable()->getName()),
$this->quoteIdentifier($toColumn->getName())
);
}
}

$pattern = "ALTER TABLE %s ALTER COLUMN %s;\n";
$script .= sprintf($pattern,
$this->quoteIdentifier($toColumn->getTable()->getName()),
$this->getChangeColumnDDL($toColumn)
);

return $script;
}

/**
* Builds the DDL SQL to modify a list of columns
*
* @param ColumnDiff[] $columnDiffs
* @return string
*/
public function getModifyColumnsDDL($columnDiffs)
{
$lines = [];
$table = null;
foreach ($columnDiffs as $columnDiff) {
$toColumn = $columnDiff->getToColumn();
if (null === $table) {
$table = $toColumn->getTable();
}
$lines[] = $this->getModifyColumnDDL($columnDiff);
}

$sep = "";

return "\n" . implode($sep, $lines);
}

/**
* @see Platform::supportsSchemas()
*/
Expand All @@ -231,11 +441,64 @@ public function hasSize($sqlType)
*/
public function doQuoting($text)
{
return '[' . strtr($text, ['.' => '].[']) . ']';
return "[$text]";
}

public function getTimestampFormatter()
{
return 'Y-m-d H:i:s';
}

/**
* @param Column $column
* @return string
*/
public function getDropDefaultConstraintDDL(Column $column)
{
$tableName = $column->getTableName();
$colName = $column->getName();

$script = "IF EXISTS (SELECT d.name FROM sys.tables t JOIN sys.default_constraints d on d.parent_object_id = t.object_id JOIN sys.columns c on c.object_id = t.object_id AND c.column_id = d.parent_column_id WHERE t.name = '".$tableName."' AND c.name = '".$colName."')
BEGIN
DECLARE @constraintName nvarchar(100), @cmd nvarchar(1000)
SET @constraintName = (SELECT d.name FROM sys.tables t JOIN sys.default_constraints d on d.parent_object_id = t.object_id JOIN sys.columns c on c.object_id = t.object_id AND c.column_id = d.parent_column_id WHERE t.name = '".$tableName."' AND c.name = '".$colName."')
SET @CMD = 'ALTER TABLE {$this->quoteIdentifier($tableName)} DROP CONSTRAINT ' + @constraintName
EXEC (@CMD)
END;\n";

return $script;
}

/**
* Checks whether a column alteration requires dropping its default constraint first.
*
* Different to other database vendors SQL Server implements column default values
* as constraints and therefore changes in a column's default value as well as changes
* in a column's type require dropping the default constraint first before being to
* alter the particular column to the new definition.
*
* @param ColumnDiff $columnDiff The column diff to evaluate.
*
* @return boolean True if the column alteration requires dropping its default constraint first, false otherwise.
*/
private function alterColumnRequiresDropDefaultConstraint(ColumnDiff $columnDiff)
{
// We can only decide whether to drop an existing default constraint
// if we know the original default value.
if (!$columnDiff->getFromColumn() instanceof Column) {
return false;
}
// We only need to drop an existing default constraint if we know the
// column was defined with a default value before.
if (!$columnDiff->getFromColumn()->hasDefaultValue()) {
return false;
}
// We need to drop an existing default constraint if the column was
// defined with a default value before and it has changed.
if (!$columnDiff->getFromColumn()->hasDefaultValue() && $columnDiff->getToColumn()->hasDefaultValue()) {
return false;
}

return true;
}
}
Loading