Skip to content

Commit

Permalink
Fix to #32154 - Convert_regular_column_of_temporal_table_to_sparse fa…
Browse files Browse the repository at this point in the history
…iling (#32370)

On some versions of Sql Server, temporal table's history table is by default setup with compression. If that's the case, we need to disable versioning and de-compress the history table before adding a sparse column or converting non-sparse column to sparse.

Fixes #32154
  • Loading branch information
maumar authored Nov 21, 2023
1 parent b385818 commit 25ba437
Show file tree
Hide file tree
Showing 2 changed files with 384 additions and 5 deletions.
77 changes: 73 additions & 4 deletions src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2676,8 +2676,6 @@ alterTableOperation.OldTable[SqlServerAnnotationNames.TemporalHistoryTableSchema

case AddColumnOperation addColumnOperation:
{
operations.Add(addColumnOperation);

// when adding a period column, we need to add it as a normal column first, and only later enable period
// removing the period information now, so that when we generate SQL that adds the column we won't be making them
// auto generated as period it won't work, unless period is enabled but we can't enable period without adding the
Expand All @@ -2694,6 +2692,29 @@ alterTableOperation.OldTable[SqlServerAnnotationNames.TemporalHistoryTableSchema
addColumnOperation.DefaultValue = DateTime.MaxValue;
}

// when adding sparse column to temporal table, we need to disable versioning.
// This is because it may be the case that HistoryTable is using compression (by default)
// and the add column operation fails in that situation
// in order to make it work we need to disable versioning (if we haven't done it already)
// and de-compress the HistoryTable
if (addColumnOperation[SqlServerAnnotationNames.Sparse] as bool? == true)
{
if (!temporalInformation.DisabledVersioning
&& !temporalInformation.ShouldEnableVersioning)
{
DisableVersioning(
tableName,
schema,
temporalInformation,
suppressTransaction,
shouldEnableVersioning: true);
}

DecompressTable(temporalInformation.HistoryTableName!, temporalInformation.HistoryTableSchema, suppressTransaction);
}

operations.Add(addColumnOperation);

// when adding (non-period) column to an existing temporal table we need to check if we have disabled versioning
// due to some other operations in the same migration (e.g. delete column)
// if so, we need to also add the same column to history table
Expand All @@ -2707,6 +2728,10 @@ alterTableOperation.OldTable[SqlServerAnnotationNames.TemporalHistoryTableSchema
operations.Add(addHistoryTableColumnOperation);
}
}
else
{
operations.Add(addColumnOperation);
}

break;
}
Expand Down Expand Up @@ -2798,8 +2823,15 @@ alterTableOperation.OldTable[SqlServerAnnotationNames.TemporalHistoryTableSchema
// for alter column operation converting column from nullable to non-nullable in the temporal table
// we must disable versioning in order to properly handle it
// specifically, switching values in history table from null to the default value
if (alterColumnOperation.OldColumn.IsNullable
&& !alterColumnOperation.IsNullable
var changeToNonNullable = alterColumnOperation.OldColumn.IsNullable
&& !alterColumnOperation.IsNullable;

// for alter column converting to sparse we also need to disable versioning
// in case HistoryTable is compressed (so that we can de-compress it)
var changeToSparse = alterColumnOperation.OldColumn[SqlServerAnnotationNames.Sparse] as bool? != true
&& alterColumnOperation[SqlServerAnnotationNames.Sparse] as bool? == true;

if ((changeToNonNullable || changeToSparse)
&& !temporalInformation.DisabledVersioning
&& !temporalInformation.ShouldEnableVersioning)
{
Expand All @@ -2811,6 +2843,11 @@ alterTableOperation.OldTable[SqlServerAnnotationNames.TemporalHistoryTableSchema
shouldEnableVersioning: true);
}

if (changeToSparse)
{
DecompressTable(temporalInformation.HistoryTableName!, temporalInformation.HistoryTableSchema, suppressTransaction);
}

operations.Add(alterColumnOperation);

// when modifying a period column, we need to perform the operations as a normal column first, and only later enable period
Expand Down Expand Up @@ -3043,6 +3080,38 @@ void EnablePeriod(string table, string? schema, string periodStartColumnName, st
});
}

void DecompressTable(string tableName, string? schema, bool suppressTransaction)
{
var stringTypeMapping = Dependencies.TypeMappingSource.GetMapping(typeof(string));

var decompressTableCommand = new StringBuilder()
.Append("IF EXISTS (")
.Append("SELECT 1 FROM [sys].[tables] [t] ")
.Append("INNER JOIN [sys].[partitions] [p] ON [t].[object_id] = [p].[object_id] ")
.Append($"WHERE [t].[name] = '{tableName}' ");

if (schema != null)
{
decompressTableCommand.Append($"AND [t].[schema_id] = schema_id('{schema}') ");
}

decompressTableCommand.AppendLine("AND data_compression <> 0)")
.Append("EXEC(")
.Append(stringTypeMapping.GenerateSqlLiteral("ALTER TABLE " +
Dependencies.SqlGenerationHelper.DelimitIdentifier(tableName, schema) +
" REBUILD PARTITION = ALL WITH (DATA_COMPRESSION = NONE)" +
Dependencies.SqlGenerationHelper.StatementTerminator))
.Append(")")
.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator);

operations.Add(
new SqlOperation
{
Sql = decompressTableCommand.ToString(),
SuppressTransaction = suppressTransaction
});
}

static TOperation CopyColumnOperation<TOperation>(ColumnOperation source)
where TOperation : ColumnOperation, new()
{
Expand Down
Loading

0 comments on commit 25ba437

Please sign in to comment.