//go:build mysql // +build mysql package mysql import ( "errors" mysqldriver "github.com/go-sql-driver/mysql" ) type ignoreSQLError struct { sql string code uint16 } func ensureIMRegistrationInviteControlTables(exec sqlExecutor) error { statements := []string{ `CREATE TABLE IF NOT EXISTS im_registration_invite_batches( id BIGINT NOT NULL, tenant_id BIGINT NOT NULL, quantity INT UNSIGNED NOT NULL, max_uses_per_code INT UNSIGNED NOT NULL DEFAULT 0, idempotency_key VARCHAR(128) NOT NULL, expires_at DATETIME(3) NOT NULL, created_by BIGINT NOT NULL, remark VARCHAR(255) NOT NULL DEFAULT '', created_at DATETIME(3) NOT NULL, updated_at DATETIME(3) NOT NULL, PRIMARY KEY(id), UNIQUE INDEX uk_im_registration_invite_batch_tenant_idempotency(tenant_id,idempotency_key), INDEX idx_im_registration_invite_batch_tenant_created(tenant_id,created_at) )`, `CREATE TABLE IF NOT EXISTS im_registration_invite_codes( id BIGINT NOT NULL, tenant_id BIGINT NOT NULL, batch_id BIGINT, code CHAR(5) CHARACTER SET ascii COLLATE ascii_bin NOT NULL, status VARCHAR(16) NOT NULL, expires_at DATETIME(3) NOT NULL, max_uses INT UNSIGNED NOT NULL DEFAULT 0, bound_user_id BIGINT NULL, bound_user_label VARCHAR(128) NOT NULL DEFAULT '', usage_count INT UNSIGNED NOT NULL DEFAULT 0, last_used_at DATETIME(3) NULL, created_by BIGINT NOT NULL, remark VARCHAR(255) NOT NULL DEFAULT '', created_at DATETIME(3) NOT NULL, updated_at DATETIME(3) NOT NULL, PRIMARY KEY(id), UNIQUE INDEX uk_im_registration_invite_code_tenant(tenant_id,code), INDEX idx_im_registration_invite_tenant_bound_user(tenant_id,bound_user_id), INDEX idx_im_registration_invite_tenant_status_expires(tenant_id,status,expires_at), INDEX idx_im_registration_invite_tenant_batch(tenant_id,batch_id), INDEX idx_im_registration_invite_tenant_created(tenant_id,created_at) )`, } for _, stmt := range statements { if _, err := exec.Exec(stmt); err != nil { return err } } alterStatements := []ignoreSQLError{ {`ALTER TABLE im_registration_invite_codes ADD COLUMN max_uses INT UNSIGNED NOT NULL DEFAULT 0 AFTER expires_at`, 1060}, {`ALTER TABLE im_registration_invite_codes ADD COLUMN bound_user_id BIGINT NULL AFTER max_uses`, 1060}, {`ALTER TABLE im_registration_invite_codes ADD COLUMN bound_user_label VARCHAR(128) NOT NULL DEFAULT '' AFTER bound_user_id`, 1060}, {`ALTER TABLE im_registration_invite_codes ADD INDEX idx_im_registration_invite_tenant_bound_user(tenant_id,bound_user_id)`, 1061}, {`ALTER TABLE im_registration_invite_batches ADD COLUMN max_uses_per_code INT UNSIGNED NOT NULL DEFAULT 0 AFTER quantity`, 1060}, } for _, stmt := range alterStatements { if err := execIgnoreMySQLError(exec, stmt.sql, stmt.code); err != nil { return err } } return nil } func execIgnoreMySQLError(exec sqlExecutor, statement string, code uint16) error { if _, err := exec.Exec(statement); err != nil { var mysqlErr *mysqldriver.MySQLError if errors.As(err, &mysqlErr) && mysqlErr.Number == code { return nil } return err } return nil } func ensureIMRegistrationInviteUseTable(exec sqlExecutor) error { if _, err := exec.Exec(`CREATE TABLE IF NOT EXISTS im_registration_invite_uses( id BIGINT NOT NULL, invite_code_id BIGINT NOT NULL, user_id BIGINT NOT NULL, request_id VARCHAR(64) NOT NULL, used_at DATETIME(3) NOT NULL, PRIMARY KEY(id), INDEX idx_im_registration_invite_use_code(invite_code_id), UNIQUE INDEX uk_im_registration_invite_use_user(user_id), INDEX idx_im_registration_invite_use_request(request_id) )`); err != nil { return err } for _, migration := range []struct { statement string ignored uint16 }{ {`ALTER TABLE im_registration_invite_uses DROP INDEX uk_im_registration_invite_use_code`, 1091}, {`ALTER TABLE im_registration_invite_uses ADD INDEX idx_im_registration_invite_use_code(invite_code_id)`, 1061}, } { if _, err := exec.Exec(migration.statement); err != nil && !isMySQLErrorNumber(err, migration.ignored) { return err } } return nil } func isMySQLErrorNumber(err error, number uint16) bool { var mysqlErr *mysqldriver.MySQLError return errors.As(err, &mysqlErr) && mysqlErr.Number == number }