Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions packages/core/src/class/launchql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,14 +258,6 @@ export class LaunchQLProject {
// Create sqitch.conf file (minimal configuration)
const sqitchConf = `[core]
\tengine = pg
\tplan_file = sqitch.plan
\ttop_dir = .
[engine "pg"]
\ttarget = db:pg:
[deploy]
\tverify = true
[rebase]
\tverify = true
`;
writeFileSync(path.join(targetPath, 'sqitch.conf'), sqitchConf);

Expand Down
258 changes: 0 additions & 258 deletions MIGRATE_SPEC.md → packages/migrate/MIGRATE_SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,208 +61,6 @@ CREATE TABLE launchql_migrate.events (
);
```

## Stored Procedures API

### Project Management

```sql
-- Register a project (auto-called by deploy if needed)
CREATE OR REPLACE PROCEDURE launchql_migrate.register_project(p_project TEXT)
LANGUAGE plpgsql AS $$
BEGIN
INSERT INTO launchql_migrate.projects (project)
VALUES (p_project)
ON CONFLICT (project) DO NOTHING;
END;
$$;
```

### Deployment Functions

```sql
-- Check if a change is deployed
CREATE OR REPLACE FUNCTION launchql_migrate.is_deployed(
p_project TEXT,
p_change_name TEXT
)
RETURNS BOOLEAN
LANGUAGE sql STABLE AS $$
SELECT EXISTS (
SELECT 1 FROM launchql_migrate.changes
WHERE project = p_project
AND change_name = p_change_name
);
$$;

-- Deploy a change
CREATE OR REPLACE PROCEDURE launchql_migrate.deploy(
p_project TEXT,
p_change_name TEXT,
p_script_hash TEXT,
p_requires TEXT[],
p_deploy_sql TEXT,
p_verify_sql TEXT DEFAULT NULL
)
LANGUAGE plpgsql AS $$
DECLARE
v_change_id TEXT;
BEGIN
-- Ensure project exists
CALL launchql_migrate.register_project(p_project);

-- Generate simple ID
v_change_id := encode(sha256((p_project || p_change_name || p_script_hash)::bytea), 'hex');

-- Check if already deployed
IF launchql_migrate.is_deployed(p_project, p_change_name) THEN
RAISE EXCEPTION 'Change % already deployed in project %', p_change_name, p_project;
END IF;

-- Check dependencies
IF p_requires IS NOT NULL THEN
PERFORM 1 FROM unnest(p_requires) AS req
WHERE NOT launchql_migrate.is_deployed(p_project, req);
IF FOUND THEN
RAISE EXCEPTION 'Missing required changes';
END IF;
END IF;

-- Execute deploy
BEGIN
EXECUTE p_deploy_sql;
EXCEPTION WHEN OTHERS THEN
INSERT INTO launchql_migrate.events (event_type, change_name, project)
VALUES ('fail', p_change_name, p_project);
RAISE;
END;

-- Execute verify if provided
IF p_verify_sql IS NOT NULL THEN
BEGIN
EXECUTE p_verify_sql;
EXCEPTION WHEN OTHERS THEN
INSERT INTO launchql_migrate.events (event_type, change_name, project)
VALUES ('fail', p_change_name, p_project);
RAISE EXCEPTION 'Verification failed';
END;
END IF;

-- Record deployment
INSERT INTO launchql_migrate.changes (change_id, change_name, project, script_hash)
VALUES (v_change_id, p_change_name, p_project, p_script_hash);

-- Record dependencies (INSERTED AFTER SUCCESSFUL DEPLOYMENT)
IF p_requires IS NOT NULL THEN
INSERT INTO launchql_migrate.dependencies (change_id, requires)
SELECT v_change_id, req FROM unnest(p_requires) AS req;
END IF;

-- Log success
INSERT INTO launchql_migrate.events (event_type, change_name, project)
VALUES ('deploy', p_change_name, p_project);
END;
$$;

-- Revert a change
CREATE OR REPLACE PROCEDURE launchql_migrate.revert(
p_project TEXT,
p_change_name TEXT,
p_revert_sql TEXT
)
LANGUAGE plpgsql AS $$
BEGIN
-- Check if deployed
IF NOT launchql_migrate.is_deployed(p_project, p_change_name) THEN
RAISE EXCEPTION 'Change % not deployed in project %', p_change_name, p_project;
END IF;

-- Check if other changes depend on this
IF EXISTS (
SELECT 1 FROM launchql_migrate.dependencies d
JOIN launchql_migrate.changes c ON c.change_id = d.change_id
WHERE d.requires = p_change_name
AND c.project = p_project
) THEN
RAISE EXCEPTION 'Other changes depend on %', p_change_name;
END IF;

-- Execute revert
EXECUTE p_revert_sql;

-- Remove from deployed
DELETE FROM launchql_migrate.changes
WHERE project = p_project AND change_name = p_change_name;

-- Log revert
INSERT INTO launchql_migrate.events (event_type, change_name, project)
VALUES ('revert', p_change_name, p_project);
END;
$$;

-- Verify a change
CREATE OR REPLACE FUNCTION launchql_migrate.verify(
p_project TEXT,
p_change_name TEXT,
p_verify_sql TEXT
)
RETURNS BOOLEAN
LANGUAGE plpgsql AS $$
BEGIN
EXECUTE p_verify_sql;
RETURN TRUE;
EXCEPTION WHEN OTHERS THEN
RETURN FALSE;
END;
$$;
```

### Query Functions

```sql
-- List deployed changes
CREATE OR REPLACE FUNCTION launchql_migrate.deployed_changes(
p_project TEXT DEFAULT NULL
)
RETURNS TABLE(project TEXT, change_name TEXT, deployed_at TIMESTAMPTZ)
LANGUAGE sql STABLE AS $$
SELECT project, change_name, deployed_at
FROM launchql_migrate.changes
WHERE p_project IS NULL OR project = p_project
ORDER BY deployed_at;
$$;

-- Get deployment status
CREATE OR REPLACE FUNCTION launchql_migrate.status(
p_project TEXT DEFAULT NULL
)
RETURNS TABLE(
project TEXT,
total_deployed INTEGER,
last_change TEXT,
last_deployed TIMESTAMPTZ
)
LANGUAGE sql STABLE AS $$
WITH latest AS (
SELECT DISTINCT ON (project)
project,
change_name,
deployed_at
FROM launchql_migrate.changes
WHERE p_project IS NULL OR project = p_project
ORDER BY project, deployed_at DESC
)
SELECT
c.project,
COUNT(*)::INTEGER AS total_deployed,
l.change_name AS last_change,
l.deployed_at AS last_deployed
FROM launchql_migrate.changes c
JOIN latest l ON l.project = c.project
WHERE p_project IS NULL OR c.project = p_project
GROUP BY c.project, l.change_name, l.deployed_at;
$$;
```

## How It Works

### Deployment Flow
Expand Down Expand Up @@ -420,62 +218,6 @@ Your PostgreSQL Database
└── events
```

To install:
```bash
# Connect to YOUR application database
psql -U myuser -d myapp_db -f launchql_migrate.sql
```

## TypeScript Integration

```typescript
import { Client } from 'pg';

class LaunchQLMigrate {
constructor(private db: Client) {}

async deploy(
project: string,
change: string,
scriptHash: string,
requires: string[] | null,
deployScript: string
) {
await this.db.query(
'CALL launchql_migrate.deploy($1, $2, $3, $4, $5)',
[project, change, scriptHash, requires, deployScript]
);
}

async revert(
project: string,
change: string,
revertScript: string
) {
await this.db.query(
'CALL launchql_migrate.revert($1, $2, $3)',
[project, change, revertScript]
);
}

async status(project?: string) {
const result = await this.db.query(
'SELECT * FROM launchql_migrate.status($1)',
[project]
);
return result.rows;
}

async isDeployed(project: string, change: string): Promise<boolean> {
const result = await this.db.query(
'SELECT launchql_migrate.is_deployed($1, $2)',
[project, change]
);
return result.rows[0].is_deployed;
}
}
```

## Summary

@launchql/migrate provides a TypeScript-based alternative to Sqitch that:
Expand Down
12 changes: 0 additions & 12 deletions packages/migrate/__tests__/cross-project.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ describe('Cross-Project Dependencies', () => {
project: 'project-a',
targetDatabase: db.name,
planPath: join(basePath, 'project-a', 'sqitch.plan'),
deployPath: 'deploy'
});

expect(resultA.deployed).toEqual(['base_schema', 'base_types']);
Expand All @@ -36,7 +35,6 @@ describe('Cross-Project Dependencies', () => {
project: 'project-b',
targetDatabase: db.name,
planPath: join(basePath, 'project-b', 'sqitch.plan'),
deployPath: 'deploy'
});

expect(resultB.deployed).toEqual(['app_schema', 'app_tables']);
Expand All @@ -59,7 +57,6 @@ describe('Cross-Project Dependencies', () => {
project: 'project-b',
targetDatabase: db.name,
planPath: join(basePath, 'project-b', 'sqitch.plan'),
deployPath: 'deploy'
})).rejects.toThrow(/project-a:base_schema/);

// Verify nothing was deployed
Expand All @@ -75,14 +72,12 @@ describe('Cross-Project Dependencies', () => {
project: 'project-a',
targetDatabase: db.name,
planPath: join(basePath, 'project-a', 'sqitch.plan'),
deployPath: 'deploy'
});

await client.deploy({
project: 'project-b',
targetDatabase: db.name,
planPath: join(basePath, 'project-b', 'sqitch.plan'),
deployPath: 'deploy'
});

// Try to revert project-a:base_types which project-b depends on
Expand All @@ -91,7 +86,6 @@ describe('Cross-Project Dependencies', () => {
project: 'project-a',
targetDatabase: db.name,
planPath: join(basePath, 'project-a', 'sqitch.plan'),
revertPath: 'revert',
toChange: 'base_schema'
})).rejects.toThrow(/Cannot revert base_types: required by project-b:app_tables/);

Expand All @@ -109,14 +103,12 @@ describe('Cross-Project Dependencies', () => {
project: 'project-a',
targetDatabase: db.name,
planPath: join(basePath, 'project-a', 'sqitch.plan'),
deployPath: 'deploy'
});

await client.deploy({
project: 'project-b',
targetDatabase: db.name,
planPath: join(basePath, 'project-b', 'sqitch.plan'),
deployPath: 'deploy'
});

// Query dependents using the SQL function
Expand Down Expand Up @@ -168,21 +160,18 @@ describe('Cross-Project Dependencies', () => {
project: 'complex-a',
targetDatabase: db.name,
planPath: join(projectA, 'sqitch.plan'),
deployPath: 'deploy'
});

await client.deploy({
project: 'complex-b',
targetDatabase: db.name,
planPath: join(projectB, 'sqitch.plan'),
deployPath: 'deploy'
});

await client.deploy({
project: 'complex-c',
targetDatabase: db.name,
planPath: join(projectC, 'sqitch.plan'),
deployPath: 'deploy'
});

// Verify all deployed
Expand All @@ -195,7 +184,6 @@ describe('Cross-Project Dependencies', () => {
project: 'complex-a',
targetDatabase: db.name,
planPath: join(projectA, 'sqitch.plan'),
revertPath: 'revert',
toChange: 'base'
})).rejects.toThrow(/Cannot revert utils: required by/);
});
Expand Down
Loading