Skip to content
Closed
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
34 changes: 32 additions & 2 deletions postgres/pgsql-client/src/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,42 @@ export class DbAdmin {
}
}

private terminateConnections(dbName: string): void {
const maintenanceDb = this.roleConfig?.rootDb ?? this.config.database;
const escapedDbName = dbName.replace(/'/g, "''");
const sql =
"SELECT pg_terminate_backend(pid) " +
`FROM pg_stat_activity WHERE datname='${escapedDbName}' ` +
"AND pid <> pg_backend_pid();";
const escapedSql = sql.replace(/"/g, '\\"');
this.run(`psql -v ON_ERROR_STOP=1 -d "${maintenanceDb}" -c "${escapedSql}"`);
}

private dropWithPsql(dbName: string): void {
const escapedDbName = dbName.replace(/"/g, '""');
this.run(
`psql -v ON_ERROR_STOP=1 -c "DROP DATABASE IF EXISTS \\"${escapedDbName}\\";"`
);
}

private safeDropDb(name: string): void {
try {
this.run(`dropdb "${name}"`);
} catch (err: any) {
if (!err.message.includes('does not exist')) {
log.warn(`Could not drop database ${name}: ${err.message}`);
if (err.message.includes('does not exist')) return;
try {
this.terminateConnections(name);
} catch (terminateError: any) {
log.warn(
`Could not terminate connections for ${name}: ${terminateError.message}`
);
}
try {
this.dropWithPsql(name);
} catch (dropError: any) {
if (!dropError.message.includes('does not exist')) {
log.warn(`Could not drop database ${name}: ${dropError.message}`);
}
}
}
}
Expand Down
61 changes: 61 additions & 0 deletions postgres/pgsql-test/__tests__/teardown.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
process.env.LOG_SCOPE = 'pgsql-test';

import { getConnEnvOptions } from '@pgpmjs/env';
import { getPgEnvOptions } from 'pg-env';
import { Client } from 'pg';

import { getConnections } from '../src/connect';

jest.setTimeout(30000);

const getRootConfig = () => {
const conn = getConnEnvOptions();
return getPgEnvOptions({ database: conn.rootDb });
};

const dbExists = async (dbName: string): Promise<boolean> => {
const client = new Client(getRootConfig());
await client.connect();
try {
const res = await client.query(
'select 1 from pg_database where datname = $1',
[dbName]
);
return res.rowCount > 0;
} finally {
await client.end();
}
};

describe('teardown', () => {
it('drops the test database on teardown', async () => {
const { db, teardown } = await getConnections({}, []);
const dbName = db.config.database;

expect(await dbExists(dbName)).toBe(true);

await teardown();

const existsAfter = await dbExists(dbName);
expect(existsAfter).toBe(false);
});

it('drops the database even with an external client connected', async () => {
const { db, teardown } = await getConnections({}, []);
const dbName = db.config.database;

const extraClient = new Client({ ...db.config });
extraClient.on('error', () => {});
await extraClient.connect();
await extraClient.query('select 1');

expect(await dbExists(dbName)).toBe(true);

await teardown();

const existsAfter = await dbExists(dbName);
expect(existsAfter).toBe(false);

await extraClient.end().catch(() => {});
});
});
2 changes: 1 addition & 1 deletion postgres/pgsql-test/src/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ export const getConnections = async (
if (teardownPromise) return teardownPromise;
teardownPromise = (async () => {
manager.beginTeardown();
await teardownPgPools();
await manager.closeAll({ keepDb: teardownOpts.keepDb });
await teardownPgPools();
})();
return teardownPromise;
};
Expand Down
Loading