From 8a28373a014050a956e557e57dd375be2688bf9f Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Fri, 29 May 2026 02:04:02 +0200 Subject: [PATCH] fix(nodes): use GREATEST for last_online merge on PostgreSQL setRemoteTrafficLocked merged last_online with MAX(last_online, ?), which is SQLite's two-argument scalar max. PostgreSQL's MAX() is aggregate-only, so node traffic sync failed every cycle with "function max(bigint, unknown) does not exist (SQLSTATE 42883)", flooding the logs. Add a dialect-aware database.GreatestExpr helper (GREATEST on Postgres, MAX on SQLite) and use it for the last_online merge. last_online is a non-null int64, so the two functions are semantically identical here. Closes #4633 --- database/dialect.go | 14 ++++++++------ web/service/inbound.go | 11 +++++++---- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/database/dialect.go b/database/dialect.go index b33a1dbe..bdf4486c 100644 --- a/database/dialect.go +++ b/database/dialect.go @@ -12,15 +12,17 @@ func JSONClientsFromInbound() string { return "FROM inbounds, JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client" } -// JSONFieldText returns a SQL expression that extracts the textual value of -// from a JSON expression. On both backends the result is the raw (unquoted) string, -// so callers do NOT need to trim surrounding quotes. func JSONFieldText(expr, key string) string { if IsPostgres() { return fmt.Sprintf("(%s ->> '%s')", expr, key) } - // SQLite's JSON_EXTRACT on a text value returns the JSON-encoded form - // (with surrounding quotes). Wrap it in json_extract(json_quote(...)) trick - // is fragile; simpler: unwrap quotes with TRIM(BOTH '"'). + return fmt.Sprintf("TRIM(JSON_EXTRACT(%s, '$.%s'), '\"')", expr, key) } + +func GreatestExpr(a, b string) string { + if IsPostgres() { + return fmt.Sprintf("GREATEST(%s, %s)", a, b) + } + return fmt.Sprintf("MAX(%s, %s)", a, b) +} diff --git a/web/service/inbound.go b/web/service/inbound.go index 111eca39..155fe1a2 100644 --- a/web/service/inbound.go +++ b/web/service/inbound.go @@ -1503,10 +1503,13 @@ func (s *InboundService) setRemoteTrafficLocked(nodeID int, snap *runtime.Traffi } if err := tx.Exec( - `UPDATE client_traffics - SET up = ?, down = ?, enable = ?, total = ?, expiry_time = ?, reset = ?, - last_online = MAX(last_online, ?) - WHERE email = ?`, + fmt.Sprintf( + `UPDATE client_traffics + SET up = ?, down = ?, enable = ?, total = ?, expiry_time = ?, reset = ?, + last_online = %s + WHERE email = ?`, + database.GreatestExpr("last_online", "?"), + ), cs.Up, cs.Down, cs.Enable, cs.Total, cs.ExpiryTime, cs.Reset, cs.LastOnline, cs.Email, ).Error; err != nil {