prostgles-server 3.0.38 → 3.0.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/DBSchemaBuilder.d.ts.map +1 -1
- package/dist/DBSchemaBuilder.js.map +1 -1
- package/dist/DboBuilder/QueryBuilder/QueryBuilder.d.ts +2 -1
- package/dist/DboBuilder/QueryBuilder/QueryBuilder.d.ts.map +1 -1
- package/dist/DboBuilder/QueryBuilder/QueryBuilder.js +47 -2
- package/dist/DboBuilder/QueryBuilder/QueryBuilder.js.map +1 -1
- package/dist/DboBuilder/QueryBuilder/makeSelectQuery.d.ts +1 -1
- package/dist/DboBuilder/QueryBuilder/makeSelectQuery.d.ts.map +1 -1
- package/dist/DboBuilder/QueryBuilder/makeSelectQuery.js.map +1 -1
- package/dist/DboBuilder/TableHandler.d.ts +62 -0
- package/dist/DboBuilder/TableHandler.d.ts.map +1 -0
- package/dist/DboBuilder/TableHandler.js +304 -0
- package/dist/DboBuilder/TableHandler.js.map +1 -0
- package/dist/DboBuilder/ViewHandler.d.ts +137 -0
- package/dist/DboBuilder/ViewHandler.d.ts.map +1 -0
- package/dist/DboBuilder/ViewHandler.js +1292 -0
- package/dist/DboBuilder/ViewHandler.js.map +1 -0
- package/dist/DboBuilder/delete.d.ts +2 -1
- package/dist/DboBuilder/delete.d.ts.map +1 -1
- package/dist/DboBuilder/delete.js.map +1 -1
- package/dist/DboBuilder/getColumns.d.ts +12 -0
- package/dist/DboBuilder/getColumns.d.ts.map +1 -0
- package/dist/DboBuilder/getColumns.js +95 -0
- package/dist/DboBuilder/getColumns.js.map +1 -0
- package/dist/DboBuilder/insert.d.ts +2 -1
- package/dist/DboBuilder/insert.d.ts.map +1 -1
- package/dist/DboBuilder/insert.js +1 -1
- package/dist/DboBuilder/insert.js.map +1 -1
- package/dist/DboBuilder/insertDataParse.d.ts +2 -1
- package/dist/DboBuilder/insertDataParse.d.ts.map +1 -1
- package/dist/DboBuilder/insertDataParse.js +2 -3
- package/dist/DboBuilder/insertDataParse.js.map +1 -1
- package/dist/DboBuilder/parseUpdateRules.d.ts +18 -0
- package/dist/DboBuilder/parseUpdateRules.d.ts.map +1 -0
- package/dist/DboBuilder/parseUpdateRules.js +119 -0
- package/dist/DboBuilder/parseUpdateRules.js.map +1 -0
- package/dist/DboBuilder/update.d.ts +2 -1
- package/dist/DboBuilder/update.d.ts.map +1 -1
- package/dist/DboBuilder/update.js.map +1 -1
- package/dist/DboBuilder/uploadFile.d.ts +2 -1
- package/dist/DboBuilder/uploadFile.d.ts.map +1 -1
- package/dist/DboBuilder/uploadFile.js.map +1 -1
- package/dist/DboBuilder.d.ts +5 -185
- package/dist/DboBuilder.d.ts.map +1 -1
- package/dist/DboBuilder.js +7 -1773
- package/dist/DboBuilder.js.map +1 -1
- package/dist/FileManager.d.ts.map +1 -1
- package/dist/FileManager.js +4 -4
- package/dist/FileManager.js.map +1 -1
- package/dist/PubSubManager.d.ts.map +1 -1
- package/dist/PubSubManager.js +4 -0
- package/dist/PubSubManager.js.map +1 -1
- package/dist/PublishParser.d.ts.map +1 -1
- package/dist/PublishParser.js.map +1 -1
- package/dist/index.js +0 -38
- package/dist/index.js.map +1 -1
- package/lib/DBSchemaBuilder.d.ts.map +1 -1
- package/lib/DBSchemaBuilder.ts +1 -1
- package/lib/DboBuilder/QueryBuilder/QueryBuilder.d.ts +2 -1
- package/lib/DboBuilder/QueryBuilder/QueryBuilder.d.ts.map +1 -1
- package/lib/DboBuilder/QueryBuilder/QueryBuilder.js +47 -2
- package/lib/DboBuilder/QueryBuilder/QueryBuilder.ts +55 -2
- package/lib/DboBuilder/QueryBuilder/makeSelectQuery.d.ts +1 -1
- package/lib/DboBuilder/QueryBuilder/makeSelectQuery.d.ts.map +1 -1
- package/lib/DboBuilder/QueryBuilder/makeSelectQuery.ts +2 -1
- package/lib/DboBuilder/TableHandler.d.ts +54 -0
- package/lib/DboBuilder/TableHandler.d.ts.map +1 -0
- package/lib/DboBuilder/TableHandler.js +303 -0
- package/lib/DboBuilder/TableHandler.ts +365 -0
- package/lib/DboBuilder/ViewHandler.d.ts +133 -0
- package/lib/DboBuilder/ViewHandler.d.ts.map +1 -0
- package/lib/DboBuilder/ViewHandler.js +1291 -0
- package/lib/DboBuilder/ViewHandler.ts +1542 -0
- package/lib/DboBuilder/delete.d.ts +2 -1
- package/lib/DboBuilder/delete.d.ts.map +1 -1
- package/lib/DboBuilder/delete.ts +2 -1
- package/lib/DboBuilder/getColumns.d.ts +12 -0
- package/lib/DboBuilder/getColumns.d.ts.map +1 -0
- package/lib/DboBuilder/getColumns.js +94 -0
- package/lib/DboBuilder/getColumns.ts +133 -0
- package/lib/DboBuilder/insert.d.ts +2 -1
- package/lib/DboBuilder/insert.d.ts.map +1 -1
- package/lib/DboBuilder/insert.js +1 -1
- package/lib/DboBuilder/insert.ts +3 -2
- package/lib/DboBuilder/insertDataParse.d.ts +2 -1
- package/lib/DboBuilder/insertDataParse.d.ts.map +1 -1
- package/lib/DboBuilder/insertDataParse.js +2 -3
- package/lib/DboBuilder/insertDataParse.ts +6 -5
- package/lib/DboBuilder/parseUpdateRules.d.ts +18 -0
- package/lib/DboBuilder/parseUpdateRules.d.ts.map +1 -0
- package/lib/DboBuilder/parseUpdateRules.js +118 -0
- package/lib/DboBuilder/parseUpdateRules.ts +156 -0
- package/lib/DboBuilder/update.d.ts +2 -1
- package/lib/DboBuilder/update.d.ts.map +1 -1
- package/lib/DboBuilder/update.ts +2 -1
- package/lib/DboBuilder/uploadFile.d.ts +2 -1
- package/lib/DboBuilder/uploadFile.d.ts.map +1 -1
- package/lib/DboBuilder/uploadFile.ts +2 -1
- package/lib/DboBuilder.d.ts +5 -185
- package/lib/DboBuilder.d.ts.map +1 -1
- package/lib/DboBuilder.js +7 -1773
- package/lib/DboBuilder.ts +170 -2296
- package/lib/FileManager.d.ts.map +1 -1
- package/lib/FileManager.js +4 -4
- package/lib/FileManager.ts +3 -1
- package/lib/PubSubManager.d.ts.map +1 -1
- package/lib/PubSubManager.js +4 -0
- package/lib/PubSubManager.ts +6 -1
- package/lib/PublishParser.d.ts.map +1 -1
- package/lib/PublishParser.ts +3 -1
- package/lib/SyncReplication.ts +1 -1
- package/lib/index.js +0 -38
- package/lib/index.ts +1 -53
- package/package.json +2 -2
- package/tests/client/PID.txt +1 -1
- package/tests/client_only_queries.ts +1 -1
- package/tests/server/package-lock.json +409 -250
- package/tests/server/package.json +1 -1
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TableHandler = void 0;
|
|
4
|
+
const prostgles_types_1 = require("prostgles-types");
|
|
5
|
+
const DboBuilder_1 = require("../DboBuilder");
|
|
6
|
+
const PubSubManager_1 = require("../PubSubManager");
|
|
7
|
+
const delete_1 = require("./delete");
|
|
8
|
+
const insert_1 = require("./insert");
|
|
9
|
+
const insertDataParse_1 = require("./insertDataParse");
|
|
10
|
+
const QueryBuilder_1 = require("./QueryBuilder/QueryBuilder");
|
|
11
|
+
const update_1 = require("./update");
|
|
12
|
+
const ViewHandler_1 = require("./ViewHandler");
|
|
13
|
+
const parseUpdateRules_1 = require("./parseUpdateRules");
|
|
14
|
+
class TableHandler extends ViewHandler_1.ViewHandler {
|
|
15
|
+
constructor(db, tableOrViewInfo, dboBuilder, t, dbTX, joinPaths) {
|
|
16
|
+
super(db, tableOrViewInfo, dboBuilder, t, dbTX, joinPaths);
|
|
17
|
+
this.parseUpdateRules = parseUpdateRules_1.parseUpdateRules.bind(this);
|
|
18
|
+
this.update = update_1.update.bind(this);
|
|
19
|
+
this.insertDataParse = insertDataParse_1.insertDataParse;
|
|
20
|
+
this.prepareReturning = async (returning, allowedFields) => {
|
|
21
|
+
let result = [];
|
|
22
|
+
if (returning) {
|
|
23
|
+
let sBuilder = new QueryBuilder_1.SelectItemBuilder({
|
|
24
|
+
allFields: this.column_names.slice(0),
|
|
25
|
+
allowedFields,
|
|
26
|
+
allowedOrderByFields: allowedFields,
|
|
27
|
+
computedFields: QueryBuilder_1.COMPUTED_FIELDS,
|
|
28
|
+
functions: QueryBuilder_1.FUNCTIONS.filter(f => f.type === "function" && f.singleColArg),
|
|
29
|
+
isView: this.is_view,
|
|
30
|
+
columns: this.columns,
|
|
31
|
+
});
|
|
32
|
+
await sBuilder.parseUserSelect(returning);
|
|
33
|
+
return sBuilder.select;
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
this.remove = this.delete;
|
|
38
|
+
this.io_stats = {
|
|
39
|
+
since: Date.now(),
|
|
40
|
+
queries: 0,
|
|
41
|
+
throttle_queries_per_sec: 500,
|
|
42
|
+
batching: null
|
|
43
|
+
};
|
|
44
|
+
this.is_view = false;
|
|
45
|
+
this.is_media = dboBuilder.prostgles.isMedia(this.name);
|
|
46
|
+
}
|
|
47
|
+
/* TO DO: Maybe finished query batching */
|
|
48
|
+
willBatch(query) {
|
|
49
|
+
const now = Date.now();
|
|
50
|
+
if (this.io_stats.since < Date.now()) {
|
|
51
|
+
this.io_stats.since = Date.now();
|
|
52
|
+
this.io_stats.queries = 0;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
this.io_stats.queries++;
|
|
56
|
+
}
|
|
57
|
+
if (this.io_stats.queries > this.io_stats.throttle_queries_per_sec) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async subscribe(filter, params = {}, localFunc, table_rules, localParams) {
|
|
62
|
+
try {
|
|
63
|
+
if (this.is_view)
|
|
64
|
+
throw "Cannot subscribe to a view";
|
|
65
|
+
if (this.t)
|
|
66
|
+
throw "subscribe not allowed within transactions";
|
|
67
|
+
if (!localParams && !localFunc)
|
|
68
|
+
throw " missing data. provide -> localFunc | localParams { socket } ";
|
|
69
|
+
if (localParams && localParams.socket && localFunc) {
|
|
70
|
+
console.error({ localParams, localFunc });
|
|
71
|
+
throw " Cannot have localFunc AND socket ";
|
|
72
|
+
}
|
|
73
|
+
const { filterFields, forcedFilter } = table_rules?.select || {}, filterOpts = await this.prepareWhere({ filter, forcedFilter, addKeywords: false, filterFields, tableAlias: undefined, localParams, tableRule: table_rules }), condition = filterOpts.where, throttle = params?.throttle || 0, selectParams = (0, PubSubManager_1.omitKeys)(params || {}, ["throttle"]);
|
|
74
|
+
// const { subOne = false } = localParams || {};
|
|
75
|
+
const filterSize = JSON.stringify(filter || {}).length;
|
|
76
|
+
if (filterSize * 4 > 2704) {
|
|
77
|
+
throw "filter too big. Might exceed the btree version 4 maximum 2704";
|
|
78
|
+
}
|
|
79
|
+
if (!localFunc) {
|
|
80
|
+
if (!this.dboBuilder.prostgles.isSuperUser)
|
|
81
|
+
throw "Subscribe not possible. Must be superuser to add triggers 1856";
|
|
82
|
+
return await this.find(filter, { ...selectParams, limit: 0 }, undefined, table_rules, localParams)
|
|
83
|
+
.then(async (isValid) => {
|
|
84
|
+
const { socket } = localParams ?? {};
|
|
85
|
+
const pubSubManager = await this.dboBuilder.getPubSubManager();
|
|
86
|
+
return pubSubManager.addSub({
|
|
87
|
+
table_info: this.tableOrViewInfo,
|
|
88
|
+
socket,
|
|
89
|
+
table_rules,
|
|
90
|
+
condition: condition,
|
|
91
|
+
func: undefined,
|
|
92
|
+
filter: { ...filter },
|
|
93
|
+
params: { ...selectParams },
|
|
94
|
+
socket_id: socket?.id,
|
|
95
|
+
table_name: this.name,
|
|
96
|
+
throttle,
|
|
97
|
+
last_throttled: 0,
|
|
98
|
+
// subOne
|
|
99
|
+
}).then(channelName => ({ channelName }));
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
const pubSubManager = await this.dboBuilder.getPubSubManager();
|
|
104
|
+
pubSubManager.addSub({
|
|
105
|
+
table_info: this.tableOrViewInfo,
|
|
106
|
+
socket: undefined,
|
|
107
|
+
table_rules,
|
|
108
|
+
condition,
|
|
109
|
+
func: localFunc,
|
|
110
|
+
filter: { ...filter },
|
|
111
|
+
params: { ...selectParams },
|
|
112
|
+
socket_id: undefined,
|
|
113
|
+
table_name: this.name,
|
|
114
|
+
throttle,
|
|
115
|
+
last_throttled: 0,
|
|
116
|
+
// subOne
|
|
117
|
+
}).then(channelName => ({ channelName }));
|
|
118
|
+
const unsubscribe = async () => {
|
|
119
|
+
const pubSubManager = await this.dboBuilder.getPubSubManager();
|
|
120
|
+
pubSubManager.removeLocalSub(this.name, condition, localFunc);
|
|
121
|
+
};
|
|
122
|
+
let res = Object.freeze({ unsubscribe });
|
|
123
|
+
return res;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch (e) {
|
|
127
|
+
if (localParams && localParams.testRule)
|
|
128
|
+
throw e;
|
|
129
|
+
throw (0, DboBuilder_1.parseError)(e, `dbo.${this.name}.subscribe()`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
subscribeOne(filter, params = {}, localFunc, table_rules, localParams) {
|
|
133
|
+
let func = localParams ? undefined : (rows) => localFunc(rows[0]);
|
|
134
|
+
return this.subscribe(filter, { ...params, limit: 2 }, func, table_rules, localParams);
|
|
135
|
+
}
|
|
136
|
+
async updateBatch(data, params, tableRules, localParams) {
|
|
137
|
+
try {
|
|
138
|
+
const queries = await Promise.all(data.map(async ([filter, data]) => await this.update(filter, data, { ...(params || {}), returning: undefined }, tableRules, { ...(localParams || {}), returnQuery: true })));
|
|
139
|
+
const keys = (data && data.length) ? Object.keys(data[0]) : [];
|
|
140
|
+
return this.db.tx(t => {
|
|
141
|
+
const _queries = queries.map(q => t.none(q));
|
|
142
|
+
return t.batch(_queries);
|
|
143
|
+
}).catch(err => (0, DboBuilder_1.makeErr)(err, localParams, this, keys));
|
|
144
|
+
}
|
|
145
|
+
catch (e) {
|
|
146
|
+
if (localParams && localParams.testRule)
|
|
147
|
+
throw e;
|
|
148
|
+
throw (0, DboBuilder_1.parseError)(e, `dbo.${this.name}.update()`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
validateNewData({ row, forcedData, allowedFields, tableRules, fixIssues = false }) {
|
|
152
|
+
const synced_field = (tableRules ?? {})?.sync?.synced_field;
|
|
153
|
+
/* Update synced_field if sync is on and missing */
|
|
154
|
+
if (synced_field && !row[synced_field]) {
|
|
155
|
+
row[synced_field] = Date.now();
|
|
156
|
+
}
|
|
157
|
+
let data = this.prepareFieldValues(row, forcedData, allowedFields, fixIssues);
|
|
158
|
+
const dataKeys = (0, prostgles_types_1.getKeys)(data);
|
|
159
|
+
dataKeys.map(col => {
|
|
160
|
+
this.dboBuilder.prostgles?.tableConfigurator?.checkColVal({ table: this.name, col, value: data[col] });
|
|
161
|
+
const colConfig = this.dboBuilder.prostgles?.tableConfigurator?.getColumnConfig(this.name, col);
|
|
162
|
+
if (colConfig && (0, prostgles_types_1.isObject)(colConfig) && "isText" in colConfig && data[col]) {
|
|
163
|
+
if (colConfig.lowerCased) {
|
|
164
|
+
data[col] = data[col].toString().toLowerCase();
|
|
165
|
+
}
|
|
166
|
+
if (colConfig.trimmed) {
|
|
167
|
+
data[col] = data[col].toString().trim();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
return { data, allowedCols: this.columns.filter(c => dataKeys.includes(c.name)).map(c => c.name) };
|
|
172
|
+
}
|
|
173
|
+
async insert(rowOrRows, param2, param3_unused, tableRules, _localParams) {
|
|
174
|
+
return insert_1.insert.bind(this)(rowOrRows, param2, param3_unused, tableRules, _localParams);
|
|
175
|
+
}
|
|
176
|
+
makeReturnQuery(items) {
|
|
177
|
+
if (items?.length)
|
|
178
|
+
return " RETURNING " + items.map(s => s.getQuery() + " AS " + (0, prostgles_types_1.asName)(s.alias)).join(", ");
|
|
179
|
+
return "";
|
|
180
|
+
}
|
|
181
|
+
async delete(filter, params, param3_unused, table_rules, localParams) {
|
|
182
|
+
return delete_1._delete.bind(this)(filter, params, param3_unused, table_rules, localParams);
|
|
183
|
+
}
|
|
184
|
+
;
|
|
185
|
+
remove(filter, params, param3_unused, tableRules, localParams) {
|
|
186
|
+
return this.delete(filter, params, param3_unused, tableRules, localParams);
|
|
187
|
+
}
|
|
188
|
+
async upsert(filter, newData, params, table_rules, localParams) {
|
|
189
|
+
try {
|
|
190
|
+
/* Do it within a transaction to ensure consisency */
|
|
191
|
+
if (!this.t) {
|
|
192
|
+
return this.dboBuilder.getTX(dbTX => _upsert(dbTX[this.name]));
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
return _upsert(this);
|
|
196
|
+
}
|
|
197
|
+
async function _upsert(tblH) {
|
|
198
|
+
return tblH.find(filter, { select: "", limit: 1 }, undefined, table_rules, localParams)
|
|
199
|
+
.then(exists => {
|
|
200
|
+
if (exists && exists.length) {
|
|
201
|
+
return tblH.update(filter, newData, params, table_rules, localParams);
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
return tblH.insert({ ...newData, ...filter }, params, undefined, table_rules, localParams);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
catch (e) {
|
|
210
|
+
if (localParams && localParams.testRule)
|
|
211
|
+
throw e;
|
|
212
|
+
throw (0, DboBuilder_1.parseError)(e, `dbo.${this.name}.upsert()`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
;
|
|
216
|
+
/* External request. Cannot sync from server */
|
|
217
|
+
async sync(filter, params, param3_unused, table_rules, localParams) {
|
|
218
|
+
if (!localParams)
|
|
219
|
+
throw "Sync not allowed within the same server code";
|
|
220
|
+
const { socket } = localParams;
|
|
221
|
+
if (!socket)
|
|
222
|
+
throw "INTERNAL ERROR: socket missing";
|
|
223
|
+
if (!table_rules || !table_rules.sync || !table_rules.select)
|
|
224
|
+
throw "INTERNAL ERROR: sync or select rules missing";
|
|
225
|
+
if (this.t)
|
|
226
|
+
throw "Sync not allowed within transactions";
|
|
227
|
+
const ALLOWED_PARAMS = ["select"];
|
|
228
|
+
const invalidParams = Object.keys(params || {}).filter(k => !ALLOWED_PARAMS.includes(k));
|
|
229
|
+
if (invalidParams.length)
|
|
230
|
+
throw "Invalid or dissallowed params found: " + invalidParams.join(", ");
|
|
231
|
+
try {
|
|
232
|
+
let { id_fields, synced_field, allow_delete } = table_rules.sync;
|
|
233
|
+
const syncFields = [...id_fields, synced_field];
|
|
234
|
+
if (!id_fields || !synced_field) {
|
|
235
|
+
const err = "INTERNAL ERROR: id_fields OR synced_field missing from publish";
|
|
236
|
+
console.error(err);
|
|
237
|
+
throw err;
|
|
238
|
+
}
|
|
239
|
+
id_fields = this.parseFieldFilter(id_fields, false);
|
|
240
|
+
let allowedSelect = this.parseFieldFilter(table_rules?.select.fields ?? false);
|
|
241
|
+
if (syncFields.find(f => !allowedSelect.includes(f))) {
|
|
242
|
+
throw `INTERNAL ERROR: sync field missing from publish.${this.name}.select.fields`;
|
|
243
|
+
}
|
|
244
|
+
let select = this.getAllowedSelectFields((params || {})?.select || "*", allowedSelect, false);
|
|
245
|
+
if (!select.length)
|
|
246
|
+
throw "Empty select not allowed";
|
|
247
|
+
/* Add sync fields if missing */
|
|
248
|
+
syncFields.map(sf => {
|
|
249
|
+
if (!select.includes(sf))
|
|
250
|
+
select.push(sf);
|
|
251
|
+
});
|
|
252
|
+
/* Step 1: parse command and params */
|
|
253
|
+
return this.find(filter, { select, limit: 0 }, undefined, table_rules, localParams)
|
|
254
|
+
.then(async (isValid) => {
|
|
255
|
+
const { filterFields, forcedFilter } = table_rules?.select || {};
|
|
256
|
+
const condition = (await this.prepareWhere({ filter, forcedFilter, filterFields, addKeywords: false, localParams, tableRule: table_rules })).where;
|
|
257
|
+
// let final_filter = getFindFilter(filter, table_rules);
|
|
258
|
+
const pubSubManager = await this.dboBuilder.getPubSubManager();
|
|
259
|
+
return pubSubManager.addSync({
|
|
260
|
+
table_info: this.tableOrViewInfo,
|
|
261
|
+
condition,
|
|
262
|
+
id_fields, synced_field,
|
|
263
|
+
allow_delete,
|
|
264
|
+
socket,
|
|
265
|
+
table_rules,
|
|
266
|
+
filter: { ...filter },
|
|
267
|
+
params: { select }
|
|
268
|
+
}).then(channelName => ({ channelName, id_fields, synced_field }));
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
catch (e) {
|
|
272
|
+
if (localParams && localParams.testRule)
|
|
273
|
+
throw e;
|
|
274
|
+
throw (0, DboBuilder_1.parseError)(e, `dbo.${this.name}.sync()`);
|
|
275
|
+
}
|
|
276
|
+
/*
|
|
277
|
+
REPLICATION
|
|
278
|
+
|
|
279
|
+
1 Sync proccess (NO DELETES ALLOWED):
|
|
280
|
+
|
|
281
|
+
Client sends:
|
|
282
|
+
"sync-request"
|
|
283
|
+
{ min_id, max_id, count, max_synced }
|
|
284
|
+
|
|
285
|
+
Server sends:
|
|
286
|
+
"sync-pull"
|
|
287
|
+
{ from_synced }
|
|
288
|
+
|
|
289
|
+
Client sends:
|
|
290
|
+
"sync-push"
|
|
291
|
+
{ data } -> WHERE synced >= from_synced
|
|
292
|
+
|
|
293
|
+
Server upserts:
|
|
294
|
+
WHERE not exists synced = synced AND id = id
|
|
295
|
+
UNTIL
|
|
296
|
+
|
|
297
|
+
Server sends
|
|
298
|
+
"sync-push"
|
|
299
|
+
{ data } -> WHERE synced >= from_synced
|
|
300
|
+
*/
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
exports.TableHandler = TableHandler;
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import pgPromise from "pg-promise";
|
|
2
|
+
import { AnyObject, asName, DeleteParams, FieldFilter, getKeys, InsertParams, isDefined, isObject, Select, SelectParams, SubscribeParams, UpdateParams } from "prostgles-types";
|
|
3
|
+
import { DboBuilder, Filter, LocalParams, makeErr, parseError, TableHandlers, TableSchema } from "../DboBuilder";
|
|
4
|
+
import { DB } from "../Prostgles";
|
|
5
|
+
import { SyncRule, TableRule, UpdateRule, ValidateRow, ValidateUpdateRow } from "../PublishParser";
|
|
6
|
+
import { omitKeys } from "../PubSubManager";
|
|
7
|
+
import { _delete } from "./delete";
|
|
8
|
+
import { insert } from "./insert";
|
|
9
|
+
import { insertDataParse } from "./insertDataParse";
|
|
10
|
+
import { COMPUTED_FIELDS, FUNCTIONS, SelectItem, SelectItemBuilder } from "./QueryBuilder/QueryBuilder";
|
|
11
|
+
import { update } from "./update";
|
|
12
|
+
import { JoinPaths, ViewHandler } from "./ViewHandler";
|
|
13
|
+
import { parseUpdateRules } from "./parseUpdateRules";
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
type ValidatedParams = {
|
|
17
|
+
row: AnyObject;
|
|
18
|
+
forcedData?: AnyObject;
|
|
19
|
+
allowedFields?: FieldFilter;
|
|
20
|
+
tableRules?: TableRule;
|
|
21
|
+
fixIssues: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class TableHandler extends ViewHandler {
|
|
25
|
+
io_stats: {
|
|
26
|
+
throttle_queries_per_sec: number;
|
|
27
|
+
since: number,
|
|
28
|
+
queries: number,
|
|
29
|
+
batching: string[] | null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
constructor(db: DB, tableOrViewInfo: TableSchema, dboBuilder: DboBuilder, t?: pgPromise.ITask<{}>, dbTX?: TableHandlers, joinPaths?: JoinPaths) {
|
|
33
|
+
super(db, tableOrViewInfo, dboBuilder, t, dbTX, joinPaths);
|
|
34
|
+
|
|
35
|
+
this.remove = this.delete;
|
|
36
|
+
|
|
37
|
+
this.io_stats = {
|
|
38
|
+
since: Date.now(),
|
|
39
|
+
queries: 0,
|
|
40
|
+
throttle_queries_per_sec: 500,
|
|
41
|
+
batching: null
|
|
42
|
+
};
|
|
43
|
+
this.is_view = false;
|
|
44
|
+
this.is_media = dboBuilder.prostgles.isMedia(this.name)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* TO DO: Maybe finished query batching */
|
|
48
|
+
willBatch(query: string) {
|
|
49
|
+
const now = Date.now();
|
|
50
|
+
if (this.io_stats.since < Date.now()) {
|
|
51
|
+
this.io_stats.since = Date.now();
|
|
52
|
+
this.io_stats.queries = 0;
|
|
53
|
+
} else {
|
|
54
|
+
this.io_stats.queries++;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (this.io_stats.queries > this.io_stats.throttle_queries_per_sec) {
|
|
58
|
+
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async subscribe(filter: Filter, params: SubscribeParams, localFunc: (items: AnyObject[]) => any): Promise<{ unsubscribe: () => any }>
|
|
64
|
+
async subscribe(filter: Filter, params: SubscribeParams, localFunc?: (items: AnyObject[]) => any, table_rules?: TableRule, localParams?: LocalParams): Promise<string>
|
|
65
|
+
async subscribe(filter: Filter, params: SubscribeParams = {}, localFunc?: (items: AnyObject[]) => any, table_rules?: TableRule, localParams?: LocalParams):
|
|
66
|
+
Promise<string | { unsubscribe: () => any }> {
|
|
67
|
+
try {
|
|
68
|
+
if (this.is_view) throw "Cannot subscribe to a view";
|
|
69
|
+
if (this.t) throw "subscribe not allowed within transactions";
|
|
70
|
+
if (!localParams && !localFunc) throw " missing data. provide -> localFunc | localParams { socket } ";
|
|
71
|
+
if (localParams && localParams.socket && localFunc) {
|
|
72
|
+
console.error({ localParams, localFunc })
|
|
73
|
+
throw " Cannot have localFunc AND socket ";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const { filterFields, forcedFilter } = table_rules?.select || {},
|
|
77
|
+
filterOpts = await this.prepareWhere({ filter, forcedFilter, addKeywords: false, filterFields, tableAlias: undefined, localParams, tableRule: table_rules }),
|
|
78
|
+
condition = filterOpts.where,
|
|
79
|
+
throttle = params?.throttle || 0,
|
|
80
|
+
selectParams = omitKeys(params || {}, ["throttle"]);
|
|
81
|
+
|
|
82
|
+
// const { subOne = false } = localParams || {};
|
|
83
|
+
const filterSize = JSON.stringify(filter || {}).length;
|
|
84
|
+
if (filterSize * 4 > 2704) {
|
|
85
|
+
throw "filter too big. Might exceed the btree version 4 maximum 2704"
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!localFunc) {
|
|
89
|
+
if (!this.dboBuilder.prostgles.isSuperUser) throw "Subscribe not possible. Must be superuser to add triggers 1856";
|
|
90
|
+
return await this.find(filter, { ...selectParams, limit: 0 }, undefined, table_rules, localParams)
|
|
91
|
+
.then(async isValid => {
|
|
92
|
+
|
|
93
|
+
const { socket } = localParams ?? {};
|
|
94
|
+
const pubSubManager = await this.dboBuilder.getPubSubManager();
|
|
95
|
+
return pubSubManager.addSub({
|
|
96
|
+
table_info: this.tableOrViewInfo,
|
|
97
|
+
socket,
|
|
98
|
+
table_rules,
|
|
99
|
+
condition: condition,
|
|
100
|
+
func: undefined,
|
|
101
|
+
filter: { ...filter },
|
|
102
|
+
params: { ...selectParams },
|
|
103
|
+
socket_id: socket?.id,
|
|
104
|
+
table_name: this.name,
|
|
105
|
+
throttle,
|
|
106
|
+
last_throttled: 0,
|
|
107
|
+
// subOne
|
|
108
|
+
}).then(channelName => ({ channelName }));
|
|
109
|
+
}) as string;
|
|
110
|
+
} else {
|
|
111
|
+
const pubSubManager = await this.dboBuilder.getPubSubManager();
|
|
112
|
+
pubSubManager.addSub({
|
|
113
|
+
table_info: this.tableOrViewInfo,
|
|
114
|
+
socket: undefined,
|
|
115
|
+
table_rules,
|
|
116
|
+
condition,
|
|
117
|
+
func: localFunc,
|
|
118
|
+
filter: { ...filter },
|
|
119
|
+
params: { ...selectParams },
|
|
120
|
+
socket_id: undefined,
|
|
121
|
+
table_name: this.name,
|
|
122
|
+
throttle,
|
|
123
|
+
last_throttled: 0,
|
|
124
|
+
// subOne
|
|
125
|
+
}).then(channelName => ({ channelName }));
|
|
126
|
+
const unsubscribe = async () => {
|
|
127
|
+
const pubSubManager = await this.dboBuilder.getPubSubManager();
|
|
128
|
+
pubSubManager.removeLocalSub(this.name, condition, localFunc)
|
|
129
|
+
};
|
|
130
|
+
let res: { unsubscribe: () => any } = Object.freeze({ unsubscribe })
|
|
131
|
+
return res;
|
|
132
|
+
}
|
|
133
|
+
} catch (e) {
|
|
134
|
+
if (localParams && localParams.testRule) throw e;
|
|
135
|
+
throw parseError(e, `dbo.${this.name}.subscribe()`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/* This should only be called from server */
|
|
140
|
+
subscribeOne(filter: Filter, params: SubscribeParams, localFunc: (item: AnyObject) => any): Promise<{ unsubscribe: () => any }>
|
|
141
|
+
subscribeOne(filter: Filter, params: SubscribeParams, localFunc: (item: AnyObject) => any, table_rules?: TableRule, localParams?: LocalParams): Promise<string>
|
|
142
|
+
subscribeOne(filter: Filter, params: SubscribeParams = {}, localFunc: (item: AnyObject) => any, table_rules?: TableRule, localParams?: LocalParams):
|
|
143
|
+
Promise<string | { unsubscribe: () => any }> {
|
|
144
|
+
let func = localParams ? undefined : (rows: AnyObject[]) => localFunc(rows[0]);
|
|
145
|
+
return this.subscribe(filter, { ...params, limit: 2 }, func, table_rules, localParams);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
async updateBatch(data: [Filter, AnyObject][], params?: UpdateParams, tableRules?: TableRule, localParams?: LocalParams): Promise<any> {
|
|
150
|
+
try {
|
|
151
|
+
const queries = await Promise.all(
|
|
152
|
+
data.map(async ([filter, data]) =>
|
|
153
|
+
await this.update(
|
|
154
|
+
filter,
|
|
155
|
+
data,
|
|
156
|
+
{ ...(params || {}), returning: undefined },
|
|
157
|
+
tableRules,
|
|
158
|
+
{ ...(localParams || {}), returnQuery: true }
|
|
159
|
+
)
|
|
160
|
+
)
|
|
161
|
+
);
|
|
162
|
+
const keys = (data && data.length) ? Object.keys(data[0]) : [];
|
|
163
|
+
return this.db.tx(t => {
|
|
164
|
+
const _queries = queries.map(q => t.none(q as unknown as string))
|
|
165
|
+
return t.batch(_queries)
|
|
166
|
+
}).catch(err => makeErr(err, localParams, this, keys));
|
|
167
|
+
} catch (e) {
|
|
168
|
+
if (localParams && localParams.testRule) throw e;
|
|
169
|
+
throw parseError(e, `dbo.${this.name}.update()`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
parseUpdateRules = parseUpdateRules.bind(this);
|
|
174
|
+
|
|
175
|
+
update = update.bind(this);
|
|
176
|
+
|
|
177
|
+
validateNewData({ row, forcedData, allowedFields, tableRules, fixIssues = false }: ValidatedParams): { data: any; allowedCols: string[] } {
|
|
178
|
+
const synced_field = (tableRules ?? {})?.sync?.synced_field;
|
|
179
|
+
|
|
180
|
+
/* Update synced_field if sync is on and missing */
|
|
181
|
+
if (synced_field && !row[synced_field]) {
|
|
182
|
+
row[synced_field] = Date.now();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
let data = this.prepareFieldValues(row, forcedData, allowedFields, fixIssues);
|
|
186
|
+
const dataKeys = getKeys(data);
|
|
187
|
+
|
|
188
|
+
dataKeys.map(col => {
|
|
189
|
+
this.dboBuilder.prostgles?.tableConfigurator?.checkColVal({ table: this.name, col, value: data[col] });
|
|
190
|
+
const colConfig = this.dboBuilder.prostgles?.tableConfigurator?.getColumnConfig(this.name, col);
|
|
191
|
+
if (colConfig && isObject(colConfig) && "isText" in colConfig && data[col]) {
|
|
192
|
+
if (colConfig.lowerCased) {
|
|
193
|
+
data[col] = data[col].toString().toLowerCase()
|
|
194
|
+
}
|
|
195
|
+
if (colConfig.trimmed) {
|
|
196
|
+
data[col] = data[col].toString().trim()
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
return { data, allowedCols: this.columns.filter(c => dataKeys.includes(c.name)).map(c => c.name) }
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
insertDataParse = insertDataParse;
|
|
205
|
+
async insert(rowOrRows: (AnyObject | AnyObject[]), param2?: InsertParams, param3_unused?: undefined, tableRules?: TableRule, _localParams?: LocalParams): Promise<any | any[] | boolean> {
|
|
206
|
+
return insert.bind(this)(rowOrRows, param2, param3_unused, tableRules, _localParams)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
prepareReturning = async (returning: Select | undefined, allowedFields: string[]): Promise<SelectItem[]> => {
|
|
210
|
+
let result: SelectItem[] = [];
|
|
211
|
+
if (returning) {
|
|
212
|
+
let sBuilder = new SelectItemBuilder({
|
|
213
|
+
allFields: this.column_names.slice(0),
|
|
214
|
+
allowedFields,
|
|
215
|
+
allowedOrderByFields: allowedFields,
|
|
216
|
+
computedFields: COMPUTED_FIELDS,
|
|
217
|
+
functions: FUNCTIONS.filter(f => f.type === "function" && f.singleColArg),
|
|
218
|
+
isView: this.is_view,
|
|
219
|
+
columns: this.columns,
|
|
220
|
+
});
|
|
221
|
+
await sBuilder.parseUserSelect(returning);
|
|
222
|
+
|
|
223
|
+
return sBuilder.select;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return result;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
makeReturnQuery(items?: SelectItem[]) {
|
|
230
|
+
if (items?.length) return " RETURNING " + items.map(s => s.getQuery() + " AS " + asName(s.alias)).join(", ");
|
|
231
|
+
return "";
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async delete(filter?: Filter, params?: DeleteParams, param3_unused?: undefined, table_rules?: TableRule, localParams?: LocalParams): Promise<any> {
|
|
235
|
+
return _delete.bind(this)(filter, params, param3_unused, table_rules, localParams);
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
remove(filter: Filter, params?: UpdateParams, param3_unused?: undefined, tableRules?: TableRule, localParams?: LocalParams) {
|
|
239
|
+
return this.delete(filter, params, param3_unused, tableRules, localParams);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async upsert(filter: Filter, newData: AnyObject, params?: UpdateParams, table_rules?: TableRule, localParams?: LocalParams): Promise<any> {
|
|
243
|
+
try {
|
|
244
|
+
/* Do it within a transaction to ensure consisency */
|
|
245
|
+
if (!this.t) {
|
|
246
|
+
return this.dboBuilder.getTX(dbTX => _upsert(dbTX[this.name] as TableHandler))
|
|
247
|
+
} else {
|
|
248
|
+
return _upsert(this);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async function _upsert(tblH: TableHandler) {
|
|
252
|
+
return tblH.find(filter, { select: "", limit: 1 }, undefined, table_rules, localParams)
|
|
253
|
+
.then(exists => {
|
|
254
|
+
if (exists && exists.length) {
|
|
255
|
+
return tblH.update(filter, newData, params, table_rules, localParams);
|
|
256
|
+
} else {
|
|
257
|
+
return tblH.insert({ ...newData, ...filter }, params, undefined, table_rules, localParams);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
} catch (e) {
|
|
262
|
+
if (localParams && localParams.testRule) throw e;
|
|
263
|
+
throw parseError(e, `dbo.${this.name}.upsert()`);
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
/* External request. Cannot sync from server */
|
|
268
|
+
async sync(filter: Filter, params: SelectParams, param3_unused: undefined, table_rules: TableRule, localParams: LocalParams) {
|
|
269
|
+
if (!localParams) throw "Sync not allowed within the same server code";
|
|
270
|
+
const { socket } = localParams;
|
|
271
|
+
if (!socket) throw "INTERNAL ERROR: socket missing";
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
if (!table_rules || !table_rules.sync || !table_rules.select) throw "INTERNAL ERROR: sync or select rules missing";
|
|
275
|
+
|
|
276
|
+
if (this.t) throw "Sync not allowed within transactions";
|
|
277
|
+
|
|
278
|
+
const ALLOWED_PARAMS = ["select"];
|
|
279
|
+
const invalidParams = Object.keys(params || {}).filter(k => !ALLOWED_PARAMS.includes(k));
|
|
280
|
+
if (invalidParams.length) throw "Invalid or dissallowed params found: " + invalidParams.join(", ");
|
|
281
|
+
|
|
282
|
+
try {
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
let { id_fields, synced_field, allow_delete }: SyncRule = table_rules.sync;
|
|
286
|
+
const syncFields = [...id_fields, synced_field];
|
|
287
|
+
|
|
288
|
+
if (!id_fields || !synced_field) {
|
|
289
|
+
const err = "INTERNAL ERROR: id_fields OR synced_field missing from publish";
|
|
290
|
+
console.error(err);
|
|
291
|
+
throw err;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
id_fields = this.parseFieldFilter(id_fields, false);
|
|
295
|
+
|
|
296
|
+
let allowedSelect = this.parseFieldFilter(table_rules?.select.fields ?? false);
|
|
297
|
+
if (syncFields.find(f => !allowedSelect.includes(f))) {
|
|
298
|
+
throw `INTERNAL ERROR: sync field missing from publish.${this.name}.select.fields`;
|
|
299
|
+
}
|
|
300
|
+
let select = this.getAllowedSelectFields(
|
|
301
|
+
(params || {})?.select || "*",
|
|
302
|
+
allowedSelect,
|
|
303
|
+
false
|
|
304
|
+
);
|
|
305
|
+
if (!select.length) throw "Empty select not allowed";
|
|
306
|
+
|
|
307
|
+
/* Add sync fields if missing */
|
|
308
|
+
syncFields.map(sf => {
|
|
309
|
+
if (!select.includes(sf)) select.push(sf);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
/* Step 1: parse command and params */
|
|
313
|
+
return this.find(filter, { select, limit: 0 }, undefined, table_rules, localParams)
|
|
314
|
+
.then(async isValid => {
|
|
315
|
+
|
|
316
|
+
const { filterFields, forcedFilter } = table_rules?.select || {};
|
|
317
|
+
const condition = (await this.prepareWhere({ filter, forcedFilter, filterFields, addKeywords: false, localParams, tableRule: table_rules })).where;
|
|
318
|
+
|
|
319
|
+
// let final_filter = getFindFilter(filter, table_rules);
|
|
320
|
+
const pubSubManager = await this.dboBuilder.getPubSubManager();
|
|
321
|
+
return pubSubManager.addSync({
|
|
322
|
+
table_info: this.tableOrViewInfo,
|
|
323
|
+
condition,
|
|
324
|
+
id_fields, synced_field,
|
|
325
|
+
allow_delete,
|
|
326
|
+
socket,
|
|
327
|
+
table_rules,
|
|
328
|
+
filter: { ...filter },
|
|
329
|
+
params: { select }
|
|
330
|
+
}).then(channelName => ({ channelName, id_fields, synced_field }));
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
} catch (e) {
|
|
334
|
+
if (localParams && localParams.testRule) throw e;
|
|
335
|
+
throw parseError(e, `dbo.${this.name}.sync()`);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/*
|
|
339
|
+
REPLICATION
|
|
340
|
+
|
|
341
|
+
1 Sync proccess (NO DELETES ALLOWED):
|
|
342
|
+
|
|
343
|
+
Client sends:
|
|
344
|
+
"sync-request"
|
|
345
|
+
{ min_id, max_id, count, max_synced }
|
|
346
|
+
|
|
347
|
+
Server sends:
|
|
348
|
+
"sync-pull"
|
|
349
|
+
{ from_synced }
|
|
350
|
+
|
|
351
|
+
Client sends:
|
|
352
|
+
"sync-push"
|
|
353
|
+
{ data } -> WHERE synced >= from_synced
|
|
354
|
+
|
|
355
|
+
Server upserts:
|
|
356
|
+
WHERE not exists synced = synced AND id = id
|
|
357
|
+
UNTIL
|
|
358
|
+
|
|
359
|
+
Server sends
|
|
360
|
+
"sync-push"
|
|
361
|
+
{ data } -> WHERE synced >= from_synced
|
|
362
|
+
*/
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
}
|