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/postgres/util.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 { reuseInFlight } from "@cocalc/util/reuse-in-flight";
7
import LRU from "lru-cache";
8
import { Pool } from "pg";
9
import { sha1 } from "@cocalc/backend/misc_node";
10
import getPool from "@cocalc/database/pool";
11
import { is_array } from "@cocalc/util/misc";
12
import lodash from "lodash";
13
14
/* Some random little utils */
15
16
// Convert timestamp fields as returned from postgresql queries
17
// into ms since the epoch, as a number.
18
export function toEpoch(rows: object | object[], fields: string[]): void {
19
if (!is_array(rows)) {
20
rows = [rows];
21
}
22
// @ts-ignore
23
for (const row of rows) {
24
for (const field of fields) {
25
if (row[field]) {
26
row[field] = new Date(row[field]).valueOf();
27
}
28
}
29
}
30
}
31
32
interface LRUQueryCacheOpts {
33
size?: number;
34
ttl_s?: number;
35
}
36
37
/**
38
* A simple LRU cache for postgres queries. This is better than getPool("some string"),
39
* because you're more in control, sha1 key sums to avoid sticking large keys in the cache,
40
* and you can clear the cache any time if you want.
41
*/
42
export class LRUQueryCache {
43
private cache: LRU<string, any>;
44
private pool: Pool;
45
46
/**
47
* Create a new LRU cache for postgres queries.
48
*
49
* @param size number of queries to cache
50
* @param ttl_s time to live in seconds
51
*/
52
constructor(opts: LRUQueryCacheOpts = {}) {
53
const { size = 100, ttl_s = 60 } = opts;
54
55
this.cache = new LRU({
56
max: size,
57
ttl: ttl_s * 1000,
58
});
59
60
this.pool = getPool();
61
}
62
63
public query = reuseInFlight(
64
async (
65
query: string,
66
args: (string | number | Date)[] = [],
67
cached = true,
68
) => {
69
const key = sha1(JSON.stringify([query, ...args]));
70
71
if (cached) {
72
let value = this.cache.get(key);
73
if (value != null) return value;
74
}
75
76
const { rows } = await this.pool.query(query, args);
77
this.cache.set(key, rows);
78
return rows;
79
},
80
);
81
82
public async queryOne<T = any>(
83
query: string,
84
args: (string | number | Date)[] = [],
85
cached = true,
86
): Promise<T | null> {
87
const rows = await this.query(query, args, cached);
88
// NOTE: fallback to "null" is there to avoid serialization errors with next.js
89
return rows[0] ?? null;
90
}
91
92
public clear(): void {
93
this.cache.clear();
94
}
95
}
96
97
// removes the field:null to reduce bandwidth usage
98
export function stripNullFields(rows) {
99
return rows.map((row) => lodash.omitBy(row, lodash.isNull)) as any[];
100
}
101
102