# Using npm script (runs migrations 001 and 002)npm run db:migrate:dev# Or manually with --local flagwrangler d1 execute DB \ --local \ --file ./migrations/001_initial_schema.sqlwrangler d1 execute DB \ --local \ --file ./migrations/002_indexes.sqlwrangler d1 execute DB \ --local \ --file ./migrations/003_custom_docs_and_known_addresses.sql
CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY, github_id INTEGER UNIQUE NOT NULL, username TEXT UNIQUE NOT NULL, email TEXT UNIQUE NOT NULL, avatar_url TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP);
Stores user-created projects with Solana program IDs:
CREATE TABLE IF NOT EXISTS projects ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL, name TEXT NOT NULL, description TEXT, program_id TEXT NOT NULL, is_public BOOLEAN DEFAULT TRUE, custom_docs TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, UNIQUE(user_id, program_id));
CREATE TABLE IF NOT EXISTS idl_versions ( id TEXT PRIMARY KEY, project_id TEXT NOT NULL, idl_json TEXT NOT NULL, cpi_md TEXT, version INTEGER NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE);
CREATE TABLE IF NOT EXISTS api_keys ( id TEXT PRIMARY KEY, project_id TEXT NOT NULL, key TEXT UNIQUE NOT NULL, last_used DATETIME, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, expires_at DATETIME, FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE);
CREATE TABLE IF NOT EXISTS known_addresses ( id TEXT PRIMARY KEY, project_id TEXT NOT NULL, label TEXT NOT NULL, address TEXT NOT NULL, description TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE);
# Run a single SQL commandwrangler d1 execute orquestra-prod \ --command "SELECT COUNT(*) FROM users" \ --remote# Run multiple commands from a filewrangler d1 execute orquestra-prod \ --file ./query.sql \ --remote
# Test on local databasewrangler d1 execute DB --file ./migrations/new_migration.sql --local# Verify with querieswrangler d1 execute DB --command ".schema" --local# Then apply to productionwrangler d1 execute orquestra-prod --file ./migrations/new_migration.sql --remote
Keep migrations idempotent
Use IF NOT EXISTS and IF EXISTS clauses:
-- Safe to run multiple timesCREATE TABLE IF NOT EXISTS new_table (...);ALTER TABLE projects ADD COLUMN IF NOT EXISTS new_column TEXT;CREATE INDEX IF NOT EXISTS idx_name ON table(column);
Version migrations sequentially
Name files with incrementing numbers:
001_initial_schema.sql
002_indexes.sql
003_custom_docs_and_known_addresses.sql
004_your_new_migration.sql
Use transactions for complex migrations
BEGIN TRANSACTION;-- Multiple related changesALTER TABLE projects ADD COLUMN new_field TEXT;UPDATE projects SET new_field = 'default' WHERE new_field IS NULL;COMMIT;