Skip to content

Commit c68e3ba

Browse files
src: generator: hypertable: edition support
Add support for the apache and community editions of timescaledb in the hypertable generator.
1 parent 5466335 commit c68e3ba

File tree

1 file changed

+73
-26
lines changed

1 file changed

+73
-26
lines changed

src/Eftdb/Generators/HypertableOperationGenerator.cs

Lines changed: 73 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using CmdScale.EntityFrameworkCore.TimescaleDB.Abstractions;
22
using CmdScale.EntityFrameworkCore.TimescaleDB.Operations;
3+
using System.Text;
34

45
namespace CmdScale.EntityFrameworkCore.TimescaleDB.Generators
56
{
@@ -16,56 +17,58 @@ public HypertableOperationGenerator(bool isDesignTime = false)
1617
}
1718

1819
sqlHelper = new SqlBuilderHelper(quoteString);
19-
2020
}
2121

2222
public List<string> Generate(CreateHypertableOperation operation)
2323
{
2424
string qualifiedTableName = sqlHelper.Regclass(operation.TableName, operation.Schema);
2525
string qualifiedIdentifier = sqlHelper.QualifiedIdentifier(operation.TableName, operation.Schema);
2626

27-
string migrateDataParam = operation.MigrateData ? ", migrate_data => true" : "";
27+
List<string> statements = [];
28+
List<string> communityStatements = [];
2829

29-
List<string> statements =
30-
[
31-
$"SELECT create_hypertable({qualifiedTableName}, '{operation.TimeColumnName}'{migrateDataParam});"
32-
];
30+
// Build create_hypertable with chunk_time_interval if provided
31+
var createHypertableCall = new StringBuilder();
32+
createHypertableCall.Append($"SELECT create_hypertable({qualifiedTableName}, '{operation.TimeColumnName}'");
33+
createHypertableCall.Append(operation.MigrateData ? ", migrate_data => true" : "");
3334

34-
// ChunkTimeInterval
3535
if (!string.IsNullOrEmpty(operation.ChunkTimeInterval))
3636
{
3737
// Check if the interval is a plain number (e.g., for microseconds).
3838
if (long.TryParse(operation.ChunkTimeInterval, out _))
3939
{
4040
// If it's a number, don't wrap it in quotes.
41-
statements.Add($"SELECT set_chunk_time_interval({qualifiedTableName}, {operation.ChunkTimeInterval}::bigint);");
41+
createHypertableCall.Append($", chunk_time_interval => {operation.ChunkTimeInterval}::bigint");
4242
}
4343
else
4444
{
4545
// If it's a string like '7 days', wrap it in quotes.
46-
statements.Add($"SELECT set_chunk_time_interval({qualifiedTableName}, INTERVAL '{operation.ChunkTimeInterval}');");
46+
createHypertableCall.Append($", chunk_time_interval => INTERVAL '{operation.ChunkTimeInterval}'");
4747
}
4848
}
4949

50-
// EnableCompression
50+
createHypertableCall.Append(");");
51+
statements.Add(createHypertableCall.ToString());
52+
53+
// EnableCompression (Community Edition only)
5154
if (operation.EnableCompression || operation.ChunkSkipColumns?.Count > 0)
5255
{
5356
bool enableCompression = operation.EnableCompression || operation.ChunkSkipColumns != null && operation.ChunkSkipColumns.Count > 0;
54-
statements.Add($"ALTER TABLE {qualifiedIdentifier} SET (timescaledb.compress = {enableCompression.ToString().ToLower()});");
57+
communityStatements.Add($"ALTER TABLE {qualifiedIdentifier} SET (timescaledb.compress = {enableCompression.ToString().ToLower()});");
5558
}
5659

57-
// ChunkSkipColumns
60+
// ChunkSkipColumns (Community Edition only)
5861
if (operation.ChunkSkipColumns != null && operation.ChunkSkipColumns.Count > 0)
5962
{
60-
statements.Add("SET timescaledb.enable_chunk_skipping = 'ON';");
63+
communityStatements.Add("SET timescaledb.enable_chunk_skipping = 'ON';");
6164

6265
foreach (string column in operation.ChunkSkipColumns)
6366
{
64-
statements.Add($"SELECT enable_chunk_skipping({qualifiedTableName}, '{column}');");
67+
communityStatements.Add($"SELECT enable_chunk_skipping({qualifiedTableName}, '{column}');");
6568
}
6669
}
6770

68-
// AdditionalDimensions
71+
// AdditionalDimensions (Available in both editions)
6972
if (operation.AdditionalDimensions != null && operation.AdditionalDimensions.Count > 0)
7073
{
7174
foreach (Dimension dimension in operation.AdditionalDimensions)
@@ -87,6 +90,11 @@ public List<string> Generate(CreateHypertableOperation operation)
8790
}
8891
}
8992

93+
// Add wrapped community statements if any exist
94+
if (communityStatements.Count > 0)
95+
{
96+
statements.Add(WrapCommunityFeatures(communityStatements));
97+
}
9098
return statements;
9199
}
92100

@@ -96,45 +104,52 @@ public List<string> Generate(AlterHypertableOperation operation)
96104
string qualifiedIdentifier = sqlHelper.QualifiedIdentifier(operation.TableName, operation.Schema);
97105

98106
List<string> statements = [];
107+
List<string> communityStatements = [];
99108

100-
// Check for ChunkTimeInterval change
109+
// Check for ChunkTimeInterval change (Available in both editions)
101110
if (operation.ChunkTimeInterval != operation.OldChunkTimeInterval)
102111
{
112+
var setChunkTimeInterval = new StringBuilder();
113+
setChunkTimeInterval.Append($"SELECT set_chunk_time_interval({qualifiedTableName}, ");
114+
103115
// Check if the interval is a plain number (e.g., for microseconds).
104116
if (long.TryParse(operation.ChunkTimeInterval, out _))
105117
{
106118
// If it's a number, don't wrap it in quotes.
107-
statements.Add($"SELECT set_chunk_time_interval({qualifiedTableName}, {operation.ChunkTimeInterval}::bigint);");
119+
setChunkTimeInterval.Append($"{operation.ChunkTimeInterval}::bigint");
108120
}
109121
else
110122
{
111123
// If it's a string like '7 days', wrap it in quotes.
112-
statements.Add($"SELECT set_chunk_time_interval({qualifiedTableName}, INTERVAL '{operation.ChunkTimeInterval}');");
124+
setChunkTimeInterval.Append($"INTERVAL '{operation.ChunkTimeInterval}'");
113125
}
126+
127+
setChunkTimeInterval.Append(");");
128+
statements.Add(setChunkTimeInterval.ToString());
114129
}
115130

116-
// Check for EnableCompression change
131+
// Check for EnableCompression change (Community Edition only)
117132
bool newCompressionState = operation.EnableCompression || operation.ChunkSkipColumns != null && operation.ChunkSkipColumns.Any();
118133
bool oldCompressionState = operation.OldEnableCompression || operation.OldChunkSkipColumns != null && operation.OldChunkSkipColumns.Any();
119134

120135
if (newCompressionState != oldCompressionState)
121136
{
122137
string compressionValue = newCompressionState.ToString().ToLower();
123-
statements.Add($"ALTER TABLE {qualifiedIdentifier} SET (timescaledb.compress = {compressionValue});");
138+
communityStatements.Add($"ALTER TABLE {qualifiedIdentifier} SET (timescaledb.compress = {compressionValue});");
124139
}
125140

126-
// Handle ChunkSkipColumns
141+
// Handle ChunkSkipColumns (Community Edition only)
127142
IReadOnlyList<string> newColumns = operation.ChunkSkipColumns ?? [];
128143
IReadOnlyList<string> oldColumns = operation.OldChunkSkipColumns ?? [];
129144
List<string> addedColumns = [.. newColumns.Except(oldColumns)];
130145

131146
if (addedColumns.Count != 0)
132147
{
133-
statements.Add("SET timescaledb.enable_chunk_skipping = 'ON';");
148+
communityStatements.Add("SET timescaledb.enable_chunk_skipping = 'ON';");
134149

135150
foreach (string column in addedColumns)
136151
{
137-
statements.Add($"SELECT enable_chunk_skipping({qualifiedTableName}, '{column}');");
152+
communityStatements.Add($"SELECT enable_chunk_skipping({qualifiedTableName}, '{column}');");
138153
}
139154
}
140155

@@ -143,7 +158,7 @@ public List<string> Generate(AlterHypertableOperation operation)
143158
{
144159
foreach (string column in removedColumns)
145160
{
146-
statements.Add($"SELECT disable_chunk_skipping({qualifiedTableName}, '{column}');");
161+
communityStatements.Add($"SELECT disable_chunk_skipping({qualifiedTableName}, '{column}');");
147162
}
148163
}
149164

@@ -194,8 +209,40 @@ public List<string> Generate(AlterHypertableOperation operation)
194209
statements.Add($"-- WARNING: TimescaleDB does not support removing dimensions. The following dimensions cannot be removed: {dimensionList}");
195210
}
196211

212+
213+
// Add wrapped community statements if any exist
214+
if (communityStatements.Count > 0)
215+
{
216+
statements.Add(WrapCommunityFeatures(communityStatements));
217+
}
197218
return statements;
198219
}
199-
}
200-
}
201220

221+
/// <summary>
222+
/// Wraps multiple SQL statements in a single license check block to ensure they only run on Community Edition
223+
/// </summary>
224+
private string WrapCommunityFeatures(List<string> sqlStatements)
225+
{
226+
var sb = new StringBuilder();
227+
sb.AppendLine("DO $$");
228+
sb.AppendLine("DECLARE");
229+
sb.AppendLine(" license TEXT;");
230+
sb.AppendLine("BEGIN");
231+
sb.AppendLine(" license := current_setting('timescaledb.license', true);");
232+
sb.AppendLine(" ");
233+
sb.AppendLine(" IF license IS NULL OR license != 'apache' THEN");
234+
235+
foreach (string sql in sqlStatements)
236+
{
237+
sb.AppendLine($" {sql}");
238+
}
239+
240+
sb.AppendLine(" ELSE");
241+
sb.AppendLine(" RAISE WARNING 'Skipping Community Edition features (compression, chunk skipping) - not available in Enterprise Edition';");
242+
sb.AppendLine(" END IF;");
243+
sb.AppendLine("END $$;");
244+
245+
return sb.ToString();
246+
}
247+
}
248+
}

0 commit comments

Comments
 (0)