prostgles-server 4.2.41 → 4.2.42
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/DboBuilder/DboBuilder.d.ts +4 -1
- package/dist/DboBuilder/DboBuilder.d.ts.map +1 -1
- package/dist/DboBuilder/DboBuilder.js +7 -18
- package/dist/DboBuilder/DboBuilder.js.map +1 -1
- package/dist/DboBuilder/QueryStreamer.d.ts +1 -1
- package/dist/DboBuilder/QueryStreamer.d.ts.map +1 -1
- package/dist/DboBuilder/QueryStreamer.js +2 -1
- package/dist/DboBuilder/QueryStreamer.js.map +1 -1
- package/dist/DboBuilder/dboBuilderUtils.d.ts +1 -1
- package/dist/DboBuilder/dboBuilderUtils.d.ts.map +1 -1
- package/dist/DboBuilder/dboBuilderUtils.js +3 -3
- package/dist/DboBuilder/dboBuilderUtils.js.map +1 -1
- package/dist/DboBuilder/runSQL.d.ts +0 -7
- package/dist/DboBuilder/runSQL.d.ts.map +1 -1
- package/dist/DboBuilder/runSQL.js +3 -18
- package/dist/DboBuilder/runSQL.js.map +1 -1
- package/dist/PostgresNotifListenManager.d.ts +1 -1
- package/dist/PostgresNotifListenManager.d.ts.map +1 -1
- package/dist/PostgresNotifListenManager.js +1 -1
- package/dist/PostgresNotifListenManager.js.map +1 -1
- package/dist/Prostgles.d.ts +8 -20
- package/dist/Prostgles.d.ts.map +1 -1
- package/dist/Prostgles.js +6 -57
- package/dist/Prostgles.js.map +1 -1
- package/dist/PubSubManager/PubSubManager.d.ts +13 -20
- package/dist/PubSubManager/PubSubManager.d.ts.map +1 -1
- package/dist/PubSubManager/PubSubManager.js +74 -80
- package/dist/PubSubManager/PubSubManager.js.map +1 -1
- package/dist/PubSubManager/addSub.js +0 -59
- package/dist/PubSubManager/addSub.js.map +1 -1
- package/dist/PubSubManager/getInitQuery.d.ts.map +1 -1
- package/dist/PubSubManager/getInitQuery.js +6 -6
- package/dist/PubSubManager/getInitQuery.js.map +1 -1
- package/dist/PubSubManager/initPubSubManager.d.ts.map +1 -1
- package/dist/PubSubManager/initPubSubManager.js +13 -35
- package/dist/PubSubManager/initPubSubManager.js.map +1 -1
- package/dist/PubSubManager/notifListener.d.ts.map +1 -1
- package/dist/PubSubManager/notifListener.js +5 -5
- package/dist/PubSubManager/notifListener.js.map +1 -1
- package/dist/SchemaWatch/SCHEMA_WATCH_QUERIES.d.ts +2 -0
- package/dist/SchemaWatch/SCHEMA_WATCH_QUERIES.d.ts.map +1 -0
- package/dist/SchemaWatch/SCHEMA_WATCH_QUERIES.js +53 -0
- package/dist/SchemaWatch/SCHEMA_WATCH_QUERIES.js.map +1 -0
- package/dist/SchemaWatch/SchemaWatch.d.ts +19 -0
- package/dist/SchemaWatch/SchemaWatch.d.ts.map +1 -0
- package/dist/SchemaWatch/SchemaWatch.js +71 -0
- package/dist/SchemaWatch/SchemaWatch.js.map +1 -0
- package/dist/SchemaWatch/getValidatedWatchSchemaType.d.ts +14 -0
- package/dist/SchemaWatch/getValidatedWatchSchemaType.d.ts.map +1 -0
- package/dist/SchemaWatch/getValidatedWatchSchemaType.js +38 -0
- package/dist/SchemaWatch/getValidatedWatchSchemaType.js.map +1 -0
- package/dist/SchemaWatch/getWatchSchemaTagList.d.ts +3 -0
- package/dist/SchemaWatch/getWatchSchemaTagList.d.ts.map +1 -0
- package/dist/SchemaWatch/getWatchSchemaTagList.js +29 -0
- package/dist/SchemaWatch/getWatchSchemaTagList.js.map +1 -0
- package/dist/SchemaWatch.d.ts +26 -9
- package/dist/SchemaWatch.d.ts.map +1 -1
- package/dist/SchemaWatch.js +116 -25
- package/dist/SchemaWatch.js.map +1 -1
- package/dist/initProstgles.d.ts.map +1 -1
- package/dist/initProstgles.js +3 -2
- package/dist/initProstgles.js.map +1 -1
- package/lib/DboBuilder/DboBuilder.ts +12 -21
- package/lib/DboBuilder/QueryStreamer.ts +2 -2
- package/lib/DboBuilder/dboBuilderUtils.ts +1 -1
- package/lib/DboBuilder/runSQL.ts +1 -18
- package/lib/PostgresNotifListenManager.ts +1 -1
- package/lib/Prostgles.ts +8 -69
- package/lib/PubSubManager/PubSubManager.ts +75 -83
- package/lib/PubSubManager/addSub.ts +0 -63
- package/lib/PubSubManager/getInitQuery.ts +7 -7
- package/lib/PubSubManager/initPubSubManager.ts +16 -41
- package/lib/PubSubManager/notifListener.ts +6 -6
- package/lib/PublishParser/PublishParser.ts +1 -1
- package/lib/SchemaWatch/SchemaWatch.ts +79 -0
- package/lib/SchemaWatch/getValidatedWatchSchemaType.ts +45 -0
- package/lib/SchemaWatch/getWatchSchemaTagList.ts +27 -0
- package/lib/initProstgles.ts +6 -4
- package/package.json +1 -1
- package/tests/server/package-lock.json +1 -1
- package/lib/SchemaWatch.ts +0 -40
- package/lib/SchemaWatchManager.ts +0 -72
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* Licensed under the MIT License. See LICENSE in the project root for license information.
|
|
4
4
|
*--------------------------------------------------------------------------------------------*/
|
|
5
5
|
|
|
6
|
-
import { DBHandlerServer, DboBuilder, PRGLIOSocket, TableInfo, TableOrViewInfo,
|
|
6
|
+
import { DBHandlerServer, DboBuilder, PRGLIOSocket, TableInfo, TableOrViewInfo, getCanExecute } from "../DboBuilder/DboBuilder";
|
|
7
7
|
import { PostgresNotifListenManager } from "../PostgresNotifListenManager";
|
|
8
|
-
import { DB,
|
|
8
|
+
import { DB, getIsSuperUser } from "../Prostgles";
|
|
9
9
|
import { addSync } from "./addSync";
|
|
10
10
|
import { initPubSubManager } from "./initPubSubManager";
|
|
11
11
|
|
|
@@ -122,7 +122,6 @@ export type PubSubManagerOptions = {
|
|
|
122
122
|
dboBuilder: DboBuilder;
|
|
123
123
|
wsChannelNamePrefix?: string;
|
|
124
124
|
pgChannelName?: string;
|
|
125
|
-
onSchemaChange?: (event: { command: string; query: string }) => void;
|
|
126
125
|
}
|
|
127
126
|
|
|
128
127
|
export type Subscription = Pick<SubscriptionParams,
|
|
@@ -147,20 +146,24 @@ export type Subscription = Pick<SubscriptionParams,
|
|
|
147
146
|
}[];
|
|
148
147
|
}
|
|
149
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Used to facilitate table subscribe and sync
|
|
151
|
+
*/
|
|
150
152
|
export class PubSubManager {
|
|
151
153
|
static DELIMITER = '|$prstgls$|' as const;
|
|
152
154
|
|
|
153
155
|
static EXCLUDE_QUERY_FROM_SCHEMA_WATCH_ID = "prostgles internal query that should be excluded from schema watch " as const;
|
|
154
156
|
|
|
155
157
|
public static canCreate = async (db: DB) => {
|
|
156
|
-
const canExecute = await
|
|
157
|
-
const isSuperUs = await
|
|
158
|
+
const canExecute = await getCanExecute(db);
|
|
159
|
+
const isSuperUs = await getIsSuperUser(db);
|
|
158
160
|
return { canExecute, isSuperUs, yes: canExecute && isSuperUs };
|
|
159
161
|
}
|
|
160
162
|
|
|
161
163
|
public static create = async (options: PubSubManagerOptions) => {
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
+
const instance = new PubSubManager(options);
|
|
165
|
+
const result = await initPubSubManager.bind(instance)();
|
|
166
|
+
return result;
|
|
164
167
|
}
|
|
165
168
|
|
|
166
169
|
get db(): DB {
|
|
@@ -177,17 +180,14 @@ export class PubSubManager {
|
|
|
177
180
|
subs: Subscription[] = [];
|
|
178
181
|
syncs: SyncParams[] = [];
|
|
179
182
|
socketChannelPreffix: string;
|
|
180
|
-
onSchemaChange?: ((event: { command: string; query: string }) => void) = undefined;
|
|
181
|
-
|
|
182
183
|
postgresNotifListenManager?: PostgresNotifListenManager;
|
|
183
184
|
|
|
184
185
|
private constructor(options: PubSubManagerOptions) {
|
|
185
|
-
const { wsChannelNamePrefix,
|
|
186
|
+
const { wsChannelNamePrefix, dboBuilder } = options;
|
|
186
187
|
if (!dboBuilder.db || !dboBuilder.dbo) {
|
|
187
188
|
throw 'MISSING: db_pg, db';
|
|
188
189
|
}
|
|
189
190
|
|
|
190
|
-
this.onSchemaChange = onSchemaChange;
|
|
191
191
|
this.dboBuilder = dboBuilder;
|
|
192
192
|
|
|
193
193
|
this.socketChannelPreffix = wsChannelNamePrefix || "_psqlWS_";
|
|
@@ -195,20 +195,6 @@ export class PubSubManager {
|
|
|
195
195
|
log("Created PubSubManager");
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
-
NOTIF_TYPE = {
|
|
199
|
-
data: "data_has_changed",
|
|
200
|
-
data_trigger_change: "data_watch_triggers_have_changed",
|
|
201
|
-
schema: "schema_has_changed"
|
|
202
|
-
} as const;
|
|
203
|
-
NOTIF_CHANNEL = {
|
|
204
|
-
preffix: 'prostgles_' as const,
|
|
205
|
-
getFull: (appID?: string) => {
|
|
206
|
-
const finalAppId = appID ?? this.appID;
|
|
207
|
-
if (!finalAppId) throw "No appID";
|
|
208
|
-
return this.NOTIF_CHANNEL.preffix + finalAppId;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
198
|
/**
|
|
213
199
|
* Used facilitate concurrent prostgles connections to the same database
|
|
214
200
|
*/
|
|
@@ -225,13 +211,10 @@ export class PubSubManager {
|
|
|
225
211
|
}
|
|
226
212
|
this.subs = [];
|
|
227
213
|
this.syncs = [];
|
|
228
|
-
|
|
229
|
-
throw "this.postgresNotifListenManager missing"
|
|
230
|
-
}
|
|
231
|
-
this.postgresNotifListenManager.destroy();
|
|
214
|
+
this.postgresNotifListenManager?.destroy();
|
|
232
215
|
}
|
|
233
216
|
|
|
234
|
-
|
|
217
|
+
getIsDestroyed = () => {
|
|
235
218
|
if (this.destroyed) {
|
|
236
219
|
console.trace("Could not start destroyed instance");
|
|
237
220
|
return false
|
|
@@ -241,13 +224,12 @@ export class PubSubManager {
|
|
|
241
224
|
|
|
242
225
|
appChecking = false;
|
|
243
226
|
checkedListenerTableCond?: string[];
|
|
244
|
-
init = initPubSubManager.bind(this);
|
|
245
227
|
|
|
246
228
|
initialiseEventTriggers = async () => {
|
|
247
229
|
if (!this.appID) throw "prepareTriggers failed: this.appID missing";
|
|
248
230
|
|
|
249
231
|
const { watchSchema } = this.dboBuilder.prostgles.opts;
|
|
250
|
-
if (watchSchema && !(await
|
|
232
|
+
if (watchSchema && !(await getIsSuperUser(this.db))) {
|
|
251
233
|
console.warn("prostgles watchSchema requires superuser db user. Will not watch using event triggers")
|
|
252
234
|
}
|
|
253
235
|
|
|
@@ -299,52 +281,7 @@ export class PubSubManager {
|
|
|
299
281
|
DELETE FROM prostgles.app_triggers
|
|
300
282
|
WHERE app_id NOT IN (SELECT id FROM prostgles.apps);
|
|
301
283
|
|
|
302
|
-
|
|
303
|
-
IF EXISTS (
|
|
304
|
-
SELECT 1 FROM pg_catalog.pg_event_trigger
|
|
305
|
-
WHERE evtname = 'prostgles_schema_watch_trigger'
|
|
306
|
-
) AND is_super_user IS TRUE
|
|
307
|
-
THEN
|
|
308
|
-
DROP EVENT TRIGGER IF EXISTS prostgles_schema_watch_trigger;
|
|
309
|
-
END IF;
|
|
310
|
-
|
|
311
|
-
ev_trg_needed := EXISTS (
|
|
312
|
-
SELECT 1 FROM prostgles.apps
|
|
313
|
-
WHERE watching_schema_tag_names IS NOT NULL
|
|
314
|
-
);
|
|
315
|
-
ev_trg_exists := EXISTS (
|
|
316
|
-
SELECT 1 FROM pg_catalog.pg_event_trigger
|
|
317
|
-
WHERE evtname = ${asValue(DB_OBJ_NAMES.schema_watch_trigger)}
|
|
318
|
-
);
|
|
319
|
-
|
|
320
|
-
/* DROP stale event trigger */
|
|
321
|
-
IF
|
|
322
|
-
is_super_user IS TRUE
|
|
323
|
-
AND ev_trg_needed IS FALSE
|
|
324
|
-
AND ev_trg_exists IS TRUE
|
|
325
|
-
THEN
|
|
326
|
-
|
|
327
|
-
SELECT format(
|
|
328
|
-
$$ DROP EVENT TRIGGER IF EXISTS %I ; $$
|
|
329
|
-
, ${asValue(DB_OBJ_NAMES.schema_watch_trigger)}
|
|
330
|
-
)
|
|
331
|
-
INTO q;
|
|
332
|
-
EXECUTE q;
|
|
333
|
-
|
|
334
|
-
/* CREATE event trigger */
|
|
335
|
-
ELSIF
|
|
336
|
-
is_super_user IS TRUE
|
|
337
|
-
AND ev_trg_needed IS TRUE
|
|
338
|
-
AND ev_trg_exists IS FALSE
|
|
339
|
-
THEN
|
|
340
|
-
|
|
341
|
-
DROP EVENT TRIGGER IF EXISTS ${DB_OBJ_NAMES.schema_watch_trigger};
|
|
342
|
-
CREATE EVENT TRIGGER ${DB_OBJ_NAMES.schema_watch_trigger} ON ddl_command_end
|
|
343
|
-
WHEN TAG IN (\${EVENT_TRIGGER_TAGS:csv})
|
|
344
|
-
EXECUTE PROCEDURE ${DB_OBJ_NAMES.schema_watch_func}();
|
|
345
|
-
|
|
346
|
-
END IF;
|
|
347
|
-
|
|
284
|
+
${SCHEMA_WATCH_EVENT_TRIGGER_QUERY}
|
|
348
285
|
|
|
349
286
|
END
|
|
350
287
|
$do$;
|
|
@@ -367,11 +304,6 @@ export class PubSubManager {
|
|
|
367
304
|
}
|
|
368
305
|
}
|
|
369
306
|
|
|
370
|
-
isReady() {
|
|
371
|
-
if (!this.postgresNotifListenManager) throw "this.postgresNotifListenManager missing";
|
|
372
|
-
return this.postgresNotifListenManager.isListening();
|
|
373
|
-
}
|
|
374
|
-
|
|
375
307
|
getClientSubs(client: Pick<Subscription, "localFuncs" | "socket_id" | "channel_name">): Subscription[] {
|
|
376
308
|
return this.subs.filter(s => {
|
|
377
309
|
return s.channel_name === client.channel_name && (matchesLocalFuncs(client.localFuncs, s.localFuncs) || client.socket_id && s.socket_id === client.socket_id)
|
|
@@ -577,6 +509,66 @@ export class PubSubManager {
|
|
|
577
509
|
}
|
|
578
510
|
}
|
|
579
511
|
|
|
512
|
+
const SCHEMA_WATCH_EVENT_TRIGGER_QUERY = `
|
|
513
|
+
/* DROP the old buggy schema watch trigger */
|
|
514
|
+
IF EXISTS (
|
|
515
|
+
SELECT 1 FROM pg_catalog.pg_event_trigger
|
|
516
|
+
WHERE evtname = 'prostgles_schema_watch_trigger'
|
|
517
|
+
) AND is_super_user IS TRUE
|
|
518
|
+
THEN
|
|
519
|
+
DROP EVENT TRIGGER IF EXISTS prostgles_schema_watch_trigger;
|
|
520
|
+
END IF;
|
|
521
|
+
|
|
522
|
+
ev_trg_needed := EXISTS (
|
|
523
|
+
SELECT 1 FROM prostgles.apps
|
|
524
|
+
WHERE watching_schema_tag_names IS NOT NULL
|
|
525
|
+
);
|
|
526
|
+
ev_trg_exists := EXISTS (
|
|
527
|
+
SELECT 1 FROM pg_catalog.pg_event_trigger
|
|
528
|
+
WHERE evtname = ${asValue(DB_OBJ_NAMES.schema_watch_trigger)}
|
|
529
|
+
);
|
|
530
|
+
|
|
531
|
+
/* DROP stale event trigger */
|
|
532
|
+
IF
|
|
533
|
+
is_super_user IS TRUE
|
|
534
|
+
AND ev_trg_needed IS FALSE
|
|
535
|
+
AND ev_trg_exists IS TRUE
|
|
536
|
+
THEN
|
|
537
|
+
|
|
538
|
+
SELECT format(
|
|
539
|
+
$$ DROP EVENT TRIGGER IF EXISTS %I ; $$
|
|
540
|
+
, ${asValue(DB_OBJ_NAMES.schema_watch_trigger)}
|
|
541
|
+
)
|
|
542
|
+
INTO q;
|
|
543
|
+
EXECUTE q;
|
|
544
|
+
|
|
545
|
+
/* CREATE event trigger */
|
|
546
|
+
ELSIF
|
|
547
|
+
is_super_user IS TRUE
|
|
548
|
+
AND ev_trg_needed IS TRUE
|
|
549
|
+
AND ev_trg_exists IS FALSE
|
|
550
|
+
THEN
|
|
551
|
+
|
|
552
|
+
DROP EVENT TRIGGER IF EXISTS ${DB_OBJ_NAMES.schema_watch_trigger};
|
|
553
|
+
CREATE EVENT TRIGGER ${DB_OBJ_NAMES.schema_watch_trigger} ON ddl_command_end
|
|
554
|
+
WHEN TAG IN (\${EVENT_TRIGGER_TAGS:csv})
|
|
555
|
+
EXECUTE PROCEDURE ${DB_OBJ_NAMES.schema_watch_func}();
|
|
556
|
+
|
|
557
|
+
END IF;
|
|
558
|
+
`
|
|
559
|
+
|
|
560
|
+
export const NOTIF_TYPE = {
|
|
561
|
+
data: "data_has_changed",
|
|
562
|
+
data_trigger_change: "data_watch_triggers_have_changed",
|
|
563
|
+
schema: "schema_has_changed"
|
|
564
|
+
} as const;
|
|
565
|
+
export const NOTIF_CHANNEL = {
|
|
566
|
+
preffix: 'prostgles_' as const,
|
|
567
|
+
getFull: (appID: string | undefined) => {
|
|
568
|
+
if (!appID) throw "No appID";
|
|
569
|
+
return NOTIF_CHANNEL.preffix + appID;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
580
572
|
|
|
581
573
|
export const parseCondition = (condition: string): string => condition && condition.trim().length ? condition : "TRUE"
|
|
582
574
|
|
|
@@ -127,66 +127,3 @@ export async function addSub(this: PubSubManager, subscriptionParams: Omit<AddSu
|
|
|
127
127
|
|
|
128
128
|
return result;
|
|
129
129
|
}
|
|
130
|
-
|
|
131
|
-
// const _upsertSub = async function(
|
|
132
|
-
// this: PubSubManager,
|
|
133
|
-
// newSubData: {
|
|
134
|
-
// table_name: string;
|
|
135
|
-
// condition: string;
|
|
136
|
-
// is_ready: boolean;
|
|
137
|
-
// channel_name: string;
|
|
138
|
-
// viewOptions: SubscriptionParams["viewOptions"];
|
|
139
|
-
// },
|
|
140
|
-
// consumer: Pick<SubscriptionParams, "channel_name" | "socket" | "func">,
|
|
141
|
-
// isReadyOverride: boolean | undefined
|
|
142
|
-
// ){
|
|
143
|
-
// const { table_name, condition: _cond, is_ready = false, viewOptions } = newSubData;
|
|
144
|
-
// const { channel_name, func, socket } = consumer;
|
|
145
|
-
// const condition = parseCondition(_cond);
|
|
146
|
-
// const newSub: SubscriptionParams = {
|
|
147
|
-
// socket,
|
|
148
|
-
// table_name,
|
|
149
|
-
// filter,
|
|
150
|
-
// params,
|
|
151
|
-
// table_rules,
|
|
152
|
-
// channel_name,
|
|
153
|
-
// parentSubParams,
|
|
154
|
-
// func: func ?? undefined,
|
|
155
|
-
// socket_id: socket?.id,
|
|
156
|
-
// throttle: validated_throttle,
|
|
157
|
-
// is_throttling: null,
|
|
158
|
-
// last_throttled: 0,
|
|
159
|
-
// is_ready,
|
|
160
|
-
// };
|
|
161
|
-
|
|
162
|
-
// this.subs[table_name] = this.subs[table_name] ?? {};
|
|
163
|
-
// this.subs[table_name]![condition] = this.subs[table_name]![condition] ?? { subs: [] };
|
|
164
|
-
// this.subs[table_name]![condition]!.subs = this.subs[table_name]![condition]!.subs ?? [];
|
|
165
|
-
|
|
166
|
-
// // console.log("1034 upsertSub", this.subs)
|
|
167
|
-
// const sub_idx = this.subs[table_name]![condition]!.subs.findIndex(s =>
|
|
168
|
-
// s.channel_name === channel_name &&
|
|
169
|
-
// (
|
|
170
|
-
// socket && s.socket_id === socket.id ||
|
|
171
|
-
// func && s.func === func
|
|
172
|
-
// ) &&
|
|
173
|
-
// JSON.stringify(s.viewOptions) === JSON.stringify((subscriptionParams.viewOptions)
|
|
174
|
-
// ));
|
|
175
|
-
// if (sub_idx < 0) {
|
|
176
|
-
// this.subs[table_name]![condition]!.subs.push(newSub);
|
|
177
|
-
// if (socket) {
|
|
178
|
-
// const chnUnsub = channel_name + "unsubscribe";
|
|
179
|
-
// socket.removeAllListeners(chnUnsub);
|
|
180
|
-
// socket.once(chnUnsub, (_data: any, cb: BasicCallback) => {
|
|
181
|
-
// const res = this.onSocketDisconnected({ socket, subChannel: channel_name });
|
|
182
|
-
// cb(null, { res });
|
|
183
|
-
// });
|
|
184
|
-
// }
|
|
185
|
-
// } else {
|
|
186
|
-
// this.subs[table_name]![condition]!.subs[sub_idx] = newSub;
|
|
187
|
-
// }
|
|
188
|
-
|
|
189
|
-
// if (isReadyOverride ?? is_ready) {
|
|
190
|
-
// this.pushSubData(newSub);
|
|
191
|
-
// }
|
|
192
|
-
// };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { asValue, PubSubManager } from "./PubSubManager";
|
|
2
|
+
import { asValue, NOTIF_CHANNEL, NOTIF_TYPE, PubSubManager } from "./PubSubManager";
|
|
3
3
|
const { version } = require("../../package.json");
|
|
4
4
|
|
|
5
5
|
export const DB_OBJ_NAMES = {
|
|
@@ -289,11 +289,11 @@ BEGIN
|
|
|
289
289
|
LOOP
|
|
290
290
|
|
|
291
291
|
PERFORM pg_notify(
|
|
292
|
-
${asValue(
|
|
292
|
+
${asValue(NOTIF_CHANNEL.preffix)} || v_trigger.app_id ,
|
|
293
293
|
LEFT(concat_ws(
|
|
294
294
|
${asValue(PubSubManager.DELIMITER)},
|
|
295
295
|
|
|
296
|
-
${asValue(
|
|
296
|
+
${asValue(NOTIF_TYPE.data)},
|
|
297
297
|
COALESCE(TG_TABLE_NAME, 'MISSING'),
|
|
298
298
|
COALESCE(TG_OP, 'MISSING'),
|
|
299
299
|
CASE WHEN has_errors
|
|
@@ -487,10 +487,10 @@ BEGIN
|
|
|
487
487
|
SELECT * FROM prostgles.apps
|
|
488
488
|
LOOP
|
|
489
489
|
PERFORM pg_notify(
|
|
490
|
-
${asValue(
|
|
490
|
+
${asValue(NOTIF_CHANNEL.preffix)} || app.id,
|
|
491
491
|
LEFT(concat_ws(
|
|
492
492
|
${asValue(PubSubManager.DELIMITER)},
|
|
493
|
-
${asValue(
|
|
493
|
+
${asValue(NOTIF_TYPE.data_trigger_change)},
|
|
494
494
|
json_build_object(
|
|
495
495
|
'TG_OP', TG_OP,
|
|
496
496
|
'duration', (EXTRACT(EPOCH FROM now()) * 1000) - start_time,
|
|
@@ -551,10 +551,10 @@ BEGIN
|
|
|
551
551
|
WHERE tg_tag = ANY(watching_schema_tag_names)
|
|
552
552
|
LOOP
|
|
553
553
|
PERFORM pg_notify(
|
|
554
|
-
${asValue(
|
|
554
|
+
${asValue(NOTIF_CHANNEL.preffix)} || app.id,
|
|
555
555
|
LEFT(concat_ws(
|
|
556
556
|
${asValue(PubSubManager.DELIMITER)},
|
|
557
|
-
${asValue(
|
|
557
|
+
${asValue(NOTIF_TYPE.schema)}, tg_tag , TG_event, curr_query
|
|
558
558
|
), 7999/4)
|
|
559
559
|
);
|
|
560
560
|
END LOOP;
|
|
@@ -1,50 +1,23 @@
|
|
|
1
|
-
import { getKeys, isObject } from "prostgles-types";
|
|
2
1
|
import { PostgresNotifListenManager } from "../PostgresNotifListenManager";
|
|
3
|
-
import {
|
|
2
|
+
import { getWatchSchemaTagList } from "../SchemaWatch/getWatchSchemaTagList";
|
|
3
|
+
import { asValue, log, NOTIF_CHANNEL, PubSubManager } from "./PubSubManager";
|
|
4
4
|
const REALTIME_TRIGGER_CHECK_QUERY = "prostgles-server internal query used to manage realtime triggers" as const;
|
|
5
5
|
import { getInitQuery } from "./getInitQuery";
|
|
6
|
-
import { ProstglesInitOptions } from "../Prostgles";
|
|
7
|
-
import { EVENT_TRIGGER_TAGS } from "../Event_Trigger_Tags";
|
|
8
|
-
|
|
9
|
-
const getWatchSchemaTagList = (watchSchema: ProstglesInitOptions["watchSchema"]) => {
|
|
10
|
-
if(!watchSchema) return undefined;
|
|
11
|
-
|
|
12
|
-
if(watchSchema === "*"){
|
|
13
|
-
return EVENT_TRIGGER_TAGS.slice(0);
|
|
14
|
-
}
|
|
15
|
-
if (isObject(watchSchema) && typeof watchSchema !== "function"){
|
|
16
|
-
const watchSchemaKeys = getKeys(watchSchema);
|
|
17
|
-
const isInclusive = Object.values(watchSchema).every(v => v);
|
|
18
|
-
return EVENT_TRIGGER_TAGS
|
|
19
|
-
.slice(0)
|
|
20
|
-
.filter(v => {
|
|
21
|
-
const matches = watchSchemaKeys.includes(v);
|
|
22
|
-
return isInclusive? matches : !matches;
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const coreTags: typeof EVENT_TRIGGER_TAGS[number][] = [
|
|
27
|
-
'COMMENT', 'CREATE TABLE', 'ALTER TABLE', 'DROP TABLE', 'CREATE VIEW',
|
|
28
|
-
'DROP VIEW', 'ALTER VIEW', 'CREATE TABLE AS', 'SELECT INTO', 'CREATE POLICY'
|
|
29
|
-
];
|
|
30
|
-
return coreTags;
|
|
31
|
-
}
|
|
32
6
|
|
|
33
7
|
export async function initPubSubManager(this: PubSubManager): Promise<PubSubManager | undefined> {
|
|
34
|
-
if (!this.
|
|
8
|
+
if (!this.getIsDestroyed()) return undefined;
|
|
35
9
|
|
|
36
10
|
let tries = 5;
|
|
37
11
|
try {
|
|
38
12
|
|
|
39
13
|
const initQuery = await getInitQuery.bind(this)()
|
|
40
14
|
await this.db.any(initQuery);
|
|
41
|
-
if (!this.
|
|
42
|
-
|
|
15
|
+
if (!this.getIsDestroyed()) return;
|
|
43
16
|
|
|
44
17
|
/* Prepare App id */
|
|
45
18
|
if (!this.appID) {
|
|
46
19
|
const check_frequency_ms = this.appCheckFrequencyMS;
|
|
47
|
-
const watching_schema_tag_names = this.
|
|
20
|
+
const watching_schema_tag_names = this.dboBuilder.prostgles.schemaWatch?.type.watchType !== "NONE" ? getWatchSchemaTagList(this.dboBuilder.prostgles.opts.watchSchema) : null;
|
|
48
21
|
const raw = await this.db.one(
|
|
49
22
|
"INSERT INTO prostgles.apps (check_frequency_ms, watching_schema_tag_names, application_name) \
|
|
50
23
|
VALUES($1, $2, current_setting('application_name')) \
|
|
@@ -65,6 +38,14 @@ export async function initPubSubManager(this: PubSubManager): Promise<PubSubMana
|
|
|
65
38
|
this.appChecking = true;
|
|
66
39
|
|
|
67
40
|
const listeners = this.getActiveListeners();
|
|
41
|
+
const updateCurrentlyUsedTriggersQuery = !listeners.length? "" : `
|
|
42
|
+
UPDATE prostgles.app_triggers
|
|
43
|
+
SET last_used = CASE WHEN (table_name, condition) IN (
|
|
44
|
+
${listeners.map(l => ` ( ${asValue(l.table_name)}, ${asValue(l.condition)} ) `).join(", ")}
|
|
45
|
+
) THEN NOW() ELSE last_used END
|
|
46
|
+
WHERE app_id = ${asValue(this.appID)};
|
|
47
|
+
`;
|
|
48
|
+
|
|
68
49
|
const checkedListenerTableCond = listeners.map(l => `${l.table_name}.${l.condition}`);
|
|
69
50
|
let dataTriggerCheckQuery = "";
|
|
70
51
|
if(this.checkedListenerTableCond?.sort().join() !== checkedListenerTableCond.sort().join()){
|
|
@@ -79,13 +60,7 @@ export async function initPubSubManager(this: PubSubManager): Promise<PubSubMana
|
|
|
79
60
|
--LOCK TABLE prostgles.app_triggers IN ACCESS EXCLUSIVE MODE;
|
|
80
61
|
|
|
81
62
|
/* UPDATE currently used triggers */
|
|
82
|
-
${
|
|
83
|
-
UPDATE prostgles.app_triggers
|
|
84
|
-
SET last_used = CASE WHEN (table_name, condition) IN (
|
|
85
|
-
${listeners.map(l => ` ( ${asValue(l.table_name)}, ${asValue(l.condition)} ) `).join(", ")}
|
|
86
|
-
) THEN NOW() ELSE last_used END
|
|
87
|
-
WHERE app_id = ${asValue(this.appID)};
|
|
88
|
-
`}
|
|
63
|
+
${updateCurrentlyUsedTriggersQuery}
|
|
89
64
|
|
|
90
65
|
/* DELETE stale triggers for current app. Other triggers will be deleted on app startup */
|
|
91
66
|
DELETE FROM prostgles.app_triggers
|
|
@@ -175,9 +150,9 @@ export async function initPubSubManager(this: PubSubManager): Promise<PubSubMana
|
|
|
175
150
|
}
|
|
176
151
|
}
|
|
177
152
|
|
|
178
|
-
this.postgresNotifListenManager = new PostgresNotifListenManager(this.db, this.notifListener,
|
|
153
|
+
this.postgresNotifListenManager = new PostgresNotifListenManager(this.db, this.notifListener, NOTIF_CHANNEL.getFull(this.appID));
|
|
179
154
|
|
|
180
|
-
await this.initialiseEventTriggers()
|
|
155
|
+
await this.initialiseEventTriggers();
|
|
181
156
|
|
|
182
157
|
return this;
|
|
183
158
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { log, pickKeys, PubSubManager } from "./PubSubManager";
|
|
1
|
+
import { log, NOTIF_TYPE, pickKeys, PubSubManager } from "./PubSubManager";
|
|
2
2
|
|
|
3
3
|
/* Relay relevant data to relevant subscriptions */
|
|
4
4
|
export async function notifListener(this: PubSubManager, data: { payload: string }) {
|
|
@@ -14,25 +14,25 @@ export async function notifListener(this: PubSubManager, data: { payload: string
|
|
|
14
14
|
|
|
15
15
|
log(str);
|
|
16
16
|
|
|
17
|
-
if (notifType ===
|
|
17
|
+
if (notifType === NOTIF_TYPE.schema) {
|
|
18
18
|
|
|
19
|
-
if (this.onSchemaChange) {
|
|
19
|
+
if (this.dboBuilder.prostgles.schemaWatch?.onSchemaChange) {
|
|
20
20
|
const [_, command, _event_type, query] = dataArr;
|
|
21
21
|
await this.dboBuilder.prostgles.opts.onLog?.({ type: "debug", command: "schemaChangeNotif", duration: 0, data: { command, query } });
|
|
22
22
|
|
|
23
23
|
if (query && command) {
|
|
24
|
-
this.onSchemaChange({ command, query })
|
|
24
|
+
this.dboBuilder.prostgles.schemaWatch.onSchemaChange({ command, query })
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
return;
|
|
29
|
-
} else if(notifType ===
|
|
29
|
+
} else if(notifType === NOTIF_TYPE.data_trigger_change) {
|
|
30
30
|
|
|
31
31
|
await this.refreshTriggers();
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
if (notifType !==
|
|
35
|
+
if (notifType !== NOTIF_TYPE.data) {
|
|
36
36
|
console.error("Unexpected notif type: ", notifType);
|
|
37
37
|
return;
|
|
38
38
|
}
|
|
@@ -6,7 +6,7 @@ import { getFileTableRules } from "./getFileTableRules";
|
|
|
6
6
|
import { getSchemaFromPublish } from "./getSchemaFromPublish";
|
|
7
7
|
import { getTableRulesWithoutFileTable } from "./getTableRulesWithoutFileTable";
|
|
8
8
|
import { DboTable, DboTableCommand, ParsedPublishTable, PublishMethods, PublishObject, PublishParams, RULE_TO_METHODS, TableRule } from "./publishTypesAndUtils";
|
|
9
|
-
import { VoidFunction } from "../SchemaWatch";
|
|
9
|
+
import { VoidFunction } from "../SchemaWatch/SchemaWatch";
|
|
10
10
|
|
|
11
11
|
export class PublishParser {
|
|
12
12
|
publish: any;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { DboBuilder } from "../DboBuilder/DboBuilder";
|
|
2
|
+
import { EVENT_TRIGGER_TAGS } from "../Event_Trigger_Tags";
|
|
3
|
+
import { OnSchemaChangeCallback } from "../Prostgles";
|
|
4
|
+
import { PubSubManager, log } from "../PubSubManager/PubSubManager";
|
|
5
|
+
import { ValidatedWatchSchemaType, getValidatedWatchSchemaType } from "./getValidatedWatchSchemaType";
|
|
6
|
+
|
|
7
|
+
export type VoidFunction = () => void;
|
|
8
|
+
|
|
9
|
+
export class SchemaWatch {
|
|
10
|
+
|
|
11
|
+
dboBuilder: DboBuilder;
|
|
12
|
+
type: ValidatedWatchSchemaType;
|
|
13
|
+
private constructor(dboBuilder: DboBuilder){
|
|
14
|
+
this.dboBuilder = dboBuilder;
|
|
15
|
+
this.type = getValidatedWatchSchemaType(dboBuilder);
|
|
16
|
+
if(this.type.watchType === "NONE") {
|
|
17
|
+
this.onSchemaChange = undefined;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static create = async (dboBuilder: DboBuilder) => {
|
|
22
|
+
const instance = new SchemaWatch(dboBuilder);
|
|
23
|
+
if(instance.type.watchType === "DDL_trigger") {
|
|
24
|
+
await dboBuilder.getPubSubManager()
|
|
25
|
+
}
|
|
26
|
+
return instance;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
onSchemaChange: OnSchemaChangeCallback | undefined = async (event) => {
|
|
30
|
+
if(this.type.watchType === "NONE") return;
|
|
31
|
+
|
|
32
|
+
const { watchSchema, onReady, tsGeneratedTypesDir } = this.dboBuilder.prostgles.opts;
|
|
33
|
+
if (watchSchema && this.dboBuilder.prostgles.loaded) {
|
|
34
|
+
log("Schema changed");
|
|
35
|
+
const { query, command } = event;
|
|
36
|
+
if (typeof query === "string" && query.includes(PubSubManager.EXCLUDE_QUERY_FROM_SCHEMA_WATCH_ID)) {
|
|
37
|
+
log("Schema change event excluded from triggers due to EXCLUDE_QUERY_FROM_SCHEMA_WATCH_ID");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (typeof watchSchema === "function") {
|
|
42
|
+
/* Only call the provided func */
|
|
43
|
+
watchSchema(event);
|
|
44
|
+
|
|
45
|
+
} else if (watchSchema === "hotReloadMode") {
|
|
46
|
+
if (tsGeneratedTypesDir) {
|
|
47
|
+
/* Hot reload integration. Will only touch tsGeneratedTypesDir */
|
|
48
|
+
console.log("watchSchema: Re-writing TS schema");
|
|
49
|
+
|
|
50
|
+
await this.dboBuilder.prostgles.refreshDBO();
|
|
51
|
+
this.dboBuilder.prostgles.writeDBSchema(true);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
} else if (watchSchema) {
|
|
55
|
+
/* Full re-init. Sockets must reconnect */
|
|
56
|
+
console.log("watchSchema: Full re-initialisation", { query })
|
|
57
|
+
this.dboBuilder.prostgles.init(onReady as any, { type: "schema change", query, command });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Fallback for watchSchema in case of not a superuser (cannot add db event listener)
|
|
66
|
+
*/
|
|
67
|
+
export const watchSchemaFallback = async function(this: DboBuilder, { queryWithoutRLS, command }: { queryWithoutRLS: string; command: string; }){
|
|
68
|
+
const SCHEMA_ALTERING_COMMANDS = EVENT_TRIGGER_TAGS;// ["CREATE", "ALTER", "DROP", "REVOKE", "GRANT"];
|
|
69
|
+
const isNotPickedUpByDDLTrigger = ["REVOKE", "GRANT"].includes(command);
|
|
70
|
+
const { watchSchema, watchSchemaType } = this.prostgles?.opts || {};
|
|
71
|
+
if (
|
|
72
|
+
watchSchema &&
|
|
73
|
+
(!this.prostgles.isSuperUser || watchSchemaType === "prostgles_queries" || isNotPickedUpByDDLTrigger)
|
|
74
|
+
) {
|
|
75
|
+
if (SCHEMA_ALTERING_COMMANDS.includes(command as any)) {
|
|
76
|
+
this.prostgles.schemaWatch?.onSchemaChange?.({ command, query: queryWithoutRLS })
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { DboBuilder } from "../DboBuilder/DboBuilder";
|
|
2
|
+
import { OnSchemaChangeCallback } from "../Prostgles";
|
|
3
|
+
|
|
4
|
+
export type ValidatedWatchSchemaType =
|
|
5
|
+
| { watchType: "NONE" }
|
|
6
|
+
| { watchType: "DDL_trigger"; onChange?: OnSchemaChangeCallback; }
|
|
7
|
+
| { watchType: "prostgles_queries"; onChange?: OnSchemaChangeCallback; isFallbackFromDDL: boolean; }
|
|
8
|
+
|
|
9
|
+
export const getValidatedWatchSchemaType = (dboBuilder: DboBuilder): ValidatedWatchSchemaType => {
|
|
10
|
+
const {watchSchema, watchSchemaType, tsGeneratedTypesDir} = dboBuilder.prostgles.opts;
|
|
11
|
+
if(!watchSchema) return { watchType: "NONE" };
|
|
12
|
+
|
|
13
|
+
if (watchSchema === "hotReloadMode" && !tsGeneratedTypesDir) {
|
|
14
|
+
throw "tsGeneratedTypesDir option is needed for watchSchema: hotReloadMode to work ";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const onChange = typeof watchSchema === "function"? watchSchema : undefined;
|
|
18
|
+
|
|
19
|
+
if(watchSchemaType === "DDL_trigger" || !watchSchemaType){
|
|
20
|
+
if(!dboBuilder.prostgles.isSuperUser){
|
|
21
|
+
|
|
22
|
+
if(watchSchemaType === "DDL_trigger"){
|
|
23
|
+
console.error(`watchSchemaType "DDL_trigger" cannot be used because db user is not a superuser. Will fallback to watchSchemaType "prostgles_queries" `)
|
|
24
|
+
} else {
|
|
25
|
+
console.warn(`watchSchema fallback to watchSchemaType "prostgles_queries" due to non-superuser`)
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
watchType: "prostgles_queries",
|
|
29
|
+
onChange,
|
|
30
|
+
isFallbackFromDDL: true
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
watchType: "DDL_trigger",
|
|
36
|
+
onChange
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
watchType: watchSchemaType,
|
|
42
|
+
isFallbackFromDDL: false,
|
|
43
|
+
onChange
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { getKeys, isObject } from "prostgles-types";
|
|
2
|
+
import { EVENT_TRIGGER_TAGS } from "../Event_Trigger_Tags";
|
|
3
|
+
import { ProstglesInitOptions } from "../Prostgles";
|
|
4
|
+
|
|
5
|
+
export const getWatchSchemaTagList = (watchSchema: ProstglesInitOptions["watchSchema"]) => {
|
|
6
|
+
if(!watchSchema) return undefined;
|
|
7
|
+
|
|
8
|
+
if(watchSchema === "*"){
|
|
9
|
+
return EVENT_TRIGGER_TAGS.slice(0);
|
|
10
|
+
}
|
|
11
|
+
if (isObject(watchSchema) && typeof watchSchema !== "function"){
|
|
12
|
+
const watchSchemaKeys = getKeys(watchSchema);
|
|
13
|
+
const isInclusive = Object.values(watchSchema).every(v => v);
|
|
14
|
+
return EVENT_TRIGGER_TAGS
|
|
15
|
+
.slice(0)
|
|
16
|
+
.filter(v => {
|
|
17
|
+
const matches = watchSchemaKeys.includes(v);
|
|
18
|
+
return isInclusive? matches : !matches;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const coreTags: typeof EVENT_TRIGGER_TAGS[number][] = [
|
|
23
|
+
'COMMENT', 'CREATE TABLE', 'ALTER TABLE', 'DROP TABLE', 'CREATE VIEW',
|
|
24
|
+
'DROP VIEW', 'ALTER VIEW', 'CREATE TABLE AS', 'SELECT INTO', 'CREATE POLICY'
|
|
25
|
+
];
|
|
26
|
+
return coreTags;
|
|
27
|
+
}
|