Skip to content

Commit 39a307e

Browse files
authored
Merge branch 'main' into fix/data-races
2 parents 4d9b622 + edf6561 commit 39a307e

2 files changed

Lines changed: 68 additions & 6 deletions

File tree

db.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -121,12 +121,16 @@ type HistoryMessage struct {
121121
}
122122

123123
func (s *server) saveMessageToHistory(userID, chatJID, senderJID, messageID, messageType, textContent, mediaLink, quotedMessageID, dataJson string) error {
124-
query := `INSERT INTO message_history (user_id, chat_jid, sender_jid, message_id, timestamp, message_type, text_content, media_link, quoted_message_id, datajson)
125-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`
126-
if s.db.DriverName() == "sqlite" {
127-
query = `INSERT INTO message_history (user_id, chat_jid, sender_jid, message_id, timestamp, message_type, text_content, media_link, quoted_message_id, datajson)
128-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
129-
}
124+
// Idempotent insert: HistorySync batches (and reconnects) routinely redeliver
125+
// messages already persisted via the live Message event. The (user_id, message_id)
126+
// unique constraint makes those duplicates an expected condition, not an error,
127+
// so skip them silently instead of failing the insert and logging at ERROR. See #292.
128+
// Rebind adapts the ? placeholders to the active driver ($1.. on Postgres,
129+
// ? on SQLite), so the query is defined once. ON CONFLICT DO NOTHING is valid
130+
// on both Postgres and modern SQLite.
131+
query := s.db.Rebind(`INSERT INTO message_history (user_id, chat_jid, sender_jid, message_id, timestamp, message_type, text_content, media_link, quoted_message_id, datajson)
132+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
133+
ON CONFLICT (user_id, message_id) DO NOTHING`)
130134
_, err := s.db.Exec(query, userID, chatJID, senderJID, messageID, time.Now(), messageType, textContent, mediaLink, quotedMessageID, dataJson)
131135
if err != nil {
132136
return fmt.Errorf("failed to save message to history: %w", err)

db_history_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package main
2+
3+
import (
4+
"testing"
5+
)
6+
7+
// TestSaveMessageToHistoryIdempotent verifies the fix for #292: persisting a
8+
// message whose (user_id, message_id) already exists must NOT return an error
9+
// (the plain INSERT previously violated the message_history unique constraint
10+
// and was logged at ERROR on every HistorySync), and must not create a
11+
// duplicate row.
12+
func TestSaveMessageToHistoryIdempotent(t *testing.T) {
13+
s := makeTestServer(t)
14+
15+
const (
16+
userID = "user-1"
17+
chat = "123456@s.whatsapp.net"
18+
sender = "123456@s.whatsapp.net"
19+
msgID = "MSG-DUP-1"
20+
)
21+
22+
// First insert: a normal live Message event.
23+
if err := s.saveMessageToHistory(userID, chat, sender, msgID, "text", "hello", "", "", "{}"); err != nil {
24+
t.Fatalf("first insert failed: %v", err)
25+
}
26+
27+
// Second insert with the same (user_id, message_id): simulates the same
28+
// message arriving again in a HistorySync batch or on reconnect. With the
29+
// fix this is a silent no-op; without it, it returns a unique-constraint
30+
// violation.
31+
if err := s.saveMessageToHistory(userID, chat, sender, msgID, "text", "hello", "", "", "{}"); err != nil {
32+
t.Fatalf("duplicate insert should be a silent no-op, got error: %v", err)
33+
}
34+
35+
// Exactly one row must exist for this (user_id, message_id).
36+
var count int
37+
if err := s.db.Get(&count,
38+
"SELECT COUNT(*) FROM message_history WHERE user_id = ? AND message_id = ?",
39+
userID, msgID); err != nil {
40+
t.Fatalf("count query failed: %v", err)
41+
}
42+
if count != 1 {
43+
t.Fatalf("expected exactly 1 row after duplicate insert, got %d", count)
44+
}
45+
46+
// A different message_id for the same user must still insert normally
47+
// (the conflict clause must not swallow legitimate inserts).
48+
if err := s.saveMessageToHistory(userID, chat, sender, "MSG-OTHER", "text", "world", "", "", "{}"); err != nil {
49+
t.Fatalf("insert of a distinct message failed: %v", err)
50+
}
51+
if err := s.db.Get(&count,
52+
"SELECT COUNT(*) FROM message_history WHERE user_id = ?", userID); err != nil {
53+
t.Fatalf("count query failed: %v", err)
54+
}
55+
if count != 2 {
56+
t.Fatalf("expected 2 distinct rows, got %d", count)
57+
}
58+
}

0 commit comments

Comments
 (0)