branch: main
schema.rs
7134 bytesRaw
use worker::SqlStorage;

/// Initialize all tables and indexes. Called once from DurableObject::new.
/// Uses IF NOT EXISTS throughout so it's safe to call on every instantiation.
pub fn init(sql: &SqlStorage) {
    sql.exec(
        "CREATE TABLE IF NOT EXISTS refs (
            name        TEXT PRIMARY KEY,
            commit_hash TEXT NOT NULL
        )",
        None,
    )
    .expect("create refs");

    sql.exec(
        "CREATE TABLE IF NOT EXISTS commits (
            hash            TEXT PRIMARY KEY,
            tree_hash       TEXT NOT NULL,
            author          TEXT NOT NULL,
            author_email    TEXT NOT NULL,
            author_time     INTEGER NOT NULL,
            committer       TEXT NOT NULL,
            committer_email TEXT NOT NULL,
            commit_time     INTEGER NOT NULL,
            message         TEXT NOT NULL
        )",
        None,
    )
    .expect("create commits");

    sql.exec(
        "CREATE TABLE IF NOT EXISTS commit_parents (
            commit_hash TEXT NOT NULL,
            parent_hash TEXT NOT NULL,
            ordinal     INTEGER NOT NULL,
            PRIMARY KEY (commit_hash, ordinal)
        )",
        None,
    )
    .expect("create commit_parents");

    sql.exec(
        "CREATE TABLE IF NOT EXISTS commit_graph (
            commit_hash   TEXT NOT NULL,
            level         INTEGER NOT NULL,
            ancestor_hash TEXT NOT NULL,
            PRIMARY KEY (commit_hash, level)
        )",
        None,
    )
    .expect("create commit_graph");

    sql.exec(
        "CREATE TABLE IF NOT EXISTS trees (
            tree_hash  TEXT NOT NULL,
            name       TEXT NOT NULL,
            mode       INTEGER NOT NULL,
            entry_hash TEXT NOT NULL,
            PRIMARY KEY (tree_hash, name)
        )",
        None,
    )
    .expect("create trees");

    sql.exec(
        "CREATE TABLE IF NOT EXISTS blob_groups (
            group_id       INTEGER PRIMARY KEY AUTOINCREMENT,
            path_hint      TEXT,
            latest_version INTEGER NOT NULL DEFAULT 0
        )",
        None,
    )
    .expect("create blob_groups");

    sql.exec(
        "CREATE TABLE IF NOT EXISTS blobs (
            blob_hash        TEXT PRIMARY KEY,
            group_id         INTEGER NOT NULL REFERENCES blob_groups(group_id),
            version_in_group INTEGER NOT NULL,
            is_keyframe      INTEGER NOT NULL DEFAULT 0,
            data             BLOB NOT NULL,
            raw_size         INTEGER NOT NULL,
            stored_size      INTEGER NOT NULL DEFAULT 0,
            UNIQUE (group_id, version_in_group)
        )",
        None,
    )
    .expect("create blobs");

    // Overflow table for blobs whose compressed data exceeds the 2 MB
    // per-row SQLite limit in Cloudflare DOs.  The blobs table stores an
    // empty sentinel for the data column; actual bytes live here in chunks.
    sql.exec(
        "CREATE TABLE IF NOT EXISTS blob_chunks (
            group_id         INTEGER NOT NULL,
            version_in_group INTEGER NOT NULL,
            chunk_index      INTEGER NOT NULL,
            data             BLOB NOT NULL,
            PRIMARY KEY (group_id, version_in_group, chunk_index)
        )",
        None,
    )
    .expect("create blob_chunks");

    // Raw object bytes for commits and trees. Stored verbatim so we can
    // return them byte-for-byte identical during fetch (preserving timezone,
    // entry order, etc. that the parsed tables lose).
    // Blobs are NOT stored here — they're reconstructed from xpatch chains.
    sql.exec(
        "CREATE TABLE IF NOT EXISTS raw_objects (
            hash TEXT PRIMARY KEY,
            data BLOB NOT NULL
        )",
        None,
    )
    .expect("create raw_objects");

    // -- Indexes --

    sql.exec(
        "CREATE INDEX IF NOT EXISTS idx_commits_time
         ON commits(commit_time DESC)",
        None,
    )
    .expect("create idx_commits_time");

    sql.exec(
        "CREATE INDEX IF NOT EXISTS idx_commit_parents_parent
         ON commit_parents(parent_hash)",
        None,
    )
    .expect("create idx_commit_parents_parent");

    sql.exec(
        "CREATE INDEX IF NOT EXISTS idx_trees_entry
         ON trees(entry_hash)",
        None,
    )
    .expect("create idx_trees_entry");

    sql.exec(
        "CREATE INDEX IF NOT EXISTS idx_blobs_group
         ON blobs(group_id, version_in_group)",
        None,
    )
    .expect("create idx_blobs_group");

    sql.exec(
        "CREATE INDEX IF NOT EXISTS idx_blob_groups_path
         ON blob_groups(path_hint)",
        None,
    )
    .expect("create idx_blob_groups_path");

    // -- Config --
    sql.exec(
        "CREATE TABLE IF NOT EXISTS config (
            key   TEXT PRIMARY KEY,
            value TEXT NOT NULL
        )",
        None,
    )
    .expect("create config");

    // -- FTS5 --
    // FTS5 virtual tables don't support IF NOT EXISTS in all SQLite builds.
    // Wrap in a check against sqlite_master to be safe.
    sql.exec(
        "CREATE VIRTUAL TABLE IF NOT EXISTS fts_head USING fts5(path, content)",
        None,
    )
    .expect("create fts_head");

    // FTS5 for commit message search
    sql.exec(
        "CREATE VIRTUAL TABLE IF NOT EXISTS fts_commits USING fts5(hash UNINDEXED, message, author)",
        None,
    )
    .expect("create fts_commits");

    // -- Issues and pull requests --

    sql.exec(
        "CREATE TABLE IF NOT EXISTS issues (
            id                INTEGER PRIMARY KEY AUTOINCREMENT,
            number            INTEGER NOT NULL,
            kind              TEXT NOT NULL DEFAULT 'issue',
            title             TEXT NOT NULL,
            body              TEXT NOT NULL DEFAULT '',
            author_id         TEXT NOT NULL,
            author_name       TEXT NOT NULL,
            state             TEXT NOT NULL DEFAULT 'open',
            source_branch     TEXT,
            target_branch     TEXT,
            source_hash       TEXT,
            merge_commit_hash TEXT,
            created_at        INTEGER NOT NULL,
            updated_at        INTEGER NOT NULL
        )",
        None,
    )
    .expect("create issues");

    sql.exec(
        "CREATE TABLE IF NOT EXISTS issue_comments (
            id          INTEGER PRIMARY KEY AUTOINCREMENT,
            issue_id    INTEGER NOT NULL,
            author_id   TEXT NOT NULL,
            author_name TEXT NOT NULL,
            body        TEXT NOT NULL,
            created_at  INTEGER NOT NULL,
            updated_at  INTEGER NOT NULL
        )",
        None,
    )
    .expect("create issue_comments");

    sql.exec(
        "CREATE UNIQUE INDEX IF NOT EXISTS idx_issues_number ON issues(number)",
        None,
    )
    .expect("create idx_issues_number");

    sql.exec(
        "CREATE INDEX IF NOT EXISTS idx_issues_kind_state ON issues(kind, state)",
        None,
    )
    .expect("create idx_issues_kind_state");

    sql.exec(
        "CREATE INDEX IF NOT EXISTS idx_issue_comments_issue ON issue_comments(issue_id)",
        None,
    )
    .expect("create idx_issue_comments_issue");
}