@@ -15,6 +15,7 @@ import (
1515 "os"
1616 "path/filepath"
1717 "sort"
18+ "strconv"
1819 "strings"
1920 "time"
2021
@@ -31,6 +32,9 @@ type Config struct {
3132 Password string
3233 Database string
3334
35+ // Table TTL (Go duration string, e.g. "720h")
36+ TablesTTL string
37+
3438 // TLS settings
3539 TLSCAFile string
3640 TLSCertFile string
@@ -50,6 +54,7 @@ func loadConfig() (*Config, error) {
5054 User : getEnv ("CLICKHOUSE_USER" , "default" ),
5155 Password : getEnv ("CLICKHOUSE_PASSWORD" , "" ),
5256 Database : getEnv ("HYPERDX_OTEL_EXPORTER_CLICKHOUSE_DATABASE" , "default" ),
57+ TablesTTL : getEnv ("HYPERDX_OTEL_EXPORTER_TABLES_TTL" , "720h" ),
5358 TLSCAFile : getEnv ("CLICKHOUSE_TLS_CA_FILE" , "" ),
5459 TLSCertFile : getEnv ("CLICKHOUSE_TLS_CERT_FILE" , "" ),
5560 TLSKeyFile : getEnv ("CLICKHOUSE_TLS_KEY_FILE" , "" ),
@@ -215,9 +220,46 @@ func createClickHouseDB(cfg *Config) (*sql.DB, error) {
215220 return db , nil
216221}
217222
223+ // parseTTLDuration parses a duration string that supports days ("30d") in
224+ // addition to the standard Go duration format ("720h", "90m", "3600s").
225+ func parseTTLDuration (s string ) (time.Duration , error ) {
226+ // Handle "d" suffix (days) which Go's time.ParseDuration doesn't support
227+ if strings .HasSuffix (s , "d" ) {
228+ days , err := strconv .Atoi (strings .TrimSuffix (s , "d" ))
229+ if err != nil {
230+ return 0 , fmt .Errorf ("invalid duration %q: %w" , s , err )
231+ }
232+ return time .Duration (days ) * 24 * time .Hour , nil
233+ }
234+ return time .ParseDuration (s )
235+ }
236+
237+ // ttlToClickHouseInterval converts a duration string (e.g. "30d", "720h",
238+ // "90m") to a ClickHouse interval expression, following the same approach as
239+ // the upstream otel-collector-contrib ClickHouse exporter's GenerateTTLExpr.
240+ func ttlToClickHouseInterval (ttl string ) (string , error ) {
241+ d , err := parseTTLDuration (ttl )
242+ if err != nil {
243+ return "" , fmt .Errorf ("invalid TTL duration %q: %w" , ttl , err )
244+ }
245+ if d <= 0 {
246+ return "" , fmt .Errorf ("TTL must be positive, got %q" , ttl )
247+ }
248+ switch {
249+ case d % (24 * time .Hour ) == 0 :
250+ return fmt .Sprintf ("toIntervalDay(%d)" , d / (24 * time .Hour )), nil
251+ case d % time .Hour == 0 :
252+ return fmt .Sprintf ("toIntervalHour(%d)" , d / time .Hour ), nil
253+ case d % time .Minute == 0 :
254+ return fmt .Sprintf ("toIntervalMinute(%d)" , d / time .Minute ), nil
255+ default :
256+ return fmt .Sprintf ("toIntervalSecond(%d)" , d / time .Second ), nil
257+ }
258+ }
259+
218260// processSchemaDir creates a temporary directory with SQL files that have
219- // the ${DATABASE} macro replaced with the actual database name
220- func processSchemaDir (schemaDir , database string ) (string , error ) {
261+ // the ${DATABASE} and ${TABLES_TTL} macros replaced with actual values
262+ func processSchemaDir (schemaDir , database , tablesTTLExpr string ) (string , error ) {
221263 tempDir , err := os .MkdirTemp ("" , "schema-*" )
222264 if err != nil {
223265 return "" , fmt .Errorf ("failed to create temp directory: %w" , err )
@@ -247,8 +289,9 @@ func processSchemaDir(schemaDir, database string) (string, error) {
247289 return fmt .Errorf ("failed to read file %s: %w" , path , err )
248290 }
249291
250- // Replace ${DATABASE} macro with actual database name
292+ // Replace macros with actual values
251293 processedContent := strings .ReplaceAll (string (content ), "${DATABASE}" , database )
294+ processedContent = strings .ReplaceAll (processedContent , "${TABLES_TTL}" , tablesTTLExpr )
252295
253296 // Write processed content to temp directory
254297 if err := os .WriteFile (destPath , []byte (processedContent ), 0644 ); err != nil {
@@ -345,9 +388,16 @@ func main() {
345388 }
346389 log .Println ("Successfully connected to ClickHouse" )
347390
348- // Process schema directory (replace ${DATABASE} macro)
391+ // Parse tables TTL
392+ tablesTTLExpr , err := ttlToClickHouseInterval (cfg .TablesTTL )
393+ if err != nil {
394+ log .Fatalf ("Invalid HYPERDX_OTEL_EXPORTER_TABLES_TTL: %v" , err )
395+ }
396+ log .Printf ("Tables TTL: %s (%s)" , cfg .TablesTTL , tablesTTLExpr )
397+
398+ // Process schema directory (replace ${DATABASE} and ${TABLES_TTL} macros)
349399 log .Printf ("Preparing SQL files with database: %s" , cfg .Database )
350- tempDir , err := processSchemaDir (cfg .SchemaDir , cfg .Database )
400+ tempDir , err := processSchemaDir (cfg .SchemaDir , cfg .Database , tablesTTLExpr )
351401 if err != nil {
352402 log .Fatalf ("Failed to process schema directory: %v" , err )
353403 }
0 commit comments