From 6ee114617398b042faa5e6b0d463f5ac13fd48dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ask=20Bj=C3=B8rn=20Hansen?= Date: Sat, 23 May 2026 23:52:09 -0700 Subject: [PATCH] fix(chdb): scan ClickHouse log_scores into signed model fields The PostgreSQL model migration regenerated LogScore.ID, MonitorID and ServerID as signed/pgtype fields, but the ClickHouse log_scores columns are unsigned (id UInt64, monitor_id/server_id UInt32). clickhouse-go refuses to scan UInt64 into *int64, so every score row failed to parse and the scores endpoints returned empty. Scan each row into locals matching the ClickHouse column types, then convert into the model, mirroring the existing leap handling. Factor the shared scan into scanLogScore used by both Logscores and LogscoresTimeRange. --- chdb/logscores.go | 96 ++++++++++++++++++++++++++--------------------- 1 file changed, 54 insertions(+), 42 deletions(-) diff --git a/chdb/logscores.go b/chdb/logscores.go index 794f0aa..7f5069c 100644 --- a/chdb/logscores.go +++ b/chdb/logscores.go @@ -2,16 +2,62 @@ package chdb import ( "context" + "database/sql" "fmt" "strings" "time" "github.com/ClickHouse/clickhouse-go/v2" + "github.com/jackc/pgx/v5/pgtype" "go.ntppool.org/common/logger" "go.ntppool.org/common/tracing" "go.ntppool.org/data-api/ntpdb" ) +// scanLogScore reads one ClickHouse log_scores row into an ntpdb.LogScore. +// The ClickHouse columns are unsigned (id UInt64, monitor_id/server_id UInt32, +// rtt UInt32), but the PostgreSQL-generated model uses signed/pgtype fields, so +// the row is scanned into locals matching the ClickHouse types and then converted. +func scanLogScore(rows interface{ Scan(...any) error }) (ntpdb.LogScore, error) { + row := ntpdb.LogScore{} + + var ( + id uint64 + monitorID uint32 + serverID uint32 + ts time.Time + offset sql.NullFloat64 + rtt sql.NullInt32 + leap uint8 + ) + + if err := rows.Scan( + &id, + &monitorID, + &serverID, + &ts, + &row.Score, + &row.Step, + &offset, + &rtt, + &leap, + &row.Attributes.Warning, + &row.Attributes.Error, + ); err != nil { + return row, err + } + + row.ID = int64(id) + row.MonitorID = pgtype.Int8{Int64: int64(monitorID), Valid: true} + row.ServerID = int64(serverID) + row.Ts = pgtype.Timestamptz{Time: ts, Valid: true} + row.Offset = pgtype.Float8{Float64: offset.Float64, Valid: offset.Valid} + row.Rtt = pgtype.Int4{Int32: rtt.Int32, Valid: rtt.Valid} + row.Attributes.Leap = int8(leap) + + return row, nil +} + func (d *ClickHouse) Logscores(ctx context.Context, serverID, monitorID int, since time.Time, limit int, fullHistory bool) ([]ntpdb.LogScore, error) { log := logger.Setup() ctx, span := tracing.Tracer().Start(ctx, "CH Logscores") @@ -74,32 +120,12 @@ func (d *ClickHouse) Logscores(ctx context.Context, serverID, monitorID int, sin rv := []ntpdb.LogScore{} for rows.Next() { - - row := ntpdb.LogScore{} - - var leap uint8 - - if err := rows.Scan( - &row.ID, - &row.MonitorID, - &row.ServerID, - &row.Ts, - &row.Score, - &row.Step, - &row.Offset, - &row.Rtt, - &leap, - &row.Attributes.Warning, - &row.Attributes.Error, - ); err != nil { + row, err := scanLogScore(rows) + if err != nil { log.Error("could not parse row", "err", err) continue } - - row.Attributes.Leap = int8(leap) - rv = append(rv, row) - } // log.InfoContext(ctx, "returning data", "rv", rv) @@ -138,7 +164,8 @@ func (d *ClickHouse) LogscoresTimeRange(ctx context.Context, serverID, monitorID args = append(args, limit) } - log.DebugContext(ctx, "clickhouse time range query", + log.DebugContext( + ctx, "clickhouse time range query", "query", query, "args", args, "server_id", serverID, @@ -181,31 +208,16 @@ func (d *ClickHouse) LogscoresTimeRange(ctx context.Context, serverID, monitorID rv := []ntpdb.LogScore{} for rows.Next() { - row := ntpdb.LogScore{} - var leap uint8 - - if err := rows.Scan( - &row.ID, - &row.MonitorID, - &row.ServerID, - &row.Ts, - &row.Score, - &row.Step, - &row.Offset, - &row.Rtt, - &leap, - &row.Attributes.Warning, - &row.Attributes.Error, - ); err != nil { + row, err := scanLogScore(rows) + if err != nil { log.Error("could not parse row", "err", err) continue } - - row.Attributes.Leap = int8(leap) rv = append(rv, row) } - log.InfoContext(ctx, "time range query results", + log.InfoContext( + ctx, "time range query results", "rows_returned", len(rv), "server_id", serverID, "monitor_id", monitorID,