mirror of
https://github.com/tulir/gomuks.git
synced 2025-04-20 10:33:41 -05:00
135 lines
4.4 KiB
Go
135 lines
4.4 KiB
Go
// Copyright (c) 2024 Tulir Asokan
|
|
//
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
package database
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"sync"
|
|
|
|
"go.mau.fi/util/dbutil"
|
|
"maunium.net/go/mautrix/id"
|
|
)
|
|
|
|
const (
|
|
clearTimelineQuery = `
|
|
DELETE FROM timeline WHERE room_id = $1
|
|
`
|
|
appendTimelineQuery = `
|
|
INSERT INTO timeline (room_id, event_rowid) VALUES ($1, $2)
|
|
ON CONFLICT DO NOTHING
|
|
RETURNING rowid, event_rowid
|
|
`
|
|
prependTimelineQuery = `
|
|
INSERT INTO timeline (room_id, rowid, event_rowid) VALUES ($1, $2, $3)
|
|
`
|
|
checkTimelineContainsQuery = `
|
|
SELECT EXISTS(SELECT 1 FROM timeline WHERE room_id = $1 AND event_rowid = $2)
|
|
`
|
|
findMinRowIDQuery = `SELECT MIN(rowid) FROM timeline`
|
|
getTimelineQuery = `
|
|
SELECT event.rowid, timeline.rowid,
|
|
event.room_id, event_id, sender, type, state_key, timestamp, content, decrypted, decrypted_type,
|
|
unsigned, local_content, transaction_id, redacted_by, relates_to, relation_type,
|
|
megolm_session_id, decryption_error, send_error, reactions, last_edit_rowid, unread_type
|
|
FROM timeline
|
|
JOIN event ON event.rowid = timeline.event_rowid
|
|
WHERE timeline.room_id = $1 AND ($2 = 0 OR timeline.rowid < $2)
|
|
ORDER BY timeline.rowid DESC
|
|
LIMIT $3
|
|
`
|
|
)
|
|
|
|
type TimelineRowID int64
|
|
|
|
type TimelineRowTuple struct {
|
|
Timeline TimelineRowID `json:"timeline_rowid"`
|
|
Event EventRowID `json:"event_rowid"`
|
|
}
|
|
|
|
var timelineRowTupleScanner = dbutil.ConvertRowFn[TimelineRowTuple](func(row dbutil.Scannable) (trt TimelineRowTuple, err error) {
|
|
err = row.Scan(&trt.Timeline, &trt.Event)
|
|
return
|
|
})
|
|
|
|
func (trt TimelineRowTuple) GetMassInsertValues() [2]any {
|
|
return [2]any{trt.Timeline, trt.Event}
|
|
}
|
|
|
|
var appendTimelineQueryBuilder = dbutil.NewMassInsertBuilder[EventRowID, [1]any](appendTimelineQuery, "($1, $%d)")
|
|
var prependTimelineQueryBuilder = dbutil.NewMassInsertBuilder[TimelineRowTuple, [1]any](prependTimelineQuery, "($1, $%d, $%d)")
|
|
|
|
type TimelineQuery struct {
|
|
*dbutil.QueryHelper[*Event]
|
|
|
|
minRowID TimelineRowID
|
|
minRowIDFound bool
|
|
prependLock sync.Mutex
|
|
}
|
|
|
|
// Clear clears the timeline of a given room.
|
|
func (tq *TimelineQuery) Clear(ctx context.Context, roomID id.RoomID) error {
|
|
return tq.Exec(ctx, clearTimelineQuery, roomID)
|
|
}
|
|
|
|
func (tq *TimelineQuery) reserveRowIDs(ctx context.Context, count int) (startFrom TimelineRowID, err error) {
|
|
tq.prependLock.Lock()
|
|
defer tq.prependLock.Unlock()
|
|
if !tq.minRowIDFound {
|
|
err = tq.GetDB().QueryRow(ctx, findMinRowIDQuery).Scan(&tq.minRowID)
|
|
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
|
return
|
|
}
|
|
if tq.minRowID >= 0 {
|
|
// No negative row IDs exist, start at -2
|
|
tq.minRowID = -2
|
|
} else {
|
|
// We fetched the lowest row ID, but we want the next available one, so decrement one
|
|
tq.minRowID--
|
|
}
|
|
tq.minRowIDFound = true
|
|
}
|
|
startFrom = tq.minRowID
|
|
tq.minRowID -= TimelineRowID(count)
|
|
return
|
|
}
|
|
|
|
// Prepend adds the given event row IDs to the beginning of the timeline.
|
|
// The events must be sorted in reverse chronological order (newest event first).
|
|
func (tq *TimelineQuery) Prepend(ctx context.Context, roomID id.RoomID, rowIDs []EventRowID) (prependEntries []TimelineRowTuple, err error) {
|
|
var startFrom TimelineRowID
|
|
startFrom, err = tq.reserveRowIDs(ctx, len(rowIDs))
|
|
if err != nil {
|
|
return
|
|
}
|
|
prependEntries = make([]TimelineRowTuple, len(rowIDs))
|
|
for i, rowID := range rowIDs {
|
|
prependEntries[i] = TimelineRowTuple{
|
|
Timeline: startFrom - TimelineRowID(i),
|
|
Event: rowID,
|
|
}
|
|
}
|
|
query, params := prependTimelineQueryBuilder.Build([1]any{roomID}, prependEntries)
|
|
err = tq.Exec(ctx, query, params...)
|
|
return
|
|
}
|
|
|
|
// Append adds the given event row IDs to the end of the timeline.
|
|
func (tq *TimelineQuery) Append(ctx context.Context, roomID id.RoomID, rowIDs []EventRowID) ([]TimelineRowTuple, error) {
|
|
query, params := appendTimelineQueryBuilder.Build([1]any{roomID}, rowIDs)
|
|
return timelineRowTupleScanner.NewRowIter(tq.GetDB().Query(ctx, query, params...)).AsList()
|
|
}
|
|
|
|
func (tq *TimelineQuery) Get(ctx context.Context, roomID id.RoomID, limit int, before TimelineRowID) ([]*Event, error) {
|
|
return tq.QueryMany(ctx, getTimelineQuery, roomID, before, limit)
|
|
}
|
|
|
|
func (tq *TimelineQuery) Has(ctx context.Context, roomID id.RoomID, eventRowID EventRowID) (exists bool, err error) {
|
|
err = tq.GetDB().QueryRow(ctx, checkTimelineContainsQuery, roomID, eventRowID).Scan(&exists)
|
|
return
|
|
}
|