fix(clickhouse): harden R/W path P0–P3 (cleanup, durability, rollups)

Honest TTL cleanup semantics; enqueue-safe dedup with flush retry and writer
metrics; model insert hooks; latest-per-node and hourly metric/openresty
rollups; small-host pool/async defaults, traffic hourly TTL, and UV labeling.
This commit is contained in:
ryan
2026-07-10 10:34:04 +08:00
parent 9b3555c569
commit 160e63558f
37 changed files with 1779 additions and 269 deletions
@@ -99,6 +99,9 @@ type mockConn struct {
batchQuery string
prepareCalled bool
preparedQuery string
queries []string
queryArgs [][]any
queryFn func(ctx context.Context, query string, args ...any) (driver.Rows, error)
}
func (m *mockConn) Contributors() []string { return nil }
@@ -107,8 +110,13 @@ func (m *mockConn) ServerVersion() (*driver.ServerVersion, error) { return nil,
func (m *mockConn) Select(_ context.Context, _ any, _ string, _ ...any) error { return nil }
func (m *mockConn) Query(_ context.Context, _ string, _ ...any) (driver.Rows, error) {
return nil, nil
func (m *mockConn) Query(ctx context.Context, query string, args ...any) (driver.Rows, error) {
m.queries = append(m.queries, query)
m.queryArgs = append(m.queryArgs, args)
if m.queryFn != nil {
return m.queryFn(ctx, query, args...)
}
return &mockRows{}, nil
}
func (m *mockConn) QueryRow(_ context.Context, _ string, _ ...any) driver.Row { return nil }
@@ -158,4 +166,96 @@ func (m *mockBatch) Rows() int { return len(m.rows) }
func (m *mockBatch) Columns() []column.Interface { return nil }
func (m *mockBatch) Close() error { return nil }
func (m *mockBatch) Close() error { return nil }
// mockRows is an empty driver.Rows implementation for query-path unit tests.
type mockRows struct {
index int
data [][]any
err error
}
func (m *mockRows) Next() bool {
if m.err != nil {
return false
}
if m.index >= len(m.data) {
return false
}
m.index++
return true
}
func (m *mockRows) Scan(dest ...any) error {
if m.err != nil {
return m.err
}
if m.index == 0 || m.index > len(m.data) {
return nil
}
row := m.data[m.index-1]
for i := range dest {
if i >= len(row) {
break
}
if err := assignMockScanValue(dest[i], row[i]); err != nil {
return err
}
}
return nil
}
func (m *mockRows) ScanStruct(_ any) error { return nil }
func (m *mockRows) ColumnTypes() []driver.ColumnType { return nil }
func (m *mockRows) Totals(_ ...any) error { return nil }
func (m *mockRows) Columns() []string { return nil }
func (m *mockRows) Close() error { return nil }
func (m *mockRows) Err() error { return m.err }
func (m *mockRows) HasData() bool { return len(m.data) > 0 }
func assignMockScanValue(dest any, value any) error {
switch d := dest.(type) {
case *string:
if v, ok := value.(string); ok {
*d = v
}
case *uint64:
switch v := value.(type) {
case uint64:
*d = v
case int:
*d = uint64(v)
case int64:
*d = uint64(v)
}
case *int64:
switch v := value.(type) {
case int64:
*d = v
case int:
*d = int64(v)
case uint64:
*d = int64(v)
}
case *float64:
switch v := value.(type) {
case float64:
*d = v
case float32:
*d = float64(v)
case int:
*d = float64(v)
}
case *time.Time:
if v, ok := value.(time.Time); ok {
*d = v
}
}
return nil
}