Back to Bytebase

Step 02: Database Migration

docs/plans/saas/02.database-migration.md

3.17.17.4 KB
Original Source

Step 02: Database Migration

Create the workspace table, add workspace to root tables, and update indexes. This step depends on Step 01 (principal split, bigserial migration) being completed first.


1. Create workspace Table

sql
CREATE TABLE workspace (
    id           text PRIMARY KEY,          -- 'ws-xxxxxx'
    resource_id  text UNIQUE NOT NULL,      -- user-chosen slug: 'acme-corp'
    name         text NOT NULL,             -- display name
    created_at   timestamptz NOT NULL DEFAULT now(),
    deleted      boolean NOT NULL DEFAULT FALSE
);

-- For existing single-workspace installations, create the default workspace.
-- The workspace ID is derived from the existing setting.
INSERT INTO workspace (id, resource_id, name)
SELECT
    'ws-' || (value->>'workspaceId'),
    COALESCE(NULLIF(value->>'workspaceId', ''), 'default'),
    'Default Workspace'
FROM setting
WHERE name = 'WORKSPACE_PROFILE';

No plan column -- workspace plan/license info is already stored in the setting table (name = 'SYSTEM', value contains license). Once setting gets workspace, plan info is naturally workspace-scoped.


2. Add workspace to Root Tables

Add workspace to the 10 existing root-level tables. (service_account and workload_identity will get workspace added in this step too, since Step 01 creates them without it.)

For each table, the pattern is:

  1. Add nullable column with FK
  2. Backfill with the default workspace ID
  3. Set NOT NULL
sql
-- Helper: get the default workspace ID
-- All statements below use this subquery inline.

-- project
ALTER TABLE project ADD COLUMN workspace text REFERENCES workspace(resource_id);
UPDATE project SET workspace = (SELECT id FROM workspace LIMIT 1);
ALTER TABLE project ALTER COLUMN workspace SET NOT NULL;

-- instance
ALTER TABLE instance ADD COLUMN workspace text REFERENCES workspace(resource_id);
UPDATE instance SET workspace = (SELECT id FROM workspace LIMIT 1);
ALTER TABLE instance ALTER COLUMN workspace SET NOT NULL;

-- setting
ALTER TABLE setting ADD COLUMN workspace text REFERENCES workspace(resource_id);
UPDATE setting SET workspace = (SELECT id FROM workspace LIMIT 1);
ALTER TABLE setting ALTER COLUMN workspace SET NOT NULL;

-- policy
ALTER TABLE policy ADD COLUMN workspace text REFERENCES workspace(resource_id);
UPDATE policy SET workspace = (SELECT id FROM workspace LIMIT 1);
ALTER TABLE policy ALTER COLUMN workspace SET NOT NULL;

-- role
ALTER TABLE role ADD COLUMN workspace text REFERENCES workspace(resource_id);
UPDATE role SET workspace = (SELECT id FROM workspace LIMIT 1);
ALTER TABLE role ALTER COLUMN workspace SET NOT NULL;

-- idp
ALTER TABLE idp ADD COLUMN workspace text REFERENCES workspace(resource_id);
UPDATE idp SET workspace = (SELECT id FROM workspace LIMIT 1);
ALTER TABLE idp ALTER COLUMN workspace SET NOT NULL;

-- review_config
ALTER TABLE review_config ADD COLUMN workspace text REFERENCES workspace(resource_id);
UPDATE review_config SET workspace = (SELECT id FROM workspace LIMIT 1);
ALTER TABLE review_config ALTER COLUMN workspace SET NOT NULL;

-- user_group
ALTER TABLE user_group ADD COLUMN workspace text REFERENCES workspace(resource_id);
UPDATE user_group SET workspace = (SELECT id FROM workspace LIMIT 1);
ALTER TABLE user_group ALTER COLUMN workspace SET NOT NULL;

-- export_archive
ALTER TABLE export_archive ADD COLUMN workspace text REFERENCES workspace(resource_id);
UPDATE export_archive SET workspace = (SELECT id FROM workspace LIMIT 1);
ALTER TABLE export_archive ALTER COLUMN workspace SET NOT NULL;

-- audit_log
ALTER TABLE audit_log ADD COLUMN workspace text REFERENCES workspace(resource_id);
UPDATE audit_log SET workspace = (SELECT id FROM workspace LIMIT 1);
ALTER TABLE audit_log ALTER COLUMN workspace SET NOT NULL;

-- service_account (created in Step 01 without workspace)
ALTER TABLE service_account ADD COLUMN workspace text REFERENCES workspace(resource_id);
UPDATE service_account SET workspace = (SELECT id FROM workspace LIMIT 1);
ALTER TABLE service_account ALTER COLUMN workspace SET NOT NULL;

-- workload_identity (created in Step 01 without workspace)
ALTER TABLE workload_identity ADD COLUMN workspace text REFERENCES workspace(resource_id);
UPDATE workload_identity SET workspace = (SELECT id FROM workspace LIMIT 1);
ALTER TABLE workload_identity ALTER COLUMN workspace SET NOT NULL;

Project-child tables (plan, issue, db, project_webhook, worksheet, db_group, release, access_grant, query_history) and grandchild tables do NOT get a workspace column -- they are scoped through their existing project FK chain.


3. Update Unique Indexes

Only root tables need index changes to include workspace:

sql
-- project
DROP INDEX idx_project_unique_resource_id;
CREATE UNIQUE INDEX idx_project_unique_resource_id ON project(workspace, resource_id);

-- instance
DROP INDEX idx_instance_unique_resource_id;
CREATE UNIQUE INDEX idx_instance_unique_resource_id ON instance(workspace, resource_id);

-- principal: no change needed (global identity, email unique globally)

-- setting
DROP INDEX idx_setting_unique_name;
CREATE UNIQUE INDEX idx_setting_unique_name ON setting(workspace, name);

-- role
DROP INDEX idx_role_unique_resource_id;
CREATE UNIQUE INDEX idx_role_unique_resource_id ON role(workspace, resource_id);

-- idp
DROP INDEX idx_idp_unique_resource_id;
CREATE UNIQUE INDEX idx_idp_unique_resource_id ON idp(workspace, resource_id);

-- user_group
DROP INDEX idx_user_group_unique_email;
CREATE UNIQUE INDEX idx_user_group_unique_email ON user_group(workspace, email) WHERE email IS NOT NULL;

-- service_account
CREATE UNIQUE INDEX idx_service_account_unique_workspace_email ON service_account(workspace, email);

-- workload_identity
CREATE UNIQUE INDEX idx_workload_identity_unique_workspace_email ON workload_identity(workspace, email);

Child table unique indexes remain unchanged -- their uniqueness is naturally scoped because their parent references (e.g., plan.project, db.instance) are already workspace-unique:

sql
-- These DO NOT change (already scoped through parent FKs):
-- idx_db_unique_instance_name ON db(instance, name)             -- instance is workspace-unique
-- idx_plan_check_run_unique_plan_id ON plan_check_run(plan_id)  -- plan_id is globally unique (bigserial)
-- idx_issue_unique_plan_id ON issue(plan_id)                    -- plan_id is globally unique (bigserial)
-- uk_task_run_task_id_attempt ON task_run(task_id, attempt)     -- task_id is globally unique (bigserial)
-- idx_db_group_unique_project_resource_id ON db_group(project, resource_id)       -- project is workspace-unique
-- idx_release_project_train_iteration ON release(project, train, iteration)       -- project is workspace-unique

4. Add Workspace Query Performance Indexes

sql
CREATE INDEX idx_project_workspace ON project(workspace);
CREATE INDEX idx_instance_workspace ON instance(workspace);
CREATE INDEX idx_setting_workspace ON setting(workspace);
CREATE INDEX idx_policy_workspace ON policy(workspace);
CREATE INDEX idx_audit_log_workspace ON audit_log(workspace);
CREATE INDEX idx_service_account_workspace ON service_account(workspace);
CREATE INDEX idx_workload_identity_workspace ON workload_identity(workspace);

Migration Test

Update TestLatestVersion in backend/migrator/migrator_test.go after adding new migration files.

Update backend/migrator/migration/LATEST.sql to reflect the final schema state with the workspace table and workspace columns.