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/util/db-schema/operators.ts
Views: 687
1
/*
2
TODO: Postgres supports regular expressions and SIMILAR to:
3
https://www.postgresql.org/docs/current/functions-matching.html
4
However, there are significant performance implications
5
to using those. Maybe restrict use of regexp to admins only?
6
*/
7
import LRU from "lru-cache";
8
import { unreachable } from "../misc";
9
10
// ORDER MATTERS! -- this gets looped over and searches happen -- so
11
// the 1-character ops must be after 2-character ops that contain them.
12
// This is ONLY used by the database (and for interaction with it).
13
export const OPERATORS = [
14
"!=",
15
"<>",
16
"<=",
17
">=",
18
"==",
19
"<",
20
">",
21
"=",
22
"IS NOT",
23
"IS",
24
"ILIKE",
25
"LIKE",
26
"NOT ILIKE",
27
"NOT LIKE",
28
"ANY", // only array
29
"MINLEN", // only array
30
"MAXLEN", // only array
31
] as const;
32
export type Operator = (typeof OPERATORS)[number];
33
34
export function isToOperand(operand: string) {
35
switch (`${operand}`.toLowerCase()) {
36
case "null":
37
case "undefined":
38
return "null";
39
case "unknown":
40
return "unknown";
41
case "true":
42
return "true";
43
case "false":
44
return "false";
45
default:
46
return "true";
47
}
48
}
49
50
export function opToFunction(op: Operator): (a, b) => boolean {
51
switch (op) {
52
case "=":
53
case "==":
54
return (a, b) => a === b;
55
case "!=":
56
case "<>":
57
return (a, b) => a !== b;
58
case "<=":
59
return (a, b) => a <= b;
60
case ">=":
61
return (a, b) => a >= b;
62
case "<":
63
return (a, b) => a < b;
64
case ">":
65
return (a, b) => a > b;
66
case "IS":
67
return (a, b) => {
68
// see https://www.postgresql.org/docs/current/functions-comparison.html
69
switch (`${b}`.toLowerCase()) {
70
case "null":
71
case "undefined":
72
case "unknown":
73
return a == null;
74
case "true":
75
return !!a;
76
case "false":
77
return !a;
78
default: // shouldn't happen
79
return false;
80
}
81
};
82
case "IS NOT": {
83
const f = opToFunction("IS");
84
return (a, b) => !f(a, b);
85
}
86
case "LIKE":
87
return (a, b) => {
88
const re = likeRegExp(b);
89
return `${a}`.match(re) != null;
90
};
91
case "NOT LIKE": {
92
const f = opToFunction("LIKE");
93
return (a, b) => !f(a, b);
94
}
95
case "ILIKE":
96
return (a, b) => {
97
const re = likeRegExp(b, true);
98
return `${a}`.match(re) != null;
99
};
100
case "NOT ILIKE": {
101
const f = opToFunction("ILIKE");
102
return (a, b) => !f(a, b);
103
}
104
case "ANY":
105
return (a, b) => {
106
if (!Array.isArray(b)) {
107
return false;
108
}
109
return b.includes(a);
110
};
111
case "MINLEN":
112
// array b has at least a entries
113
return (a, b) => {
114
if (!Array.isArray(b)) {
115
return false;
116
}
117
return b.length >= a;
118
};
119
case "MAXLEN":
120
// array b has at least a entries
121
return (a, b) => {
122
if (!Array.isArray(b)) {
123
return false;
124
}
125
return b.length <= a;
126
};
127
default:
128
unreachable(op);
129
throw Error(`operator must be one of '${JSON.stringify(OPERATORS)}'`);
130
}
131
}
132
133
// This is from
134
// https://stackoverflow.com/questions/1314045/emulating-sql-like-in-javascript
135
136
const likeRegExpCache = new LRU<string, RegExp>({ max: 100 });
137
138
function likeRegExp(expression: string, caseInsensitive?: boolean): RegExp {
139
const key = expression + `${caseInsensitive}`;
140
if (likeRegExpCache.has(key)) {
141
return likeRegExpCache.get(key) as RegExp;
142
}
143
const re = new RegExp(
144
`^${expression
145
.split(/(\[.+?\])/g)
146
.map((s, i) =>
147
i % 2
148
? s.replace(/\\/g, "\\\\")
149
: s.replace(/[-\/\\^$*+?.()|[\]{}%_]/g, (m) => {
150
switch (m) {
151
case "%":
152
return ".*";
153
case "_":
154
return ".";
155
default:
156
return `\\${m}`;
157
}
158
}),
159
)
160
.join("")}$`,
161
caseInsensitive ? "i" : "",
162
);
163
likeRegExpCache.set(key, re);
164
return re;
165
}
166
167