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 )
@@ -85,6 +89,11 @@ public List<string> Generate(CreateHypertableOperation operation)
8589 }
8690 }
8791
92+ // Add wrapped community statements if any exist
93+ if ( communityStatements . Count > 0 )
94+ {
95+ statements . Add ( WrapCommunityFeatures ( communityStatements ) ) ;
96+ }
8897 return statements ;
8998 }
9099
@@ -94,45 +103,52 @@ public List<string> Generate(AlterHypertableOperation operation)
94103 string qualifiedIdentifier = sqlHelper . QualifiedIdentifier ( operation . TableName , operation . Schema ) ;
95104
96105 List < string > statements = [ ] ;
106+ List < string > communityStatements = [ ] ;
97107
98- // Check for ChunkTimeInterval change
108+ // Check for ChunkTimeInterval change (Available in both editions)
99109 if ( operation . ChunkTimeInterval != operation . OldChunkTimeInterval )
100110 {
111+ var setChunkTimeInterval = new StringBuilder ( ) ;
112+ setChunkTimeInterval . Append ( $ "SELECT set_chunk_time_interval({ qualifiedTableName } , ") ;
113+
101114 // Check if the interval is a plain number (e.g., for microseconds).
102115 if ( long . TryParse ( operation . ChunkTimeInterval , out _ ) )
103116 {
104117 // If it's a number, don't wrap it in quotes.
105- statements . Add ( $ "SELECT set_chunk_time_interval( { qualifiedTableName } , { operation . ChunkTimeInterval } ::bigint); ") ;
118+ setChunkTimeInterval . Append ( $ "{ operation . ChunkTimeInterval } ::bigint") ;
106119 }
107120 else
108121 {
109122 // If it's a string like '7 days', wrap it in quotes.
110- statements . Add ( $ "SELECT set_chunk_time_interval( { qualifiedTableName } , INTERVAL '{ operation . ChunkTimeInterval } '); ") ;
123+ setChunkTimeInterval . Append ( $ "INTERVAL '{ operation . ChunkTimeInterval } '") ;
111124 }
125+
126+ setChunkTimeInterval . Append ( ");" ) ;
127+ statements . Add ( setChunkTimeInterval . ToString ( ) ) ;
112128 }
113129
114- // Check for EnableCompression change
130+ // Check for EnableCompression change (Community Edition only)
115131 bool newCompressionState = operation . EnableCompression || operation . ChunkSkipColumns != null && operation . ChunkSkipColumns . Any ( ) ;
116132 bool oldCompressionState = operation . OldEnableCompression || operation . OldChunkSkipColumns != null && operation . OldChunkSkipColumns . Any ( ) ;
117133
118134 if ( newCompressionState != oldCompressionState )
119135 {
120136 string compressionValue = newCompressionState . ToString ( ) . ToLower ( ) ;
121- statements . Add ( $ "ALTER TABLE { qualifiedIdentifier } SET (timescaledb.compress = { compressionValue } );") ;
137+ communityStatements . Add ( $ "ALTER TABLE { qualifiedIdentifier } SET (timescaledb.compress = { compressionValue } );") ;
122138 }
123139
124- // Handle ChunkSkipColumns
140+ // Handle ChunkSkipColumns (Community Edition only)
125141 IReadOnlyList < string > newColumns = operation . ChunkSkipColumns ?? [ ] ;
126142 IReadOnlyList < string > oldColumns = operation . OldChunkSkipColumns ?? [ ] ;
127143 List < string > addedColumns = [ .. newColumns . Except ( oldColumns ) ] ;
128144
129145 if ( addedColumns . Count != 0 )
130146 {
131- statements . Add ( "SET timescaledb.enable_chunk_skipping = 'ON';" ) ;
147+ communityStatements . Add ( "SET timescaledb.enable_chunk_skipping = 'ON';" ) ;
132148
133149 foreach ( string column in addedColumns )
134150 {
135- statements . Add ( $ "SELECT enable_chunk_skipping({ qualifiedTableName } , '{ column } ');") ;
151+ communityStatements . Add ( $ "SELECT enable_chunk_skipping({ qualifiedTableName } , '{ column } ');") ;
136152 }
137153 }
138154
@@ -141,7 +157,7 @@ public List<string> Generate(AlterHypertableOperation operation)
141157 {
142158 foreach ( string column in removedColumns )
143159 {
144- statements . Add ( $ "SELECT disable_chunk_skipping({ qualifiedTableName } , '{ column } ');") ;
160+ communityStatements . Add ( $ "SELECT disable_chunk_skipping({ qualifiedTableName } , '{ column } ');") ;
145161 }
146162 }
147163
@@ -192,8 +208,40 @@ public List<string> Generate(AlterHypertableOperation operation)
192208 statements . Add ( $ "-- WARNING: TimescaleDB does not support removing dimensions. The following dimensions cannot be removed: { dimensionList } ") ;
193209 }
194210
211+
212+ // Add wrapped community statements if any exist
213+ if ( communityStatements . Count > 0 )
214+ {
215+ statements . Add ( WrapCommunityFeatures ( communityStatements ) ) ;
216+ }
195217 return statements ;
196218 }
197- }
198- }
199219
220+ /// <summary>
221+ /// Wraps multiple SQL statements in a single license check block to ensure they only run on Community Edition
222+ /// </summary>
223+ private string WrapCommunityFeatures ( List < string > sqlStatements )
224+ {
225+ var sb = new StringBuilder ( ) ;
226+ sb . AppendLine ( "DO $$" ) ;
227+ sb . AppendLine ( "DECLARE" ) ;
228+ sb . AppendLine ( " license TEXT;" ) ;
229+ sb . AppendLine ( "BEGIN" ) ;
230+ sb . AppendLine ( " license := current_setting('timescaledb.license', true);" ) ;
231+ sb . AppendLine ( " " ) ;
232+ sb . AppendLine ( " IF license IS NULL OR license != 'apache' THEN" ) ;
233+
234+ foreach ( string sql in sqlStatements )
235+ {
236+ sb . AppendLine ( $ " { sql } ") ;
237+ }
238+
239+ sb . AppendLine ( " ELSE" ) ;
240+ sb . AppendLine ( " RAISE WARNING 'Skipping Community Edition features (compression, chunk skipping) - not available in Enterprise Edition';" ) ;
241+ sb . AppendLine ( " END IF;" ) ;
242+ sb . AppendLine ( "END $$;" ) ;
243+
244+ return sb . ToString ( ) ;
245+ }
246+ }
247+ }
0 commit comments