feat: shard group messages by month

This commit is contained in:
richjohnrambo
2026-09-15 23:53:51 +08:00
parent b7c2536527
commit 139681fadb
25 changed files with 1718 additions and 247 deletions
+284 -118
View File
@@ -270,6 +270,9 @@ func finalizeTenantMessage(id t.TenantID, msg t.Message, recalledBy sql.NullInt6
func normalizeGroupMessage(id t.TenantID, msg t.Message, recalledBy sql.NullInt64) t.Message {
msg.TenantID = id
if rawID, err := strconv.ParseInt(msg.Id, 10, 64); err == nil && rawID > 0 {
msg.SetUid(store.EncodeUid(rawID))
}
msg.From = common.EncodeUidString(msg.From).String()
if recalledBy.Valid {
msg.RecalledBy = common.EncodeUidString(strconv.FormatInt(recalledBy.Int64, 10)).String()
@@ -1390,44 +1393,70 @@ func tenantMessageDeleteList(tx *sqlx.Tx, tenantID t.TenantID, topic string, toD
return nil
}
func groupMessageDeleteList(tx *sqlx.Tx, topic string, toDel *t.DelMessage) error {
func groupMessageDeleteList(tx *sqlx.Tx, tenantID t.TenantID, topic string, toDel *t.DelMessage) error {
var err error
ctx := context.Background()
tables, err := groupMessageTablesForTopic(tx, ctx, tenantID, topic)
if err != nil {
return err
}
if toDel == nil {
if _, err = tx.Exec("DELETE FROM im_dellog WHERE topic=?", topic); err != nil {
return err
}
_, err = tx.Exec("DELETE FROM im_group_messages WHERE topic=?", topic)
for _, table := range tables {
if _, err = tx.Exec("DELETE FROM "+table+" WHERE topic=?", topic); err != nil {
return err
}
}
if _, err = tx.Exec("DELETE FROM im_group_message_topic_shards WHERE topic=?", topic); err != nil {
return err
}
_, err = tx.Exec("DELETE FROM im_group_message_dedup WHERE topic=?", topic)
return err
}
delRanges := toDel.SeqIdRanges
if toDel.DeletedFor == "" {
where := "m.topic=?"
args := []any{topic}
if len(delRanges) > 0 {
rSQL, rArgs := common.RangesToSql(delRanges)
where += " AND m.seqid " + rSQL
args = append(args, rArgs...)
}
where += " AND m.deleted_at IS NULL"
if newerThan := toDel.GetNewerThan(); newerThan != nil {
where += " AND m.created_at>?"
args = append(args, newerThan)
}
var seqIDs []int64
if err = tx.Select(&seqIDs, "SELECT seqid FROM im_group_messages AS m WHERE "+where, args...); err != nil {
return err
seqIDs := make([]int64, 0)
for _, table := range tables {
where := "m.topic=?"
args := []any{topic}
if len(delRanges) > 0 {
rSQL, rArgs := common.RangesToSql(delRanges)
where += " AND m.seqid " + rSQL
args = append(args, rArgs...)
}
where += " AND m.deleted_at IS NULL"
if newerThan := toDel.GetNewerThan(); newerThan != nil {
where += " AND m.created_at>?"
args = append(args, newerThan)
}
var tableSeqIDs []int64
if err = tx.Select(&tableSeqIDs, "SELECT seqid FROM "+table+" AS m WHERE "+where, args...); err != nil {
return err
}
seqIDs = append(seqIDs, tableSeqIDs...)
}
if len(seqIDs) == 0 {
return nil
}
sort.Slice(seqIDs, func(i, j int) bool { return seqIDs[i] < seqIDs[j] })
delRanges = t.SliceToRanges(seqIDs)
uniqueSeqIDs := seqIDs[:0]
for _, seqID := range seqIDs {
if len(uniqueSeqIDs) == 0 || uniqueSeqIDs[len(uniqueSeqIDs)-1] != seqID {
uniqueSeqIDs = append(uniqueSeqIDs, seqID)
}
}
delRanges = t.SliceToRanges(uniqueSeqIDs)
rSQL, rArgs := common.RangesToSql(delRanges)
where = "m.topic=? AND m.seqid " + rSQL
args = append([]any{topic}, rArgs...)
if _, err = tx.Exec("UPDATE im_group_messages AS m SET m.updated_at=?,m.deleted_at=?,m.status='deleted',m.from_user=0,m.head=NULL,m.content=NULL,m.content_size=0 WHERE "+
where, append([]any{t.TimeNow(), t.TimeNow()}, args...)...); err != nil {
return err
where := "m.topic=? AND m.seqid " + rSQL
args := append([]any{topic}, rArgs...)
now := t.TimeNow()
for _, table := range tables {
if _, err = tx.Exec("UPDATE "+table+" AS m SET m.updated_at=?,m.deleted_at=?,m.status='deleted',m.from_user=0,m.head=NULL,m.content=NULL,m.content_size=0 WHERE "+
where, append([]any{now, now}, args...)...); err != nil {
return err
}
}
}
stmt, err := tx.Prepare("INSERT INTO im_dellog(topic,deletedfor,delid,low,hi) VALUES(?,?,?,?,?)")
@@ -1481,7 +1510,7 @@ func (a *adapter) TenantTopicDelete(id t.TenantID, topic string, isChan, hard bo
return err
}
if t.GetTopicCat(topic) == t.TopicCatGrp {
err = groupMessageDeleteList(tx, topic, nil)
err = groupMessageDeleteList(tx, id, topic, nil)
} else {
err = tenantMessageDeleteList(tx, id, topic, nil)
}
@@ -2053,6 +2082,9 @@ func (a *adapter) TenantGroupMessageSave(id t.TenantID, msg *t.Message) error {
if tenantAdp != a {
return tenantAdp.TenantGroupMessageSave(id, msg)
}
if err = a.ensureCurrentGroupMessageShardWindow(); err != nil {
return err
}
ctx, cancel := a.getContext()
if cancel != nil {
defer cancel()
@@ -2061,24 +2093,77 @@ func (a *adapter) TenantGroupMessageSave(id t.TenantID, msg *t.Message) error {
if err != nil {
return err
}
if msg.ShardMonth == "" {
msg.ShardMonth = msg.CreatedAt.UTC().Format("200601")
}
table, ok := groupMessageShardTable(msg.ShardMonth)
if !ok || msg.Uid().IsZero() {
return t.ErrMalformed
}
if msg.ShardMonth == time.Now().UTC().Format("200601") {
if _, err = a.db.ExecContext(ctx, "UPDATE im_group_message_shards SET status='writable' WHERE shard_month=? AND status='prepared'", msg.ShardMonth); err != nil {
return err
}
}
var shardStatus string
if err = a.db.QueryRowContext(ctx, "SELECT status FROM im_group_message_shards WHERE shard_month=? AND table_name=?", msg.ShardMonth, table).Scan(&shardStatus); err != nil {
if err == sql.ErrNoRows {
return fmt.Errorf("mysql: group message shard %s is not registered", msg.ShardMonth)
}
return err
}
if shardStatus != "writable" && shardStatus != "sealing" {
return fmt.Errorf("mysql: group message shard %s is %s", msg.ShardMonth, shardStatus)
}
clientMsgID := sql.NullString{String: msg.ClientMsgID, Valid: msg.ClientMsgID != ""}
contentJSON := common.ToJSON(msg.Content)
res, err := a.db.ExecContext(ctx,
`INSERT INTO im_group_messages
(topic,group_id,seqid,from_user,client_msg_id,msg_type,status,content,head,mentioned_user_ids,mention_all,
tx, err := a.db.BeginTxx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
messageID := store.DecodeUid(msg.Uid())
if clientMsgID.Valid {
if _, err = tx.ExecContext(ctx, `INSERT INTO im_group_message_dedup
(topic,from_user,client_msg_id,seqid,shard_month,message_id,status,expires_at)
VALUES(?,?,?,?,?,?,'done',?)`, 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
(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)
VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
msg.Topic, groupID, msg.SeqId, decodeUID(t.ParseUid(msg.From)), clientMsgID, messageHeadType(msg.Head), "normal",
VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`, table)
_, err = tx.ExecContext(ctx, query,
messageID, msg.Topic, groupID, msg.SeqId, decodeUID(t.ParseUid(msg.From)), clientMsgID, messageHeadType(msg.Head), "normal",
contentJSON, msg.Head, messageHeadJSON(msg.Head, "mentioned_user_ids"), messageHeadBool(msg.Head, "mention_all"),
0, len(contentJSON), messageVersion(msg.Version), msg.CreatedAt, msg.UpdatedAt)
if err == nil {
lastID, _ := res.LastInsertId()
msg.SetUid(t.Uid(lastID))
}
if isDupe(err) {
return t.ErrDuplicate
}
return err
if err != nil {
return err
}
if _, err = tx.ExecContext(ctx, `INSERT INTO im_group_message_topic_shards
(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))`,
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 shard_month=?`,
msg.CreatedAt, msg.CreatedAt, msg.CreatedAt, msg.CreatedAt, msg.ShardMonth); err != nil {
return err
}
return tx.Commit()
}
func (a *adapter) TenantMessageGetByClientID(id t.TenantID, from t.Uid, clientMsgID string) (*t.Message, error) {
@@ -2131,22 +2216,44 @@ func (a *adapter) TenantGroupMessageGetByClientID(id t.TenantID, topic string, f
if cancel != nil {
defer cancel()
}
row := struct {
t.Message
RecalledBy sql.NullInt64 `db:"recalled_by"`
}{}
err = a.db.QueryRowxContext(ctx,
"SELECT id,created_at AS createdat,updated_at AS updatedat,deleted_at AS deletedat,0 AS delid,seqid,topic,from_user AS `from`,IFNULL(client_msg_id,'') AS client_msg_id,version,recalled_at,recalled_by,head,content "+
"FROM im_group_messages WHERE topic=? AND from_user=? AND client_msg_id=?",
topic, decodeUID(from), clientMsgID).StructScan(&row)
if err == sql.ErrNoRows {
return nil, nil
var seq int64
if err = a.db.QueryRowContext(ctx, `SELECT seqid FROM im_group_message_dedup
WHERE topic=? AND from_user=? AND client_msg_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
}
msg := normalizeGroupMessage(id, row.Message, row.RecalledBy)
return &msg, nil
for _, table := range tables {
month := groupMessageMonthFromTable(table)
idColumn := "id"
if month != "" {
idColumn = "message_id"
}
row := struct {
t.Message
RecalledBy sql.NullInt64 `db:"recalled_by"`
}{}
query := fmt.Sprintf("SELECT %s AS id,created_at AS createdat,updated_at AS updatedat,deleted_at AS deletedat,0 AS delid,seqid,topic,from_user AS `from`,IFNULL(client_msg_id,'') AS client_msg_id,version,recalled_at,recalled_by,head,content,'%s' AS shard_month FROM %s WHERE topic=? AND from_user=? AND client_msg_id=?", idColumn, month, table)
err = a.db.QueryRowxContext(ctx, query, topic, decodeUID(from), clientMsgID).StructScan(&row)
if err == nil {
msg := normalizeGroupMessage(id, row.Message, row.RecalledBy)
return &msg, nil
}
if err != sql.ErrNoRows {
return nil, err
}
}
return nil, nil
}
func (a *adapter) TenantMessageGetOne(id t.TenantID, topic string, seq int64) (*t.Message, error) {
@@ -2270,7 +2377,7 @@ func (a *adapter) TenantGroupMessageRecall(id t.TenantID, topic string, seq int6
tx.Rollback()
}
}()
msg, err := tenantGroupMessageGetOne(tx, ctx, id, topic, seq, " FOR UPDATE")
msg, messageTable, err := tenantGroupMessageGetOneWithTable(tx, ctx, id, topic, seq, " FOR UPDATE")
if err != nil {
if err == sql.ErrNoRows {
return nil, t.ErrNotFound
@@ -2280,8 +2387,8 @@ func (a *adapter) TenantGroupMessageRecall(id t.TenantID, topic string, seq int6
if msg.Version != expectedVersion || msg.RecalledAt != nil || msg.DeletedAt != nil {
return nil, t.ErrDuplicate
}
_, err = tx.ExecContext(ctx,
"UPDATE im_group_messages SET updated_at=?,version=version+1,status='recalled',recalled_at=?,recalled_by=?,head=NULL,content=NULL WHERE topic=? AND seqid=? AND version=?",
_, err = tx.ExecContext(ctx, fmt.Sprintf(
"UPDATE %s SET updated_at=?,version=version+1,status='recalled',recalled_at=?,recalled_by=?,head=NULL,content=NULL WHERE topic=? AND seqid=? AND version=?", messageTable),
recalledAt, recalledAt, decodeUID(actor), topic, seq, expectedVersion)
if err != nil {
return nil, err
@@ -2375,7 +2482,7 @@ func (a *adapter) TenantGroupMessageEdit(id t.TenantID, topic string, seq int64,
tx.Rollback()
}
}()
msg, err := tenantGroupMessageGetOne(tx, ctx, id, topic, seq, " FOR UPDATE")
msg, messageTable, err := tenantGroupMessageGetOneWithTable(tx, ctx, id, topic, seq, " FOR UPDATE")
if err != nil {
if err == sql.ErrNoRows {
return nil, t.ErrNotFound
@@ -2391,8 +2498,8 @@ func (a *adapter) TenantGroupMessageEdit(id t.TenantID, topic string, seq int64,
return nil, err
}
contentJSON := common.ToJSON(content)
_, err = tx.ExecContext(ctx,
"UPDATE im_group_messages SET updated_at=?,version=version+1,msg_type=?,head=?,content=?,content_size=? WHERE topic=? AND seqid=? AND version=?",
_, err = tx.ExecContext(ctx, fmt.Sprintf(
"UPDATE %s SET updated_at=?,version=version+1,msg_type=?,head=?,content=?,content_size=? WHERE topic=? AND seqid=? AND version=?", messageTable),
editedAt, messageHeadType(head), head, contentJSON, len(contentJSON), topic, seq, expectedVersion)
if err != nil {
return nil, err
@@ -2483,58 +2590,84 @@ func (a *adapter) TenantGroupMessageGetAll(id t.TenantID, topic string, uid t.Ui
return tenantAdp.TenantGroupMessageGetAll(id, topic, uid, opts)
}
limit := a.maxMessageResults
args := []any{store.DecodeUid(uid), topic}
seq := ""
seqClause := ""
seqArgs := make([]any, 0)
if opts != nil {
seq = "AND m.seqid "
seqClause = "AND m.seqid "
if len(opts.IdRanges) > 0 {
constr, addArgs := common.RangesToSql(opts.IdRanges)
seq += constr
args = append(args, addArgs...)
seqClause += constr
seqArgs = append(seqArgs, addArgs...)
} else {
seq += "BETWEEN ? AND ?"
seqClause += "BETWEEN ? AND ?"
if opts.Since > 0 {
args = append(args, opts.Since)
seqArgs = append(seqArgs, opts.Since)
} else {
args = append(args, 0)
seqArgs = append(seqArgs, 0)
}
if opts.Before > 1 {
args = append(args, opts.Before-1)
seqArgs = append(seqArgs, opts.Before-1)
} else {
args = append(args, 1<<31-1)
seqArgs = append(seqArgs, int64(^uint64(0)>>1))
}
}
if opts.Limit > 0 && opts.Limit < limit {
limit = opts.Limit
}
}
args = append(args, limit)
ctx, cancel := a.getContext()
if cancel != nil {
defer cancel()
}
rows, err := a.db.QueryxContext(ctx,
"SELECT m.id,m.created_at AS createdat,m.updated_at AS updatedat,m.deleted_at AS deletedat,0 AS delid,m.seqid,m.topic,m.from_user AS `from`,IFNULL(m.client_msg_id,'') AS client_msg_id,m.version,m.recalled_at,m.recalled_by,m.head,m.content "+
"FROM im_group_messages AS m LEFT JOIN im_dellog AS d "+
"ON d.topic COLLATE utf8mb4_unicode_ci=m.topic AND m.seqid BETWEEN d.low AND d.hi-1 AND d.deletedfor=? "+
"WHERE m.status!='deleted' AND m.topic=? "+seq+" AND d.deletedfor IS NULL ORDER BY m.seqid DESC LIMIT ?",
args...)
tables, err := groupMessageTablesForTopic(a.db, ctx, id, topic)
if err != nil {
return nil, err
}
defer rows.Close()
msgs := make([]t.Message, 0, limit)
for rows.Next() {
row := struct {
t.Message
RecalledBy sql.NullInt64 `db:"recalled_by"`
}{}
if err = rows.StructScan(&row); err != nil {
seen := make(map[int64]struct{}, limit)
for _, table := range tables {
if len(msgs) >= limit {
break
}
month := groupMessageMonthFromTable(table)
idColumn := "id"
if month != "" {
idColumn = "message_id"
}
args := []any{store.DecodeUid(uid), topic}
args = append(args, seqArgs...)
args = append(args, limit-len(msgs))
query := fmt.Sprintf("SELECT m.%s AS id,m.created_at AS createdat,m.updated_at AS updatedat,m.deleted_at AS deletedat,0 AS delid,m.seqid,m.topic,m.from_user AS `from`,IFNULL(m.client_msg_id,'') AS client_msg_id,m.version,m.recalled_at,m.recalled_by,m.head,m.content,'%s' AS shard_month FROM %s AS m LEFT JOIN im_dellog AS d ON d.topic COLLATE utf8mb4_unicode_ci=m.topic AND m.seqid BETWEEN d.low AND d.hi-1 AND d.deletedfor=? WHERE m.status!='deleted' AND m.topic=? %s AND d.deletedfor IS NULL ORDER BY m.seqid DESC LIMIT ?", idColumn, month, table, seqClause)
rows, queryErr := a.db.QueryxContext(ctx, query, args...)
if queryErr != nil {
return nil, queryErr
}
for rows.Next() {
row := struct {
t.Message
RecalledBy sql.NullInt64 `db:"recalled_by"`
}{}
if err = rows.StructScan(&row); err != nil {
rows.Close()
return nil, err
}
if _, exists := seen[row.SeqId]; exists {
continue
}
seen[row.SeqId] = struct{}{}
msgs = append(msgs, normalizeGroupMessage(id, row.Message, row.RecalledBy))
}
if err = rows.Err(); err != nil {
rows.Close()
return nil, err
}
msgs = append(msgs, normalizeGroupMessage(id, row.Message, row.RecalledBy))
rows.Close()
}
return msgs, rows.Err()
sort.Slice(msgs, func(i, j int) bool { return msgs[i].SeqId > msgs[j].SeqId })
if len(msgs) > limit {
msgs = msgs[:limit]
}
return msgs, nil
}
func (a *adapter) TenantMessageSearch(id t.TenantID, topic string, uid t.Uid, queryText, messageType, cursor string, limit int) ([]t.Message, string, error) {
@@ -2619,7 +2752,7 @@ func (a *adapter) tenantGroupMessageSearch(id t.TenantID, topic string, uid t.Ui
where := []string{"m.status!='deleted'", "m.topic=?"}
args := []any{topic}
if cursor != "" {
parsed, err := strconv.Atoi(cursor)
parsed, err := strconv.ParseInt(cursor, 10, 64)
if err != nil || parsed <= 0 {
return nil, "", t.ErrMalformed
}
@@ -2640,39 +2773,55 @@ func (a *adapter) tenantGroupMessageSearch(id t.TenantID, topic string, uid t.Ui
if cancel != nil {
defer cancel()
}
queryArgs := append([]any{store.DecodeUid(uid)}, args...)
queryArgs = append(queryArgs, limit+1)
rows, err := a.db.QueryxContext(ctx,
"SELECT m.id,m.created_at AS createdat,m.updated_at AS updatedat,m.deleted_at AS deletedat,0 AS delid,m.seqid,m.topic,m.from_user AS `from`,IFNULL(m.client_msg_id,'') AS client_msg_id,m.version,m.recalled_at,m.recalled_by,m.head,m.content "+
"FROM im_group_messages AS m LEFT JOIN im_dellog AS d "+
"ON d.topic COLLATE utf8mb4_unicode_ci=m.topic AND m.seqid BETWEEN d.low AND d.hi-1 AND d.deletedfor=? "+
"WHERE "+strings.Join(where, " AND ")+" AND d.deletedfor IS NULL ORDER BY m.seqid DESC LIMIT ?",
queryArgs...)
tables, err := groupMessageTablesForTopic(a.db, ctx, id, topic)
if err != nil {
return nil, "", err
}
defer rows.Close()
msgs := make([]t.Message, 0, limit)
nextCursor := ""
lastSeq := int64(0)
for rows.Next() {
row := struct {
t.Message
RecalledBy sql.NullInt64 `db:"recalled_by"`
}{}
if err = rows.StructScan(&row); err != nil {
msgs := make([]t.Message, 0, limit+1)
seen := make(map[int64]struct{}, limit+1)
for _, table := range tables {
if len(msgs) > limit {
break
}
month := groupMessageMonthFromTable(table)
idColumn := "id"
if month != "" {
idColumn = "message_id"
}
queryArgs := append([]any{store.DecodeUid(uid)}, args...)
queryArgs = append(queryArgs, limit+1-len(msgs))
query := fmt.Sprintf("SELECT m.%s AS id,m.created_at AS createdat,m.updated_at AS updatedat,m.deleted_at AS deletedat,0 AS delid,m.seqid,m.topic,m.from_user AS `from`,IFNULL(m.client_msg_id,'') AS client_msg_id,m.version,m.recalled_at,m.recalled_by,m.head,m.content,'%s' AS shard_month FROM %s AS m LEFT JOIN im_dellog AS d ON d.topic COLLATE utf8mb4_unicode_ci=m.topic AND m.seqid BETWEEN d.low AND d.hi-1 AND d.deletedfor=? WHERE %s AND d.deletedfor IS NULL ORDER BY m.seqid DESC LIMIT ?", idColumn, month, table, strings.Join(where, " AND "))
rows, queryErr := a.db.QueryxContext(ctx, query, queryArgs...)
if queryErr != nil {
return nil, "", queryErr
}
for rows.Next() {
row := struct {
t.Message
RecalledBy sql.NullInt64 `db:"recalled_by"`
}{}
if err = rows.StructScan(&row); err != nil {
rows.Close()
return nil, "", err
}
if _, exists := seen[row.SeqId]; exists {
continue
}
seen[row.SeqId] = struct{}{}
msgs = append(msgs, normalizeGroupMessage(id, row.Message, row.RecalledBy))
}
if err = rows.Err(); err != nil {
rows.Close()
return nil, "", err
}
msg := normalizeGroupMessage(id, row.Message, row.RecalledBy)
if len(msgs) == limit {
nextCursor = strconv.FormatInt(lastSeq, 10)
continue
}
msgs = append(msgs, msg)
lastSeq = msg.SeqId
rows.Close()
}
if err = rows.Err(); err != nil {
return nil, "", err
sort.Slice(msgs, func(i, j int) bool { return msgs[i].SeqId > msgs[j].SeqId })
nextCursor := ""
if len(msgs) > limit {
msgs = msgs[:limit]
lastSeq := msgs[len(msgs)-1].SeqId
nextCursor = strconv.FormatInt(lastSeq, 10)
}
return msgs, nextCursor, nil
}
@@ -7101,19 +7250,36 @@ func (a *adapter) groupIDByTopic(ctx context.Context, topic string) (int64, erro
}
func tenantGroupMessageGetOne(q sqlx.QueryerContext, ctx context.Context, tenantID t.TenantID, topic string, seq int64, suffix string) (*t.Message, error) {
row := struct {
t.Message
RecalledBy sql.NullInt64 `db:"recalled_by"`
}{}
err := sqlx.GetContext(ctx, q, &row,
"SELECT id,created_at AS createdat,updated_at AS updatedat,deleted_at AS deletedat,0 AS delid,seqid,topic,from_user AS `from`,IFNULL(client_msg_id,'') AS client_msg_id,version,recalled_at,recalled_by,head,content "+
"FROM im_group_messages WHERE topic=? AND seqid=?"+suffix,
topic, seq)
msg, _, err := tenantGroupMessageGetOneWithTable(q, ctx, tenantID, topic, seq, suffix)
return msg, err
}
func tenantGroupMessageGetOneWithTable(q sqlx.QueryerContext, ctx context.Context, tenantID t.TenantID, topic string, seq int64, suffix string) (*t.Message, string, error) {
tables, err := groupMessageTablesForSeq(q, ctx, tenantID, topic, seq)
if err != nil {
return nil, err
return nil, "", err
}
msg := normalizeGroupMessage(tenantID, row.Message, row.RecalledBy)
return &msg, nil
for _, table := range tables {
month := groupMessageMonthFromTable(table)
idColumn := "id"
if month != "" {
idColumn = "message_id"
}
row := struct {
t.Message
RecalledBy sql.NullInt64 `db:"recalled_by"`
}{}
query := fmt.Sprintf("SELECT %s AS id,created_at AS createdat,updated_at AS updatedat,deleted_at AS deletedat,0 AS delid,seqid,topic,from_user AS `from`,IFNULL(client_msg_id,'') AS client_msg_id,version,recalled_at,recalled_by,head,content,'%s' AS shard_month FROM %s WHERE topic=? AND seqid=?%s", idColumn, month, table, suffix)
err = sqlx.GetContext(ctx, q, &row, query, topic, seq)
if err == nil {
msg := normalizeGroupMessage(tenantID, row.Message, row.RecalledBy)
return &msg, table, nil
}
if err != sql.ErrNoRows {
return nil, "", err
}
}
return nil, "", sql.ErrNoRows
}
func messageVersion(version int) int {
@@ -7594,7 +7760,7 @@ func (a *adapter) TenantGroupMessageDeleteList(id t.TenantID, topic string, del
tx.Rollback()
}
}()
if err = groupMessageDeleteList(tx, topic, del); err != nil {
if err = groupMessageDeleteList(tx, id, topic, del); err != nil {
return err
}
return tx.Commit()