//go:build mysql // +build mysql package mysql import ( "context" "fmt" "time" "github.com/jmoiron/sqlx" "github.com/tinode/chat/server/store" t "github.com/tinode/chat/server/store/types" ) func ensureGroupMessageShardInfrastructure(exec sqlExecutor, tenantID t.TenantID) error { statements := []string{ `CREATE TABLE IF NOT EXISTS im_group_message_shards ( tenant_id BIGINT NOT NULL DEFAULT 0, shard_month CHAR(6) NOT NULL, table_name VARCHAR(64) NOT NULL, shard_start DATETIME(3) NOT NULL, shard_end DATETIME(3) NOT NULL, status VARCHAR(16) NOT NULL DEFAULT 'prepared', row_count BIGINT NOT NULL DEFAULT 0, min_created_at DATETIME(3), max_created_at DATETIME(3), created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), PRIMARY KEY(tenant_id, shard_month), UNIQUE INDEX uk_im_group_message_shards_table(tenant_id, table_name), INDEX idx_im_group_message_shards_status(tenant_id, status, shard_month) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`, } for _, statement := range statements { if _, err := exec.Exec(statement); err != nil { return err } } return ensureGroupMessageShardWindow(exec, tenantID, time.Now().UTC()) } func ensureGroupMessageShardWindow(exec sqlExecutor, tenantID t.TenantID, now time.Time) error { first := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.UTC) for _, offset := range []int{-1, 0, 1, 2} { start := first.AddDate(0, offset, 0) status := "prepared" if offset == -1 { status = "sealing" } else if offset == 0 { status = "writable" } if err := ensureGroupMessageShard(exec, tenantID, start, status); err != nil { return err } } _, err := exec.Exec("UPDATE im_group_message_shards SET status='sealed' WHERE tenant_id=? AND status='sealing' AND shard_end= seq { if table, ok := groupMessageShardTable(tenantID, route.Month); ok { tables = append(tables, table) } } } return tables } func groupMessageAllShardRoutes(q sqlx.QueryerContext, ctx context.Context, tenantID t.TenantID) ([]store.GroupMessageShardRoute, error) { var rows []struct { Month string `db:"month"` } if err := sqlx.SelectContext(ctx, q, &rows, `SELECT shard_month AS month FROM im_group_message_shards WHERE tenant_id=? ORDER BY shard_month DESC`, tenantID); err != nil { return nil, err } routes := make([]store.GroupMessageShardRoute, 0, len(rows)) for _, row := range rows { if validShardMonth(row.Month) { routes = append(routes, store.GroupMessageShardRoute{Month: row.Month, MinSeq: 1, MaxSeq: 1<<62 - 1, Count: 0}) } } return routes, nil } func groupMessageMonthFromTable(table string) string { const suffixLength = 6 if len(table) > suffixLength && validShardMonth(table[len(table)-suffixLength:]) { return table[len(table)-suffixLength:] } return "" }