11using CmdScale . EntityFrameworkCore . TimescaleDB . Abstractions ;
22using CmdScale . EntityFrameworkCore . TimescaleDB . Operations ;
3+ using System . Text ;
34
45namespace CmdScale . EntityFrameworkCore . TimescaleDB . Generators
56{
@@ -16,54 +17,57 @@ 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- List < string > statements =
28- [
29- $ "SELECT create_hypertable({ qualifiedTableName } , '{ operation . TimeColumnName } ');"
30- ] ;
27+ List < string > statements = [ ] ;
28+ List < string > communityStatements = [ ] ;
3129
32- // ChunkTimeInterval
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+
3334 if ( ! string . IsNullOrEmpty ( operation . ChunkTimeInterval ) )
3435 {
3536 // Check if the interval is a plain number (e.g., for microseconds).
3637 if ( long . TryParse ( operation . ChunkTimeInterval , out _ ) )
3738 {
3839 // If it's a number, don't wrap it in quotes.
39- statements . Add ( $ "SELECT set_chunk_time_interval( { qualifiedTableName } , { operation . ChunkTimeInterval } ::bigint); ") ;
40+ createHypertableCall . Append ( $ ", chunk_time_interval => { operation . ChunkTimeInterval } ::bigint") ;
4041 }
4142 else
4243 {
4344 // If it's a string like '7 days', wrap it in quotes.
44- statements . Add ( $ "SELECT set_chunk_time_interval( { qualifiedTableName } , INTERVAL '{ operation . ChunkTimeInterval } '); ") ;
45+ createHypertableCall . Append ( $ ", chunk_time_interval => INTERVAL '{ operation . ChunkTimeInterval } '") ;
4546 }
4647 }
48+
49+ createHypertableCall . Append ( ");" ) ;
50+ statements . Add ( createHypertableCall . ToString ( ) ) ;
4751
48- // EnableCompression
52+ // EnableCompression (Community Edition only)
4953 if ( operation . EnableCompression || operation . ChunkSkipColumns ? . Count > 0 )
5054 {
5155 bool enableCompression = operation . EnableCompression || operation . ChunkSkipColumns != null && operation . ChunkSkipColumns . Count > 0 ;
52- statements . Add ( $ "ALTER TABLE { qualifiedIdentifier } SET (timescaledb.compress = { enableCompression . ToString ( ) . ToLower ( ) } );") ;
56+ communityStatements . Add ( $ "ALTER TABLE { qualifiedIdentifier } SET (timescaledb.compress = { enableCompression . ToString ( ) . ToLower ( ) } );") ;
5357 }
5458
55- // ChunkSkipColumns
59+ // ChunkSkipColumns (Community Edition only)
5660 if ( operation . ChunkSkipColumns != null && operation . ChunkSkipColumns . Count > 0 )
5761 {
58- statements . Add ( "SET timescaledb.enable_chunk_skipping = 'ON';" ) ;
62+ communityStatements . Add ( "SET timescaledb.enable_chunk_skipping = 'ON';" ) ;
5963
6064 foreach ( string column in operation . ChunkSkipColumns )
6165 {
62- statements . Add ( $ "SELECT enable_chunk_skipping({ qualifiedTableName } , '{ column } ');") ;
66+ communityStatements . Add ( $ "SELECT enable_chunk_skipping({ qualifiedTableName } , '{ column } ');") ;
6367 }
6468 }
6569
66- // AdditionalDimensions
70+ // AdditionalDimensions (Available in both editions)
6771 if ( operation . AdditionalDimensions != null && operation . AdditionalDimensions . Count > 0 )
6872 {
6973 foreach ( Dimension dimension in operation . AdditionalDimensions )
@@ -79,6 +83,11 @@ public List<string> Generate(CreateHypertableOperation operation)
7983 }
8084 }
8185
86+ // Add wrapped community statements if any exist
87+ if ( communityStatements . Count > 0 )
88+ {
89+ statements . Add ( WrapCommunityFeatures ( communityStatements ) ) ;
90+ }
8291 return statements ;
8392 }
8493
@@ -88,45 +97,52 @@ public List<string> Generate(AlterHypertableOperation operation)
8897 string qualifiedIdentifier = sqlHelper . QualifiedIdentifier ( operation . TableName , operation . Schema ) ;
8998
9099 List < string > statements = [ ] ;
100+ List < string > communityStatements = [ ] ;
91101
92- // Check for ChunkTimeInterval change
102+ // Check for ChunkTimeInterval change (Available in both editions)
93103 if ( operation . ChunkTimeInterval != operation . OldChunkTimeInterval )
94104 {
105+ var setChunkTimeInterval = new StringBuilder ( ) ;
106+ setChunkTimeInterval . Append ( $ "SELECT set_chunk_time_interval({ qualifiedTableName } , ") ;
107+
95108 // Check if the interval is a plain number (e.g., for microseconds).
96109 if ( long . TryParse ( operation . ChunkTimeInterval , out _ ) )
97110 {
98111 // If it's a number, don't wrap it in quotes.
99- statements . Add ( $ "SELECT set_chunk_time_interval( { qualifiedTableName } , { operation . ChunkTimeInterval } ::bigint); ") ;
112+ setChunkTimeInterval . Append ( $ "{ operation . ChunkTimeInterval } ::bigint") ;
100113 }
101114 else
102115 {
103116 // If it's a string like '7 days', wrap it in quotes.
104- statements . Add ( $ "SELECT set_chunk_time_interval( { qualifiedTableName } , INTERVAL '{ operation . ChunkTimeInterval } '); ") ;
117+ setChunkTimeInterval . Append ( $ "INTERVAL '{ operation . ChunkTimeInterval } '") ;
105118 }
119+
120+ setChunkTimeInterval . Append ( ");" ) ;
121+ statements . Add ( setChunkTimeInterval . ToString ( ) ) ;
106122 }
107123
108- // Check for EnableCompression change
124+ // Check for EnableCompression change (Community Edition only)
109125 bool newCompressionState = operation . EnableCompression || operation . ChunkSkipColumns != null && operation . ChunkSkipColumns . Any ( ) ;
110126 bool oldCompressionState = operation . OldEnableCompression || operation . OldChunkSkipColumns != null && operation . OldChunkSkipColumns . Any ( ) ;
111127
112128 if ( newCompressionState != oldCompressionState )
113129 {
114130 string compressionValue = newCompressionState . ToString ( ) . ToLower ( ) ;
115- statements . Add ( $ "ALTER TABLE { qualifiedIdentifier } SET (timescaledb.compress = { compressionValue } );") ;
131+ communityStatements . Add ( $ "ALTER TABLE { qualifiedIdentifier } SET (timescaledb.compress = { compressionValue } );") ;
116132 }
117133
118- // Handle ChunkSkipColumns
134+ // Handle ChunkSkipColumns (Community Edition only)
119135 IReadOnlyList < string > newColumns = operation . ChunkSkipColumns ?? [ ] ;
120136 IReadOnlyList < string > oldColumns = operation . OldChunkSkipColumns ?? [ ] ;
121137 List < string > addedColumns = [ .. newColumns . Except ( oldColumns ) ] ;
122138
123139 if ( addedColumns . Count != 0 )
124140 {
125- statements . Add ( "SET timescaledb.enable_chunk_skipping = 'ON';" ) ;
141+ communityStatements . Add ( "SET timescaledb.enable_chunk_skipping = 'ON';" ) ;
126142
127143 foreach ( string column in addedColumns )
128144 {
129- statements . Add ( $ "SELECT enable_chunk_skipping({ qualifiedTableName } , '{ column } ');") ;
145+ communityStatements . Add ( $ "SELECT enable_chunk_skipping({ qualifiedTableName } , '{ column } ');") ;
130146 }
131147 }
132148
@@ -135,7 +151,7 @@ public List<string> Generate(AlterHypertableOperation operation)
135151 {
136152 foreach ( string column in removedColumns )
137153 {
138- statements . Add ( $ "SELECT disable_chunk_skipping({ qualifiedTableName } , '{ column } ');") ;
154+ communityStatements . Add ( $ "SELECT disable_chunk_skipping({ qualifiedTableName } , '{ column } ');") ;
139155 }
140156 }
141157
@@ -180,8 +196,40 @@ public List<string> Generate(AlterHypertableOperation operation)
180196 statements . Add ( $ "-- WARNING: TimescaleDB does not support removing dimensions. The following dimensions cannot be removed: { dimensionList } ") ;
181197 }
182198
199+
200+ // Add wrapped community statements if any exist
201+ if ( communityStatements . Count > 0 )
202+ {
203+ statements . Add ( WrapCommunityFeatures ( communityStatements ) ) ;
204+ }
183205 return statements ;
184206 }
185- }
186- }
187207
208+ /// <summary>
209+ /// Wraps multiple SQL statements in a single license check block to ensure they only run on Community Edition
210+ /// </summary>
211+ private string WrapCommunityFeatures ( List < string > sqlStatements )
212+ {
213+ var sb = new StringBuilder ( ) ;
214+ sb . AppendLine ( "DO $$" ) ;
215+ sb . AppendLine ( "DECLARE" ) ;
216+ sb . AppendLine ( " license TEXT;" ) ;
217+ sb . AppendLine ( "BEGIN" ) ;
218+ sb . AppendLine ( " license := current_setting('timescaledb.license', true);" ) ;
219+ sb . AppendLine ( " " ) ;
220+ sb . AppendLine ( " IF license IS NULL OR license != 'apache' THEN" ) ;
221+
222+ foreach ( string sql in sqlStatements )
223+ {
224+ sb . AppendLine ( $ " { sql } ") ;
225+ }
226+
227+ sb . AppendLine ( " ELSE" ) ;
228+ sb . AppendLine ( " RAISE WARNING 'Skipping Community Edition features (compression, chunk skipping) - not available in Enterprise Edition';" ) ;
229+ sb . AppendLine ( " END IF;" ) ;
230+ sb . AppendLine ( "END $$;" ) ;
231+
232+ return sb . ToString ( ) ;
233+ }
234+ }
235+ }
0 commit comments