diff --git a/server/db/mysql/group_schema_test.go b/server/db/mysql/group_schema_test.go index 09bad53..9573112 100644 --- a/server/db/mysql/group_schema_test.go +++ b/server/db/mysql/group_schema_test.go @@ -117,14 +117,14 @@ func TestTenantPartitionTableNames(t *testing.T) { func TestTenantShardSQL(t *testing.T) { query := tenantShardSQL(7, "SELECT * FROM im_topics t JOIN im_group_members gm ON gm.topic=t.name JOIN im_subscriptions s ON s.topic=gm.topic; INSERT INTO im_system_notifications(event_id) VALUES(?)") - for _, expected := range []string{"im_topics", "im_group_members", "im_subscriptions", "im_system_notifications"} { + for _, expected := range []string{"im_007_topics", "im_007_group_members", "im_007_subscriptions", "im_007_system_notifications"} { if !strings.Contains(query, expected) { - t.Fatalf("shared tenant query is missing %q: %s", expected, query) + t.Fatalf("tenant shard query is missing %q: %s", expected, query) } } - for _, legacy := range []string{"im_007_topics", "im_007_group_members", "im_007_subscriptions", "im_007_system_notifications"} { - if strings.Contains(query, legacy) { - t.Fatalf("shared tenant query should not reference %q: %s", legacy, query) + for _, shared := range []string{" im_topics ", " im_group_members ", " im_subscriptions ", " im_system_notifications"} { + if strings.Contains(query, shared) { + t.Fatalf("tenant shard query should not reference shared table %q: %s", shared, query) } } } diff --git a/server/db/mysql/tenant_groups.go b/server/db/mysql/tenant_groups.go index 6ec4c3e..05aad15 100644 --- a/server/db/mysql/tenant_groups.go +++ b/server/db/mysql/tenant_groups.go @@ -17,7 +17,7 @@ import ( ) const ( - groupSettingsSelectColumns = `topic,owner_user_id,name,avatar_file_id,description,group_status,join_approval,mute_all, + groupSettingsSelectColumns = `tenant_id,topic,owner_user_id,name,avatar_file_id,description,group_status,join_approval,mute_all, member_upload_files,member_private_chat,member_invite,max_member_count,max_admin_count,member_count,version, created_by,dissolved_by,dissolved_at,created_at,updated_at` groupAnnouncementSelectColumnsBase = `id,announcement_id,topic,author_user_id,content,status,version,published_at,idempotency_key,created_at,updated_at, @@ -66,10 +66,10 @@ func (a *adapter) TenantGroupSettingsCreate(id t.TenantID, settings *t.GroupSett return nil, err } _, err = a.db.ExecContext(ctx, `INSERT INTO im_group_settings - (id,topic,owner_user_id,name,avatar_file_id,description,group_status,join_approval,mute_all,member_upload_files,member_private_chat,member_invite,max_member_count,max_admin_count,member_count,version,created_by,created_at,updated_at) - VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) + (id,tenant_id,topic,owner_user_id,name,avatar_file_id,description,group_status,join_approval,mute_all,member_upload_files,member_private_chat,member_invite,max_member_count,max_admin_count,member_count,version,created_by,created_at,updated_at) + VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE name=VALUES(name),avatar_file_id=VALUES(avatar_file_id),description=VALUES(description),updated_at=VALUES(updated_at)`, - settingsID, settings.Topic, decodeUID(settings.OwnerUserID), settings.Name, settings.AvatarFileID, settings.Description, settings.GroupStatus, + settingsID, id, settings.Topic, decodeUID(settings.OwnerUserID), settings.Name, settings.AvatarFileID, settings.Description, settings.GroupStatus, settings.JoinApproval, settings.MuteAll, settings.MemberUploadFiles, settings.MemberPrivateChat, settings.MemberInvite, settings.MaxMemberCount, settings.MaxAdminCount, settings.MemberCount, settings.Version, decodeUID(settings.CreatedBy), settings.CreatedAt, settings.UpdatedAt) if err != nil { @@ -91,7 +91,7 @@ func (a *adapter) TenantGroupSettingsGet(id t.TenantID, topic string) (*t.GroupS if cancel != nil { defer cancel() } - return a.groupSettingsByTopic(ctx, topic) + return a.groupSettingsByTopic(ctx, id, topic) } func (a *adapter) TenantGroupSettingsUpdate(id t.TenantID, topic string, patch t.GroupSettingsPatch) (*t.GroupSettings, error) { @@ -154,8 +154,8 @@ func (a *adapter) TenantGroupSettingsUpdate(id t.TenantID, topic string, patch t args = append(args, patch.MemberCountDelta) } set = append(set, "version=version+1") - args = append(args, topic) - where := "topic=?" + args = append(args, id, topic) + where := "tenant_id=? AND topic=?" if patch.Version > 0 { where += " AND version=?" args = append(args, patch.Version) @@ -187,7 +187,7 @@ func (a *adapter) TenantGroupSettingsUpdate(id t.TenantID, topic string, patch t if err = tx.Commit(); err != nil { return nil, err } - return a.groupSettingsByTopic(ctx, topic) + return a.groupSettingsByTopic(ctx, id, topic) } func (a *adapter) TenantGroupDissolve(id t.TenantID, topic string, actor t.Uid, reason string, now time.Time) (*t.GroupSettings, error) { @@ -213,7 +213,7 @@ func (a *adapter) TenantGroupDissolve(id t.TenantID, topic string, actor t.Uid, defer tx.Rollback() res, err := tx.ExecContext(ctx, `UPDATE im_group_settings SET group_status='dissolved',dissolved_by=?,dissolved_at=?,member_count=0,version=version+1,updated_at=? - WHERE topic=? AND group_status<>'dissolved'`, decodeUID(actor), now, now, strings.TrimSpace(topic)) + WHERE tenant_id=? AND topic=? AND group_status<>'dissolved'`, decodeUID(actor), now, now, id, strings.TrimSpace(topic)) if err != nil { return nil, err } @@ -238,7 +238,7 @@ func (a *adapter) TenantGroupDissolve(id t.TenantID, topic string, actor t.Uid, if err = tx.Commit(); err != nil { return nil, err } - return a.groupSettingsByTopic(ctx, topic) + return a.groupSettingsByTopic(ctx, id, topic) } func (a *adapter) TenantGroupMemberUpsert(id t.TenantID, member *t.GroupMember) (*t.GroupMember, error) { @@ -1245,7 +1245,7 @@ func (a *adapter) TenantGroupCreate(id t.TenantID, topic *t.Topic, owner t.Uid, return err } settings.MemberCount = 1 - if err = insertGroupSettingsTx(tx, settings); err != nil { + if err = insertGroupSettingsTx(tx, id, settings); err != nil { return err } ownerMember := &t.GroupMember{Topic: topic.Id, UserID: owner, Role: t.GroupMemberRoleOwner, @@ -1458,8 +1458,8 @@ func (a *adapter) TenantGroupOwnerTransfer(id t.TenantID, topic string, previous if _, err = tx.ExecContext(ctx, tenantShardSQL(id, "UPDATE im_topics SET owner=?,updatedat=? WHERE tenant_id=? AND name=?"), decodeUID(newOwner), now, id, topic); err != nil { return err } - args := []any{decodeUID(newOwner), now, topic} - query := "UPDATE im_group_settings SET owner_user_id=?,version=version+1,updated_at=? WHERE topic=?" + args := []any{decodeUID(newOwner), now, id, topic} + query := "UPDATE im_group_settings SET owner_user_id=?,version=version+1,updated_at=? WHERE tenant_id=? AND topic=?" if version > 0 { query += " AND version=?" args = append(args, version) @@ -1489,23 +1489,23 @@ func (a *adapter) TenantGroupCanPost(id t.TenantID, topic string, uid t.Uid, adm } var muteAll, memberMuted bool err := a.db.QueryRowContext(ctx, `SELECT - COALESCE((SELECT mute_all FROM im_group_settings WHERE topic=? AND group_status=? LIMIT 1),0), + COALESCE((SELECT mute_all FROM im_group_settings WHERE tenant_id=? AND topic=? AND group_status=? LIMIT 1),0), EXISTS(SELECT 1 FROM im_group_mutes WHERE topic=? AND user_id=? AND status=? AND (expires_at IS NULL OR expires_at>?))`, - topic, t.GroupStatusActive, topic, decodeUID(uid), t.GroupMuteStatusActive, now).Scan(&muteAll, &memberMuted) + id, topic, t.GroupStatusActive, topic, decodeUID(uid), t.GroupMuteStatusActive, now).Scan(&muteAll, &memberMuted) if err != nil { return false, err } return !muteAll && !memberMuted, nil } -func insertGroupSettingsTx(tx *sqlx.Tx, settings *t.GroupSettings) error { +func insertGroupSettingsTx(tx *sqlx.Tx, tenantID t.TenantID, settings *t.GroupSettings) error { settingsID, err := nextSnowflakeID() if err != nil { return err } _, err = tx.Exec(`INSERT INTO im_group_settings - (id,topic,owner_user_id,name,avatar_file_id,description,group_status,join_approval,mute_all,member_upload_files,member_private_chat,member_invite,max_member_count,max_admin_count,member_count,version,created_by,created_at,updated_at) - VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`, settingsID, settings.Topic, decodeUID(settings.OwnerUserID), settings.Name, + (id,tenant_id,topic,owner_user_id,name,avatar_file_id,description,group_status,join_approval,mute_all,member_upload_files,member_private_chat,member_invite,max_member_count,max_admin_count,member_count,version,created_by,created_at,updated_at) + VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`, settingsID, tenantID, settings.Topic, decodeUID(settings.OwnerUserID), settings.Name, settings.AvatarFileID, settings.Description, settings.GroupStatus, settings.JoinApproval, settings.MuteAll, settings.MemberUploadFiles, settings.MemberPrivateChat, settings.MemberInvite, settings.MaxMemberCount, settings.MaxAdminCount, settings.MemberCount, settings.Version, decodeUID(settings.CreatedBy), settings.CreatedAt, settings.UpdatedAt) @@ -1569,9 +1569,9 @@ func (a *adapter) routeGroupTenant(id t.TenantID) (*adapter, error) { return a, validTenant(id) } -func (a *adapter) groupSettingsByTopic(ctx context.Context, topic string) (*t.GroupSettings, error) { +func (a *adapter) groupSettingsByTopic(ctx context.Context, tenantID t.TenantID, topic string) (*t.GroupSettings, error) { var item t.GroupSettings - err := a.db.QueryRowxContext(ctx, "SELECT "+groupSettingsSelectColumns+" FROM im_group_settings WHERE topic=?", topic).StructScan(&item) + err := a.db.QueryRowxContext(ctx, "SELECT "+groupSettingsSelectColumns+" FROM im_group_settings WHERE tenant_id=? AND topic=?", tenantID, topic).StructScan(&item) if err == sql.ErrNoRows { return nil, t.ErrNotFound } diff --git a/server/db/mysql/tenant_tables.go b/server/db/mysql/tenant_tables.go index ab67567..4f1f0af 100644 --- a/server/db/mysql/tenant_tables.go +++ b/server/db/mysql/tenant_tables.go @@ -5,12 +5,16 @@ package mysql import ( "fmt" + "strings" t "github.com/tinode/chat/server/store/types" ) func tenantShardSQL(tenantID t.TenantID, query string) string { - return query + query = strings.ReplaceAll(query, "im_topics", tenantTopicsTable(tenantID)) + query = strings.ReplaceAll(query, "im_group_members", tenantGroupMembersTable(tenantID)) + query = strings.ReplaceAll(query, "im_subscriptions", tenantSubscriptionsTable(tenantID)) + return strings.ReplaceAll(query, "im_system_notifications", tenantSystemNotificationsTable(tenantID)) } func tenantTablePrefix(tenantID t.TenantID) string { diff --git a/server/store/types/groups.go b/server/store/types/groups.go index 342521e..53fec90 100644 --- a/server/store/types/groups.go +++ b/server/store/types/groups.go @@ -29,6 +29,7 @@ const ( ) type GroupSettings struct { + TenantID TenantID `db:"tenant_id"` Topic string `db:"topic"` OwnerUserID Uid `db:"-"` Name string `db:"name"`