fix(chdb): scan ClickHouse log_scores into signed model fields
ci/woodpecker/push/woodpecker Pipeline was successful

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.
This commit is contained in:
ask
2026-05-23 23:52:09 -07:00
parent ab2c853fd7
commit 6ee1146173
+54 -42
View File
@@ -2,16 +2,62 @@ package chdb
import ( import (
"context" "context"
"database/sql"
"fmt" "fmt"
"strings" "strings"
"time" "time"
"github.com/ClickHouse/clickhouse-go/v2" "github.com/ClickHouse/clickhouse-go/v2"
"github.com/jackc/pgx/v5/pgtype"
"go.ntppool.org/common/logger" "go.ntppool.org/common/logger"
"go.ntppool.org/common/tracing" "go.ntppool.org/common/tracing"
"go.ntppool.org/data-api/ntpdb" "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) { func (d *ClickHouse) Logscores(ctx context.Context, serverID, monitorID int, since time.Time, limit int, fullHistory bool) ([]ntpdb.LogScore, error) {
log := logger.Setup() log := logger.Setup()
ctx, span := tracing.Tracer().Start(ctx, "CH Logscores") 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{} rv := []ntpdb.LogScore{}
for rows.Next() { for rows.Next() {
row, err := scanLogScore(rows)
row := ntpdb.LogScore{} if err != nil {
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 {
log.Error("could not parse row", "err", err) log.Error("could not parse row", "err", err)
continue continue
} }
row.Attributes.Leap = int8(leap)
rv = append(rv, row) rv = append(rv, row)
} }
// log.InfoContext(ctx, "returning data", "rv", rv) // log.InfoContext(ctx, "returning data", "rv", rv)
@@ -138,7 +164,8 @@ func (d *ClickHouse) LogscoresTimeRange(ctx context.Context, serverID, monitorID
args = append(args, limit) args = append(args, limit)
} }
log.DebugContext(ctx, "clickhouse time range query", log.DebugContext(
ctx, "clickhouse time range query",
"query", query, "query", query,
"args", args, "args", args,
"server_id", serverID, "server_id", serverID,
@@ -181,31 +208,16 @@ func (d *ClickHouse) LogscoresTimeRange(ctx context.Context, serverID, monitorID
rv := []ntpdb.LogScore{} rv := []ntpdb.LogScore{}
for rows.Next() { for rows.Next() {
row := ntpdb.LogScore{} row, err := scanLogScore(rows)
var leap uint8 if err != nil {
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 {
log.Error("could not parse row", "err", err) log.Error("could not parse row", "err", err)
continue continue
} }
row.Attributes.Leap = int8(leap)
rv = append(rv, row) rv = append(rv, row)
} }
log.InfoContext(ctx, "time range query results", log.InfoContext(
ctx, "time range query results",
"rows_returned", len(rv), "rows_returned", len(rv),
"server_id", serverID, "server_id", serverID,
"monitor_id", monitorID, "monitor_id", monitorID,