From 60ca42e5d5a21bf37ddfbca494f2862f97e5561d Mon Sep 17 00:00:00 2001 From: lingniu Date: Wed, 1 Jul 2026 22:06:12 +0800 Subject: [PATCH] fix: quote tdengine history inserts --- go/vehicle-gateway/internal/history/writer.go | 42 +++++++++++++++++-- .../internal/history/writer_test.go | 16 +++++++ 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/go/vehicle-gateway/internal/history/writer.go b/go/vehicle-gateway/internal/history/writer.go index 64333c97..c58f6b25 100644 --- a/go/vehicle-gateway/internal/history/writer.go +++ b/go/vehicle-gateway/internal/history/writer.go @@ -63,7 +63,7 @@ func (w *Writer) AppendRawFrame(ctx context.Context, env envelope.FrameEnvelope) _, err := w.exec.ExecContext(ctx, fmt.Sprintf(`INSERT INTO %s (ts, frame_id, event_id, message_id, event_time, received_at, raw_size_bytes, raw_hex, raw_text, parsed_json, fields_json, parse_status, parse_error, source_endpoint) -VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, table), rawValues(env)...) +VALUES (%s)`, table, joinLiterals(rawValues(env)))) return err } @@ -80,7 +80,7 @@ func (w *Writer) AppendLocation(ctx context.Context, env envelope.FrameEnvelope) _, err := w.exec.ExecContext(ctx, fmt.Sprintf(`INSERT INTO %s (ts, event_id, frame_id, received_at, longitude, latitude, altitude_m, speed_kmh, direction_deg, alarm_flag, status_flag, total_mileage_km) -VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, table), locationValues(env, longitude, latitude)...) +VALUES (%s)`, table, joinLiterals(locationValues(env, longitude, latitude)))) return err } @@ -95,7 +95,7 @@ func (w *Writer) AppendMileagePoint(ctx context.Context, env envelope.FrameEnvel } _, err := w.exec.ExecContext(ctx, fmt.Sprintf(`INSERT INTO %s (ts, event_id, frame_id, received_at, total_mileage_km, speed_kmh, longitude, latitude) -VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, table), mileageValues(env, totalMileage)...) +VALUES (%s)`, table, joinLiterals(mileageValues(env, totalMileage)))) return err } @@ -298,3 +298,39 @@ func millis(value int64) time.Time { func quote(value string) string { return strings.ReplaceAll(value, "'", "''") } + +func joinLiterals(values []any) string { + out := make([]string, 0, len(values)) + for _, value := range values { + out = append(out, literal(value)) + } + return strings.Join(out, ", ") +} + +func literal(value any) string { + if value == nil { + return "NULL" + } + switch typed := value.(type) { + case time.Time: + return "'" + typed.UTC().Format("2006-01-02 15:04:05.000") + "'" + case string: + return "'" + quote(typed) + "'" + case envelope.ParseStatus: + return "'" + quote(string(typed)) + "'" + case int: + return strconv.FormatInt(int64(typed), 10) + case int64: + return strconv.FormatInt(typed, 10) + case uint16: + return strconv.FormatUint(uint64(typed), 10) + case uint32: + return strconv.FormatUint(uint64(typed), 10) + case float64: + return strconv.FormatFloat(typed, 'f', -1, 64) + case float32: + return strconv.FormatFloat(float64(typed), 'f', -1, 64) + default: + return "'" + quote(fmt.Sprint(typed)) + "'" + } +} diff --git a/go/vehicle-gateway/internal/history/writer_test.go b/go/vehicle-gateway/internal/history/writer_test.go index 753b0532..ddc27c61 100644 --- a/go/vehicle-gateway/internal/history/writer_test.go +++ b/go/vehicle-gateway/internal/history/writer_test.go @@ -45,6 +45,13 @@ func TestWriterAppendsRawLocationAndMileage(t *testing.T) { if got := countSQL(exec.calls, "INSERT INTO mil_"); got != 1 { t.Fatalf("mileage insert count = %d", got) } + rawInsert := findSQL(exec.calls, "INSERT INTO raw_") + if !strings.Contains(rawInsert, "'go_") || !strings.Contains(rawInsert, "'2e") && !strings.Contains(rawInsert, "'") { + t.Fatalf("raw insert should quote string literals: %s", rawInsert) + } + if strings.Contains(rawInsert, "VALUES (?,") { + t.Fatalf("raw insert should not use placeholders for tdengine websocket plain insert: %s", rawInsert) + } } func TestWriterSkipsSparseDerivedRows(t *testing.T) { @@ -103,6 +110,15 @@ func countSQL(calls []execCall, pattern string) int { return count } +func findSQL(calls []execCall, pattern string) string { + for _, call := range calls { + if strings.Contains(call.query, pattern) { + return call.query + } + } + return "" +} + type execCall struct { query string args []any