CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/database/pool/pool.ts
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2021 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
import { Client, Pool, PoolClient } from "pg";
7
import { syncSchema } from "@cocalc/database/postgres/schema";
8
9
import {
10
pgdatabase as database,
11
pghost as host,
12
pguser as user,
13
pgssl as ssl,
14
} from "@cocalc/backend/data";
15
import { getLogger } from "@cocalc/backend/logger";
16
import { STATEMENT_TIMEOUT_MS } from "../consts";
17
import getCachedPool, { CacheTime } from "./cached";
18
import dbPassword from "./password";
19
20
const L = getLogger("db:pool");
21
22
export * from "./util";
23
24
let pool: Pool | undefined = undefined;
25
26
export default function getPool(cacheTime?: CacheTime): Pool {
27
if (cacheTime != null) {
28
return getCachedPool(cacheTime);
29
}
30
if (pool == null) {
31
L.debug(
32
`creating a new Pool(host:${host}, database:${database}, user:${user}, ssl:${JSON.stringify(ssl)} statement_timeout:${STATEMENT_TIMEOUT_MS}ms)`,
33
);
34
pool = new Pool({
35
password: dbPassword(),
36
user,
37
host,
38
database,
39
statement_timeout: STATEMENT_TIMEOUT_MS, // fixes https://github.com/sagemathinc/cocalc/issues/6014
40
// the test suite assumes small pool, or there will be random failures sometimes (?)
41
max: process.env.PGDATABASE == TEST ? 2 : undefined,
42
ssl,
43
});
44
const end = pool.end.bind(pool);
45
pool.end = async () => {
46
pool = undefined;
47
end();
48
};
49
}
50
return pool;
51
}
52
53
export async function getTransactionClient(): Promise<PoolClient> {
54
const client = await getPoolClient();
55
try {
56
await client.query("BEGIN");
57
} catch (err) {
58
await client.query("ROLLBACK");
59
client.release();
60
throw err;
61
}
62
return client;
63
}
64
65
export async function getPoolClient(): Promise<PoolClient> {
66
const pool = await getPool();
67
return await pool.connect();
68
}
69
70
export function getClient(): Client {
71
return new Client({ password: dbPassword(), user, host, database, ssl });
72
}
73
74
// This is used for testing. It ensures the schema is loaded and
75
// test database is defined.
76
77
// Call this with {reset:true} to reset the ephemeral
78
// database to a clean state with the schema loaded.
79
// You *can't* just initEphemeralDatabase({reset:true}) in the pre-amble
80
// of jest tests though, since all the tests are running in parallel, and
81
// they would mess up each other's state...
82
const TEST = "smc_ephemeral_testing_database";
83
export async function initEphemeralDatabase({
84
reset,
85
}: { reset?: boolean } = {}) {
86
if (database != TEST) {
87
throw Error(
88
`You can't use initEphemeralDatabase() and test using the database if the env variabe PGDATABASE is not set to ${TEST}!`,
89
);
90
}
91
const db = new Pool({
92
password: dbPassword(),
93
user,
94
host,
95
database: "smc",
96
statement_timeout: STATEMENT_TIMEOUT_MS,
97
ssl,
98
});
99
const { rows } = await db.query(
100
"SELECT COUNT(*) AS count FROM pg_catalog.pg_database WHERE datname = $1",
101
[TEST],
102
);
103
//await db.query(`DROP DATABASE IF EXISTS ${TEST}`);
104
const databaseExists = rows[0].count > 0;
105
if (!databaseExists) {
106
await db.query(`CREATE DATABASE ${TEST}`);
107
}
108
await db.end();
109
// sync the schema
110
await syncSchema();
111
if (databaseExists && reset) {
112
// Drop all data from all tables for a clean slate.
113
// Unfortunately, this can take a little while.
114
await dropAllData();
115
}
116
}
117
118
async function dropAllData() {
119
const pool = getPool();
120
if (pool?.["options"]?.database != TEST) {
121
// safety check!
122
throw Error(
123
`You can't use dropAllData() if the env variabe PGDATABASE is not set to ${TEST}!`,
124
);
125
}
126
const client = await pool.connect();
127
128
try {
129
// Get all table names
130
const result = await client.query(
131
"SELECT tablename FROM pg_tables WHERE schemaname='public'",
132
);
133
const tableNames = result.rows.map((row) => row.tablename);
134
await client.query(`TRUNCATE ${tableNames.join(",")}`);
135
} catch (err) {
136
throw err;
137
} finally {
138
client.release();
139
}
140
}
141
142