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,153 @@
|
|
|
1
|
+
|
|
2
|
+
import { SelectParams, isObject } from "prostgles-types";
|
|
3
|
+
import { TableRule } from "../../PublishParser/PublishParser";
|
|
4
|
+
import { Filter, LocalParams, getClientErrorFromPGError, getErrorAsObject, getSerializedClientErrorFromPGError, withUserRLS } from "../DboBuilder";
|
|
5
|
+
import { getNewQuery } from "../QueryBuilder/getNewQuery";
|
|
6
|
+
import { getSelectQuery } from "../QueryBuilder/getSelectQuery";
|
|
7
|
+
import { NewQuery } from "../QueryBuilder/QueryBuilder";
|
|
8
|
+
import { canRunSQL } from "../runSQL";
|
|
9
|
+
import { TableHandler } from "../TableHandler/TableHandler";
|
|
10
|
+
import { ViewHandler } from "./ViewHandler";
|
|
11
|
+
|
|
12
|
+
export const find = async function(this: ViewHandler, filter?: Filter, selectParams?: SelectParams, _?: undefined, tableRules?: TableRule, localParams?: LocalParams): Promise<any[]> {
|
|
13
|
+
const start = Date.now();
|
|
14
|
+
const command = selectParams?.limit === 1 && selectParams?.returnType === "row"? "findOne" : "find";
|
|
15
|
+
try {
|
|
16
|
+
filter = filter || {};
|
|
17
|
+
const allowedReturnTypes = Object.keys({
|
|
18
|
+
row: 1, statement: 1, value: 1, values: 1,
|
|
19
|
+
"statement-no-rls": 1, "statement-where": 1,
|
|
20
|
+
} satisfies Record<Required<SelectParams>["returnType"], 1>);
|
|
21
|
+
|
|
22
|
+
const { returnType } = selectParams || {};
|
|
23
|
+
if (returnType && !allowedReturnTypes.includes(returnType)) {
|
|
24
|
+
throw `returnType (${returnType}) can only be ${allowedReturnTypes.join(" OR ")}`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const { testRule = false } = localParams || {};
|
|
28
|
+
|
|
29
|
+
if (testRule) return [];
|
|
30
|
+
if (selectParams) {
|
|
31
|
+
const validParamNames = Object.keys({
|
|
32
|
+
"select": 1, "orderBy": 1, "offset": 1, "limit": 1,
|
|
33
|
+
"returnType": 1, "groupBy": 1, "having": 1
|
|
34
|
+
} satisfies Record<keyof SelectParams, 1>);
|
|
35
|
+
|
|
36
|
+
const invalidParams = Object.keys(selectParams).filter(k => !validParamNames.includes(k as any));
|
|
37
|
+
if (invalidParams && invalidParams.length) throw "Invalid params: " + invalidParams.join(", ") + " \n Expecting: " + validParamNames.join(", ");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* Validate publish */
|
|
41
|
+
if (tableRules) {
|
|
42
|
+
|
|
43
|
+
if (!tableRules.select) throw "select rules missing for " + this.name;
|
|
44
|
+
const fields = tableRules.select.fields;
|
|
45
|
+
const maxLimit = tableRules.select.maxLimit;
|
|
46
|
+
|
|
47
|
+
if (<any>tableRules.select !== "*" && typeof tableRules.select !== "boolean" && !isObject(tableRules.select)) {
|
|
48
|
+
throw `\nInvalid publish.${this.name}.select\nExpecting any of: "*" | { fields: "*" } | true | false`;
|
|
49
|
+
}
|
|
50
|
+
if (!fields) {
|
|
51
|
+
throw ` invalid ${this.name}.select rule -> fields (required) setting missing.\nExpecting any of: "*" | { col_name: false } | { col1: true, col2: true }`;
|
|
52
|
+
}
|
|
53
|
+
if (maxLimit && !Number.isInteger(maxLimit)) {
|
|
54
|
+
throw ` invalid publish.${this.name}.select.maxLimit -> expecting integer but got ` + maxLimit;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const _selectParams = selectParams ?? {}
|
|
59
|
+
const selectParamsLimitCheck = localParams?.bypassLimit && !Number.isFinite(_selectParams.limit)? { ..._selectParams, limit: null } : { limit: 1000, ..._selectParams }
|
|
60
|
+
const newQuery = await getNewQuery(
|
|
61
|
+
this,
|
|
62
|
+
filter,
|
|
63
|
+
selectParamsLimitCheck,
|
|
64
|
+
_,
|
|
65
|
+
tableRules,
|
|
66
|
+
localParams,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const queryWithoutRLS = getSelectQuery(
|
|
70
|
+
this,
|
|
71
|
+
newQuery,
|
|
72
|
+
undefined,
|
|
73
|
+
!!selectParamsLimitCheck?.groupBy
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const queryWithRLS = withUserRLS(localParams, queryWithoutRLS);
|
|
77
|
+
if (testRule) {
|
|
78
|
+
try {
|
|
79
|
+
await this.db.any(withUserRLS(localParams, "EXPLAIN " + queryWithRLS));
|
|
80
|
+
return [];
|
|
81
|
+
} catch (e) {
|
|
82
|
+
console.error(e);
|
|
83
|
+
throw `Internal error: publish config is not valid for publish.${this.name}.select `
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Used for subscribe */
|
|
88
|
+
if(localParams?.returnNewQuery) return (newQuery as unknown as any);
|
|
89
|
+
if (localParams?.returnQuery) {
|
|
90
|
+
if(localParams?.returnQuery === "where-condition"){
|
|
91
|
+
return newQuery.whereOpts.condition as any;
|
|
92
|
+
}
|
|
93
|
+
return ((localParams?.returnQuery === "noRLS"? queryWithoutRLS : queryWithRLS) as unknown as any[]);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const result = await runQueryReturnType({
|
|
97
|
+
queryWithoutRLS,
|
|
98
|
+
queryWithRLS,
|
|
99
|
+
returnType,
|
|
100
|
+
handler: this,
|
|
101
|
+
localParams,
|
|
102
|
+
newQuery,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
await this._log({ command, localParams, data: { filter, selectParams }, duration: Date.now() - start });
|
|
106
|
+
return result;
|
|
107
|
+
} catch (e) {
|
|
108
|
+
this._log({ command, localParams, data: { filter, selectParams }, duration: Date.now() - start, error: getErrorAsObject(e) });
|
|
109
|
+
throw getSerializedClientErrorFromPGError(e, { type: "tableMethod", localParams, view: this });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
type RunQueryReturnTypeArgs = {
|
|
114
|
+
queryWithRLS: string;
|
|
115
|
+
queryWithoutRLS: string;
|
|
116
|
+
returnType: SelectParams["returnType"];
|
|
117
|
+
handler: ViewHandler | TableHandler;
|
|
118
|
+
localParams: LocalParams | undefined;
|
|
119
|
+
newQuery: NewQuery | undefined;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export const runQueryReturnType = async ({ newQuery, handler, localParams, queryWithRLS, queryWithoutRLS, returnType,}: RunQueryReturnTypeArgs) => {
|
|
123
|
+
|
|
124
|
+
const query = queryWithRLS;
|
|
125
|
+
const sqlTypes = ["statement", "statement-no-rls", "statement-where"];
|
|
126
|
+
if(!returnType || returnType === "values"){
|
|
127
|
+
|
|
128
|
+
return handler.dbHandler.any(query).then(data => {
|
|
129
|
+
if (returnType === "values") {
|
|
130
|
+
return data.map(d => Object.values(d)[0]);
|
|
131
|
+
}
|
|
132
|
+
return data;
|
|
133
|
+
}).catch(err => getClientErrorFromPGError(err, { type: "tableMethod", localParams, view: handler, }));
|
|
134
|
+
|
|
135
|
+
} else if (sqlTypes.some(v => v === returnType)) {
|
|
136
|
+
if (!(await canRunSQL(handler.dboBuilder.prostgles, localParams))) {
|
|
137
|
+
throw `Not allowed: {returnType: ${JSON.stringify(returnType)}} requires execute sql privileges `
|
|
138
|
+
}
|
|
139
|
+
if(returnType === "statement-no-rls"){
|
|
140
|
+
return queryWithoutRLS as any;
|
|
141
|
+
}
|
|
142
|
+
if(returnType === "statement-where"){
|
|
143
|
+
if(!newQuery) throw `returnType ${returnType} not possible for this command type`;
|
|
144
|
+
return newQuery.whereOpts.condition as any;
|
|
145
|
+
}
|
|
146
|
+
return query as unknown as any[];
|
|
147
|
+
|
|
148
|
+
} else if (["row", "value"].includes(returnType)) {
|
|
149
|
+
return handler.dbHandler.oneOrNone(query).then(data => {
|
|
150
|
+
return (data && returnType === "value") ? Object.values(data)[0] : data;
|
|
151
|
+
}).catch(err => getClientErrorFromPGError(err, { type: "tableMethod", localParams, view: handler, }));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { AnyObject, EXISTS_KEY, EXISTS_KEYS, FieldFilter, asName } from "prostgles-types";
|
|
2
|
+
import { LocalParams, ExistsFilterConfig } from "../DboBuilder";
|
|
3
|
+
import { ViewHandler } from "./ViewHandler";
|
|
4
|
+
import { TableRule } from "../../PublishParser/PublishParser";
|
|
5
|
+
import { TableHandler } from "../TableHandler/TableHandler";
|
|
6
|
+
import { getTableJoinQuery } from "./getTableJoinQuery";
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
export async function getExistsCondition(this: ViewHandler, eConfig: ExistsFilterConfig, localParams: LocalParams | undefined): Promise<string> {
|
|
10
|
+
|
|
11
|
+
const thisTable = this.name;
|
|
12
|
+
const isNotExists = ["$notExists", "$notExistsJoined"].includes(eConfig.existType);
|
|
13
|
+
|
|
14
|
+
const { targetTableFilter } = eConfig;
|
|
15
|
+
|
|
16
|
+
/* Nested $exists is not allowed */
|
|
17
|
+
if (targetTableFilter && Object.keys(targetTableFilter).find(fk => EXISTS_KEYS.includes(fk as EXISTS_KEY))) {
|
|
18
|
+
throw { stack: ["prepareExistCondition()"], message: "Nested exists dissallowed" };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let t2Rules: TableRule | undefined = undefined,
|
|
22
|
+
forcedFilter: AnyObject | undefined,
|
|
23
|
+
filterFields: FieldFilter | undefined,
|
|
24
|
+
tableAlias;
|
|
25
|
+
|
|
26
|
+
/* Check if allowed to view data - forcedFilters will bypass this check through isForcedFilterBypass */
|
|
27
|
+
if (localParams?.isRemoteRequest && !localParams?.socket && !localParams?.httpReq) {
|
|
28
|
+
throw "Unexpected: localParams isRemoteRequest and missing socket/httpReq: ";
|
|
29
|
+
}
|
|
30
|
+
const targetTable = eConfig.isJoined? eConfig.parsedPath.at(-1)!.table : eConfig.targetTable;
|
|
31
|
+
if ((localParams?.socket || localParams?.httpReq) && this.dboBuilder.publishParser) {
|
|
32
|
+
|
|
33
|
+
t2Rules = await this.dboBuilder.publishParser.getValidatedRequestRuleWusr({
|
|
34
|
+
tableName: targetTable,
|
|
35
|
+
command: "find",
|
|
36
|
+
localParams
|
|
37
|
+
}) as TableRule;
|
|
38
|
+
|
|
39
|
+
if (!t2Rules || !t2Rules.select) throw "Dissallowed";
|
|
40
|
+
({ forcedFilter, filterFields } = t2Rules.select);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const tableHandler = this.dboBuilder.dbo[targetTable] as TableHandler
|
|
44
|
+
const finalWhere = (await tableHandler.prepareWhere({
|
|
45
|
+
select: undefined,
|
|
46
|
+
filter: targetTableFilter,
|
|
47
|
+
forcedFilter,
|
|
48
|
+
filterFields,
|
|
49
|
+
addWhere: false,
|
|
50
|
+
tableAlias,
|
|
51
|
+
localParams,
|
|
52
|
+
tableRule: t2Rules
|
|
53
|
+
})).where
|
|
54
|
+
|
|
55
|
+
let innerQuery = [
|
|
56
|
+
`SELECT 1`,
|
|
57
|
+
`FROM ${asName(targetTable)}`,
|
|
58
|
+
`${finalWhere ? `WHERE ${finalWhere}` : ""}`
|
|
59
|
+
].join("\n");
|
|
60
|
+
|
|
61
|
+
if(eConfig.isJoined){
|
|
62
|
+
const { query } = getTableJoinQuery({
|
|
63
|
+
path: eConfig.parsedPath,
|
|
64
|
+
rootTableAlias: thisTable,
|
|
65
|
+
type: "EXISTS",
|
|
66
|
+
finalWhere,
|
|
67
|
+
});
|
|
68
|
+
innerQuery = query;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return `${isNotExists ? " NOT " : " "} EXISTS ( \n${innerQuery} \n) `;
|
|
72
|
+
|
|
73
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { EXISTS_KEY, EXISTS_KEYS, getKeys } from "prostgles-types";
|
|
2
|
+
import { ExistsFilterConfig } from "../DboBuilder";
|
|
3
|
+
import { ViewHandler } from "./ViewHandler";
|
|
4
|
+
import { parseJoinPath } from "./parseJoinPath";
|
|
5
|
+
|
|
6
|
+
export const getExistsFilters = (filter: any, viewHandler: ViewHandler): ExistsFilterConfig[] => {
|
|
7
|
+
|
|
8
|
+
/* Exists join filter */
|
|
9
|
+
const ERR = "Invalid exists filter. \nExpecting something like: \n | { $exists: { tableName.tableName2: Filter } } \n | { $exists: { \"**.tableName3\": Filter } }\n | { path: string[]; filter: AnyObject }"
|
|
10
|
+
const existsConfigs: ExistsFilterConfig[] = getKeys(filter)
|
|
11
|
+
.filter((k ): k is typeof EXISTS_KEYS[number] => EXISTS_KEYS.includes(k as EXISTS_KEY) && !!Object.keys(filter[k] ?? {}).length)
|
|
12
|
+
.map(key => {
|
|
13
|
+
|
|
14
|
+
const isJoined = key.toLowerCase().includes("join");
|
|
15
|
+
const filterValue = filter[key];
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* type ExistsJoined =
|
|
20
|
+
* | { "table1.table2": { column: filterValue } }
|
|
21
|
+
* | { path: string[]; filter: AnyObject }
|
|
22
|
+
*/
|
|
23
|
+
const dataKeys = Object.keys(filterValue);
|
|
24
|
+
const isDetailed = dataKeys.length === 2 && dataKeys.every(key => ["path", "filter"].includes(key));
|
|
25
|
+
|
|
26
|
+
const firstKey = dataKeys[0]!;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Non joined exists are never detailed
|
|
30
|
+
*/
|
|
31
|
+
if(!isJoined){
|
|
32
|
+
const format = `Expecting single table in exists filter. Example: { $exists: { tableName: Filter } }`
|
|
33
|
+
if(isDetailed){
|
|
34
|
+
throw `Exists filters cannot be detailed. ${format}`
|
|
35
|
+
}
|
|
36
|
+
const targetTable = firstKey;
|
|
37
|
+
if (!viewHandler.dboBuilder.dbo[targetTable]) {
|
|
38
|
+
throw `Table ${JSON.stringify(targetTable)} not found. ${format}`
|
|
39
|
+
}
|
|
40
|
+
const res: ExistsFilterConfig = {
|
|
41
|
+
isJoined: false,
|
|
42
|
+
existType: key as EXISTS_KEY,
|
|
43
|
+
targetTableFilter: filterValue[firstKey],
|
|
44
|
+
targetTable: firstKey,
|
|
45
|
+
}
|
|
46
|
+
return res;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Prevent some errors with table names that contain "."
|
|
51
|
+
*/
|
|
52
|
+
const firstKeyIsATable = !!viewHandler.dboBuilder.dbo[firstKey];
|
|
53
|
+
const [path, targetTableFilter] = isDetailed? [filterValue.path, filterValue.filter] : [(firstKeyIsATable? [firstKey] : firstKey.split(".")), filterValue[firstKey]];
|
|
54
|
+
|
|
55
|
+
if (!path.length) {
|
|
56
|
+
throw ERR + "\nBut got: " + JSON.stringify(filterValue);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
isJoined: true,
|
|
61
|
+
existType: key as EXISTS_KEY,
|
|
62
|
+
path,
|
|
63
|
+
parsedPath: parseJoinPath({
|
|
64
|
+
rawPath: path,
|
|
65
|
+
rootTable: viewHandler.name,
|
|
66
|
+
viewHandler,
|
|
67
|
+
allowMultiOrJoin: true,
|
|
68
|
+
}),
|
|
69
|
+
targetTableFilter,
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return existsConfigs;
|
|
74
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TableInfo as TInfo
|
|
3
|
+
} from "prostgles-types/dist";
|
|
4
|
+
import { TableRule } from "../../PublishParser/PublishParser";
|
|
5
|
+
import { LocalParams } from "../DboBuilder";
|
|
6
|
+
import { ViewHandler } from "./ViewHandler";
|
|
7
|
+
|
|
8
|
+
export async function getInfo(this: ViewHandler, lang?: string, param2?: any, param3?: any, tableRules?: TableRule, localParams?: LocalParams): Promise<TInfo> {
|
|
9
|
+
const p = this.getValidatedRules(tableRules, localParams);
|
|
10
|
+
if (!p.getInfo) {
|
|
11
|
+
await this._log({ command: "getInfo", localParams, data: { lang }, duration: 0, error: "Not allowed" });
|
|
12
|
+
throw "Not allowed";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const fileTableName = this.dboBuilder.prostgles?.opts?.fileTable?.tableName;
|
|
16
|
+
|
|
17
|
+
await this._log({ command: "getInfo", localParams, data: { lang }, duration: 0 });
|
|
18
|
+
return {
|
|
19
|
+
oid: this.tableOrViewInfo.oid,
|
|
20
|
+
comment: this.tableOrViewInfo.comment,
|
|
21
|
+
info: this.dboBuilder.prostgles?.tableConfigurator?.getTableInfo({ tableName: this.name, lang }),
|
|
22
|
+
isFileTable: !this.is_media? undefined : {
|
|
23
|
+
allowedNestedInserts: tableRules?.insert?.allowedNestedInserts
|
|
24
|
+
},
|
|
25
|
+
isView: this.is_view,
|
|
26
|
+
hasFiles: Boolean(!this.is_media && fileTableName && this.columns.some(c => c.references?.some(r => r.ftable === fileTableName))),
|
|
27
|
+
fileTableName,
|
|
28
|
+
dynamicRules: {
|
|
29
|
+
update: Boolean(tableRules?.update?.dynamicFields?.length)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { asName } from "prostgles-types";
|
|
2
|
+
import { ParsedJoinPath } from "./parseJoinPath";
|
|
3
|
+
|
|
4
|
+
type getTableJoinsArgs = {
|
|
5
|
+
rootTableAlias: string;
|
|
6
|
+
type: "INNER" | "LEFT" | "EXISTS";
|
|
7
|
+
finalWhere?: string;
|
|
8
|
+
path: ParsedJoinPath[];
|
|
9
|
+
}
|
|
10
|
+
export const getTableJoinQuery = ({ path, type, rootTableAlias, finalWhere }: getTableJoinsArgs): { targetAlias: string; query: string } => {
|
|
11
|
+
|
|
12
|
+
const [firstPath] = path;
|
|
13
|
+
if(!firstPath){
|
|
14
|
+
throw `Cannot create join query for empty path`;
|
|
15
|
+
}
|
|
16
|
+
const aliasSufix = "jd";
|
|
17
|
+
const getTableAlias = (table: string, pathIndex: number) => asName(`${aliasSufix}_${pathIndex}_${table}`);
|
|
18
|
+
|
|
19
|
+
const query = path.map(({ table, on }, i) => {
|
|
20
|
+
if(!on) throw "on missing";
|
|
21
|
+
const tableName = table;
|
|
22
|
+
const tableAlias = getTableAlias(table, i);
|
|
23
|
+
const prevTableAlias = i === 0? rootTableAlias : getTableAlias(path[i-1]!.table, i-1);
|
|
24
|
+
|
|
25
|
+
const onCondition = getJoinOnCondition({ on, leftAlias: prevTableAlias, rightAlias: tableAlias });
|
|
26
|
+
|
|
27
|
+
const isExists = type === "EXISTS"
|
|
28
|
+
const joinType = isExists? "INNER" : type;
|
|
29
|
+
const keyword = `${joinType} JOIN`;
|
|
30
|
+
const isLast = i === path.length - 1;
|
|
31
|
+
const isFirst = !i;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* rootTable joins to first path
|
|
35
|
+
* first path joins to target table through inner joins
|
|
36
|
+
*/
|
|
37
|
+
const whereJoinCondition = (isLast && isExists) ?
|
|
38
|
+
`WHERE (${getJoinOnCondition({
|
|
39
|
+
on: firstPath.on,
|
|
40
|
+
leftAlias: rootTableAlias,
|
|
41
|
+
rightAlias: getTableAlias(firstPath.table, 0)
|
|
42
|
+
})})` : "";
|
|
43
|
+
|
|
44
|
+
const tableSelect = (isExists && isLast)? [
|
|
45
|
+
`(`,
|
|
46
|
+
` SELECT *`,
|
|
47
|
+
` FROM ${tableName}`,
|
|
48
|
+
(finalWhere? ` WHERE ${finalWhere}` : ""),
|
|
49
|
+
`)`
|
|
50
|
+
].filter(v=>v).join("\n") : tableName;
|
|
51
|
+
if(isExists && isFirst){
|
|
52
|
+
return [
|
|
53
|
+
`SELECT 1`,
|
|
54
|
+
`FROM ${tableSelect} ${tableAlias}`,
|
|
55
|
+
whereJoinCondition
|
|
56
|
+
].filter(v=>v).join("\n");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return [
|
|
60
|
+
`${keyword} ${tableSelect} ${tableAlias}`,
|
|
61
|
+
` ON ${onCondition}`,
|
|
62
|
+
whereJoinCondition
|
|
63
|
+
].filter(v=>v).join("\n");
|
|
64
|
+
|
|
65
|
+
}).join("\n");
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
query,
|
|
69
|
+
targetAlias: getTableAlias(path.at(-1)!.table, path.length - 1)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
type GetJoinOnConditionArgs = {
|
|
74
|
+
on: Record<string, string>[];
|
|
75
|
+
leftAlias: string;
|
|
76
|
+
rightAlias: string;
|
|
77
|
+
getLeftColName?: (col: string) => string;
|
|
78
|
+
getRightColName?: (col: string) => string;
|
|
79
|
+
}
|
|
80
|
+
export const getJoinOnCondition = ({ on, leftAlias, rightAlias, getLeftColName = asName, getRightColName = asName }: GetJoinOnConditionArgs ) => {
|
|
81
|
+
return on.map(constraint => Object.entries(constraint).map(([leftCol, rightCol]) => {
|
|
82
|
+
return `${leftAlias}.${getLeftColName(leftCol)} = ${rightAlias}.${getRightColName(rightCol)}`;
|
|
83
|
+
}).join(" AND ")).join(" OR ")
|
|
84
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { AnyObject, isObject } from "prostgles-types";
|
|
2
|
+
import { FILTER_OPERANDS, FILTER_OPERAND_TO_SQL_OPERAND, parseFilterRightValue } from "../../Filtering";
|
|
3
|
+
import { FUNCTIONS, parseFunction } from "../QueryBuilder/Functions";
|
|
4
|
+
import { asNameAlias, parseFunctionObject } from "../QueryBuilder/QueryBuilder";
|
|
5
|
+
import { TableSchemaColumn } from "../DboBuilderTypes";
|
|
6
|
+
import { asValue } from "../../PubSubManager/PubSubManager";
|
|
7
|
+
|
|
8
|
+
const allowedComparators = FILTER_OPERANDS; //[">", "<", "=", "<=", ">=", "<>", "!="]
|
|
9
|
+
type Args = {
|
|
10
|
+
filter: AnyObject;
|
|
11
|
+
complexFilterKey: string;
|
|
12
|
+
tableAlias: string | undefined;
|
|
13
|
+
allowed_colnames: string[];
|
|
14
|
+
columns: TableSchemaColumn[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* Parse complex filters
|
|
18
|
+
{
|
|
19
|
+
$filter: [
|
|
20
|
+
{ $func: [...] },
|
|
21
|
+
"=",
|
|
22
|
+
value | { $func: [..] }
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
*/
|
|
26
|
+
export const parseComplexFilter = ({
|
|
27
|
+
filter,
|
|
28
|
+
complexFilterKey,
|
|
29
|
+
tableAlias,
|
|
30
|
+
allowed_colnames,
|
|
31
|
+
columns,
|
|
32
|
+
}: Args) => {
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* { $funcName: [arg1, arg2] }
|
|
36
|
+
* { $column: "column_name" }
|
|
37
|
+
*/
|
|
38
|
+
const getFuncQuery = (funcData: AnyObject): string => {
|
|
39
|
+
if(isObject(funcData) && "$column" in funcData){
|
|
40
|
+
const column = funcData["$column"]
|
|
41
|
+
if(typeof column !== "string"){
|
|
42
|
+
throw `expecting: \n { $column: "column_name" } received:\n ${JSON.stringify(funcData)}`;
|
|
43
|
+
}
|
|
44
|
+
if(!allowed_colnames.includes(column)){
|
|
45
|
+
throw `Dissallowed or Invalid column ${column}. Allowed columns: ${allowed_colnames}`;
|
|
46
|
+
}
|
|
47
|
+
return asNameAlias(column, tableAlias)
|
|
48
|
+
}
|
|
49
|
+
const { funcName, args } = parseFunctionObject(funcData);
|
|
50
|
+
const funcDef = parseFunction({ func: funcName, args, functions: FUNCTIONS, allowedFields: allowed_colnames });
|
|
51
|
+
return funcDef.getQuery({ args, tableAlias, allColumns: columns, allowedFields: allowed_colnames });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const complexFilter = filter[complexFilterKey];
|
|
55
|
+
if (!Array.isArray(complexFilter)) {
|
|
56
|
+
throw `Invalid $filter. Must contain an array of at least element but got: ${JSON.stringify(complexFilter)} `
|
|
57
|
+
}
|
|
58
|
+
const [leftFilter, comparator, rightFilterOrValue] = complexFilter;
|
|
59
|
+
|
|
60
|
+
const leftVal = getFuncQuery(leftFilter);
|
|
61
|
+
let result = leftVal;
|
|
62
|
+
if (comparator) {
|
|
63
|
+
if (typeof comparator !== "string" || !allowedComparators.includes(comparator as any)) {
|
|
64
|
+
throw `Invalid $filter. comparator ${JSON.stringify(comparator)} is not valid. Expecting one of: ${allowedComparators}`;
|
|
65
|
+
}
|
|
66
|
+
if (!rightFilterOrValue) {
|
|
67
|
+
throw "Invalid $filter. Expecting a value or function after the comparator";
|
|
68
|
+
}
|
|
69
|
+
const maybeValidComparator = comparator as keyof typeof FILTER_OPERAND_TO_SQL_OPERAND;
|
|
70
|
+
const sqlOperand = FILTER_OPERAND_TO_SQL_OPERAND[maybeValidComparator];
|
|
71
|
+
if(!sqlOperand){
|
|
72
|
+
throw `Invalid $filter. comparator ${comparator} is not valid. Expecting one of: ${allowedComparators}`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let rightVal = isObject(rightFilterOrValue) ?
|
|
76
|
+
getFuncQuery(rightFilterOrValue) :
|
|
77
|
+
parseFilterRightValue(rightFilterOrValue, {
|
|
78
|
+
selectItem: undefined,
|
|
79
|
+
expect: ["$in", "$nin"].includes(comparator)? "csv" : undefined
|
|
80
|
+
});
|
|
81
|
+
if(maybeValidComparator === "$between" || maybeValidComparator === "$notBetween"){
|
|
82
|
+
|
|
83
|
+
if(!Array.isArray(rightVal) || rightVal.length !== 2){
|
|
84
|
+
throw "Between filter expects an array of two values";
|
|
85
|
+
}
|
|
86
|
+
rightVal = asValue(rightVal[0]) + " AND " + asValue(rightVal[1]);
|
|
87
|
+
}
|
|
88
|
+
if (leftVal === rightVal){
|
|
89
|
+
throw "Invalid $filter. Cannot compare two identical function signatures: " + JSON.stringify(leftFilter);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
result += ` ${sqlOperand} ${rightVal}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { FieldFilter, getKeys } from "prostgles-types";
|
|
2
|
+
import { isPlainObject } from "../DboBuilder";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Filter string array
|
|
6
|
+
* @param {FieldFilter} fieldParams - { col1: 0, col2: 0 } | { col1: true, col2: true } | "*" | ["key1", "key2"] | []
|
|
7
|
+
* @param {boolean} allow_empty - allow empty select. defaults to true
|
|
8
|
+
*/
|
|
9
|
+
export const parseFieldFilter = <AllowedKeys extends string[]>(
|
|
10
|
+
fieldParams: FieldFilter<Record<AllowedKeys[number], any>> = "*",
|
|
11
|
+
allow_empty = true,
|
|
12
|
+
all_cols: AllowedKeys
|
|
13
|
+
): AllowedKeys | [""] => {
|
|
14
|
+
|
|
15
|
+
if (!all_cols) throw "all_cols missing"
|
|
16
|
+
const all_fields = all_cols;// || this.column_names.slice(0);
|
|
17
|
+
let colNames: AllowedKeys = [] as any;
|
|
18
|
+
const initialParams = JSON.stringify(fieldParams);
|
|
19
|
+
|
|
20
|
+
if (fieldParams) {
|
|
21
|
+
|
|
22
|
+
/*
|
|
23
|
+
"field1, field2, field4" | "*"
|
|
24
|
+
*/
|
|
25
|
+
if (typeof fieldParams === "string") {
|
|
26
|
+
fieldParams = fieldParams.split(",").map(k => k.trim());
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* string[] */
|
|
30
|
+
if (Array.isArray(fieldParams) && !fieldParams.find(f => typeof f !== "string")) {
|
|
31
|
+
/*
|
|
32
|
+
["*"]
|
|
33
|
+
*/
|
|
34
|
+
if (fieldParams[0] === "*") {
|
|
35
|
+
return all_fields.slice(0) as typeof all_fields;
|
|
36
|
+
|
|
37
|
+
/*
|
|
38
|
+
[""]
|
|
39
|
+
*/
|
|
40
|
+
} else if (fieldParams[0] === "") {
|
|
41
|
+
if (allow_empty) {
|
|
42
|
+
return [""];
|
|
43
|
+
} else {
|
|
44
|
+
throw "Empty value not allowed";
|
|
45
|
+
}
|
|
46
|
+
/*
|
|
47
|
+
["field1", "field2", "field3"]
|
|
48
|
+
*/
|
|
49
|
+
} else {
|
|
50
|
+
colNames = fieldParams.slice(0) as AllowedKeys;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/*
|
|
54
|
+
{ field1: true, field2: true } = only field1 and field2
|
|
55
|
+
{ field1: false, field2: false } = all fields except field1 and field2
|
|
56
|
+
*/
|
|
57
|
+
} else if (isPlainObject(fieldParams)) {
|
|
58
|
+
|
|
59
|
+
if (!getKeys(fieldParams).length) {
|
|
60
|
+
return [] as unknown as typeof all_fields; //all_fields.slice(0) as typeof all_fields;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const keys = getKeys(fieldParams as {
|
|
64
|
+
[key: string]: boolean | 0 | 1;
|
|
65
|
+
}) as AllowedKeys;
|
|
66
|
+
if (keys[0] === "") {
|
|
67
|
+
if (allow_empty) {
|
|
68
|
+
return [""];
|
|
69
|
+
} else {
|
|
70
|
+
throw "Empty value not allowed";
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
validate(keys);
|
|
75
|
+
|
|
76
|
+
keys.forEach(key => {
|
|
77
|
+
const allowedVals = [true, false, 0, 1];
|
|
78
|
+
if (!allowedVals.includes((fieldParams as any)[key])) throw `Invalid field selection value for: { ${key}: ${(fieldParams as any)[key]} }. \n Allowed values: ${allowedVals.join(" OR ")}`
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
const allowed = keys.filter(key => (fieldParams as any)[key]),
|
|
82
|
+
disallowed = keys.filter(key => !(fieldParams as any)[key]);
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
if (disallowed && disallowed.length) {
|
|
86
|
+
return all_fields.filter(col => !disallowed.includes(col)) as typeof all_fields;
|
|
87
|
+
} else {
|
|
88
|
+
return [...allowed] as any;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
} else {
|
|
92
|
+
throw " Unrecognised field filter.\nExpecting any of: string | string[] | { [field]: boolean } \n Received -> " + initialParams;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
validate(colNames);
|
|
96
|
+
}
|
|
97
|
+
return colNames as any;
|
|
98
|
+
|
|
99
|
+
function validate(cols: AllowedKeys) {
|
|
100
|
+
const bad_keys = cols.filter(col => !all_fields.includes(col));
|
|
101
|
+
if (bad_keys && bad_keys.length) {
|
|
102
|
+
throw "\nUnrecognised or illegal fields: " + bad_keys.join(", ");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|