Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class HypertableOperationGeneratorTests
private static string GetGeneratedCode(dynamic operation)
{
IndentedStringBuilder builder = new();

HypertableOperationGenerator generator = new(true);
List<string> statements = generator.Generate(operation);
SqlBuilderHelper.BuildQueryString(statements, builder);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ public void Generate_Add_with_non_default_schedule_creates_add_and_alter_sql()
IndexName = "IX_TestTable_Time",
InitialStart = testDate,
ScheduleInterval = "2 days",
MaxRuntime = "1 hour",
MaxRetries = 5,
RetryPeriod = "10 minutes"
MaxRuntime = "1 hour",
MaxRetries = 5,
RetryPeriod = "10 minutes"
};

string expected = @".Sql(@""
Expand All @@ -78,10 +78,10 @@ FROM timescaledb_information.jobs
public void Generate_Drop_creates_correct_remove_policy_sql()
{
// Arrange
DropReorderPolicyOperation operation = new()
{
DropReorderPolicyOperation operation = new()
{
Schema = "public",
TableName = "TestTable"
TableName = "TestTable"
};

string expected = @".Sql(@""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,8 @@ EAggregateFunction function

public static ContinuousAggregateBuilder<TEntity, TSourceEntity> AddGroupByColumn<TEntity, TSourceEntity, TProperty>(
this ContinuousAggregateBuilder<TEntity, TSourceEntity> builder,
Expression<Func<TSourceEntity, TProperty>> propertyExpression)
where TEntity : class
Expression<Func<TSourceEntity, TProperty>> propertyExpression)
where TEntity : class
where TSourceEntity : class
{
string propertyName = GetPropertyName(propertyExpression);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public static class HypertableAnnotations
public const string IsHypertable = "TimescaleDB:IsHypertable";
public const string HypertableTimeColumn = "TimescaleDB:TimeColumnName";
public const string EnableCompression = "TimescaleDB:EnableCompression";
public const string ChunkTimeInterval ="TimescaleDB:ChunkTimeInterval";
public const string ChunkTimeInterval = "TimescaleDB:ChunkTimeInterval";
public const string ChunkSkipColumns = "TimescaleDB:ChunkSkipColumns";
public const string AdditionalDimensions = "TimescaleDB:AdditionalDimensions";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,47 @@ public List<string> Generate(AlterHypertableOperation operation)
}
}

// Handle AdditionalDimensions - only add new dimensions
// NOTE: TimescaleDB does NOT support removing dimensions from hypertables.
// Once a dimension is added, it cannot be removed. Therefore, we only generate
// SQL for adding new dimensions and ignore dimension removals.
IReadOnlyList<Dimension> newDimensions = operation.AdditionalDimensions ?? [];
IReadOnlyList<Dimension> oldDimensions = operation.OldAdditionalDimensions ?? [];

// Find dimensions that are in new but not in old (added dimensions)
foreach (Dimension newDim in newDimensions)
{
bool exists = oldDimensions.Any(oldDim =>
oldDim.ColumnName == newDim.ColumnName &&
oldDim.Type == newDim.Type &&
oldDim.Interval == newDim.Interval &&
oldDim.NumberOfPartitions == newDim.NumberOfPartitions);

if (!exists)
{
if (newDim.Type == EDimensionType.Range)
{
statements.Add($"SELECT add_dimension({qualifiedTableName}, by_range('{newDim.ColumnName}', INTERVAL '{newDim.Interval}'));");
}
else if (newDim.Type == EDimensionType.Hash)
{
statements.Add($"SELECT add_dimension({qualifiedTableName}, by_hash('{newDim.ColumnName}', {newDim.NumberOfPartitions}));");
}
}
}

// Warn if dimensions were removed (which cannot be reversed in TimescaleDB)
List<Dimension> removedDimensions = [.. oldDimensions
.Where(oldDim => !newDimensions.Any(newDim =>
oldDim.ColumnName == newDim.ColumnName &&
oldDim.Type == newDim.Type))];

if (removedDimensions.Count > 0)
{
string dimensionList = string.Join(", ", removedDimensions.Select(d => $"'{d.ColumnName}'"));
statements.Add($"-- WARNING: TimescaleDB does not support removing dimensions. The following dimensions cannot be removed: {dimensionList}");
}

return statements;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using CmdScale.EntityFrameworkCore.TimescaleDB.Operations;
using CmdScale.EntityFrameworkCore.TimescaleDB.Abstractions;
using CmdScale.EntityFrameworkCore.TimescaleDB.Operations;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations.Operations;

Expand Down Expand Up @@ -28,7 +29,8 @@ public IReadOnlyList<MigrationOperation> GetDifferences(IRelationalModel? source
.Where(x =>
x.Target.ChunkTimeInterval != x.Source.ChunkTimeInterval ||
x.Target.EnableCompression != x.Source.EnableCompression ||
!AreChunkSkipColumnsEqual(x.Target.ChunkSkipColumns, x.Source.ChunkSkipColumns)
!AreChunkSkipColumnsEqual(x.Target.ChunkSkipColumns, x.Source.ChunkSkipColumns) ||
!AreDimensionsEqual(x.Target.AdditionalDimensions, x.Source.AdditionalDimensions)
);

foreach (var hypertable in updatedHypertables)
Expand All @@ -40,9 +42,11 @@ public IReadOnlyList<MigrationOperation> GetDifferences(IRelationalModel? source
ChunkTimeInterval = hypertable.Target.ChunkTimeInterval,
EnableCompression = hypertable.Target.EnableCompression,
ChunkSkipColumns = hypertable.Target.ChunkSkipColumns,
AdditionalDimensions = hypertable.Target.AdditionalDimensions,
OldChunkTimeInterval = hypertable.Source.ChunkTimeInterval,
OldEnableCompression = hypertable.Source.EnableCompression,
OldChunkSkipColumns = hypertable.Source.ChunkSkipColumns
OldChunkSkipColumns = hypertable.Source.ChunkSkipColumns,
OldAdditionalDimensions = hypertable.Source.AdditionalDimensions
});
}

Expand All @@ -59,5 +63,29 @@ private static bool AreChunkSkipColumnsEqual(IReadOnlyList<string>? list1, IRead

return new HashSet<string>(list1).SetEquals(list2);
}

private static bool AreDimensionsEqual(IReadOnlyList<Dimension>? list1, IReadOnlyList<Dimension>? list2)
{
if (list1 == null && list2 == null) return true;
if (list1 == null || list2 == null) return false;
if (list1.Count != list2.Count) return false;

// Compare each dimension's properties
for (int i = 0; i < list1.Count; i++)
{
Dimension dim1 = list1[i];
Dimension dim2 = list2[i];

if (dim1.ColumnName != dim2.ColumnName ||
dim1.Type != dim2.Type ||
dim1.Interval != dim2.Interval ||
dim1.NumberOfPartitions != dim2.NumberOfPartitions)
{
return false;
}
}

return true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ public class AlterContinuousAggregateOperation : MigrationOperation

public string? ChunkInterval { get; set; }
public string? OldChunkInterval { get; set; }

public bool CreateGroupIndexes { get; set; }
public bool OldCreateGroupIndexes { get; set; }

public bool MaterializedOnly { get; set; }
public bool OldMaterializedOnly { get; set; }
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore.Migrations.Operations;
using CmdScale.EntityFrameworkCore.TimescaleDB.Abstractions;
using Microsoft.EntityFrameworkCore.Migrations.Operations;

namespace CmdScale.EntityFrameworkCore.TimescaleDB.Operations
{
Expand All @@ -13,9 +14,11 @@ public class AlterHypertableOperation : MigrationOperation
// Only timestamp-like and Integer-like columns are supported for chunk skipping
// Cannot be reverted once enabled
public IReadOnlyList<string>? ChunkSkipColumns { get; set; }
public IReadOnlyList<Dimension>? AdditionalDimensions { get; set; }

public string OldChunkTimeInterval { get; set; } = string.Empty;
public bool OldEnableCompression { get; set; }
public IReadOnlyList<string>? OldChunkSkipColumns { get; set; }
public IReadOnlyList<Dimension>? OldAdditionalDimensions { get; set; }
}
}
Loading