Files
mailgoserver/internal/db/crud_jmap_state.go
T
2026-08-22 06:45:05 +01:00

126 lines
4.0 KiB
Go

package db
import (
"database/sql"
"strconv"
)
// MessagesState returns the current JMAP (internal/jmap) Email state string for
// mailboxID — the highest modseq across both esrv_mailbox_messages and its tombstones
// table, since a destroy must advance the reported state even though the message row
// itself is gone by then.
func (d *DB) MessagesState(mailboxID int64) (string, error) {
var msgMax, tombMax int64
if err := d.QueryRow(`SELECT COALESCE(MAX(modseq), 0) FROM esrv_mailbox_messages WHERE mailbox_id = ?`, mailboxID).Scan(&msgMax); err != nil {
return "", err
}
if err := d.QueryRow(`SELECT COALESCE(MAX(modseq), 0) FROM esrv_mailbox_message_tombstones WHERE mailbox_id = ?`, mailboxID).Scan(&tombMax); err != nil {
return "", err
}
max := msgMax
if tombMax > max {
max = tombMax
}
return strconv.FormatInt(max, 10), nil
}
// FoldersState returns the current JMAP (internal/jmap) Mailbox state string for
// mailboxID.
func (d *DB) FoldersState(mailboxID int64) (string, error) {
var max int64
err := d.QueryRow(`SELECT COALESCE(MAX(modseq), 0) FROM esrv_mailbox_folders WHERE mailbox_id = ?`, mailboxID).Scan(&max)
return strconv.FormatInt(max, 10), err
}
// MessageChanges is one Email/changes result (internal/jmap): what changed for
// mailboxID since sinceState. No maxChanges paging in v1 (see the JMAP plan's
// disclosed scope cuts) — HasMore is always false.
type MessageChanges struct {
Created []int64
Updated []int64
Destroyed []int64
NewState string
HasMore bool
}
func scanInt64Rows(rows *sql.Rows) ([]int64, error) {
defer rows.Close()
var out []int64
for rows.Next() {
var id int64
if err := rows.Scan(&id); err != nil {
return nil, err
}
out = append(out, id)
}
return out, rows.Err()
}
// MessageChangesSince computes MessageChanges for mailboxID against sinceState (a
// modseq value, as returned by a prior MessagesState/MessageChangesSince call) — see
// schema.go's comment on esrv_mailbox_messages.modseq/created_modseq for the created/
// updated/destroyed derivation.
func (d *DB) MessageChangesSince(mailboxID, sinceState int64) (MessageChanges, error) {
var out MessageChanges
createdRows, err := d.Query(`SELECT id FROM esrv_mailbox_messages WHERE mailbox_id = ? AND created_modseq > ?`, mailboxID, sinceState)
if err != nil {
return out, err
}
if out.Created, err = scanInt64Rows(createdRows); err != nil {
return out, err
}
updatedRows, err := d.Query(`SELECT id FROM esrv_mailbox_messages WHERE mailbox_id = ? AND modseq > ? AND created_modseq <= ?`, mailboxID, sinceState, sinceState)
if err != nil {
return out, err
}
if out.Updated, err = scanInt64Rows(updatedRows); err != nil {
return out, err
}
destroyedRows, err := d.Query(`SELECT message_id FROM esrv_mailbox_message_tombstones WHERE mailbox_id = ? AND modseq > ?`, mailboxID, sinceState)
if err != nil {
return out, err
}
if out.Destroyed, err = scanInt64Rows(destroyedRows); err != nil {
return out, err
}
out.NewState, err = d.MessagesState(mailboxID)
return out, err
}
// FolderChanges is one Mailbox/changes result (internal/jmap). No destroyed list: a
// JMAP Mailbox/set destroy reparents under Trash (db.MoveFolderToTrash), the row never
// disappears — see schema.go's comment on esrv_mailbox_folders.modseq.
type FolderChanges struct {
Created []int64
Updated []int64
NewState string
HasMore bool
}
func (d *DB) FolderChangesSince(mailboxID, sinceState int64) (FolderChanges, error) {
var out FolderChanges
createdRows, err := d.Query(`SELECT id FROM esrv_mailbox_folders WHERE mailbox_id = ? AND created_modseq > ?`, mailboxID, sinceState)
if err != nil {
return out, err
}
if out.Created, err = scanInt64Rows(createdRows); err != nil {
return out, err
}
updatedRows, err := d.Query(`SELECT id FROM esrv_mailbox_folders WHERE mailbox_id = ? AND modseq > ? AND created_modseq <= ?`, mailboxID, sinceState, sinceState)
if err != nil {
return out, err
}
if out.Updated, err = scanInt64Rows(updatedRows); err != nil {
return out, err
}
out.NewState, err = d.FoldersState(mailboxID)
return out, err
}