prostgles-server 3.0.98 → 3.0.100
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.js +6 -6
- package/dist/AuthHandler.js.map +1 -1
- package/dist/DboBuilder/ViewHandler.js +0 -1
- package/dist/DboBuilder/ViewHandler.js.map +1 -1
- package/dist/DboBuilder/subscribe.d.ts.map +1 -1
- package/dist/DboBuilder/subscribe.js +0 -17
- package/dist/DboBuilder/subscribe.js.map +1 -1
- package/dist/JSONBValidation/validate_jsonb_schema_sql.d.ts +1 -1
- package/dist/JSONBValidation/validate_jsonb_schema_sql.d.ts.map +1 -1
- package/dist/JSONBValidation/validate_jsonb_schema_sql.js +8 -4
- package/dist/JSONBValidation/validate_jsonb_schema_sql.js.map +1 -1
- package/dist/PubSubManager/PubSubManager.d.ts +4 -7
- package/dist/PubSubManager/PubSubManager.d.ts.map +1 -1
- package/dist/PubSubManager/PubSubManager.js +13 -19
- package/dist/PubSubManager/PubSubManager.js.map +1 -1
- package/dist/PubSubManager/getInitQuery.d.ts +9 -0
- package/dist/PubSubManager/getInitQuery.d.ts.map +1 -0
- package/dist/PubSubManager/getInitQuery.js +537 -0
- package/dist/PubSubManager/getInitQuery.js.map +1 -0
- package/dist/PubSubManager/initPubSubManager.d.ts.map +1 -1
- package/dist/PubSubManager/initPubSubManager.js +46 -577
- package/dist/PubSubManager/initPubSubManager.js.map +1 -1
- package/dist/SchemaWatch.js +1 -0
- package/dist/SchemaWatch.js.map +1 -1
- package/dist/TableConfig.d.ts.map +1 -1
- package/dist/TableConfig.js +5 -1
- package/dist/TableConfig.js.map +1 -1
- package/lib/AuthHandler.js +6 -6
- package/lib/AuthHandler.ts +7 -7
- package/lib/DboBuilder/ViewHandler.js +0 -1
- package/lib/DboBuilder/ViewHandler.ts +1 -1
- package/lib/DboBuilder/subscribe.d.ts.map +1 -1
- package/lib/DboBuilder/subscribe.js +0 -17
- package/lib/DboBuilder/subscribe.ts +0 -25
- package/lib/JSONBValidation/validate_jsonb_schema_sql.d.ts +1 -1
- package/lib/JSONBValidation/validate_jsonb_schema_sql.d.ts.map +1 -1
- package/lib/JSONBValidation/validate_jsonb_schema_sql.js +8 -4
- package/lib/JSONBValidation/validate_jsonb_schema_sql.ts +8 -4
- package/lib/PubSubManager/PubSubManager.d.ts +4 -7
- package/lib/PubSubManager/PubSubManager.d.ts.map +1 -1
- package/lib/PubSubManager/PubSubManager.js +16 -19
- package/lib/PubSubManager/PubSubManager.ts +18 -20
- package/lib/PubSubManager/getInitQuery.d.ts +9 -0
- package/lib/PubSubManager/getInitQuery.d.ts.map +1 -0
- package/lib/PubSubManager/getInitQuery.js +536 -0
- package/lib/PubSubManager/getInitQuery.ts +537 -0
- package/lib/PubSubManager/initPubSubManager.d.ts.map +1 -1
- package/lib/PubSubManager/initPubSubManager.js +46 -577
- package/lib/PubSubManager/initPubSubManager.ts +48 -585
- package/lib/SchemaWatch.js +1 -0
- package/lib/SchemaWatch.ts +1 -1
- package/lib/TableConfig.d.ts.map +1 -1
- package/lib/TableConfig.js +5 -1
- package/lib/TableConfig.ts +5 -1
- package/package.json +3 -3
- package/tests/client/PID.txt +1 -1
- package/tests/server/package-lock.json +1 -1
|
@@ -18,6 +18,7 @@ import { SelectParams, FieldFilter, asName, WAL, isEmpty, AnyObject } from "pros
|
|
|
18
18
|
import { ClientExpressData, syncData } from "../SyncReplication";
|
|
19
19
|
import { TableRule } from "../PublishParser";
|
|
20
20
|
import { find } from "prostgles-types/dist/util";
|
|
21
|
+
import { DB_OBJ_NAMES } from "./getInitQuery";
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
type PGP = pgPromise.IMain<{}, pg.IClient>;
|
|
@@ -138,7 +139,7 @@ export class PubSubManager {
|
|
|
138
139
|
postgresNotifListenManager?: PostgresNotifListenManager;
|
|
139
140
|
|
|
140
141
|
private constructor(options: PubSubManagerOptions) {
|
|
141
|
-
const { wsChannelNamePrefix,
|
|
142
|
+
const { wsChannelNamePrefix, onSchemaChange, dboBuilder } = options;
|
|
142
143
|
if (!dboBuilder.db || !dboBuilder.dbo) {
|
|
143
144
|
throw 'MISSING: db_pg, db';
|
|
144
145
|
}
|
|
@@ -167,6 +168,9 @@ export class PubSubManager {
|
|
|
167
168
|
}
|
|
168
169
|
}
|
|
169
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Used facilitate concurrent prostgles connections to the same database
|
|
173
|
+
*/
|
|
170
174
|
appID?: string;
|
|
171
175
|
|
|
172
176
|
appCheckFrequencyMS = 10 * 1000;
|
|
@@ -207,11 +211,10 @@ export class PubSubManager {
|
|
|
207
211
|
if (this.appCheck) {
|
|
208
212
|
clearInterval(this.appCheck);
|
|
209
213
|
}
|
|
210
|
-
this.onSocketDisconnected();
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
if (!this.postgresNotifListenManager) throw "this.postgresNotifListenManager missing"
|
|
214
|
+
this.onSocketDisconnected();
|
|
215
|
+
if (!this.postgresNotifListenManager) {
|
|
216
|
+
throw "this.postgresNotifListenManager missing"
|
|
217
|
+
}
|
|
215
218
|
this.postgresNotifListenManager.destroy();
|
|
216
219
|
}
|
|
217
220
|
|
|
@@ -226,12 +229,6 @@ export class PubSubManager {
|
|
|
226
229
|
appChecking = false;
|
|
227
230
|
init = initPubSubManager.bind(this);
|
|
228
231
|
|
|
229
|
-
DB_OBJ_NAMES = {
|
|
230
|
-
trigger_add_remove_func: "prostgles.trigger_add_remove_func",
|
|
231
|
-
data_watch_func: "prostgles.prostgles_trigger_function",
|
|
232
|
-
schema_watch_func: "prostgles.schema_watch_func",
|
|
233
|
-
schema_watch_trigger: "prostgles_schema_watch_trigger_new"
|
|
234
|
-
} as const;
|
|
235
232
|
|
|
236
233
|
static SCHEMA_ALTERING_QUERIES = ['CREATE TABLE', 'ALTER TABLE', 'DROP TABLE', 'CREATE VIEW', 'DROP VIEW', 'ALTER VIEW', 'CREATE TABLE AS', 'SELECT INTO'];
|
|
237
234
|
|
|
@@ -279,6 +276,7 @@ export class PubSubManager {
|
|
|
279
276
|
);
|
|
280
277
|
|
|
281
278
|
is_super_user := EXISTS (select 1 from pg_user where usename = CURRENT_USER AND usesuper IS TRUE);
|
|
279
|
+
|
|
282
280
|
/**
|
|
283
281
|
* Delete stale app records
|
|
284
282
|
* */
|
|
@@ -300,7 +298,7 @@ export class PubSubManager {
|
|
|
300
298
|
ev_trg_needed := EXISTS (SELECT 1 FROM prostgles.apps WHERE watching_schema IS TRUE);
|
|
301
299
|
ev_trg_exists := EXISTS (
|
|
302
300
|
SELECT 1 FROM pg_catalog.pg_event_trigger
|
|
303
|
-
WHERE evtname = ${asValue(
|
|
301
|
+
WHERE evtname = ${asValue(DB_OBJ_NAMES.schema_watch_trigger)}
|
|
304
302
|
);
|
|
305
303
|
|
|
306
304
|
-- RAISE NOTICE ' ev_trg_needed %, ev_trg_exists %', ev_trg_needed, ev_trg_exists;
|
|
@@ -312,7 +310,7 @@ export class PubSubManager {
|
|
|
312
310
|
|
|
313
311
|
SELECT format(
|
|
314
312
|
$$ DROP EVENT TRIGGER IF EXISTS %I ; $$
|
|
315
|
-
, ${asValue(
|
|
313
|
+
, ${asValue(DB_OBJ_NAMES.schema_watch_trigger)}
|
|
316
314
|
)
|
|
317
315
|
INTO q;
|
|
318
316
|
|
|
@@ -329,11 +327,11 @@ export class PubSubManager {
|
|
|
329
327
|
AND ev_trg_exists IS FALSE
|
|
330
328
|
THEN
|
|
331
329
|
|
|
332
|
-
DROP EVENT TRIGGER IF EXISTS ${
|
|
333
|
-
CREATE EVENT TRIGGER ${
|
|
330
|
+
DROP EVENT TRIGGER IF EXISTS ${DB_OBJ_NAMES.schema_watch_trigger};
|
|
331
|
+
CREATE EVENT TRIGGER ${DB_OBJ_NAMES.schema_watch_trigger} ON ddl_command_end
|
|
334
332
|
WHEN TAG IN ('COMMENT', 'CREATE TABLE', 'ALTER TABLE', 'DROP TABLE', 'CREATE VIEW', 'DROP VIEW', 'ALTER VIEW', 'CREATE TABLE AS', 'SELECT INTO')
|
|
335
333
|
--WHEN TAG IN ('CREATE TABLE', 'ALTER TABLE', 'DROP TABLE', 'CREATE TRIGGER', 'DROP TRIGGER')
|
|
336
|
-
EXECUTE PROCEDURE ${
|
|
334
|
+
EXECUTE PROCEDURE ${DB_OBJ_NAMES.schema_watch_func}();
|
|
337
335
|
|
|
338
336
|
--RAISE NOTICE ' CREATED EVENT TRIGGER %', q;
|
|
339
337
|
END IF;
|
|
@@ -540,7 +538,7 @@ export class PubSubManager {
|
|
|
540
538
|
});
|
|
541
539
|
}
|
|
542
540
|
|
|
543
|
-
upsertSocket(socket: any
|
|
541
|
+
upsertSocket(socket: any) {
|
|
544
542
|
if (socket && !this.sockets[socket.id]) {
|
|
545
543
|
this.sockets[socket.id] = socket;
|
|
546
544
|
socket.on("disconnect", () => this.onSocketDisconnected(socket));
|
|
@@ -572,7 +570,7 @@ export class PubSubManager {
|
|
|
572
570
|
|
|
573
571
|
if (!synced_field) throw "synced_field missing from table_rules";
|
|
574
572
|
|
|
575
|
-
this.upsertSocket(socket
|
|
573
|
+
this.upsertSocket(socket);
|
|
576
574
|
|
|
577
575
|
const upsertSync = () => {
|
|
578
576
|
const newSync = {
|
|
@@ -692,7 +690,7 @@ export class PubSubManager {
|
|
|
692
690
|
|
|
693
691
|
const channel_name = `${this.socketChannelPreffix}.${table_info.name}.${JSON.stringify(filter)}.${JSON.stringify(params)}.${"m"}.sub`;
|
|
694
692
|
|
|
695
|
-
this.upsertSocket(socket
|
|
693
|
+
this.upsertSocket(socket);
|
|
696
694
|
|
|
697
695
|
const upsertSub = (newSubData: { table_name: string; condition: string; is_ready: boolean; parentSubParams: SubscriptionParams["parentSubParams"] }, isReadyOverride: boolean | undefined) => {
|
|
698
696
|
const { table_name, condition: _cond, is_ready = false, parentSubParams } = newSubData,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { PubSubManager } from "./PubSubManager";
|
|
2
|
+
export declare const DB_OBJ_NAMES: {
|
|
3
|
+
readonly trigger_add_remove_func: "prostgles.trigger_add_remove_func";
|
|
4
|
+
readonly data_watch_func: "prostgles.prostgles_trigger_function";
|
|
5
|
+
readonly schema_watch_func: "prostgles.schema_watch_func";
|
|
6
|
+
readonly schema_watch_trigger: "prostgles_schema_watch_trigger_new";
|
|
7
|
+
};
|
|
8
|
+
export declare const getInitQuery: (this: PubSubManager) => Promise<string>;
|
|
9
|
+
//# sourceMappingURL=getInitQuery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getInitQuery.d.ts","sourceRoot":"","sources":["getInitQuery.ts"],"names":[],"mappings":"AACA,OAAO,EAAgB,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAG9D,eAAO,MAAM,YAAY;;;;;CAKf,CAAC;AAEX,eAAO,MAAM,YAAY,SAAwB,aAAa,KAAG,QAAQ,MAAM,CA6gB9E,CAAA"}
|
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getInitQuery = exports.DB_OBJ_NAMES = void 0;
|
|
4
|
+
const PubSubManager_1 = require("./PubSubManager");
|
|
5
|
+
const { version } = require("../../package.json");
|
|
6
|
+
exports.DB_OBJ_NAMES = {
|
|
7
|
+
trigger_add_remove_func: "prostgles.trigger_add_remove_func",
|
|
8
|
+
data_watch_func: "prostgles.prostgles_trigger_function",
|
|
9
|
+
schema_watch_func: "prostgles.schema_watch_func",
|
|
10
|
+
schema_watch_trigger: "prostgles_schema_watch_trigger_new"
|
|
11
|
+
};
|
|
12
|
+
const getInitQuery = async function () {
|
|
13
|
+
const getQuery = async (withoutHash = false) => {
|
|
14
|
+
const { schema_md5 = "none" } = withoutHash ? {} : await this.db.oneOrNone("SELECT md5($1) as schema_md5", [await getQuery(true)]);
|
|
15
|
+
return `
|
|
16
|
+
|
|
17
|
+
BEGIN; -- ISOLATION LEVEL SERIALIZABLE;-- TRANSACTION ISOLATION LEVEL SERIALIZABLE;
|
|
18
|
+
|
|
19
|
+
--SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
|
|
20
|
+
|
|
21
|
+
/*
|
|
22
|
+
* ${PubSubManager_1.PubSubManager.EXCLUDE_QUERY_FROM_SCHEMA_WATCH_ID}
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
DO
|
|
26
|
+
$do$
|
|
27
|
+
BEGIN
|
|
28
|
+
|
|
29
|
+
/* Reduce deadlocks */
|
|
30
|
+
PERFORM pg_sleep(random());
|
|
31
|
+
|
|
32
|
+
/* Drop older version. */
|
|
33
|
+
IF EXISTS (SELECT 1 FROM information_schema.schemata WHERE schema_name = 'prostgles') THEN
|
|
34
|
+
|
|
35
|
+
/* The seemingly useless IF nesting is done to prevent pg evaluating the entire condition and throw a 'schema_md5 column does not exist' */
|
|
36
|
+
IF
|
|
37
|
+
/* Cannot check schema version */
|
|
38
|
+
NOT EXISTS(
|
|
39
|
+
SELECT 1
|
|
40
|
+
FROM information_schema.columns
|
|
41
|
+
WHERE table_schema = 'prostgles'
|
|
42
|
+
AND table_name = 'versions'
|
|
43
|
+
AND column_name = 'schema_md5'
|
|
44
|
+
)
|
|
45
|
+
THEN
|
|
46
|
+
DROP SCHEMA IF EXISTS prostgles CASCADE;
|
|
47
|
+
ELSIF
|
|
48
|
+
/* There is no newer schema */
|
|
49
|
+
NOT EXISTS(
|
|
50
|
+
SELECT 1
|
|
51
|
+
FROM prostgles.versions
|
|
52
|
+
WHERE schema_md5 <> ${(0, PubSubManager_1.asValue)(schema_md5)}
|
|
53
|
+
AND version >= ${(0, PubSubManager_1.asValue)(version)}
|
|
54
|
+
)
|
|
55
|
+
THEN
|
|
56
|
+
DROP SCHEMA IF EXISTS prostgles CASCADE;
|
|
57
|
+
|
|
58
|
+
END IF;
|
|
59
|
+
|
|
60
|
+
END IF;
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
IF NOT EXISTS (
|
|
64
|
+
SELECT 1
|
|
65
|
+
FROM information_schema.schemata
|
|
66
|
+
WHERE schema_name = 'prostgles'
|
|
67
|
+
)
|
|
68
|
+
THEN
|
|
69
|
+
|
|
70
|
+
CREATE SCHEMA IF NOT EXISTS prostgles;
|
|
71
|
+
|
|
72
|
+
CREATE TABLE IF NOT EXISTS prostgles.versions(
|
|
73
|
+
version TEXT PRIMARY KEY,
|
|
74
|
+
schema_md5 TEXT NOT NULL
|
|
75
|
+
);
|
|
76
|
+
COMMENT ON TABLE prostgles.versions IS 'Stores the prostgles schema creation query hash and package version number to identify when a newer schema needs to be re-created';
|
|
77
|
+
|
|
78
|
+
INSERT INTO prostgles.versions(version, schema_md5)
|
|
79
|
+
VALUES(${(0, PubSubManager_1.asValue)(version)}, ${(0, PubSubManager_1.asValue)(schema_md5)})
|
|
80
|
+
ON CONFLICT DO NOTHING;
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
CREATE OR REPLACE FUNCTION prostgles.random_string(length INTEGER DEFAULT 33) RETURNS TEXT AS $$
|
|
84
|
+
DECLARE
|
|
85
|
+
chars TEXT[] := '{0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z}';
|
|
86
|
+
result TEXT := '';
|
|
87
|
+
i INTEGER := 0;
|
|
88
|
+
BEGIN
|
|
89
|
+
|
|
90
|
+
IF length < 0 THEN
|
|
91
|
+
RAISE exception 'Given length cannot be less than 0';
|
|
92
|
+
END IF;
|
|
93
|
+
|
|
94
|
+
FOR i IN 1..length LOOP
|
|
95
|
+
result := result || chars[1+random()*(array_length(chars, 1)-1)];
|
|
96
|
+
END LOOP;
|
|
97
|
+
|
|
98
|
+
RETURN result;
|
|
99
|
+
|
|
100
|
+
END;
|
|
101
|
+
$$ language plpgsql;
|
|
102
|
+
COMMENT ON FUNCTION prostgles.random_string IS 'UUIDs without installing pgcrypto';
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
CREATE OR REPLACE FUNCTION prostgles.debug(VARIADIC args TEXT[]) RETURNS VOID AS $$
|
|
106
|
+
BEGIN
|
|
107
|
+
|
|
108
|
+
--PERFORM pg_notify('debug', concat_ws(' ', args));
|
|
109
|
+
IF
|
|
110
|
+
NOT EXISTS (
|
|
111
|
+
SELECT 1
|
|
112
|
+
FROM information_schema.tables
|
|
113
|
+
WHERE table_schema = 'prostgles'
|
|
114
|
+
AND table_name = 'debug'
|
|
115
|
+
)
|
|
116
|
+
THEN
|
|
117
|
+
CREATE TABLE IF NOT EXISTS prostgles.debug(m TEXT);
|
|
118
|
+
END IF;
|
|
119
|
+
|
|
120
|
+
INSERT INTO prostgles.debug(m) VALUES(concat_ws(' ', args));
|
|
121
|
+
|
|
122
|
+
END;
|
|
123
|
+
$$ LANGUAGE plpgsql;
|
|
124
|
+
COMMENT ON FUNCTION prostgles.debug IS 'Used for internal debugging';
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
CREATE TABLE IF NOT EXISTS prostgles.apps (
|
|
128
|
+
id TEXT PRIMARY KEY DEFAULT prostgles.random_string(),
|
|
129
|
+
added TIMESTAMP DEFAULT NOW(),
|
|
130
|
+
application_name TEXT,
|
|
131
|
+
last_check TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
132
|
+
last_check_ended TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
133
|
+
watching_schema BOOLEAN DEFAULT FALSE,
|
|
134
|
+
check_frequency_ms INTEGER NOT NULL
|
|
135
|
+
);
|
|
136
|
+
COMMENT ON TABLE prostgles.apps IS 'Keep track of prostgles server apps connected to db to combine common triggers. Heartbeat used due to no logout triggers in postgres';
|
|
137
|
+
|
|
138
|
+
CREATE TABLE IF NOT EXISTS prostgles.app_triggers (
|
|
139
|
+
app_id TEXT NOT NULL,
|
|
140
|
+
table_name TEXT NOT NULL,
|
|
141
|
+
condition TEXT NOT NULL,
|
|
142
|
+
|
|
143
|
+
/* The view from the root subscription, found in the condition.
|
|
144
|
+
We need this because old_table/new_table data is not reflected in the view inside the AFTER trigger
|
|
145
|
+
*/
|
|
146
|
+
related_view_name TEXT,
|
|
147
|
+
related_view_def TEXT, /* view definition */
|
|
148
|
+
|
|
149
|
+
inserted TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
150
|
+
last_used TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
151
|
+
PRIMARY KEY (app_id, table_name, condition) /* This unqique index limits the condition column value to be less than 'SELECT current_setting('block_size'); */
|
|
152
|
+
);
|
|
153
|
+
COMMENT ON TABLE prostgles.app_triggers IS 'Tables and conditions that are currently subscribed/synced';
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
CREATE OR REPLACE VIEW prostgles.v_triggers AS
|
|
157
|
+
SELECT *
|
|
158
|
+
, (ROW_NUMBER() OVER( ORDER BY table_name, condition ))::text AS id
|
|
159
|
+
, ROW_NUMBER() OVER(PARTITION BY app_id, table_name ORDER BY table_name, condition ) - 1 AS c_id
|
|
160
|
+
FROM prostgles.app_triggers;
|
|
161
|
+
COMMENT ON VIEW prostgles.v_triggers IS 'Augment trigger table with natural IDs and per app IDs';
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
CREATE OR REPLACE FUNCTION ${exports.DB_OBJ_NAMES.data_watch_func}() RETURNS TRIGGER
|
|
165
|
+
AS $$
|
|
166
|
+
|
|
167
|
+
DECLARE t_ids TEXT[];
|
|
168
|
+
DECLARE c_ids INTEGER[];
|
|
169
|
+
DECLARE err_c_ids INTEGER[];
|
|
170
|
+
DECLARE unions TEXT := '';
|
|
171
|
+
DECLARE query TEXT := '';
|
|
172
|
+
DECLARE nrw RECORD;
|
|
173
|
+
DECLARE erw RECORD;
|
|
174
|
+
DECLARE has_errors BOOLEAN := FALSE;
|
|
175
|
+
|
|
176
|
+
DECLARE err_text TEXT;
|
|
177
|
+
DECLARE err_detail TEXT;
|
|
178
|
+
DECLARE err_hint TEXT;
|
|
179
|
+
|
|
180
|
+
DECLARE view_def_query TEXT := '';
|
|
181
|
+
|
|
182
|
+
BEGIN
|
|
183
|
+
|
|
184
|
+
-- PERFORM pg_notify('debug', concat_ws(' ', 'TABLE', TG_TABLE_NAME, TG_OP));
|
|
185
|
+
|
|
186
|
+
SELECT string_agg(
|
|
187
|
+
format(
|
|
188
|
+
$c$
|
|
189
|
+
SELECT CASE WHEN EXISTS(
|
|
190
|
+
SELECT 1 FROM %I WHERE %s
|
|
191
|
+
) THEN %s::text END AS t_ids
|
|
192
|
+
$c$,
|
|
193
|
+
table_name,
|
|
194
|
+
condition,
|
|
195
|
+
id
|
|
196
|
+
),
|
|
197
|
+
E' UNION \n '
|
|
198
|
+
)
|
|
199
|
+
INTO unions
|
|
200
|
+
FROM prostgles.v_triggers
|
|
201
|
+
WHERE table_name = TG_TABLE_NAME;
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
/* unions = 'old_table union new_table' or any one of the tables */
|
|
205
|
+
IF unions IS NOT NULL THEN
|
|
206
|
+
|
|
207
|
+
SELECT
|
|
208
|
+
format(
|
|
209
|
+
E'WITH %I AS (\n %s \n) ',
|
|
210
|
+
TG_TABLE_NAME,
|
|
211
|
+
concat_ws(
|
|
212
|
+
E' UNION ALL \n ',
|
|
213
|
+
CASE WHEN (TG_OP = 'DELETE' OR TG_OP = 'UPDATE') THEN ' SELECT * FROM old_table ' END,
|
|
214
|
+
CASE WHEN (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN ' SELECT * FROM new_table ' END
|
|
215
|
+
)
|
|
216
|
+
)
|
|
217
|
+
||
|
|
218
|
+
COALESCE((
|
|
219
|
+
SELECT ', ' || string_agg(format(E' %I AS ( \n %s \n ) ', related_view_name, related_view_def), ', ')
|
|
220
|
+
FROM (
|
|
221
|
+
SELECT DISTINCT related_view_name, related_view_def
|
|
222
|
+
FROM prostgles.v_triggers
|
|
223
|
+
WHERE table_name = TG_TABLE_NAME
|
|
224
|
+
AND related_view_name IS NOT NULL
|
|
225
|
+
AND related_view_def IS NOT NULL
|
|
226
|
+
) t
|
|
227
|
+
), '')
|
|
228
|
+
||
|
|
229
|
+
format(
|
|
230
|
+
$c$
|
|
231
|
+
SELECT ARRAY_AGG(DISTINCT t.t_ids)
|
|
232
|
+
FROM (
|
|
233
|
+
%s
|
|
234
|
+
) t
|
|
235
|
+
$c$, unions
|
|
236
|
+
)
|
|
237
|
+
INTO query;
|
|
238
|
+
|
|
239
|
+
BEGIN
|
|
240
|
+
EXECUTE query INTO t_ids;
|
|
241
|
+
|
|
242
|
+
--RAISE NOTICE 'trigger fired ok';
|
|
243
|
+
|
|
244
|
+
EXCEPTION WHEN OTHERS THEN
|
|
245
|
+
|
|
246
|
+
has_errors := TRUE;
|
|
247
|
+
|
|
248
|
+
GET STACKED DIAGNOSTICS
|
|
249
|
+
err_text = MESSAGE_TEXT,
|
|
250
|
+
err_detail = PG_EXCEPTION_DETAIL,
|
|
251
|
+
err_hint = PG_EXCEPTION_HINT;
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
END;
|
|
255
|
+
|
|
256
|
+
--RAISE NOTICE 'has_errors: % ', has_errors;
|
|
257
|
+
--RAISE NOTICE 'unions: % , cids: %', unions, c_ids;
|
|
258
|
+
|
|
259
|
+
IF (t_ids IS NOT NULL OR has_errors) THEN
|
|
260
|
+
|
|
261
|
+
FOR nrw IN
|
|
262
|
+
SELECT app_id, string_agg(c_id::text, ',') as cids
|
|
263
|
+
FROM prostgles.v_triggers
|
|
264
|
+
WHERE id = ANY(t_ids)
|
|
265
|
+
OR has_errors
|
|
266
|
+
GROUP BY app_id
|
|
267
|
+
LOOP
|
|
268
|
+
|
|
269
|
+
PERFORM pg_notify(
|
|
270
|
+
${(0, PubSubManager_1.asValue)(this.NOTIF_CHANNEL.preffix)} || nrw.app_id ,
|
|
271
|
+
concat_ws(
|
|
272
|
+
${(0, PubSubManager_1.asValue)(PubSubManager_1.PubSubManager.DELIMITER)},
|
|
273
|
+
|
|
274
|
+
${(0, PubSubManager_1.asValue)(this.NOTIF_TYPE.data)},
|
|
275
|
+
COALESCE(TG_TABLE_NAME, 'MISSING'),
|
|
276
|
+
COALESCE(TG_OP, 'MISSING'),
|
|
277
|
+
CASE WHEN has_errors
|
|
278
|
+
THEN concat_ws('; ', 'error', err_text, err_detail, err_hint, 'query: ' || query )
|
|
279
|
+
ELSE COALESCE(nrw.cids, '')
|
|
280
|
+
END
|
|
281
|
+
${this.dboBuilder.prostgles.opts.DEBUG_MODE ? (", (select json_agg(t)::TEXT FROM (SELECT * from old_table) t), query") : ""}
|
|
282
|
+
)
|
|
283
|
+
);
|
|
284
|
+
END LOOP;
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
IF has_errors THEN
|
|
288
|
+
|
|
289
|
+
DELETE FROM prostgles.app_triggers;
|
|
290
|
+
RAISE NOTICE 'trigger dropped due to exception: % % %', err_text, err_detail, err_hint;
|
|
291
|
+
|
|
292
|
+
END IF;
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
END IF;
|
|
296
|
+
END IF;
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
RETURN NULL;
|
|
300
|
+
|
|
301
|
+
/*
|
|
302
|
+
EXCEPTION WHEN OTHERS THEN
|
|
303
|
+
DELETE FROM prostgles.app_triggers; -- delete all or will need to loop through all conditions to find issue;
|
|
304
|
+
RAISE NOTICE 'trigger dropped due to exception';
|
|
305
|
+
${"--EXCEPTION_WHEN_COLUMN_WAS_RENAMED_THEN_DROP_TRIGGER"};
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
RETURN NULL;
|
|
310
|
+
*/
|
|
311
|
+
END;
|
|
312
|
+
|
|
313
|
+
--COMMIT;
|
|
314
|
+
$$ LANGUAGE plpgsql;
|
|
315
|
+
COMMENT ON FUNCTION ${exports.DB_OBJ_NAMES.data_watch_func} IS 'Prostgles internal function used to notify when data in the table changed';
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
CREATE OR REPLACE FUNCTION ${exports.DB_OBJ_NAMES.trigger_add_remove_func}() RETURNS TRIGGER
|
|
320
|
+
AS $$
|
|
321
|
+
|
|
322
|
+
DECLARE operations TEXT[] := ARRAY['insert', 'update', 'delete'];
|
|
323
|
+
DECLARE op TEXT;
|
|
324
|
+
DECLARE query TEXT;
|
|
325
|
+
DECLARE trw RECORD;
|
|
326
|
+
|
|
327
|
+
BEGIN
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
--RAISE NOTICE 'prostgles.app_triggers % ', TG_OP;
|
|
331
|
+
|
|
332
|
+
/* If no other listeners on table then DROP triggers */
|
|
333
|
+
IF TG_OP = 'DELETE' THEN
|
|
334
|
+
|
|
335
|
+
--RAISE NOTICE 'DELETE trigger_add_remove_func table: % ', ' ' || COALESCE((SELECT concat_ws(' ', string_agg(table_name, ' & '), count(*), min(inserted) ) FROM prostgles.app_triggers) , ' 0 ');
|
|
336
|
+
--RAISE NOTICE 'DELETE trigger_add_remove_func old_table: % ', '' || COALESCE((SELECT concat_ws(' ', string_agg(table_name, ' & '), count(*), min(inserted) ) FROM old_table), ' 0 ');
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
/* Drop actual triggers if needed */
|
|
340
|
+
FOR trw IN
|
|
341
|
+
SELECT DISTINCT table_name FROM old_table ot
|
|
342
|
+
WHERE NOT EXISTS (
|
|
343
|
+
SELECT 1 FROM prostgles.app_triggers t
|
|
344
|
+
WHERE t.table_name = ot.table_name
|
|
345
|
+
)
|
|
346
|
+
LOOP
|
|
347
|
+
|
|
348
|
+
FOREACH op IN ARRAY operations
|
|
349
|
+
LOOP
|
|
350
|
+
--RAISE NOTICE ' DROP DATA TRIGGER FOR: % ', trw.table_name;
|
|
351
|
+
EXECUTE format(' DROP TRIGGER IF EXISTS %I ON %I ;' , 'prostgles_triggers_' || trw.table_name || '_' || op, trw.table_name);
|
|
352
|
+
END LOOP;
|
|
353
|
+
|
|
354
|
+
END LOOP;
|
|
355
|
+
|
|
356
|
+
/* If newly added listeners on table then CREATE triggers */
|
|
357
|
+
ELSIF TG_OP = 'INSERT' THEN
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
--RAISE NOTICE 'INSERT trigger_add_remove_func table: % ', ' ' || COALESCE((SELECT concat_ws(' ', string_agg(table_name, ' & '), count(*), min(inserted) ) FROM prostgles.triggers) , ' 0 ');
|
|
361
|
+
--RAISE NOTICE 'INSERT trigger_add_remove_func new_table: % ', '' || COALESCE((SELECT concat_ws(' ', string_agg(table_name, ' & '), count(*), min(inserted) ) FROM new_table), ' 0 ');
|
|
362
|
+
|
|
363
|
+
/* Loop through newly added tables */
|
|
364
|
+
FOR trw IN
|
|
365
|
+
|
|
366
|
+
SELECT DISTINCT table_name
|
|
367
|
+
FROM new_table nt
|
|
368
|
+
|
|
369
|
+
/* Table did not exist prior to this insert */
|
|
370
|
+
WHERE NOT EXISTS (
|
|
371
|
+
SELECT 1
|
|
372
|
+
FROM prostgles.app_triggers t
|
|
373
|
+
WHERE t.table_name = nt.table_name
|
|
374
|
+
AND t.inserted < nt.inserted -- exclude current record (this is an after trigger). Turn into before trigger?
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
/* Table is valid */
|
|
378
|
+
AND EXISTS (
|
|
379
|
+
SELECT 1
|
|
380
|
+
FROM information_schema.tables
|
|
381
|
+
WHERE table_schema = 'public'
|
|
382
|
+
AND table_name = nt.table_name
|
|
383
|
+
)
|
|
384
|
+
LOOP
|
|
385
|
+
|
|
386
|
+
/*
|
|
387
|
+
RAISE NOTICE ' CREATE DATA TRIGGER FOR: % TABLE EXISTS?', trw.table_name, SELECT EXISTS (
|
|
388
|
+
SELECT 1
|
|
389
|
+
FROM information_schema.tables
|
|
390
|
+
WHERE table_schema = 'public'
|
|
391
|
+
AND table_name = nt.table_name
|
|
392
|
+
);
|
|
393
|
+
*/
|
|
394
|
+
|
|
395
|
+
query := format(
|
|
396
|
+
$q$
|
|
397
|
+
DROP TRIGGER IF EXISTS %1$I ON %2$I;
|
|
398
|
+
CREATE TRIGGER %1$I
|
|
399
|
+
AFTER INSERT ON %2$I
|
|
400
|
+
REFERENCING NEW TABLE AS new_table
|
|
401
|
+
FOR EACH STATEMENT EXECUTE PROCEDURE ${exports.DB_OBJ_NAMES.data_watch_func}();
|
|
402
|
+
COMMENT ON TRIGGER %1$I ON %2$I IS 'Prostgles internal trigger used to notify when data in the table changed';
|
|
403
|
+
$q$,
|
|
404
|
+
'prostgles_triggers_' || trw.table_name || '_insert', trw.table_name
|
|
405
|
+
) || format(
|
|
406
|
+
$q$
|
|
407
|
+
DROP TRIGGER IF EXISTS %1$I ON %2$I;
|
|
408
|
+
CREATE TRIGGER %1$I
|
|
409
|
+
AFTER UPDATE ON %2$I
|
|
410
|
+
REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
|
|
411
|
+
FOR EACH STATEMENT EXECUTE PROCEDURE ${exports.DB_OBJ_NAMES.data_watch_func}();
|
|
412
|
+
COMMENT ON TRIGGER %1$I ON %2$I IS 'Prostgles internal trigger used to notify when data in the table changed';
|
|
413
|
+
$q$,
|
|
414
|
+
'prostgles_triggers_' || trw.table_name || '_update', trw.table_name
|
|
415
|
+
) || format(
|
|
416
|
+
$q$
|
|
417
|
+
DROP TRIGGER IF EXISTS %1$I ON %2$I;
|
|
418
|
+
CREATE TRIGGER %1$I
|
|
419
|
+
AFTER DELETE ON %2$I
|
|
420
|
+
REFERENCING OLD TABLE AS old_table
|
|
421
|
+
FOR EACH STATEMENT EXECUTE PROCEDURE ${exports.DB_OBJ_NAMES.data_watch_func}();
|
|
422
|
+
COMMENT ON TRIGGER %1$I ON %2$I IS 'Prostgles internal trigger used to notify when data in the table changed';
|
|
423
|
+
$q$,
|
|
424
|
+
'prostgles_triggers_' || trw.table_name || '_delete', trw.table_name
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
--RAISE NOTICE ' % ', query;
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
query := format(
|
|
431
|
+
$q$
|
|
432
|
+
DO $e$
|
|
433
|
+
BEGIN
|
|
434
|
+
|
|
435
|
+
IF EXISTS (
|
|
436
|
+
SELECT 1
|
|
437
|
+
FROM information_schema.tables
|
|
438
|
+
WHERE table_schema = 'public'
|
|
439
|
+
AND table_name = %L
|
|
440
|
+
) THEN
|
|
441
|
+
|
|
442
|
+
%s
|
|
443
|
+
|
|
444
|
+
END IF;
|
|
445
|
+
|
|
446
|
+
END $e$;
|
|
447
|
+
$q$,
|
|
448
|
+
trw.table_name,
|
|
449
|
+
query
|
|
450
|
+
) ;
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
EXECUTE query;
|
|
454
|
+
|
|
455
|
+
END LOOP;
|
|
456
|
+
|
|
457
|
+
END IF;
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
RETURN NULL;
|
|
461
|
+
END;
|
|
462
|
+
|
|
463
|
+
$$ LANGUAGE plpgsql;
|
|
464
|
+
COMMENT ON FUNCTION ${exports.DB_OBJ_NAMES.trigger_add_remove_func} IS 'Used to add/remove table watch triggers concurrently ';
|
|
465
|
+
|
|
466
|
+
DROP TRIGGER IF EXISTS prostgles_triggers_insert ON prostgles.app_triggers;
|
|
467
|
+
CREATE TRIGGER prostgles_triggers_insert
|
|
468
|
+
AFTER INSERT ON prostgles.app_triggers
|
|
469
|
+
REFERENCING NEW TABLE AS new_table
|
|
470
|
+
FOR EACH STATEMENT EXECUTE PROCEDURE ${exports.DB_OBJ_NAMES.trigger_add_remove_func}();
|
|
471
|
+
|
|
472
|
+
DROP TRIGGER IF EXISTS prostgles_triggers_delete ON prostgles.app_triggers;
|
|
473
|
+
CREATE TRIGGER prostgles_triggers_delete
|
|
474
|
+
AFTER DELETE ON prostgles.app_triggers
|
|
475
|
+
REFERENCING OLD TABLE AS old_table
|
|
476
|
+
FOR EACH STATEMENT EXECUTE PROCEDURE ${exports.DB_OBJ_NAMES.trigger_add_remove_func}();
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
CREATE OR REPLACE FUNCTION ${exports.DB_OBJ_NAMES.schema_watch_func}() RETURNS event_trigger AS $$
|
|
480
|
+
|
|
481
|
+
DECLARE curr_query TEXT := '';
|
|
482
|
+
DECLARE arw RECORD;
|
|
483
|
+
|
|
484
|
+
BEGIN
|
|
485
|
+
|
|
486
|
+
--RAISE NOTICE 'SCHEMA_WATCH: %', tg_tag;
|
|
487
|
+
|
|
488
|
+
/*
|
|
489
|
+
This event trigger will outlive a prostgles app instance.
|
|
490
|
+
Must ensure it only fires if an app instance is running
|
|
491
|
+
*/
|
|
492
|
+
IF
|
|
493
|
+
EXISTS (
|
|
494
|
+
SELECT 1
|
|
495
|
+
FROM information_schema.tables
|
|
496
|
+
WHERE table_schema = 'prostgles'
|
|
497
|
+
AND table_name = 'apps'
|
|
498
|
+
)
|
|
499
|
+
THEN
|
|
500
|
+
|
|
501
|
+
SELECT LEFT(COALESCE(current_query(), ''), 5000)
|
|
502
|
+
INTO curr_query;
|
|
503
|
+
|
|
504
|
+
FOR arw IN
|
|
505
|
+
SELECT * FROM prostgles.apps WHERE watching_schema IS TRUE
|
|
506
|
+
|
|
507
|
+
LOOP
|
|
508
|
+
PERFORM pg_notify(
|
|
509
|
+
${(0, PubSubManager_1.asValue)(this.NOTIF_CHANNEL.preffix)} || arw.id,
|
|
510
|
+
concat_ws(
|
|
511
|
+
${(0, PubSubManager_1.asValue)(PubSubManager_1.PubSubManager.DELIMITER)},
|
|
512
|
+
${(0, PubSubManager_1.asValue)(this.NOTIF_TYPE.schema)}, tg_tag , TG_event, curr_query
|
|
513
|
+
)
|
|
514
|
+
);
|
|
515
|
+
END LOOP;
|
|
516
|
+
|
|
517
|
+
END IF;
|
|
518
|
+
|
|
519
|
+
END;
|
|
520
|
+
$$ LANGUAGE plpgsql;
|
|
521
|
+
COMMENT ON FUNCTION ${exports.DB_OBJ_NAMES.schema_watch_func} IS 'Prostgles internal function used to notify when schema has changed';
|
|
522
|
+
|
|
523
|
+
END IF;
|
|
524
|
+
|
|
525
|
+
END
|
|
526
|
+
$do$;
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
COMMIT;
|
|
530
|
+
`;
|
|
531
|
+
};
|
|
532
|
+
const res = getQuery();
|
|
533
|
+
(0, PubSubManager_1.log)(res);
|
|
534
|
+
return res;
|
|
535
|
+
};
|
|
536
|
+
exports.getInitQuery = getInitQuery;
|