Skip to content

Commit 1eaeb1b

Browse files
feat: implement support for schedule_interval, max_runtime, max_retries, and retry_period on reorder policies.
1 parent 85b1898 commit 1eaeb1b

15 files changed

Lines changed: 506 additions & 206 deletions

File tree

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
using CmdScale.EntityFrameworkCore.TimescaleDB.Abstractions;
2+
using CmdScale.EntityFrameworkCore.TimescaleDB.Operations;
3+
using Microsoft.EntityFrameworkCore.Infrastructure;
4+
5+
namespace CmdScale.EntityFrameworkCore.TimescaleDB.Design.Generators
6+
{
7+
internal class HypertableOperationGenerator
8+
{
9+
public static void Generate(CreateHypertableOperation operation, IndentedStringBuilder builder)
10+
{
11+
List<string> statements =
12+
[
13+
$"SELECT create_hypertable('\"\"{operation.TableName}\"\"', '{operation.TimeColumnName}');"
14+
];
15+
16+
// ChunkTimeInterval
17+
if (!string.IsNullOrEmpty(operation.ChunkTimeInterval))
18+
{
19+
// Check if the interval is a plain number (e.g., for microseconds).
20+
if (long.TryParse(operation.ChunkTimeInterval, out _))
21+
{
22+
// If it's a number, don't wrap it in quotes.
23+
statements.Add($"SELECT set_chunk_time_interval('\"\"{operation.TableName}\"\"', {operation.ChunkTimeInterval}::bigint);");
24+
}
25+
else
26+
{
27+
// If it's a string like '7 days', wrap it in quotes.
28+
statements.Add($"SELECT set_chunk_time_interval('\"\"{operation.TableName}\"\"', INTERVAL '{operation.ChunkTimeInterval}');");
29+
}
30+
}
31+
32+
// EnableCompression
33+
if (operation.EnableCompression)
34+
{
35+
statements.Add($"ALTER TABLE \"\"{operation.TableName}\"\" SET (timescaledb.compress = true);");
36+
}
37+
38+
// ChunkSkipColumns
39+
if (operation.ChunkSkipColumns != null && operation.ChunkSkipColumns.Count > 0)
40+
{
41+
statements.Add("SET timescaledb.enable_chunk_skipping = 'ON';");
42+
43+
foreach (string column in operation.ChunkSkipColumns)
44+
{
45+
statements.Add($"SELECT enable_chunk_skipping('\"\"{operation.TableName}\"\"', '{column}');");
46+
}
47+
}
48+
49+
// AdditionalDimensions
50+
if (operation.AdditionalDimensions != null && operation.AdditionalDimensions.Count > 0)
51+
{
52+
foreach (Dimension dimension in operation.AdditionalDimensions)
53+
{
54+
if (dimension.Type == EDimensionType.Range)
55+
{
56+
statements.Add($"SELECT add_dimension('\"\"{operation.TableName}\"\"', by_range('{dimension.ColumnName}', INTERVAL '{dimension.Interval}'));");
57+
}
58+
else if (dimension.Type == EDimensionType.Hash)
59+
{
60+
statements.Add($"SELECT add_dimension('\"\"{operation.TableName}\"\"', by_hash('{dimension.ColumnName}', {dimension.NumberOfPartitions}));");
61+
}
62+
}
63+
}
64+
65+
MigrationBuilderSqlHelper.BuildQueryString(statements, builder);
66+
}
67+
68+
public static void Generate(AlterHypertableOperation operation, IndentedStringBuilder builder)
69+
{
70+
List<string> statements = [];
71+
72+
// Check for ChunkTimeInterval change
73+
if (operation.ChunkTimeInterval != operation.OldChunkTimeInterval)
74+
{
75+
if (operation.ChunkTimeInterval != operation.OldChunkTimeInterval)
76+
{
77+
// Check if the interval is a plain number (e.g., for microseconds).
78+
if (long.TryParse(operation.ChunkTimeInterval, out _))
79+
{
80+
// If it's a number, don't wrap it in quotes.
81+
statements.Add($"SELECT set_chunk_time_interval('\"\"{operation.TableName}\"\"', {operation.ChunkTimeInterval}::bigint);");
82+
}
83+
else
84+
{
85+
// If it's a string like '7 days', wrap it in quotes.
86+
statements.Add($"SELECT set_chunk_time_interval('\"\"{operation.TableName}\"\"', INTERVAL '{operation.ChunkTimeInterval}');");
87+
}
88+
}
89+
}
90+
91+
// Check for EnableCompression change
92+
if (operation.EnableCompression != operation.OldEnableCompression)
93+
{
94+
string compressionValue = operation.EnableCompression.ToString().ToLower();
95+
statements.Add($"ALTER TABLE \"\"{operation.TableName}\"\" SET (timescaledb.compress = {compressionValue});");
96+
}
97+
98+
// Handle ChunkSkipColumns
99+
IReadOnlyList<string> newColumns = operation.ChunkSkipColumns ?? [];
100+
IReadOnlyList<string> oldColumns = operation.OldChunkSkipColumns ?? [];
101+
List<string> addedColumns = [.. newColumns.Except(oldColumns)];
102+
103+
if (addedColumns.Count != 0)
104+
{
105+
statements.Add("SET timescaledb.enable_chunk_skipping = 'ON';");
106+
107+
foreach (string column in addedColumns)
108+
{
109+
statements.Add($"SELECT enable_chunk_skipping('\"\"{operation.TableName}\"\"', '{column}');");
110+
}
111+
}
112+
113+
List<string> removedColumns = [.. oldColumns.Except(newColumns)];
114+
if (removedColumns.Count != 0)
115+
{
116+
foreach (string column in removedColumns)
117+
{
118+
statements.Add($"SELECT disable_chunk_skipping('\"\"{operation.TableName}\"\"', '{column}');");
119+
}
120+
}
121+
122+
MigrationBuilderSqlHelper.BuildQueryString(statements, builder);
123+
}
124+
}
125+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using Microsoft.EntityFrameworkCore.Infrastructure;
2+
3+
namespace CmdScale.EntityFrameworkCore.TimescaleDB.Design.Generators
4+
{
5+
public static class MigrationBuilderSqlHelper
6+
{
7+
public static void BuildQueryString(List<string> statements, IndentedStringBuilder builder)
8+
{
9+
if (statements.Count > 0)
10+
{
11+
builder.AppendLine(".Sql(@\"");
12+
using (builder.Indent())
13+
{
14+
foreach (string statement in statements)
15+
{
16+
builder.AppendLine(statement);
17+
}
18+
}
19+
builder.Append("\")");
20+
}
21+
}
22+
}
23+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
using CmdScale.EntityFrameworkCore.TimescaleDB.Operations;
2+
using Microsoft.EntityFrameworkCore.Infrastructure;
3+
using System.Globalization;
4+
5+
namespace CmdScale.EntityFrameworkCore.TimescaleDB.Design.Generators
6+
{
7+
internal class ReorderPolicyOperationGenerator
8+
{
9+
public static void Generate(AddReorderPolicyOperation operation, IndentedStringBuilder builder)
10+
{
11+
List<string> statements =
12+
[
13+
BuildAddReorderPolicySql(operation.TableName, operation.IndexName, operation.InitialStart)
14+
];
15+
16+
List<string> alterJobClauses = BuildAlterJobClauses(operation);
17+
if (alterJobClauses.Count != 0)
18+
{
19+
statements.Add(BuildAlterJobSql(operation.TableName, alterJobClauses));
20+
}
21+
22+
MigrationBuilderSqlHelper.BuildQueryString(statements, builder);
23+
}
24+
25+
public static void Generate(AlterReorderPolicyOperation operation, IndentedStringBuilder builder)
26+
{
27+
List<string> statements = [];
28+
bool needsRecreation = operation.IndexName != operation.OldIndexName || operation.InitialStart != operation.OldInitialStart;
29+
List<string> alterJobClauses = BuildAlterJobClauses(operation);
30+
31+
if (needsRecreation)
32+
{
33+
statements.Add($"SELECT remove_reorder_policy('\"\"{operation.TableName}\"\"', if_exists => true);");
34+
statements.Add(BuildAddReorderPolicySql(operation.TableName, operation.IndexName, operation.InitialStart));
35+
36+
if (alterJobClauses.Count != 0)
37+
{
38+
statements.Add(BuildAlterJobSql(operation.TableName, alterJobClauses));
39+
}
40+
}
41+
else if (alterJobClauses.Count != 0)
42+
{
43+
statements.Add(BuildAlterJobSql(operation.TableName, alterJobClauses));
44+
}
45+
46+
MigrationBuilderSqlHelper.BuildQueryString(statements, builder);
47+
}
48+
49+
public static void Generate(DropReorderPolicyOperation operation, IndentedStringBuilder builder)
50+
{
51+
List<string> statements =
52+
[
53+
$"SELECT remove_reorder_policy('\"\"{operation.TableName}\"\"', if_exists => true);"
54+
];
55+
MigrationBuilderSqlHelper.BuildQueryString(statements, builder);
56+
}
57+
58+
private static List<string> BuildAlterJobClauses(AddReorderPolicyOperation operation)
59+
{
60+
List<string> clauses = [];
61+
if (operation.ScheduleInterval != DefaultValues.ReorderPolicyScheduleInterval)
62+
clauses.Add($"schedule_interval => INTERVAL '{operation.ScheduleInterval}'");
63+
64+
if (operation.MaxRuntime != null) // Default is null (no limit)
65+
clauses.Add($"max_runtime => INTERVAL '{operation.MaxRuntime}'");
66+
67+
if (operation.MaxRetries != DefaultValues.ReorderPolicyMaxRetries)
68+
clauses.Add($"max_retries => {operation.MaxRetries}");
69+
70+
if (operation.RetryPeriod != DefaultValues.ReorderPolicyRetryPeriod)
71+
clauses.Add($"retry_period => INTERVAL '{operation.RetryPeriod}'");
72+
73+
return clauses;
74+
}
75+
76+
private static List<string> BuildAlterJobClauses(AlterReorderPolicyOperation operation)
77+
{
78+
List<string> clauses = [];
79+
80+
if (operation.ScheduleInterval != operation.OldScheduleInterval)
81+
clauses.Add($"schedule_interval => INTERVAL '{operation.ScheduleInterval}'");
82+
83+
if (operation.MaxRuntime != operation.OldMaxRuntime)
84+
{
85+
string maxRuntimeValue = string.IsNullOrWhiteSpace(operation.MaxRuntime) ? "NULL" : $"INTERVAL '{operation.MaxRuntime}'";
86+
clauses.Add($"max_runtime => {maxRuntimeValue}");
87+
}
88+
89+
if (operation.MaxRetries != operation.OldMaxRetries)
90+
clauses.Add($"max_retries => {operation.MaxRetries}");
91+
92+
if (operation.RetryPeriod != operation.OldRetryPeriod)
93+
clauses.Add($"retry_period => INTERVAL '{operation.RetryPeriod}'");
94+
95+
return clauses;
96+
}
97+
98+
private static string BuildAlterJobSql(string tableName, IEnumerable<string> clauses)
99+
{
100+
return $@"
101+
SELECT alter_job(job_id, {string.Join(", ", clauses)})
102+
FROM timescaledb_information.jobs
103+
WHERE proc_name = 'policy_reorder' AND hypertable_name = '{tableName}';".Trim();
104+
}
105+
106+
private static string BuildAddReorderPolicySql(string tableName, string indexName, DateTime? initialStart)
107+
{
108+
string baseSql = $"SELECT add_reorder_policy('\"\"{tableName}\"\"', '{indexName}'";
109+
110+
List<string> optionalArgs = [];
111+
112+
// Add optional arguments if they are provided
113+
if (initialStart.HasValue)
114+
{
115+
// Use ISO 8601 format for timestamps to avoid ambiguity
116+
string timestamp = initialStart.Value.ToUniversalTime().ToString("o", CultureInfo.InvariantCulture);
117+
optionalArgs.Add($"initial_start => '{timestamp}'");
118+
}
119+
120+
if (optionalArgs.Count > 0)
121+
{
122+
baseSql += $", {string.Join(", ", optionalArgs)}";
123+
}
124+
125+
baseSql += ");";
126+
return baseSql;
127+
}
128+
}
129+
}

0 commit comments

Comments
 (0)