feat: complete shared database tenant sharding

This commit is contained in:
richjohnrambo
2026-09-23 02:19:07 +08:00
parent 1ec2feee47
commit e37b52c3c0
37 changed files with 946 additions and 1420 deletions
+119 -210
View File
@@ -322,11 +322,11 @@ func messageHeadBool(head t.KVMap, key string) bool {
func (a *adapter) tenantTopicBumpMutationVersionTx(ctx context.Context, tx *sqlx.Tx, id t.TenantID, topic string, at time.Time) error {
if a.tenantScopedDB {
_, err := tx.ExecContext(ctx, "UPDATE im_topics SET mutation_version=mutation_version+1,updatedat=?,touchedat=? WHERE name=?",
_, err := tx.ExecContext(ctx, tenantShardSQL(id, "UPDATE im_topics SET mutation_version=mutation_version+1,updatedat=?,touchedat=? WHERE name=?"),
at, at, topic)
return err
}
_, err := tx.ExecContext(ctx, "UPDATE im_topics SET mutation_version=mutation_version+1,updatedat=?,touchedat=? WHERE tenant_id=? AND name=?",
_, err := tx.ExecContext(ctx, tenantShardSQL(id, "UPDATE im_topics SET mutation_version=mutation_version+1,updatedat=?,touchedat=? WHERE tenant_id=? AND name=?"),
at, at, id, topic)
return err
}
@@ -634,7 +634,7 @@ func (a *adapter) TenantUserDelete(id t.TenantID, uid t.Uid, hard bool) error {
}()
var ownTopics []string
q := "SELECT name FROM im_topics WHERE tenant_id=? AND owner=?"
q := tenantShardSQL(id, "SELECT name FROM im_topics WHERE tenant_id=? AND owner=?")
args := []any{id, decoded}
if !hard {
q += " AND state!=?"
@@ -663,20 +663,20 @@ func (a *adapter) TenantUserDelete(id t.TenantID, uid t.Uid, hard bool) error {
return err
}
if len(ownTopics) > 0 {
if _, err = tx.Exec("DELETE dl FROM im_dellog AS dl JOIN im_topics AS tp ON tp.tenant_id=dl.tenant_id AND tp.name=dl.topic WHERE tp.tenant_id=? AND tp.owner=?", id, decoded); err != nil {
if _, err = tx.Exec(tenantShardSQL(id, "DELETE dl FROM im_dellog AS dl JOIN im_topics AS tp ON tp.tenant_id=dl.tenant_id AND tp.name=dl.topic WHERE tp.tenant_id=? AND tp.owner=?"), id, decoded); err != nil {
return err
}
if _, err = tx.Exec(fmt.Sprintf("DELETE m FROM %s AS m JOIN im_topics AS tp ON tp.tenant_id=m.tenant_id AND tp.name=m.topic WHERE tp.tenant_id=? AND tp.owner=?", tenantMessagesTable(id)), id, decoded); err != nil {
if _, err = tx.Exec(tenantShardSQL(id, fmt.Sprintf("DELETE m FROM %s AS m JOIN im_topics AS tp ON tp.tenant_id=m.tenant_id AND tp.name=m.topic WHERE tp.tenant_id=? AND tp.owner=?", tenantMessagesTable(id))), id, decoded); err != nil {
return err
}
sqlq, params, _ := sqlx.In("DELETE FROM im_subscriptions WHERE tenant_id=? AND topic IN (?)", id, ownAll)
sqlq, params, _ := sqlx.In(tenantShardSQL(id, "DELETE FROM im_subscriptions WHERE tenant_id=? AND topic IN (?)"), id, ownAll)
if _, err = tx.Exec(tx.Rebind(sqlq), params...); err != nil {
return err
}
if _, err = tx.Exec("DELETE tt FROM im_topictags AS tt JOIN im_topics AS tp ON tp.tenant_id=tt.tenant_id AND tp.name=tt.topic WHERE tp.tenant_id=? AND tp.owner=?", id, decoded); err != nil {
if _, err = tx.Exec(tenantShardSQL(id, "DELETE tt FROM im_topictags AS tt JOIN im_topics AS tp ON tp.tenant_id=tt.tenant_id AND tp.name=tt.topic WHERE tp.tenant_id=? AND tp.owner=?"), id, decoded); err != nil {
return err
}
if _, err = tx.Exec("DELETE FROM im_topics WHERE tenant_id=? AND owner=?", id, decoded); err != nil {
if _, err = tx.Exec(tenantShardSQL(id, "DELETE FROM im_topics WHERE tenant_id=? AND owner=?"), id, decoded); err != nil {
return err
}
}
@@ -694,23 +694,23 @@ func (a *adapter) TenantUserDelete(id t.TenantID, uid t.Uid, hard bool) error {
return err
}
if len(ownAll) > 0 {
sqlq, params, _ := sqlx.In("UPDATE im_subscriptions SET updatedat=?,deletedat=? WHERE tenant_id=? AND topic IN (?)", now, now, id, ownAll)
sqlq, params, _ := sqlx.In(tenantShardSQL(id, "UPDATE im_subscriptions SET updatedat=?,deletedat=? WHERE tenant_id=? AND topic IN (?)"), now, now, id, ownAll)
if _, err = tx.Exec(tx.Rebind(sqlq), params...); err != nil {
return err
}
}
if _, err = tx.Exec("UPDATE im_topics SET updatedat=?,touchedat=?,state=?,stateat=? WHERE tenant_id=? AND owner=?",
if _, err = tx.Exec(tenantShardSQL(id, "UPDATE im_topics SET updatedat=?,touchedat=?,state=?,stateat=? WHERE tenant_id=? AND owner=?"),
now, now, t.StateDeleted, now, id, decoded); err != nil {
return err
}
if _, err = tx.Exec("UPDATE im_topics AS tp JOIN im_subscriptions AS s ON tp.tenant_id=s.tenant_id AND tp.name=s.topic "+
if _, err = tx.Exec(tenantShardSQL(id, "UPDATE im_topics AS tp JOIN im_subscriptions AS s ON tp.tenant_id=s.tenant_id AND tp.name=s.topic "+
"SET tp.updatedat=?,tp.touchedat=?,tp.state=?,tp.stateat=? WHERE tp.tenant_id=? AND tp.owner=0 AND s.userid=? AND tp.name LIKE 'p2p%'",
now, now, t.StateDeleted, now, id, decoded); err != nil {
), now, now, t.StateDeleted, now, id, decoded); err != nil {
return err
}
if _, err = tx.Exec("UPDATE im_subscriptions AS s_one JOIN im_subscriptions AS s_two ON s_one.tenant_id=s_two.tenant_id AND s_one.topic=s_two.topic "+
if _, err = tx.Exec(tenantShardSQL(id, "UPDATE im_subscriptions AS s_one JOIN im_subscriptions AS s_two ON s_one.tenant_id=s_two.tenant_id AND s_one.topic=s_two.topic "+
"SET s_two.updatedat=?,s_two.deletedat=? WHERE s_one.tenant_id=? AND s_one.userid=? AND s_one.topic LIKE 'p2p%'",
now, now, id, decoded); err != nil {
), now, now, id, decoded); err != nil {
return err
}
if _, err = tx.Exec("UPDATE im_users SET updatedat=?,state=?,stateat=? WHERE tenant_id=? AND id=?",
@@ -729,13 +729,13 @@ func (a *adapter) tenantTopicStateForUser(tx *sqlx.Tx, tenantID t.TenantID, deco
if now.IsZero() {
now = t.TimeNow()
}
if _, err := tx.Exec("UPDATE im_topics SET state=?,stateat=? WHERE tenant_id=? AND owner=? AND state!=?",
if _, err := tx.Exec(tenantShardSQL(tenantID, "UPDATE im_topics SET state=?,stateat=? WHERE tenant_id=? AND owner=? AND state!=?"),
objState, now, tenantID, decodedUID, t.StateDeleted); err != nil {
return err
}
_, err := tx.Exec("UPDATE im_topics AS tp JOIN im_subscriptions AS s ON tp.tenant_id=s.tenant_id AND tp.name=s.topic "+
_, err := tx.Exec(tenantShardSQL(tenantID, "UPDATE im_topics AS tp JOIN im_subscriptions AS s ON tp.tenant_id=s.tenant_id AND tp.name=s.topic "+
"SET tp.state=?,tp.stateat=? WHERE tp.tenant_id=? AND tp.owner=0 AND s.userid=? AND tp.state!=?",
objState, now, tenantID, decodedUID, t.StateDeleted)
), objState, now, tenantID, decodedUID, t.StateDeleted)
return err
}
@@ -822,10 +822,10 @@ func (a *adapter) TenantUserUnreadCount(id t.TenantID, uids ...t.Uid) (map[t.Uid
if len(decoded) == 0 {
return counts, nil
}
q, args, _ := sqlx.In("SELECT s.userid,SUM(tp.seqid)-SUM(s.readseqid) AS unreadcount "+
q, args, _ := sqlx.In(tenantShardSQL(id, "SELECT s.userid,SUM(tp.seqid)-SUM(s.readseqid) AS unreadcount "+
"FROM im_topics AS tp JOIN im_subscriptions AS s ON tp.tenant_id=s.tenant_id AND tp.name=s.topic "+
"WHERE s.tenant_id=? AND s.userid IN (?) AND s.deletedat IS NULL AND tp.state!=? "+
"AND INSTR(s.modewant,'R')>0 AND INSTR(s.modegiven,'R')>0 GROUP BY s.userid", id, decoded, t.StateDeleted)
"AND INSTR(s.modewant,'R')>0 AND INSTR(s.modegiven,'R')>0 GROUP BY s.userid"), id, decoded, t.StateDeleted)
ctx, cancel := a.getContext()
if cancel != nil {
defer cancel()
@@ -886,9 +886,9 @@ func tenantTopicCreate(tx *sqlx.Tx, topic *t.Topic) error {
if topic == nil || topic.TenantID.IsZero() {
return t.ErrMalformed
}
_, err := tx.Exec("INSERT INTO im_topics(tenant_id,createdat,updatedat,touchedat,state,name,usebt,owner,access,public,trusted,tags,aux) "+
_, err := tx.Exec(tenantShardSQL(topic.TenantID, "INSERT INTO im_topics(tenant_id,createdat,updatedat,touchedat,state,name,usebt,owner,access,public,trusted,tags,aux) "+
"VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)",
topic.TenantID, topic.CreatedAt, topic.UpdatedAt, topic.TouchedAt, topic.State, topic.Id, topic.UseBt,
), topic.TenantID, topic.CreatedAt, topic.UpdatedAt, topic.TouchedAt, topic.State, topic.Id, topic.UseBt,
decodeUID(t.ParseUid(topic.Owner)), topic.Access, common.ToJSON(topic.Public), common.ToJSON(topic.Trusted),
topic.Tags, common.ToJSON(topic.Aux))
if err != nil {
@@ -913,25 +913,25 @@ func createTenantSubscription(tx *sqlx.Tx, tenantID t.TenantID, sub *t.Subscript
DeletedAt sql.NullTime `db:"deletedat"`
}{}
err := tx.Get(&existing,
"SELECT id,deletedat FROM im_subscriptions WHERE tenant_id=? AND topic=? AND userid=? ORDER BY deletedat IS NULL DESC,id ASC LIMIT 1",
tenantShardSQL(tenantID, "SELECT id,deletedat FROM im_subscriptions WHERE tenant_id=? AND topic=? AND userid=? ORDER BY deletedat IS NULL DESC,id ASC LIMIT 1"),
tenantID, sub.Topic, decodedUID)
if err != nil && err != sql.ErrNoRows {
return err
}
if err == nil {
if existing.DeletedAt.Valid {
_, err = tx.Exec("UPDATE im_subscriptions SET createdat=?,updatedat=?,deletedat=NULL,modeWant=?,modeGiven=?,"+
_, err = tx.Exec(tenantShardSQL(tenantID, "UPDATE im_subscriptions SET createdat=?,updatedat=?,deletedat=NULL,modeWant=?,modeGiven=?,"+
"delid=0,recvseqid=0,readseqid=0,private=? WHERE tenant_id=? AND id=?",
sub.CreatedAt, sub.UpdatedAt, sub.ModeWant.String(), sub.ModeGiven.String(), jpriv, tenantID, existing.ID)
), sub.CreatedAt, sub.UpdatedAt, sub.ModeWant.String(), sub.ModeGiven.String(), jpriv, tenantID, existing.ID)
} else {
_, err = tx.Exec("UPDATE im_subscriptions SET updatedat=?,modeWant=?,modeGiven=?,private=? WHERE tenant_id=? AND id=?",
_, err = tx.Exec(tenantShardSQL(tenantID, "UPDATE im_subscriptions SET updatedat=?,modeWant=?,modeGiven=?,private=? WHERE tenant_id=? AND id=?"),
sub.UpdatedAt, sub.ModeWant.String(), sub.ModeGiven.String(), jpriv, tenantID, existing.ID)
}
if err != nil {
return err
}
if isOwner {
_, err = tx.Exec("UPDATE im_topics SET owner=? WHERE tenant_id=? AND name=?", decodedUID, tenantID, sub.Topic)
_, err = tx.Exec(tenantShardSQL(tenantID, "UPDATE im_topics SET owner=? WHERE tenant_id=? AND name=?"), decodedUID, tenantID, sub.Topic)
}
return err
}
@@ -941,30 +941,30 @@ func createTenantSubscription(tx *sqlx.Tx, tenantID t.TenantID, sub *t.Subscript
return err
}
_, err = tx.Exec(
"INSERT INTO im_subscriptions(id,tenant_id,createdat,updatedat,deletedat,userid,topic,modeWant,modeGiven,private) "+
"VALUES(?,?,?,?,NULL,?,?,?,?,?)",
tenantShardSQL(tenantID, "INSERT INTO im_subscriptions(id,tenant_id,createdat,updatedat,deletedat,userid,topic,modeWant,modeGiven,private) "+
"VALUES(?,?,?,?,NULL,?,?,?,?,?)"),
subID, tenantID, sub.CreatedAt, sub.UpdatedAt, decodedUID, sub.Topic, sub.ModeWant.String(), sub.ModeGiven.String(), jpriv)
if isIDOutOfRange(err) {
_, err = tx.Exec(
"INSERT INTO im_subscriptions(tenant_id,createdat,updatedat,deletedat,userid,topic,modeWant,modeGiven,private) "+
"VALUES(?,?,?,NULL,?,?,?,?,?)",
tenantShardSQL(tenantID, "INSERT INTO im_subscriptions(tenant_id,createdat,updatedat,deletedat,userid,topic,modeWant,modeGiven,private) "+
"VALUES(?,?,?,NULL,?,?,?,?,?)"),
tenantID, sub.CreatedAt, sub.UpdatedAt, decodedUID, sub.Topic, sub.ModeWant.String(), sub.ModeGiven.String(), jpriv)
}
if err != nil && isDupe(err) {
if undelete {
_, err = tx.Exec("UPDATE im_subscriptions SET createdat=?,updatedat=?,deletedat=NULL,modeWant=?,modeGiven=?,"+
_, err = tx.Exec(tenantShardSQL(tenantID, "UPDATE im_subscriptions SET createdat=?,updatedat=?,deletedat=NULL,modeWant=?,modeGiven=?,"+
"delid=0,recvseqid=0,readseqid=0 WHERE tenant_id=? AND topic=? AND userid=?",
sub.CreatedAt, sub.UpdatedAt, sub.ModeWant.String(), sub.ModeGiven.String(), tenantID, sub.Topic, decodedUID)
), sub.CreatedAt, sub.UpdatedAt, sub.ModeWant.String(), sub.ModeGiven.String(), tenantID, sub.Topic, decodedUID)
} else {
_, err = tx.Exec("UPDATE im_subscriptions SET createdat=?,updatedat=?,deletedat=NULL,modeWant=?,modeGiven=?,"+
_, err = tx.Exec(tenantShardSQL(tenantID, "UPDATE im_subscriptions SET createdat=?,updatedat=?,deletedat=NULL,modeWant=?,modeGiven=?,"+
"delid=0,recvseqid=0,readseqid=0,private=? WHERE tenant_id=? AND topic=? AND userid=?",
sub.CreatedAt, sub.UpdatedAt, sub.ModeWant.String(), sub.ModeGiven.String(), jpriv, tenantID, sub.Topic, decodedUID)
), sub.CreatedAt, sub.UpdatedAt, sub.ModeWant.String(), sub.ModeGiven.String(), jpriv, tenantID, sub.Topic, decodedUID)
}
}
if err == nil && isOwner {
_, err = tx.Exec("UPDATE im_topics SET owner=? WHERE tenant_id=? AND name=?", decodedUID, tenantID, sub.Topic)
_, err = tx.Exec(tenantShardSQL(tenantID, "UPDATE im_topics SET owner=? WHERE tenant_id=? AND name=?"), decodedUID, tenantID, sub.Topic)
}
return err
}
@@ -1033,7 +1033,7 @@ func (a *adapter) TenantTopicCreateP2P(id t.TenantID, one, two *t.Subscription)
topic.ObjHeader.MergeTimes(&one.ObjHeader)
topic.TouchedAt = one.GetTouchedAt()
var existingTopicID int64
err = tx.Get(&existingTopicID, "SELECT id FROM im_topics WHERE tenant_id=? AND name=? ORDER BY id ASC LIMIT 1", id, topic.Id)
err = tx.Get(&existingTopicID, tenantShardSQL(id, "SELECT id FROM im_topics WHERE tenant_id=? AND name=? ORDER BY id ASC LIMIT 1"), id, topic.Id)
if err != nil && err != sql.ErrNoRows {
return err
}
@@ -1041,7 +1041,7 @@ func (a *adapter) TenantTopicCreateP2P(id t.TenantID, one, two *t.Subscription)
if err = tenantTopicCreate(tx, topic); err != nil {
return err
}
} else if _, err = tx.ExecContext(ctx, "UPDATE im_topics SET updatedat=?,touchedat=?,state=? WHERE tenant_id=? AND id=?",
} else if _, err = tx.ExecContext(ctx, tenantShardSQL(id, "UPDATE im_topics SET updatedat=?,touchedat=?,state=? WHERE tenant_id=? AND id=?"),
topic.UpdatedAt, topic.TouchedAt, t.StateOK, id, existingTopicID); err != nil {
return err
}
@@ -1067,7 +1067,7 @@ func (a *adapter) TenantTopicGet(id t.TenantID, topic string) (*t.Topic, error)
var top t.Topic
err = a.db.GetContext(ctx, &top,
"SELECT tenant_id,createdat,updatedat,state,stateat,touchedat,name AS id,usebt,access,owner,seqid,delid,subcnt,IFNULL(mutation_version,0) AS mutation_version,public,trusted,tags,aux "+
"FROM im_topics WHERE tenant_id=? AND name=?", id, topic)
tenantShardSQL(id, "FROM im_topics WHERE tenant_id=? AND name=?"), id, topic)
if err == sql.ErrNoRows {
return nil, nil
}
@@ -1077,13 +1077,13 @@ func (a *adapter) TenantTopicGet(id t.TenantID, topic string) (*t.Topic, error)
if t.GetTopicCat(topic) == t.TopicCatGrp {
var subCnt int
if err = a.db.GetContext(ctx, &subCnt,
"SELECT COUNT(*) FROM im_subscriptions WHERE tenant_id=? AND topic IN (?,?) AND deletedat IS NULL",
tenantShardSQL(id, "SELECT COUNT(*) FROM im_subscriptions WHERE tenant_id=? AND topic IN (?,?) AND deletedat IS NULL"),
id, topic, t.GrpToChn(topic)); err != nil {
return nil, err
}
if subCnt != top.SubCnt {
top.SubCnt = subCnt
if _, err = a.db.ExecContext(ctx, "UPDATE im_topics SET subcnt=? WHERE tenant_id=? AND name=?", subCnt, id, topic); err != nil {
if _, err = a.db.ExecContext(ctx, tenantShardSQL(id, "UPDATE im_topics SET subcnt=? WHERE tenant_id=? AND name=?"), subCnt, id, topic); err != nil {
return nil, err
}
}
@@ -1106,6 +1106,7 @@ func (a *adapter) TenantTopicsForUser(id t.TenantID, uid t.Uid, keepDeleted bool
}
q := `SELECT tenant_id,createdat,updatedat,deletedat,topic,delid,recvseqid,
readseqid,modewant,modegiven,private FROM im_subscriptions WHERE tenant_id=? AND userid=?`
q = tenantShardSQL(id, q)
args := []any{id, decodeUID(uid)}
if !keepDeleted {
q += " AND deletedat IS NULL"
@@ -1190,7 +1191,7 @@ func (a *adapter) TenantTopicsForUser(id t.TenantID, uid t.Uid, keepDeleted bool
return nil, err
}
if len(topq) > 0 {
q = "SELECT tenant_id,updatedat,state,touchedat,name AS id,usebt,access,seqid,delid,subcnt,IFNULL(mutation_version,0) AS mutation_version,public,trusted FROM im_topics WHERE tenant_id=? AND name IN (?)"
q = tenantShardSQL(id, "SELECT tenant_id,updatedat,state,touchedat,name AS id,usebt,access,seqid,delid,subcnt,IFNULL(mutation_version,0) AS mutation_version,public,trusted FROM im_topics WHERE tenant_id=? AND name IN (?)")
q, args, _ = sqlx.In(q, id, topq)
if !keepDeleted {
q += " AND state!=?"
@@ -1306,6 +1307,7 @@ func (a *adapter) TenantUsersForTopic(id t.TenantID, topic string, keepDeleted b
FROM im_subscriptions AS s JOIN im_users AS u ON s.tenant_id=u.tenant_id AND s.userid=u.id
LEFT JOIN im_user_profiles AS p ON p.tenant_id=s.tenant_id AND p.user_id=s.userid
WHERE s.tenant_id=? AND s.topic=?`
q = tenantShardSQL(id, q)
args := []any{id, topic}
if !keepDeleted {
q += " AND u.state!=?"
@@ -1426,7 +1428,7 @@ func (a *adapter) TenantOwnTopics(id t.TenantID, uid t.Uid) ([]string, error) {
if a.tenantScopedDB {
return a.OwnTopics(uid)
}
return a.tenantTopicNames("SELECT name FROM im_topics WHERE tenant_id=? AND owner=? AND state!=?",
return a.tenantTopicNames(tenantShardSQL(id, "SELECT name FROM im_topics WHERE tenant_id=? AND owner=? AND state!=?"),
id, decodeUID(uid), t.StateDeleted)
}
@@ -1441,8 +1443,8 @@ func (a *adapter) TenantChannelsForUser(id t.TenantID, uid t.Uid) ([]string, err
if a.tenantScopedDB {
return a.ChannelsForUser(uid)
}
return a.tenantTopicNames("SELECT topic FROM im_subscriptions WHERE tenant_id=? AND userid=? AND topic LIKE 'chn%' "+
"AND INSTR(modewant,'P')>0 AND INSTR(modegiven,'P')>0 AND deletedat IS NULL", id, decodeUID(uid))
return a.tenantTopicNames(tenantShardSQL(id, "SELECT topic FROM im_subscriptions WHERE tenant_id=? AND userid=? AND topic LIKE 'chn%' "+
"AND INSTR(modewant,'P')>0 AND INSTR(modegiven,'P')>0 AND deletedat IS NULL"), id, decodeUID(uid))
}
func (a *adapter) TenantTopicShare(id t.TenantID, topic string, subs []*t.Subscription) error {
@@ -1480,7 +1482,7 @@ func (a *adapter) TenantTopicShare(id t.TenantID, topic string, subs []*t.Subscr
}
}
if topic != "" {
if _, err = tx.Exec("UPDATE im_topics SET subcnt=subcnt+? WHERE tenant_id=? AND name=?", len(subs), id, topic); err != nil {
if _, err = tx.Exec(tenantShardSQL(id, "UPDATE im_topics SET subcnt=subcnt+? WHERE tenant_id=? AND name=?"), len(subs), id, topic); err != nil {
return err
}
}
@@ -1563,11 +1565,7 @@ func groupMessageDeleteList(tx *sqlx.Tx, tenantID t.TenantID, topic string, toDe
return err
}
}
if _, err = tx.Exec("DELETE FROM im_group_message_topic_shards WHERE tenant_id=? AND topic=?", tenantID, topic); err != nil {
return err
}
_, err = tx.Exec("DELETE FROM im_group_message_dedup WHERE tenant_id=? AND topic=?", tenantID, topic)
return err
return nil
}
delRanges := toDel.SeqIdRanges
if toDel.DeletedFor == "" {
@@ -1659,7 +1657,7 @@ func (a *adapter) TenantTopicDelete(id t.TenantID, topic string, isChan, hard bo
}
}()
if hard {
q, args, _ := sqlx.In("DELETE FROM im_subscriptions WHERE tenant_id=? AND topic IN (?)", id, topics)
q, args, _ := sqlx.In(tenantShardSQL(id, "DELETE FROM im_subscriptions WHERE tenant_id=? AND topic IN (?)"), id, topics)
if _, err = tx.Exec(tx.Rebind(q), args...); err != nil {
return err
}
@@ -1674,16 +1672,16 @@ func (a *adapter) TenantTopicDelete(id t.TenantID, topic string, isChan, hard bo
if _, err = tx.Exec("DELETE FROM im_topictags WHERE tenant_id=? AND topic=?", id, topic); err != nil {
return err
}
if _, err = tx.Exec("DELETE FROM im_topics WHERE tenant_id=? AND name=?", id, topic); err != nil {
if _, err = tx.Exec(tenantShardSQL(id, "DELETE FROM im_topics WHERE tenant_id=? AND name=?"), id, topic); err != nil {
return err
}
} else {
now := t.TimeNow()
q, args, _ := sqlx.In("UPDATE im_subscriptions SET updatedat=?,deletedat=? WHERE tenant_id=? AND topic IN (?)", now, now, id, topics)
q, args, _ := sqlx.In(tenantShardSQL(id, "UPDATE im_subscriptions SET updatedat=?,deletedat=? WHERE tenant_id=? AND topic IN (?)"), now, now, id, topics)
if _, err = tx.Exec(tx.Rebind(q), args...); err != nil {
return err
}
if _, err = tx.Exec("UPDATE im_topics SET updatedat=?,touchedat=?,state=?,stateat=? WHERE tenant_id=? AND name=?",
if _, err = tx.Exec(tenantShardSQL(id, "UPDATE im_topics SET updatedat=?,touchedat=?,state=?,stateat=? WHERE tenant_id=? AND name=?"),
now, now, t.StateDeleted, now, id, topic); err != nil {
return err
}
@@ -1706,7 +1704,7 @@ func (a *adapter) TenantTopicUpdateOnMessage(id t.TenantID, topic string, msg *t
if cancel != nil {
defer cancel()
}
_, err = a.db.ExecContext(ctx, "UPDATE im_topics SET seqid=?,touchedat=? WHERE tenant_id=? AND name=?",
_, err = a.db.ExecContext(ctx, tenantShardSQL(id, "UPDATE im_topics SET seqid=?,touchedat=? WHERE tenant_id=? AND name=?"),
msg.SeqId, msg.CreatedAt, id, topic)
return err
}
@@ -1727,7 +1725,7 @@ func (a *adapter) TenantTopicUpdateSubCnt(id t.TenantID, topic string) error {
defer cancel()
}
_, err = a.db.ExecContext(ctx,
"UPDATE im_topics SET subcnt=(SELECT COUNT(*) FROM im_subscriptions WHERE tenant_id=? AND topic IN (?,?) AND deletedat IS NULL) WHERE tenant_id=? AND name=?",
tenantShardSQL(id, "UPDATE im_topics SET subcnt=(SELECT COUNT(*) FROM im_subscriptions WHERE tenant_id=? AND topic IN (?,?) AND deletedat IS NULL) WHERE tenant_id=? AND name=?"),
id, topic, t.GrpToChn(topic), id, topic)
return err
}
@@ -1761,7 +1759,7 @@ func (a *adapter) TenantTopicUpdate(id t.TenantID, topic string, update map[stri
}
cols, args := common.UpdateByMap(update)
args = append(args, id, topic)
res, err := tx.Exec("UPDATE im_topics SET "+strings.Join(cols, ",")+" WHERE tenant_id=? AND name=?", args...)
res, err := tx.Exec(tenantShardSQL(id, "UPDATE im_topics SET "+strings.Join(cols, ",")+" WHERE tenant_id=? AND name=?"), args...)
if err != nil {
return err
}
@@ -1813,6 +1811,7 @@ func (a *adapter) TenantTopicBumpMutationVersion(id t.TenantID, topic string, at
query = "SELECT mutation_version FROM im_topics WHERE tenant_id=? AND name=?"
args = []any{id, topic}
}
query = tenantShardSQL(id, query)
if err = tx.GetContext(ctx, &version, query, args...); err != nil {
return 0, err
}
@@ -1837,7 +1836,7 @@ func (a *adapter) TenantTopicOwnerChange(id t.TenantID, topic string, owner t.Ui
if cancel != nil {
defer cancel()
}
_, err = a.db.ExecContext(ctx, "UPDATE im_topics SET owner=? WHERE tenant_id=? AND name=?", decodeUID(owner), id, topic)
_, err = a.db.ExecContext(ctx, tenantShardSQL(id, "UPDATE im_topics SET owner=? WHERE tenant_id=? AND name=?"), decodeUID(owner), id, topic)
return err
}
@@ -1858,6 +1857,7 @@ func (a *adapter) TenantSubscriptionGet(id t.TenantID, topic string, uid t.Uid,
}
query := `SELECT tenant_id,createdat,updatedat,deletedat,userid AS user,topic,delid,recvseqid,
readseqid,modewant,modegiven,private FROM im_subscriptions WHERE tenant_id=? AND topic=? AND userid=?`
query = tenantShardSQL(id, query)
if !keepDeleted {
query += " AND deletedat IS NULL"
}
@@ -1890,8 +1890,8 @@ func (a *adapter) TenantSubsForUser(id t.TenantID, uid t.Uid) ([]t.Subscription,
subs, err := a.SubsForUser(uid)
return stampTenantSubs(id, subs), err
}
rows, err := a.db.Queryx(`SELECT tenant_id,createdat,updatedat,deletedat,userid AS user,topic,delid,recvseqid,
readseqid,modewant,modegiven FROM im_subscriptions WHERE tenant_id=? AND userid=? AND deletedat IS NULL`,
rows, err := a.db.Queryx(tenantShardSQL(id, `SELECT tenant_id,createdat,updatedat,deletedat,userid AS user,topic,delid,recvseqid,
readseqid,modewant,modegiven FROM im_subscriptions WHERE tenant_id=? AND userid=? AND deletedat IS NULL`),
id, decodeUID(uid))
if err != nil {
return nil, err
@@ -1923,6 +1923,7 @@ func (a *adapter) TenantSubsForTopic(id t.TenantID, topic string, keepDeleted bo
}
q := `SELECT tenant_id,createdat,updatedat,deletedat,userid AS user,topic,delid,recvseqid,
readseqid,modewant,modegiven,private FROM im_subscriptions WHERE tenant_id=? AND topic=?`
q = tenantShardSQL(id, q)
args := []any{id, topic}
if !keepDeleted {
q += " AND deletedat IS NULL"
@@ -1976,7 +1977,7 @@ func (a *adapter) TenantSubsUpdate(id t.TenantID, topic string, uid t.Uid, updat
return a.SubsUpdate(topic, uid, update)
}
cols, args := common.UpdateByMap(update)
q := "UPDATE im_subscriptions SET " + strings.Join(cols, ",") + " WHERE tenant_id=? AND topic=?"
q := tenantShardSQL(id, "UPDATE im_subscriptions SET ") + strings.Join(cols, ",") + " WHERE tenant_id=? AND topic=?"
args = append(args, id, topic)
if !uid.IsZero() {
q += " AND userid=?"
@@ -2016,7 +2017,7 @@ func (a *adapter) TenantSubsDelete(id t.TenantID, topic string, uid t.Uid) error
}()
now := t.TimeNow()
decoded := decodeUID(uid)
res, err := tx.Exec("UPDATE im_subscriptions SET updatedat=?,deletedat=? WHERE tenant_id=? AND topic=? AND userid=? AND deletedat IS NULL",
res, err := tx.Exec(tenantShardSQL(id, "UPDATE im_subscriptions SET updatedat=?,deletedat=? WHERE tenant_id=? AND topic=? AND userid=? AND deletedat IS NULL"),
now, now, id, topic, decoded)
if err != nil {
return err
@@ -2030,7 +2031,7 @@ func (a *adapter) TenantSubsDelete(id t.TenantID, topic string, uid t.Uid) error
}
}
if t.GetTopicCat(topic) == t.TopicCatGrp {
if _, err = tx.Exec("UPDATE im_topics SET subcnt=subcnt-1 WHERE tenant_id=? AND name=?", id, t.ChnToGrp(topic)); err != nil {
if _, err = tx.Exec(tenantShardSQL(id, "UPDATE im_topics SET subcnt=subcnt-1 WHERE tenant_id=? AND name=?"), id, t.ChnToGrp(topic)); err != nil {
return err
}
}
@@ -2038,7 +2039,7 @@ func (a *adapter) TenantSubsDelete(id t.TenantID, topic string, uid t.Uid) error
}
func tenantSubsDelForUser(tx *sqlx.Tx, tenantID t.TenantID, decodedUID int64, hard bool) error {
rows, err := tx.Query("SELECT topic FROM im_subscriptions WHERE tenant_id=? AND userid=? AND deletedat IS NULL", tenantID, decodedUID)
rows, err := tx.Query(tenantShardSQL(tenantID, "SELECT topic FROM im_subscriptions WHERE tenant_id=? AND userid=? AND deletedat IS NULL"), tenantID, decodedUID)
if err != nil {
return err
}
@@ -2060,16 +2061,16 @@ func tenantSubsDelForUser(tx *sqlx.Tx, tenantID t.TenantID, decodedUID int64, ha
return err
}
if len(topics) > 0 {
q, args, _ := sqlx.In("UPDATE im_topics SET subcnt=subcnt-1 WHERE tenant_id=? AND name IN (?)", tenantID, topics)
q, args, _ := sqlx.In(tenantShardSQL(tenantID, "UPDATE im_topics SET subcnt=subcnt-1 WHERE tenant_id=? AND name IN (?)"), tenantID, topics)
if _, err = tx.Exec(tx.Rebind(q), args...); err != nil {
return err
}
}
if hard {
_, err = tx.Exec("DELETE FROM im_subscriptions WHERE tenant_id=? AND userid=?", tenantID, decodedUID)
_, err = tx.Exec(tenantShardSQL(tenantID, "DELETE FROM im_subscriptions WHERE tenant_id=? AND userid=?"), tenantID, decodedUID)
} else {
now := t.TimeNow()
_, err = tx.Exec("UPDATE im_subscriptions SET updatedat=?,deletedat=? WHERE tenant_id=? AND userid=? AND deletedat IS NULL",
_, err = tx.Exec(tenantShardSQL(tenantID, "UPDATE im_subscriptions SET updatedat=?,deletedat=? WHERE tenant_id=? AND userid=? AND deletedat IS NULL"),
now, now, tenantID, decodedUID)
}
return err
@@ -2111,6 +2112,7 @@ func (a *adapter) TenantFind(id t.TenantID, caller, promoPrefix string, req [][]
"FROM im_topics AS t JOIN im_topictags AS tg ON t.tenant_id=tg.tenant_id AND t.name=tg.topic " +
"WHERE t.tenant_id=? AND " + stateConstraint + "tg.tag IN (?" + strings.Repeat(",?", len(allTags)-1) + ") " +
"GROUP BY t.name,t.createdat,t.updatedat,t.usebt,t.access,t.subcnt,t.public,t.trusted,t.tags "
query = tenantShardSQL(id, query)
if len(allReq) > 0 {
q, addArgs := common.DisjunctionSql(req, "tg.tag")
query += q
@@ -2177,8 +2179,8 @@ func (a *adapter) TenantFindOne(id t.TenantID, tag string) (string, error) {
defer cancel()
}
rows, err := a.db.QueryxContext(ctx,
"SELECT tp.name AS topic FROM im_topics AS tp LEFT JOIN im_topictags AS tt ON tp.tenant_id=tt.tenant_id AND tp.name=tt.topic "+
"WHERE tp.tenant_id=? AND tt.tag=? LIMIT 1", id, tag)
tenantShardSQL(id, "SELECT tp.name AS topic FROM im_topics AS tp LEFT JOIN im_topictags AS tt ON tp.tenant_id=tt.tenant_id AND tp.name=tt.topic "+
"WHERE tp.tenant_id=? AND tt.tag=? LIMIT 1"), id, tag)
if err != nil {
return "", err
}
@@ -2273,17 +2275,6 @@ func (a *adapter) TenantGroupMessageSave(id t.TenantID, msg *t.Message) error {
}
defer tx.Rollback()
messageID := store.DecodeUid(msg.Uid())
if clientMsgID.Valid {
if _, err = tx.ExecContext(ctx, `INSERT INTO im_group_message_dedup
(tenant_id,topic,from_user,client_msg_id,seqid,shard_month,message_id,status,expires_at)
VALUES(?,?,?,?,?,?,?,'done',?)`, id, msg.Topic, decodeUID(t.ParseUid(msg.From)), clientMsgID,
msg.SeqId, msg.ShardMonth, messageID, time.Now().UTC().Add(30*24*time.Hour)); err != nil {
if isDupe(err) {
return t.ErrDuplicate
}
return err
}
}
query := fmt.Sprintf(`INSERT INTO %s
(tenant_id,message_id,topic,group_id,seqid,from_user,client_msg_id,msg_type,status,content,head,mentioned_user_ids,mention_all,
file_count,content_size,version,created_at,updated_at,content_nonce,key_version)
@@ -2298,21 +2289,6 @@ func (a *adapter) TenantGroupMessageSave(id t.TenantID, msg *t.Message) error {
if err != nil {
return err
}
if _, err = tx.ExecContext(ctx, `INSERT INTO im_group_message_topic_shards
(tenant_id,topic,shard_month,min_seq,max_seq,message_count,min_created_at,max_created_at)
VALUES(?,?,?,?,?,1,?,?) ON DUPLICATE KEY UPDATE
min_seq=LEAST(min_seq,VALUES(min_seq)),max_seq=GREATEST(max_seq,VALUES(max_seq)),
message_count=message_count+1,min_created_at=LEAST(min_created_at,VALUES(min_created_at)),
max_created_at=GREATEST(max_created_at,VALUES(max_created_at))`,
id, msg.Topic, msg.ShardMonth, msg.SeqId, msg.SeqId, msg.CreatedAt, msg.CreatedAt); err != nil {
return err
}
if _, err = tx.ExecContext(ctx, `UPDATE im_group_message_shards SET row_count=row_count+1,
min_created_at=IF(min_created_at IS NULL,?,LEAST(min_created_at,?)),
max_created_at=IF(max_created_at IS NULL,?,GREATEST(max_created_at,?)) WHERE tenant_id=? AND shard_month=?`,
msg.CreatedAt, msg.CreatedAt, msg.CreatedAt, msg.CreatedAt, id, msg.ShardMonth); err != nil {
return err
}
return tx.Commit()
}
@@ -2366,19 +2342,6 @@ func (a *adapter) TenantGroupMessageGetByClientID(id t.TenantID, topic string, f
if cancel != nil {
defer cancel()
}
var seq int64
if err = a.db.QueryRowContext(ctx, `SELECT seqid FROM im_group_message_dedup
WHERE tenant_id=? AND topic=? AND from_user=? AND client_msg_id=?`, id, topic, decodeUID(from), clientMsgID).Scan(&seq); err == nil {
msg, getErr := tenantGroupMessageGetOne(a.db, ctx, id, topic, seq, "")
if getErr == nil {
return msg, nil
}
if getErr != sql.ErrNoRows {
return nil, getErr
}
} else if err != sql.ErrNoRows {
return nil, err
}
tables, err := groupMessageTablesForTopic(a.db, ctx, id, topic)
if err != nil {
return nil, err
@@ -3111,14 +3074,14 @@ func (a *adapter) TenantMessageMutations(id t.TenantID, topic string, uid t.Uid,
}
var currentVersion int64
if a.tenantScopedDB {
if err := a.db.GetContext(ctx, &currentVersion, "SELECT IFNULL(mutation_version,0) FROM im_topics WHERE name=?", topic); err != nil {
if err := a.db.GetContext(ctx, &currentVersion, tenantShardSQL(id, "SELECT IFNULL(mutation_version,0) FROM im_topics WHERE name=?"), topic); err != nil {
if err == sql.ErrNoRows {
return nil, "", false, 0, t.ErrNotFound
}
return nil, "", false, 0, err
}
} else {
if err := a.db.GetContext(ctx, &currentVersion, "SELECT IFNULL(mutation_version,0) FROM im_topics WHERE tenant_id=? AND name=?", id, topic); err != nil {
if err := a.db.GetContext(ctx, &currentVersion, tenantShardSQL(id, "SELECT IFNULL(mutation_version,0) FROM im_topics WHERE tenant_id=? AND name=?"), id, topic); err != nil {
if err == sql.ErrNoRows {
return nil, "", false, 0, t.ErrNotFound
}
@@ -5261,7 +5224,7 @@ func (a *adapter) TenantFileUserHasAccess(id t.TenantID, fid string, uid t.Uid)
defer cancel()
}
var allowed int
err = a.db.GetContext(ctx, &allowed, fmt.Sprintf(`
err = a.db.GetContext(ctx, &allowed, tenantShardSQL(id, fmt.Sprintf(`
SELECT COUNT(*) FROM im_fileuploads AS fu
WHERE fu.tenant_id=? AND (fu.id=? OR fu.location=? OR fu.location LIKE ?) AND fu.status=? AND (
fu.userid=?
@@ -5310,7 +5273,7 @@ func (a *adapter) TenantFileUserHasAccess(id t.TenantID, fid string, uid t.Uid)
WHERE (gf.file_id=? OR gf.file_id LIKE CONCAT(?,'.%%')) AND gf.status='active'
AND s.deletedat IS NULL AND INSTR(s.modewant,'R')>0 AND INSTR(s.modegiven,'R')>0
)
) LIMIT 1`, tenantFriendshipsTable(id), tenantMessagesTable(id)),
) LIMIT 1`, tenantFriendshipsTable(id), tenantMessagesTable(id))),
id, decodeUID(fileID), fileID.String32(), "%/"+fileID.String32(), t.UploadCompleted, decodeUID(uid), decodeUID(uid), decodeUID(uid), t.RelationshipFriend,
decodeUID(uid), decodeUID(uid), t.GroupMemberStatusActive, decodeUID(uid), decodeUID(uid),
decodeUID(uid), fid, fid)
@@ -5379,33 +5342,14 @@ func (a *adapter) TenantVerificationCreate(id t.TenantID, vc *t.VerificationCode
return t.ErrMalformed
}
ctx, cancel := a.getContextForTx()
ctx, cancel := a.getContext()
if cancel != nil {
defer cancel()
}
tx, err := a.db.BeginTxx(ctx, nil)
if err != nil {
return err
}
defer func() {
if err != nil {
tx.Rollback()
}
}()
now := t.TimeNow()
_, err = tx.ExecContext(ctx,
`UPDATE im_verification_codes
SET state=?,updated_at=?
WHERE tenant_id=? AND scene=? AND phone_hash=? AND state=?`,
t.VerificationStateExpired, now, id, vc.Scene, vc.PhoneHash, t.VerificationStatePending)
if err != nil {
return err
}
vc.CreatedAt = now
vc.UpdatedAt = now
_, err = tx.ExecContext(ctx,
_, err := a.db.ExecContext(ctx,
`INSERT INTO im_verification_codes
(tenant_id,challenge_id,scene,action,phone_hash,phone_mask,target_user,code_hash,state,attempts,max_attempts,
expires_at,resend_after,created_at,updated_at)
@@ -5418,7 +5362,7 @@ func (a *adapter) TenantVerificationCreate(id t.TenantID, vc *t.VerificationCode
if err != nil {
return err
}
return tx.Commit()
return nil
}
func (a *adapter) TenantVerificationGet(id t.TenantID, challengeID string) (*t.VerificationCode, error) {
@@ -5854,23 +5798,10 @@ func (a *adapter) TenantActionTokenCreate(id t.TenantID, token *t.ActionToken) e
token.TokenID == "" || token.Action == "" || token.TokenHash == "" || token.ExpiresAt.IsZero() {
return t.ErrMalformed
}
ctx, cancel := a.getContext()
if cancel != nil {
defer cancel()
}
now := t.TimeNow()
token.CreatedAt = now
token.UpdatedAt = now
_, err := a.db.ExecContext(ctx,
`INSERT INTO im_action_tokens
(tenant_id,user_id,token_id,action,target,token_hash,state,expires_at,created_at,updated_at)
VALUES(?,?,?,?,?,?,?,?,?,?)`,
id, decodeUID(token.UserID), token.TokenID, token.Action, token.Target, token.TokenHash, token.State,
token.ExpiresAt, token.CreatedAt, token.UpdatedAt)
if isDupe(err) {
return t.ErrDuplicate
}
return err
return nil
}
func (a *adapter) TenantActionTokenConsume(id t.TenantID, uid t.Uid, token, action, target string, now time.Time) (*t.ActionToken, error) {
@@ -5893,50 +5824,7 @@ func (a *adapter) TenantActionTokenConsume(id t.TenantID, uid t.Uid, token, acti
if !ok || tokenID == "" || secret == "" {
return nil, t.ErrMalformed
}
tokenHash := securityTokenHash(id, uid, action, target, tokenID, secret)
ctx, cancel := a.getContextForTx()
if cancel != nil {
defer cancel()
}
tx, err := a.db.BeginTxx(ctx, nil)
if err != nil {
return nil, err
}
defer func() {
if err != nil {
tx.Rollback()
}
}()
var at t.ActionToken
err = tx.GetContext(ctx, &at,
`SELECT id,user_id,token_id,action,target,token_hash,state,expires_at,consumed_at,created_at,updated_at
FROM im_action_tokens
WHERE tenant_id=? AND user_id=? AND token_id=? AND action=? AND target=? LIMIT 1`,
id, decodeUID(uid), tokenID, action, target)
if err == sql.ErrNoRows {
return nil, t.ErrNotFound
}
if err != nil {
return nil, err
}
if at.State != 0 || at.TokenHash != tokenHash || !now.Before(at.ExpiresAt) {
return nil, t.ErrNotFound
}
res, err := tx.ExecContext(ctx,
`UPDATE im_action_tokens SET state=1,consumed_at=?,updated_at=?
WHERE tenant_id=? AND user_id=? AND token_id=? AND action=? AND target=? AND state=0 AND expires_at>?`,
now, now, id, decodeUID(uid), tokenID, action, target, now)
if err != nil {
return nil, err
}
if rows, _ := res.RowsAffected(); rows == 0 {
return nil, t.ErrNotFound
}
if err = tx.Commit(); err != nil {
return nil, err
}
at.TenantID = id
return &at, nil
return nil, t.ErrUnsupported
}
func (a *adapter) TenantIndependentPasswordGet(id t.TenantID, uid t.Uid, purpose string) (*t.IndependentPassword, error) {
@@ -6166,7 +6054,7 @@ func (a *adapter) TenantSystemNotificationCreate(id t.TenantID, notification *t.
notification.CreatedAt = now
notification.UpdatedAt = now
_, err := a.db.ExecContext(ctx,
`INSERT INTO im_system_notifications
tenantShardSQL(id, `INSERT INTO im_system_notifications
(tenant_id,user_id,event_id,event_type,title,summary,action,read_at,created_at,updated_at)
VALUES(?,?,?,?,?,?,?,?,?,?)
ON DUPLICATE KEY UPDATE
@@ -6174,7 +6062,7 @@ func (a *adapter) TenantSystemNotificationCreate(id t.TenantID, notification *t.
title=VALUES(title),
summary=VALUES(summary),
action=VALUES(action),
updated_at=VALUES(updated_at)`,
updated_at=VALUES(updated_at)`),
id, decodeUID(notification.UserID), notification.EventID, notification.EventType, notification.Title,
notification.Summary, common.ToJSON(notification.Action), notification.ReadAt, notification.CreatedAt, notification.UpdatedAt)
return err
@@ -6207,6 +6095,7 @@ func (a *adapter) TenantSystemNotificationList(id t.TenantID, uid t.Uid, cursor
query := `SELECT id,user_id,event_id,event_type,title,summary,action,read_at,created_at,updated_at
FROM im_system_notifications
WHERE tenant_id=? AND user_id=?`
query = tenantShardSQL(id, query)
args := []any{id, decodeUID(uid)}
if lastID > 0 {
query += " AND id<?"
@@ -6265,7 +6154,7 @@ func (a *adapter) TenantSystemNotificationUnreadCount(id t.TenantID, uid t.Uid)
}
var count int
if err := a.db.GetContext(ctx, &count,
`SELECT COUNT(*) FROM im_system_notifications WHERE tenant_id=? AND user_id=? AND read_at IS NULL`,
tenantShardSQL(id, `SELECT COUNT(*) FROM im_system_notifications WHERE tenant_id=? AND user_id=? AND read_at IS NULL`),
id, decodeUID(uid)); err != nil {
return 0, err
}
@@ -6292,25 +6181,25 @@ func (a *adapter) TenantSystemNotificationUnreadCountByEventPrefix(id t.TenantID
}
var count int
if err := a.db.GetContext(ctx, &count,
`SELECT COUNT(*) FROM im_system_notifications WHERE tenant_id=? AND user_id=? AND read_at IS NULL AND event_type LIKE ?`,
tenantShardSQL(id, `SELECT COUNT(*) FROM im_system_notifications WHERE tenant_id=? AND user_id=? AND read_at IS NULL AND event_type LIKE ?`),
id, decodeUID(uid), eventPrefix+"%"); err != nil {
return 0, err
}
return count, nil
}
func (a *adapter) TenantSystemNotificationMarkRead(id t.TenantID, uid t.Uid, eventID string, readAt time.Time) error {
func (a *adapter) TenantSystemNotificationMarkRead(id t.TenantID, uid t.Uid, eventID string, readAt time.Time) (*t.SystemNotification, error) {
if !a.tenantScopedDB {
tenantAdp, err := a.routedTenantAdapter(id)
if err != nil {
return err
return nil, err
}
if tenantAdp != a {
return tenantAdp.TenantSystemNotificationMarkRead(id, uid, eventID, readAt)
}
}
if err := validTenant(id); err != nil || uid.IsZero() || eventID == "" {
return t.ErrMalformed
return nil, t.ErrMalformed
}
if readAt.IsZero() {
readAt = t.TimeNow()
@@ -6319,18 +6208,38 @@ func (a *adapter) TenantSystemNotificationMarkRead(id t.TenantID, uid t.Uid, eve
if cancel != nil {
defer cancel()
}
res, err := a.db.ExecContext(ctx,
`UPDATE im_system_notifications
SET read_at=?,updated_at=?
WHERE tenant_id=? AND user_id=? AND event_id=?`,
_, err := a.db.ExecContext(ctx,
tenantShardSQL(id, `UPDATE im_system_notifications
SET read_at=COALESCE(read_at,?),updated_at=?
WHERE tenant_id=? AND user_id=? AND event_id=?`),
readAt, readAt, id, decodeUID(uid), eventID)
if err != nil {
return err
return nil, err
}
if rows, _ := res.RowsAffected(); rows == 0 {
return t.ErrNotFound
notification, err := a.tenantSystemNotificationGet(ctx, id, uid, eventID)
if err != nil {
return nil, err
}
return nil
return notification, nil
}
func (a *adapter) tenantSystemNotificationGet(ctx context.Context, id t.TenantID, uid t.Uid, eventID string) (*t.SystemNotification, error) {
var notification t.SystemNotification
err := a.db.QueryRowxContext(ctx, tenantShardSQL(id, `SELECT id,user_id,event_id,event_type,title,summary,action,read_at,created_at,updated_at
FROM im_system_notifications
WHERE tenant_id=? AND user_id=? AND event_id=?`),
id, decodeUID(uid), eventID).StructScan(&notification)
if err != nil {
if err == sql.ErrNoRows {
return nil, t.ErrNotFound
}
return nil, err
}
if action, ok := common.FromJSON(notification.Action).(map[string]any); ok {
notification.Action = t.KVMap(action)
}
notification.TenantID = id
return &notification, nil
}
func (a *adapter) TenantFriendRequestCreate(id t.TenantID, req *t.FriendRequest) (*t.FriendRequest, error) {
@@ -6705,10 +6614,10 @@ func (a *adapter) TenantFriendRequestAccept(id t.TenantID, requestID string, act
targetSub.SetTouchedAt(handledAt)
targetSub.SetPublic(applicantUser.Public)
targetSub.SetTrusted(applicantUser.Trusted)
if err = createSubscription(tx, applicantSub, true); err != nil {
if err = createTenantSubscription(tx, id, applicantSub, true); err != nil {
return nil, err
}
if err = createSubscription(tx, targetSub, true); err != nil {
if err = createTenantSubscription(tx, id, targetSub, true); err != nil {
return nil, err
}
topicRow := &t.Topic{TenantID: id, ObjHeader: t.ObjHeader{Id: topic, CreatedAt: handledAt, UpdatedAt: handledAt}, TouchedAt: handledAt}
@@ -6721,7 +6630,7 @@ func (a *adapter) TenantFriendRequestAccept(id t.TenantID, requestID string, act
if !isDupe(err) {
return nil, err
}
if _, err = tx.ExecContext(ctx, "UPDATE im_topics SET updatedat=?,touchedat=?,state=? WHERE tenant_id=? AND name=?",
if _, err = tx.ExecContext(ctx, tenantShardSQL(id, "UPDATE im_topics SET updatedat=?,touchedat=?,state=? WHERE tenant_id=? AND name=?"),
handledAt, handledAt, t.StateOK, id, topic); err != nil {
return nil, err
}