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/jsonb-utils.ts
Views: 687
1
import { is_date as isDate } from "@cocalc/util/misc";
2
3
/*
4
jsonbSet: This little piece of very hard to write (and clever?) code
5
makes it so we can set or **merge in at any nested level** (!)
6
arbitrary JSON objects. We can also delete any key at any
7
level by making the value null or undefined! This is amazingly
8
easy to use in queries -- basically making JSONP with postgres
9
as expressive as RethinkDB REQL (even better in some ways).
10
*/
11
12
// The input desc is an object that describes what is being set, e.g.,
13
// account_id = '... some uuid ';
14
// desc = {"users": {[account_id]:{group:'collaborator'}}}
15
// changes the users JSONB field of the table so that users[account_id] has
16
// the group set to "collaborator".
17
// If the merge field is set then we merge in the change; otherwise,
18
// we replace the value.
19
20
// IMPORTANT: this is a dangerous attack vector -- do not call this function
21
// with unsanitized input from a user!
22
23
export function jsonbSet(
24
desc: object,
25
merge: boolean = false
26
): { set: string; params: any[] } {
27
const params: any[] = [];
28
function pushParam(val: any, type: string): number {
29
if (type.toUpperCase() == "JSONB") {
30
val = JSON.stringify(val); // this is needed by the driver....}
31
}
32
params.push(val);
33
return params.length;
34
}
35
36
function set(field: string, data: object, path: string[]): string {
37
let obj = `COALESCE(${field}#>'{${path.join(",")}}', '{}'::JSONB)`;
38
for (const key in data) {
39
const val = data[key];
40
if (val == null) {
41
// remove key from object
42
obj = `(${obj} - '${key}')`;
43
} else {
44
// set key in object
45
if (merge && typeof val === "object" && !isDate(val)) {
46
const subobj = set(field, val, path.concat([key]));
47
obj = `JSONB_SET(${obj}, '{${key}}', ${subobj})`;
48
} else {
49
// completely replace field[key] with val.
50
obj = `JSONB_SET(${obj}, '{${key}}', $${pushParam(
51
val,
52
"JSONB"
53
)}::JSONB)`;
54
}
55
}
56
}
57
return obj;
58
}
59
60
const v: string[] = [];
61
for (const field in desc) {
62
const data = desc[field];
63
v.push(`${field}=${set(field, data, [])}`);
64
}
65
return { set: v.join(","), params };
66
}
67
68