prostgles-server 4.2.159 → 4.2.161
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/Auth/setEmailProvider.js +2 -2
- package/dist/Auth/setEmailProvider.js.map +1 -1
- package/lib/Auth/AuthHandler.ts +436 -0
- package/lib/Auth/AuthTypes.ts +280 -0
- package/lib/Auth/getSafeReturnURL.ts +35 -0
- package/lib/Auth/sendEmail.ts +83 -0
- package/lib/Auth/setAuthProviders.ts +128 -0
- package/lib/Auth/setEmailProvider.ts +85 -0
- package/lib/Auth/setupAuthRoutes.ts +161 -0
- package/lib/DBEventsManager.ts +178 -0
- package/lib/DBSchemaBuilder.ts +225 -0
- package/lib/DboBuilder/DboBuilder.ts +319 -0
- package/lib/DboBuilder/DboBuilderTypes.ts +361 -0
- package/lib/DboBuilder/QueryBuilder/Functions.ts +1153 -0
- package/lib/DboBuilder/QueryBuilder/QueryBuilder.ts +288 -0
- package/lib/DboBuilder/QueryBuilder/getJoinQuery.ts +263 -0
- package/lib/DboBuilder/QueryBuilder/getNewQuery.ts +271 -0
- package/lib/DboBuilder/QueryBuilder/getSelectQuery.ts +136 -0
- package/lib/DboBuilder/QueryBuilder/prepareHaving.ts +22 -0
- package/lib/DboBuilder/QueryStreamer.ts +250 -0
- package/lib/DboBuilder/TableHandler/DataValidator.ts +428 -0
- package/lib/DboBuilder/TableHandler/TableHandler.ts +205 -0
- package/lib/DboBuilder/TableHandler/delete.ts +115 -0
- package/lib/DboBuilder/TableHandler/insert.ts +183 -0
- package/lib/DboBuilder/TableHandler/insertTest.ts +78 -0
- package/lib/DboBuilder/TableHandler/onDeleteFromFileTable.ts +62 -0
- package/lib/DboBuilder/TableHandler/runInsertUpdateQuery.ts +134 -0
- package/lib/DboBuilder/TableHandler/update.ts +126 -0
- package/lib/DboBuilder/TableHandler/updateBatch.ts +49 -0
- package/lib/DboBuilder/TableHandler/updateFile.ts +48 -0
- package/lib/DboBuilder/TableHandler/upsert.ts +34 -0
- package/lib/DboBuilder/ViewHandler/ViewHandler.ts +393 -0
- package/lib/DboBuilder/ViewHandler/count.ts +38 -0
- package/lib/DboBuilder/ViewHandler/find.ts +153 -0
- package/lib/DboBuilder/ViewHandler/getExistsCondition.ts +73 -0
- package/lib/DboBuilder/ViewHandler/getExistsFilters.ts +74 -0
- package/lib/DboBuilder/ViewHandler/getInfo.ts +32 -0
- package/lib/DboBuilder/ViewHandler/getTableJoinQuery.ts +84 -0
- package/lib/DboBuilder/ViewHandler/parseComplexFilter.ts +96 -0
- package/lib/DboBuilder/ViewHandler/parseFieldFilter.ts +105 -0
- package/lib/DboBuilder/ViewHandler/parseJoinPath.ts +208 -0
- package/lib/DboBuilder/ViewHandler/prepareSortItems.ts +163 -0
- package/lib/DboBuilder/ViewHandler/prepareWhere.ts +90 -0
- package/lib/DboBuilder/ViewHandler/size.ts +37 -0
- package/lib/DboBuilder/ViewHandler/subscribe.ts +118 -0
- package/lib/DboBuilder/ViewHandler/validateViewRules.ts +70 -0
- package/lib/DboBuilder/dboBuilderUtils.ts +222 -0
- package/lib/DboBuilder/getColumns.ts +114 -0
- package/lib/DboBuilder/getCondition.ts +201 -0
- package/lib/DboBuilder/getSubscribeRelatedTables.ts +190 -0
- package/lib/DboBuilder/getTablesForSchemaPostgresSQL.ts +426 -0
- package/lib/DboBuilder/insertNestedRecords.ts +355 -0
- package/lib/DboBuilder/parseUpdateRules.ts +187 -0
- package/lib/DboBuilder/prepareShortestJoinPaths.ts +186 -0
- package/lib/DboBuilder/runSQL.ts +182 -0
- package/lib/DboBuilder/runTransaction.ts +50 -0
- package/lib/DboBuilder/sqlErrCodeToMsg.ts +254 -0
- package/lib/DboBuilder/uploadFile.ts +69 -0
- package/lib/Event_Trigger_Tags.ts +118 -0
- package/lib/FileManager/FileManager.ts +358 -0
- package/lib/FileManager/getValidatedFileType.ts +69 -0
- package/lib/FileManager/initFileManager.ts +187 -0
- package/lib/FileManager/upload.ts +62 -0
- package/lib/FileManager/uploadStream.ts +79 -0
- package/lib/Filtering.ts +463 -0
- package/lib/JSONBValidation/validate_jsonb_schema_sql.ts +502 -0
- package/lib/JSONBValidation/validation.ts +143 -0
- package/lib/Logging.ts +127 -0
- package/lib/PostgresNotifListenManager.ts +143 -0
- package/lib/Prostgles.ts +485 -0
- package/lib/ProstglesTypes.ts +196 -0
- package/lib/PubSubManager/PubSubManager.ts +609 -0
- package/lib/PubSubManager/addSub.ts +138 -0
- package/lib/PubSubManager/addSync.ts +141 -0
- package/lib/PubSubManager/getCreatePubSubManagerError.ts +72 -0
- package/lib/PubSubManager/getPubSubManagerInitQuery.ts +662 -0
- package/lib/PubSubManager/initPubSubManager.ts +79 -0
- package/lib/PubSubManager/notifListener.ts +173 -0
- package/lib/PubSubManager/orphanTriggerCheck.ts +70 -0
- package/lib/PubSubManager/pushSubData.ts +55 -0
- package/lib/PublishParser/PublishParser.ts +162 -0
- package/lib/PublishParser/getFileTableRules.ts +124 -0
- package/lib/PublishParser/getSchemaFromPublish.ts +141 -0
- package/lib/PublishParser/getTableRulesWithoutFileTable.ts +177 -0
- package/lib/PublishParser/publishTypesAndUtils.ts +399 -0
- package/lib/RestApi.ts +127 -0
- package/lib/SchemaWatch/SchemaWatch.ts +90 -0
- package/lib/SchemaWatch/createSchemaWatchEventTrigger.ts +3 -0
- package/lib/SchemaWatch/getValidatedWatchSchemaType.ts +45 -0
- package/lib/SchemaWatch/getWatchSchemaTagList.ts +27 -0
- package/lib/SyncReplication.ts +557 -0
- package/lib/TableConfig/TableConfig.ts +468 -0
- package/lib/TableConfig/getColumnDefinitionQuery.ts +111 -0
- package/lib/TableConfig/getConstraintDefinitionQueries.ts +95 -0
- package/lib/TableConfig/getFutureTableSchema.ts +64 -0
- package/lib/TableConfig/getPGIndexes.ts +53 -0
- package/lib/TableConfig/getTableColumnQueries.ts +129 -0
- package/lib/TableConfig/initTableConfig.ts +326 -0
- package/lib/index.ts +13 -0
- package/lib/initProstgles.ts +319 -0
- package/lib/onSocketConnected.ts +102 -0
- package/lib/runClientRequest.ts +129 -0
- package/lib/shortestPath.ts +122 -0
- package/lib/typeTests/DBoGenerated.d.ts +320 -0
- package/lib/typeTests/dboTypeCheck.ts +81 -0
- package/lib/utils.ts +15 -0
- package/package.json +1 -1
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { AnyObject, UpdateParams } from "prostgles-types";
|
|
2
|
+
import { TableRule } from "../../PublishParser/PublishParser";
|
|
3
|
+
import { Filter, LocalParams, getErrorAsObject, getSerializedClientErrorFromPGError, withUserRLS } from "../DboBuilder";
|
|
4
|
+
import { getInsertTableRules, getReferenceColumnInserts } from "../insertNestedRecords";
|
|
5
|
+
import { prepareNewData } from "./DataValidator";
|
|
6
|
+
import { runInsertUpdateQuery } from "./runInsertUpdateQuery";
|
|
7
|
+
import { TableHandler } from "./TableHandler";
|
|
8
|
+
import { updateFile } from "./updateFile";
|
|
9
|
+
|
|
10
|
+
export async function update(this: TableHandler, filter: Filter, _newData: AnyObject, params?: UpdateParams, tableRules?: TableRule, localParams?: LocalParams): Promise<AnyObject | void> {
|
|
11
|
+
const ACTION = "update";
|
|
12
|
+
const start = Date.now();
|
|
13
|
+
try {
|
|
14
|
+
/** postValidate */
|
|
15
|
+
const finalDBtx = this.getFinalDBtx(localParams);
|
|
16
|
+
const wrapInTx = () => this.dboBuilder.getTX(_dbtx => _dbtx[this.name]?.[ACTION]?.(filter, _newData, params, tableRules, localParams))
|
|
17
|
+
const rule = tableRules?.[ACTION]
|
|
18
|
+
if(rule?.postValidate && !finalDBtx){
|
|
19
|
+
return wrapInTx();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let newData = _newData;
|
|
23
|
+
if(this.is_media){
|
|
24
|
+
({ newData } = await updateFile.bind(this)({ newData, filter, localParams, tableRules }));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const parsedRules = await this.parseUpdateRules(filter, params, tableRules, localParams)
|
|
28
|
+
if (localParams?.testRule) {
|
|
29
|
+
return parsedRules;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!newData || !Object.keys(newData).length) {
|
|
33
|
+
throw "no update data provided\nEXPECTING db.table.update(filter, updateData, options)";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const { fields, validateRow, forcedData, returningFields, forcedFilter, filterFields } = parsedRules;
|
|
37
|
+
const { onConflict, fixIssues = false } = params || {};
|
|
38
|
+
const { returnQuery = false } = localParams ?? {};
|
|
39
|
+
|
|
40
|
+
if (params) {
|
|
41
|
+
const good_paramsObj: Record<keyof UpdateParams, 1> = { returning: 1, returnType: 1, fixIssues: 1, onConflict: 1, multi: 1 };
|
|
42
|
+
const good_params = Object.keys(good_paramsObj);
|
|
43
|
+
const bad_params = Object.keys(params).filter(k => !good_params.includes(k));
|
|
44
|
+
if (bad_params && bad_params.length) throw "Invalid params: " + bad_params.join(", ") + " \n Expecting: " + good_params.join(", ");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const { data, allowedCols } = await prepareNewData({
|
|
48
|
+
row: newData,
|
|
49
|
+
forcedData,
|
|
50
|
+
allowedFields: fields,
|
|
51
|
+
tableRules,
|
|
52
|
+
fixIssues,
|
|
53
|
+
tableConfigurator: this.dboBuilder.prostgles.tableConfigurator,
|
|
54
|
+
tableHandler: this,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const updateFilter = await this.prepareWhere({
|
|
58
|
+
select: undefined,
|
|
59
|
+
filter,
|
|
60
|
+
forcedFilter,
|
|
61
|
+
filterFields,
|
|
62
|
+
localParams,
|
|
63
|
+
tableRule: tableRules
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Nested inserts
|
|
68
|
+
*/
|
|
69
|
+
const nData = { ...data };
|
|
70
|
+
const nestedInserts = getReferenceColumnInserts(this, nData, true);
|
|
71
|
+
const nestedInsertsResultsObj: Record<string, any> = {};
|
|
72
|
+
if(nestedInserts.length){
|
|
73
|
+
const updateCount = await this.count(updateFilter.filter);
|
|
74
|
+
if(+updateCount > 1){
|
|
75
|
+
throw "Cannot do a nestedInsert from an update that targets more than 1 row";
|
|
76
|
+
}
|
|
77
|
+
if(!finalDBtx){
|
|
78
|
+
return wrapInTx();
|
|
79
|
+
}
|
|
80
|
+
await Promise.all(nestedInserts.map(async nestedInsert => {
|
|
81
|
+
const nesedTableHandler = finalDBtx[nestedInsert.tableName] as TableHandler | undefined;
|
|
82
|
+
if(!nesedTableHandler) throw `nestedInsert Tablehandler not found for ${nestedInsert.tableName}`;
|
|
83
|
+
const refTableRules = !localParams? undefined : await getInsertTableRules(this, nestedInsert.tableName, localParams);
|
|
84
|
+
const nestedLocalParams: LocalParams = { ...localParams, nestedInsert: { depth: 1, previousData: nData, previousTable: this.name, referencingColumn: nestedInsert.col } }
|
|
85
|
+
const nestedInsertResult = await nesedTableHandler.insert(nestedInsert.data, { returning: "*" }, undefined, refTableRules, nestedLocalParams);
|
|
86
|
+
nestedInsertsResultsObj[nestedInsert.col] = nestedInsertResult;
|
|
87
|
+
|
|
88
|
+
nData[nestedInsert.col] = nestedInsertResult[nestedInsert.fcol];
|
|
89
|
+
return {
|
|
90
|
+
...nestedInsert,
|
|
91
|
+
result: nestedInsertResult,
|
|
92
|
+
}
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// let query = await this.colSet.getUpdateQuery(nData, allowedCols, this.getFinalDbo(localParams), validateRow, localParams)
|
|
97
|
+
let query = (await this.dataValidator.parse({ command: "update", rows: [nData], allowedCols, dbTx: this.getFinalDbo(localParams), validationOptions: { validate: validateRow, localParams }})).getQuery()
|
|
98
|
+
query += "\n" + updateFilter.where;
|
|
99
|
+
if (onConflict === "DoNothing") query += " ON CONFLICT DO NOTHING ";
|
|
100
|
+
if(onConflict === "DoUpdate"){
|
|
101
|
+
throw "onConflict 'DoUpdate' not possible for an update";
|
|
102
|
+
}
|
|
103
|
+
const queryWithoutUserRLS = query;
|
|
104
|
+
query = withUserRLS(localParams, query);
|
|
105
|
+
|
|
106
|
+
if (returnQuery) return query as unknown as void;
|
|
107
|
+
|
|
108
|
+
const result = await runInsertUpdateQuery({
|
|
109
|
+
tableHandler: this,
|
|
110
|
+
data: undefined,
|
|
111
|
+
fields,
|
|
112
|
+
localParams,
|
|
113
|
+
params,
|
|
114
|
+
queryWithoutUserRLS,
|
|
115
|
+
returningFields,
|
|
116
|
+
rule,
|
|
117
|
+
type: "update",
|
|
118
|
+
nestedInsertsResultsObj
|
|
119
|
+
});
|
|
120
|
+
await this._log({ command: "update", localParams, data: { filter, _newData, params }, duration: Date.now() - start });
|
|
121
|
+
return result;
|
|
122
|
+
} catch (e) {
|
|
123
|
+
await this._log({ command: "update", localParams, data: { filter, _newData, params }, duration: Date.now() - start, error: getErrorAsObject(e) });
|
|
124
|
+
throw getSerializedClientErrorFromPGError(e, { type: "tableMethod", localParams, view: this });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { AnyObject, UpdateParams } from "prostgles-types";
|
|
2
|
+
import { TableRule } from "../../PublishParser/PublishParser";
|
|
3
|
+
import { Filter, LocalParams, getClientErrorFromPGError, getErrorAsObject, getSerializedClientErrorFromPGError, withUserRLS } from "../DboBuilder";
|
|
4
|
+
import { TableHandler } from "./TableHandler";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export async function updateBatch(this: TableHandler, updates: [Filter, AnyObject][], params?: UpdateParams, tableRules?: TableRule, localParams?: LocalParams): Promise<any> {
|
|
8
|
+
const start = Date.now();
|
|
9
|
+
try {
|
|
10
|
+
const { checkFilter, postValidate } = tableRules?.update ?? {};
|
|
11
|
+
if(checkFilter || postValidate){
|
|
12
|
+
throw `updateBatch not allowed for tables with checkFilter or postValidate rules`
|
|
13
|
+
}
|
|
14
|
+
const updateQueries: string[] = await Promise.all(
|
|
15
|
+
updates.map(async ([filter, data]) => {
|
|
16
|
+
const query = (await this.update(
|
|
17
|
+
filter,
|
|
18
|
+
data,
|
|
19
|
+
{ ...(params ?? {}), returning: undefined },
|
|
20
|
+
tableRules,
|
|
21
|
+
{ ...(localParams ?? {}), returnQuery: "noRLS" }
|
|
22
|
+
)) as unknown as string;
|
|
23
|
+
|
|
24
|
+
return query;
|
|
25
|
+
})
|
|
26
|
+
);
|
|
27
|
+
const queries = [
|
|
28
|
+
withUserRLS(localParams, ""),
|
|
29
|
+
...updateQueries
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const t = localParams?.tx?.t ?? this.tx?.t;
|
|
33
|
+
if(t){
|
|
34
|
+
const result = await t.none(queries.join(";\n"));
|
|
35
|
+
await this._log({ command: "updateBatch", localParams, data: { data: updates, params }, duration: Date.now() - start });
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
const result = await this.db.tx(t => {
|
|
39
|
+
return t.none(queries.join(";\n"));
|
|
40
|
+
})
|
|
41
|
+
.catch(err => getClientErrorFromPGError(err, { type: "tableMethod", localParams, view: this, allowedKeys: []}));
|
|
42
|
+
|
|
43
|
+
await this._log({ command: "updateBatch", localParams, data: { data: updates, params }, duration: Date.now() - start });
|
|
44
|
+
return result;
|
|
45
|
+
} catch (e) {
|
|
46
|
+
await this._log({ command: "updateBatch", localParams, data: { data: updates, params }, duration: Date.now() - start, error: getErrorAsObject(e) });
|
|
47
|
+
throw getSerializedClientErrorFromPGError(e, { type: "tableMethod", localParams, view: this });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { AnyObject, getKeys, isObject } from "prostgles-types";
|
|
2
|
+
import { LocalParams, Media } from "../DboBuilder";
|
|
3
|
+
import { TableRule, ValidateRow, ValidateRowBasic } from "../../PublishParser/PublishParser";
|
|
4
|
+
import { omitKeys } from "../../PubSubManager/PubSubManager";
|
|
5
|
+
import { isFile, uploadFile } from "../uploadFile";
|
|
6
|
+
import { TableHandler } from "./TableHandler";
|
|
7
|
+
import { DBOFullyTyped } from "../../DBSchemaBuilder";
|
|
8
|
+
|
|
9
|
+
type Args = {
|
|
10
|
+
newData: AnyObject;
|
|
11
|
+
filter: AnyObject;
|
|
12
|
+
tableRules: TableRule | undefined;
|
|
13
|
+
localParams: LocalParams | undefined;
|
|
14
|
+
}
|
|
15
|
+
export const updateFile = async function(this: TableHandler, { filter, newData, tableRules, localParams }: Args): Promise<{ newData: AnyObject }> {
|
|
16
|
+
|
|
17
|
+
const rule = tableRules?.update;
|
|
18
|
+
|
|
19
|
+
if(tableRules && !tableRules.update){
|
|
20
|
+
throw "Not allowed"
|
|
21
|
+
}
|
|
22
|
+
if(localParams?.testRule){
|
|
23
|
+
return { newData: {} };
|
|
24
|
+
}
|
|
25
|
+
const existingMediaId: string = !(!filter || !isObject(filter) || getKeys(filter).join() !== "id" || typeof (filter as any).id !== "string")? (filter as any).id : undefined
|
|
26
|
+
if(!existingMediaId){
|
|
27
|
+
throw new Error(`Updating the file table with file data can only be done by providing a single id filter. E.g. { id: "9ea4e23c-2b1a-4e33-8ec0-c15919bb45ec" } `);
|
|
28
|
+
}
|
|
29
|
+
if(!isFile(newData)){
|
|
30
|
+
throw new Error("Expecting { data: Buffer, name: string } but received " + JSON.stringify(newData))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const fileManager = this.dboBuilder.prostgles.fileManager
|
|
34
|
+
if(!fileManager) throw new Error("fileManager missing");
|
|
35
|
+
if(rule?.validate && !localParams) throw new Error("localParams missing");
|
|
36
|
+
const validate: ValidateRowBasic | undefined = rule?.validate? async (row) => {
|
|
37
|
+
return rule.validate!({ update: row, filter, dbx: (this.tx?.dbTX || this.dboBuilder.dbo) as any, localParams: localParams! })
|
|
38
|
+
} : undefined;
|
|
39
|
+
|
|
40
|
+
const existingFile: Media | undefined = await (localParams?.tx?.dbTX?.[this.name] as TableHandler || this).findOne({ id: existingMediaId });
|
|
41
|
+
|
|
42
|
+
if(!existingFile?.name) throw new Error("Existing file record not found");
|
|
43
|
+
|
|
44
|
+
await fileManager.deleteFile(existingFile.name);
|
|
45
|
+
const newFile = await uploadFile.bind(this)({ row: newData, validate, localParams, mediaId: existingFile.id })
|
|
46
|
+
return { newData: omitKeys(newFile, ["id"]) };
|
|
47
|
+
|
|
48
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { AnyObject, UpdateParams } from "prostgles-types";
|
|
2
|
+
import { TableRule } from "../../PublishParser/publishTypesAndUtils";
|
|
3
|
+
import { Filter, LocalParams } from "../DboBuilderTypes";
|
|
4
|
+
import { getErrorAsObject, getSerializedClientErrorFromPGError } from "../dboBuilderUtils";
|
|
5
|
+
import { TableHandler } from "./TableHandler";
|
|
6
|
+
|
|
7
|
+
export const upsert = async function(this: TableHandler, filter: Filter, newData: AnyObject, params?: UpdateParams, table_rules?: TableRule, localParams?: LocalParams): Promise<any> {
|
|
8
|
+
const start = Date.now();
|
|
9
|
+
try {
|
|
10
|
+
const _upsert = async function (tblH: TableHandler) {
|
|
11
|
+
return tblH.find(filter, { select: "", limit: 1 }, undefined, table_rules, localParams)
|
|
12
|
+
.then(exists => {
|
|
13
|
+
if (exists && exists.length) {
|
|
14
|
+
return tblH.update(filter, newData, params, table_rules, localParams);
|
|
15
|
+
} else {
|
|
16
|
+
return tblH.insert({ ...newData, ...filter }, params, undefined, table_rules, localParams);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* Do it within a transaction to ensure consisency */
|
|
22
|
+
if (!this.tx) {
|
|
23
|
+
return this.dboBuilder.getTX(dbTX => _upsert(dbTX[this.name] as TableHandler))
|
|
24
|
+
} else {
|
|
25
|
+
const result = await _upsert(this);
|
|
26
|
+
await this._log({ command: "upsert", localParams, data: { filter, newData, params }, duration: Date.now() - start });
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
} catch (e) {
|
|
31
|
+
await this._log({ command: "upsert", localParams, data: { filter, newData, params }, duration: Date.now() - start, error: getErrorAsObject(e) });
|
|
32
|
+
throw getSerializedClientErrorFromPGError(e, { type: "tableMethod", localParams, view: this });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
import * as pgPromise from 'pg-promise';
|
|
2
|
+
import {
|
|
3
|
+
AnyObject,
|
|
4
|
+
ColumnInfo, FieldFilter, SelectParams,
|
|
5
|
+
SubscribeParams,
|
|
6
|
+
asName,
|
|
7
|
+
isEmpty,
|
|
8
|
+
isObject
|
|
9
|
+
} from "prostgles-types";
|
|
10
|
+
import { TableEvent } from "../../Logging";
|
|
11
|
+
import { DB } from "../../Prostgles";
|
|
12
|
+
import { Join } from "../../ProstglesTypes";
|
|
13
|
+
import { TableRule } from "../../PublishParser/PublishParser";
|
|
14
|
+
import { Graph } from "../../shortestPath";
|
|
15
|
+
import {
|
|
16
|
+
DboBuilder,
|
|
17
|
+
Filter,
|
|
18
|
+
LocalParams,
|
|
19
|
+
TableHandlers, ValidatedTableRules,
|
|
20
|
+
escapeTSNames,
|
|
21
|
+
getSerializedClientErrorFromPGError,
|
|
22
|
+
postgresToTsType
|
|
23
|
+
} from "../DboBuilder";
|
|
24
|
+
import { TableSchema } from '../DboBuilderTypes';
|
|
25
|
+
import { COMPUTED_FIELDS, FieldSpec } from "../QueryBuilder/Functions";
|
|
26
|
+
import { asNameAlias } from "../QueryBuilder/QueryBuilder";
|
|
27
|
+
import { getColumns } from "../getColumns";
|
|
28
|
+
import { count } from "./count";
|
|
29
|
+
import { find } from "./find";
|
|
30
|
+
import { getInfo } from "./getInfo";
|
|
31
|
+
import { parseFieldFilter } from "./parseFieldFilter";
|
|
32
|
+
import { prepareWhere } from "./prepareWhere";
|
|
33
|
+
import { size } from "./size";
|
|
34
|
+
import { LocalFuncs, subscribe } from "./subscribe";
|
|
35
|
+
import { validateViewRules } from "./validateViewRules";
|
|
36
|
+
|
|
37
|
+
export type JoinPaths = {
|
|
38
|
+
t1: string;
|
|
39
|
+
t2: string;
|
|
40
|
+
path: string[];
|
|
41
|
+
}[];
|
|
42
|
+
|
|
43
|
+
export class ViewHandler {
|
|
44
|
+
db: DB;
|
|
45
|
+
name: string;
|
|
46
|
+
escapedName: string;
|
|
47
|
+
columns: TableSchema["columns"];
|
|
48
|
+
columnsForTypes: ColumnInfo[];
|
|
49
|
+
column_names: string[];
|
|
50
|
+
tableOrViewInfo: TableSchema;
|
|
51
|
+
tsColumnDefs: string[] = [];
|
|
52
|
+
joins: Join[];
|
|
53
|
+
joinGraph?: Graph;
|
|
54
|
+
joinPaths?: JoinPaths;
|
|
55
|
+
dboBuilder: DboBuilder;
|
|
56
|
+
|
|
57
|
+
tx?: {
|
|
58
|
+
t: pgPromise.ITask<{}>;
|
|
59
|
+
dbTX: TableHandlers;
|
|
60
|
+
}
|
|
61
|
+
get dbHandler() {
|
|
62
|
+
return this.tx?.t ?? this.db;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
is_view = true;
|
|
66
|
+
filterDef = "";
|
|
67
|
+
is_media = false;
|
|
68
|
+
constructor(db: DB, tableOrViewInfo: TableSchema, dboBuilder: DboBuilder, tx?: { t: pgPromise.ITask<{}>, dbTX: TableHandlers }, joinPaths?: JoinPaths) {
|
|
69
|
+
if (!db || !tableOrViewInfo) throw "";
|
|
70
|
+
|
|
71
|
+
this.db = db;
|
|
72
|
+
this.tx = tx;
|
|
73
|
+
this.joinPaths = joinPaths;
|
|
74
|
+
this.tableOrViewInfo = tableOrViewInfo;
|
|
75
|
+
this.name = tableOrViewInfo.escaped_identifier;
|
|
76
|
+
this.escapedName = tableOrViewInfo.escaped_identifier;
|
|
77
|
+
this.columns = tableOrViewInfo.columns;
|
|
78
|
+
/* cols are sorted by name to reduce .d.ts schema rewrites */
|
|
79
|
+
this.columnsForTypes = tableOrViewInfo.columns.slice(0).sort((a, b) => a.name.localeCompare(b.name));
|
|
80
|
+
|
|
81
|
+
this.column_names = tableOrViewInfo.columns.map(c => c.name);
|
|
82
|
+
|
|
83
|
+
this.dboBuilder = dboBuilder;
|
|
84
|
+
this.joins = this.dboBuilder.joins ?? [];
|
|
85
|
+
this.columnsForTypes.map(({ name, udt_name, is_nullable }) => {
|
|
86
|
+
this.tsColumnDefs.push(`${escapeTSNames(name)}?: ${postgresToTsType(udt_name) as string} ${is_nullable ? " | null " : ""};`);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
_log = ({ command, data, localParams, duration, error }: Pick<TableEvent, "command" | "data" | "localParams"> & { duration: number; error?: any; }) => {
|
|
91
|
+
if(localParams?.noLog){
|
|
92
|
+
if(localParams?.socket || localParams.httpReq) {
|
|
93
|
+
throw new Error("noLog option is not allowed from a remote client");
|
|
94
|
+
}
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const sid = this.dboBuilder.prostgles.authHandler?.getSIDNoError(localParams);
|
|
98
|
+
return this.dboBuilder.prostgles.opts.onLog?.({
|
|
99
|
+
type: "table",
|
|
100
|
+
command,
|
|
101
|
+
duration,
|
|
102
|
+
error,
|
|
103
|
+
txInfo: this.tx?.t.ctx,
|
|
104
|
+
sid,
|
|
105
|
+
socketId: localParams?.socket?.id,
|
|
106
|
+
tableName: this.name,
|
|
107
|
+
data,
|
|
108
|
+
localParams,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
getRowHashSelect(allowedFields: FieldFilter, alias?: string, tableAlias?: string): string {
|
|
113
|
+
let allowed_cols = this.column_names;
|
|
114
|
+
if (allowedFields) allowed_cols = this.parseFieldFilter(allowedFields);
|
|
115
|
+
return "md5(" +
|
|
116
|
+
allowed_cols
|
|
117
|
+
/* CTID not available in AFTER trigger */
|
|
118
|
+
// .concat(this.is_view? [] : ["ctid"])
|
|
119
|
+
.sort()
|
|
120
|
+
.map(f => (tableAlias ? (asName(tableAlias) + ".") : "") + asName(f))
|
|
121
|
+
.map(f => `md5(coalesce(${f}::text, 'dd'))`)
|
|
122
|
+
.join(" || ") +
|
|
123
|
+
`)` + (alias ? ` as ${asName(alias)}` : "");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
validateViewRules = validateViewRules.bind(this);
|
|
127
|
+
|
|
128
|
+
getShortestJoin(table1: string, table2: string, startAlias: number, isInner = false): { query: string, toOne: boolean } {
|
|
129
|
+
const getJoinCondition = (on: Record<string, string>[], leftTable: string, rightTable: string) => {
|
|
130
|
+
return on.map(cond => Object.keys(cond).map(lKey => `${leftTable}.${lKey} = ${rightTable}.${cond[lKey]}`).join("\nAND ")).join(" OR ")
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// let toOne = true;
|
|
134
|
+
const query = this.joins.map(({ tables, on, type }, i) => {
|
|
135
|
+
if (type.split("-")[1] === "many") {
|
|
136
|
+
// toOne = false;
|
|
137
|
+
}
|
|
138
|
+
const tl = `tl${startAlias + i}`,
|
|
139
|
+
tr = `tr${startAlias + i}`;
|
|
140
|
+
return `FROM ${tables[0]} ${tl} ${isInner ? "INNER" : "LEFT"} JOIN ${tables[1]} ${tr} ON ${getJoinCondition(on, tl, tr)}`;
|
|
141
|
+
}).join("\n");
|
|
142
|
+
return { query, toOne: false }
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
checkFilter(filter: any) {
|
|
146
|
+
if (filter === null || filter && !isObject(filter)) throw `invalid filter -> ${JSON.stringify(filter)} \nExpecting: undefined | {} | { field_name: "value" } | { field: { $gt: 22 } } ... `;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
getInfo = getInfo.bind(this)
|
|
150
|
+
|
|
151
|
+
getColumns = getColumns.bind(this);
|
|
152
|
+
|
|
153
|
+
getValidatedRules(tableRules?: TableRule, localParams?: LocalParams): ValidatedTableRules {
|
|
154
|
+
|
|
155
|
+
if (localParams?.socket && !tableRules) {
|
|
156
|
+
throw "INTERNAL ERROR: Unexpected case -> localParams && !tableRules";
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/* Computed fields are allowed only if select is allowed */
|
|
160
|
+
const allColumns: FieldSpec[] = this.column_names.slice(0).map(fieldName => ({
|
|
161
|
+
type: "column",
|
|
162
|
+
name: fieldName,
|
|
163
|
+
getQuery: ({ tableAlias }) => asNameAlias(fieldName, tableAlias),
|
|
164
|
+
selected: false
|
|
165
|
+
} as FieldSpec)).concat(COMPUTED_FIELDS.map(c => ({
|
|
166
|
+
type: c.type,
|
|
167
|
+
name: c.name,
|
|
168
|
+
getQuery: ({ tableAlias, allowedFields }) => c.getQuery({
|
|
169
|
+
allowedFields,
|
|
170
|
+
ctidField: undefined,
|
|
171
|
+
allColumns: this.columns,
|
|
172
|
+
|
|
173
|
+
/* CTID not available in AFTER trigger */
|
|
174
|
+
// ctidField: this.is_view? undefined : "ctid",
|
|
175
|
+
tableAlias
|
|
176
|
+
}),
|
|
177
|
+
selected: false
|
|
178
|
+
})));
|
|
179
|
+
|
|
180
|
+
if (tableRules) {
|
|
181
|
+
if (isEmpty(tableRules)) throw "INTERNAL ERROR: Unexpected case -> Empty table rules for " + this.name;
|
|
182
|
+
const throwFieldsErr = (command: "select" | "update" | "delete" | "insert", fieldType = "fields") => {
|
|
183
|
+
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 }`;
|
|
184
|
+
},
|
|
185
|
+
getFirstSpecified = (...fieldParams: (FieldFilter | undefined)[]): string[] => {
|
|
186
|
+
const firstValid = fieldParams.find(fp => fp !== undefined);
|
|
187
|
+
return this.parseFieldFilter(firstValid)
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const res: ValidatedTableRules = {
|
|
191
|
+
allColumns,
|
|
192
|
+
getColumns: tableRules?.getColumns ?? true,
|
|
193
|
+
getInfo: tableRules?.getColumns ?? true,
|
|
194
|
+
} as ValidatedTableRules;
|
|
195
|
+
|
|
196
|
+
if (tableRules.select) {
|
|
197
|
+
if (!tableRules.select.fields) return throwFieldsErr("select");
|
|
198
|
+
|
|
199
|
+
let maxLimit: number | null = null;
|
|
200
|
+
if (!localParams?.bypassLimit && tableRules.select.maxLimit !== undefined && tableRules.select.maxLimit !== maxLimit) {
|
|
201
|
+
const ml = tableRules.select.maxLimit;
|
|
202
|
+
if (ml !== null && (!Number.isInteger(ml) || ml < 0)) throw ` Invalid publish.${this.name}.select.maxLimit -> expecting a positive integer OR null but got ` + ml;
|
|
203
|
+
maxLimit = ml;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const fields = this.parseFieldFilter(tableRules.select.fields)
|
|
207
|
+
res.select = {
|
|
208
|
+
fields,
|
|
209
|
+
orderByFields: tableRules.select.orderByFields ? this.parseFieldFilter(tableRules.select.orderByFields) : fields,
|
|
210
|
+
forcedFilter: { ...tableRules.select.forcedFilter },
|
|
211
|
+
filterFields: this.parseFieldFilter(tableRules.select.filterFields),
|
|
212
|
+
maxLimit
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (tableRules.update) {
|
|
217
|
+
if (!tableRules.update.fields) return throwFieldsErr("update");
|
|
218
|
+
|
|
219
|
+
res.update = {
|
|
220
|
+
fields: this.parseFieldFilter(tableRules.update.fields),
|
|
221
|
+
forcedData: { ...tableRules.update.forcedData },
|
|
222
|
+
forcedFilter: { ...tableRules.update.forcedFilter },
|
|
223
|
+
returningFields: getFirstSpecified(tableRules.update?.returningFields, tableRules?.select?.fields, tableRules.update.fields),
|
|
224
|
+
filterFields: this.parseFieldFilter(tableRules.update.filterFields)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (tableRules.insert) {
|
|
229
|
+
if (!tableRules.insert.fields) return throwFieldsErr("insert");
|
|
230
|
+
|
|
231
|
+
res.insert = {
|
|
232
|
+
fields: this.parseFieldFilter(tableRules.insert.fields),
|
|
233
|
+
forcedData: { ...tableRules.insert.forcedData },
|
|
234
|
+
returningFields: getFirstSpecified(tableRules.insert.returningFields, tableRules?.select?.fields, tableRules.insert.fields)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (tableRules.delete) {
|
|
239
|
+
if (!tableRules.delete.filterFields) return throwFieldsErr("delete", "filterFields");
|
|
240
|
+
|
|
241
|
+
res.delete = {
|
|
242
|
+
forcedFilter: { ...tableRules.delete.forcedFilter },
|
|
243
|
+
filterFields: this.parseFieldFilter(tableRules.delete.filterFields),
|
|
244
|
+
returningFields: getFirstSpecified(tableRules.delete.returningFields, tableRules?.select?.fields, tableRules.delete.filterFields)
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (!tableRules.select && !tableRules.update && !tableRules.delete && !tableRules.insert) {
|
|
249
|
+
if ([null, false].includes(tableRules.getInfo as any)) res.getInfo = false;
|
|
250
|
+
if ([null, false].includes(tableRules.getColumns as any)) res.getColumns = false;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return res;
|
|
254
|
+
} else {
|
|
255
|
+
const allCols = this.column_names.slice(0);
|
|
256
|
+
return {
|
|
257
|
+
allColumns,
|
|
258
|
+
getColumns: true,
|
|
259
|
+
getInfo: true,
|
|
260
|
+
select: {
|
|
261
|
+
fields: allCols,
|
|
262
|
+
filterFields: allCols,
|
|
263
|
+
orderByFields: allCols,
|
|
264
|
+
forcedFilter: {},
|
|
265
|
+
maxLimit: null,
|
|
266
|
+
},
|
|
267
|
+
update: {
|
|
268
|
+
fields: allCols,
|
|
269
|
+
filterFields: allCols,
|
|
270
|
+
forcedFilter: {},
|
|
271
|
+
forcedData: {},
|
|
272
|
+
returningFields: allCols
|
|
273
|
+
},
|
|
274
|
+
insert: {
|
|
275
|
+
fields: allCols,
|
|
276
|
+
forcedData: {},
|
|
277
|
+
returningFields: allCols
|
|
278
|
+
},
|
|
279
|
+
delete: {
|
|
280
|
+
filterFields: allCols,
|
|
281
|
+
forcedFilter: {},
|
|
282
|
+
returningFields: allCols
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
}
|
|
289
|
+
find = find.bind(this);
|
|
290
|
+
|
|
291
|
+
async findOne(filter?: Filter, selectParams?: SelectParams, _param3_unused?: undefined, table_rules?: TableRule, localParams?: LocalParams): Promise<any> {
|
|
292
|
+
|
|
293
|
+
try {
|
|
294
|
+
const { limit, ...params } = selectParams ?? {};
|
|
295
|
+
if (limit) {
|
|
296
|
+
throw "limit not allowed in findOne()";
|
|
297
|
+
}
|
|
298
|
+
const start = Date.now();
|
|
299
|
+
const result = await this.find(filter, { ...params, limit: 1, returnType: "row" }, undefined, table_rules, localParams);
|
|
300
|
+
await this._log({ command: "find", localParams, data: { filter, selectParams }, duration: Date.now() - start });
|
|
301
|
+
return result;
|
|
302
|
+
} catch (e) {
|
|
303
|
+
throw getSerializedClientErrorFromPGError(e, { type: "tableMethod", localParams, view: this });
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async subscribe(filter: Filter, params: SubscribeParams, localFuncs: LocalFuncs): Promise<{ unsubscribe: () => any }>
|
|
308
|
+
async subscribe(filter: Filter, params: SubscribeParams, localFuncs: undefined, table_rules: TableRule | undefined, localParams: LocalParams): Promise<string>
|
|
309
|
+
async subscribe(filter: Filter, params: SubscribeParams, localFuncs?: LocalFuncs, table_rules?: TableRule, localParams?: LocalParams):
|
|
310
|
+
Promise<{ unsubscribe: () => any } | string> {
|
|
311
|
+
//@ts-ignore
|
|
312
|
+
return subscribe.bind(this)(filter, params, localFuncs, table_rules, localParams);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/* This should only be called from server */
|
|
316
|
+
subscribeOne(filter: Filter, params: SubscribeParams, localFunc: (item: AnyObject) => any): Promise<{ unsubscribe: () => any }>
|
|
317
|
+
subscribeOne(filter: Filter, params: SubscribeParams, localFunc: undefined, table_rules: TableRule, localParams: LocalParams): Promise<string>
|
|
318
|
+
subscribeOne(filter: Filter, params: SubscribeParams = {}, localFunc?: (item: AnyObject) => any, table_rules?: TableRule, localParams?: LocalParams):
|
|
319
|
+
Promise<string | { unsubscribe: () => any }> {
|
|
320
|
+
|
|
321
|
+
//@ts-ignore
|
|
322
|
+
const func = localParams? undefined : (rows: AnyObject[]) => localFunc(rows[0]);
|
|
323
|
+
//@ts-ignore
|
|
324
|
+
return this.subscribe(filter, { ...params, limit: 2 }, func, table_rules, localParams);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
count = count.bind(this);
|
|
328
|
+
size = size.bind(this);
|
|
329
|
+
|
|
330
|
+
getAllowedSelectFields(selectParams: FieldFilter = "*", allowed_cols: FieldFilter, allow_empty = true): string[] {
|
|
331
|
+
const all_columns = this.column_names.slice(0);
|
|
332
|
+
let allowedFields = all_columns.slice(0),
|
|
333
|
+
resultFields: string[] = [];
|
|
334
|
+
|
|
335
|
+
if (selectParams) {
|
|
336
|
+
resultFields = this.parseFieldFilter(selectParams, allow_empty);
|
|
337
|
+
}
|
|
338
|
+
if (allowed_cols) {
|
|
339
|
+
allowedFields = this.parseFieldFilter(allowed_cols, allow_empty);
|
|
340
|
+
}
|
|
341
|
+
let col_names = (resultFields || []).filter(f => !allowedFields || allowedFields.includes(f));
|
|
342
|
+
|
|
343
|
+
/* Maintain allowed cols order */
|
|
344
|
+
if (selectParams === "*" && allowedFields && allowedFields.length){
|
|
345
|
+
col_names = allowedFields;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return col_names;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Parses group or simple filter
|
|
353
|
+
*/
|
|
354
|
+
prepareWhere = prepareWhere.bind(this);
|
|
355
|
+
|
|
356
|
+
intersectColumns(allowedFields: FieldFilter, dissallowedFields: FieldFilter, fixIssues = false): string[] {
|
|
357
|
+
let result: string[] = [];
|
|
358
|
+
if (allowedFields) {
|
|
359
|
+
result = this.parseFieldFilter(allowedFields);
|
|
360
|
+
}
|
|
361
|
+
if (dissallowedFields) {
|
|
362
|
+
const _dissalowed = this.parseFieldFilter(dissallowedFields);
|
|
363
|
+
|
|
364
|
+
if (!fixIssues) {
|
|
365
|
+
|
|
366
|
+
throw `dissallowed/invalid field found for ${this.name}: `
|
|
367
|
+
}
|
|
368
|
+
result = result.filter(key => !_dissalowed.includes(key));
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return result;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
parseFieldFilter(fieldParams: FieldFilter = "*", allow_empty = true, allowed_cols?: string[]): string[] {
|
|
375
|
+
return parseFieldFilter(fieldParams, allow_empty, allowed_cols ?? this.column_names.slice(0))
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Throw error if illegal keys found in object
|
|
383
|
+
*/
|
|
384
|
+
export const validateObj = <T extends Record<string, any>>(obj: T, allowedKeys: string[]): T => {
|
|
385
|
+
if (obj && Object.keys(obj).length) {
|
|
386
|
+
const invalid_keys = Object.keys(obj).filter(k => !allowedKeys.includes(k));
|
|
387
|
+
if (invalid_keys.length) {
|
|
388
|
+
throw "Invalid/Illegal fields found: " + invalid_keys.join(", ");
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return obj;
|
|
393
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { SelectParams } from "prostgles-types";
|
|
2
|
+
import { TableRule } from "../../PublishParser/publishTypesAndUtils";
|
|
3
|
+
import { Filter, LocalParams } from "../DboBuilder";
|
|
4
|
+
import { getErrorAsObject, getSerializedClientErrorFromPGError, withUserRLS } from "../dboBuilderUtils";
|
|
5
|
+
import { ViewHandler } from "./ViewHandler";
|
|
6
|
+
|
|
7
|
+
export async function count(this: ViewHandler, _filter?: Filter, selectParams?: SelectParams, _param3_unused?: undefined, table_rules?: TableRule, localParams?: LocalParams): Promise<number> {
|
|
8
|
+
const filter = _filter || {};
|
|
9
|
+
const { limit: _limit, ...selectParamsWithoutLimit } = selectParams ?? {};
|
|
10
|
+
const start = Date.now();
|
|
11
|
+
try {
|
|
12
|
+
const result = await this.find(filter, { select: selectParamsWithoutLimit?.select ?? "", limit: 0 }, undefined, table_rules, localParams)
|
|
13
|
+
.then(async _allowed => {
|
|
14
|
+
const findQuery = await this.find(
|
|
15
|
+
filter,
|
|
16
|
+
selectParamsWithoutLimit,
|
|
17
|
+
undefined,
|
|
18
|
+
table_rules,
|
|
19
|
+
{ ...localParams, returnQuery: "noRLS", bypassLimit: true }
|
|
20
|
+
) as unknown as string;
|
|
21
|
+
const query = [
|
|
22
|
+
withUserRLS(localParams, ""),
|
|
23
|
+
"SELECT COUNT(*)",
|
|
24
|
+
"FROM (",
|
|
25
|
+
findQuery,
|
|
26
|
+
") t"
|
|
27
|
+
].join("\n");
|
|
28
|
+
const handler = this.tx?.t ?? this.db;
|
|
29
|
+
return handler.one(query).then(({ count }) => +count);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
await this._log({ command: "count", localParams, data: { filter }, duration: Date.now() - start });
|
|
33
|
+
return result;
|
|
34
|
+
} catch (e) {
|
|
35
|
+
await this._log({ command: "count", localParams, data: { filter }, duration: Date.now() - start, error: getErrorAsObject(e) });
|
|
36
|
+
throw getSerializedClientErrorFromPGError(e, { type: "tableMethod", localParams, view: this });
|
|
37
|
+
}
|
|
38
|
+
}
|