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,609 @@
|
|
|
1
|
+
/*---------------------------------------------------------------------------------------------
|
|
2
|
+
* Copyright (c) Stefan L. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License. See LICENSE in the project root for license information.
|
|
4
|
+
*--------------------------------------------------------------------------------------------*/
|
|
5
|
+
|
|
6
|
+
import * as crypto from "crypto";
|
|
7
|
+
import { DBHandlerServer, DboBuilder, PRGLIOSocket, TableInfo, TableOrViewInfo } from "../DboBuilder/DboBuilder";
|
|
8
|
+
import { PostgresNotifListenManager } from "../PostgresNotifListenManager";
|
|
9
|
+
import { DB, getIsSuperUser } from "../Prostgles";
|
|
10
|
+
import { addSync } from "./addSync";
|
|
11
|
+
import { initPubSubManager } from "./initPubSubManager";
|
|
12
|
+
|
|
13
|
+
import * as pgPromise from 'pg-promise';
|
|
14
|
+
import pg from 'pg-promise/typescript/pg-subset';
|
|
15
|
+
|
|
16
|
+
import { AnyObject, FieldFilter, SelectParams, SubscribeParams, WAL } from "prostgles-types";
|
|
17
|
+
|
|
18
|
+
import { find, pickKeys, tryCatch } from "prostgles-types/dist/util";
|
|
19
|
+
import { LocalFuncs, getOnDataFunc, matchesLocalFuncs } from "../DboBuilder/ViewHandler/subscribe";
|
|
20
|
+
import { EVENT_TRIGGER_TAGS } from "../Event_Trigger_Tags";
|
|
21
|
+
import { EventTypes } from "../Logging";
|
|
22
|
+
import { TableRule } from "../PublishParser/PublishParser";
|
|
23
|
+
import { syncData } from "../SyncReplication";
|
|
24
|
+
import { addSub } from "./addSub";
|
|
25
|
+
import { DB_OBJ_NAMES } from "./getPubSubManagerInitQuery";
|
|
26
|
+
import { notifListener } from "./notifListener";
|
|
27
|
+
import { DELETE_DISCONNECTED_APPS_QUERY } from "./orphanTriggerCheck";
|
|
28
|
+
import { pushSubData } from "./pushSubData";
|
|
29
|
+
|
|
30
|
+
type PGP = pgPromise.IMain<{}, pg.IClient>;
|
|
31
|
+
const pgp: PGP = pgPromise({
|
|
32
|
+
});
|
|
33
|
+
export const asValue = (v: any) => pgp.as.format("$1", [v]);
|
|
34
|
+
export const DEFAULT_SYNC_BATCH_SIZE = 50;
|
|
35
|
+
|
|
36
|
+
export const log = (...args: any[]) => {
|
|
37
|
+
if (process.env.TEST_TYPE) {
|
|
38
|
+
console.log(...args)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type BasicCallback = (err?: any, res?: any) => void
|
|
43
|
+
|
|
44
|
+
export type SyncParams = {
|
|
45
|
+
socket_id: string;
|
|
46
|
+
sid: string | undefined;
|
|
47
|
+
channel_name: string;
|
|
48
|
+
table_name: string;
|
|
49
|
+
table_rules?: TableRule;
|
|
50
|
+
synced_field: string;
|
|
51
|
+
allow_delete: boolean;
|
|
52
|
+
id_fields: string[];
|
|
53
|
+
batch_size: number;
|
|
54
|
+
filter: object;
|
|
55
|
+
params: {
|
|
56
|
+
select: FieldFilter
|
|
57
|
+
};
|
|
58
|
+
condition: string;
|
|
59
|
+
wal?: WAL,
|
|
60
|
+
throttle?: number;
|
|
61
|
+
lr?: AnyObject;
|
|
62
|
+
last_synced: number;
|
|
63
|
+
is_syncing: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type AddSyncParams = {
|
|
67
|
+
socket: PRGLIOSocket;
|
|
68
|
+
table_info: TableInfo;
|
|
69
|
+
table_rules: TableRule;
|
|
70
|
+
synced_field: string;
|
|
71
|
+
allow_delete?: boolean;
|
|
72
|
+
id_fields: string[];
|
|
73
|
+
filter: object;
|
|
74
|
+
params: {
|
|
75
|
+
select: FieldFilter
|
|
76
|
+
};
|
|
77
|
+
condition: string;
|
|
78
|
+
throttle?: number;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export type ViewSubscriptionOptions = ({
|
|
82
|
+
type: "view";
|
|
83
|
+
viewName: string;
|
|
84
|
+
definition: string;
|
|
85
|
+
} | {
|
|
86
|
+
type: "table";
|
|
87
|
+
viewName?: undefined;
|
|
88
|
+
definition?: undefined;
|
|
89
|
+
}) & {
|
|
90
|
+
relatedTables: {
|
|
91
|
+
tableName: string;
|
|
92
|
+
tableNameEscaped: string;
|
|
93
|
+
condition: string;
|
|
94
|
+
}[];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export type SubscriptionParams = Pick<SubscribeParams, "throttle" | "throttleOpts"> & {
|
|
98
|
+
socket_id?: string;
|
|
99
|
+
channel_name: string;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* If this is a view then an array with all related tables will be
|
|
103
|
+
* */
|
|
104
|
+
viewOptions?: ViewSubscriptionOptions;
|
|
105
|
+
parentSubParams: Omit<SubscriptionParams, "parentSubParams"> | undefined;
|
|
106
|
+
|
|
107
|
+
table_info: TableOrViewInfo;
|
|
108
|
+
|
|
109
|
+
/* Used as input */
|
|
110
|
+
table_rules?: TableRule;
|
|
111
|
+
filter: object;
|
|
112
|
+
params: SelectParams;
|
|
113
|
+
|
|
114
|
+
localFuncs?: LocalFuncs;
|
|
115
|
+
socket: PRGLIOSocket | undefined;
|
|
116
|
+
|
|
117
|
+
last_throttled: number;
|
|
118
|
+
is_throttling?: any;
|
|
119
|
+
is_ready?: boolean;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export type PubSubManagerOptions = {
|
|
123
|
+
dboBuilder: DboBuilder;
|
|
124
|
+
wsChannelNamePrefix?: string;
|
|
125
|
+
pgChannelName?: string;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export type Subscription = Pick<SubscriptionParams,
|
|
129
|
+
| "throttle"
|
|
130
|
+
| "is_throttling"
|
|
131
|
+
| "last_throttled"
|
|
132
|
+
| "throttleOpts"
|
|
133
|
+
| "channel_name"
|
|
134
|
+
| "is_ready"
|
|
135
|
+
| "localFuncs"
|
|
136
|
+
| "socket"
|
|
137
|
+
| "socket_id"
|
|
138
|
+
| "table_info"
|
|
139
|
+
| "filter"
|
|
140
|
+
| "params"
|
|
141
|
+
| "table_rules"
|
|
142
|
+
> & {
|
|
143
|
+
triggers: {
|
|
144
|
+
table_name: string;
|
|
145
|
+
condition: string;
|
|
146
|
+
is_related: boolean;
|
|
147
|
+
}[];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Used to facilitate table subscribe and sync
|
|
152
|
+
*/
|
|
153
|
+
export class PubSubManager {
|
|
154
|
+
static DELIMITER = '|$prstgls$|' as const;
|
|
155
|
+
|
|
156
|
+
static EXCLUDE_QUERY_FROM_SCHEMA_WATCH_ID = "prostgles internal query that should be excluded from schema watch " as const;
|
|
157
|
+
|
|
158
|
+
public static create = async (options: PubSubManagerOptions) => {
|
|
159
|
+
const instance = new PubSubManager(options);
|
|
160
|
+
const result = await initPubSubManager.bind(instance)();
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
appInfoWasInserted = false;
|
|
165
|
+
get appId() {
|
|
166
|
+
return this.dboBuilder.prostgles.appId;
|
|
167
|
+
}
|
|
168
|
+
get db(): DB {
|
|
169
|
+
return this.dboBuilder.db;
|
|
170
|
+
}
|
|
171
|
+
get dbo(): DBHandlerServer {
|
|
172
|
+
return this.dboBuilder.dbo;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
dboBuilder: DboBuilder;
|
|
176
|
+
_triggers?: Record<string, string[]>;
|
|
177
|
+
sockets: AnyObject = {};
|
|
178
|
+
|
|
179
|
+
subs: Subscription[] = [];
|
|
180
|
+
syncs: SyncParams[] = [];
|
|
181
|
+
socketChannelPreffix: string;
|
|
182
|
+
postgresNotifListenManager?: PostgresNotifListenManager;
|
|
183
|
+
|
|
184
|
+
private constructor(options: PubSubManagerOptions) {
|
|
185
|
+
const { wsChannelNamePrefix, dboBuilder } = options;
|
|
186
|
+
if (!dboBuilder.db || !dboBuilder.dbo) {
|
|
187
|
+
throw 'MISSING: db_pg, db';
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
this.dboBuilder = dboBuilder;
|
|
191
|
+
|
|
192
|
+
this.socketChannelPreffix = wsChannelNamePrefix || "_psqlWS_";
|
|
193
|
+
|
|
194
|
+
log("Created PubSubManager");
|
|
195
|
+
}
|
|
196
|
+
appCheckFrequencyMS = 10 * 1000;
|
|
197
|
+
appCheck?: ReturnType<typeof setInterval>;
|
|
198
|
+
|
|
199
|
+
destroyed = false;
|
|
200
|
+
destroy = () => {
|
|
201
|
+
this.destroyed = true;
|
|
202
|
+
if (this.appCheck) {
|
|
203
|
+
clearInterval(this.appCheck);
|
|
204
|
+
}
|
|
205
|
+
this.subs = [];
|
|
206
|
+
this.syncs = [];
|
|
207
|
+
this.postgresNotifListenManager?.destroy();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
getIsDestroyed = () => {
|
|
211
|
+
if (this.destroyed) {
|
|
212
|
+
console.trace("Could not start destroyed instance");
|
|
213
|
+
return false
|
|
214
|
+
}
|
|
215
|
+
return true
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
appChecking = false;
|
|
219
|
+
checkedListenerTableCond?: string[];
|
|
220
|
+
|
|
221
|
+
initialiseEventTriggers = async () => {
|
|
222
|
+
const { watchSchema } = this.dboBuilder.prostgles.opts;
|
|
223
|
+
if (watchSchema && !(await getIsSuperUser(this.db))) {
|
|
224
|
+
console.warn("prostgles watchSchema requires superuser db user. Will not watch using event triggers")
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
/** We use these names because they include schema where necessary */
|
|
229
|
+
const allTableNames = Object.keys(this.dbo).filter(k => this.dbo[k]?.tableOrViewInfo);
|
|
230
|
+
const tableFilterQuery = allTableNames.length ? `OR table_name NOT IN (${allTableNames.map(tblName => asValue(tblName)).join(", ")})` : "";
|
|
231
|
+
const query = pgp.as.format(`
|
|
232
|
+
BEGIN;-- ISOLATION LEVEL SERIALIZABLE;
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* ${PubSubManager.EXCLUDE_QUERY_FROM_SCHEMA_WATCH_ID}
|
|
236
|
+
* Drop stale triggers
|
|
237
|
+
* */
|
|
238
|
+
DO
|
|
239
|
+
$do$
|
|
240
|
+
DECLARE trg RECORD;
|
|
241
|
+
q TEXT;
|
|
242
|
+
ev_trg_needed BOOLEAN := FALSE;
|
|
243
|
+
ev_trg_exists BOOLEAN := FALSE;
|
|
244
|
+
is_super_user BOOLEAN := FALSE;
|
|
245
|
+
BEGIN
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Delete disconnected app records, this will delete related triggers
|
|
249
|
+
* */
|
|
250
|
+
${DELETE_DISCONNECTED_APPS_QUERY};
|
|
251
|
+
|
|
252
|
+
DELETE FROM prostgles.app_triggers
|
|
253
|
+
WHERE app_id NOT IN (SELECT id FROM prostgles.apps)
|
|
254
|
+
${tableFilterQuery}
|
|
255
|
+
;
|
|
256
|
+
|
|
257
|
+
/** IS THIS STILL NEEDED? Delete existing triggers without locking
|
|
258
|
+
*/
|
|
259
|
+
LOCK TABLE prostgles.app_triggers IN ACCESS EXCLUSIVE MODE;
|
|
260
|
+
EXECUTE format(
|
|
261
|
+
$q$
|
|
262
|
+
|
|
263
|
+
CREATE TEMP TABLE %1$I AS --ON COMMIT DROP AS
|
|
264
|
+
SELECT * FROM prostgles.app_triggers;
|
|
265
|
+
|
|
266
|
+
DELETE FROM prostgles.app_triggers;
|
|
267
|
+
|
|
268
|
+
INSERT INTO prostgles.app_triggers
|
|
269
|
+
SELECT * FROM %1$I;
|
|
270
|
+
|
|
271
|
+
DROP TABLE IF EXISTS %1$I;
|
|
272
|
+
$q$,
|
|
273
|
+
${asValue('triggers_' + this.appId)}
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
${SCHEMA_WATCH_EVENT_TRIGGER_QUERY}
|
|
277
|
+
|
|
278
|
+
END
|
|
279
|
+
$do$;
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
COMMIT;
|
|
283
|
+
`, { EVENT_TRIGGER_TAGS });
|
|
284
|
+
|
|
285
|
+
await this.db.tx(t => t.any(query))
|
|
286
|
+
.catch(e => {
|
|
287
|
+
console.error("prepareTriggers failed: ", e);
|
|
288
|
+
throw e;
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
return true;
|
|
292
|
+
|
|
293
|
+
} catch (e) {
|
|
294
|
+
console.error("prepareTriggers failed: ", e);
|
|
295
|
+
throw e;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
getClientSubs({ channel_name, localFuncs, socket_id }: Pick<Subscription, "localFuncs" | "socket_id" | "channel_name">): Subscription[] {
|
|
300
|
+
return this.subs.filter(s => {
|
|
301
|
+
return s.channel_name === channel_name &&
|
|
302
|
+
(matchesLocalFuncs(localFuncs, s.localFuncs) || socket_id && s.socket_id === socket_id)
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
getTriggerSubs(table_name: string, condition: string): Subscription[] {
|
|
307
|
+
const subs = this.subs.filter(s => find(s.triggers, { table_name, condition }));
|
|
308
|
+
return subs;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
removeLocalSub(channelName: string, localFuncs: LocalFuncs) {
|
|
312
|
+
const matchingSubIdx = this.subs.findIndex(s =>
|
|
313
|
+
s.channel_name === channelName &&
|
|
314
|
+
getOnDataFunc(localFuncs) === getOnDataFunc(s.localFuncs)
|
|
315
|
+
);
|
|
316
|
+
if (matchingSubIdx > -1) {
|
|
317
|
+
this.subs.splice(matchingSubIdx, 1);
|
|
318
|
+
} else {
|
|
319
|
+
console.error("Could not unsubscribe. Subscription might not have initialised yet", { channelName })
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
getSyncs(table_name: string, condition: string) {
|
|
324
|
+
return (this.syncs || [])
|
|
325
|
+
.filter((s: SyncParams) => s.table_name === table_name && s.condition === condition);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
notifListener = notifListener.bind(this);
|
|
329
|
+
|
|
330
|
+
getSubData = async (sub: Subscription): Promise<
|
|
331
|
+
{ data: any[]; err?: undefined; } |
|
|
332
|
+
{ data?: undefined; err: any; }
|
|
333
|
+
> => {
|
|
334
|
+
const { table_info, filter, params, table_rules } = sub; //, subOne = false
|
|
335
|
+
const { name: table_name } = table_info;
|
|
336
|
+
|
|
337
|
+
if (!this.dbo?.[table_name]?.find) {
|
|
338
|
+
throw new Error(`this.dbo.${table_name}.find undefined`);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
try {
|
|
342
|
+
const data = await this.dbo?.[table_name]!.find!(filter, params, undefined, table_rules)
|
|
343
|
+
return { data };
|
|
344
|
+
} catch(err){
|
|
345
|
+
return { err };
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
pushSubData = pushSubData.bind(this);
|
|
350
|
+
|
|
351
|
+
upsertSocket(socket: PRGLIOSocket | undefined) {
|
|
352
|
+
if (socket && !this.sockets[socket.id]) {
|
|
353
|
+
this.sockets[socket.id] = socket;
|
|
354
|
+
socket.on("disconnect", () => {
|
|
355
|
+
|
|
356
|
+
this.subs = this.subs.filter(s => {
|
|
357
|
+
return !(s.socket && s.socket.id === socket.id);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
this.syncs = this.syncs.filter(s => {
|
|
361
|
+
return !(s.socket_id && s.socket_id === socket.id);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
delete this.sockets[socket.id];
|
|
365
|
+
|
|
366
|
+
this._log({
|
|
367
|
+
type: "sync",
|
|
368
|
+
command: "upsertSocket.disconnect",
|
|
369
|
+
tableName: "",
|
|
370
|
+
duration: 0,
|
|
371
|
+
sid: this.dboBuilder.prostgles.authHandler?.getSIDNoError({ socket }),
|
|
372
|
+
socketId: socket.id,
|
|
373
|
+
connectedSocketIds: this.connectedSocketIds,
|
|
374
|
+
remainingSubs: JSON.stringify(this.subs.map(s => ({ tableName: s.table_info.name, triggers: s.triggers }))),
|
|
375
|
+
remainingSyncs: JSON.stringify(this.syncs.map(s => pickKeys(s, ["table_name", "condition"]))),
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
return "ok";
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
get connectedSocketIds() {
|
|
384
|
+
return this.dboBuilder.prostgles.connectedSockets.map(s => s.id);
|
|
385
|
+
}
|
|
386
|
+
_log = (params: EventTypes.Sync) => {
|
|
387
|
+
return this.dboBuilder.prostgles.opts.onLog?.({ ...params });
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
syncTimeout?: ReturnType<typeof setTimeout>;
|
|
391
|
+
syncData = syncData.bind(this);
|
|
392
|
+
|
|
393
|
+
addSync = addSync.bind(this);
|
|
394
|
+
|
|
395
|
+
addSub = addSub.bind(this);
|
|
396
|
+
|
|
397
|
+
getActiveListeners = (): { table_name: string; condition: string }[] => {
|
|
398
|
+
const activeListeners: { table_name: string; condition: string }[] = [];
|
|
399
|
+
const upsert = (t: string, c: string) => {
|
|
400
|
+
if (!activeListeners.find(r => r.table_name === t && r.condition === c)) {
|
|
401
|
+
activeListeners.push({ table_name: t, condition: c });
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
(this.syncs ?? []).map(s => {
|
|
405
|
+
upsert(s.table_name, s.condition)
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
this.subs.forEach(s => {
|
|
409
|
+
s.triggers.forEach(trg => {
|
|
410
|
+
upsert(trg.table_name, trg.condition);
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
return activeListeners;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Sync triggers with database
|
|
419
|
+
* */
|
|
420
|
+
refreshTriggers = async () => {
|
|
421
|
+
|
|
422
|
+
const triggers: {
|
|
423
|
+
table_name: string;
|
|
424
|
+
condition: string;
|
|
425
|
+
}[] = await this.db.any(`
|
|
426
|
+
SELECT *
|
|
427
|
+
FROM prostgles.v_triggers
|
|
428
|
+
WHERE app_id = $1
|
|
429
|
+
ORDER BY table_name, condition
|
|
430
|
+
`, [this.dboBuilder.prostgles.appId]
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
this._triggers = {};
|
|
434
|
+
triggers.map(t => {
|
|
435
|
+
this._triggers ??= {};
|
|
436
|
+
this._triggers[t.table_name] ??= [];
|
|
437
|
+
if (!this._triggers[t.table_name]?.includes(t.condition)) {
|
|
438
|
+
this._triggers[t.table_name]?.push(t.condition)
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
addingTrigger: any;
|
|
444
|
+
addTriggerPool?: Record<string, string[]> = undefined;
|
|
445
|
+
async addTrigger(params: { table_name: string; condition: string; }, viewOptions: ViewSubscriptionOptions | undefined, socket: PRGLIOSocket | undefined) {
|
|
446
|
+
|
|
447
|
+
const addedTrigger = await tryCatch(async () => {
|
|
448
|
+
|
|
449
|
+
const { table_name } = { ...params }
|
|
450
|
+
let { condition } = { ...params }
|
|
451
|
+
if (!table_name) throw "MISSING table_name";
|
|
452
|
+
|
|
453
|
+
if (!condition || !condition.trim().length) {
|
|
454
|
+
condition = "TRUE";
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (this.dbo[table_name]?.tableOrViewInfo?.isHyperTable) {
|
|
458
|
+
throw "Triggers do not work on timescaledb hypertables due to bug:\nhttps://github.com/timescale/timescaledb/issues/1084"
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const trgVals = {
|
|
462
|
+
tbl: asValue(table_name),
|
|
463
|
+
cond: asValue(condition),
|
|
464
|
+
condHash: asValue(
|
|
465
|
+
crypto.createHash('md5').update(condition).digest('hex')
|
|
466
|
+
)
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
await this.db.tx(t => t.any(`
|
|
470
|
+
BEGIN WORK;
|
|
471
|
+
/* ${PubSubManager.EXCLUDE_QUERY_FROM_SCHEMA_WATCH_ID} */
|
|
472
|
+
/* why is this lock level needed? */
|
|
473
|
+
--LOCK TABLE prostgles.app_triggers IN ACCESS EXCLUSIVE MODE;
|
|
474
|
+
|
|
475
|
+
/** app_triggers is not refreshed when tables are dropped */
|
|
476
|
+
DELETE FROM prostgles.app_triggers at
|
|
477
|
+
WHERE app_id = ${asValue(this.appId)}
|
|
478
|
+
AND NOT EXISTS (
|
|
479
|
+
SELECT 1
|
|
480
|
+
FROM pg_catalog.pg_trigger t
|
|
481
|
+
WHERE tgname like format('prostgles_triggers_%s_', at.table_name) || '%'
|
|
482
|
+
AND tgenabled = 'O'
|
|
483
|
+
);
|
|
484
|
+
|
|
485
|
+
INSERT INTO prostgles.app_triggers (
|
|
486
|
+
table_name,
|
|
487
|
+
condition,
|
|
488
|
+
condition_hash,
|
|
489
|
+
app_id,
|
|
490
|
+
related_view_name,
|
|
491
|
+
related_view_def
|
|
492
|
+
)
|
|
493
|
+
VALUES (
|
|
494
|
+
${trgVals.tbl},
|
|
495
|
+
${trgVals.cond},
|
|
496
|
+
${trgVals.condHash},
|
|
497
|
+
${asValue(this.appId)},
|
|
498
|
+
${asValue(viewOptions?.viewName ?? null)},
|
|
499
|
+
${asValue(viewOptions?.definition ?? null)}
|
|
500
|
+
)
|
|
501
|
+
ON CONFLICT DO NOTHING;
|
|
502
|
+
|
|
503
|
+
COMMIT WORK;
|
|
504
|
+
`));
|
|
505
|
+
|
|
506
|
+
/** This might be redundant due to trigger on app_triggers */
|
|
507
|
+
await this.refreshTriggers();
|
|
508
|
+
|
|
509
|
+
return trgVals;
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
await this._log({
|
|
513
|
+
type: "sync",
|
|
514
|
+
command: "addTrigger",
|
|
515
|
+
condition: addedTrigger.cond ?? params.condition,
|
|
516
|
+
duration: addedTrigger.duration,
|
|
517
|
+
socketId: socket?.id,
|
|
518
|
+
state: !addedTrigger.tbl? "fail" : "ok",
|
|
519
|
+
error: addedTrigger.error,
|
|
520
|
+
sid: this.dboBuilder.prostgles.authHandler?.getSIDNoError({ socket }),
|
|
521
|
+
tableName: addedTrigger.tbl ?? params.table_name,
|
|
522
|
+
connectedSocketIds: this.dboBuilder.prostgles.connectedSockets.map(s => s.id),
|
|
523
|
+
localParams: { socket }
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
if(addedTrigger.error) throw addedTrigger.error;
|
|
527
|
+
|
|
528
|
+
return addedTrigger;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const SCHEMA_WATCH_EVENT_TRIGGER_QUERY = `
|
|
533
|
+
|
|
534
|
+
is_super_user := EXISTS (select 1 from pg_user where usename = CURRENT_USER AND usesuper IS TRUE);
|
|
535
|
+
|
|
536
|
+
/* DROP the old buggy schema watch trigger */
|
|
537
|
+
IF EXISTS (
|
|
538
|
+
SELECT 1 FROM pg_catalog.pg_event_trigger
|
|
539
|
+
WHERE evtname = 'prostgles_schema_watch_trigger'
|
|
540
|
+
) AND is_super_user IS TRUE
|
|
541
|
+
THEN
|
|
542
|
+
DROP EVENT TRIGGER IF EXISTS prostgles_schema_watch_trigger;
|
|
543
|
+
END IF;
|
|
544
|
+
|
|
545
|
+
ev_trg_needed := EXISTS (
|
|
546
|
+
SELECT 1 FROM prostgles.apps
|
|
547
|
+
WHERE watching_schema_tag_names IS NOT NULL
|
|
548
|
+
);
|
|
549
|
+
ev_trg_exists := EXISTS (
|
|
550
|
+
SELECT 1 FROM pg_catalog.pg_event_trigger
|
|
551
|
+
WHERE evtname = ${asValue(DB_OBJ_NAMES.schema_watch_trigger)}
|
|
552
|
+
);
|
|
553
|
+
|
|
554
|
+
/* DROP stale event trigger */
|
|
555
|
+
IF
|
|
556
|
+
is_super_user IS TRUE
|
|
557
|
+
AND ev_trg_needed IS FALSE
|
|
558
|
+
AND ev_trg_exists IS TRUE
|
|
559
|
+
THEN
|
|
560
|
+
|
|
561
|
+
SELECT format(
|
|
562
|
+
$$
|
|
563
|
+
DROP EVENT TRIGGER IF EXISTS %I ;
|
|
564
|
+
DROP EVENT TRIGGER IF EXISTS %I ;
|
|
565
|
+
$$
|
|
566
|
+
, ${asValue(DB_OBJ_NAMES.schema_watch_trigger)}
|
|
567
|
+
, ${asValue(DB_OBJ_NAMES.schema_watch_trigger_drop)}
|
|
568
|
+
)
|
|
569
|
+
INTO q;
|
|
570
|
+
EXECUTE q;
|
|
571
|
+
|
|
572
|
+
/* CREATE event trigger */
|
|
573
|
+
ELSIF
|
|
574
|
+
is_super_user IS TRUE
|
|
575
|
+
AND ev_trg_needed IS TRUE
|
|
576
|
+
AND ev_trg_exists IS FALSE
|
|
577
|
+
THEN
|
|
578
|
+
|
|
579
|
+
DROP EVENT TRIGGER IF EXISTS ${DB_OBJ_NAMES.schema_watch_trigger};
|
|
580
|
+
CREATE EVENT TRIGGER ${DB_OBJ_NAMES.schema_watch_trigger}
|
|
581
|
+
ON ddl_command_end
|
|
582
|
+
WHEN TAG IN (\${EVENT_TRIGGER_TAGS:csv})
|
|
583
|
+
EXECUTE PROCEDURE ${DB_OBJ_NAMES.schema_watch_func}();
|
|
584
|
+
|
|
585
|
+
DROP EVENT TRIGGER IF EXISTS ${DB_OBJ_NAMES.schema_watch_trigger_drop};
|
|
586
|
+
CREATE EVENT TRIGGER ${DB_OBJ_NAMES.schema_watch_trigger_drop}
|
|
587
|
+
ON sql_drop
|
|
588
|
+
--WHEN TAG IN (\${EVENT_TRIGGER_TAGS:csv})
|
|
589
|
+
EXECUTE PROCEDURE ${DB_OBJ_NAMES.schema_watch_func}();
|
|
590
|
+
|
|
591
|
+
END IF;
|
|
592
|
+
`
|
|
593
|
+
|
|
594
|
+
export const NOTIF_TYPE = {
|
|
595
|
+
data: "data_has_changed",
|
|
596
|
+
data_trigger_change: "data_watch_triggers_have_changed",
|
|
597
|
+
schema: "schema_has_changed"
|
|
598
|
+
} as const;
|
|
599
|
+
export const NOTIF_CHANNEL = {
|
|
600
|
+
preffix: 'prostgles_' as const,
|
|
601
|
+
getFull: (appID: string | undefined) => {
|
|
602
|
+
if (!appID) throw "No appID";
|
|
603
|
+
return NOTIF_CHANNEL.preffix + appID;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
export const parseCondition = (condition: string): string => condition && condition.trim().length ? condition : "TRUE"
|
|
608
|
+
|
|
609
|
+
export { omitKeys, pickKeys } from "prostgles-types";
|