prostgles-server 4.2.482 → 4.2.484
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/DboBuilder/DboBuilder.d.ts +4 -0
- package/dist/DboBuilder/DboBuilder.d.ts.map +1 -1
- package/dist/DboBuilder/DboBuilder.js +4 -0
- package/dist/DboBuilder/DboBuilder.js.map +1 -1
- package/dist/DboBuilder/ViewHandler/ViewHandler.d.ts +1 -0
- package/dist/DboBuilder/ViewHandler/ViewHandler.d.ts.map +1 -1
- package/dist/DboBuilder/ViewHandler/parseComplexFilter.d.ts.map +1 -1
- package/dist/DboBuilder/ViewHandler/parseComplexFilter.js +3 -2
- package/dist/DboBuilder/ViewHandler/parseComplexFilter.js.map +1 -1
- package/dist/DboBuilder/ViewHandler/prepareWhere.d.ts +1 -0
- package/dist/DboBuilder/ViewHandler/prepareWhere.d.ts.map +1 -1
- package/dist/DboBuilder/ViewHandler/prepareWhere.js +26 -13
- package/dist/DboBuilder/ViewHandler/prepareWhere.js.map +1 -1
- package/dist/DboBuilder/getCondition.d.ts +1 -0
- package/dist/DboBuilder/getCondition.d.ts.map +1 -1
- package/dist/DboBuilder/getCondition.js +10 -8
- package/dist/DboBuilder/getCondition.js.map +1 -1
- package/dist/DboBuilder/getSubscribeRelatedTables.d.ts.map +1 -1
- package/dist/DboBuilder/getSubscribeRelatedTables.js +26 -19
- package/dist/DboBuilder/getSubscribeRelatedTables.js.map +1 -1
- package/dist/{Filtering.d.ts → Filtering/Filtering.d.ts} +5 -7
- package/dist/Filtering/Filtering.d.ts.map +1 -0
- package/dist/Filtering/Filtering.js +244 -0
- package/dist/Filtering/Filtering.js.map +1 -0
- package/dist/Filtering/getFilterItemCondition.d.ts +7 -0
- package/dist/Filtering/getFilterItemCondition.d.ts.map +1 -0
- package/dist/Filtering/getFilterItemCondition.js +186 -0
- package/dist/Filtering/getFilterItemCondition.js.map +1 -0
- package/dist/Filtering/parseFilterRightValue.d.ts +8 -0
- package/dist/Filtering/parseFilterRightValue.d.ts.map +1 -0
- package/dist/Filtering/parseFilterRightValue.js +23 -0
- package/dist/Filtering/parseFilterRightValue.js.map +1 -0
- package/dist/PubSubManager/PubSubManager.d.ts +23 -2
- package/dist/PubSubManager/PubSubManager.d.ts.map +1 -1
- package/dist/PubSubManager/PubSubManager.js.map +1 -1
- package/dist/PubSubManager/refreshTriggers.d.ts.map +1 -1
- package/dist/PubSubManager/refreshTriggers.js +5 -1
- package/dist/PubSubManager/refreshTriggers.js.map +1 -1
- package/lib/DboBuilder/DboBuilder.ts +4 -0
- package/lib/DboBuilder/ViewHandler/parseComplexFilter.ts +2 -5
- package/lib/DboBuilder/ViewHandler/prepareWhere.ts +31 -16
- package/lib/DboBuilder/getCondition.ts +15 -13
- package/lib/DboBuilder/getSubscribeRelatedTables.ts +43 -23
- package/lib/Filtering/Filtering.ts +286 -0
- package/lib/Filtering/getFilterItemCondition.ts +205 -0
- package/lib/Filtering/parseFilterRightValue.ts +24 -0
- package/lib/PubSubManager/PubSubManager.ts +11 -1
- package/lib/PubSubManager/refreshTriggers.ts +7 -2
- package/package.json +1 -1
- package/dist/Filtering.d.ts.map +0 -1
- package/dist/Filtering.js +0 -418
- package/dist/Filtering.js.map +0 -1
- package/lib/Filtering.ts +0 -468
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GeomFilterKeys,
|
|
3
|
+
GeomFilter_Funcs,
|
|
4
|
+
TextFilterKeys,
|
|
5
|
+
TextFilter_FullTextSearchFilterKeys,
|
|
6
|
+
includes,
|
|
7
|
+
isDefined,
|
|
8
|
+
isObject,
|
|
9
|
+
} from "prostgles-types";
|
|
10
|
+
import { parseFilterRightValue } from "./parseFilterRightValue";
|
|
11
|
+
import { FILTER_OPERAND_TO_SQL_OPERAND, FILTER_OPERANDS } from "./Filtering";
|
|
12
|
+
import type { SelectItemValidated } from "../DboBuilder/QueryBuilder/QueryBuilder";
|
|
13
|
+
import { pgp } from "../DboBuilder/DboBuilderTypes";
|
|
14
|
+
|
|
15
|
+
export const getFilterItemCondition = ({
|
|
16
|
+
selItem,
|
|
17
|
+
leftQ,
|
|
18
|
+
rightF,
|
|
19
|
+
}: {
|
|
20
|
+
leftQ: string;
|
|
21
|
+
selItem: SelectItemValidated;
|
|
22
|
+
rightF: unknown;
|
|
23
|
+
}): string => {
|
|
24
|
+
const asValue = (v: any) => pgp.as.format("$1", [v]);
|
|
25
|
+
|
|
26
|
+
const parseRightVal = (val: any, expect?: "csv" | "array" | "json" | "jsonb") => {
|
|
27
|
+
try {
|
|
28
|
+
return parseFilterRightValue(val, { selectItem: selItem, expect });
|
|
29
|
+
} catch (e: any) {
|
|
30
|
+
throw new Error(e);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/* Matching sel item */
|
|
35
|
+
if (isObject(rightF)) {
|
|
36
|
+
const filterKeys = Object.keys(rightF);
|
|
37
|
+
let filterOperand = filterKeys[0] as (typeof FILTER_OPERANDS)[number];
|
|
38
|
+
|
|
39
|
+
/** JSON cannot be compared so we'll cast it to TEXT */
|
|
40
|
+
if (selItem.column_udt_type === "json" || includes(TextFilterKeys, filterOperand)) {
|
|
41
|
+
leftQ += "::TEXT ";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** It's an object key which means it's an equality comparison against a json object */
|
|
45
|
+
if (selItem.column_udt_type?.startsWith("json") && !includes(FILTER_OPERANDS, filterOperand)) {
|
|
46
|
+
return leftQ + " = " + parseRightVal(rightF);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let filterValue = rightF[filterOperand] as string | null | number | Date | any[];
|
|
50
|
+
const ALLOWED_FUNCS = [...GeomFilter_Funcs, ...TextFilter_FullTextSearchFilterKeys] as const;
|
|
51
|
+
let funcName: undefined | (typeof ALLOWED_FUNCS)[number];
|
|
52
|
+
let funcArgs: undefined | any[];
|
|
53
|
+
|
|
54
|
+
if (
|
|
55
|
+
selItem.column_udt_type === "interval" &&
|
|
56
|
+
isObject(rightF) &&
|
|
57
|
+
Object.values(rightF).every((v) => Number.isFinite(v))
|
|
58
|
+
) {
|
|
59
|
+
filterOperand = "=";
|
|
60
|
+
filterValue = Object.entries(rightF)
|
|
61
|
+
.map(([k, v]) => `${v}${k}`)
|
|
62
|
+
.join(" ");
|
|
63
|
+
} else if (
|
|
64
|
+
(filterKeys.length !== 1 || !isDefined(filterOperand)) &&
|
|
65
|
+
selItem.column_udt_type !== "jsonb"
|
|
66
|
+
) {
|
|
67
|
+
throw new Error("Bad filter. Expecting one key only");
|
|
68
|
+
} else if (isObject(filterValue) && !(filterValue instanceof Date)) {
|
|
69
|
+
/**
|
|
70
|
+
* Filter notation
|
|
71
|
+
* geom && st_makeenvelope(funcArgs)
|
|
72
|
+
*/
|
|
73
|
+
const filterValueKeys = Object.keys(filterValue);
|
|
74
|
+
funcName = filterValueKeys[0] as any;
|
|
75
|
+
if (includes(ALLOWED_FUNCS, funcName)) {
|
|
76
|
+
funcArgs = filterValue[funcName as any];
|
|
77
|
+
} else {
|
|
78
|
+
funcName = undefined;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** st_makeenvelope */
|
|
83
|
+
if (
|
|
84
|
+
includes(GeomFilterKeys, filterOperand) &&
|
|
85
|
+
funcName &&
|
|
86
|
+
includes(GeomFilter_Funcs, funcName)
|
|
87
|
+
) {
|
|
88
|
+
/**
|
|
89
|
+
* If leftQ is geography then:
|
|
90
|
+
* - err can happen: 'Antipodal (180 degrees long) edge detected!'
|
|
91
|
+
* - inacurrate results at large envelopes due to the curvature of the earth
|
|
92
|
+
* https://gis.stackexchange.com/questions/78816/maximum-size-on-the-bounding-box-with-st-makeenvelope-and-and-geography-colum
|
|
93
|
+
*/
|
|
94
|
+
if (funcName.toLowerCase() === "st_makeenvelope") {
|
|
95
|
+
leftQ += "::geometry";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return `${leftQ} ${filterOperand} ${funcName}${parseRightVal(funcArgs, "csv")}`;
|
|
99
|
+
} else if (["=", "$eq"].includes(filterOperand) && !funcName) {
|
|
100
|
+
if (filterValue === null) return leftQ + " IS NULL ";
|
|
101
|
+
return leftQ + " = " + parseRightVal(filterValue);
|
|
102
|
+
} else if (["<>", "$ne"].includes(filterOperand)) {
|
|
103
|
+
if (filterValue === null) return leftQ + " IS NOT NULL ";
|
|
104
|
+
return leftQ + " <> " + parseRightVal(filterValue);
|
|
105
|
+
} else if ([">", "$gt"].includes(filterOperand)) {
|
|
106
|
+
return leftQ + " > " + parseRightVal(filterValue);
|
|
107
|
+
} else if (["<", "$lt"].includes(filterOperand)) {
|
|
108
|
+
return leftQ + " < " + parseRightVal(filterValue);
|
|
109
|
+
} else if ([">=", "$gte"].includes(filterOperand)) {
|
|
110
|
+
return leftQ + " >= " + parseRightVal(filterValue);
|
|
111
|
+
} else if (["<=", "$lte"].includes(filterOperand)) {
|
|
112
|
+
return leftQ + " <= " + parseRightVal(filterValue);
|
|
113
|
+
} else if (["$in", "$nin"].includes(filterOperand)) {
|
|
114
|
+
const isIn = filterOperand === "$in";
|
|
115
|
+
if (filterValue !== null && !Array.isArray(filterValue)) {
|
|
116
|
+
throw new Error("In filter expects an array");
|
|
117
|
+
}
|
|
118
|
+
if (!filterValue?.length) {
|
|
119
|
+
return isIn ? " FALSE " : " TRUE ";
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const nonNullFilterValues = filterValue.filter((v) => v !== null);
|
|
123
|
+
const conditions: string[] = [];
|
|
124
|
+
if (nonNullFilterValues.length) {
|
|
125
|
+
conditions.push(
|
|
126
|
+
leftQ + (isIn ? " IN " : " NOT IN ") + parseRightVal(nonNullFilterValues, "csv"),
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
if (filterValue.includes(null)) {
|
|
130
|
+
const comparator = isIn ? " IS NULL " : " IS NOT NULL ";
|
|
131
|
+
conditions.push(` ${leftQ} ${comparator} `);
|
|
132
|
+
}
|
|
133
|
+
const joinedConditions = conditions.join(isIn ? " OR " : " AND ");
|
|
134
|
+
return conditions.length > 1 ? `(${joinedConditions})` : joinedConditions;
|
|
135
|
+
} else if (["$between"].includes(filterOperand)) {
|
|
136
|
+
if (!Array.isArray(filterValue) || filterValue.length !== 2) {
|
|
137
|
+
throw new Error("Between filter expects an array of two values");
|
|
138
|
+
}
|
|
139
|
+
return leftQ + " BETWEEN " + asValue(filterValue[0]) + " AND " + asValue(filterValue[1]);
|
|
140
|
+
} else if (["$ilike"].includes(filterOperand)) {
|
|
141
|
+
return leftQ + " ILIKE " + asValue(filterValue);
|
|
142
|
+
} else if (["$like"].includes(filterOperand)) {
|
|
143
|
+
return leftQ + " LIKE " + asValue(filterValue);
|
|
144
|
+
} else if (["$nilike"].includes(filterOperand)) {
|
|
145
|
+
return leftQ + " NOT ILIKE " + asValue(filterValue);
|
|
146
|
+
} else if (["$nlike"].includes(filterOperand)) {
|
|
147
|
+
return leftQ + " NOT LIKE " + asValue(filterValue);
|
|
148
|
+
} else if (filterOperand === "$isDistinctFrom" || filterOperand === "$isNotDistinctFrom") {
|
|
149
|
+
const operator = FILTER_OPERAND_TO_SQL_OPERAND[filterOperand];
|
|
150
|
+
return leftQ + ` ${operator} ` + asValue(filterValue);
|
|
151
|
+
|
|
152
|
+
/* MAYBE TEXT OR MAYBE ARRAY */
|
|
153
|
+
} else if (
|
|
154
|
+
["@>", "<@", "$contains", "$containedBy", "$overlaps", "&&", "@@"].includes(filterOperand)
|
|
155
|
+
) {
|
|
156
|
+
const operand =
|
|
157
|
+
filterOperand === "@@" ? "@@"
|
|
158
|
+
: ["@>", "$contains"].includes(filterOperand) ? "@>"
|
|
159
|
+
: ["&&", "$overlaps"].includes(filterOperand) ? "&&"
|
|
160
|
+
: "<@";
|
|
161
|
+
|
|
162
|
+
if (operand === "<@" || operand === "@>") {
|
|
163
|
+
if (selItem.column_udt_type === "jsonb") {
|
|
164
|
+
const filterValueAsString =
|
|
165
|
+
typeof filterValue === "string" ? filterValue : JSON.stringify(filterValue);
|
|
166
|
+
return leftQ + operand + parseRightVal(filterValueAsString);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/* Array for sure */
|
|
171
|
+
if (Array.isArray(filterValue)) {
|
|
172
|
+
return leftQ + operand + parseRightVal(filterValue, "array");
|
|
173
|
+
|
|
174
|
+
/* FTSQuery */
|
|
175
|
+
} else if (
|
|
176
|
+
["@@"].includes(filterOperand) &&
|
|
177
|
+
includes(TextFilter_FullTextSearchFilterKeys, funcName)
|
|
178
|
+
) {
|
|
179
|
+
let lq = `to_tsvector(${leftQ}::text)`;
|
|
180
|
+
if (selItem.columnPGDataType === "tsvector") lq = leftQ!;
|
|
181
|
+
|
|
182
|
+
const res = `${lq} ${operand} ` + `${funcName}${parseRightVal(funcArgs, "csv")}`;
|
|
183
|
+
|
|
184
|
+
return res;
|
|
185
|
+
} else {
|
|
186
|
+
throw new Error("Unrecognised filter operand: " + filterOperand + " ");
|
|
187
|
+
}
|
|
188
|
+
} else {
|
|
189
|
+
throw new Error("Unrecognised filter operand: " + filterOperand + " ");
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/* Is an equal filter */
|
|
193
|
+
if (rightF === null) {
|
|
194
|
+
return leftQ + " IS NULL ";
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Ensure that when comparing an array to a json column, the array is cast to json
|
|
199
|
+
*/
|
|
200
|
+
let valueStr = asValue(rightF);
|
|
201
|
+
if (selItem.column_udt_type?.startsWith("json") && Array.isArray(rightF)) {
|
|
202
|
+
valueStr = pgp.as.format(`$1::jsonb`, [JSON.stringify(rightF)]);
|
|
203
|
+
}
|
|
204
|
+
return `${leftQ} = ${valueStr}`;
|
|
205
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { pgp } from "../DboBuilder/DboBuilderTypes";
|
|
2
|
+
import { type SelectItemValidated } from "../DboBuilder/QueryBuilder/QueryBuilder";
|
|
3
|
+
|
|
4
|
+
type ParseRightValOpts = {
|
|
5
|
+
expect?: "csv" | "array" | "json" | "jsonb";
|
|
6
|
+
selectItem: SelectItemValidated | undefined;
|
|
7
|
+
};
|
|
8
|
+
export const parseFilterRightValue = (val: any, { expect, selectItem }: ParseRightValOpts) => {
|
|
9
|
+
const asValue = (v: any) => pgp.as.format("$1", [v]);
|
|
10
|
+
const checkIfArr = () => {
|
|
11
|
+
if (!Array.isArray(val)) {
|
|
12
|
+
throw "This type of filter/column expects an Array of items";
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
if (expect === "csv" || expect?.startsWith("json")) {
|
|
16
|
+
checkIfArr();
|
|
17
|
+
return pgp.as.format(`($1:${expect})`, [val]);
|
|
18
|
+
} else if (expect === "array" || selectItem?.columnPGDataType === "ARRAY") {
|
|
19
|
+
checkIfArr();
|
|
20
|
+
return pgp.as.format(" ARRAY[$1:csv]", [val]);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return asValue(val);
|
|
24
|
+
};
|
|
@@ -146,7 +146,17 @@ export type Subscription = Pick<
|
|
|
146
146
|
triggers: AddTriggerParams[];
|
|
147
147
|
};
|
|
148
148
|
|
|
149
|
-
export type
|
|
149
|
+
export type TableTriggerInfo = {
|
|
150
|
+
condition: string;
|
|
151
|
+
hash: string;
|
|
152
|
+
columnInfo: {
|
|
153
|
+
join_condition: string;
|
|
154
|
+
tracked_columns: Record<string, number>;
|
|
155
|
+
where_statement: string;
|
|
156
|
+
} | null;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
export type PubSubManagerTriggers = Map<string, TableTriggerInfo[]>;
|
|
150
160
|
|
|
151
161
|
/**
|
|
152
162
|
* Used to facilitate table subscribe and sync
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PubSubManager } from "./PubSubManager";
|
|
1
|
+
import type { PubSubManager, TableTriggerInfo } from "./PubSubManager";
|
|
2
2
|
|
|
3
3
|
export async function refreshTriggers(this: PubSubManager) {
|
|
4
4
|
const start = Date.now();
|
|
@@ -6,6 +6,7 @@ export async function refreshTriggers(this: PubSubManager) {
|
|
|
6
6
|
table_name: string;
|
|
7
7
|
condition: string;
|
|
8
8
|
condition_hash: string;
|
|
9
|
+
columns_info: TableTriggerInfo["columnInfo"];
|
|
9
10
|
}>(
|
|
10
11
|
`
|
|
11
12
|
SELECT *
|
|
@@ -23,7 +24,11 @@ export async function refreshTriggers(this: PubSubManager) {
|
|
|
23
24
|
this._triggers.set(t.table_name, this._triggers.get(t.table_name) ?? []);
|
|
24
25
|
const tableTriggers = this._triggers.get(t.table_name)!;
|
|
25
26
|
if (!tableTriggers.map((t) => t.condition).includes(t.condition)) {
|
|
26
|
-
tableTriggers.push({
|
|
27
|
+
tableTriggers.push({
|
|
28
|
+
condition: t.condition,
|
|
29
|
+
hash: t.condition_hash,
|
|
30
|
+
columnInfo: t.columns_info,
|
|
31
|
+
});
|
|
27
32
|
}
|
|
28
33
|
});
|
|
29
34
|
|
package/package.json
CHANGED
package/dist/Filtering.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Filtering.d.ts","sourceRoot":"","sources":["../lib/Filtering.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAiBlE,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,wCAAwC,CAAC;AAElF,eAAO,MAAM,eAAe,oSAMlB,CAAC;AAEX,eAAO,MAAM,6BAA6B,EAqBrC,MAAM,CAAC,CAAC,OAAO,eAAe,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;AAEtD;;;GAGG;AACH,KAAK,mBAAmB,GAAG;IACzB,MAAM,EAAE,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,SAAS,CAAC;IAC3C,MAAM,EAAE,mBAAmB,EAAE,GAAG,SAAS,CAAC;IAC1C,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,kBAAkB,EAAE,MAAM,EAAE,CAAC;CAC9B,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,MAAM,mBAAmB,KAAG,MAuW3D,CAAC;AAEF,KAAK,iBAAiB,GAAG;IACvB,MAAM,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;IAC5C,UAAU,EAAE,mBAAmB,GAAG,SAAS,CAAC;CAC7C,CAAC;AACF,eAAO,MAAM,qBAAqB,GAAI,KAAK,GAAG,EAAE,wBAAwB,iBAAiB,WAgBxF,CAAC"}
|