delivery-log-storage: the table for a message's log lines

Migration 0003_send_log_lines.sql, DDL only: queue id, the line's own time,
the line. No reference to send_log, because a message's first lines are
logged before the journal milter writes its row and one queue id is several
rows.

Store methods: InsertSendLogLine; InsertSendLogLineOnce for lines the tailer
reads twice after an unclean stop; SendLogLines, which gives a row the lines
of its queue id dated from an hour before the row to the queue lifetime after
it, so a later message under a reused id is not shown; PruneSendLogLines,
which deletes lines whose queue id has no send-log row - the whole of the
table's retention. A line is cut to 4096 bytes.

Nothing writes or reads the table yet. Checked on Windows: go vet and
go test ./internal/store pass, with the schema test pinning the new table,
its index and user_version 3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mix
2026-10-12 10:08:56 +03:00
co-authored by Claude Opus 5.5
parent 4468aa8dd7
commit 3d17206063
8 changed files with 371 additions and 11 deletions
+7 -6
View File
@@ -57,6 +57,7 @@ func TestBaselineSchema(t *testing.T) {
"dmarc_report_records": "id report_row_id source_ip count disposition spf_result dkim_result header_from",
"inbound_spam_log": "id inbound_domain client_ip from_addr to_addr subject decision engine detail created_at",
"inbound_spam_list": "id list_type entry_type value note created_at",
"send_log_lines": "id queue_id logged_at line",
}
rows, err := st.db.Query("SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name")
if err != nil {
@@ -102,7 +103,7 @@ func TestBaselineSchema(t *testing.T) {
wantIdx := []string{
"idx_dmarc_report_records_report", "idx_dmarc_reports_domain", "idx_dmarc_reports_received",
"idx_inbound_spam_log_created_at", "idx_inbound_spam_log_domain_created_at",
"idx_send_log_app_login_created_at", "idx_send_log_created_at", "idx_send_log_domain", "idx_send_log_queue_id",
"idx_send_log_app_login_created_at", "idx_send_log_created_at", "idx_send_log_domain", "idx_send_log_lines_queue_id", "idx_send_log_queue_id",
"idx_sessions_expires_at",
}
sort.Strings(wantIdx)
@@ -111,8 +112,8 @@ func TestBaselineSchema(t *testing.T) {
}
var version, appID int64
if err := st.db.QueryRow("PRAGMA user_version").Scan(&version); err != nil || version != 2 {
t.Errorf("user_version = %d, %v; want 2", version, err)
if err := st.db.QueryRow("PRAGMA user_version").Scan(&version); err != nil || version != 3 {
t.Errorf("user_version = %d, %v; want 3", version, err)
}
if err := st.db.QueryRow("PRAGMA application_id").Scan(&appID); err != nil || appID != applicationID {
t.Errorf("application_id = %#x, %v; want %#x", appID, err, applicationID)
@@ -188,13 +189,13 @@ func TestOpenMigratesA20Database(t *testing.T) {
}
defer st.Close()
var version int
if err := st.db.QueryRow("PRAGMA user_version").Scan(&version); err != nil || version != 2 {
t.Errorf("user_version = %d, %v; want 2", version, err)
if err := st.db.QueryRow("PRAGMA user_version").Scan(&version); err != nil || version != 3 {
t.Errorf("user_version = %d, %v; want 3", version, err)
}
if ok, err := st.UserExists(); err != nil || !ok {
t.Errorf("the user of the 2.0 database: exists = %v, %v", ok, err)
}
for _, table := range []string{"inbound_spam_log", "inbound_spam_list"} {
for _, table := range []string{"inbound_spam_log", "inbound_spam_list", "send_log_lines"} {
if len(tableColumns(t, st, table)) == 0 {
t.Errorf("table %s was not created", table)
}