@@ -217,6 +217,17 @@ func (td *tableDiscovery) ReloadTableDefinitions() {
217217 configuredTables = td .configureTables (tables , databaseName )
218218 }
219219 }
220+ var tablePresence map [string ][]TablePresence
221+ if td .cfg .ClusterName != "" {
222+ var err error
223+ tablePresence , err = td .getTablePresenceAcrossClusters (databaseName )
224+ if err != nil {
225+ logger .Warn ().Msgf ("could not get table presence across clusters: %v" , err )
226+ }
227+ logger .Info ().Msgf ("Table presence across clusters: %v" , tablePresence )
228+ configuredTables = td .populateClusterNodes (configuredTables , databaseName , tablePresence )
229+ }
230+
220231 configuredTables = td .readVirtualTables (configuredTables )
221232
222233 td .ReloadTablesError = nil
@@ -328,7 +339,7 @@ func (td *tableDiscovery) configureTables(tables map[string]map[string]columnMet
328339 comment := td .tableComment (databaseName , table )
329340 createTableQuery := td .createTableQuery (databaseName , table )
330341 // we assume here that @timestamp field is always present in the table, or it's explicitly configured
331- configuredTables [table ] = discoveredTable {table , databaseName , columns , indexConfig , comment , createTableQuery , "" , false }
342+ configuredTables [table ] = discoveredTable {table , databaseName , columns , indexConfig , comment , createTableQuery , "" , false , false }
332343 }
333344 } else {
334345 notConfiguredTables = append (notConfiguredTables , table )
@@ -358,7 +369,7 @@ func (td *tableDiscovery) autoConfigureTables(tables map[string]map[string]colum
358369 maybeTimestampField = td .tableTimestampField (databaseName , table , ClickHouse )
359370 }
360371 const isVirtualTable = false
361- configuredTables [table ] = discoveredTable {table , databaseName , columns , config.IndexConfiguration {}, comment , createTableQuery , maybeTimestampField , isVirtualTable }
372+ configuredTables [table ] = discoveredTable {table , databaseName , columns , config.IndexConfiguration {}, comment , createTableQuery , maybeTimestampField , isVirtualTable , false }
362373
363374 }
364375 for tableName , table := range configuredTables {
@@ -415,6 +426,7 @@ func (td *tableDiscovery) populateTableDefinitions(configuredTables map[string]d
415426 CreateTableQuery : resTable .createTableQuery ,
416427 DiscoveredTimestampFieldName : timestampFieldName ,
417428 VirtualTable : resTable .virtualTable ,
429+ ExistsOnAllNodes : resTable .existsOnAllNodes ,
418430 }
419431 if containsAttributes (resTable .columnTypes ) {
420432 table .Config .Attributes = []Attribute {NewDefaultStringAttribute ()}
@@ -699,6 +711,90 @@ func (td *tableDiscovery) enrichTableWithMapFields(inputTable map[string]map[str
699711 return outputTable
700712}
701713
714+ type TablePresence struct {
715+ Database string
716+ Table string
717+ FoundNodes int
718+ TotalNodes int
719+ ExistsOnAllNodes bool
720+ }
721+
722+ func (td * tableDiscovery ) getTablePresenceAcrossClusters (database string ) (map [string ][]TablePresence , error ) {
723+ // Step 1: Get all cluster names
724+ clusterQuery := `SELECT DISTINCT cluster FROM system.clusters ORDER BY cluster`
725+ rows , err := td .dbConnPool .Query (context .Background (), clusterQuery )
726+ if err != nil {
727+ return nil , fmt .Errorf ("failed to query cluster list: %w" , err )
728+ }
729+ defer rows .Close ()
730+
731+ var clusters []string
732+ for rows .Next () {
733+ var cluster string
734+ if err := rows .Scan (& cluster ); err != nil {
735+ return nil , fmt .Errorf ("failed to scan cluster name: %w" , err )
736+ }
737+ clusters = append (clusters , cluster )
738+ }
739+
740+ // Step 2: For each cluster, safely query for the given database
741+ presenceData := make (map [string ][]TablePresence )
742+
743+ for _ , cluster := range clusters {
744+ query := `
745+ WITH (
746+ SELECT count(DISTINCT host_name)
747+ FROM system.clusters
748+ WHERE cluster = ?
749+ ) AS total_nodes
750+
751+ SELECT
752+ database,
753+ name AS table_name,
754+ count(DISTINCT hostName()) AS found_nodes,
755+ total_nodes,
756+ count(DISTINCT hostName()) = total_nodes AS exists_on_all_nodes
757+ FROM cluster(?, system.tables)
758+ WHERE database = ?
759+ GROUP BY database, name, total_nodes
760+ `
761+
762+ rows , err := td .dbConnPool .Query (context .Background (), query , cluster , cluster , database )
763+ if err != nil {
764+ return nil , fmt .Errorf ("failed to query tables for cluster %s: %w" , cluster , err )
765+ }
766+ defer rows .Close ()
767+
768+ var tables []TablePresence
769+ for rows .Next () {
770+ var tp TablePresence
771+ if err := rows .Scan (& tp .Database , & tp .Table , & tp .FoundNodes , & tp .TotalNodes , & tp .ExistsOnAllNodes ); err != nil {
772+ return nil , fmt .Errorf ("failed to scan table row: %w" , err )
773+ }
774+ tables = append (tables , tp )
775+ }
776+
777+ if len (tables ) > 0 {
778+ presenceData [cluster ] = tables
779+ }
780+ }
781+
782+ return presenceData , nil
783+ }
784+
785+ func (td * tableDiscovery ) populateClusterNodes (configuredTables map [string ]discoveredTable , databaseName string , tablePresence map [string ][]TablePresence ) map [string ]discoveredTable {
786+ for _ , tables := range tablePresence {
787+ for _ , table := range tables {
788+ if table .Database == databaseName {
789+ if discoTable , ok := configuredTables [table .Table ]; ok {
790+ discoTable .existsOnAllNodes = table .ExistsOnAllNodes
791+ }
792+ }
793+ }
794+ }
795+ return configuredTables
796+ }
797+
702798func (td * tableDiscovery ) readTables (database string ) (map [string ]map [string ]columnMetadata , error ) {
703799 logger .Debug ().Msgf ("describing tables: %s" , database )
704800
0 commit comments