prostgles-server 4.2.157 → 4.2.159
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/AuthHandler.js +2 -2
- package/dist/Auth/AuthHandler.js.map +1 -1
- package/dist/Auth/AuthTypes.d.ts +4 -8
- package/dist/Auth/AuthTypes.d.ts.map +1 -1
- package/dist/Auth/setAuthProviders.d.ts +1 -1
- package/dist/Auth/setAuthProviders.d.ts.map +1 -1
- package/dist/Auth/setAuthProviders.js +6 -7
- package/dist/Auth/setAuthProviders.js.map +1 -1
- package/dist/Auth/setEmailProvider.d.ts +1 -1
- package/dist/Auth/setEmailProvider.d.ts.map +1 -1
- package/dist/Auth/setEmailProvider.js +22 -2
- package/dist/Auth/setEmailProvider.js.map +1 -1
- package/dist/Auth/setupAuthRoutes.js +1 -1
- package/dist/Auth/setupAuthRoutes.js.map +1 -1
- package/dist/Prostgles.d.ts +1 -0
- package/dist/Prostgles.d.ts.map +1 -1
- package/dist/Prostgles.js +6 -0
- package/dist/Prostgles.js.map +1 -1
- package/dist/initProstgles.d.ts.map +1 -1
- package/dist/initProstgles.js +2 -6
- package/dist/initProstgles.js.map +1 -1
- package/package.json +1 -1
- package/lib/Auth/AuthHandler.ts +0 -436
- package/lib/Auth/AuthTypes.ts +0 -285
- package/lib/Auth/getSafeReturnURL.ts +0 -35
- package/lib/Auth/sendEmail.ts +0 -83
- package/lib/Auth/setAuthProviders.ts +0 -129
- package/lib/Auth/setEmailProvider.ts +0 -63
- package/lib/Auth/setupAuthRoutes.ts +0 -161
- package/lib/DBEventsManager.ts +0 -178
- package/lib/DBSchemaBuilder.ts +0 -225
- package/lib/DboBuilder/DboBuilder.ts +0 -319
- package/lib/DboBuilder/DboBuilderTypes.ts +0 -361
- package/lib/DboBuilder/QueryBuilder/Functions.ts +0 -1153
- package/lib/DboBuilder/QueryBuilder/QueryBuilder.ts +0 -288
- package/lib/DboBuilder/QueryBuilder/getJoinQuery.ts +0 -263
- package/lib/DboBuilder/QueryBuilder/getNewQuery.ts +0 -271
- package/lib/DboBuilder/QueryBuilder/getSelectQuery.ts +0 -136
- package/lib/DboBuilder/QueryBuilder/prepareHaving.ts +0 -22
- package/lib/DboBuilder/QueryStreamer.ts +0 -250
- package/lib/DboBuilder/TableHandler/DataValidator.ts +0 -428
- package/lib/DboBuilder/TableHandler/TableHandler.ts +0 -205
- package/lib/DboBuilder/TableHandler/delete.ts +0 -115
- package/lib/DboBuilder/TableHandler/insert.ts +0 -183
- package/lib/DboBuilder/TableHandler/insertTest.ts +0 -78
- package/lib/DboBuilder/TableHandler/onDeleteFromFileTable.ts +0 -62
- package/lib/DboBuilder/TableHandler/runInsertUpdateQuery.ts +0 -134
- package/lib/DboBuilder/TableHandler/update.ts +0 -126
- package/lib/DboBuilder/TableHandler/updateBatch.ts +0 -49
- package/lib/DboBuilder/TableHandler/updateFile.ts +0 -48
- package/lib/DboBuilder/TableHandler/upsert.ts +0 -34
- package/lib/DboBuilder/ViewHandler/ViewHandler.ts +0 -393
- package/lib/DboBuilder/ViewHandler/count.ts +0 -38
- package/lib/DboBuilder/ViewHandler/find.ts +0 -153
- package/lib/DboBuilder/ViewHandler/getExistsCondition.ts +0 -73
- package/lib/DboBuilder/ViewHandler/getExistsFilters.ts +0 -74
- package/lib/DboBuilder/ViewHandler/getInfo.ts +0 -32
- package/lib/DboBuilder/ViewHandler/getTableJoinQuery.ts +0 -84
- package/lib/DboBuilder/ViewHandler/parseComplexFilter.ts +0 -96
- package/lib/DboBuilder/ViewHandler/parseFieldFilter.ts +0 -105
- package/lib/DboBuilder/ViewHandler/parseJoinPath.ts +0 -208
- package/lib/DboBuilder/ViewHandler/prepareSortItems.ts +0 -163
- package/lib/DboBuilder/ViewHandler/prepareWhere.ts +0 -90
- package/lib/DboBuilder/ViewHandler/size.ts +0 -37
- package/lib/DboBuilder/ViewHandler/subscribe.ts +0 -118
- package/lib/DboBuilder/ViewHandler/validateViewRules.ts +0 -70
- package/lib/DboBuilder/dboBuilderUtils.ts +0 -222
- package/lib/DboBuilder/getColumns.ts +0 -114
- package/lib/DboBuilder/getCondition.ts +0 -201
- package/lib/DboBuilder/getSubscribeRelatedTables.ts +0 -190
- package/lib/DboBuilder/getTablesForSchemaPostgresSQL.ts +0 -426
- package/lib/DboBuilder/insertNestedRecords.ts +0 -355
- package/lib/DboBuilder/parseUpdateRules.ts +0 -187
- package/lib/DboBuilder/prepareShortestJoinPaths.ts +0 -186
- package/lib/DboBuilder/runSQL.ts +0 -182
- package/lib/DboBuilder/runTransaction.ts +0 -50
- package/lib/DboBuilder/sqlErrCodeToMsg.ts +0 -254
- package/lib/DboBuilder/uploadFile.ts +0 -69
- package/lib/Event_Trigger_Tags.ts +0 -118
- package/lib/FileManager/FileManager.ts +0 -358
- package/lib/FileManager/getValidatedFileType.ts +0 -69
- package/lib/FileManager/initFileManager.ts +0 -187
- package/lib/FileManager/upload.ts +0 -62
- package/lib/FileManager/uploadStream.ts +0 -79
- package/lib/Filtering.ts +0 -463
- package/lib/JSONBValidation/validate_jsonb_schema_sql.ts +0 -502
- package/lib/JSONBValidation/validation.ts +0 -143
- package/lib/Logging.ts +0 -127
- package/lib/PostgresNotifListenManager.ts +0 -143
- package/lib/Prostgles.ts +0 -479
- package/lib/ProstglesTypes.ts +0 -196
- package/lib/PubSubManager/PubSubManager.ts +0 -609
- package/lib/PubSubManager/addSub.ts +0 -138
- package/lib/PubSubManager/addSync.ts +0 -141
- package/lib/PubSubManager/getCreatePubSubManagerError.ts +0 -72
- package/lib/PubSubManager/getPubSubManagerInitQuery.ts +0 -662
- package/lib/PubSubManager/initPubSubManager.ts +0 -79
- package/lib/PubSubManager/notifListener.ts +0 -173
- package/lib/PubSubManager/orphanTriggerCheck.ts +0 -70
- package/lib/PubSubManager/pushSubData.ts +0 -55
- package/lib/PublishParser/PublishParser.ts +0 -162
- package/lib/PublishParser/getFileTableRules.ts +0 -124
- package/lib/PublishParser/getSchemaFromPublish.ts +0 -141
- package/lib/PublishParser/getTableRulesWithoutFileTable.ts +0 -177
- package/lib/PublishParser/publishTypesAndUtils.ts +0 -399
- package/lib/RestApi.ts +0 -127
- package/lib/SchemaWatch/SchemaWatch.ts +0 -90
- package/lib/SchemaWatch/createSchemaWatchEventTrigger.ts +0 -3
- package/lib/SchemaWatch/getValidatedWatchSchemaType.ts +0 -45
- package/lib/SchemaWatch/getWatchSchemaTagList.ts +0 -27
- package/lib/SyncReplication.ts +0 -557
- package/lib/TableConfig/TableConfig.ts +0 -468
- package/lib/TableConfig/getColumnDefinitionQuery.ts +0 -111
- package/lib/TableConfig/getConstraintDefinitionQueries.ts +0 -95
- package/lib/TableConfig/getFutureTableSchema.ts +0 -64
- package/lib/TableConfig/getPGIndexes.ts +0 -53
- package/lib/TableConfig/getTableColumnQueries.ts +0 -129
- package/lib/TableConfig/initTableConfig.ts +0 -326
- package/lib/index.ts +0 -13
- package/lib/initProstgles.ts +0 -322
- package/lib/onSocketConnected.ts +0 -102
- package/lib/runClientRequest.ts +0 -129
- package/lib/shortestPath.ts +0 -122
- package/lib/typeTests/DBoGenerated.d.ts +0 -320
- package/lib/typeTests/dboTypeCheck.ts +0 -81
- package/lib/utils.ts +0 -15
- package/tests/client/hooks.spec.ts +0 -205
- package/tests/client/index.ts +0 -139
- package/tests/client/package-lock.json +0 -637
- package/tests/client/package.json +0 -26
- package/tests/client/renderReactHook.ts +0 -177
- package/tests/client/tsconfig.json +0 -15
- package/tests/client/useProstgles.spec.ts +0 -120
- package/tests/clientFileTests.spec.ts +0 -102
- package/tests/clientOnlyQueries.spec.ts +0 -667
- package/tests/clientRestApi.spec.ts +0 -82
- package/tests/config_test/DBoGenerated.d.ts +0 -407
- package/tests/config_test/index.html +0 -109
- package/tests/config_test/index.js +0 -86
- package/tests/config_test/index.js.map +0 -1
- package/tests/config_test/index.ts +0 -91
- package/tests/config_test/init.sql +0 -48
- package/tests/config_test/package.json +0 -29
- package/tests/config_test/tsconfig.json +0 -23
- package/tests/config_testDBoGenerated.d.ts +0 -407
- package/tests/isomorphicQueries.spec.ts +0 -1493
- package/tests/server/DBoGenerated.d.ts +0 -537
- package/tests/server/index.html +0 -73
- package/tests/server/index.ts +0 -289
- package/tests/server/init.sql +0 -224
- package/tests/server/package-lock.json +0 -2164
- package/tests/server/package.json +0 -25
- package/tests/server/publishTypeCheck.ts +0 -136
- package/tests/server/server.ts +0 -35
- package/tests/server/testPublish.ts +0 -147
- package/tests/server/testTableConfig.ts +0 -156
- package/tests/server/tsconfig.json +0 -22
- package/tests/serverOnlyQueries.spec.ts +0 -32
- package/tests/test.sh +0 -20
|
@@ -1,428 +0,0 @@
|
|
|
1
|
-
import { AnyObject, ColumnInfo, FieldFilter, ValidatedColumnInfo, asName, getKeys, isEmpty, isObject, pickKeys, unpatchText } from "prostgles-types/dist";
|
|
2
|
-
import { ValidateRowBasic } from "../../PublishParser/PublishParser";
|
|
3
|
-
import { DBHandlerServer } from "../../Prostgles";
|
|
4
|
-
import { asValue } from "../../PubSubManager/PubSubManager";
|
|
5
|
-
import { LocalParams, TableSchemaColumn, pgp } from "../DboBuilder";
|
|
6
|
-
import { TableHandler, ValidatedParams } from "./TableHandler";
|
|
7
|
-
import { parseFunctionObject } from "../QueryBuilder/QueryBuilder";
|
|
8
|
-
import { validateObj } from "../ViewHandler/ViewHandler";
|
|
9
|
-
|
|
10
|
-
type RowFieldDataPlain = {
|
|
11
|
-
type: "plain";
|
|
12
|
-
column: TableSchemaColumn;
|
|
13
|
-
fieldValue: any;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
type RowFieldDataFunction = {
|
|
17
|
-
type: "function";
|
|
18
|
-
column: TableSchemaColumn;
|
|
19
|
-
funcName: string;
|
|
20
|
-
args: any[];
|
|
21
|
-
}
|
|
22
|
-
type RowFieldData = RowFieldDataPlain | RowFieldDataFunction;
|
|
23
|
-
|
|
24
|
-
type ParsedRowFieldData = {
|
|
25
|
-
escapedCol: string;
|
|
26
|
-
escapedVal: string;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
type ParseDataArgs = {
|
|
30
|
-
rows: AnyObject[];
|
|
31
|
-
allowedCols: string[];
|
|
32
|
-
dbTx: DBHandlerServer;
|
|
33
|
-
command: "update" | "insert";
|
|
34
|
-
validationOptions: {
|
|
35
|
-
localParams: undefined | LocalParams;
|
|
36
|
-
validate: undefined | ValidateRowBasic;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export class DataValidator {
|
|
41
|
-
rowFieldData?: RowFieldData[][];
|
|
42
|
-
parsedRowFieldData?: ParsedRowFieldData[][];
|
|
43
|
-
tableHandler: TableHandler;
|
|
44
|
-
constructor(tableHandler: TableHandler) {
|
|
45
|
-
this.tableHandler = tableHandler;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
parse = async (args: ParseDataArgs) => {
|
|
49
|
-
const { command } = args;
|
|
50
|
-
const rowFieldData = await getValidatedRowFieldData(args, this.tableHandler);
|
|
51
|
-
const parsedRowFieldData = await getParsedRowFieldData(rowFieldData, args);
|
|
52
|
-
if (command === "update") {
|
|
53
|
-
if (rowFieldData.some(rowParts => rowParts.length === 0)) {
|
|
54
|
-
throw "Empty row. No data provided for update";
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return {
|
|
59
|
-
parsedRowFieldData,
|
|
60
|
-
getQuery: () => getQuery(command, parsedRowFieldData, this.tableHandler.escapedName),
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const getQuery = (type: "insert" | "update", parsedRowFieldData: ParsedRowFieldData[][], escapedTableName: string): string => {
|
|
66
|
-
if (type === "insert") {
|
|
67
|
-
|
|
68
|
-
const uniqueColumns = Array.from(new Set(parsedRowFieldData.flatMap(row => row.map(r => r.escapedCol))))
|
|
69
|
-
const values = parsedRowFieldData.map(row => `(${uniqueColumns.map(colName => row.find(r => r.escapedCol === colName)?.escapedVal ?? 'DEFAULT')})`).join(",\n");
|
|
70
|
-
const whatToInsert = !uniqueColumns.length ? "DEFAULT VALUES" : `(${uniqueColumns}) VALUES ${values}`
|
|
71
|
-
return `INSERT INTO ${escapedTableName} ${whatToInsert} `;
|
|
72
|
-
} else {
|
|
73
|
-
const query = parsedRowFieldData.map(rowParts => {
|
|
74
|
-
return `UPDATE ${escapedTableName} SET ` + rowParts.map(r => `${r.escapedCol} = ${r.escapedVal} `).join(",\n")
|
|
75
|
-
}).join(";\n") + " ";
|
|
76
|
-
|
|
77
|
-
return query;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
type PrepareFieldValuesArgs = {
|
|
82
|
-
row: AnyObject | undefined;
|
|
83
|
-
forcedData: AnyObject | undefined;
|
|
84
|
-
allowedCols: FieldFilter | undefined;
|
|
85
|
-
removeDisallowedColumns?: boolean;
|
|
86
|
-
tableHandler: TableHandler;
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Apply forcedData, remove disallowed columns, validate against allowed columns:
|
|
90
|
-
* @example ({ item_id: 1 }, { user_id: 32 }) => { item_id: 1, user_id: 32 }
|
|
91
|
-
* OR
|
|
92
|
-
* ({ a: 1 }, { b: 32 }, ["c", "d"]) => throw "a field is not allowed"
|
|
93
|
-
* @param {Object} obj - initial data
|
|
94
|
-
* @param {Object} forcedData - set/override property
|
|
95
|
-
* @param {string[]} allowed_cols - allowed columns (excluding forcedData) from table rules
|
|
96
|
-
*/
|
|
97
|
-
const getValidatedRow = ({ row = {}, forcedData = {}, allowedCols, removeDisallowedColumns = false, tableHandler }: PrepareFieldValuesArgs): AnyObject => {
|
|
98
|
-
const column_names = tableHandler.column_names.slice(0);
|
|
99
|
-
if (!column_names.length) {
|
|
100
|
-
throw "table column_names mising";
|
|
101
|
-
}
|
|
102
|
-
const validatedAllowedColumns = tableHandler.parseFieldFilter(allowedCols, false);
|
|
103
|
-
|
|
104
|
-
let finalRow = { ...row };
|
|
105
|
-
if (removeDisallowedColumns && !isEmpty(finalRow)) {
|
|
106
|
-
finalRow = pickKeys(finalRow, validatedAllowedColumns);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/* If has keys check against allowed_cols */
|
|
110
|
-
validateObj(finalRow, validatedAllowedColumns)
|
|
111
|
-
|
|
112
|
-
/** Apply forcedData */
|
|
113
|
-
if (!isEmpty(forcedData)) {
|
|
114
|
-
finalRow = { ...finalRow, ...forcedData };
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/** Validate forcedData */
|
|
118
|
-
validateObj(finalRow, column_names.slice(0));
|
|
119
|
-
return finalRow;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Add synced_field value if missing
|
|
124
|
-
* prepareFieldValues(): Apply forcedData, remove disallowed columns, validate against allowed columns
|
|
125
|
-
* tableConfigurator?.checkColVal(): Validate column min/max/isText/lowerCased/trimmed values
|
|
126
|
-
*/
|
|
127
|
-
export const prepareNewData = async ({ row, forcedData, allowedFields, tableRules, fixIssues = false, tableConfigurator, tableHandler }: ValidatedParams) => {
|
|
128
|
-
const synced_field = (tableRules ?? {})?.sync?.synced_field;
|
|
129
|
-
|
|
130
|
-
/* Update synced_field if sync is on and missing */
|
|
131
|
-
if (synced_field && !row[synced_field]) {
|
|
132
|
-
row[synced_field] = Date.now();
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const data = getValidatedRow({ tableHandler, row, forcedData, allowedCols: allowedFields , removeDisallowedColumns: fixIssues });
|
|
136
|
-
const dataKeys = getKeys(data);
|
|
137
|
-
|
|
138
|
-
dataKeys.forEach(col => {
|
|
139
|
-
tableConfigurator?.checkColVal({ table: tableHandler.name, col, value: data[col] });
|
|
140
|
-
const colConfig = tableConfigurator?.getColumnConfig(tableHandler.name, col);
|
|
141
|
-
if (colConfig && isObject(colConfig) && "isText" in colConfig && data[col]) {
|
|
142
|
-
if (colConfig.lowerCased) {
|
|
143
|
-
data[col] = data[col].toString().toLowerCase()
|
|
144
|
-
}
|
|
145
|
-
if (colConfig.trimmed) {
|
|
146
|
-
data[col] = data[col].toString().trim()
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
const allowedCols = tableHandler.columns.filter(c => dataKeys.includes(c.name)).map(c => c.name);
|
|
152
|
-
return { data, allowedCols }
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Ensures:
|
|
158
|
-
* - allowedCols are valid and checked against data
|
|
159
|
-
* - validate()
|
|
160
|
-
* - update is not empty
|
|
161
|
-
* - no duplicate column names ( could update with $func and plain value for same column )
|
|
162
|
-
*/
|
|
163
|
-
const getValidatedRowFieldData = async ({ allowedCols, rows, validationOptions, dbTx, command }: ParseDataArgs, tableHandler: TableHandler) => {
|
|
164
|
-
if (!allowedCols.length && command === "update") {
|
|
165
|
-
throw "allowedColumns cannot be empty";
|
|
166
|
-
}
|
|
167
|
-
const rowFieldData = await Promise.all(
|
|
168
|
-
rows.map(async nonvalidatedRow => {
|
|
169
|
-
|
|
170
|
-
let row = pickKeys(nonvalidatedRow, allowedCols);
|
|
171
|
-
const initialRowKeys = Object.keys(row);
|
|
172
|
-
if (validationOptions.validate) {
|
|
173
|
-
if(!validationOptions.localParams){
|
|
174
|
-
throw "localParams missing for validate";
|
|
175
|
-
}
|
|
176
|
-
row = await validationOptions.validate({ row, dbx: dbTx, localParams: validationOptions.localParams });
|
|
177
|
-
}
|
|
178
|
-
const keysAddedDuringValidate = Object.keys(row).filter(newKey => !initialRowKeys.includes(newKey));
|
|
179
|
-
|
|
180
|
-
const getColumn = (fieldName: string) => {
|
|
181
|
-
if (!allowedCols.concat(keysAddedDuringValidate).includes(fieldName)) {
|
|
182
|
-
throw `Unexpected/Dissallowed column name: ${fieldName}`;
|
|
183
|
-
}
|
|
184
|
-
const column = tableHandler.columns.find(c => c.name === fieldName);
|
|
185
|
-
if (!column) {
|
|
186
|
-
throw `Invalid column: ${fieldName}`;
|
|
187
|
-
}
|
|
188
|
-
return column;
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
const rowPartValues = Object.entries(row).map(([fieldName, fieldValue]) => {
|
|
192
|
-
const column = getColumn(fieldName);
|
|
193
|
-
if (isObject(fieldValue)) {
|
|
194
|
-
|
|
195
|
-
// const textPatch = getTextPatch(column, fieldValue);
|
|
196
|
-
// if(textPatch){
|
|
197
|
-
// return {
|
|
198
|
-
// type: "plain",
|
|
199
|
-
// column,
|
|
200
|
-
// fieldValue: textPatch,
|
|
201
|
-
// } satisfies RowFieldData;
|
|
202
|
-
// }
|
|
203
|
-
|
|
204
|
-
const [firstKey, ...otherkeys] = Object.keys(fieldValue);
|
|
205
|
-
const func = firstKey && !otherkeys.length? convertionFuncs.some(f => `$${f.name}` === firstKey) : undefined;
|
|
206
|
-
if(func){
|
|
207
|
-
const { funcName, args } = parseFunctionObject(fieldValue);
|
|
208
|
-
return {
|
|
209
|
-
type: "function",
|
|
210
|
-
column,
|
|
211
|
-
funcName,
|
|
212
|
-
args,
|
|
213
|
-
} satisfies RowFieldData
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
return {
|
|
217
|
-
type: "plain",
|
|
218
|
-
column: getColumn(fieldName),
|
|
219
|
-
fieldValue,
|
|
220
|
-
} satisfies RowFieldData;
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
return rowPartValues;
|
|
224
|
-
}));
|
|
225
|
-
|
|
226
|
-
return rowFieldData;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const getTextPatch = async (c: TableSchemaColumn, fieldValue: any) => {
|
|
230
|
-
|
|
231
|
-
if (c.data_type === "text" && fieldValue && isObject(fieldValue) && !["from", "to"].find(key => typeof fieldValue[key] !== "number")) {
|
|
232
|
-
const unrecProps = Object.keys(fieldValue).filter(k => !["from", "to", "text", "md5"].includes(k));
|
|
233
|
-
if (unrecProps.length) {
|
|
234
|
-
throw "Unrecognised params in textPatch field: " + unrecProps.join(", ");
|
|
235
|
-
}
|
|
236
|
-
const patchedTextData: {
|
|
237
|
-
fieldName: string;
|
|
238
|
-
from: number;
|
|
239
|
-
to: number;
|
|
240
|
-
text: string;
|
|
241
|
-
md5: string
|
|
242
|
-
} = {
|
|
243
|
-
...fieldValue,
|
|
244
|
-
fieldName: c.name
|
|
245
|
-
} as any;
|
|
246
|
-
|
|
247
|
-
// if (tableRules && !tableRules.select) throw "Select needs to be permitted to patch data";
|
|
248
|
-
// const rows = await this.find(filter, { select: patchedTextData.reduce((a, v) => ({ ...a, [v.fieldName]: 1 }), {}) }, undefined, tableRules);
|
|
249
|
-
|
|
250
|
-
// if (rows.length !== 1) {
|
|
251
|
-
// throw "Cannot patch data within a filter that affects more/less than 1 row";
|
|
252
|
-
// }
|
|
253
|
-
// return unpatchText(rows[0][p.fieldName], patchedTextData);
|
|
254
|
-
const rawValue = `OVERLAY(${asName(c.name)} PLACING ${asValue(patchedTextData.text)} FROM ${asValue(patchedTextData.from)} FOR ${asValue(patchedTextData.to - patchedTextData.from + 1)})`
|
|
255
|
-
return rawValue;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
return undefined
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
const getParsedRowFieldDataFunction = async (rowPart: RowFieldDataFunction, args: ParseDataArgs) => {
|
|
262
|
-
|
|
263
|
-
const func = convertionFuncs.find(f => `$${f.name}` === rowPart.funcName);
|
|
264
|
-
if (!func) {
|
|
265
|
-
throw `Unknown function: ${rowPart.funcName}. Expecting one of: ${convertionFuncs.map(f => f.name).join(", ")}`;
|
|
266
|
-
}
|
|
267
|
-
if (func.onlyAllowedFor && func.onlyAllowedFor !== args.command) {
|
|
268
|
-
throw `Function ${rowPart.funcName} is only allowed for ${func.onlyAllowedFor} but not ${args.command}`;
|
|
269
|
-
}
|
|
270
|
-
return func.getQuery(rowPart);
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
const getParsedRowFieldData = async (rowFieldData: RowFieldData[][], args: ParseDataArgs) => {
|
|
274
|
-
const parsedRowFieldData = Promise.all(rowFieldData.map(rowParts => {
|
|
275
|
-
return Promise.all(rowParts.map(async rowPart => {
|
|
276
|
-
let escapedVal: string;
|
|
277
|
-
if (rowPart.type === "function") {
|
|
278
|
-
escapedVal = await getParsedRowFieldDataFunction(rowPart, args);
|
|
279
|
-
} else {
|
|
280
|
-
|
|
281
|
-
/** Prevent pg-promise formatting jsonb */
|
|
282
|
-
const colIsJSON = ["json", "jsonb"].includes(rowPart.column.data_type);
|
|
283
|
-
escapedVal = pgp.as.format(colIsJSON ? "$1:json" : "$1", [rowPart.fieldValue])
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Cast to type to avoid array errors (they do not cast automatically)
|
|
288
|
-
*/
|
|
289
|
-
escapedVal += `::${rowPart.column.udt_name}`;
|
|
290
|
-
|
|
291
|
-
return {
|
|
292
|
-
escapedCol: asName(rowPart.column.name),
|
|
293
|
-
escapedVal,
|
|
294
|
-
};
|
|
295
|
-
}));
|
|
296
|
-
}));
|
|
297
|
-
|
|
298
|
-
return parsedRowFieldData;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
type ConvertionFunc = {
|
|
304
|
-
name: string;
|
|
305
|
-
description?: string;
|
|
306
|
-
onlyAllowedFor?: "insert" | "update";
|
|
307
|
-
getQuery: (fieldPart: RowFieldDataFunction) => string;
|
|
308
|
-
};
|
|
309
|
-
|
|
310
|
-
const convertionFuncs: ConvertionFunc[] = [
|
|
311
|
-
...[
|
|
312
|
-
"ST_GeomFromText",
|
|
313
|
-
"ST_Point",
|
|
314
|
-
"ST_MakePoint",
|
|
315
|
-
"ST_MakePointM",
|
|
316
|
-
"ST_PointFromText",
|
|
317
|
-
"ST_GeomFromEWKT",
|
|
318
|
-
"ST_GeomFromGeoJSON"
|
|
319
|
-
].map(name => ({
|
|
320
|
-
name,
|
|
321
|
-
getQuery: ({ args }) => {
|
|
322
|
-
const argList = args.map(arg => asValue(arg)).join(", ");
|
|
323
|
-
return `${name}(${argList})`;
|
|
324
|
-
}
|
|
325
|
-
} satisfies ConvertionFunc)),
|
|
326
|
-
{
|
|
327
|
-
name: "to_timestamp",
|
|
328
|
-
getQuery: ({ args }) => `to_timestamp(${asValue(args[0])}::BIGINT/1000.0)::timestamp`
|
|
329
|
-
}, {
|
|
330
|
-
name: "merge",
|
|
331
|
-
description: "Merge the provided jsonb objects into the existing column value, ensuring that a null source value will be coalesced with provided values",
|
|
332
|
-
onlyAllowedFor: "update",
|
|
333
|
-
getQuery: ({ args, column }) => {
|
|
334
|
-
if (!args.length) throw "merge function requires at least one argument";
|
|
335
|
-
const argVals = args.map(arg => asValue(arg))
|
|
336
|
-
const argList = argVals.join(" || ");
|
|
337
|
-
return `COALESCE(${asName(column.name)}, ${argVals.join(", ")}) || ${argList}`;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
];
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
export class ColSet {
|
|
344
|
-
opts: {
|
|
345
|
-
columns: ColumnInfo[];
|
|
346
|
-
tableName: string;
|
|
347
|
-
colNames: string[];
|
|
348
|
-
};
|
|
349
|
-
|
|
350
|
-
constructor(columns: ColumnInfo[], tableName: string) {
|
|
351
|
-
this.opts = { columns, tableName, colNames: columns.map(c => c.name) }
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
// private async getRow(data: any, allowedCols: string[], dbTx: DBHandlerServer, validate: ValidateRow | undefined, command: "update" | "insert", localParams: LocalParams | undefined): Promise<ParsedRowFieldData[]> {
|
|
356
|
-
// const badCol = allowedCols.find(c => !this.opts.colNames.includes(c))
|
|
357
|
-
// if (!allowedCols || badCol) {
|
|
358
|
-
// throw "Missing or unexpected columns: " + badCol;
|
|
359
|
-
// }
|
|
360
|
-
|
|
361
|
-
// if (command === "update" && isEmpty(data)) {
|
|
362
|
-
// throw "No data provided for update";
|
|
363
|
-
// }
|
|
364
|
-
|
|
365
|
-
// let row = pickKeys(data, allowedCols);
|
|
366
|
-
// if (validate) {
|
|
367
|
-
// if (!localParams) throw "localParams missing"
|
|
368
|
-
// row = await validate({ row, dbx: dbTx, localParams });
|
|
369
|
-
// }
|
|
370
|
-
|
|
371
|
-
// return Object.entries(row).map(([fieldName, fieldValue]) => {
|
|
372
|
-
// const col = this.opts.columns.find(c => c.name === fieldName);
|
|
373
|
-
// if (!col) throw "Unexpected missing col name";
|
|
374
|
-
|
|
375
|
-
// /**
|
|
376
|
-
// * Add conversion functions for PostGIS data
|
|
377
|
-
// */
|
|
378
|
-
// let escapedVal = "";
|
|
379
|
-
// if ((col.udt_name === "geometry" || col.udt_name === "geography") && isObject(fieldValue)) {
|
|
380
|
-
|
|
381
|
-
// const dataKeys = Object.keys(fieldValue);
|
|
382
|
-
// const funcName = dataKeys[0]!;
|
|
383
|
-
// const func = convertionFuncs.find(f => f.name === funcName);
|
|
384
|
-
// const funcArgs = fieldValue?.[funcName]
|
|
385
|
-
// if (dataKeys.length !== 1 || !func || !Array.isArray(funcArgs)) {
|
|
386
|
-
// throw `Expecting only one function key (${convertionFuncs.join(", ")}) \nwith an array of arguments \n within column (${fieldName}) data but got: ${JSON.stringify(fieldValue)} \nExample: { geo_col: { ST_GeomFromText: ["POINT(-71.064544 42.28787)", 4326] } }`;
|
|
387
|
-
// }
|
|
388
|
-
// escapedVal = func.getQuery(funcArgs);
|
|
389
|
-
// } else if (col.udt_name === "text") {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
// } else {
|
|
393
|
-
// /** Prevent pg-promise formatting jsonb */
|
|
394
|
-
// const colIsJSON = ["json", "jsonb"].includes(col.data_type);
|
|
395
|
-
// escapedVal = pgp.as.format(colIsJSON ? "$1:json" : "$1", [fieldValue])
|
|
396
|
-
// }
|
|
397
|
-
|
|
398
|
-
// /**
|
|
399
|
-
// * Cast to type to avoid array errors (they do not cast automatically)
|
|
400
|
-
// */
|
|
401
|
-
// escapedVal += `::${col.udt_name}`
|
|
402
|
-
|
|
403
|
-
// return {
|
|
404
|
-
// escapedCol: asName(fieldName),
|
|
405
|
-
// escapedVal,
|
|
406
|
-
// }
|
|
407
|
-
// });
|
|
408
|
-
|
|
409
|
-
// }
|
|
410
|
-
|
|
411
|
-
// async getInsertQuery(data: AnyObject[], allowedCols: string[], dbTx: DBHandlerServer, validate: ValidateRowBasic | undefined, localParams: LocalParams | undefined) {
|
|
412
|
-
// const inserts = (await Promise.all(data.map(async d => {
|
|
413
|
-
// const rowParts = await this.getRow(d, allowedCols, dbTx, validate, "insert", localParams);
|
|
414
|
-
// return Object.fromEntries(rowParts.map(rp => [rp.escapedCol, rp.escapedVal]));
|
|
415
|
-
// })));
|
|
416
|
-
// const uniqueColumns = Array.from(new Set(inserts.flatMap(row => Object.keys(row))))
|
|
417
|
-
// const values = inserts.map(row => `(${uniqueColumns.map(colName => row[colName] ?? 'DEFAULT')})`).join(",\n");
|
|
418
|
-
// const whatToInsert = !uniqueColumns.length ? "DEFAULT VALUES" : `(${uniqueColumns}) VALUES ${values}`
|
|
419
|
-
// return `INSERT INTO ${this.opts.tableName} ${whatToInsert} `;
|
|
420
|
-
// }
|
|
421
|
-
// async getUpdateQuery(data: AnyObject | AnyObject[], allowedCols: string[], dbTx: DBHandlerServer, validate: ValidateRowBasic | undefined, localParams: LocalParams | undefined): Promise<string> {
|
|
422
|
-
// const res = (await Promise.all((Array.isArray(data) ? data : [data]).map(async d => {
|
|
423
|
-
// const rowParts = await this.getRow(d, allowedCols, dbTx, validate, "update", localParams);
|
|
424
|
-
// return `UPDATE ${this.opts.tableName} SET ` + rowParts.map(r => `${r.escapedCol} = ${r.escapedVal} `).join(",\n")
|
|
425
|
-
// }))).join(";\n") + " ";
|
|
426
|
-
// return res;
|
|
427
|
-
// }
|
|
428
|
-
}
|
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
import pgPromise from "pg-promise";
|
|
2
|
-
import { AnyObject, asName, DeleteParams, FieldFilter, InsertParams, Select, UpdateParams } from "prostgles-types";
|
|
3
|
-
import { DB } from "../../Prostgles";
|
|
4
|
-
import { SyncRule, TableRule } from "../../PublishParser/PublishParser";
|
|
5
|
-
import TableConfigurator from "../../TableConfig/TableConfig";
|
|
6
|
-
import { DboBuilder, Filter, getErrorAsObject, getSerializedClientErrorFromPGError, LocalParams, TableHandlers } from "../DboBuilder";
|
|
7
|
-
import type { TableSchema } from "../DboBuilderTypes";
|
|
8
|
-
import { parseUpdateRules } from "../parseUpdateRules";
|
|
9
|
-
import { COMPUTED_FIELDS, FUNCTIONS } from "../QueryBuilder/Functions";
|
|
10
|
-
import { SelectItem, SelectItemBuilder } from "../QueryBuilder/QueryBuilder";
|
|
11
|
-
import { JoinPaths, ViewHandler } from "../ViewHandler/ViewHandler";
|
|
12
|
-
import { DataValidator } from "./DataValidator";
|
|
13
|
-
import { _delete } from "./delete";
|
|
14
|
-
import { insert } from "./insert";
|
|
15
|
-
import { update } from "./update";
|
|
16
|
-
import { updateBatch } from "./updateBatch";
|
|
17
|
-
import { upsert } from "./upsert";
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
export type ValidatedParams = {
|
|
21
|
-
row: AnyObject;
|
|
22
|
-
forcedData?: AnyObject;
|
|
23
|
-
allowedFields?: FieldFilter;
|
|
24
|
-
tableRules?: TableRule;
|
|
25
|
-
fixIssues: boolean;
|
|
26
|
-
tableConfigurator: TableConfigurator | undefined;
|
|
27
|
-
tableHandler: TableHandler;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export class TableHandler extends ViewHandler {
|
|
31
|
-
|
|
32
|
-
dataValidator: DataValidator;
|
|
33
|
-
constructor(db: DB, tableOrViewInfo: TableSchema, dboBuilder: DboBuilder, tx?: {t: pgPromise.ITask<{}>, dbTX: TableHandlers}, joinPaths?: JoinPaths) {
|
|
34
|
-
super(db, tableOrViewInfo, dboBuilder, tx, joinPaths);
|
|
35
|
-
|
|
36
|
-
this.remove = this.delete;
|
|
37
|
-
|
|
38
|
-
this.dataValidator = new DataValidator(this);
|
|
39
|
-
this.is_view = false;
|
|
40
|
-
this.is_media = dboBuilder.prostgles.isMedia(this.name)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
getFinalDBtx = (localParams: LocalParams | undefined) => {
|
|
44
|
-
return localParams?.tx?.dbTX ?? this.tx?.dbTX;
|
|
45
|
-
}
|
|
46
|
-
getFinalDbo = (localParams: LocalParams | undefined) => {
|
|
47
|
-
return this.getFinalDBtx(localParams) ?? this.dboBuilder.dbo;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
parseUpdateRules = parseUpdateRules.bind(this);
|
|
51
|
-
|
|
52
|
-
update = update.bind(this);
|
|
53
|
-
updateBatch = updateBatch.bind(this);
|
|
54
|
-
|
|
55
|
-
async insert(
|
|
56
|
-
rowOrRows: AnyObject | AnyObject[],
|
|
57
|
-
param2?: InsertParams,
|
|
58
|
-
param3_unused?: undefined,
|
|
59
|
-
tableRules?: TableRule,
|
|
60
|
-
_localParams?: LocalParams
|
|
61
|
-
): Promise<any | any[] | boolean> {
|
|
62
|
-
return insert.bind(this)(rowOrRows, param2, param3_unused, tableRules, _localParams)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
prepareReturning = async (returning: Select | undefined, allowedFields: string[]): Promise<SelectItem[]> => {
|
|
66
|
-
const result: SelectItem[] = [];
|
|
67
|
-
if (returning) {
|
|
68
|
-
const sBuilder = new SelectItemBuilder({
|
|
69
|
-
allFields: this.column_names.slice(0),
|
|
70
|
-
allowedFields,
|
|
71
|
-
allowedOrderByFields: allowedFields,
|
|
72
|
-
computedFields: COMPUTED_FIELDS,
|
|
73
|
-
functions: FUNCTIONS.filter(f => f.type === "function" && f.singleColArg),
|
|
74
|
-
isView: this.is_view,
|
|
75
|
-
columns: this.columns,
|
|
76
|
-
});
|
|
77
|
-
await sBuilder.parseUserSelect(returning);
|
|
78
|
-
|
|
79
|
-
return sBuilder.select;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return result;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
makeReturnQuery(items?: SelectItem[]) {
|
|
86
|
-
if (items?.length) return " RETURNING " + getSelectItemQuery(items);
|
|
87
|
-
return "";
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async delete(filter?: Filter, params?: DeleteParams, param3_unused?: undefined, table_rules?: TableRule, localParams?: LocalParams): Promise<any> {
|
|
91
|
-
return _delete.bind(this)(filter, params, param3_unused, table_rules, localParams);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
remove(filter: Filter, params?: UpdateParams, param3_unused?: undefined, tableRules?: TableRule, localParams?: LocalParams) {
|
|
95
|
-
return this.delete(filter, params, param3_unused, tableRules, localParams);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
upsert = upsert.bind(this);
|
|
99
|
-
|
|
100
|
-
/* External request. Cannot sync from server */
|
|
101
|
-
async sync(filter: Filter, params: { select?: FieldFilter }, param3_unused: undefined, table_rules: TableRule, localParams: LocalParams) {
|
|
102
|
-
const start = Date.now();
|
|
103
|
-
try {
|
|
104
|
-
|
|
105
|
-
if(!this.dboBuilder.canSubscribe){
|
|
106
|
-
throw "Cannot subscribe. PubSubManager not initiated";
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (!localParams) throw "Sync not allowed within the server code";
|
|
110
|
-
const { socket } = localParams;
|
|
111
|
-
if (!socket) throw "socket missing";
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if (!table_rules || !table_rules.sync || !table_rules.select) throw "sync or select table rules missing";
|
|
115
|
-
|
|
116
|
-
if (this.tx) throw "Sync not allowed within transactions";
|
|
117
|
-
|
|
118
|
-
const ALLOWED_PARAMS = ["select"];
|
|
119
|
-
const invalidParams = Object.keys(params || {}).filter(k => !ALLOWED_PARAMS.includes(k));
|
|
120
|
-
if (invalidParams.length) throw "Invalid or dissallowed params found: " + invalidParams.join(", ");
|
|
121
|
-
|
|
122
|
-
const { synced_field, allow_delete }: SyncRule = table_rules.sync;
|
|
123
|
-
|
|
124
|
-
if (!table_rules.sync.id_fields.length || !synced_field) {
|
|
125
|
-
const err = "INTERNAL ERROR: id_fields OR synced_field missing from publish";
|
|
126
|
-
console.error(err);
|
|
127
|
-
throw err;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const id_fields = this.parseFieldFilter(table_rules.sync.id_fields, false);
|
|
131
|
-
const syncFields = [...id_fields, synced_field];
|
|
132
|
-
|
|
133
|
-
const allowedSelect = this.parseFieldFilter(table_rules?.select.fields ?? false);
|
|
134
|
-
if (syncFields.find(f => !allowedSelect.includes(f))) {
|
|
135
|
-
throw `INTERNAL ERROR: sync field missing from publish.${this.name}.select.fields`;
|
|
136
|
-
}
|
|
137
|
-
const select = this.getAllowedSelectFields(
|
|
138
|
-
params?.select ?? "*",
|
|
139
|
-
allowedSelect,
|
|
140
|
-
false
|
|
141
|
-
);
|
|
142
|
-
if (!select.length) throw "Empty select not allowed";
|
|
143
|
-
|
|
144
|
-
/* Add sync fields if missing */
|
|
145
|
-
syncFields.map(sf => {
|
|
146
|
-
if (!select.includes(sf)) select.push(sf);
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
/* Step 1: parse command and params */
|
|
150
|
-
const result = await this.find(filter, { select, limit: 0 }, undefined, table_rules, localParams)
|
|
151
|
-
.then(async _isValid => {
|
|
152
|
-
|
|
153
|
-
const { filterFields, forcedFilter } = table_rules?.select || {};
|
|
154
|
-
const condition = (await this.prepareWhere({ select: undefined, filter, forcedFilter, filterFields, addWhere: false, localParams, tableRule: table_rules })).where;
|
|
155
|
-
|
|
156
|
-
const pubSubManager = await this.dboBuilder.getPubSubManager();
|
|
157
|
-
return pubSubManager.addSync({
|
|
158
|
-
table_info: this.tableOrViewInfo,
|
|
159
|
-
condition,
|
|
160
|
-
id_fields,
|
|
161
|
-
synced_field,
|
|
162
|
-
allow_delete,
|
|
163
|
-
socket,
|
|
164
|
-
table_rules,
|
|
165
|
-
filter: { ...filter },
|
|
166
|
-
params: { select }
|
|
167
|
-
}).then(channelName => ({ channelName, id_fields, synced_field }));
|
|
168
|
-
});
|
|
169
|
-
await this._log({ command: "sync", localParams, data: { filter, params }, duration: Date.now() - start });
|
|
170
|
-
return result;
|
|
171
|
-
} catch (e) {
|
|
172
|
-
await this._log({ command: "sync", localParams, data: { filter, params }, duration: Date.now() - start, error: getErrorAsObject(e) });
|
|
173
|
-
throw getSerializedClientErrorFromPGError(e, { type: "tableMethod", localParams, view: this });
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/*
|
|
177
|
-
REPLICATION
|
|
178
|
-
|
|
179
|
-
1 Sync proccess (NO DELETES ALLOWED):
|
|
180
|
-
|
|
181
|
-
Client sends:
|
|
182
|
-
"sync-request"
|
|
183
|
-
{ min_id, max_id, count, max_synced }
|
|
184
|
-
|
|
185
|
-
Server sends:
|
|
186
|
-
"sync-pull"
|
|
187
|
-
{ from_synced }
|
|
188
|
-
|
|
189
|
-
Client sends:
|
|
190
|
-
"sync-push"
|
|
191
|
-
{ data } -> WHERE synced >= from_synced
|
|
192
|
-
|
|
193
|
-
Server upserts:
|
|
194
|
-
WHERE not exists synced = synced AND id = id
|
|
195
|
-
UNTIL
|
|
196
|
-
|
|
197
|
-
Server sends
|
|
198
|
-
"sync-push"
|
|
199
|
-
{ data } -> WHERE synced >= from_synced
|
|
200
|
-
*/
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
export const getSelectItemQuery = (items: SelectItem[]) => items.map(s => s.getQuery() + " AS " + asName(s.alias)).join(", ")
|