prostgles-server 3.0.64 → 3.0.66
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/AuthHandler.d.ts +11 -11
- package/dist/AuthHandler.d.ts.map +1 -1
- package/dist/DBSchemaBuilder.d.ts +3 -3
- package/dist/DBSchemaBuilder.d.ts.map +1 -1
- package/dist/DboBuilder/QueryBuilder/Functions.d.ts +3 -3
- package/dist/DboBuilder/QueryBuilder/Functions.d.ts.map +1 -1
- package/dist/DboBuilder/QueryBuilder/QueryBuilder.d.ts +3 -3
- package/dist/DboBuilder/QueryBuilder/QueryBuilder.d.ts.map +1 -1
- package/dist/DboBuilder/TableHandler.d.ts +1 -1
- package/dist/DboBuilder/TableHandler.d.ts.map +1 -1
- package/dist/DboBuilder/ViewHandler.d.ts +5 -4
- package/dist/DboBuilder/ViewHandler.d.ts.map +1 -1
- package/dist/DboBuilder/ViewHandler.js +7 -134
- package/dist/DboBuilder/ViewHandler.js.map +1 -1
- package/dist/DboBuilder/delete.js +1 -1
- package/dist/DboBuilder/delete.js.map +1 -1
- package/dist/DboBuilder/insert.js +1 -1
- package/dist/DboBuilder/insert.js.map +1 -1
- package/dist/DboBuilder/insertDataParse.js +1 -1
- package/dist/DboBuilder/insertDataParse.js.map +1 -1
- package/dist/DboBuilder/runSQL.js +1 -1
- package/dist/DboBuilder/runSQL.js.map +1 -1
- package/dist/DboBuilder/subscribe.d.ts +11 -0
- package/dist/DboBuilder/subscribe.d.ts.map +1 -0
- package/dist/DboBuilder/subscribe.js +190 -0
- package/dist/DboBuilder/subscribe.js.map +1 -0
- package/dist/DboBuilder/update.js +1 -1
- package/dist/DboBuilder/update.js.map +1 -1
- package/dist/DboBuilder.d.ts +24 -24
- package/dist/DboBuilder.d.ts.map +1 -1
- package/dist/DboBuilder.js +1 -1
- package/dist/DboBuilder.js.map +1 -1
- package/dist/FileManager.d.ts +6 -6
- package/dist/FileManager.d.ts.map +1 -1
- package/dist/FileManager.js +17 -17
- package/dist/FileManager.js.map +1 -1
- package/dist/Filtering.d.ts +1 -1
- package/dist/Filtering.d.ts.map +1 -1
- package/dist/PostgresNotifListenManager.d.ts +1 -1
- package/dist/PostgresNotifListenManager.d.ts.map +1 -1
- package/dist/Prostgles.d.ts +14 -14
- package/dist/Prostgles.d.ts.map +1 -1
- package/dist/Prostgles.js +12 -12
- package/dist/Prostgles.js.map +1 -1
- package/{lib → dist/PubSubManager}/PubSubManager.d.ts +24 -19
- package/dist/PubSubManager/PubSubManager.d.ts.map +1 -0
- package/dist/PubSubManager/PubSubManager.js +770 -0
- package/dist/PubSubManager/PubSubManager.js.map +1 -0
- package/dist/PubSubManager/initPubSubManager.d.ts +3 -0
- package/dist/PubSubManager/initPubSubManager.d.ts.map +1 -0
- package/dist/PubSubManager/initPubSubManager.js +616 -0
- package/dist/PubSubManager/initPubSubManager.js.map +1 -0
- package/dist/PublishParser.d.ts +32 -32
- package/dist/PublishParser.d.ts.map +1 -1
- package/dist/PublishParser.js +1 -1
- package/dist/PublishParser.js.map +1 -1
- package/dist/SchemaWatch.d.ts +1 -1
- package/dist/SchemaWatch.d.ts.map +1 -1
- package/dist/SyncReplication.d.ts +6 -6
- package/dist/SyncReplication.d.ts.map +1 -1
- package/dist/SyncReplication.js +1 -1
- package/dist/SyncReplication.js.map +1 -1
- package/dist/TableConfig.d.ts +21 -21
- package/dist/TableConfig.d.ts.map +1 -1
- package/dist/TableConfig.js +13 -13
- package/dist/TableConfig.js.map +1 -1
- package/dist/shortestPath.d.ts +1 -1
- package/dist/shortestPath.d.ts.map +1 -1
- package/dist/validation.d.ts +9 -9
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +1 -1
- package/dist/validation.js.map +1 -1
- package/lib/DboBuilder/ViewHandler.d.ts +4 -3
- package/lib/DboBuilder/ViewHandler.d.ts.map +1 -1
- package/lib/DboBuilder/ViewHandler.js +7 -134
- package/lib/DboBuilder/ViewHandler.ts +15 -164
- package/lib/DboBuilder/delete.js +1 -1
- package/lib/DboBuilder/delete.ts +1 -1
- package/lib/DboBuilder/insert.js +1 -1
- package/lib/DboBuilder/insert.ts +1 -1
- package/lib/DboBuilder/insertDataParse.js +1 -1
- package/lib/DboBuilder/insertDataParse.ts +1 -1
- package/lib/DboBuilder/runSQL.js +1 -1
- package/lib/DboBuilder/runSQL.ts +1 -1
- package/lib/DboBuilder/subscribe.d.ts +11 -0
- package/lib/DboBuilder/subscribe.d.ts.map +1 -0
- package/lib/DboBuilder/subscribe.js +189 -0
- package/lib/DboBuilder/subscribe.ts +230 -0
- package/lib/DboBuilder/update.js +1 -1
- package/lib/DboBuilder/update.ts +1 -1
- package/lib/DboBuilder.d.ts +1 -1
- package/lib/DboBuilder.d.ts.map +1 -1
- package/lib/DboBuilder.js +1 -1
- package/lib/DboBuilder.ts +1 -1
- package/lib/Prostgles.js +1 -1
- package/lib/Prostgles.ts +1 -1
- package/{dist → lib/PubSubManager}/PubSubManager.d.ts +19 -14
- package/lib/PubSubManager/PubSubManager.d.ts.map +1 -0
- package/lib/PubSubManager/PubSubManager.js +792 -0
- package/lib/PubSubManager/PubSubManager.ts +1016 -0
- package/lib/PubSubManager/initPubSubManager.d.ts +3 -0
- package/lib/PubSubManager/initPubSubManager.d.ts.map +1 -0
- package/lib/PubSubManager/initPubSubManager.js +615 -0
- package/lib/PubSubManager/initPubSubManager.ts +630 -0
- package/lib/PublishParser.js +1 -1
- package/lib/PublishParser.ts +1 -1
- package/lib/SyncReplication.d.ts +1 -1
- package/lib/SyncReplication.d.ts.map +1 -1
- package/lib/SyncReplication.js +1 -1
- package/lib/SyncReplication.ts +1 -1
- package/lib/TableConfig.js +1 -1
- package/lib/TableConfig.ts +1 -1
- package/lib/validation.js +1 -1
- package/lib/validation.ts +1 -1
- package/package.json +5 -4
- package/tests/client/PID.txt +1 -1
- package/tests/server/package-lock.json +7 -5
- package/dist/PubSubManager.d.ts.map +0 -1
- package/dist/PubSubManager.js +0 -1398
- package/dist/PubSubManager.js.map +0 -1
- package/lib/PubSubManager.d.ts.map +0 -1
- package/lib/PubSubManager.js +0 -1420
- package/lib/PubSubManager.ts +0 -1655
|
@@ -0,0 +1,1016 @@
|
|
|
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 { PostgresNotifListenManager } from "../PostgresNotifListenManager";
|
|
7
|
+
import { get } from "../utils";
|
|
8
|
+
import { TableOrViewInfo, TableInfo, DBHandlerServer, DboBuilder, PRGLIOSocket, canEXECUTE } from "../DboBuilder";
|
|
9
|
+
import { DB, isSuperUser } from "../Prostgles";
|
|
10
|
+
import { initPubSubManager } from "./initPubSubManager";
|
|
11
|
+
|
|
12
|
+
import * as Bluebird from "bluebird";
|
|
13
|
+
import * as pgPromise from 'pg-promise';
|
|
14
|
+
import pg from 'pg-promise/typescript/pg-subset';
|
|
15
|
+
|
|
16
|
+
import { SelectParams, FieldFilter, asName, WAL, isEmpty, AnyObject, getKeys } from "prostgles-types";
|
|
17
|
+
|
|
18
|
+
import { ClientExpressData, syncData } from "../SyncReplication";
|
|
19
|
+
import { TableRule } from "../PublishParser";
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
type PGP = pgPromise.IMain<{}, pg.IClient>;
|
|
23
|
+
let pgp: PGP = pgPromise({
|
|
24
|
+
promiseLib: Bluebird
|
|
25
|
+
});
|
|
26
|
+
export const asValue = (v: any) => pgp.as.format("$1", [v]);
|
|
27
|
+
export const DEFAULT_SYNC_BATCH_SIZE = 50;
|
|
28
|
+
|
|
29
|
+
export const log = (...args: any[]) => {
|
|
30
|
+
if (process.env.TEST_TYPE) {
|
|
31
|
+
console.log(...args)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type BasicCallback = (err?: any, res?: any) => void
|
|
36
|
+
|
|
37
|
+
export type SyncParams = {
|
|
38
|
+
socket_id: string;
|
|
39
|
+
channel_name: string;
|
|
40
|
+
table_name: string;
|
|
41
|
+
table_rules?: TableRule;
|
|
42
|
+
synced_field: string;
|
|
43
|
+
allow_delete: boolean;
|
|
44
|
+
id_fields: string[];
|
|
45
|
+
batch_size: number;
|
|
46
|
+
filter: object;
|
|
47
|
+
params: {
|
|
48
|
+
select: FieldFilter
|
|
49
|
+
};
|
|
50
|
+
condition: string;
|
|
51
|
+
wal?: WAL,
|
|
52
|
+
throttle?: number;
|
|
53
|
+
lr?: AnyObject;
|
|
54
|
+
last_synced: number;
|
|
55
|
+
is_syncing: boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type AddSyncParams = {
|
|
59
|
+
socket: any;
|
|
60
|
+
table_info: TableInfo;
|
|
61
|
+
table_rules: TableRule;
|
|
62
|
+
synced_field: string;
|
|
63
|
+
allow_delete?: boolean;
|
|
64
|
+
id_fields: string[];
|
|
65
|
+
filter: object;
|
|
66
|
+
params: {
|
|
67
|
+
select: FieldFilter
|
|
68
|
+
};
|
|
69
|
+
condition: string;
|
|
70
|
+
throttle?: number;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export type ViewSubscriptionOptions = {
|
|
74
|
+
viewName: string;
|
|
75
|
+
definition: string;
|
|
76
|
+
relatedTables: {
|
|
77
|
+
tableName: string;
|
|
78
|
+
tableNameEscaped: string;
|
|
79
|
+
condition: string;
|
|
80
|
+
}[];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
type SubscriptionParams = {
|
|
84
|
+
socket_id?: string;
|
|
85
|
+
channel_name: string;
|
|
86
|
+
table_name: string;
|
|
87
|
+
socket: PRGLIOSocket | undefined;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* If this is a view then an array with all related tables will be
|
|
91
|
+
* */
|
|
92
|
+
viewOptions?: ViewSubscriptionOptions;
|
|
93
|
+
parentSubParams: Omit<SubscriptionParams, "parentSubParams"> | undefined;
|
|
94
|
+
|
|
95
|
+
table_info: TableOrViewInfo;
|
|
96
|
+
table_rules?: TableRule;
|
|
97
|
+
filter: object;
|
|
98
|
+
params: SelectParams;
|
|
99
|
+
func?: (data: any) => any;
|
|
100
|
+
throttle?: number;
|
|
101
|
+
last_throttled: number;
|
|
102
|
+
is_throttling?: any;
|
|
103
|
+
is_ready?: boolean;
|
|
104
|
+
// subOne?: boolean;
|
|
105
|
+
}
|
|
106
|
+
type AddSubscriptionParams = SubscriptionParams & {
|
|
107
|
+
condition: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export type PubSubManagerOptions = {
|
|
111
|
+
dboBuilder: DboBuilder;
|
|
112
|
+
// db: DB;
|
|
113
|
+
// dbo: DBHandlerServer;
|
|
114
|
+
wsChannelNamePrefix?: string;
|
|
115
|
+
pgChannelName?: string;
|
|
116
|
+
onSchemaChange?: (event: { command: string; query: string }) => void;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export class PubSubManager {
|
|
120
|
+
static DELIMITER = '|$prstgls$|';
|
|
121
|
+
|
|
122
|
+
dboBuilder: DboBuilder;
|
|
123
|
+
get db(): DB {
|
|
124
|
+
return this.dboBuilder.db;
|
|
125
|
+
}
|
|
126
|
+
get dbo(): DBHandlerServer {
|
|
127
|
+
return this.dboBuilder.dbo;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
_triggers?: Record<string, string[]>;
|
|
131
|
+
sockets: any;
|
|
132
|
+
subs: { [ke: string]: { [ke: string]: { subs: SubscriptionParams[] } } };
|
|
133
|
+
syncs: SyncParams[];
|
|
134
|
+
socketChannelPreffix: string;
|
|
135
|
+
onSchemaChange?: ((event: { command: string; query: string }) => void) = undefined;
|
|
136
|
+
|
|
137
|
+
postgresNotifListenManager?: PostgresNotifListenManager;
|
|
138
|
+
|
|
139
|
+
private constructor(options: PubSubManagerOptions) {
|
|
140
|
+
const { wsChannelNamePrefix, pgChannelName, onSchemaChange, dboBuilder } = options;
|
|
141
|
+
if (!dboBuilder.db || !dboBuilder.dbo) {
|
|
142
|
+
throw 'MISSING: db_pg, db';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
this.onSchemaChange = onSchemaChange;
|
|
146
|
+
this.dboBuilder = dboBuilder;
|
|
147
|
+
|
|
148
|
+
this.sockets = {};
|
|
149
|
+
this.subs = {};
|
|
150
|
+
this.syncs = [];
|
|
151
|
+
this.socketChannelPreffix = wsChannelNamePrefix || "_psqlWS_";
|
|
152
|
+
|
|
153
|
+
log("Created PubSubManager");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
NOTIF_TYPE = {
|
|
157
|
+
data: "data_has_changed",
|
|
158
|
+
schema: "schema_has_changed"
|
|
159
|
+
}
|
|
160
|
+
NOTIF_CHANNEL = {
|
|
161
|
+
preffix: 'prostgles_',
|
|
162
|
+
getFull: (appID?: string) => {
|
|
163
|
+
const finalAppId = appID ?? this.appID;
|
|
164
|
+
if (!finalAppId) throw "No appID";
|
|
165
|
+
return this.NOTIF_CHANNEL.preffix + finalAppId;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
appID?: string;
|
|
170
|
+
|
|
171
|
+
appCheckFrequencyMS = 10 * 1000;
|
|
172
|
+
appCheck?: ReturnType<typeof setInterval>;
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
// ,datname
|
|
177
|
+
// ,usename
|
|
178
|
+
// ,client_hostname
|
|
179
|
+
// ,client_port
|
|
180
|
+
// ,backend_start
|
|
181
|
+
// ,query_start
|
|
182
|
+
// ,query
|
|
183
|
+
// ,state
|
|
184
|
+
|
|
185
|
+
// console.log(await _db.any(`
|
|
186
|
+
// SELECT pid, application_name, state
|
|
187
|
+
// FROM pg_stat_activity
|
|
188
|
+
// WHERE application_name IS NOT NULL AND application_name != '' -- state = 'active';
|
|
189
|
+
// `))
|
|
190
|
+
|
|
191
|
+
public static canCreate = async (db: DB) => {
|
|
192
|
+
|
|
193
|
+
const canExecute = await canEXECUTE(db);
|
|
194
|
+
const isSuperUs = await isSuperUser(db);
|
|
195
|
+
return { canExecute, isSuperUs, yes: canExecute && isSuperUs };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
public static create = async (options: PubSubManagerOptions) => {
|
|
199
|
+
const res = new PubSubManager(options);
|
|
200
|
+
return await res.init();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
destroyed = false;
|
|
204
|
+
destroy = () => {
|
|
205
|
+
this.destroyed = true;
|
|
206
|
+
if (this.appCheck) {
|
|
207
|
+
clearInterval(this.appCheck);
|
|
208
|
+
}
|
|
209
|
+
this.onSocketDisconnected();
|
|
210
|
+
// if(this.postgresNotifListenManager){
|
|
211
|
+
// this.postgresNotifListenManager.stopListening();
|
|
212
|
+
// }
|
|
213
|
+
if (!this.postgresNotifListenManager) throw "this.postgresNotifListenManager missing"
|
|
214
|
+
this.postgresNotifListenManager.destroy();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
canContinue = () => {
|
|
218
|
+
if (this.destroyed) {
|
|
219
|
+
console.trace("Could not start destroyed instance");
|
|
220
|
+
return false
|
|
221
|
+
}
|
|
222
|
+
return true
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
appChecking = false;
|
|
226
|
+
init = initPubSubManager.bind(this);
|
|
227
|
+
|
|
228
|
+
DB_OBJ_NAMES = {
|
|
229
|
+
trigger_add_remove_func: "prostgles.trigger_add_remove_func",
|
|
230
|
+
data_watch_func: "prostgles.prostgles_trigger_function",
|
|
231
|
+
schema_watch_func: "prostgles.schema_watch_func",
|
|
232
|
+
schema_watch_trigger: "prostgles_schema_watch_trigger_new"
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
static SCHEMA_ALTERING_QUERIES = ['CREATE TABLE', 'ALTER TABLE', 'DROP TABLE', 'CREATE VIEW', 'DROP VIEW', 'ALTER VIEW', 'CREATE TABLE AS', 'SELECT INTO'];
|
|
236
|
+
|
|
237
|
+
static EXCLUDE_QUERY_FROM_SCHEMA_WATCH_ID = "prostgles internal query that should be excluded from "
|
|
238
|
+
prepareTriggers = async () => {
|
|
239
|
+
// SELECT * FROM pg_catalog.pg_event_trigger WHERE evtname
|
|
240
|
+
if (!this.appID) throw "prepareTriggers failed: this.appID missing";
|
|
241
|
+
if (this.dboBuilder.prostgles.opts.watchSchema && !(await isSuperUser(this.db))) {
|
|
242
|
+
console.warn("prostgles watchSchema requires superuser db user. Will not watch using event triggers")
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
try {
|
|
246
|
+
|
|
247
|
+
await this.db.any(`
|
|
248
|
+
BEGIN;-- ISOLATION LEVEL SERIALIZABLE;
|
|
249
|
+
|
|
250
|
+
/** ${PubSubManager.EXCLUDE_QUERY_FROM_SCHEMA_WATCH_ID}
|
|
251
|
+
* Drop stale triggers
|
|
252
|
+
* */
|
|
253
|
+
DO
|
|
254
|
+
$do$
|
|
255
|
+
DECLARE trg RECORD;
|
|
256
|
+
q TEXT;
|
|
257
|
+
ev_trg_needed BOOLEAN := FALSE;
|
|
258
|
+
ev_trg_exists BOOLEAN := FALSE;
|
|
259
|
+
is_super_user BOOLEAN := FALSE;
|
|
260
|
+
BEGIN
|
|
261
|
+
--SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
|
|
262
|
+
|
|
263
|
+
LOCK TABLE prostgles.app_triggers IN ACCESS EXCLUSIVE MODE;
|
|
264
|
+
EXECUTE format(
|
|
265
|
+
$q$
|
|
266
|
+
|
|
267
|
+
CREATE TEMP TABLE %1$I AS --ON COMMIT DROP AS
|
|
268
|
+
SELECT * FROM prostgles.app_triggers;
|
|
269
|
+
|
|
270
|
+
DELETE FROM prostgles.app_triggers;
|
|
271
|
+
|
|
272
|
+
INSERT INTO prostgles.app_triggers
|
|
273
|
+
SELECT * FROM %1$I;
|
|
274
|
+
|
|
275
|
+
DROP TABLE IF EXISTS %1$I;
|
|
276
|
+
$q$,
|
|
277
|
+
${asValue('triggers_' + this.appID)}
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
is_super_user := EXISTS (select 1 from pg_user where usename = CURRENT_USER AND usesuper IS TRUE);
|
|
281
|
+
/**
|
|
282
|
+
* Delete stale app records
|
|
283
|
+
* */
|
|
284
|
+
DELETE FROM prostgles.apps
|
|
285
|
+
WHERE last_check < NOW() - 8 * check_frequency_ms * interval '1 millisecond';
|
|
286
|
+
|
|
287
|
+
DELETE FROM prostgles.app_triggers
|
|
288
|
+
WHERE app_id NOT IN (SELECT id FROM prostgles.apps);
|
|
289
|
+
|
|
290
|
+
/* DROP the old buggy schema watch trigger */
|
|
291
|
+
IF EXISTS (
|
|
292
|
+
SELECT 1 FROM pg_catalog.pg_event_trigger
|
|
293
|
+
WHERE evtname = 'prostgles_schema_watch_trigger'
|
|
294
|
+
) AND is_super_user IS TRUE
|
|
295
|
+
THEN
|
|
296
|
+
DROP EVENT TRIGGER IF EXISTS prostgles_schema_watch_trigger;
|
|
297
|
+
END IF;
|
|
298
|
+
|
|
299
|
+
ev_trg_needed := EXISTS (SELECT 1 FROM prostgles.apps WHERE watching_schema IS TRUE);
|
|
300
|
+
ev_trg_exists := EXISTS (
|
|
301
|
+
SELECT 1 FROM pg_catalog.pg_event_trigger
|
|
302
|
+
WHERE evtname = ${asValue(this.DB_OBJ_NAMES.schema_watch_trigger)}
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
-- RAISE NOTICE ' ev_trg_needed %, ev_trg_exists %', ev_trg_needed, ev_trg_exists;
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* DROP stale event trigger
|
|
309
|
+
* */
|
|
310
|
+
IF is_super_user IS TRUE AND ev_trg_needed IS FALSE AND ev_trg_exists IS TRUE THEN
|
|
311
|
+
|
|
312
|
+
SELECT format(
|
|
313
|
+
$$ DROP EVENT TRIGGER IF EXISTS %I ; $$
|
|
314
|
+
, ${asValue(this.DB_OBJ_NAMES.schema_watch_trigger)}
|
|
315
|
+
)
|
|
316
|
+
INTO q;
|
|
317
|
+
|
|
318
|
+
--RAISE NOTICE ' DROP EVENT TRIGGER %', q;
|
|
319
|
+
|
|
320
|
+
EXECUTE q;
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* CREATE event trigger
|
|
324
|
+
* */
|
|
325
|
+
ELSIF
|
|
326
|
+
is_super_user IS TRUE
|
|
327
|
+
AND ev_trg_needed IS TRUE
|
|
328
|
+
AND ev_trg_exists IS FALSE
|
|
329
|
+
THEN
|
|
330
|
+
|
|
331
|
+
DROP EVENT TRIGGER IF EXISTS ${this.DB_OBJ_NAMES.schema_watch_trigger};
|
|
332
|
+
CREATE EVENT TRIGGER ${this.DB_OBJ_NAMES.schema_watch_trigger} ON ddl_command_end
|
|
333
|
+
WHEN TAG IN ('COMMENT', 'CREATE TABLE', 'ALTER TABLE', 'DROP TABLE', 'CREATE VIEW', 'DROP VIEW', 'ALTER VIEW', 'CREATE TABLE AS', 'SELECT INTO')
|
|
334
|
+
--WHEN TAG IN ('CREATE TABLE', 'ALTER TABLE', 'DROP TABLE', 'CREATE TRIGGER', 'DROP TRIGGER')
|
|
335
|
+
EXECUTE PROCEDURE ${this.DB_OBJ_NAMES.schema_watch_func}();
|
|
336
|
+
|
|
337
|
+
--RAISE NOTICE ' CREATED EVENT TRIGGER %', q;
|
|
338
|
+
END IF;
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
END
|
|
342
|
+
$do$;
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
COMMIT;
|
|
346
|
+
`).catch(e => {
|
|
347
|
+
console.error("prepareTriggers failed: ", e);
|
|
348
|
+
throw e;
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
return true;
|
|
352
|
+
|
|
353
|
+
} catch (e) {
|
|
354
|
+
console.error("prepareTriggers failed: ", e);
|
|
355
|
+
throw e;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
isReady() {
|
|
360
|
+
if (!this.postgresNotifListenManager) throw "this.postgresNotifListenManager missing";
|
|
361
|
+
return this.postgresNotifListenManager.isListening();
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
getSubs(table_name: string, condition: string): SubscriptionParams[] {
|
|
365
|
+
return this.subs?.[table_name]?.[condition]?.subs
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
getSyncs(table_name: string, condition: string) {
|
|
369
|
+
return (this.syncs || [])
|
|
370
|
+
.filter((s: SyncParams) => s.table_name === table_name && s.condition === condition);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/* Relay relevant data to relevant subscriptions */
|
|
374
|
+
notifListener = async (data: { payload: string }) => {
|
|
375
|
+
const str = data.payload;
|
|
376
|
+
|
|
377
|
+
if (!str) {
|
|
378
|
+
console.error("Empty notif?")
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
const dataArr = str.split(PubSubManager.DELIMITER),
|
|
382
|
+
notifType = dataArr[0];
|
|
383
|
+
|
|
384
|
+
log(str);
|
|
385
|
+
|
|
386
|
+
if (notifType === this.NOTIF_TYPE.schema) {
|
|
387
|
+
if (this.onSchemaChange) {
|
|
388
|
+
const command = dataArr[1],
|
|
389
|
+
event_type = dataArr[2],
|
|
390
|
+
query = dataArr[3];
|
|
391
|
+
|
|
392
|
+
if (query) {
|
|
393
|
+
this.onSchemaChange({ command, query })
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (notifType !== this.NOTIF_TYPE.data) {
|
|
401
|
+
console.error("Unexpected notif type: ", notifType);
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const table_name = dataArr[1],
|
|
406
|
+
op_name = dataArr[2],
|
|
407
|
+
condition_ids_str = dataArr[3];
|
|
408
|
+
|
|
409
|
+
// const triggers = await this.db.any("SELECT * FROM prostgles.triggers WHERE table_name = $1 AND id IN ($2:csv)", [table_name, condition_ids_str.split(",").map(v => +v)]);
|
|
410
|
+
// const conditions: string[] = triggers.map(t => t.condition);
|
|
411
|
+
|
|
412
|
+
log("PG Trigger ->", dataArr.join("__"))
|
|
413
|
+
if (
|
|
414
|
+
condition_ids_str && condition_ids_str.startsWith("error") &&
|
|
415
|
+
this._triggers && this._triggers[table_name] && this._triggers[table_name].length
|
|
416
|
+
) {
|
|
417
|
+
const pref = "INTERNAL ERROR. Schema might have changed";
|
|
418
|
+
console.error(`${pref}: ${condition_ids_str}`)
|
|
419
|
+
this._triggers[table_name].map(c => {
|
|
420
|
+
const subs = this.getSubs(table_name, c);
|
|
421
|
+
subs.map(s => {
|
|
422
|
+
this.pushSubData(s, pref + ". Check server logs");
|
|
423
|
+
})
|
|
424
|
+
});
|
|
425
|
+
} else if (
|
|
426
|
+
condition_ids_str?.split(",").length &&
|
|
427
|
+
condition_ids_str?.split(",").every((c: string) => Number.isInteger(+c)) &&
|
|
428
|
+
this._triggers?.[table_name]?.length
|
|
429
|
+
) {
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
const idxs = condition_ids_str.split(",").map(v => +v);
|
|
433
|
+
const conditions = this._triggers[table_name].filter((c, i) => idxs.includes(i))
|
|
434
|
+
|
|
435
|
+
log("PG Trigger -> ", { table_name, op_name, condition_ids_str, conditions }, this._triggers[table_name]);
|
|
436
|
+
|
|
437
|
+
conditions.map(condition => {
|
|
438
|
+
|
|
439
|
+
const subs = this.getSubs(table_name, condition);
|
|
440
|
+
const syncs = this.getSyncs(table_name, condition);
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
syncs.map((s) => {
|
|
444
|
+
this.syncData(s, undefined, "trigger");
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
if (!subs) {
|
|
448
|
+
|
|
449
|
+
// console.error(`sub missing for ${table_name} ${condition}`, this.triggers);
|
|
450
|
+
// console.log(this.subs)
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/* Throttle the subscriptions */
|
|
455
|
+
for (var i = 0; i < subs.length; i++) {
|
|
456
|
+
var sub = subs[i];
|
|
457
|
+
if (
|
|
458
|
+
this.dbo[sub.table_name] &&
|
|
459
|
+
sub.is_ready &&
|
|
460
|
+
(sub.socket_id && this.sockets[sub.socket_id]) || sub.func
|
|
461
|
+
) {
|
|
462
|
+
const throttle = sub.throttle || 0;
|
|
463
|
+
if (sub.last_throttled <= Date.now() - throttle) {
|
|
464
|
+
|
|
465
|
+
/* It is assumed the policy was checked before this point */
|
|
466
|
+
this.pushSubData(sub);
|
|
467
|
+
// sub.last_throttled = Date.now();
|
|
468
|
+
} else if (!sub.is_throttling) {
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
log("throttling sub")
|
|
472
|
+
sub.is_throttling = setTimeout(() => {
|
|
473
|
+
log("throttling finished. pushSubData...")
|
|
474
|
+
sub.is_throttling = null;
|
|
475
|
+
this.pushSubData(sub);
|
|
476
|
+
}, throttle);// sub.throttle);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
} else {
|
|
483
|
+
|
|
484
|
+
// if(!this._triggers || !this._triggers[table_name] || !this._triggers[table_name].length){
|
|
485
|
+
// console.warn(190, "Trigger sub not found. DROPPING TRIGGER", table_name, condition_ids_str, this._triggers);
|
|
486
|
+
// this.dropTrigger(table_name);
|
|
487
|
+
// } else {
|
|
488
|
+
// }
|
|
489
|
+
console.warn(190, "Trigger sub issue: ", table_name, condition_ids_str, this._triggers);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
pushSubData(sub: SubscriptionParams, err?: any) {
|
|
495
|
+
if (!sub) throw "pushSubData: invalid sub";
|
|
496
|
+
const { table_name, filter, params, table_rules, socket_id, channel_name, func } = sub; //, subOne = false
|
|
497
|
+
|
|
498
|
+
sub.last_throttled = Date.now();
|
|
499
|
+
|
|
500
|
+
if (err) {
|
|
501
|
+
if (socket_id) {
|
|
502
|
+
this.sockets[socket_id].emit(channel_name, { err });
|
|
503
|
+
}
|
|
504
|
+
return true;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
return new Promise(async (resolve, reject) => {
|
|
508
|
+
/* TODO: Retire subOne -> it's redundant */
|
|
509
|
+
// this.dbo[table_name][subOne? "findOne" : "find"](filter, params, null, table_rules)
|
|
510
|
+
if (!this.dbo?.[table_name]?.find) {
|
|
511
|
+
throw new Error(`1107 this.dbo.${table_name}.find`);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
this.dbo?.[table_name]?.find?.(filter, params, undefined, table_rules)
|
|
515
|
+
.then(data => {
|
|
516
|
+
|
|
517
|
+
if (socket_id && this.sockets[socket_id]) {
|
|
518
|
+
log("Pushed " + data.length + " records to sub")
|
|
519
|
+
this.sockets[socket_id].emit(channel_name, { data }, () => {
|
|
520
|
+
resolve(data);
|
|
521
|
+
});
|
|
522
|
+
/* TO DO: confirm receiving data or server will unsubscribe
|
|
523
|
+
{ data }, (cb)=> { console.log(cb) });
|
|
524
|
+
*/
|
|
525
|
+
} else if (func) {
|
|
526
|
+
func(data);
|
|
527
|
+
resolve(data);
|
|
528
|
+
}
|
|
529
|
+
sub.last_throttled = Date.now();
|
|
530
|
+
}).catch(err => {
|
|
531
|
+
const errObj = { _err_msg: err.toString(), err };
|
|
532
|
+
if (socket_id && this.sockets[socket_id]) {
|
|
533
|
+
this.sockets[socket_id].emit(channel_name, { err: errObj });
|
|
534
|
+
} else if (func) {
|
|
535
|
+
func({ err: errObj });
|
|
536
|
+
}
|
|
537
|
+
reject(errObj)
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
upsertSocket(socket: any, channel_name: string) {
|
|
543
|
+
if (socket && !this.sockets[socket.id]) {
|
|
544
|
+
this.sockets[socket.id] = socket;
|
|
545
|
+
socket.on("disconnect", () => this.onSocketDisconnected(socket));
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
syncTimeout?: ReturnType<typeof setTimeout>;
|
|
550
|
+
async syncData(sync: SyncParams, clientData: ClientExpressData | undefined, source: "trigger" | "client") {
|
|
551
|
+
return await syncData(this, sync, clientData, source);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Returns a sync channel
|
|
556
|
+
* A sync channel is unique per socket for each filter
|
|
557
|
+
*/
|
|
558
|
+
async addSync(syncParams: AddSyncParams) {
|
|
559
|
+
const {
|
|
560
|
+
socket = null, table_info = null, table_rules, synced_field = null,
|
|
561
|
+
allow_delete = false, id_fields = [], filter = {},
|
|
562
|
+
params, condition = "", throttle = 0
|
|
563
|
+
} = syncParams || {};
|
|
564
|
+
|
|
565
|
+
let conditionParsed = parseCondition(condition);
|
|
566
|
+
if (!socket || !table_info) throw "socket or table_info missing";
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
const { name: table_name } = table_info,
|
|
570
|
+
channel_name = `${this.socketChannelPreffix}.${table_name}.${JSON.stringify(filter)}.sync`;
|
|
571
|
+
|
|
572
|
+
if (!synced_field) throw "synced_field missing from table_rules";
|
|
573
|
+
|
|
574
|
+
this.upsertSocket(socket, channel_name);
|
|
575
|
+
|
|
576
|
+
const upsertSync = () => {
|
|
577
|
+
let newSync = {
|
|
578
|
+
channel_name,
|
|
579
|
+
table_name,
|
|
580
|
+
filter,
|
|
581
|
+
condition: conditionParsed,
|
|
582
|
+
synced_field,
|
|
583
|
+
id_fields,
|
|
584
|
+
allow_delete,
|
|
585
|
+
table_rules,
|
|
586
|
+
throttle: Math.max(throttle || 0, table_rules?.sync?.throttle || 0),
|
|
587
|
+
batch_size: get(table_rules, "sync.batch_size") || DEFAULT_SYNC_BATCH_SIZE,
|
|
588
|
+
last_throttled: 0,
|
|
589
|
+
socket_id: socket.id,
|
|
590
|
+
is_sync: true,
|
|
591
|
+
last_synced: 0,
|
|
592
|
+
lr: undefined,
|
|
593
|
+
table_info,
|
|
594
|
+
is_syncing: false,
|
|
595
|
+
wal: undefined,
|
|
596
|
+
socket,
|
|
597
|
+
params
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
/* Only a sync per socket per table per condition allowed */
|
|
601
|
+
this.syncs = this.syncs || [];
|
|
602
|
+
let existing = this.syncs.find(s => s.socket_id === socket.id && s.channel_name === channel_name);
|
|
603
|
+
if (!existing) {
|
|
604
|
+
this.syncs.push(newSync);
|
|
605
|
+
// console.log("Added SYNC");
|
|
606
|
+
|
|
607
|
+
socket.removeAllListeners(channel_name + "unsync");
|
|
608
|
+
socket.once(channel_name + "unsync", (_data: any, cb: BasicCallback) => {
|
|
609
|
+
this.onSocketDisconnected(socket, channel_name);
|
|
610
|
+
cb(null, { res: "ok" })
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
socket.removeAllListeners(channel_name);
|
|
614
|
+
socket.on(channel_name, (data: any, cb: BasicCallback) => {
|
|
615
|
+
|
|
616
|
+
if (!data) {
|
|
617
|
+
cb({ err: "Unexpected request. Need data or onSyncRequest" });
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/*
|
|
622
|
+
*/
|
|
623
|
+
|
|
624
|
+
/* Server will:
|
|
625
|
+
1. Ask for last_synced emit(onSyncRequest)
|
|
626
|
+
2. Ask for data >= server_synced emit(onPullRequest)
|
|
627
|
+
-> Upsert that data
|
|
628
|
+
2. Push data >= last_synced emit(data.data)
|
|
629
|
+
|
|
630
|
+
Client will:
|
|
631
|
+
1. Send last_synced on(onSyncRequest)
|
|
632
|
+
2. Send data >= server_synced on(onPullRequest)
|
|
633
|
+
3. Send data on CRUD emit(data.data | data.deleted)
|
|
634
|
+
4. Upsert data.data | deleted on(data.data | data.deleted)
|
|
635
|
+
*/
|
|
636
|
+
|
|
637
|
+
// if(data.data){
|
|
638
|
+
// console.error("THIS SHOUKD NEVER FIRE !! NEW DATA FROM SYNC");
|
|
639
|
+
// this.upsertClientData(newSync, data.data);
|
|
640
|
+
// } else
|
|
641
|
+
if (data.onSyncRequest) {
|
|
642
|
+
// console.log("syncData from socket")
|
|
643
|
+
this.syncData(newSync, data.onSyncRequest, "client");
|
|
644
|
+
|
|
645
|
+
// console.log("onSyncRequest ", socket._user)
|
|
646
|
+
} else {
|
|
647
|
+
console.error("Unexpected sync request data from client: ", data)
|
|
648
|
+
}
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
// socket.emit(channel_name, { onSyncRequest: true }, (response) => {
|
|
652
|
+
// console.log(response)
|
|
653
|
+
// });
|
|
654
|
+
} else {
|
|
655
|
+
console.error("UNCLOSED DUPLICATE SYNC FOUND");
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
return newSync;
|
|
659
|
+
};
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
// const { min_id, max_id, count, max_synced } = params;
|
|
663
|
+
|
|
664
|
+
let sync = upsertSync();
|
|
665
|
+
|
|
666
|
+
await this.addTrigger({ table_name, condition: conditionParsed });
|
|
667
|
+
|
|
668
|
+
return channel_name;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
/* Must return a channel for socket */
|
|
673
|
+
/* The distinct list of channel names must have a corresponding trigger in the database */
|
|
674
|
+
async addSub(subscriptionParams: Omit<AddSubscriptionParams, "channel_name" | "parentSubParams">) {
|
|
675
|
+
const {
|
|
676
|
+
socket, func = null, table_info = null, table_rules, filter = {},
|
|
677
|
+
params = {}, condition = "", throttle = 0, //subOne = false,
|
|
678
|
+
viewOptions
|
|
679
|
+
} = subscriptionParams || {};
|
|
680
|
+
|
|
681
|
+
let validated_throttle = subscriptionParams.throttle || 10;
|
|
682
|
+
if ((!socket && !func) || !table_info) throw "socket/func or table_info missing";
|
|
683
|
+
|
|
684
|
+
const pubThrottle = get(table_rules, ["subscribe", "throttle"]) || 0;
|
|
685
|
+
if (pubThrottle && Number.isInteger(pubThrottle) && pubThrottle > 0) {
|
|
686
|
+
validated_throttle = pubThrottle;
|
|
687
|
+
}
|
|
688
|
+
if (throttle && Number.isInteger(throttle) && throttle >= pubThrottle) {
|
|
689
|
+
validated_throttle = throttle;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const channel_name = `${this.socketChannelPreffix}.${table_info.name}.${JSON.stringify(filter)}.${JSON.stringify(params)}.${"m"}.sub`;
|
|
693
|
+
|
|
694
|
+
this.upsertSocket(socket, channel_name);
|
|
695
|
+
|
|
696
|
+
const upsertSub = (newSubData: { table_name: string; condition: string; is_ready: boolean; parentSubParams: SubscriptionParams["parentSubParams"] }) => {
|
|
697
|
+
const { table_name, condition: _cond, is_ready = false, parentSubParams } = newSubData,
|
|
698
|
+
condition = parseCondition(_cond),
|
|
699
|
+
newSub: SubscriptionParams = {
|
|
700
|
+
socket,
|
|
701
|
+
table_name: table_info.name,
|
|
702
|
+
table_info,
|
|
703
|
+
filter,
|
|
704
|
+
params,
|
|
705
|
+
table_rules,
|
|
706
|
+
channel_name,
|
|
707
|
+
parentSubParams,
|
|
708
|
+
func: func ?? undefined,
|
|
709
|
+
socket_id: socket?.id,
|
|
710
|
+
throttle: validated_throttle,
|
|
711
|
+
is_throttling: null,
|
|
712
|
+
last_throttled: 0,
|
|
713
|
+
is_ready,
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
this.subs[table_name] = this.subs[table_name] ?? {};
|
|
717
|
+
this.subs[table_name][condition] = this.subs[table_name][condition] ?? { subs: [] };
|
|
718
|
+
this.subs[table_name][condition].subs = this.subs[table_name][condition].subs ?? [];
|
|
719
|
+
|
|
720
|
+
// console.log("1034 upsertSub", this.subs)
|
|
721
|
+
const sub_idx = this.subs[table_name][condition].subs.findIndex(s =>
|
|
722
|
+
s.channel_name === channel_name &&
|
|
723
|
+
(
|
|
724
|
+
socket && s.socket_id === socket.id ||
|
|
725
|
+
func && s.func === func
|
|
726
|
+
)
|
|
727
|
+
);
|
|
728
|
+
if (sub_idx < 0) {
|
|
729
|
+
this.subs[table_name][condition].subs.push(newSub);
|
|
730
|
+
if (socket) {
|
|
731
|
+
const chnUnsub = channel_name + "unsubscribe";
|
|
732
|
+
socket.removeAllListeners(chnUnsub);
|
|
733
|
+
socket.once(chnUnsub, (_data: any, cb: BasicCallback) => {
|
|
734
|
+
const res = this.onSocketDisconnected(socket, channel_name);
|
|
735
|
+
cb(null, { res });
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
} else {
|
|
739
|
+
this.subs[table_name][condition].subs[sub_idx] = newSub;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
if (is_ready) {
|
|
743
|
+
this.pushSubData(newSub);
|
|
744
|
+
}
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
|
|
748
|
+
if (table_info.is_view) {
|
|
749
|
+
if (viewOptions?.relatedTables.length) {
|
|
750
|
+
|
|
751
|
+
viewOptions?.relatedTables.map(async relatedTable => {
|
|
752
|
+
const params: Omit<Parameters<typeof upsertSub>[0], "is_ready"> = {
|
|
753
|
+
table_name: relatedTable.tableName,
|
|
754
|
+
condition: relatedTable.condition,
|
|
755
|
+
parentSubParams: {
|
|
756
|
+
...subscriptionParams,
|
|
757
|
+
channel_name
|
|
758
|
+
},
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
upsertSub({
|
|
762
|
+
...params,
|
|
763
|
+
is_ready: false
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
await this.addTrigger(params);
|
|
767
|
+
|
|
768
|
+
upsertSub({
|
|
769
|
+
...params,
|
|
770
|
+
is_ready: true
|
|
771
|
+
});
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
return channel_name
|
|
775
|
+
} else {
|
|
776
|
+
throw "PubSubManager: view parent_tables missing";
|
|
777
|
+
}
|
|
778
|
+
/* */
|
|
779
|
+
} else {
|
|
780
|
+
/* Just a table, add table + condition trigger */
|
|
781
|
+
// console.log(table_info, 202);
|
|
782
|
+
|
|
783
|
+
upsertSub({
|
|
784
|
+
table_name: table_info.name,
|
|
785
|
+
condition: parseCondition(condition),
|
|
786
|
+
parentSubParams: undefined,
|
|
787
|
+
is_ready: false
|
|
788
|
+
});
|
|
789
|
+
await this.addTrigger({
|
|
790
|
+
table_name: table_info.name,
|
|
791
|
+
condition: parseCondition(condition),
|
|
792
|
+
});
|
|
793
|
+
upsertSub({
|
|
794
|
+
table_name: table_info.name,
|
|
795
|
+
condition: parseCondition(condition),
|
|
796
|
+
parentSubParams: undefined,
|
|
797
|
+
is_ready: true
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
return channel_name
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
removeLocalSub(table_name: string, condition: string, func: (items: object[]) => any) {
|
|
805
|
+
let cond = parseCondition(condition);
|
|
806
|
+
if (get(this.subs, [table_name, cond, "subs"])) {
|
|
807
|
+
this.subs[table_name][cond].subs.map((sub, i) => {
|
|
808
|
+
if (
|
|
809
|
+
sub.func && sub.func === func
|
|
810
|
+
) {
|
|
811
|
+
this.subs[table_name][cond].subs.splice(i, 1);
|
|
812
|
+
}
|
|
813
|
+
});
|
|
814
|
+
} else {
|
|
815
|
+
console.error("Could not unsubscribe. Subscription might not have initialised yet")
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
getActiveListeners = (): { table_name: string; condition: string }[] => {
|
|
820
|
+
let result: { table_name: string; condition: string }[] = [];
|
|
821
|
+
const upsert = (t: string, c: string) => {
|
|
822
|
+
if (!result.find(r => r.table_name === t && r.condition === c)) {
|
|
823
|
+
result.push({ table_name: t, condition: c });
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
(this.syncs || []).map(s => {
|
|
827
|
+
upsert(s.table_name, s.condition)
|
|
828
|
+
});
|
|
829
|
+
Object.keys(this.subs || {}).map(table_name => {
|
|
830
|
+
Object.keys(this.subs[table_name] || {}).map(condition => {
|
|
831
|
+
if (this.subs[table_name][condition].subs.length) {
|
|
832
|
+
upsert(table_name, condition);
|
|
833
|
+
}
|
|
834
|
+
});
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
return result;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
onSocketDisconnected(socket?: PRGLIOSocket, channel_name?: string) {
|
|
841
|
+
// process.on('warning', e => {
|
|
842
|
+
// console.warn(e.stack)
|
|
843
|
+
// });
|
|
844
|
+
// console.log("onSocketDisconnected", channel_name, this.syncs)
|
|
845
|
+
if (this.subs) {
|
|
846
|
+
Object.keys(this.subs).map(table_name => {
|
|
847
|
+
Object.keys(this.subs[table_name]).map(condition => {
|
|
848
|
+
this.subs[table_name][condition].subs.map((sub, i) => {
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* If a channel name is specified then delete triggers
|
|
852
|
+
*/
|
|
853
|
+
if (
|
|
854
|
+
(socket && sub.socket_id === socket.id) &&
|
|
855
|
+
(!channel_name || sub.channel_name === channel_name)
|
|
856
|
+
) {
|
|
857
|
+
this.subs[table_name][condition].subs.splice(i, 1);
|
|
858
|
+
if (!this.subs[table_name][condition].subs.length) {
|
|
859
|
+
delete this.subs[table_name][condition];
|
|
860
|
+
|
|
861
|
+
if (isEmpty(this.subs[table_name])) {
|
|
862
|
+
delete this.subs[table_name];
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
});
|
|
867
|
+
})
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
if (this.syncs) {
|
|
872
|
+
this.syncs = this.syncs.filter(s => {
|
|
873
|
+
const matchesSocket = Boolean(socket && s.socket_id !== socket.id)
|
|
874
|
+
if (channel_name) {
|
|
875
|
+
return matchesSocket || s.channel_name !== channel_name
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
return matchesSocket;
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
if (!socket) {
|
|
883
|
+
|
|
884
|
+
} else if (!channel_name) {
|
|
885
|
+
delete this.sockets[socket.id];
|
|
886
|
+
} else {
|
|
887
|
+
socket.removeAllListeners(channel_name);
|
|
888
|
+
socket.removeAllListeners(channel_name + "unsync");
|
|
889
|
+
socket.removeAllListeners(channel_name + "unsubscribe");
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
return "ok";
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
|
|
896
|
+
checkIfTimescaleBug = async (table_name: string) => {
|
|
897
|
+
const schema = "_timescaledb_catalog",
|
|
898
|
+
res = await this.db.oneOrNone("SELECT EXISTS( \
|
|
899
|
+
SELECT * \
|
|
900
|
+
FROM information_schema.tables \
|
|
901
|
+
WHERE 1 = 1 \
|
|
902
|
+
AND table_schema = ${schema} \
|
|
903
|
+
AND table_name = 'hypertable' \
|
|
904
|
+
);", { schema });
|
|
905
|
+
if (res.exists) {
|
|
906
|
+
let isHyperTable = await this.db.any("SELECT * FROM " + asName(schema) + ".hypertable WHERE table_name = ${table_name};", { table_name, schema });
|
|
907
|
+
if (isHyperTable && isHyperTable.length) {
|
|
908
|
+
throw "Triggers do not work on timescaledb hypertables due to bug:\nhttps://github.com/timescale/timescaledb/issues/1084"
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
return true;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
/*
|
|
915
|
+
A table will only have a trigger with all conditions (for different subs)
|
|
916
|
+
conditions = ["user_id = 1"]
|
|
917
|
+
fields = ["user_id"]
|
|
918
|
+
*/
|
|
919
|
+
|
|
920
|
+
getMyTriggerQuery = async () => {
|
|
921
|
+
return pgp.as.format(`
|
|
922
|
+
SELECT * --, ROW_NUMBER() OVER(PARTITION BY table_name ORDER BY table_name, condition ) - 1 as id
|
|
923
|
+
FROM prostgles.v_triggers
|
|
924
|
+
WHERE app_id = $1
|
|
925
|
+
ORDER BY table_name, condition
|
|
926
|
+
`, [this.appID]
|
|
927
|
+
)
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
// waitingTriggers: { [key: string]: string[] } = undefined;
|
|
931
|
+
addingTrigger: any;
|
|
932
|
+
addTriggerPool?: Record<string, string[]> = undefined;
|
|
933
|
+
async addTrigger(params: { table_name: string; condition: string; }, viewOptions?: ViewSubscriptionOptions) {
|
|
934
|
+
try {
|
|
935
|
+
|
|
936
|
+
let { table_name, condition } = { ...params }
|
|
937
|
+
if (!table_name) throw "MISSING table_name";
|
|
938
|
+
if (!this.appID) throw "MISSING appID";
|
|
939
|
+
|
|
940
|
+
if (!condition || !condition.trim().length) {
|
|
941
|
+
condition = "TRUE";
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
// console.log(1623, { app_id, addTrigger: { table_name, condition } });
|
|
945
|
+
|
|
946
|
+
await this.checkIfTimescaleBug(table_name);
|
|
947
|
+
|
|
948
|
+
const trgVals = {
|
|
949
|
+
tbl: asValue(table_name),
|
|
950
|
+
cond: asValue(condition),
|
|
951
|
+
};
|
|
952
|
+
|
|
953
|
+
await this.db.any(`
|
|
954
|
+
BEGIN WORK;
|
|
955
|
+
LOCK TABLE prostgles.app_triggers IN ACCESS EXCLUSIVE MODE;
|
|
956
|
+
|
|
957
|
+
INSERT INTO prostgles.app_triggers (table_name, condition, app_id, related_view_name, related_view_def)
|
|
958
|
+
VALUES (${trgVals.tbl}, ${trgVals.cond}, ${asValue(this.appID)}, ${viewOptions?.viewName ?? null}, ${viewOptions?.definition ?? null})
|
|
959
|
+
ON CONFLICT DO NOTHING;
|
|
960
|
+
|
|
961
|
+
COMMIT WORK;
|
|
962
|
+
`);
|
|
963
|
+
|
|
964
|
+
log("addTrigger.. ", { table_name, condition });
|
|
965
|
+
|
|
966
|
+
const triggers: {
|
|
967
|
+
table_name: string;
|
|
968
|
+
condition: string;
|
|
969
|
+
}[] = await this.db.any(await this.getMyTriggerQuery());
|
|
970
|
+
|
|
971
|
+
|
|
972
|
+
this._triggers = {};
|
|
973
|
+
triggers.map(t => {
|
|
974
|
+
this._triggers = this._triggers || {};
|
|
975
|
+
this._triggers[t.table_name] = this._triggers[t.table_name] || [];
|
|
976
|
+
if (!this._triggers[t.table_name].includes(t.condition)) {
|
|
977
|
+
this._triggers[t.table_name].push(t.condition)
|
|
978
|
+
}
|
|
979
|
+
});
|
|
980
|
+
log("trigger added.. ", { table_name, condition });
|
|
981
|
+
|
|
982
|
+
return true;
|
|
983
|
+
// console.log("1612", JSON.stringify(triggers, null, 2))
|
|
984
|
+
// console.log("1613",JSON.stringify(this._triggers, null, 2))
|
|
985
|
+
|
|
986
|
+
|
|
987
|
+
} catch (e) {
|
|
988
|
+
console.trace("Failed adding trigger", e);
|
|
989
|
+
// throw e
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
|
|
996
|
+
const parseCondition = (condition: string): string => Boolean(condition && condition.trim().length) ? condition : "TRUE"
|
|
997
|
+
|
|
998
|
+
export function omitKeys<T extends AnyObject, Exclude extends keyof T>(obj: T, exclude: Exclude[]): Omit<T, Exclude> {
|
|
999
|
+
return pickKeys(obj, getKeys(obj).filter(k => !exclude.includes(k as any)))
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
export function pickKeys<T extends AnyObject, Include extends keyof T>(obj: T, include: Include[] = []): Pick<T, Include> {
|
|
1003
|
+
let keys = include;
|
|
1004
|
+
if (!keys.length) {
|
|
1005
|
+
return {} as any;
|
|
1006
|
+
}
|
|
1007
|
+
if (obj && keys.length) {
|
|
1008
|
+
let res: AnyObject = {};
|
|
1009
|
+
keys.forEach(k => {
|
|
1010
|
+
res[k] = obj[k];
|
|
1011
|
+
});
|
|
1012
|
+
return res as any;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
return obj;
|
|
1016
|
+
}
|