prostgles-server 3.0.38 → 3.0.40
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/DBSchemaBuilder.d.ts.map +1 -1
- package/dist/DBSchemaBuilder.js.map +1 -1
- package/dist/DboBuilder/QueryBuilder/QueryBuilder.d.ts +2 -1
- package/dist/DboBuilder/QueryBuilder/QueryBuilder.d.ts.map +1 -1
- package/dist/DboBuilder/QueryBuilder/QueryBuilder.js +47 -2
- package/dist/DboBuilder/QueryBuilder/QueryBuilder.js.map +1 -1
- package/dist/DboBuilder/QueryBuilder/makeSelectQuery.d.ts +1 -1
- package/dist/DboBuilder/QueryBuilder/makeSelectQuery.d.ts.map +1 -1
- package/dist/DboBuilder/QueryBuilder/makeSelectQuery.js.map +1 -1
- package/dist/DboBuilder/TableHandler.d.ts +62 -0
- package/dist/DboBuilder/TableHandler.d.ts.map +1 -0
- package/dist/DboBuilder/TableHandler.js +304 -0
- package/dist/DboBuilder/TableHandler.js.map +1 -0
- package/dist/DboBuilder/ViewHandler.d.ts +137 -0
- package/dist/DboBuilder/ViewHandler.d.ts.map +1 -0
- package/dist/DboBuilder/ViewHandler.js +1292 -0
- package/dist/DboBuilder/ViewHandler.js.map +1 -0
- package/dist/DboBuilder/delete.d.ts +2 -1
- package/dist/DboBuilder/delete.d.ts.map +1 -1
- package/dist/DboBuilder/delete.js.map +1 -1
- package/dist/DboBuilder/getColumns.d.ts +12 -0
- package/dist/DboBuilder/getColumns.d.ts.map +1 -0
- package/dist/DboBuilder/getColumns.js +95 -0
- package/dist/DboBuilder/getColumns.js.map +1 -0
- package/dist/DboBuilder/insert.d.ts +2 -1
- package/dist/DboBuilder/insert.d.ts.map +1 -1
- package/dist/DboBuilder/insert.js +1 -1
- package/dist/DboBuilder/insert.js.map +1 -1
- package/dist/DboBuilder/insertDataParse.d.ts +2 -1
- package/dist/DboBuilder/insertDataParse.d.ts.map +1 -1
- package/dist/DboBuilder/insertDataParse.js +2 -3
- package/dist/DboBuilder/insertDataParse.js.map +1 -1
- package/dist/DboBuilder/parseUpdateRules.d.ts +18 -0
- package/dist/DboBuilder/parseUpdateRules.d.ts.map +1 -0
- package/dist/DboBuilder/parseUpdateRules.js +119 -0
- package/dist/DboBuilder/parseUpdateRules.js.map +1 -0
- package/dist/DboBuilder/update.d.ts +2 -1
- package/dist/DboBuilder/update.d.ts.map +1 -1
- package/dist/DboBuilder/update.js.map +1 -1
- package/dist/DboBuilder/uploadFile.d.ts +2 -1
- package/dist/DboBuilder/uploadFile.d.ts.map +1 -1
- package/dist/DboBuilder/uploadFile.js.map +1 -1
- package/dist/DboBuilder.d.ts +5 -185
- package/dist/DboBuilder.d.ts.map +1 -1
- package/dist/DboBuilder.js +7 -1773
- package/dist/DboBuilder.js.map +1 -1
- package/dist/FileManager.d.ts.map +1 -1
- package/dist/FileManager.js +4 -4
- package/dist/FileManager.js.map +1 -1
- package/dist/PubSubManager.d.ts.map +1 -1
- package/dist/PubSubManager.js +4 -0
- package/dist/PubSubManager.js.map +1 -1
- package/dist/PublishParser.d.ts.map +1 -1
- package/dist/PublishParser.js.map +1 -1
- package/dist/index.js +0 -38
- package/dist/index.js.map +1 -1
- package/lib/DBSchemaBuilder.d.ts.map +1 -1
- package/lib/DBSchemaBuilder.ts +1 -1
- package/lib/DboBuilder/QueryBuilder/QueryBuilder.d.ts +2 -1
- package/lib/DboBuilder/QueryBuilder/QueryBuilder.d.ts.map +1 -1
- package/lib/DboBuilder/QueryBuilder/QueryBuilder.js +47 -2
- package/lib/DboBuilder/QueryBuilder/QueryBuilder.ts +55 -2
- package/lib/DboBuilder/QueryBuilder/makeSelectQuery.d.ts +1 -1
- package/lib/DboBuilder/QueryBuilder/makeSelectQuery.d.ts.map +1 -1
- package/lib/DboBuilder/QueryBuilder/makeSelectQuery.ts +2 -1
- package/lib/DboBuilder/TableHandler.d.ts +54 -0
- package/lib/DboBuilder/TableHandler.d.ts.map +1 -0
- package/lib/DboBuilder/TableHandler.js +303 -0
- package/lib/DboBuilder/TableHandler.ts +365 -0
- package/lib/DboBuilder/ViewHandler.d.ts +133 -0
- package/lib/DboBuilder/ViewHandler.d.ts.map +1 -0
- package/lib/DboBuilder/ViewHandler.js +1291 -0
- package/lib/DboBuilder/ViewHandler.ts +1542 -0
- package/lib/DboBuilder/delete.d.ts +2 -1
- package/lib/DboBuilder/delete.d.ts.map +1 -1
- package/lib/DboBuilder/delete.ts +2 -1
- package/lib/DboBuilder/getColumns.d.ts +12 -0
- package/lib/DboBuilder/getColumns.d.ts.map +1 -0
- package/lib/DboBuilder/getColumns.js +94 -0
- package/lib/DboBuilder/getColumns.ts +133 -0
- package/lib/DboBuilder/insert.d.ts +2 -1
- package/lib/DboBuilder/insert.d.ts.map +1 -1
- package/lib/DboBuilder/insert.js +1 -1
- package/lib/DboBuilder/insert.ts +3 -2
- package/lib/DboBuilder/insertDataParse.d.ts +2 -1
- package/lib/DboBuilder/insertDataParse.d.ts.map +1 -1
- package/lib/DboBuilder/insertDataParse.js +2 -3
- package/lib/DboBuilder/insertDataParse.ts +6 -5
- package/lib/DboBuilder/parseUpdateRules.d.ts +18 -0
- package/lib/DboBuilder/parseUpdateRules.d.ts.map +1 -0
- package/lib/DboBuilder/parseUpdateRules.js +118 -0
- package/lib/DboBuilder/parseUpdateRules.ts +156 -0
- package/lib/DboBuilder/update.d.ts +2 -1
- package/lib/DboBuilder/update.d.ts.map +1 -1
- package/lib/DboBuilder/update.ts +2 -1
- package/lib/DboBuilder/uploadFile.d.ts +2 -1
- package/lib/DboBuilder/uploadFile.d.ts.map +1 -1
- package/lib/DboBuilder/uploadFile.ts +2 -1
- package/lib/DboBuilder.d.ts +5 -185
- package/lib/DboBuilder.d.ts.map +1 -1
- package/lib/DboBuilder.js +7 -1773
- package/lib/DboBuilder.ts +170 -2296
- package/lib/FileManager.d.ts.map +1 -1
- package/lib/FileManager.js +4 -4
- package/lib/FileManager.ts +3 -1
- package/lib/PubSubManager.d.ts.map +1 -1
- package/lib/PubSubManager.js +4 -0
- package/lib/PubSubManager.ts +6 -1
- package/lib/PublishParser.d.ts.map +1 -1
- package/lib/PublishParser.ts +3 -1
- package/lib/SyncReplication.ts +1 -1
- package/lib/index.js +0 -38
- package/lib/index.ts +1 -53
- package/package.json +2 -2
- package/tests/client/PID.txt +1 -1
- package/tests/client_only_queries.ts +1 -1
- package/tests/server/package-lock.json +409 -250
- package/tests/server/package.json +1 -1
package/dist/DboBuilder.js
CHANGED
|
@@ -5,9 +5,8 @@
|
|
|
5
5
|
*--------------------------------------------------------------------------------------------*/
|
|
6
6
|
var _a;
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
exports.canEXECUTE = exports.prepareSort = exports.postgresToTsType = exports.isPlainObject = exports.DboBuilder = exports.
|
|
8
|
+
exports.canEXECUTE = exports.prepareSort = exports.postgresToTsType = exports.isPlainObject = exports.DboBuilder = exports.parseError = exports.EXISTS_KEYS = exports.makeErr = exports.escapeTSNames = exports.pgp = void 0;
|
|
9
9
|
const Bluebird = require("bluebird");
|
|
10
|
-
const makeSelectQuery_1 = require("./DboBuilder/QueryBuilder/makeSelectQuery");
|
|
11
10
|
const pgPromise = require("pg-promise");
|
|
12
11
|
const runSQL_1 = require("./DboBuilder/runSQL");
|
|
13
12
|
const prostgles_types_1 = require("prostgles-types");
|
|
@@ -25,24 +24,12 @@ const prostgles_types_1 = require("prostgles-types");
|
|
|
25
24
|
// return []
|
|
26
25
|
// }
|
|
27
26
|
const utils_1 = require("./utils");
|
|
28
|
-
const QueryBuilder_1 = require("./DboBuilder/QueryBuilder/QueryBuilder");
|
|
29
27
|
const PubSubManager_1 = require("./PubSubManager");
|
|
30
|
-
const
|
|
31
|
-
const insert_1 = require("./DboBuilder/insert");
|
|
32
|
-
const update_1 = require("./DboBuilder/update");
|
|
33
|
-
const delete_1 = require("./DboBuilder/delete");
|
|
34
|
-
const Filtering_1 = require("./Filtering");
|
|
28
|
+
const ViewHandler_1 = require("./DboBuilder/ViewHandler");
|
|
35
29
|
exports.pgp = pgPromise({
|
|
36
30
|
promiseLib: Bluebird
|
|
37
31
|
// ,query: function (e) { console.log({psql: e.query, params: e.params}); }
|
|
38
32
|
});
|
|
39
|
-
function replaceNonAlphaNumeric(string, replacement = "_") {
|
|
40
|
-
return string.replace(/[\W_]+/g, replacement);
|
|
41
|
-
}
|
|
42
|
-
function capitalizeFirstLetter(string, nonalpha_replacement) {
|
|
43
|
-
const str = replaceNonAlphaNumeric(string, nonalpha_replacement);
|
|
44
|
-
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
45
|
-
}
|
|
46
33
|
function snakify(str, capitalize = false) {
|
|
47
34
|
return str.split("").map((c, i) => {
|
|
48
35
|
if (!i) {
|
|
@@ -104,7 +91,6 @@ function makeErr(err, localParams, view, allowedKeys) {
|
|
|
104
91
|
}
|
|
105
92
|
exports.makeErr = makeErr;
|
|
106
93
|
exports.EXISTS_KEYS = ["$exists", "$notExists", "$existsJoined", "$notExistsJoined"];
|
|
107
|
-
const FILTER_FUNCS = QueryBuilder_1.FUNCTIONS.filter(f => f.canBeUsedForFilter);
|
|
108
94
|
/**
|
|
109
95
|
* Ensure the error is an Object and has
|
|
110
96
|
*/
|
|
@@ -124,1749 +110,9 @@ function parseError(e, caller) {
|
|
|
124
110
|
return result;
|
|
125
111
|
}
|
|
126
112
|
exports.parseError = parseError;
|
|
127
|
-
class ColSet {
|
|
128
|
-
constructor(columns, tableName) {
|
|
129
|
-
this.opts = { columns, tableName, colNames: columns.map(c => c.name) };
|
|
130
|
-
}
|
|
131
|
-
async getRow(data, allowedCols, dbTx, validate) {
|
|
132
|
-
const badCol = allowedCols.find(c => !this.opts.colNames.includes(c));
|
|
133
|
-
if (!allowedCols || badCol) {
|
|
134
|
-
throw "Missing or unexpected columns: " + badCol;
|
|
135
|
-
}
|
|
136
|
-
if ((0, prostgles_types_1.isEmpty)(data))
|
|
137
|
-
throw "No data";
|
|
138
|
-
let row = (0, PubSubManager_1.pickKeys)(data, allowedCols);
|
|
139
|
-
if (validate) {
|
|
140
|
-
row = await validate(row, dbTx);
|
|
141
|
-
}
|
|
142
|
-
const rowKeys = Object.keys(row);
|
|
143
|
-
return rowKeys.map(key => {
|
|
144
|
-
const col = this.opts.columns.find(c => c.name === key);
|
|
145
|
-
if (!col)
|
|
146
|
-
throw "Unexpected missing col name";
|
|
147
|
-
/**
|
|
148
|
-
* Add utility functions for PostGIS data
|
|
149
|
-
*/
|
|
150
|
-
let escapedVal = "";
|
|
151
|
-
if (["geometry", "geography"].includes(col.udt_name) && row[key] && isPlainObject(row[key])) {
|
|
152
|
-
const basicFunc = (args) => {
|
|
153
|
-
return args.map(arg => (0, PubSubManager_1.asValue)(arg)).join(", ");
|
|
154
|
-
};
|
|
155
|
-
const basicFuncNames = ["ST_GeomFromText", "ST_Point", "ST_MakePoint", "ST_MakePointM", "ST_PointFromText", "ST_GeomFromEWKT", "ST_GeomFromGeoJSON"];
|
|
156
|
-
const dataKeys = Object.keys(row[key]);
|
|
157
|
-
const funcName = dataKeys[0];
|
|
158
|
-
const funcExists = basicFuncNames.includes(funcName);
|
|
159
|
-
const funcArgs = row[key]?.[funcName];
|
|
160
|
-
if (dataKeys.length !== 1 || !funcExists || !Array.isArray(funcArgs)) {
|
|
161
|
-
throw `Expecting only one function key (${basicFuncNames.join(", ")}) \nwith an array of arguments \n within column (${key}) data but got: ${JSON.stringify(row[key])} \nExample: { geo_col: { ST_GeomFromText: ["POINT(-71.064544 42.28787)", 4326] } }`;
|
|
162
|
-
}
|
|
163
|
-
escapedVal = `${funcName}(${basicFunc(funcArgs)})`;
|
|
164
|
-
}
|
|
165
|
-
else {
|
|
166
|
-
/** Prevent pg-promise formatting jsonb */
|
|
167
|
-
const colIsJSON = ["json", "jsonb"].includes(col.data_type);
|
|
168
|
-
escapedVal = exports.pgp.as.format(colIsJSON ? "$1:json" : "$1", [row[key]]);
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* Cast to type to avoid array errors (they do not cast automatically)
|
|
172
|
-
*/
|
|
173
|
-
escapedVal += `::${col.udt_name}`;
|
|
174
|
-
return {
|
|
175
|
-
escapedCol: (0, prostgles_types_1.asName)(key),
|
|
176
|
-
escapedVal,
|
|
177
|
-
};
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
async getInsertQuery(data, allowedCols, dbTx, validate) {
|
|
181
|
-
const res = (await Promise.all((Array.isArray(data) ? data : [data]).map(async (d) => {
|
|
182
|
-
const rowParts = await this.getRow(d, allowedCols, dbTx, validate);
|
|
183
|
-
const select = rowParts.map(r => r.escapedCol).join(", "), values = rowParts.map(r => r.escapedVal).join(", ");
|
|
184
|
-
return `INSERT INTO ${(0, prostgles_types_1.asName)(this.opts.tableName)} (${select}) VALUES (${values})`;
|
|
185
|
-
}))).join(";\n") + " ";
|
|
186
|
-
return res;
|
|
187
|
-
}
|
|
188
|
-
async getUpdateQuery(data, allowedCols, dbTx, validate) {
|
|
189
|
-
const res = (await Promise.all((Array.isArray(data) ? data : [data]).map(async (d) => {
|
|
190
|
-
const rowParts = await this.getRow(d, allowedCols, dbTx, validate);
|
|
191
|
-
return `UPDATE ${(0, prostgles_types_1.asName)(this.opts.tableName)} SET ` + rowParts.map(r => `${r.escapedCol} = ${r.escapedVal} `).join(",\n");
|
|
192
|
-
}))).join(";\n") + " ";
|
|
193
|
-
return res;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
class ViewHandler {
|
|
197
|
-
constructor(db, tableOrViewInfo, dboBuilder, t, dbTX, joinPaths) {
|
|
198
|
-
this.tsColumnDefs = [];
|
|
199
|
-
this.is_view = true;
|
|
200
|
-
this.filterDef = "";
|
|
201
|
-
// pubSubManager: PubSubManager;
|
|
202
|
-
this.is_media = false;
|
|
203
|
-
if (!db || !tableOrViewInfo)
|
|
204
|
-
throw "";
|
|
205
|
-
this.db = db;
|
|
206
|
-
this.t = t;
|
|
207
|
-
this.dbTX = dbTX;
|
|
208
|
-
this.joinPaths = joinPaths;
|
|
209
|
-
this.tableOrViewInfo = tableOrViewInfo;
|
|
210
|
-
this.name = tableOrViewInfo.name;
|
|
211
|
-
this.escapedName = (0, prostgles_types_1.asName)(this.name);
|
|
212
|
-
this.columns = tableOrViewInfo.columns;
|
|
213
|
-
/* cols are sorted by name to reduce .d.ts schema rewrites */
|
|
214
|
-
this.columnsForTypes = tableOrViewInfo.columns.slice(0).sort((a, b) => a.name.localeCompare(b.name));
|
|
215
|
-
this.column_names = tableOrViewInfo.columns.map(c => c.name);
|
|
216
|
-
// this.pubSubManager = pubSubManager;
|
|
217
|
-
this.dboBuilder = dboBuilder;
|
|
218
|
-
this.joins = this.dboBuilder.joins ?? [];
|
|
219
|
-
// fix this
|
|
220
|
-
// and also make hot schema reload over ws
|
|
221
|
-
this.colSet = new ColSet(this.columns, this.name);
|
|
222
|
-
const { $and: $and_key, $or: $or_key } = this.dboBuilder.prostgles.keywords;
|
|
223
|
-
// this.tsDataName = snakify(this.name, true);
|
|
224
|
-
// if(this.tsDataName === "T") this.tsDataName = this.tsDataName + "_";
|
|
225
|
-
// this.tsDataDef = `export type ${this.tsDataName} = {\n`;
|
|
226
|
-
this.columnsForTypes.map(({ name, udt_name, is_nullable }) => {
|
|
227
|
-
this.tsColumnDefs.push(`${escapeTSNames(name)}?: ${postgresToTsType(udt_name)} ${is_nullable ? " | null " : ""};`);
|
|
228
|
-
});
|
|
229
|
-
// this.tsDataDef += "};";
|
|
230
|
-
// this.tsDataDef += "\n";
|
|
231
|
-
// this.tsDataDef += `export type ${this.tsDataName}_Filter = ${this.tsDataName} | object | { ${JSON.stringify($and_key)}: (${this.tsDataName} | object)[] } | { ${JSON.stringify($or_key)}: (${this.tsDataName} | object)[] } `;
|
|
232
|
-
// this.filterDef = ` ${this.tsDataName}_Filter `;
|
|
233
|
-
// const filterDef = this.filterDef;
|
|
234
|
-
// this.tsDboDefs = [
|
|
235
|
-
// ` getColumns: () => Promise<any[]>;`,
|
|
236
|
-
// ` find: (filter?: ${filterDef}, selectParams?: SelectParams) => Promise<Partial<${this.tsDataName} & { [x: string]: any }>[]>;`,
|
|
237
|
-
// ` findOne: (filter?: ${filterDef}, selectParams?: SelectParams) => Promise<Partial<${this.tsDataName} & { [x: string]: any }>>;`,
|
|
238
|
-
// ` subscribe: (filter: ${filterDef}, params: SelectParams, onData: (items: Partial<${this.tsDataName} & { [x: string]: any }>[]) => any) => Promise<{ unsubscribe: () => any }>;`,
|
|
239
|
-
// ` subscribeOne: (filter: ${filterDef}, params: SelectParams, onData: (item: Partial<${this.tsDataName} & { [x: string]: any }>) => any) => Promise<{ unsubscribe: () => any }>;`,
|
|
240
|
-
// ` count: (filter?: ${filterDef}) => Promise<number>;`
|
|
241
|
-
// ];
|
|
242
|
-
// this.makeDef();
|
|
243
|
-
}
|
|
244
|
-
// makeDef(){
|
|
245
|
-
// this.tsDboName = `DBO_${snakify(this.name)}`;
|
|
246
|
-
// this.tsDboDef = `export type ${this.tsDboName} = {\n ${this.tsDboDefs.join("\n")} \n};\n`;
|
|
247
|
-
// }
|
|
248
|
-
getRowHashSelect(allowedFields, alias, tableAlias) {
|
|
249
|
-
let allowed_cols = this.column_names;
|
|
250
|
-
if (allowedFields)
|
|
251
|
-
allowed_cols = this.parseFieldFilter(allowedFields);
|
|
252
|
-
return "md5(" +
|
|
253
|
-
allowed_cols
|
|
254
|
-
/* CTID not available in AFTER trigger */
|
|
255
|
-
// .concat(this.is_view? [] : ["ctid"])
|
|
256
|
-
.sort()
|
|
257
|
-
.map(f => (tableAlias ? ((0, prostgles_types_1.asName)(tableAlias) + ".") : "") + (0, prostgles_types_1.asName)(f))
|
|
258
|
-
.map(f => `md5(coalesce(${f}::text, 'dd'))`)
|
|
259
|
-
.join(" || ") +
|
|
260
|
-
`)` + (alias ? ` as ${(0, prostgles_types_1.asName)(alias)}` : "");
|
|
261
|
-
}
|
|
262
|
-
async validateViewRules(args) {
|
|
263
|
-
const { fields, filterFields, returningFields, forcedFilter, dynamicFields, rule, } = args;
|
|
264
|
-
/* Safely test publish rules */
|
|
265
|
-
if (fields) {
|
|
266
|
-
try {
|
|
267
|
-
const _fields = this.parseFieldFilter(fields);
|
|
268
|
-
if (this.is_media && rule === "insert" && !_fields.includes("id")) {
|
|
269
|
-
throw "Must allow id insert for media table";
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
catch (e) {
|
|
273
|
-
throw ` issue with publish.${this.name}.${rule}.fields: \nVALUE: ` + JSON.stringify(fields, null, 2) + "\nERROR: " + JSON.stringify(e, null, 2);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
if (filterFields) {
|
|
277
|
-
try {
|
|
278
|
-
this.parseFieldFilter(filterFields);
|
|
279
|
-
}
|
|
280
|
-
catch (e) {
|
|
281
|
-
throw ` issue with publish.${this.name}.${rule}.filterFields: \nVALUE: ` + JSON.stringify(filterFields, null, 2) + "\nERROR: " + JSON.stringify(e, null, 2);
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
if (returningFields) {
|
|
285
|
-
try {
|
|
286
|
-
this.parseFieldFilter(returningFields);
|
|
287
|
-
}
|
|
288
|
-
catch (e) {
|
|
289
|
-
throw ` issue with publish.${this.name}.${rule}.returningFields: \nVALUE: ` + JSON.stringify(returningFields, null, 2) + "\nERROR: " + JSON.stringify(e, null, 2);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
if (forcedFilter) {
|
|
293
|
-
try {
|
|
294
|
-
await this.find(forcedFilter, { limit: 0 });
|
|
295
|
-
}
|
|
296
|
-
catch (e) {
|
|
297
|
-
throw ` issue with publish.${this.name}.${rule}.forcedFilter: \nVALUE: ` + JSON.stringify(forcedFilter, null, 2) + "\nERROR: " + JSON.stringify(e, null, 2);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
if (dynamicFields) {
|
|
301
|
-
for await (const dfieldRule of dynamicFields) {
|
|
302
|
-
try {
|
|
303
|
-
const { fields, filter } = dfieldRule;
|
|
304
|
-
this.parseFieldFilter(fields);
|
|
305
|
-
await this.find(filter, { limit: 0 });
|
|
306
|
-
}
|
|
307
|
-
catch (e) {
|
|
308
|
-
throw ` issue with publish.${this.name}.${rule}.dynamicFields: \nVALUE: ` + JSON.stringify(dfieldRule, null, 2) + "\nERROR: " + JSON.stringify(e, null, 2);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
return true;
|
|
313
|
-
}
|
|
314
|
-
getShortestJoin(table1, table2, startAlias, isInner = false) {
|
|
315
|
-
// let searchedTables = [], result;
|
|
316
|
-
// while (!result && searchedTables.length <= this.joins.length * 2){
|
|
317
|
-
// }
|
|
318
|
-
const getJoinCondition = (on, leftTable, rightTable) => {
|
|
319
|
-
return on.map(cond => Object.keys(cond).map(lKey => `${leftTable}.${lKey} = ${rightTable}.${cond[lKey]}`).join("\nAND ")).join(" OR ");
|
|
320
|
-
};
|
|
321
|
-
let toOne = true, query = this.joins.map(({ tables, on, type }, i) => {
|
|
322
|
-
if (type.split("-")[1] === "many") {
|
|
323
|
-
toOne = false;
|
|
324
|
-
}
|
|
325
|
-
const tl = `tl${startAlias + i}`, tr = `tr${startAlias + i}`;
|
|
326
|
-
return `FROM ${tables[0]} ${tl} ${isInner ? "INNER" : "LEFT"} JOIN ${tables[1]} ${tr} ON ${getJoinCondition(on, tl, tr)}`;
|
|
327
|
-
}).join("\n");
|
|
328
|
-
return { query, toOne: false };
|
|
329
|
-
}
|
|
330
|
-
getJoins(source, target, path, checkTableConfig) {
|
|
331
|
-
let paths = [];
|
|
332
|
-
if (!this.joinPaths)
|
|
333
|
-
throw `${source} - ${target} Join info missing or dissallowed`;
|
|
334
|
-
if (path && !path.length)
|
|
335
|
-
throw `Empty join path ( $path ) specified for ${source} <-> ${target}`;
|
|
336
|
-
/* Find the join path between tables */
|
|
337
|
-
if (checkTableConfig) {
|
|
338
|
-
const tableConfigJoinInfo = this.dboBuilder?.prostgles?.tableConfigurator?.getJoinInfo(source, target);
|
|
339
|
-
if (tableConfigJoinInfo)
|
|
340
|
-
return tableConfigJoinInfo;
|
|
341
|
-
}
|
|
342
|
-
let jp;
|
|
343
|
-
if (!path) {
|
|
344
|
-
jp = this.joinPaths.find(j => j.t1 === source && j.t2 === target);
|
|
345
|
-
}
|
|
346
|
-
else {
|
|
347
|
-
jp = {
|
|
348
|
-
t1: source,
|
|
349
|
-
t2: target,
|
|
350
|
-
path
|
|
351
|
-
};
|
|
352
|
-
}
|
|
353
|
-
/* Self join */
|
|
354
|
-
if (source === target) {
|
|
355
|
-
const tableHandler = this.dboBuilder.tablesOrViews?.find(t => t.name === source);
|
|
356
|
-
if (!tableHandler)
|
|
357
|
-
throw `Table not found for joining ${source}`;
|
|
358
|
-
const fcols = tableHandler.columns.filter(c => c.references?.some(({ ftable }) => ftable === this.name));
|
|
359
|
-
if (fcols.length) {
|
|
360
|
-
throw "Self referencing not supported yet";
|
|
361
|
-
// return {
|
|
362
|
-
// paths: [{
|
|
363
|
-
// source,
|
|
364
|
-
// target,
|
|
365
|
-
// table: target,
|
|
366
|
-
// on: fcols.map(fc => fc.references!.some(({ fcols }) => fcols.map(fcol => [fc.name, fcol])))
|
|
367
|
-
// }],
|
|
368
|
-
// expectOne: false
|
|
369
|
-
// }
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
if (!jp || !this.joinPaths.find(j => path ? j.path.join() === path.join() : j.t1 === source && j.t2 === target)) {
|
|
373
|
-
throw `Joining ${source} <-...-> ${target} dissallowed or missing`;
|
|
374
|
-
}
|
|
375
|
-
/* Make the join chain info excluding root table */
|
|
376
|
-
paths = (path || jp.path).slice(1).map((t2, i, arr) => {
|
|
377
|
-
const t1 = i === 0 ? source : arr[i - 1];
|
|
378
|
-
this.joins ?? (this.joins = this.dboBuilder.joins);
|
|
379
|
-
/* Get join options */
|
|
380
|
-
const jo = this.joins.find(j => j.tables.includes(t1) && j.tables.includes(t2));
|
|
381
|
-
if (!jo)
|
|
382
|
-
throw `Joining ${t1} <-> ${t2} dissallowed or missing`;
|
|
383
|
-
;
|
|
384
|
-
let on = [];
|
|
385
|
-
jo.on.map(cond => {
|
|
386
|
-
let condArr = [];
|
|
387
|
-
Object.keys(cond).map(leftKey => {
|
|
388
|
-
const rightKey = cond[leftKey];
|
|
389
|
-
/* Left table is joining on keys */
|
|
390
|
-
if (jo.tables[0] === t1) {
|
|
391
|
-
condArr.push([leftKey, rightKey]);
|
|
392
|
-
/* Left table is joining on values */
|
|
393
|
-
}
|
|
394
|
-
else {
|
|
395
|
-
condArr.push([rightKey, leftKey]);
|
|
396
|
-
}
|
|
397
|
-
});
|
|
398
|
-
on.push(condArr);
|
|
399
|
-
});
|
|
400
|
-
return {
|
|
401
|
-
source,
|
|
402
|
-
target,
|
|
403
|
-
table: t2,
|
|
404
|
-
on
|
|
405
|
-
};
|
|
406
|
-
});
|
|
407
|
-
let expectOne = false;
|
|
408
|
-
// paths.map(({ source, target, on }, i) => {
|
|
409
|
-
// if(expectOne && on.length === 1){
|
|
410
|
-
// const sourceCol = on[0][1];
|
|
411
|
-
// const targetCol = on[0][0];
|
|
412
|
-
// const sCol = this.dboBuilder.dbo[source].columns.find(c => c.name === sourceCol)
|
|
413
|
-
// const tCol = this.dboBuilder.dbo[target].columns.find(c => c.name === targetCol)
|
|
414
|
-
// console.log({ sourceCol, targetCol, sCol, source, tCol, target, on})
|
|
415
|
-
// expectOne = sCol.is_pkey && tCol.is_pkey
|
|
416
|
-
// }
|
|
417
|
-
// })
|
|
418
|
-
return {
|
|
419
|
-
paths,
|
|
420
|
-
expectOne
|
|
421
|
-
};
|
|
422
|
-
}
|
|
423
|
-
checkFilter(filter) {
|
|
424
|
-
if (filter === null || filter && !isPojoObject(filter))
|
|
425
|
-
throw `invalid filter -> ${JSON.stringify(filter)} \nExpecting: undefined | {} | { field_name: "value" } | { field: { $gt: 22 } } ... `;
|
|
426
|
-
}
|
|
427
|
-
async getInfo(lang, param2, param3, tableRules, localParams) {
|
|
428
|
-
const p = this.getValidatedRules(tableRules, localParams);
|
|
429
|
-
if (!p.getInfo)
|
|
430
|
-
throw "Not allowed";
|
|
431
|
-
let has_media = undefined;
|
|
432
|
-
const mediaTable = this.dboBuilder.prostgles?.opts?.fileTable?.tableName;
|
|
433
|
-
if (!this.is_media && mediaTable) {
|
|
434
|
-
const joinConf = this.dboBuilder.prostgles?.opts?.fileTable?.referencedTables?.[this.name];
|
|
435
|
-
if (joinConf) {
|
|
436
|
-
has_media = typeof joinConf === "string" ? joinConf : "one";
|
|
437
|
-
}
|
|
438
|
-
else {
|
|
439
|
-
const jp = this.dboBuilder.joinPaths.find(jp => jp.t1 === this.name && jp.t2 === mediaTable);
|
|
440
|
-
if (jp && jp.path.length <= 3) {
|
|
441
|
-
if (jp.path.length <= 2) {
|
|
442
|
-
has_media = "one";
|
|
443
|
-
}
|
|
444
|
-
else {
|
|
445
|
-
await Promise.all(jp.path.map(async (tableName) => {
|
|
446
|
-
const pkeyFcols = this?.dboBuilder?.dbo?.[tableName]?.columns?.filter(c => c.is_pkey).map(c => c.name);
|
|
447
|
-
const cols = this?.dboBuilder?.dbo?.[tableName]?.columns?.filter(c => c?.references?.some(({ ftable }) => jp.path.includes(ftable)));
|
|
448
|
-
if (cols && cols.length && has_media !== "many") {
|
|
449
|
-
if (cols.some(c => !pkeyFcols?.includes(c.name))) {
|
|
450
|
-
has_media = "many";
|
|
451
|
-
}
|
|
452
|
-
else {
|
|
453
|
-
has_media = "one";
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
}));
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
return {
|
|
462
|
-
oid: this.tableOrViewInfo.oid,
|
|
463
|
-
comment: this.tableOrViewInfo.comment,
|
|
464
|
-
info: this.dboBuilder.prostgles?.tableConfigurator?.getTableInfo({ tableName: this.name, lang }),
|
|
465
|
-
is_media: this.is_media,
|
|
466
|
-
is_view: this.is_view,
|
|
467
|
-
has_media,
|
|
468
|
-
media_table_name: mediaTable,
|
|
469
|
-
dynamicRules: {
|
|
470
|
-
update: Boolean(tableRules?.update?.dynamicFields?.length)
|
|
471
|
-
}
|
|
472
|
-
};
|
|
473
|
-
}
|
|
474
|
-
// TODO: fix renamed table trigger problem
|
|
475
|
-
async getColumns(lang, params, _param3, tableRules, localParams) {
|
|
476
|
-
try {
|
|
477
|
-
const p = this.getValidatedRules(tableRules, localParams);
|
|
478
|
-
if (!p.getColumns)
|
|
479
|
-
throw "Not allowed";
|
|
480
|
-
// console.log("getColumns", this.name, this.columns.map(c => c.name))
|
|
481
|
-
let dynamicUpdateFields;
|
|
482
|
-
if (params && "parseUpdateRules" in this && this.parseUpdateRules) {
|
|
483
|
-
if (!isPlainObject(params) || !isPlainObject(params.data) || !isPlainObject(params.filter) || params.rule !== "update") {
|
|
484
|
-
throw "params must be { rule: 'update', data, filter } but got: " + JSON.stringify(params);
|
|
485
|
-
}
|
|
486
|
-
if (!tableRules?.update) {
|
|
487
|
-
dynamicUpdateFields = [];
|
|
488
|
-
}
|
|
489
|
-
else {
|
|
490
|
-
const { data, filter } = params;
|
|
491
|
-
const updateRules = await this.parseUpdateRules(filter, data, undefined, tableRules, localParams);
|
|
492
|
-
dynamicUpdateFields = updateRules.fields;
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
let columns = this.columns
|
|
496
|
-
.filter(c => {
|
|
497
|
-
const { insert, select, update } = p || {};
|
|
498
|
-
return [
|
|
499
|
-
...(insert?.fields || []),
|
|
500
|
-
...(select?.fields || []),
|
|
501
|
-
...(update?.fields || []),
|
|
502
|
-
].includes(c.name);
|
|
503
|
-
})
|
|
504
|
-
.map(_c => {
|
|
505
|
-
let c = { ..._c };
|
|
506
|
-
let label = c.comment || capitalizeFirstLetter(c.name, " ");
|
|
507
|
-
let select = c.privileges.some(p => p.privilege_type === "SELECT"), insert = c.privileges.some(p => p.privilege_type === "INSERT"), update = c.privileges.some(p => p.privilege_type === "UPDATE"), _delete = this.tableOrViewInfo.privileges.delete; // c.privileges.some(p => p.privilege_type === "DELETE");
|
|
508
|
-
delete c.privileges;
|
|
509
|
-
const prostgles = this.dboBuilder?.prostgles;
|
|
510
|
-
const fileConfig = prostgles.fileManager?.getColInfo({ colName: c.name, tableName: this.name });
|
|
511
|
-
/** Do not allow updates to file table unless it's to delete fields */
|
|
512
|
-
if (prostgles.fileManager?.config && prostgles.fileManager.tableName === this.name) {
|
|
513
|
-
update = false;
|
|
514
|
-
}
|
|
515
|
-
let result = {
|
|
516
|
-
...c,
|
|
517
|
-
label,
|
|
518
|
-
tsDataType: postgresToTsType(c.udt_name),
|
|
519
|
-
insert: insert && Boolean(p.insert && p.insert.fields && p.insert.fields.includes(c.name)) && tableRules?.insert?.forcedData?.[c.name] === undefined,
|
|
520
|
-
select: select && Boolean(p.select && p.select.fields && p.select.fields.includes(c.name)),
|
|
521
|
-
orderBy: select && Boolean(p.select && p.select.fields && p.select.orderByFields.includes(c.name)),
|
|
522
|
-
filter: Boolean(p.select && p.select.filterFields && p.select.filterFields.includes(c.name)),
|
|
523
|
-
update: update && Boolean(p.update && p.update.fields && p.update.fields.includes(c.name)) && tableRules?.update?.forcedData?.[c.name] === undefined,
|
|
524
|
-
delete: _delete && Boolean(p.delete && p.delete.filterFields && p.delete.filterFields.includes(c.name)),
|
|
525
|
-
...(prostgles?.tableConfigurator?.getColInfo({ table: this.name, col: c.name, lang }) || {}),
|
|
526
|
-
...(fileConfig && { file: fileConfig })
|
|
527
|
-
};
|
|
528
|
-
if (dynamicUpdateFields) {
|
|
529
|
-
result.update = dynamicUpdateFields.includes(c.name);
|
|
530
|
-
}
|
|
531
|
-
return result;
|
|
532
|
-
}).filter(c => c.select || c.update || c.delete || c.insert);
|
|
533
|
-
//.sort((a, b) => a.ordinal_position - b.ordinal_position);
|
|
534
|
-
// const tblInfo = await this.getInfo();
|
|
535
|
-
// if(tblInfo && tblInfo.media_table_name && tblInfo.has_media){
|
|
536
|
-
// const mediaRules = this.dboBuilder.dbo[tblInfo.media_table_name]?.
|
|
537
|
-
// return columns.concat({
|
|
538
|
-
// comment: "",
|
|
539
|
-
// data_type: "file",
|
|
540
|
-
// delete: false,
|
|
541
|
-
// });
|
|
542
|
-
// }
|
|
543
|
-
return columns;
|
|
544
|
-
}
|
|
545
|
-
catch (e) {
|
|
546
|
-
throw parseError(e, `db.${this.name}.getColumns()`);
|
|
547
|
-
// throw "Something went wrong in " + `db.${this.name}.getColumns()`;
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
getValidatedRules(tableRules, localParams) {
|
|
551
|
-
if ((0, utils_1.get)(localParams, "socket") && !tableRules) {
|
|
552
|
-
throw "INTERNAL ERROR: Unexpected case -> localParams && !tableRules";
|
|
553
|
-
}
|
|
554
|
-
/* Computed fields are allowed only if select is allowed */
|
|
555
|
-
const allColumns = this.column_names.slice(0).map(fieldName => ({
|
|
556
|
-
type: "column",
|
|
557
|
-
name: fieldName,
|
|
558
|
-
getQuery: ({ tableAlias }) => (0, QueryBuilder_1.asNameAlias)(fieldName, tableAlias),
|
|
559
|
-
selected: false
|
|
560
|
-
})).concat(QueryBuilder_1.COMPUTED_FIELDS.map(c => ({
|
|
561
|
-
type: c.type,
|
|
562
|
-
name: c.name,
|
|
563
|
-
getQuery: ({ tableAlias, allowedFields }) => c.getQuery({
|
|
564
|
-
allowedFields,
|
|
565
|
-
ctidField: undefined,
|
|
566
|
-
allColumns: this.columns,
|
|
567
|
-
/* CTID not available in AFTER trigger */
|
|
568
|
-
// ctidField: this.is_view? undefined : "ctid",
|
|
569
|
-
tableAlias
|
|
570
|
-
}),
|
|
571
|
-
selected: false
|
|
572
|
-
})));
|
|
573
|
-
if (tableRules) {
|
|
574
|
-
if ((0, prostgles_types_1.isEmpty)(tableRules))
|
|
575
|
-
throw "INTERNAL ERROR: Unexpected case -> Empty table rules for " + this.name;
|
|
576
|
-
const throwFieldsErr = (command, fieldType = "fields") => {
|
|
577
|
-
throw `Invalid publish.${this.name}.${command} rule -> ${fieldType} setting is missing.\nPlease specify allowed ${fieldType} in this format: "*" | { col_name: false } | { col1: true, col2: true }`;
|
|
578
|
-
}, getFirstSpecified = (...fieldParams) => {
|
|
579
|
-
const firstValid = fieldParams.find(fp => fp !== undefined);
|
|
580
|
-
return this.parseFieldFilter(firstValid);
|
|
581
|
-
};
|
|
582
|
-
let res = {
|
|
583
|
-
allColumns,
|
|
584
|
-
getColumns: tableRules?.getColumns ?? true,
|
|
585
|
-
getInfo: tableRules?.getColumns ?? true,
|
|
586
|
-
};
|
|
587
|
-
/* SELECT */
|
|
588
|
-
if (tableRules.select) {
|
|
589
|
-
if (!tableRules.select.fields)
|
|
590
|
-
return throwFieldsErr("select");
|
|
591
|
-
let maxLimit = null;
|
|
592
|
-
if (tableRules.select.maxLimit !== undefined && tableRules.select.maxLimit !== maxLimit) {
|
|
593
|
-
const ml = tableRules.select.maxLimit;
|
|
594
|
-
if (ml !== null && (!Number.isInteger(ml) || ml < 0))
|
|
595
|
-
throw ` Invalid publish.${this.name}.select.maxLimit -> expecting a positive integer OR null but got ` + ml;
|
|
596
|
-
maxLimit = ml;
|
|
597
|
-
}
|
|
598
|
-
const fields = this.parseFieldFilter(tableRules.select.fields);
|
|
599
|
-
res.select = {
|
|
600
|
-
fields,
|
|
601
|
-
orderByFields: tableRules.select.orderByFields ? this.parseFieldFilter(tableRules.select.orderByFields) : fields,
|
|
602
|
-
forcedFilter: { ...tableRules.select.forcedFilter },
|
|
603
|
-
filterFields: this.parseFieldFilter(tableRules.select.filterFields),
|
|
604
|
-
maxLimit
|
|
605
|
-
};
|
|
606
|
-
}
|
|
607
|
-
/* UPDATE */
|
|
608
|
-
if (tableRules.update) {
|
|
609
|
-
if (!tableRules.update.fields)
|
|
610
|
-
return throwFieldsErr("update");
|
|
611
|
-
res.update = {
|
|
612
|
-
fields: this.parseFieldFilter(tableRules.update.fields),
|
|
613
|
-
forcedData: { ...tableRules.update.forcedData },
|
|
614
|
-
forcedFilter: { ...tableRules.update.forcedFilter },
|
|
615
|
-
returningFields: getFirstSpecified(tableRules.update?.returningFields, tableRules?.select?.fields, tableRules.update.fields),
|
|
616
|
-
filterFields: this.parseFieldFilter(tableRules.update.filterFields)
|
|
617
|
-
};
|
|
618
|
-
}
|
|
619
|
-
/* INSERT */
|
|
620
|
-
if (tableRules.insert) {
|
|
621
|
-
if (!tableRules.insert.fields)
|
|
622
|
-
return throwFieldsErr("insert");
|
|
623
|
-
res.insert = {
|
|
624
|
-
fields: this.parseFieldFilter(tableRules.insert.fields),
|
|
625
|
-
forcedData: { ...tableRules.insert.forcedData },
|
|
626
|
-
returningFields: getFirstSpecified(tableRules.insert.returningFields, tableRules?.select?.fields, tableRules.insert.fields)
|
|
627
|
-
};
|
|
628
|
-
}
|
|
629
|
-
/* DELETE */
|
|
630
|
-
if (tableRules.delete) {
|
|
631
|
-
if (!tableRules.delete.filterFields)
|
|
632
|
-
return throwFieldsErr("delete", "filterFields");
|
|
633
|
-
res.delete = {
|
|
634
|
-
forcedFilter: { ...tableRules.delete.forcedFilter },
|
|
635
|
-
filterFields: this.parseFieldFilter(tableRules.delete.filterFields),
|
|
636
|
-
returningFields: getFirstSpecified(tableRules.delete.returningFields, tableRules?.select?.fields, tableRules.delete.filterFields)
|
|
637
|
-
};
|
|
638
|
-
}
|
|
639
|
-
if (!tableRules.select && !tableRules.update && !tableRules.delete && !tableRules.insert) {
|
|
640
|
-
if ([null, false].includes(tableRules.getInfo))
|
|
641
|
-
res.getInfo = false;
|
|
642
|
-
if ([null, false].includes(tableRules.getColumns))
|
|
643
|
-
res.getColumns = false;
|
|
644
|
-
}
|
|
645
|
-
return res;
|
|
646
|
-
}
|
|
647
|
-
else {
|
|
648
|
-
const all_cols = this.column_names.slice(0);
|
|
649
|
-
return {
|
|
650
|
-
allColumns,
|
|
651
|
-
getColumns: true,
|
|
652
|
-
getInfo: true,
|
|
653
|
-
select: {
|
|
654
|
-
fields: all_cols,
|
|
655
|
-
filterFields: all_cols,
|
|
656
|
-
orderByFields: all_cols,
|
|
657
|
-
forcedFilter: {},
|
|
658
|
-
maxLimit: null,
|
|
659
|
-
},
|
|
660
|
-
update: {
|
|
661
|
-
fields: all_cols,
|
|
662
|
-
filterFields: all_cols,
|
|
663
|
-
forcedFilter: {},
|
|
664
|
-
forcedData: {},
|
|
665
|
-
returningFields: all_cols
|
|
666
|
-
},
|
|
667
|
-
insert: {
|
|
668
|
-
fields: all_cols,
|
|
669
|
-
forcedData: {},
|
|
670
|
-
returningFields: all_cols
|
|
671
|
-
},
|
|
672
|
-
delete: {
|
|
673
|
-
filterFields: all_cols,
|
|
674
|
-
forcedFilter: {},
|
|
675
|
-
returningFields: all_cols
|
|
676
|
-
}
|
|
677
|
-
};
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
async find(filter, selectParams, param3_unused, tableRules, localParams) {
|
|
681
|
-
try {
|
|
682
|
-
filter = filter || {};
|
|
683
|
-
const allowedReturnTypes = ["row", "value", "values", "statement"];
|
|
684
|
-
const { returnType } = selectParams || {};
|
|
685
|
-
if (returnType && !allowedReturnTypes.includes(returnType)) {
|
|
686
|
-
throw `returnType (${returnType}) can only be ${allowedReturnTypes.join(" OR ")}`;
|
|
687
|
-
}
|
|
688
|
-
const { testRule = false, returnQuery = false } = localParams || {};
|
|
689
|
-
if (testRule)
|
|
690
|
-
return [];
|
|
691
|
-
if (selectParams) {
|
|
692
|
-
const good_params = ["select", "orderBy", "offset", "limit", "returnType", "groupBy"];
|
|
693
|
-
const bad_params = Object.keys(selectParams).filter(k => !good_params.includes(k));
|
|
694
|
-
if (bad_params && bad_params.length)
|
|
695
|
-
throw "Invalid params: " + bad_params.join(", ") + " \n Expecting: " + good_params.join(", ");
|
|
696
|
-
}
|
|
697
|
-
/* Validate publish */
|
|
698
|
-
if (tableRules) {
|
|
699
|
-
let fields, filterFields, forcedFilter, maxLimit;
|
|
700
|
-
if (!tableRules.select)
|
|
701
|
-
throw "select rules missing for " + this.name;
|
|
702
|
-
fields = tableRules.select.fields;
|
|
703
|
-
forcedFilter = tableRules.select.forcedFilter;
|
|
704
|
-
filterFields = tableRules.select.filterFields;
|
|
705
|
-
maxLimit = tableRules.select.maxLimit;
|
|
706
|
-
if (tableRules.select !== "*" && typeof tableRules.select !== "boolean" && !isPlainObject(tableRules.select))
|
|
707
|
-
throw `\nINVALID publish.${this.name}.select\nExpecting any of: "*" | { fields: "*" } | true | false`;
|
|
708
|
-
if (!fields)
|
|
709
|
-
throw ` invalid ${this.name}.select rule -> fields (required) setting missing.\nExpecting any of: "*" | { col_name: false } | { col1: true, col2: true }`;
|
|
710
|
-
if (maxLimit && !Number.isInteger(maxLimit))
|
|
711
|
-
throw ` invalid publish.${this.name}.select.maxLimit -> expecting integer but got ` + maxLimit;
|
|
712
|
-
}
|
|
713
|
-
let q = await (0, QueryBuilder_1.getNewQuery)(this, filter, selectParams, param3_unused, tableRules, localParams, this.columns), _query = (0, makeSelectQuery_1.makeSelectQuery)(this, q, undefined, undefined, selectParams);
|
|
714
|
-
// console.log(_query, JSON.stringify(q, null, 2))
|
|
715
|
-
if (testRule) {
|
|
716
|
-
try {
|
|
717
|
-
await this.db.any("EXPLAIN " + _query);
|
|
718
|
-
return [];
|
|
719
|
-
}
|
|
720
|
-
catch (e) {
|
|
721
|
-
console.error(e);
|
|
722
|
-
throw `INTERNAL ERROR: Publish config is not valid for publish.${this.name}.select `;
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
if (returnQuery)
|
|
726
|
-
return _query;
|
|
727
|
-
if (returnType === "statement") {
|
|
728
|
-
if (!(await (0, runSQL_1.canRunSQL)(this.dboBuilder.prostgles, localParams))) {
|
|
729
|
-
throw `Not allowed: {returnType: "statement"} requires sql privileges `;
|
|
730
|
-
}
|
|
731
|
-
return _query;
|
|
732
|
-
}
|
|
733
|
-
if (["row", "value"].includes(returnType)) {
|
|
734
|
-
return (this.t || this.db).oneOrNone(_query).then(data => {
|
|
735
|
-
return (data && returnType === "value") ? Object.values(data)[0] : data;
|
|
736
|
-
}).catch(err => makeErr(err, localParams, this));
|
|
737
|
-
}
|
|
738
|
-
else {
|
|
739
|
-
return (this.t || this.db).any(_query).then(data => {
|
|
740
|
-
if (returnType === "values") {
|
|
741
|
-
return data.map(d => Object.values(d)[0]);
|
|
742
|
-
}
|
|
743
|
-
return data;
|
|
744
|
-
}).catch(err => makeErr(err, localParams, this));
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
catch (e) {
|
|
748
|
-
// console.trace(e)
|
|
749
|
-
if (localParams && localParams.testRule)
|
|
750
|
-
throw e;
|
|
751
|
-
throw parseError(e, `dbo.${this.name}.find()`);
|
|
752
|
-
// throw { err: parseError(e), msg: `Issue with dbo.${this.name}.find()`, args: { filter, selectParams} };
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
findOne(filter, selectParams, param3_unused, table_rules, localParams) {
|
|
756
|
-
try {
|
|
757
|
-
const { select = "*", orderBy, offset = 0 } = selectParams || {};
|
|
758
|
-
if (selectParams) {
|
|
759
|
-
const good_params = ["select", "orderBy", "offset"];
|
|
760
|
-
const bad_params = Object.keys(selectParams).filter(k => !good_params.includes(k));
|
|
761
|
-
if (bad_params && bad_params.length)
|
|
762
|
-
throw "Invalid params: " + bad_params.join(", ") + " \n Expecting: " + good_params.join(", ");
|
|
763
|
-
}
|
|
764
|
-
return this.find(filter, { select, orderBy, limit: 1, offset, returnType: "row" }, undefined, table_rules, localParams);
|
|
765
|
-
}
|
|
766
|
-
catch (e) {
|
|
767
|
-
if (localParams && localParams.testRule)
|
|
768
|
-
throw e;
|
|
769
|
-
throw parseError(e, `Issue with dbo.${this.name}.findOne()`);
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
async count(filter, param2_unused, param3_unused, table_rules, localParams) {
|
|
773
|
-
filter = filter || {};
|
|
774
|
-
try {
|
|
775
|
-
return await this.find(filter, { select: "", limit: 0 }, undefined, table_rules, localParams)
|
|
776
|
-
.then(async (allowed) => {
|
|
777
|
-
const { filterFields, forcedFilter } = (0, utils_1.get)(table_rules, "select") || {};
|
|
778
|
-
const where = (await this.prepareWhere({ filter, forcedFilter, filterFields, addKeywords: true, localParams, tableRule: table_rules })).where;
|
|
779
|
-
let query = "SELECT COUNT(*) FROM " + this.escapedName + " " + where;
|
|
780
|
-
return (this.t || this.db).one(query, { _psqlWS_tableName: this.name }).then(({ count }) => +count);
|
|
781
|
-
});
|
|
782
|
-
}
|
|
783
|
-
catch (e) {
|
|
784
|
-
if (localParams && localParams.testRule)
|
|
785
|
-
throw e;
|
|
786
|
-
throw parseError(e, `dbo.${this.name}.count()`);
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
async size(filter, selectParams, param3_unused, table_rules, localParams) {
|
|
790
|
-
filter = filter || {};
|
|
791
|
-
try {
|
|
792
|
-
return await this.find(filter, { ...selectParams, limit: 2 }, undefined, table_rules, localParams)
|
|
793
|
-
.then(async (_allowed) => {
|
|
794
|
-
// let rules: TableRule = table_rules || {};
|
|
795
|
-
// rules.select.maxLimit = Number.MAX_SAFE_INTEGER;
|
|
796
|
-
// rules.select.fields = rules.select.fields || "*";
|
|
797
|
-
const q = await this.find(filter, { ...selectParams, limit: selectParams?.limit ?? Number.MAX_SAFE_INTEGER }, undefined, table_rules, { ...localParams, returnQuery: true });
|
|
798
|
-
const query = `
|
|
799
|
-
SELECT sum(pg_column_size((prgl_size_query.*))) as size
|
|
800
|
-
FROM (
|
|
801
|
-
${q}
|
|
802
|
-
) prgl_size_query
|
|
803
|
-
`;
|
|
804
|
-
return (this.t || this.db).one(query, { _psqlWS_tableName: this.name }).then(({ size }) => size || '0');
|
|
805
|
-
});
|
|
806
|
-
}
|
|
807
|
-
catch (e) {
|
|
808
|
-
if (localParams && localParams.testRule)
|
|
809
|
-
throw e;
|
|
810
|
-
throw parseError(e, `dbo.${this.name}.size()`);
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
getAllowedSelectFields(selectParams = "*", allowed_cols, allow_empty = true) {
|
|
814
|
-
let all_columns = this.column_names.slice(0), allowedFields = all_columns.slice(0), resultFields = [];
|
|
815
|
-
if (selectParams) {
|
|
816
|
-
resultFields = this.parseFieldFilter(selectParams, allow_empty);
|
|
817
|
-
}
|
|
818
|
-
if (allowed_cols) {
|
|
819
|
-
allowedFields = this.parseFieldFilter(allowed_cols, allow_empty);
|
|
820
|
-
}
|
|
821
|
-
let col_names = (resultFields || []).filter(f => !allowedFields || allowedFields.includes(f));
|
|
822
|
-
/* Maintain allowed cols order */
|
|
823
|
-
if (selectParams === "*" && allowedFields && allowedFields.length)
|
|
824
|
-
col_names = allowedFields;
|
|
825
|
-
return col_names;
|
|
826
|
-
}
|
|
827
|
-
prepareColumnSet(selectParams = "*", allowed_cols, allow_empty = true, onlyNames = true) {
|
|
828
|
-
let all_columns = this.column_names.slice(0);
|
|
829
|
-
let col_names = this.getAllowedSelectFields(selectParams, all_columns, allow_empty);
|
|
830
|
-
/** Ensure order is maintained */
|
|
831
|
-
if (selectParams && Array.isArray(selectParams) && typeof selectParams[0] === "string") {
|
|
832
|
-
col_names = col_names.sort((a, b) => selectParams.indexOf(a) - selectParams.indexOf(b));
|
|
833
|
-
}
|
|
834
|
-
try {
|
|
835
|
-
let colSet = new exports.pgp.helpers.ColumnSet(col_names);
|
|
836
|
-
return onlyNames ? colSet.names : colSet;
|
|
837
|
-
}
|
|
838
|
-
catch (e) {
|
|
839
|
-
throw e;
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
prepareSelect(selectParams = "*", allowed_cols, allow_empty = true, tableAlias) {
|
|
843
|
-
if (tableAlias) {
|
|
844
|
-
let cs = this.prepareColumnSet(selectParams, allowed_cols, true, false);
|
|
845
|
-
return cs.columns.map(col => `${this.escapedName}.${(0, prostgles_types_1.asName)(col.name)}`).join(", ");
|
|
846
|
-
}
|
|
847
|
-
else {
|
|
848
|
-
return this.prepareColumnSet(selectParams, allowed_cols, true, true);
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
async prepareHaving(params) {
|
|
852
|
-
return "";
|
|
853
|
-
}
|
|
854
|
-
/**
|
|
855
|
-
* Parses group or simple filter
|
|
856
|
-
*/
|
|
857
|
-
async prepareWhere(params) {
|
|
858
|
-
const { filter, select, forcedFilter, filterFields: ff, addKeywords = true, tableAlias, localParams, tableRule } = params;
|
|
859
|
-
const { $and: $and_key, $or: $or_key } = this.dboBuilder.prostgles.keywords;
|
|
860
|
-
let filterFields = ff;
|
|
861
|
-
/* Local update allow all. TODO -> FIX THIS */
|
|
862
|
-
if (!ff && !tableRule)
|
|
863
|
-
filterFields = "*";
|
|
864
|
-
const parseFullFilter = async (f, parentFilter = null, isForcedFilterBypass) => {
|
|
865
|
-
if (!f)
|
|
866
|
-
throw "Invalid/missing group filter provided";
|
|
867
|
-
let result = "";
|
|
868
|
-
let keys = (0, prostgles_types_1.getKeys)(f);
|
|
869
|
-
if (!keys.length)
|
|
870
|
-
return result;
|
|
871
|
-
if ((keys.includes($and_key) || keys.includes($or_key))) {
|
|
872
|
-
if (keys.length > 1)
|
|
873
|
-
throw `\ngroup filter must contain only one array property. e.g.: { ${$and_key}: [...] } OR { ${$or_key}: [...] } `;
|
|
874
|
-
if (parentFilter && Object.keys(parentFilter).includes(""))
|
|
875
|
-
throw "group filter ($and/$or) can only be placed at the root or within another group filter";
|
|
876
|
-
}
|
|
877
|
-
const { [$and_key]: $and, [$or_key]: $or } = f, group = $and || $or;
|
|
878
|
-
if (group && group.length) {
|
|
879
|
-
const operand = $and ? " AND " : " OR ";
|
|
880
|
-
let conditions = (await Promise.all(group.map(async (gf) => await parseFullFilter(gf, group, isForcedFilterBypass)))).filter(c => c);
|
|
881
|
-
if (conditions && conditions.length) {
|
|
882
|
-
if (conditions.length === 1)
|
|
883
|
-
return conditions.join(operand);
|
|
884
|
-
else
|
|
885
|
-
return ` ( ${conditions.sort().join(operand)} ) `;
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
else if (!group) {
|
|
889
|
-
/** forcedFilters do not get checked against publish and are treated as server-side requests */
|
|
890
|
-
result = await this.getCondition({
|
|
891
|
-
filter: { ...f },
|
|
892
|
-
select,
|
|
893
|
-
allowed_colnames: isForcedFilterBypass ? this.column_names.slice(0) : this.parseFieldFilter(filterFields),
|
|
894
|
-
tableAlias,
|
|
895
|
-
localParams: isForcedFilterBypass ? undefined : localParams,
|
|
896
|
-
tableRules: isForcedFilterBypass ? undefined : tableRule
|
|
897
|
-
});
|
|
898
|
-
}
|
|
899
|
-
return result;
|
|
900
|
-
};
|
|
901
|
-
if (!isPlainObject(filter))
|
|
902
|
-
throw "\nInvalid filter\nExpecting an object but got -> " + JSON.stringify(filter);
|
|
903
|
-
/* A forced filter condition will not check if the existsJoined filter tables have been published */
|
|
904
|
-
const forcedFilterCond = forcedFilter ? await parseFullFilter(forcedFilter, null, true) : undefined;
|
|
905
|
-
const filterCond = await parseFullFilter(filter, null, false);
|
|
906
|
-
let cond = [
|
|
907
|
-
forcedFilterCond, filterCond
|
|
908
|
-
].filter(c => c).join(" AND ");
|
|
909
|
-
const finalFilter = forcedFilter ? {
|
|
910
|
-
[$and_key]: [forcedFilter, filter].filter(prostgles_types_1.isDefined)
|
|
911
|
-
} : { ...filter };
|
|
912
|
-
if (cond && addKeywords)
|
|
913
|
-
cond = "WHERE " + cond;
|
|
914
|
-
return { where: cond || "", filter: finalFilter };
|
|
915
|
-
}
|
|
916
|
-
async prepareExistCondition(eConfig, localParams) {
|
|
917
|
-
let res = "";
|
|
918
|
-
const thisTable = this.name;
|
|
919
|
-
const isNotExists = ["$notExists", "$notExistsJoined"].includes(eConfig.existType);
|
|
920
|
-
let { f2, tables, isJoined } = eConfig;
|
|
921
|
-
let t2 = tables[tables.length - 1];
|
|
922
|
-
tables.forEach(t => {
|
|
923
|
-
if (!this.dboBuilder.dbo[t])
|
|
924
|
-
throw { stack: ["prepareExistCondition()"], message: `Invalid or dissallowed table: ${t}` };
|
|
925
|
-
});
|
|
926
|
-
/* Nested $exists not allowed */
|
|
927
|
-
if (f2 && Object.keys(f2).find(fk => exports.EXISTS_KEYS.includes(fk))) {
|
|
928
|
-
throw { stack: ["prepareExistCondition()"], message: "Nested exists dissallowed" };
|
|
929
|
-
}
|
|
930
|
-
const makeTableChain = (finalFilter) => {
|
|
931
|
-
let joinPaths = [];
|
|
932
|
-
let expectOne = true;
|
|
933
|
-
tables.map((t2, depth) => {
|
|
934
|
-
let t1 = depth ? tables[depth - 1] : thisTable;
|
|
935
|
-
let exactPaths = [t1, t2];
|
|
936
|
-
if (!depth && eConfig.shortestJoin)
|
|
937
|
-
exactPaths = undefined;
|
|
938
|
-
const jinf = this.getJoins(t1, t2, exactPaths, true);
|
|
939
|
-
expectOne = Boolean(expectOne && jinf.expectOne);
|
|
940
|
-
joinPaths = joinPaths.concat(jinf.paths);
|
|
941
|
-
});
|
|
942
|
-
let r = makeJoin({ paths: joinPaths, expectOne }, 0);
|
|
943
|
-
return r;
|
|
944
|
-
function makeJoin(joinInfo, ji) {
|
|
945
|
-
const { paths } = joinInfo;
|
|
946
|
-
const jp = paths[ji];
|
|
947
|
-
// let prevTable = ji? paths[ji - 1].table : jp.source;
|
|
948
|
-
let table = paths[ji].table;
|
|
949
|
-
let tableAlias = (0, prostgles_types_1.asName)(ji < paths.length - 1 ? `jd${ji}` : table);
|
|
950
|
-
let prevTableAlias = (0, prostgles_types_1.asName)(ji ? `jd${ji - 1}` : thisTable);
|
|
951
|
-
let cond = `${jp.on.map(c => {
|
|
952
|
-
return c.map(([c1, c2]) => `${prevTableAlias}.${(0, prostgles_types_1.asName)(c1)} = ${tableAlias}.${(0, prostgles_types_1.asName)(c2)}`).join(" AND ");
|
|
953
|
-
}).join("\n OR ")}`;
|
|
954
|
-
let j = `SELECT 1 \n` +
|
|
955
|
-
`FROM ${(0, prostgles_types_1.asName)(table)} ${tableAlias} \n` +
|
|
956
|
-
`WHERE ${cond} \n`; //
|
|
957
|
-
if (ji === paths.length - 1 &&
|
|
958
|
-
finalFilter) {
|
|
959
|
-
j += `AND ${finalFilter} \n`;
|
|
960
|
-
}
|
|
961
|
-
const indent = (a, b) => a;
|
|
962
|
-
if (ji < paths.length - 1) {
|
|
963
|
-
j += `AND ${makeJoin(joinInfo, ji + 1)} \n`;
|
|
964
|
-
}
|
|
965
|
-
j = indent(j, ji + 1);
|
|
966
|
-
let res = `${isNotExists ? " NOT " : " "} EXISTS ( \n` +
|
|
967
|
-
j +
|
|
968
|
-
`) \n`;
|
|
969
|
-
return indent(res, ji);
|
|
970
|
-
}
|
|
971
|
-
};
|
|
972
|
-
let finalWhere = "";
|
|
973
|
-
let t2Rules = undefined, forcedFilter, filterFields, tableAlias;
|
|
974
|
-
/* Check if allowed to view data - forcedFilters will bypass this check through isForcedFilterBypass */
|
|
975
|
-
if (localParams?.isRemoteRequest && (!localParams?.socket && !localParams?.httpReq))
|
|
976
|
-
throw "Unexpected: localParams isRemoteRequest and missing socket/httpReq: ";
|
|
977
|
-
if (localParams && (localParams.socket || localParams.httpReq) && this.dboBuilder.publishParser) {
|
|
978
|
-
t2Rules = await this.dboBuilder.publishParser.getValidatedRequestRuleWusr({ tableName: t2, command: "find", localParams });
|
|
979
|
-
if (!t2Rules || !t2Rules.select)
|
|
980
|
-
throw "Dissallowed";
|
|
981
|
-
({ forcedFilter, filterFields } = t2Rules.select);
|
|
982
|
-
}
|
|
983
|
-
try {
|
|
984
|
-
finalWhere = (await this.dboBuilder.dbo[t2].prepareWhere({
|
|
985
|
-
filter: f2,
|
|
986
|
-
forcedFilter,
|
|
987
|
-
filterFields,
|
|
988
|
-
addKeywords: false,
|
|
989
|
-
tableAlias,
|
|
990
|
-
localParams,
|
|
991
|
-
tableRule: t2Rules
|
|
992
|
-
})).where;
|
|
993
|
-
}
|
|
994
|
-
catch (err) {
|
|
995
|
-
// console.trace(err)
|
|
996
|
-
throw err;
|
|
997
|
-
}
|
|
998
|
-
if (!isJoined) {
|
|
999
|
-
res = `${isNotExists ? " NOT " : " "} EXISTS (SELECT 1 \nFROM ${(0, prostgles_types_1.asName)(t2)} \n${finalWhere ? `WHERE ${finalWhere}` : ""}) `;
|
|
1000
|
-
}
|
|
1001
|
-
else {
|
|
1002
|
-
res = makeTableChain(finalWhere);
|
|
1003
|
-
}
|
|
1004
|
-
return res;
|
|
1005
|
-
}
|
|
1006
|
-
/**
|
|
1007
|
-
* parses a single filter
|
|
1008
|
-
* @example
|
|
1009
|
-
* { fff: 2 } => "fff" = 2
|
|
1010
|
-
* { fff: { $ilike: 'abc' } } => "fff" ilike 'abc'
|
|
1011
|
-
*/
|
|
1012
|
-
async getCondition(params) {
|
|
1013
|
-
const { filter, select, allowed_colnames, tableAlias, localParams, tableRules } = params;
|
|
1014
|
-
let data = { ...filter };
|
|
1015
|
-
/* Exists join filter */
|
|
1016
|
-
const ERR = "Invalid exists filter. \nExpecting somethibng like: { $exists: { tableName.tableName2: Filter } } | { $exists: { \"**.tableName3\": Filter } }\n";
|
|
1017
|
-
const SP_WILDCARD = "**";
|
|
1018
|
-
let existsKeys = Object.keys(data)
|
|
1019
|
-
.filter(k => exports.EXISTS_KEYS.includes(k) && Object.keys(data[k] || {}).length)
|
|
1020
|
-
.map(key => {
|
|
1021
|
-
const isJoined = exports.EXISTS_KEYS.slice(-2).includes(key);
|
|
1022
|
-
let firstKey = Object.keys(data[key])[0], tables = firstKey.split("."), f2 = data[key][firstKey], shortestJoin = false;
|
|
1023
|
-
if (!isJoined) {
|
|
1024
|
-
if (tables.length !== 1)
|
|
1025
|
-
throw "Expecting single table in exists filter. Example: { $exists: { tableName: Filter } }";
|
|
1026
|
-
}
|
|
1027
|
-
else {
|
|
1028
|
-
/* First part can be the ** param meaning shortest join. Will be overriden by anything in tableConfig */
|
|
1029
|
-
if (!tables.length)
|
|
1030
|
-
throw ERR + "\nBut got: " + data[key];
|
|
1031
|
-
if (tables[0] === SP_WILDCARD) {
|
|
1032
|
-
tables = tables.slice(1);
|
|
1033
|
-
shortestJoin = true;
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
return {
|
|
1037
|
-
key,
|
|
1038
|
-
existType: key,
|
|
1039
|
-
isJoined,
|
|
1040
|
-
shortestJoin,
|
|
1041
|
-
f2,
|
|
1042
|
-
tables
|
|
1043
|
-
};
|
|
1044
|
-
});
|
|
1045
|
-
/* Exists with exact path */
|
|
1046
|
-
// Object.keys(data).map(k => {
|
|
1047
|
-
// let isthis = isPlainObject(data[k]) && !this.column_names.includes(k) && !k.split(".").find(kt => !this.dboBuilder.dbo[kt]);
|
|
1048
|
-
// if(isthis) {
|
|
1049
|
-
// existsKeys.push({
|
|
1050
|
-
// key: k,
|
|
1051
|
-
// notJoined: false,
|
|
1052
|
-
// exactPaths: k.split(".")
|
|
1053
|
-
// });
|
|
1054
|
-
// }
|
|
1055
|
-
// });
|
|
1056
|
-
let funcConds = [];
|
|
1057
|
-
const funcFilterkeys = FILTER_FUNCS.filter(f => {
|
|
1058
|
-
return f.name in data;
|
|
1059
|
-
});
|
|
1060
|
-
funcFilterkeys.map(f => {
|
|
1061
|
-
const funcArgs = data[f.name];
|
|
1062
|
-
if (!Array.isArray(funcArgs))
|
|
1063
|
-
throw `A function filter must contain an array. E.g: { $funcFilterName: ["col1"] } \n but got: ${JSON.stringify((0, PubSubManager_1.pickKeys)(data, [f.name]))} `;
|
|
1064
|
-
const fields = this.parseFieldFilter(f.getFields(funcArgs), true, allowed_colnames);
|
|
1065
|
-
const dissallowedCols = fields.filter(fname => !allowed_colnames.includes(fname));
|
|
1066
|
-
if (dissallowedCols.length) {
|
|
1067
|
-
throw `Invalid/disallowed columns found in function filter: ${dissallowedCols}`;
|
|
1068
|
-
}
|
|
1069
|
-
funcConds.push(f.getQuery({ args: funcArgs, allColumns: this.columns, allowedFields: allowed_colnames, tableAlias }));
|
|
1070
|
-
});
|
|
1071
|
-
let existsCond = "";
|
|
1072
|
-
if (existsKeys.length) {
|
|
1073
|
-
existsCond = (await Promise.all(existsKeys.map(async (k) => await this.prepareExistCondition(k, localParams)))).join(" AND ");
|
|
1074
|
-
}
|
|
1075
|
-
/* Computed field queries */
|
|
1076
|
-
const p = this.getValidatedRules(tableRules, localParams);
|
|
1077
|
-
const computedFields = p.allColumns.filter(c => c.type === "computed");
|
|
1078
|
-
let computedColConditions = [];
|
|
1079
|
-
Object.keys(data || {}).map(key => {
|
|
1080
|
-
const compCol = computedFields.find(cf => cf.name === key);
|
|
1081
|
-
if (compCol) {
|
|
1082
|
-
computedColConditions.push(compCol.getQuery({
|
|
1083
|
-
tableAlias,
|
|
1084
|
-
allowedFields: p.select.fields,
|
|
1085
|
-
allColumns: this.columns,
|
|
1086
|
-
/* CTID not available in AFTER trigger */
|
|
1087
|
-
// ctidField: this.is_view? undefined : "ctid"
|
|
1088
|
-
ctidField: undefined,
|
|
1089
|
-
}) + ` = ${exports.pgp.as.format("$1", [data[key]])}`);
|
|
1090
|
-
delete data[key];
|
|
1091
|
-
}
|
|
1092
|
-
});
|
|
1093
|
-
let allowedSelect = [];
|
|
1094
|
-
/* Select aliases take precedence over col names. This is to ensure filters work correctly and even on computed cols*/
|
|
1095
|
-
if (select) {
|
|
1096
|
-
/* Allow filtering by selected fields/funcs */
|
|
1097
|
-
allowedSelect = select.filter(s => {
|
|
1098
|
-
/* */
|
|
1099
|
-
if (["function", "computed", "column"].includes(s.type)) {
|
|
1100
|
-
if (s.type !== "column" || allowed_colnames.includes(s.alias)) {
|
|
1101
|
-
return true;
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
return false;
|
|
1105
|
-
});
|
|
1106
|
-
}
|
|
1107
|
-
/* Add remaining allowed fields */
|
|
1108
|
-
allowedSelect = allowedSelect.concat(p.allColumns.filter(c => allowed_colnames.includes(c.name) &&
|
|
1109
|
-
!allowedSelect.find(s => s.alias === c.name)).map(f => ({
|
|
1110
|
-
type: f.type,
|
|
1111
|
-
alias: f.name,
|
|
1112
|
-
getQuery: (tableAlias) => f.getQuery({
|
|
1113
|
-
tableAlias,
|
|
1114
|
-
allColumns: this.columns,
|
|
1115
|
-
allowedFields: allowed_colnames
|
|
1116
|
-
}),
|
|
1117
|
-
selected: false,
|
|
1118
|
-
getFields: () => [f.name],
|
|
1119
|
-
column_udt_type: f.type === "column" ? this.columns.find(c => c.name === f.name)?.udt_name : undefined
|
|
1120
|
-
})));
|
|
1121
|
-
/* Parse complex filters
|
|
1122
|
-
{ $filter: [{ $func: [...] }, "=", value | { $func: [..] }] }
|
|
1123
|
-
*/
|
|
1124
|
-
const complexFilters = [];
|
|
1125
|
-
const complexFilterKey = "$filter";
|
|
1126
|
-
const allowedComparators = [">", "<", "=", "<=", ">=", "<>", "!="];
|
|
1127
|
-
if (complexFilterKey in data) {
|
|
1128
|
-
const getFuncQuery = (funcData) => {
|
|
1129
|
-
const { funcName, args } = (0, QueryBuilder_1.parseFunctionObject)(funcData);
|
|
1130
|
-
const funcDef = (0, QueryBuilder_1.parseFunction)({ func: funcName, args, functions: QueryBuilder_1.FUNCTIONS, allowedFields: allowed_colnames });
|
|
1131
|
-
return funcDef.getQuery({ args, tableAlias, allColumns: this.columns, allowedFields: allowed_colnames });
|
|
1132
|
-
};
|
|
1133
|
-
const complexFilter = data[complexFilterKey];
|
|
1134
|
-
if (!Array.isArray(complexFilter))
|
|
1135
|
-
throw `Invalid $filter. Must contain an array of at least element but got: ${JSON.stringify(complexFilter)} `;
|
|
1136
|
-
const leftFilter = complexFilter[0];
|
|
1137
|
-
const comparator = complexFilter[1];
|
|
1138
|
-
const rightFilterOrValue = complexFilter[2];
|
|
1139
|
-
const leftVal = getFuncQuery(leftFilter);
|
|
1140
|
-
let result = leftVal;
|
|
1141
|
-
if (comparator) {
|
|
1142
|
-
if (!allowedComparators.includes(comparator))
|
|
1143
|
-
throw `Invalid $filter. comparator ${JSON.stringify(comparator)} is not valid. Expecting one of: ${allowedComparators}`;
|
|
1144
|
-
if (!rightFilterOrValue)
|
|
1145
|
-
throw "Invalid $filter. Expecting a value or function after the comparator";
|
|
1146
|
-
const rightVal = (0, prostgles_types_1.isObject)(rightFilterOrValue) ? getFuncQuery(rightFilterOrValue) : (0, PubSubManager_1.asValue)(rightFilterOrValue);
|
|
1147
|
-
if (leftVal === rightVal)
|
|
1148
|
-
throw "Invalid $filter. Cannot compare two identical function signatures: " + JSON.stringify(leftFilter);
|
|
1149
|
-
result += ` ${comparator} ${rightVal}`;
|
|
1150
|
-
}
|
|
1151
|
-
complexFilters.push(result);
|
|
1152
|
-
}
|
|
1153
|
-
/* Parse join filters
|
|
1154
|
-
{ $joinFilter: { $ST_DWithin: [table.col, foreignTable.col, distance] }
|
|
1155
|
-
will make an exists filter
|
|
1156
|
-
*/
|
|
1157
|
-
let filterKeys = Object.keys(data).filter(k => k !== complexFilterKey && !funcFilterkeys.find(ek => ek.name === k) && !computedFields.find(cf => cf.name === k) && !existsKeys.find(ek => ek.key === k));
|
|
1158
|
-
// if(allowed_colnames){
|
|
1159
|
-
// const aliasedColumns = (select || []).filter(s =>
|
|
1160
|
-
// ["function", "computed", "column"].includes(s.type) && allowed_colnames.includes(s.alias) ||
|
|
1161
|
-
// s.getFields().find(f => allowed_colnames.includes(f))
|
|
1162
|
-
// ).map(s => s.alias);
|
|
1163
|
-
// const validCols = [...allowed_colnames, ...aliasedColumns];
|
|
1164
|
-
// }
|
|
1165
|
-
const validFieldNames = allowedSelect.map(s => s.alias);
|
|
1166
|
-
const invalidColumn = filterKeys
|
|
1167
|
-
.find(fName => !validFieldNames.find(c => c === fName ||
|
|
1168
|
-
(fName.startsWith(c) && (fName.slice(c.length).includes("->") ||
|
|
1169
|
-
fName.slice(c.length).includes(".")))));
|
|
1170
|
-
if (invalidColumn) {
|
|
1171
|
-
throw `Table: ${this.name} -> disallowed/inexistent columns in filter: ${invalidColumn} \n Expecting one of: ${allowedSelect.map(s => s.type === "column" ? s.getQuery() : s.alias).join(", ")}`;
|
|
1172
|
-
}
|
|
1173
|
-
/* TODO: Allow filter funcs */
|
|
1174
|
-
// const singleFuncs = FUNCTIONS.filter(f => f.singleColArg);
|
|
1175
|
-
const f = (0, PubSubManager_1.pickKeys)(data, filterKeys);
|
|
1176
|
-
const q = (0, Filtering_1.parseFilterItem)({
|
|
1177
|
-
filter: f,
|
|
1178
|
-
tableAlias,
|
|
1179
|
-
pgp: exports.pgp,
|
|
1180
|
-
select: allowedSelect
|
|
1181
|
-
});
|
|
1182
|
-
let templates = [q].filter(q => q);
|
|
1183
|
-
if (existsCond)
|
|
1184
|
-
templates.push(existsCond);
|
|
1185
|
-
templates = templates.concat(funcConds);
|
|
1186
|
-
templates = templates.concat(computedColConditions);
|
|
1187
|
-
templates = templates.concat(complexFilters);
|
|
1188
|
-
/* sorted to ensure duplicate subscription channels are not created due to different condition order */
|
|
1189
|
-
return templates.sort()
|
|
1190
|
-
.join(" AND \n");
|
|
1191
|
-
}
|
|
1192
|
-
/* This relates only to SELECT */
|
|
1193
|
-
prepareSortItems(orderBy, allowed_cols, tableAlias, select) {
|
|
1194
|
-
const throwErr = () => {
|
|
1195
|
-
throw "\nInvalid orderBy option -> " + JSON.stringify(orderBy) +
|
|
1196
|
-
"Expecting: \
|
|
1197
|
-
{ key2: false, key1: true } \
|
|
1198
|
-
{ key1: 1, key2: -1 } \
|
|
1199
|
-
[{ key1: true }, { key2: false }] \
|
|
1200
|
-
[{ key: 'colName', asc: true, nulls: 'first', nullEmpty: true }]";
|
|
1201
|
-
}, parseOrderObj = (orderBy, expectOne = false) => {
|
|
1202
|
-
if (!isPlainObject(orderBy))
|
|
1203
|
-
return throwErr();
|
|
1204
|
-
const keys = Object.keys(orderBy);
|
|
1205
|
-
if (keys.length && keys.find(k => ["key", "asc", "nulls", "nullEmpty"].includes(k))) {
|
|
1206
|
-
const { key, asc, nulls, nullEmpty = false } = orderBy;
|
|
1207
|
-
if (!["string"].includes(typeof key) ||
|
|
1208
|
-
!["boolean"].includes(typeof asc) ||
|
|
1209
|
-
!["first", "last", undefined, null].includes(nulls) ||
|
|
1210
|
-
!["boolean"].includes(typeof nullEmpty)) {
|
|
1211
|
-
throw `Invalid orderBy option (${JSON.stringify(orderBy, null, 2)}) \n
|
|
1212
|
-
Expecting { key: string, asc?: boolean, nulls?: 'first' | 'last' | null | undefined, nullEmpty?: boolean } `;
|
|
1213
|
-
}
|
|
1214
|
-
return [{ key, asc, nulls, nullEmpty }];
|
|
1215
|
-
}
|
|
1216
|
-
if (expectOne && keys.length > 1) {
|
|
1217
|
-
throw "\nInvalid orderBy " + JSON.stringify(orderBy) +
|
|
1218
|
-
"\nEach orderBy array element cannot have more than one key";
|
|
1219
|
-
}
|
|
1220
|
-
/* { key2: true, key1: false } */
|
|
1221
|
-
if (!Object.values(orderBy).find(v => ![true, false].includes(v))) {
|
|
1222
|
-
return keys.map(key => ({ key, asc: Boolean(orderBy[key]) }));
|
|
1223
|
-
/* { key2: -1, key1: 1 } */
|
|
1224
|
-
}
|
|
1225
|
-
else if (!Object.values(orderBy).find(v => ![-1, 1].includes(v))) {
|
|
1226
|
-
return keys.map(key => ({ key, asc: orderBy[key] === 1 }));
|
|
1227
|
-
/* { key2: "asc", key1: "desc" } */
|
|
1228
|
-
}
|
|
1229
|
-
else if (!Object.values(orderBy).find(v => !["asc", "desc"].includes(v))) {
|
|
1230
|
-
return keys.map(key => ({ key, asc: orderBy[key] === "asc" }));
|
|
1231
|
-
}
|
|
1232
|
-
else
|
|
1233
|
-
return throwErr();
|
|
1234
|
-
};
|
|
1235
|
-
if (!orderBy)
|
|
1236
|
-
return [];
|
|
1237
|
-
let _ob = [];
|
|
1238
|
-
if (isPlainObject(orderBy)) {
|
|
1239
|
-
_ob = parseOrderObj(orderBy);
|
|
1240
|
-
}
|
|
1241
|
-
else if (typeof orderBy === "string") {
|
|
1242
|
-
/* string */
|
|
1243
|
-
_ob = [{ key: orderBy, asc: true }];
|
|
1244
|
-
}
|
|
1245
|
-
else if (Array.isArray(orderBy)) {
|
|
1246
|
-
/* Order by is formed of a list of ascending field names */
|
|
1247
|
-
let _orderBy = orderBy;
|
|
1248
|
-
if (_orderBy && !_orderBy.find(v => typeof v !== "string")) {
|
|
1249
|
-
/* [string] */
|
|
1250
|
-
_ob = _orderBy.map(key => ({ key, asc: true }));
|
|
1251
|
-
}
|
|
1252
|
-
else if (_orderBy.find(v => isPlainObject(v) && Object.keys(v).length)) {
|
|
1253
|
-
_ob = _orderBy.map(v => parseOrderObj(v, true)[0]);
|
|
1254
|
-
}
|
|
1255
|
-
else
|
|
1256
|
-
return throwErr();
|
|
1257
|
-
}
|
|
1258
|
-
else
|
|
1259
|
-
return throwErr();
|
|
1260
|
-
if (!_ob || !_ob.length)
|
|
1261
|
-
return [];
|
|
1262
|
-
const validatedAggAliases = select.filter(s => s.type !== "joinedColumn" &&
|
|
1263
|
-
(!s.fields.length || s.fields.every(f => allowed_cols.includes(f)))).map(s => s.alias);
|
|
1264
|
-
let bad_param = _ob.find(({ key }) => !(validatedAggAliases || []).includes(key) &&
|
|
1265
|
-
!allowed_cols.includes(key));
|
|
1266
|
-
if (!bad_param) {
|
|
1267
|
-
const selectedAliases = select.filter(s => s.selected).map(s => s.alias);
|
|
1268
|
-
// return (excludeOrder? "" : " ORDER BY ") + (_ob.map(({ key, asc, nulls, nullEmpty = false }) => {
|
|
1269
|
-
return _ob.map(({ key, asc, nulls, nullEmpty = false }) => {
|
|
1270
|
-
/* Order by column index when possible to bypass name collision when ordering by a computed column.
|
|
1271
|
-
(Postgres will sort by existing columns wheundefined possible)
|
|
1272
|
-
*/
|
|
1273
|
-
const orderType = asc ? " ASC " : " DESC ";
|
|
1274
|
-
const index = selectedAliases.indexOf(key) + 1;
|
|
1275
|
-
const nullOrder = nulls ? ` NULLS ${nulls === "first" ? " FIRST " : " LAST "}` : "";
|
|
1276
|
-
let colKey = (index > 0 && !nullEmpty) ? index : [tableAlias, key].filter(prostgles_types_1.isDefined).map(prostgles_types_1.asName).join(".");
|
|
1277
|
-
if (nullEmpty) {
|
|
1278
|
-
colKey = `nullif(trim(${colKey}::text), '')`;
|
|
1279
|
-
}
|
|
1280
|
-
const res = `${colKey} ${orderType} ${nullOrder}`;
|
|
1281
|
-
if (typeof colKey === "number") {
|
|
1282
|
-
return {
|
|
1283
|
-
asc,
|
|
1284
|
-
fieldPosition: colKey
|
|
1285
|
-
};
|
|
1286
|
-
}
|
|
1287
|
-
return {
|
|
1288
|
-
fieldQuery: colKey,
|
|
1289
|
-
asc,
|
|
1290
|
-
};
|
|
1291
|
-
});
|
|
1292
|
-
}
|
|
1293
|
-
else {
|
|
1294
|
-
throw "Invalid/disallowed orderBy fields or params: " + bad_param.key;
|
|
1295
|
-
}
|
|
1296
|
-
}
|
|
1297
|
-
/* This relates only to SELECT */
|
|
1298
|
-
prepareLimitQuery(limit = 1000, p) {
|
|
1299
|
-
if (limit !== undefined && limit !== null && !Number.isInteger(limit)) {
|
|
1300
|
-
throw "Unexpected LIMIT. Must be null or an integer";
|
|
1301
|
-
}
|
|
1302
|
-
let _limit = limit;
|
|
1303
|
-
// if(_limit === undefined && p.select.maxLimit === null){
|
|
1304
|
-
// _limit = 1000;
|
|
1305
|
-
/* If no limit then set as the lesser of (100, maxLimit) */
|
|
1306
|
-
// } else
|
|
1307
|
-
if (_limit !== null && !Number.isInteger(_limit) && p.select.maxLimit !== null) {
|
|
1308
|
-
_limit = [100, p.select.maxLimit].filter(Number.isInteger).sort((a, b) => a - b)[0];
|
|
1309
|
-
}
|
|
1310
|
-
else {
|
|
1311
|
-
/* If a limit higher than maxLimit specified throw error */
|
|
1312
|
-
if (Number.isInteger(p.select.maxLimit) && _limit > p.select.maxLimit) {
|
|
1313
|
-
throw `Unexpected LIMIT ${_limit}. Must be less than the published maxLimit: ` + p.select.maxLimit;
|
|
1314
|
-
}
|
|
1315
|
-
}
|
|
1316
|
-
return _limit;
|
|
1317
|
-
}
|
|
1318
|
-
/* This relates only to SELECT */
|
|
1319
|
-
prepareOffsetQuery(offset) {
|
|
1320
|
-
if (Number.isInteger(offset)) {
|
|
1321
|
-
return offset;
|
|
1322
|
-
}
|
|
1323
|
-
return 0;
|
|
1324
|
-
}
|
|
1325
|
-
intersectColumns(allowedFields, dissallowedFields, fixIssues = false) {
|
|
1326
|
-
let result = [];
|
|
1327
|
-
if (allowedFields) {
|
|
1328
|
-
result = this.parseFieldFilter(allowedFields);
|
|
1329
|
-
}
|
|
1330
|
-
if (dissallowedFields) {
|
|
1331
|
-
const _dissalowed = this.parseFieldFilter(dissallowedFields);
|
|
1332
|
-
if (!fixIssues) {
|
|
1333
|
-
throw `dissallowed/invalid field found for ${this.name}: `;
|
|
1334
|
-
}
|
|
1335
|
-
result = result.filter(key => !_dissalowed.includes(key));
|
|
1336
|
-
}
|
|
1337
|
-
return result;
|
|
1338
|
-
}
|
|
1339
|
-
/**
|
|
1340
|
-
* Prepare and validate field object:
|
|
1341
|
-
* @example ({ item_id: 1 }, { user_id: 32 }) => { item_id: 1, user_id: 32 }
|
|
1342
|
-
* OR
|
|
1343
|
-
* ({ a: 1 }, { b: 32 }, ["c", "d"]) => throw "a field is not allowed"
|
|
1344
|
-
* @param {Object} obj - initial data
|
|
1345
|
-
* @param {Object} forcedData - set/override property
|
|
1346
|
-
* @param {string[]} allowed_cols - allowed columns (excluding forcedData) from table rules
|
|
1347
|
-
*/
|
|
1348
|
-
prepareFieldValues(obj = {}, forcedData = {}, allowed_cols, fixIssues = false) {
|
|
1349
|
-
let column_names = this.column_names.slice(0);
|
|
1350
|
-
if (!column_names || !column_names.length)
|
|
1351
|
-
throw "table column_names mising";
|
|
1352
|
-
let _allowed_cols = column_names.slice(0);
|
|
1353
|
-
let _obj = { ...obj };
|
|
1354
|
-
if (allowed_cols) {
|
|
1355
|
-
_allowed_cols = this.parseFieldFilter(allowed_cols, false);
|
|
1356
|
-
}
|
|
1357
|
-
let final_filter = { ..._obj }, filter_keys = Object.keys(final_filter);
|
|
1358
|
-
if (fixIssues && filter_keys.length) {
|
|
1359
|
-
final_filter = {};
|
|
1360
|
-
filter_keys
|
|
1361
|
-
.filter(col => _allowed_cols.includes(col))
|
|
1362
|
-
.map(col => {
|
|
1363
|
-
final_filter[col] = _obj[col];
|
|
1364
|
-
});
|
|
1365
|
-
}
|
|
1366
|
-
/* If has keys check against allowed_cols */
|
|
1367
|
-
if (final_filter && Object.keys(final_filter).length && _allowed_cols) {
|
|
1368
|
-
validateObj(final_filter, _allowed_cols);
|
|
1369
|
-
}
|
|
1370
|
-
if (forcedData && Object.keys(forcedData).length) {
|
|
1371
|
-
final_filter = { ...final_filter, ...forcedData };
|
|
1372
|
-
}
|
|
1373
|
-
validateObj(final_filter, column_names.slice(0));
|
|
1374
|
-
return final_filter;
|
|
1375
|
-
}
|
|
1376
|
-
parseFieldFilter(fieldParams = "*", allow_empty = true, allowed_cols) {
|
|
1377
|
-
return ViewHandler._parseFieldFilter(fieldParams, allow_empty, allowed_cols || this.column_names.slice(0));
|
|
1378
|
-
}
|
|
1379
|
-
/**
|
|
1380
|
-
* Filter string array
|
|
1381
|
-
* @param {FieldFilter} fieldParams - { col1: 0, col2: 0 } | { col1: true, col2: true } | "*" | ["key1", "key2"] | []
|
|
1382
|
-
* @param {boolean} allow_empty - allow empty select. defaults to true
|
|
1383
|
-
*/
|
|
1384
|
-
static _parseFieldFilter(fieldParams = "*", allow_empty = true, all_cols) {
|
|
1385
|
-
if (!all_cols)
|
|
1386
|
-
throw "all_cols missing";
|
|
1387
|
-
const all_fields = all_cols; // || this.column_names.slice(0);
|
|
1388
|
-
let colNames = [], initialParams = JSON.stringify(fieldParams);
|
|
1389
|
-
if (fieldParams) {
|
|
1390
|
-
/*
|
|
1391
|
-
"field1, field2, field4" | "*"
|
|
1392
|
-
*/
|
|
1393
|
-
if (typeof fieldParams === "string") {
|
|
1394
|
-
fieldParams = fieldParams.split(",").map(k => k.trim());
|
|
1395
|
-
}
|
|
1396
|
-
/* string[] */
|
|
1397
|
-
if (Array.isArray(fieldParams) && !fieldParams.find(f => typeof f !== "string")) {
|
|
1398
|
-
/*
|
|
1399
|
-
["*"]
|
|
1400
|
-
*/
|
|
1401
|
-
if (fieldParams[0] === "*") {
|
|
1402
|
-
return all_fields.slice(0);
|
|
1403
|
-
/*
|
|
1404
|
-
[""]
|
|
1405
|
-
*/
|
|
1406
|
-
}
|
|
1407
|
-
else if (fieldParams[0] === "") {
|
|
1408
|
-
if (allow_empty) {
|
|
1409
|
-
return [""];
|
|
1410
|
-
}
|
|
1411
|
-
else {
|
|
1412
|
-
throw "Empty value not allowed";
|
|
1413
|
-
}
|
|
1414
|
-
/*
|
|
1415
|
-
["field1", "field2", "field3"]
|
|
1416
|
-
*/
|
|
1417
|
-
}
|
|
1418
|
-
else {
|
|
1419
|
-
colNames = fieldParams.slice(0);
|
|
1420
|
-
}
|
|
1421
|
-
/*
|
|
1422
|
-
{ field1: true, field2: true } = only field1 and field2
|
|
1423
|
-
{ field1: false, field2: false } = all fields except field1 and field2
|
|
1424
|
-
*/
|
|
1425
|
-
}
|
|
1426
|
-
else if (isPlainObject(fieldParams)) {
|
|
1427
|
-
if (!(0, prostgles_types_1.getKeys)(fieldParams).length) {
|
|
1428
|
-
return []; //all_fields.slice(0) as typeof all_fields;
|
|
1429
|
-
}
|
|
1430
|
-
let keys = (0, prostgles_types_1.getKeys)(fieldParams);
|
|
1431
|
-
if (keys[0] === "") {
|
|
1432
|
-
if (allow_empty) {
|
|
1433
|
-
return [""];
|
|
1434
|
-
}
|
|
1435
|
-
else {
|
|
1436
|
-
throw "Empty value not allowed";
|
|
1437
|
-
}
|
|
1438
|
-
}
|
|
1439
|
-
validate(keys);
|
|
1440
|
-
keys.forEach(key => {
|
|
1441
|
-
const allowedVals = [true, false, 0, 1];
|
|
1442
|
-
if (!allowedVals.includes(fieldParams[key]))
|
|
1443
|
-
throw `Invalid field selection value for: { ${key}: ${fieldParams[key]} }. \n Allowed values: ${allowedVals.join(" OR ")}`;
|
|
1444
|
-
});
|
|
1445
|
-
let allowed = keys.filter(key => fieldParams[key]), disallowed = keys.filter(key => !fieldParams[key]);
|
|
1446
|
-
if (disallowed && disallowed.length) {
|
|
1447
|
-
return all_fields.filter(col => !disallowed.includes(col));
|
|
1448
|
-
}
|
|
1449
|
-
else {
|
|
1450
|
-
return [...allowed];
|
|
1451
|
-
}
|
|
1452
|
-
}
|
|
1453
|
-
else {
|
|
1454
|
-
throw " Unrecognised field filter.\nExpecting any of: string | string[] | { [field]: boolean } \n Received -> " + initialParams;
|
|
1455
|
-
}
|
|
1456
|
-
validate(colNames);
|
|
1457
|
-
}
|
|
1458
|
-
return colNames;
|
|
1459
|
-
function validate(cols) {
|
|
1460
|
-
let bad_keys = cols.filter(col => !all_fields.includes(col));
|
|
1461
|
-
if (bad_keys && bad_keys.length) {
|
|
1462
|
-
throw "\nUnrecognised or illegal fields: " + bad_keys.join(", ");
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1465
|
-
}
|
|
1466
|
-
}
|
|
1467
|
-
exports.ViewHandler = ViewHandler;
|
|
1468
|
-
function isPojoObject(obj) {
|
|
1469
|
-
if (obj && (typeof obj !== "object" || Array.isArray(obj) || obj instanceof Date)) {
|
|
1470
|
-
return false;
|
|
1471
|
-
}
|
|
1472
|
-
return true;
|
|
1473
|
-
}
|
|
1474
|
-
exports.isPojoObject = isPojoObject;
|
|
1475
|
-
class TableHandler extends ViewHandler {
|
|
1476
|
-
constructor(db, tableOrViewInfo, dboBuilder, t, dbTX, joinPaths) {
|
|
1477
|
-
super(db, tableOrViewInfo, dboBuilder, t, dbTX, joinPaths);
|
|
1478
|
-
this.insertDataParse = insertDataParse_1.insertDataParse;
|
|
1479
|
-
this.prepareReturning = async (returning, allowedFields) => {
|
|
1480
|
-
let result = [];
|
|
1481
|
-
if (returning) {
|
|
1482
|
-
let sBuilder = new QueryBuilder_1.SelectItemBuilder({
|
|
1483
|
-
allFields: this.column_names.slice(0),
|
|
1484
|
-
allowedFields,
|
|
1485
|
-
allowedOrderByFields: allowedFields,
|
|
1486
|
-
computedFields: QueryBuilder_1.COMPUTED_FIELDS,
|
|
1487
|
-
functions: QueryBuilder_1.FUNCTIONS.filter(f => f.type === "function" && f.singleColArg),
|
|
1488
|
-
isView: this.is_view,
|
|
1489
|
-
columns: this.columns,
|
|
1490
|
-
});
|
|
1491
|
-
await sBuilder.parseUserSelect(returning);
|
|
1492
|
-
return sBuilder.select;
|
|
1493
|
-
}
|
|
1494
|
-
return result;
|
|
1495
|
-
};
|
|
1496
|
-
this.remove = this.delete;
|
|
1497
|
-
this.io_stats = {
|
|
1498
|
-
since: Date.now(),
|
|
1499
|
-
queries: 0,
|
|
1500
|
-
throttle_queries_per_sec: 500,
|
|
1501
|
-
batching: null
|
|
1502
|
-
};
|
|
1503
|
-
this.is_view = false;
|
|
1504
|
-
this.is_media = dboBuilder.prostgles.isMedia(this.name);
|
|
1505
|
-
}
|
|
1506
|
-
/* TO DO: Maybe finished query batching */
|
|
1507
|
-
willBatch(query) {
|
|
1508
|
-
const now = Date.now();
|
|
1509
|
-
if (this.io_stats.since < Date.now()) {
|
|
1510
|
-
this.io_stats.since = Date.now();
|
|
1511
|
-
this.io_stats.queries = 0;
|
|
1512
|
-
}
|
|
1513
|
-
else {
|
|
1514
|
-
this.io_stats.queries++;
|
|
1515
|
-
}
|
|
1516
|
-
if (this.io_stats.queries > this.io_stats.throttle_queries_per_sec) {
|
|
1517
|
-
return true;
|
|
1518
|
-
}
|
|
1519
|
-
}
|
|
1520
|
-
async subscribe(filter, params = {}, localFunc, table_rules, localParams) {
|
|
1521
|
-
try {
|
|
1522
|
-
if (this.is_view)
|
|
1523
|
-
throw "Cannot subscribe to a view";
|
|
1524
|
-
if (this.t)
|
|
1525
|
-
throw "subscribe not allowed within transactions";
|
|
1526
|
-
if (!localParams && !localFunc)
|
|
1527
|
-
throw " missing data. provide -> localFunc | localParams { socket } ";
|
|
1528
|
-
if (localParams && localParams.socket && localFunc) {
|
|
1529
|
-
console.error({ localParams, localFunc });
|
|
1530
|
-
throw " Cannot have localFunc AND socket ";
|
|
1531
|
-
}
|
|
1532
|
-
const { filterFields, forcedFilter } = (0, utils_1.get)(table_rules, "select") || {}, filterOpts = await this.prepareWhere({ filter, forcedFilter, addKeywords: false, filterFields, tableAlias: undefined, localParams, tableRule: table_rules }), condition = filterOpts.where, throttle = (0, utils_1.get)(params, "throttle") || 0, selectParams = (0, PubSubManager_1.omitKeys)(params || {}, ["throttle"]);
|
|
1533
|
-
// const { subOne = false } = localParams || {};
|
|
1534
|
-
const filterSize = JSON.stringify(filter || {}).length;
|
|
1535
|
-
if (filterSize * 4 > 2704) {
|
|
1536
|
-
throw "filter too big. Might exceed the btree version 4 maximum 2704";
|
|
1537
|
-
}
|
|
1538
|
-
if (!localFunc) {
|
|
1539
|
-
if (!this.dboBuilder.prostgles.isSuperUser)
|
|
1540
|
-
throw "Subscribe not possible. Must be superuser to add triggers 1856";
|
|
1541
|
-
return await this.find(filter, { ...selectParams, limit: 0 }, undefined, table_rules, localParams)
|
|
1542
|
-
.then(async (isValid) => {
|
|
1543
|
-
const { socket } = localParams ?? {};
|
|
1544
|
-
const pubSubManager = await this.dboBuilder.getPubSubManager();
|
|
1545
|
-
return pubSubManager.addSub({
|
|
1546
|
-
table_info: this.tableOrViewInfo,
|
|
1547
|
-
socket,
|
|
1548
|
-
table_rules,
|
|
1549
|
-
condition: condition,
|
|
1550
|
-
func: undefined,
|
|
1551
|
-
filter: { ...filter },
|
|
1552
|
-
params: { ...selectParams },
|
|
1553
|
-
socket_id: socket?.id,
|
|
1554
|
-
table_name: this.name,
|
|
1555
|
-
throttle,
|
|
1556
|
-
last_throttled: 0,
|
|
1557
|
-
// subOne
|
|
1558
|
-
}).then(channelName => ({ channelName }));
|
|
1559
|
-
});
|
|
1560
|
-
}
|
|
1561
|
-
else {
|
|
1562
|
-
const pubSubManager = await this.dboBuilder.getPubSubManager();
|
|
1563
|
-
pubSubManager.addSub({
|
|
1564
|
-
table_info: this.tableOrViewInfo,
|
|
1565
|
-
socket: undefined,
|
|
1566
|
-
table_rules,
|
|
1567
|
-
condition,
|
|
1568
|
-
func: localFunc,
|
|
1569
|
-
filter: { ...filter },
|
|
1570
|
-
params: { ...selectParams },
|
|
1571
|
-
socket_id: undefined,
|
|
1572
|
-
table_name: this.name,
|
|
1573
|
-
throttle,
|
|
1574
|
-
last_throttled: 0,
|
|
1575
|
-
// subOne
|
|
1576
|
-
}).then(channelName => ({ channelName }));
|
|
1577
|
-
const unsubscribe = async () => {
|
|
1578
|
-
const pubSubManager = await this.dboBuilder.getPubSubManager();
|
|
1579
|
-
pubSubManager.removeLocalSub(this.name, condition, localFunc);
|
|
1580
|
-
};
|
|
1581
|
-
let res = Object.freeze({ unsubscribe });
|
|
1582
|
-
return res;
|
|
1583
|
-
}
|
|
1584
|
-
}
|
|
1585
|
-
catch (e) {
|
|
1586
|
-
if (localParams && localParams.testRule)
|
|
1587
|
-
throw e;
|
|
1588
|
-
throw parseError(e, `dbo.${this.name}.subscribe()`);
|
|
1589
|
-
}
|
|
1590
|
-
}
|
|
1591
|
-
subscribeOne(filter, params = {}, localFunc, table_rules, localParams) {
|
|
1592
|
-
let func = localParams ? undefined : (rows) => localFunc(rows[0]);
|
|
1593
|
-
return this.subscribe(filter, { ...params, limit: 2 }, func, table_rules, localParams);
|
|
1594
|
-
}
|
|
1595
|
-
async updateBatch(data, params, tableRules, localParams) {
|
|
1596
|
-
try {
|
|
1597
|
-
const queries = await Promise.all(data.map(async ([filter, data]) => await this.update(filter, data, { ...(params || {}), returning: undefined }, tableRules, { ...(localParams || {}), returnQuery: true })));
|
|
1598
|
-
const keys = (data && data.length) ? Object.keys(data[0]) : [];
|
|
1599
|
-
return this.db.tx(t => {
|
|
1600
|
-
const _queries = queries.map(q => t.none(q));
|
|
1601
|
-
return t.batch(_queries);
|
|
1602
|
-
}).catch(err => makeErr(err, localParams, this, keys));
|
|
1603
|
-
}
|
|
1604
|
-
catch (e) {
|
|
1605
|
-
if (localParams && localParams.testRule)
|
|
1606
|
-
throw e;
|
|
1607
|
-
throw parseError(e, `dbo.${this.name}.update()`);
|
|
1608
|
-
}
|
|
1609
|
-
}
|
|
1610
|
-
async parseUpdateRules(filter, newData, params, tableRules, localParams) {
|
|
1611
|
-
const { testRule = false } = localParams ?? {};
|
|
1612
|
-
if (!testRule) {
|
|
1613
|
-
if (!newData || !Object.keys(newData).length)
|
|
1614
|
-
throw "no update data provided\nEXPECTING db.table.update(filter, updateData, options)";
|
|
1615
|
-
this.checkFilter(filter);
|
|
1616
|
-
}
|
|
1617
|
-
let forcedFilter = {}, forcedData = {}, validate, returningFields = "*", filterFields = "*", fields = "*";
|
|
1618
|
-
let finalUpdateFilter = { ...filter };
|
|
1619
|
-
if (tableRules) {
|
|
1620
|
-
if (!tableRules.update)
|
|
1621
|
-
throw "update rules missing for " + this.name;
|
|
1622
|
-
({ forcedFilter, forcedData, fields, filterFields, validate } = tableRules.update);
|
|
1623
|
-
returningFields = tableRules.update.returningFields ?? (0, utils_1.get)(tableRules, "select.fields") ?? "";
|
|
1624
|
-
if (!returningFields && params?.returning) {
|
|
1625
|
-
throw "You are not allowed to return any fields from the update";
|
|
1626
|
-
}
|
|
1627
|
-
if (!fields)
|
|
1628
|
-
throw ` Invalid update rule fo r ${this.name}. fields missing `;
|
|
1629
|
-
finalUpdateFilter = (await this.prepareWhere({ filter, forcedFilter, filterFields, localParams, tableRule: tableRules })).filter;
|
|
1630
|
-
if (tableRules.update.dynamicFields?.length) {
|
|
1631
|
-
/**
|
|
1632
|
-
* dynamicFields.fields used to allow a custom list of fields for specific records
|
|
1633
|
-
* dynamicFields.filter cannot overlap each other
|
|
1634
|
-
* updates must target records from a specific dynamicFields.filter or not match any dynamicFields.filter
|
|
1635
|
-
*/
|
|
1636
|
-
if (testRule) {
|
|
1637
|
-
for await (const [dfIndex, dfRule] of tableRules.update.dynamicFields.entries()) {
|
|
1638
|
-
const condition = await this.getCondition({ allowed_colnames: this.column_names, filter: dfRule.filter });
|
|
1639
|
-
if (!condition)
|
|
1640
|
-
throw "dynamicFields.filter cannot be empty: " + JSON.stringify(dfRule);
|
|
1641
|
-
await this.find(dfRule.filter, { limit: 0 });
|
|
1642
|
-
/** Ensure dynamicFields filters do not overlap */
|
|
1643
|
-
for await (const [_dfIndex, _dfRule] of tableRules.update.dynamicFields.entries()) {
|
|
1644
|
-
if (dfIndex !== _dfIndex) {
|
|
1645
|
-
if (await this.findOne({ $and: [dfRule.filter, _dfRule.filter] }, { select: "" })) {
|
|
1646
|
-
throw `dynamicFields.filter cannot overlap each other. \n
|
|
1647
|
-
Overlapping dynamicFields rules:
|
|
1648
|
-
${JSON.stringify(dfRule)}
|
|
1649
|
-
AND
|
|
1650
|
-
${JSON.stringify(_dfRule)}
|
|
1651
|
-
`;
|
|
1652
|
-
}
|
|
1653
|
-
}
|
|
1654
|
-
}
|
|
1655
|
-
}
|
|
1656
|
-
}
|
|
1657
|
-
/** Pick dynamicFields.fields if matching filter */
|
|
1658
|
-
let matchedRule;
|
|
1659
|
-
for await (const dfRule of tableRules.update.dynamicFields) {
|
|
1660
|
-
const match = await this.findOne({ $and: [finalUpdateFilter, dfRule.filter].filter(prostgles_types_1.isDefined) });
|
|
1661
|
-
if (match) {
|
|
1662
|
-
/** Ensure it doesn't overlap with other dynamicFields.filter */
|
|
1663
|
-
if (matchedRule) {
|
|
1664
|
-
throw "Your update is targeting multiple tableRules.update.dynamicFields. Restrict update filter to only target one rule";
|
|
1665
|
-
}
|
|
1666
|
-
matchedRule = dfRule;
|
|
1667
|
-
fields = dfRule.fields;
|
|
1668
|
-
}
|
|
1669
|
-
}
|
|
1670
|
-
}
|
|
1671
|
-
/* Safely test publish rules */
|
|
1672
|
-
if (testRule) {
|
|
1673
|
-
await this.validateViewRules({ fields, filterFields, returningFields, forcedFilter, dynamicFields: tableRules.update.dynamicFields, rule: "update" });
|
|
1674
|
-
if (forcedData) {
|
|
1675
|
-
try {
|
|
1676
|
-
const { data, allowedCols } = this.validateNewData({ row: forcedData, forcedData: undefined, allowedFields: "*", tableRules, fixIssues: false });
|
|
1677
|
-
const updateQ = await this.colSet.getUpdateQuery(data, allowedCols, this.dbTX || this.dboBuilder.dbo, validate ? ((row) => validate({ update: row, filter: {} }, this.dbTX || this.dboBuilder.dbo)) : undefined); //pgp.helpers.update(data, columnSet)
|
|
1678
|
-
let query = updateQ + " WHERE FALSE ";
|
|
1679
|
-
await this.db.any("EXPLAIN " + query);
|
|
1680
|
-
}
|
|
1681
|
-
catch (e) {
|
|
1682
|
-
throw " issue with forcedData: \nVALUE: " + JSON.stringify(forcedData, null, 2) + "\nERROR: " + e;
|
|
1683
|
-
}
|
|
1684
|
-
}
|
|
1685
|
-
return true;
|
|
1686
|
-
}
|
|
1687
|
-
}
|
|
1688
|
-
/* Update all allowed fields (fields) except the forcedFilter (so that the user cannot change the forced filter values) */
|
|
1689
|
-
let _fields = this.parseFieldFilter(fields);
|
|
1690
|
-
/**
|
|
1691
|
-
* A forced filter must be basic
|
|
1692
|
-
*/
|
|
1693
|
-
if (forcedFilter) {
|
|
1694
|
-
const _forcedFilterKeys = Object.keys(forcedFilter);
|
|
1695
|
-
const nonFields = _forcedFilterKeys.filter(key => !this.column_names.includes(key));
|
|
1696
|
-
if (nonFields.length)
|
|
1697
|
-
throw "forcedFilter must be a basic filter ( { col_name: 'value' } ). Invalid filter keys: " + nonFields;
|
|
1698
|
-
// const clashingFields = _forcedFilterKeys.filter(key => _fields.includes(key));
|
|
1699
|
-
}
|
|
1700
|
-
const validateRow = validate ? (row) => validate({ update: row, filter: finalUpdateFilter }, this.dbTX || this.dboBuilder.dbo) : undefined;
|
|
1701
|
-
return {
|
|
1702
|
-
fields: _fields,
|
|
1703
|
-
validateRow,
|
|
1704
|
-
finalUpdateFilter,
|
|
1705
|
-
forcedData,
|
|
1706
|
-
forcedFilter,
|
|
1707
|
-
returningFields,
|
|
1708
|
-
filterFields,
|
|
1709
|
-
};
|
|
1710
|
-
}
|
|
1711
|
-
async update(filter, newData, params, tableRules, localParams) {
|
|
1712
|
-
return update_1.update.bind(this)(filter, newData, params, tableRules, localParams);
|
|
1713
|
-
}
|
|
1714
|
-
;
|
|
1715
|
-
validateNewData({ row, forcedData, allowedFields, tableRules, fixIssues = false }) {
|
|
1716
|
-
const synced_field = (0, utils_1.get)(tableRules ?? {}, "sync.synced_field");
|
|
1717
|
-
/* Update synced_field if sync is on and missing */
|
|
1718
|
-
if (synced_field && !row[synced_field]) {
|
|
1719
|
-
row[synced_field] = Date.now();
|
|
1720
|
-
}
|
|
1721
|
-
let data = this.prepareFieldValues(row, forcedData, allowedFields, fixIssues);
|
|
1722
|
-
const dataKeys = (0, prostgles_types_1.getKeys)(data);
|
|
1723
|
-
dataKeys.map(col => {
|
|
1724
|
-
this.dboBuilder.prostgles?.tableConfigurator?.checkColVal({ table: this.name, col, value: data[col] });
|
|
1725
|
-
const colConfig = this.dboBuilder.prostgles?.tableConfigurator?.getColumnConfig(this.name, col);
|
|
1726
|
-
if (colConfig && (0, prostgles_types_1.isObject)(colConfig) && "isText" in colConfig && data[col]) {
|
|
1727
|
-
if (colConfig.lowerCased) {
|
|
1728
|
-
data[col] = data[col].toString().toLowerCase();
|
|
1729
|
-
}
|
|
1730
|
-
if (colConfig.trimmed) {
|
|
1731
|
-
data[col] = data[col].toString().trim();
|
|
1732
|
-
}
|
|
1733
|
-
}
|
|
1734
|
-
});
|
|
1735
|
-
return { data, allowedCols: this.columns.filter(c => dataKeys.includes(c.name)).map(c => c.name) };
|
|
1736
|
-
}
|
|
1737
|
-
async insert(rowOrRows, param2, param3_unused, tableRules, _localParams) {
|
|
1738
|
-
return insert_1.insert.bind(this)(rowOrRows, param2, param3_unused, tableRules, _localParams);
|
|
1739
|
-
}
|
|
1740
|
-
makeReturnQuery(items) {
|
|
1741
|
-
if (items?.length)
|
|
1742
|
-
return " RETURNING " + items.map(s => s.getQuery() + " AS " + (0, prostgles_types_1.asName)(s.alias)).join(", ");
|
|
1743
|
-
return "";
|
|
1744
|
-
}
|
|
1745
|
-
async delete(filter, params, param3_unused, table_rules, localParams) {
|
|
1746
|
-
return delete_1._delete.bind(this)(filter, params, param3_unused, table_rules, localParams);
|
|
1747
|
-
}
|
|
1748
|
-
;
|
|
1749
|
-
remove(filter, params, param3_unused, tableRules, localParams) {
|
|
1750
|
-
return this.delete(filter, params, param3_unused, tableRules, localParams);
|
|
1751
|
-
}
|
|
1752
|
-
async upsert(filter, newData, params, table_rules, localParams) {
|
|
1753
|
-
try {
|
|
1754
|
-
/* Do it within a transaction to ensure consisency */
|
|
1755
|
-
if (!this.t) {
|
|
1756
|
-
return this.dboBuilder.getTX(dbTX => _upsert(dbTX[this.name]));
|
|
1757
|
-
}
|
|
1758
|
-
else {
|
|
1759
|
-
return _upsert(this);
|
|
1760
|
-
}
|
|
1761
|
-
async function _upsert(tblH) {
|
|
1762
|
-
return tblH.find(filter, { select: "", limit: 1 }, undefined, table_rules, localParams)
|
|
1763
|
-
.then(exists => {
|
|
1764
|
-
if (exists && exists.length) {
|
|
1765
|
-
return tblH.update(filter, newData, params, table_rules, localParams);
|
|
1766
|
-
}
|
|
1767
|
-
else {
|
|
1768
|
-
return tblH.insert({ ...newData, ...filter }, params, undefined, table_rules, localParams);
|
|
1769
|
-
}
|
|
1770
|
-
});
|
|
1771
|
-
}
|
|
1772
|
-
}
|
|
1773
|
-
catch (e) {
|
|
1774
|
-
if (localParams && localParams.testRule)
|
|
1775
|
-
throw e;
|
|
1776
|
-
throw parseError(e, `dbo.${this.name}.upsert()`);
|
|
1777
|
-
}
|
|
1778
|
-
}
|
|
1779
|
-
;
|
|
1780
|
-
/* External request. Cannot sync from server */
|
|
1781
|
-
async sync(filter, params, param3_unused, table_rules, localParams) {
|
|
1782
|
-
if (!localParams)
|
|
1783
|
-
throw "Sync not allowed within the same server code";
|
|
1784
|
-
const { socket } = localParams;
|
|
1785
|
-
if (!socket)
|
|
1786
|
-
throw "INTERNAL ERROR: socket missing";
|
|
1787
|
-
if (!table_rules || !table_rules.sync || !table_rules.select)
|
|
1788
|
-
throw "INTERNAL ERROR: sync or select rules missing";
|
|
1789
|
-
if (this.t)
|
|
1790
|
-
throw "Sync not allowed within transactions";
|
|
1791
|
-
const ALLOWED_PARAMS = ["select"];
|
|
1792
|
-
const invalidParams = Object.keys(params || {}).filter(k => !ALLOWED_PARAMS.includes(k));
|
|
1793
|
-
if (invalidParams.length)
|
|
1794
|
-
throw "Invalid or dissallowed params found: " + invalidParams.join(", ");
|
|
1795
|
-
try {
|
|
1796
|
-
let { id_fields, synced_field, allow_delete } = table_rules.sync;
|
|
1797
|
-
const syncFields = [...id_fields, synced_field];
|
|
1798
|
-
if (!id_fields || !synced_field) {
|
|
1799
|
-
const err = "INTERNAL ERROR: id_fields OR synced_field missing from publish";
|
|
1800
|
-
console.error(err);
|
|
1801
|
-
throw err;
|
|
1802
|
-
}
|
|
1803
|
-
id_fields = this.parseFieldFilter(id_fields, false);
|
|
1804
|
-
let allowedSelect = this.parseFieldFilter((0, utils_1.get)(table_rules, "select.fields"), false);
|
|
1805
|
-
if (syncFields.find(f => !allowedSelect.includes(f))) {
|
|
1806
|
-
throw `INTERNAL ERROR: sync field missing from publish.${this.name}.select.fields`;
|
|
1807
|
-
}
|
|
1808
|
-
let select = this.getAllowedSelectFields((0, utils_1.get)(params || {}, "select") || "*", allowedSelect, false);
|
|
1809
|
-
if (!select.length)
|
|
1810
|
-
throw "Empty select not allowed";
|
|
1811
|
-
/* Add sync fields if missing */
|
|
1812
|
-
syncFields.map(sf => {
|
|
1813
|
-
if (!select.includes(sf))
|
|
1814
|
-
select.push(sf);
|
|
1815
|
-
});
|
|
1816
|
-
/* Step 1: parse command and params */
|
|
1817
|
-
return this.find(filter, { select, limit: 0 }, undefined, table_rules, localParams)
|
|
1818
|
-
.then(async (isValid) => {
|
|
1819
|
-
const { filterFields, forcedFilter } = (0, utils_1.get)(table_rules, "select") || {};
|
|
1820
|
-
const condition = (await this.prepareWhere({ filter, forcedFilter, filterFields, addKeywords: false, localParams, tableRule: table_rules })).where;
|
|
1821
|
-
// let final_filter = getFindFilter(filter, table_rules);
|
|
1822
|
-
const pubSubManager = await this.dboBuilder.getPubSubManager();
|
|
1823
|
-
return pubSubManager.addSync({
|
|
1824
|
-
table_info: this.tableOrViewInfo,
|
|
1825
|
-
condition,
|
|
1826
|
-
id_fields, synced_field,
|
|
1827
|
-
allow_delete,
|
|
1828
|
-
socket,
|
|
1829
|
-
table_rules,
|
|
1830
|
-
filter: { ...filter },
|
|
1831
|
-
params: { select }
|
|
1832
|
-
}).then(channelName => ({ channelName, id_fields, synced_field }));
|
|
1833
|
-
});
|
|
1834
|
-
}
|
|
1835
|
-
catch (e) {
|
|
1836
|
-
if (localParams && localParams.testRule)
|
|
1837
|
-
throw e;
|
|
1838
|
-
throw parseError(e, `dbo.${this.name}.sync()`);
|
|
1839
|
-
}
|
|
1840
|
-
/*
|
|
1841
|
-
REPLICATION
|
|
1842
|
-
|
|
1843
|
-
1 Sync proccess (NO DELETES ALLOWED):
|
|
1844
|
-
|
|
1845
|
-
Client sends:
|
|
1846
|
-
"sync-request"
|
|
1847
|
-
{ min_id, max_id, count, max_synced }
|
|
1848
|
-
|
|
1849
|
-
Server sends:
|
|
1850
|
-
"sync-pull"
|
|
1851
|
-
{ from_synced }
|
|
1852
|
-
|
|
1853
|
-
Client sends:
|
|
1854
|
-
"sync-push"
|
|
1855
|
-
{ data } -> WHERE synced >= from_synced
|
|
1856
|
-
|
|
1857
|
-
Server upserts:
|
|
1858
|
-
WHERE not exists synced = synced AND id = id
|
|
1859
|
-
UNTIL
|
|
1860
|
-
|
|
1861
|
-
Server sends
|
|
1862
|
-
"sync-push"
|
|
1863
|
-
{ data } -> WHERE synced >= from_synced
|
|
1864
|
-
*/
|
|
1865
|
-
}
|
|
1866
|
-
}
|
|
1867
|
-
exports.TableHandler = TableHandler;
|
|
1868
113
|
const Prostgles_1 = require("./Prostgles");
|
|
1869
114
|
const DBSchemaBuilder_1 = require("./DBSchemaBuilder");
|
|
115
|
+
const TableHandler_1 = require("./DboBuilder/TableHandler");
|
|
1870
116
|
class DboBuilder {
|
|
1871
117
|
constructor(prostgles) {
|
|
1872
118
|
this.schema = "public";
|
|
@@ -1915,7 +161,7 @@ class DboBuilder {
|
|
|
1915
161
|
return this.db.tx((t) => {
|
|
1916
162
|
let dbTX = {};
|
|
1917
163
|
this.tablesOrViews?.map(tov => {
|
|
1918
|
-
dbTX[tov.name] = new (tov.is_view ? ViewHandler : TableHandler)(this.db, tov, this, t, dbTX, this.joinPaths);
|
|
164
|
+
dbTX[tov.name] = new (tov.is_view ? ViewHandler_1.ViewHandler : TableHandler_1.TableHandler)(this.db, tov, this, t, dbTX, this.joinPaths);
|
|
1919
165
|
});
|
|
1920
166
|
if (!dbTX.sql) {
|
|
1921
167
|
dbTX.sql = this.runSQL;
|
|
@@ -2006,13 +252,13 @@ class DboBuilder {
|
|
|
2006
252
|
// Make joins graph
|
|
2007
253
|
this.joinGraph = {};
|
|
2008
254
|
this.joins.forEach(({ tables }) => {
|
|
2009
|
-
var _b,
|
|
255
|
+
var _b, _c;
|
|
2010
256
|
let _t = tables.slice().sort(), t1 = _t[0], t2 = _t[1];
|
|
2011
257
|
if (t1 === t2)
|
|
2012
258
|
return;
|
|
2013
259
|
(_b = this.joinGraph)[t1] ?? (_b[t1] = {});
|
|
2014
260
|
this.joinGraph[t1][t2] = 1;
|
|
2015
|
-
(
|
|
261
|
+
(_c = this.joinGraph)[t2] ?? (_c[t2] = {});
|
|
2016
262
|
this.joinGraph[t2][t1] = 1;
|
|
2017
263
|
});
|
|
2018
264
|
const tables = Array.from(new Set(this.joins.flatMap(t => t.tables)));
|
|
@@ -2061,7 +307,7 @@ class DboBuilder {
|
|
|
2061
307
|
Please provide a replacement keyword name using the $filter_keyName init option.
|
|
2062
308
|
Alternatively you can rename the table column\n`;
|
|
2063
309
|
}
|
|
2064
|
-
this.dbo[tov.name] = new (tov.is_view ? ViewHandler : TableHandler)(this.db, tov, this, undefined, undefined, this.joinPaths);
|
|
310
|
+
this.dbo[tov.name] = new (tov.is_view ? ViewHandler_1.ViewHandler : TableHandler_1.TableHandler)(this.db, tov, this, undefined, undefined, this.joinPaths);
|
|
2065
311
|
if (this.joinPaths && this.joinPaths.find(jp => [jp.t1, jp.t2].includes(tov.name))) {
|
|
2066
312
|
let table = tov.name;
|
|
2067
313
|
const makeJoin = (isLeft = true, filter, select, options) => {
|
|
@@ -2267,18 +513,6 @@ async function getTablesForSchemaPostgresSQL(db, schema = "public") {
|
|
|
2267
513
|
});
|
|
2268
514
|
return res;
|
|
2269
515
|
}
|
|
2270
|
-
/**
|
|
2271
|
-
* Throw error if illegal keys found in object
|
|
2272
|
-
*/
|
|
2273
|
-
function validateObj(obj, allowedKeys) {
|
|
2274
|
-
if (obj && Object.keys(obj).length) {
|
|
2275
|
-
const invalid_keys = Object.keys(obj).filter(k => !allowedKeys.includes(k));
|
|
2276
|
-
if (invalid_keys.length) {
|
|
2277
|
-
throw "Invalid/Illegal fields found: " + invalid_keys.join(", ");
|
|
2278
|
-
}
|
|
2279
|
-
}
|
|
2280
|
-
return obj;
|
|
2281
|
-
}
|
|
2282
516
|
function isPlainObject(o) {
|
|
2283
517
|
return Object(o) === o && Object.getPrototypeOf(o) === Object.prototype;
|
|
2284
518
|
}
|