//go:build mysql // +build mysql package mysql import ( "context" "github.com/jmoiron/sqlx" ) const userPrivacyMigrationLock = "CONCAT('im_user_privacy:', LEFT(SHA2(DATABASE(), 256), 40))" // ensureUserPrivacySchema adds columns of im_user_privacy_settings that were // introduced after the table's initial release. Databases created fresh by // createIMUserProductTables already have them; legacy databases are upgraded // in place. A missing table is skipped: it is created by the fresh-install // path and there is nothing to upgrade yet. func ensureUserPrivacySchema(db *sqlx.DB) error { ctx := context.Background() conn, err := db.Connx(ctx) if err != nil { return err } defer conn.Close() // Fast path: check for missing columns without taking the migration lock. // Databases created by createIMUserProductTables already have every column, // so this is the common case and avoids GET_LOCK entirely (prohibited on // Percona XtraDB Cluster with pxc_strict_mode=ENFORCING). added, err := ensureUserPrivacyColumns(ctx, conn) if err != nil || added { return err } _, unlock, err := acquireMigrationLock(ctx, conn, userPrivacyMigrationLock) if err != nil { return err } if unlock != nil { defer unlock() } // Double-check under the lock: another instance may have migrated while we // were waiting. Without a lock (PXC fallback) this simply runs idempotently. _, err = ensureUserPrivacyColumns(ctx, conn) return err } // ensureUserPrivacyColumns adds any columns of im_user_privacy_settings that // are missing and reports whether nothing was missing. A missing table is // skipped: it is created by the fresh-install path and there is nothing to // upgrade yet. Column definitions mirror createIMUserProductTables; the SELECT // in userPrivacyByUID references every one of them. func ensureUserPrivacyColumns(ctx context.Context, conn *sqlx.Conn) (bool, error) { tableExists, err := identityTableExists(ctx, conn, "im_user_privacy_settings") if err != nil || !tableExists { return true, err } columns := []struct{ name, ddl string }{ {"profile_visibility", "VARCHAR(32) NOT NULL DEFAULT 'friends'"}, {"last_seen_visibility", "VARCHAR(32) NOT NULL DEFAULT 'friends'"}, {"read_receipts", "TINYINT NOT NULL DEFAULT 1"}, {"friend_request_verification", "TINYINT NOT NULL DEFAULT 1"}, {"group_invite_verification", "TINYINT NOT NULL DEFAULT 0"}, } missing := false for _, col := range columns { exists, err := identityColumnExists(ctx, conn, "im_user_privacy_settings", col.name) if err != nil { return false, err } if exists { continue } missing = true if _, err = conn.ExecContext(ctx, "ALTER TABLE im_user_privacy_settings ADD COLUMN "+col.name+" "+col.ddl); err != nil { return false, err } } return !missing, nil }