fix p2p topic canonicalization and stale subscriptions

This commit is contained in:
richjohnrambo
2026-09-12 00:08:30 +08:00
parent f1dfffcfb5
commit a9a4970d9f
9 changed files with 148 additions and 66 deletions
+100
View File
@@ -1111,6 +1111,9 @@ func (a *adapter) UpgradeDb() error {
if err := a.ensureMessageTopicSeqIndex(); err != nil {
return err
}
if err := a.ensureTopicNameUniqueIndex(); err != nil {
return err
}
if err := a.ensureTopicSeqMatchesMessages(); err != nil {
return err
}
@@ -1139,6 +1142,97 @@ func (a *adapter) ensureMessageTopicSeqIndex() error {
return err
}
func (a *adapter) ensureTopicNameUniqueIndex() error {
ctx := context.Background()
conn, err := a.db.Connx(ctx)
if err != nil {
return err
}
defer conn.Close()
tenantScoped, err := identityColumnExists(ctx, conn, "im_topics", "tenant_id")
if err != nil {
return err
}
var count int
if tenantScoped {
if err = conn.QueryRowContext(ctx,
`SELECT COUNT(*) FROM information_schema.statistics
WHERE table_schema=DATABASE() AND table_name='im_topics' AND index_name='uk_topics_tenant_name' AND non_unique=0`,
).Scan(&count); err != nil {
return err
}
if count > 0 {
return nil
}
if _, err = conn.ExecContext(ctx, `UPDATE im_topics AS keep_t
JOIN (
SELECT tenant_id,name,MIN(id) AS keep_id,
MAX(seqid) AS seqid,MAX(delid) AS delid,
MAX(COALESCE(touchedat,'1000-01-01')) AS touchedat,
MAX(updatedat) AS updatedat
FROM im_topics GROUP BY tenant_id,name HAVING COUNT(*)>1
) AS dup ON dup.keep_id=keep_t.id
SET keep_t.seqid=GREATEST(keep_t.seqid,dup.seqid),
keep_t.delid=GREATEST(keep_t.delid,dup.delid),
keep_t.touchedat=NULLIF(GREATEST(COALESCE(keep_t.touchedat,'1000-01-01'),dup.touchedat),'1000-01-01'),
keep_t.updatedat=GREATEST(keep_t.updatedat,dup.updatedat)`); err != nil {
return err
}
if _, err = conn.ExecContext(ctx, `DELETE t FROM im_topics AS t
JOIN (
SELECT tenant_id,name,MIN(id) AS keep_id
FROM im_topics GROUP BY tenant_id,name HAVING COUNT(*)>1
) AS dup ON dup.tenant_id=t.tenant_id AND dup.name=t.name AND t.id<>dup.keep_id`); err != nil {
return err
}
_, err = conn.ExecContext(ctx, "ALTER TABLE im_topics ADD UNIQUE INDEX uk_topics_tenant_name(tenant_id, name)")
if isDupe(err) {
return nil
}
return err
}
if err = conn.QueryRowContext(ctx,
`SELECT COUNT(*) FROM information_schema.statistics
WHERE table_schema=DATABASE() AND table_name='im_topics' AND index_name='topics_name' AND non_unique=0`,
).Scan(&count); err != nil {
return err
}
if count > 0 {
return nil
}
if _, err = conn.ExecContext(ctx, `UPDATE im_topics AS keep_t
JOIN (
SELECT name,MIN(id) AS keep_id,
MAX(seqid) AS seqid,MAX(delid) AS delid,
MAX(COALESCE(touchedat,'1000-01-01')) AS touchedat,
MAX(updatedat) AS updatedat
FROM im_topics GROUP BY name HAVING COUNT(*)>1
) AS dup ON dup.keep_id=keep_t.id
SET keep_t.seqid=GREATEST(keep_t.seqid,dup.seqid),
keep_t.delid=GREATEST(keep_t.delid,dup.delid),
keep_t.touchedat=NULLIF(GREATEST(COALESCE(keep_t.touchedat,'1000-01-01'),dup.touchedat),'1000-01-01'),
keep_t.updatedat=GREATEST(keep_t.updatedat,dup.updatedat)`); err != nil {
return err
}
if _, err = conn.ExecContext(ctx, `DELETE t FROM im_topics AS t
JOIN (
SELECT name,MIN(id) AS keep_id
FROM im_topics GROUP BY name HAVING COUNT(*)>1
) AS dup ON dup.name=t.name AND t.id<>dup.keep_id`); err != nil {
return err
}
if _, err = conn.ExecContext(ctx, "ALTER TABLE im_topics ADD UNIQUE INDEX topics_name(name)"); err != nil {
if isDupe(err) {
return nil
}
return err
}
return nil
}
func (a *adapter) ensureTopicSeqMatchesMessages() error {
ctx := context.Background()
conn, err := a.db.Connx(ctx)
@@ -3224,6 +3318,7 @@ func (a *adapter) TopicsForUser(uid t.Uid, keepDeleted bool, opts *t.QueryOpt) (
// Fetch subscriptions. Two queries are needed: users table (p2p) and topics table (grp).
// Prepare a list of separate subscriptions to users vs topics
join := make(map[string]t.Subscription) // Keeping these to make a join with table for .private and .access
p2pResolved := make(map[string]bool)
topq := make([]any, 0, 16)
usrq := make([]any, 0, 16)
for rows.Next() {
@@ -3242,6 +3337,7 @@ func (a *adapter) TopicsForUser(uid t.Uid, keepDeleted bool, opts *t.QueryOpt) (
} else if tcat == t.TopicCatP2P {
// P2P subscription, find the other user to get user.Public and user.Trusted.
uid1, uid2, _ := t.ParseP2P(tname)
p2pResolved[tname] = false
if uid1 == uid {
usrq = append(usrq, store.DecodeUid(uid2))
sub.SetWith(uid2.UserId())
@@ -3374,6 +3470,7 @@ func (a *adapter) TopicsForUser(uid t.Uid, keepDeleted bool, opts *t.QueryOpt) (
sub.SetDefaultAccess(usr2.Access.Auth, usr2.Access.Anon)
sub.SetLastSeenAndUA(usr2.lastSeen(), usr2.UserAgent)
join[joinOn] = sub
p2pResolved[joinOn] = true
}
}
if err == nil {
@@ -3388,6 +3485,9 @@ func (a *adapter) TopicsForUser(uid t.Uid, keepDeleted bool, opts *t.QueryOpt) (
subs = make([]t.Subscription, 0, len(join))
for _, sub := range join {
if t.GetTopicCat(sub.Topic) == t.TopicCatP2P && !p2pResolved[sub.Topic] {
continue
}
subs = append(subs, sub)
}
+14 -2
View File
@@ -942,6 +942,7 @@ func (a *adapter) TenantTopicsForUser(id t.TenantID, uid t.Uid, keepDeleted bool
return nil, err
}
join := map[string]t.Subscription{}
p2pResolved := map[string]bool{}
var topq, usrq []any
for rows.Next() {
var sub t.Subscription
@@ -956,6 +957,7 @@ func (a *adapter) TenantTopicsForUser(id t.TenantID, uid t.Uid, keepDeleted bool
continue
case t.TopicCatP2P:
uid1, uid2, _ := t.ParseP2P(tname)
p2pResolved[tname] = false
if uid1 == uid {
usrq = append(usrq, decodeUID(uid2))
sub.SetWith(uid2.UserId())
@@ -1047,6 +1049,7 @@ func (a *adapter) TenantTopicsForUser(id t.TenantID, uid t.Uid, keepDeleted bool
sub.SetDefaultAccess(user.Access.Auth, user.Access.Anon)
sub.SetLastSeenAndUA(user.lastSeen(), user.UserAgent)
join[joinOn] = sub
p2pResolved[joinOn] = true
}
}
err = rows.Err()
@@ -1057,6 +1060,9 @@ func (a *adapter) TenantTopicsForUser(id t.TenantID, uid t.Uid, keepDeleted bool
}
subs := make([]t.Subscription, 0, len(join))
for _, sub := range join {
if t.GetTopicCat(sub.Topic) == t.TopicCatP2P && !p2pResolved[sub.Topic] {
continue
}
subs = append(subs, sub)
}
return common.SelectEarliestUpdatedSubs(subs, opts, a.maxResults), nil
@@ -9637,10 +9643,16 @@ func (a *adapter) TenantFriendshipDeletePair(id t.TenantID, uid1, uid2 t.Uid, no
if cancel != nil {
defer cancel()
}
_, err := a.db.ExecContext(ctx,
res, err := a.db.ExecContext(ctx,
"UPDATE im_friendships SET status=?,updated_at=? WHERE (user_id=? AND peer_id=?) OR (user_id=? AND peer_id=?)",
t.RelationshipNone, now, decodeUID(uid1), decodeUID(uid2), decodeUID(uid2), decodeUID(uid1))
return err
if err != nil {
return err
}
if affected, err := res.RowsAffected(); err == nil && affected == 0 {
return t.ErrNotFound
}
return nil
}
func (a *adapter) TenantUserBlockGet(id t.TenantID, blockerID, blockedID t.Uid) (*t.UserBlock, error) {