11using CmdScale . EntityFrameworkCore . TimescaleDB . Abstractions ;
22using CmdScale . EntityFrameworkCore . TimescaleDB . Operations ;
3+ using System . Text ;
34
45namespace 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