prostgles-server 2.0.153 → 2.0.156
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/DBEventsManager.d.ts.map +1 -1
- package/dist/DBEventsManager.js +1 -2
- package/dist/DBEventsManager.js.map +1 -1
- package/dist/DboBuilder.d.ts +0 -1
- package/dist/DboBuilder.d.ts.map +1 -1
- package/dist/DboBuilder.js +4 -8
- package/dist/DboBuilder.js.map +1 -1
- package/dist/FileManager.js +1 -2
- package/dist/FileManager.js.map +1 -1
- package/dist/Filtering.d.ts.map +1 -1
- package/dist/Filtering.js +1 -1
- package/dist/Filtering.js.map +1 -1
- package/dist/Prostgles.d.ts.map +1 -1
- package/dist/Prostgles.js +6 -6
- package/dist/Prostgles.js.map +1 -1
- package/dist/PubSubManager.d.ts +1 -1
- package/dist/PubSubManager.d.ts.map +1 -1
- package/dist/PubSubManager.js +4 -3
- package/dist/PubSubManager.js.map +1 -1
- package/dist/QueryBuilder.d.ts.map +1 -1
- package/dist/QueryBuilder.js +4 -2
- package/dist/QueryBuilder.js.map +1 -1
- package/dist/TableConfig.d.ts.map +1 -1
- package/dist/TableConfig.js +2 -3
- package/dist/TableConfig.js.map +1 -1
- package/lib/DBEventsManager.ts +2 -2
- package/lib/DboBuilder.ts +3 -5
- package/lib/FileManager.ts +2 -2
- package/lib/Filtering.ts +2 -2
- package/lib/Prostgles.ts +2 -2
- package/lib/PubSubManager.ts +766 -765
- package/lib/QueryBuilder.ts +4 -3
- package/lib/TableConfig.ts +3 -3
- package/package.json +2 -2
- package/tests/client/PID.txt +1 -1
- package/tests/client/package-lock.json +15 -15
- package/tests/client/package.json +1 -1
- package/tests/client_only_queries.js +16 -8
- package/tests/client_only_queries.ts +18 -8
- package/tests/server/package-lock.json +3 -3
package/lib/PubSubManager.ts
CHANGED
|
@@ -18,187 +18,187 @@ import { ClientExpressData, syncData } from "./SyncReplication";
|
|
|
18
18
|
|
|
19
19
|
type PGP = pgPromise.IMain<{}, pg.IClient>;
|
|
20
20
|
let pgp: PGP = pgPromise({
|
|
21
|
-
|
|
21
|
+
promiseLib: Bluebird
|
|
22
22
|
});
|
|
23
23
|
export const asValue = (v: any) => pgp.as.format("$1", [v]);
|
|
24
24
|
export const DEFAULT_SYNC_BATCH_SIZE = 50;
|
|
25
25
|
|
|
26
26
|
export const log = (...args: any[]) => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
if (process.env.TEST_TYPE) {
|
|
28
|
+
console.log(...args)
|
|
29
|
+
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
export type BasicCallback = (err?: any, res?: any) => void
|
|
33
33
|
|
|
34
34
|
export type SyncParams = {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
35
|
+
socket_id: string;
|
|
36
|
+
channel_name: string;
|
|
37
|
+
table_name: string;
|
|
38
|
+
table_rules?: TableRule;
|
|
39
|
+
synced_field: string;
|
|
40
|
+
allow_delete: boolean;
|
|
41
|
+
id_fields: string[];
|
|
42
|
+
batch_size: number;
|
|
43
|
+
filter: object;
|
|
44
|
+
params: {
|
|
45
|
+
select: FieldFilter
|
|
46
|
+
};
|
|
47
|
+
condition: string;
|
|
48
|
+
wal?: WAL,
|
|
49
|
+
throttle?: number;
|
|
50
|
+
lr?: AnyObject;
|
|
51
|
+
last_synced: number;
|
|
52
|
+
is_syncing: boolean;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
type AddSyncParams = {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
56
|
+
socket: any;
|
|
57
|
+
table_info: TableInfo;
|
|
58
|
+
table_rules: TableRule;
|
|
59
|
+
synced_field: string;
|
|
60
|
+
allow_delete?: boolean;
|
|
61
|
+
id_fields: string[];
|
|
62
|
+
filter: object;
|
|
63
|
+
params: {
|
|
64
|
+
select: FieldFilter
|
|
65
|
+
};
|
|
66
|
+
condition: string;
|
|
67
|
+
throttle?: number;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
type SubscriptionParams = {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
71
|
+
socket_id?: string;
|
|
72
|
+
channel_name: string;
|
|
73
|
+
table_name: string;
|
|
74
|
+
socket: PRGLIOSocket | undefined;
|
|
75
|
+
table_info: TableOrViewInfo;
|
|
76
|
+
table_rules?: TableRule;
|
|
77
|
+
filter: object;
|
|
78
|
+
params: SelectParams;
|
|
79
|
+
func?: (data: any) => any;
|
|
80
|
+
throttle?: number;
|
|
81
|
+
last_throttled: number;
|
|
82
|
+
is_throttling?: any;
|
|
83
|
+
is_ready?: boolean;
|
|
84
|
+
// subOne?: boolean;
|
|
85
85
|
}
|
|
86
86
|
type AddSubscriptionParams = SubscriptionParams & {
|
|
87
|
-
|
|
87
|
+
condition: string;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
export type PubSubManagerOptions = {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
91
|
+
dboBuilder: DboBuilder;
|
|
92
|
+
db: DB;
|
|
93
|
+
dbo: DbHandler;
|
|
94
|
+
wsChannelNamePrefix?: string;
|
|
95
|
+
pgChannelName?: string;
|
|
96
|
+
onSchemaChange?: (event: { command: string; query: string }) => void;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
export class PubSubManager {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
this.db = db;
|
|
120
|
-
this.dbo = dbo;
|
|
121
|
-
this.onSchemaChange = onSchemaChange;
|
|
122
|
-
this.dboBuilder = dboBuilder;
|
|
123
|
-
|
|
124
|
-
this.sockets = {};
|
|
125
|
-
this.subs = {};
|
|
126
|
-
this.syncs = [];
|
|
127
|
-
this.socketChannelPreffix = wsChannelNamePrefix || "_psqlWS_";
|
|
128
|
-
|
|
129
|
-
log("Created PubSubManager");
|
|
100
|
+
static DELIMITER = '|$prstgls$|';
|
|
101
|
+
|
|
102
|
+
dboBuilder: DboBuilder;
|
|
103
|
+
db: DB;
|
|
104
|
+
dbo: DbHandler;
|
|
105
|
+
_triggers?: Record<string, string[]>;
|
|
106
|
+
sockets: any;
|
|
107
|
+
subs: { [ke: string]: { [ke: string]: { subs: SubscriptionParams[] } } };
|
|
108
|
+
syncs: SyncParams[];
|
|
109
|
+
socketChannelPreffix: string;
|
|
110
|
+
onSchemaChange?: ((event: { command: string; query: string }) => void) = undefined;
|
|
111
|
+
|
|
112
|
+
postgresNotifListenManager?: PostgresNotifListenManager;
|
|
113
|
+
|
|
114
|
+
private constructor(options: PubSubManagerOptions) {
|
|
115
|
+
const { db, dbo, wsChannelNamePrefix, pgChannelName, onSchemaChange, dboBuilder } = options;
|
|
116
|
+
if (!db || !dbo) {
|
|
117
|
+
throw 'MISSING: db_pg, db';
|
|
130
118
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
119
|
+
this.db = db;
|
|
120
|
+
this.dbo = dbo;
|
|
121
|
+
this.onSchemaChange = onSchemaChange;
|
|
122
|
+
this.dboBuilder = dboBuilder;
|
|
123
|
+
|
|
124
|
+
this.sockets = {};
|
|
125
|
+
this.subs = {};
|
|
126
|
+
this.syncs = [];
|
|
127
|
+
this.socketChannelPreffix = wsChannelNamePrefix || "_psqlWS_";
|
|
128
|
+
|
|
129
|
+
log("Created PubSubManager");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
NOTIF_TYPE = {
|
|
133
|
+
data: "data_has_changed",
|
|
134
|
+
schema: "schema_has_changed"
|
|
135
|
+
}
|
|
136
|
+
NOTIF_CHANNEL = {
|
|
137
|
+
preffix: 'prostgles_',
|
|
138
|
+
getFull: (appID?: string) => {
|
|
139
|
+
if (!this.appID && !appID) throw "No appID";
|
|
140
|
+
return this.NOTIF_CHANNEL.preffix + (appID || this.appID);
|
|
142
141
|
}
|
|
142
|
+
}
|
|
143
143
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
appCheckFrequencyMS = 10 * 1000;
|
|
147
|
-
appCheck?: ReturnType<typeof setInterval>;
|
|
148
|
-
|
|
149
|
-
|
|
144
|
+
private appID?: string;
|
|
150
145
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
// ,client_hostname
|
|
154
|
-
// ,client_port
|
|
155
|
-
// ,backend_start
|
|
156
|
-
// ,query_start
|
|
157
|
-
// ,query
|
|
158
|
-
// ,state
|
|
159
|
-
|
|
160
|
-
// console.log(await _db.any(`
|
|
161
|
-
// SELECT pid, application_name, state
|
|
162
|
-
// FROM pg_stat_activity
|
|
163
|
-
// WHERE application_name IS NOT NULL AND application_name != '' -- state = 'active';
|
|
164
|
-
// `))
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
public static create = async (options: PubSubManagerOptions) => {
|
|
168
|
-
const res = new PubSubManager(options);
|
|
169
|
-
return await res.init();
|
|
170
|
-
}
|
|
146
|
+
appCheckFrequencyMS = 10 * 1000;
|
|
147
|
+
appCheck?: ReturnType<typeof setInterval>;
|
|
171
148
|
|
|
172
|
-
destroyed = false;
|
|
173
|
-
destroy = () => {
|
|
174
|
-
this.destroyed = true;
|
|
175
|
-
if(this.appCheck){
|
|
176
|
-
clearInterval(this.appCheck);
|
|
177
|
-
}
|
|
178
|
-
this.onSocketDisconnected();
|
|
179
|
-
// if(this.postgresNotifListenManager){
|
|
180
|
-
// this.postgresNotifListenManager.stopListening();
|
|
181
|
-
// }
|
|
182
|
-
if(!this.postgresNotifListenManager) throw "this.postgresNotifListenManager missing"
|
|
183
|
-
this.postgresNotifListenManager.destroy();
|
|
184
|
-
}
|
|
185
149
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
150
|
+
|
|
151
|
+
// ,datname
|
|
152
|
+
// ,usename
|
|
153
|
+
// ,client_hostname
|
|
154
|
+
// ,client_port
|
|
155
|
+
// ,backend_start
|
|
156
|
+
// ,query_start
|
|
157
|
+
// ,query
|
|
158
|
+
// ,state
|
|
159
|
+
|
|
160
|
+
// console.log(await _db.any(`
|
|
161
|
+
// SELECT pid, application_name, state
|
|
162
|
+
// FROM pg_stat_activity
|
|
163
|
+
// WHERE application_name IS NOT NULL AND application_name != '' -- state = 'active';
|
|
164
|
+
// `))
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
public static create = async (options: PubSubManagerOptions) => {
|
|
168
|
+
const res = new PubSubManager(options);
|
|
169
|
+
return await res.init();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
destroyed = false;
|
|
173
|
+
destroy = () => {
|
|
174
|
+
this.destroyed = true;
|
|
175
|
+
if (this.appCheck) {
|
|
176
|
+
clearInterval(this.appCheck);
|
|
177
|
+
}
|
|
178
|
+
this.onSocketDisconnected();
|
|
179
|
+
// if(this.postgresNotifListenManager){
|
|
180
|
+
// this.postgresNotifListenManager.stopListening();
|
|
181
|
+
// }
|
|
182
|
+
if (!this.postgresNotifListenManager) throw "this.postgresNotifListenManager missing"
|
|
183
|
+
this.postgresNotifListenManager.destroy();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
canContinue = () => {
|
|
187
|
+
if (this.destroyed) {
|
|
188
|
+
console.trace("Could not start destroyed instance");
|
|
189
|
+
return false
|
|
192
190
|
}
|
|
191
|
+
return true
|
|
192
|
+
}
|
|
193
193
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
194
|
+
appChecking = false;
|
|
195
|
+
init = async (): Promise<PubSubManager | undefined> => {
|
|
196
|
+
if (!this.canContinue()) return undefined;
|
|
197
197
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
198
|
+
try {
|
|
199
|
+
const schema_version = 4;
|
|
200
|
+
|
|
201
|
+
const q = `
|
|
202
202
|
BEGIN; -- ISOLATION LEVEL SERIALIZABLE;-- TRANSACTION ISOLATION LEVEL SERIALIZABLE;
|
|
203
203
|
|
|
204
204
|
--SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
|
|
@@ -698,48 +698,48 @@ export class PubSubManager {
|
|
|
698
698
|
COMMIT;
|
|
699
699
|
`;
|
|
700
700
|
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
/* Prepare App id */
|
|
714
|
-
if(!this.appID){
|
|
715
|
-
const raw = await this.db.one(
|
|
716
|
-
"INSERT INTO prostgles.apps (check_frequency_ms, watching_schema, application_name) VALUES($1, $2, current_setting('application_name')) RETURNING *; "
|
|
717
|
-
, [this.appCheckFrequencyMS, Boolean(this.onSchemaChange)]
|
|
718
|
-
);
|
|
719
|
-
this.appID = raw.id;
|
|
720
|
-
|
|
721
|
-
if(!this.appCheck) {
|
|
722
|
-
|
|
723
|
-
this.appCheck = setInterval(async () => {
|
|
724
|
-
let appQ = "";
|
|
725
|
-
try { // drop owned by api
|
|
701
|
+
// const prgl_exists = await this.db.oneOrNone(`
|
|
702
|
+
// DROP SCHEMA IF EXISTS prostgles CASCADE;
|
|
703
|
+
// SELECT 1 FROM information_schema.schemata WHERE schema_name = 'prostgles'
|
|
704
|
+
// `);
|
|
705
|
+
|
|
706
|
+
// if(!prgl_exists){
|
|
707
|
+
// await this.db.any(q);
|
|
708
|
+
// }
|
|
709
|
+
await this.db.any(q);
|
|
710
|
+
if (!this.canContinue()) return;
|
|
726
711
|
|
|
727
|
-
this.appChecking = true;
|
|
728
712
|
|
|
729
|
-
|
|
730
|
-
|
|
713
|
+
/* Prepare App id */
|
|
714
|
+
if (!this.appID) {
|
|
715
|
+
const raw = await this.db.one(
|
|
716
|
+
"INSERT INTO prostgles.apps (check_frequency_ms, watching_schema, application_name) VALUES($1, $2, current_setting('application_name')) RETURNING *; "
|
|
717
|
+
, [this.appCheckFrequencyMS, Boolean(this.onSchemaChange)]
|
|
718
|
+
);
|
|
719
|
+
this.appID = raw.id;
|
|
731
720
|
|
|
732
|
-
|
|
733
|
-
|
|
721
|
+
if (!this.appCheck) {
|
|
722
|
+
|
|
723
|
+
this.appCheck = setInterval(async () => {
|
|
724
|
+
let appQ = "";
|
|
725
|
+
try { // drop owned by api
|
|
726
|
+
|
|
727
|
+
this.appChecking = true;
|
|
728
|
+
|
|
729
|
+
let trgUpdateLastUsed = "",
|
|
730
|
+
listeners = this.getActiveListeners();
|
|
731
|
+
|
|
732
|
+
if (listeners.length) {
|
|
733
|
+
trgUpdateLastUsed = `
|
|
734
734
|
UPDATE prostgles.app_triggers
|
|
735
735
|
SET last_used = CASE WHEN (table_name, condition) IN (
|
|
736
|
-
${listeners.map(l => ` ( ${asValue(l.table_name)}, ${asValue(l.condition)} ) `
|
|
736
|
+
${listeners.map(l => ` ( ${asValue(l.table_name)}, ${asValue(l.condition)} ) `).join(", ")}
|
|
737
737
|
) THEN NOW() ELSE last_used END
|
|
738
738
|
WHERE app_id = ${asValue(this.appID)};
|
|
739
739
|
`
|
|
740
|
-
|
|
740
|
+
}
|
|
741
741
|
|
|
742
|
-
|
|
742
|
+
appQ = `
|
|
743
743
|
|
|
744
744
|
DO $$
|
|
745
745
|
BEGIN
|
|
@@ -812,46 +812,46 @@ export class PubSubManager {
|
|
|
812
812
|
--COMMIT;
|
|
813
813
|
END $$;
|
|
814
814
|
`
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
this.appChecking = false;
|
|
822
|
-
}, 0.8 * this.appCheckFrequencyMS);
|
|
823
|
-
}
|
|
815
|
+
await this.db.any(appQ);
|
|
816
|
+
log("updated last_check");
|
|
817
|
+
} catch (e) {
|
|
818
|
+
console.error("appCheck FAILED: \n", e, appQ);
|
|
824
819
|
}
|
|
825
820
|
|
|
826
|
-
this.
|
|
821
|
+
this.appChecking = false;
|
|
822
|
+
}, 0.8 * this.appCheckFrequencyMS);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
827
825
|
|
|
828
|
-
|
|
826
|
+
this.postgresNotifListenManager = new PostgresNotifListenManager(this.db, this.notifListener, this.NOTIF_CHANNEL.getFull());
|
|
829
827
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
console.error("PubSubManager init failed: ", e);
|
|
834
|
-
}
|
|
835
|
-
}
|
|
828
|
+
await this.prepareTriggers()
|
|
829
|
+
|
|
830
|
+
return this;
|
|
836
831
|
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
832
|
+
} catch (e) {
|
|
833
|
+
console.error("PubSubManager init failed: ", e);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
DB_OBJ_NAMES = {
|
|
838
|
+
trigger_add_remove_func: "prostgles.trigger_add_remove_func",
|
|
839
|
+
data_watch_func: "prostgles.prostgles_trigger_function",
|
|
840
|
+
schema_watch_func: "prostgles.schema_watch_func",
|
|
841
|
+
schema_watch_trigger: "prostgles_schema_watch_trigger_new"
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
static EXCLUDE_QUERY_FROM_SCHEMA_WATCH_ID = "prostgles internal query that should be excluded from "
|
|
845
|
+
prepareTriggers = async () => {
|
|
846
|
+
// SELECT * FROM pg_catalog.pg_event_trigger WHERE evtname
|
|
847
|
+
if (!this.appID) throw "prepareTriggers failed: this.appID missing";
|
|
848
|
+
if (this.dboBuilder.prostgles.opts.watchSchema && !(await isSuperUser(this.db))) {
|
|
849
|
+
console.warn("prostgles watchSchema requires superuser db user. Will not watch")
|
|
842
850
|
}
|
|
843
851
|
|
|
844
|
-
|
|
845
|
-
prepareTriggers = async () => {
|
|
846
|
-
// SELECT * FROM pg_catalog.pg_event_trigger WHERE evtname
|
|
847
|
-
if(!this.appID) throw "prepareTriggers failed: this.appID missing";
|
|
848
|
-
if(this.dboBuilder.prostgles.opts.watchSchema && !(await isSuperUser(this.db))){
|
|
849
|
-
console.warn("prostgles watchSchema requires superuser db user. Will not watch")
|
|
850
|
-
}
|
|
852
|
+
try {
|
|
851
853
|
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
await this.db.any(`
|
|
854
|
+
await this.db.any(`
|
|
855
855
|
BEGIN;-- ISOLATION LEVEL SERIALIZABLE;
|
|
856
856
|
|
|
857
857
|
/** ${PubSubManager.EXCLUDE_QUERY_FROM_SCHEMA_WATCH_ID}
|
|
@@ -952,606 +952,607 @@ export class PubSubManager {
|
|
|
952
952
|
|
|
953
953
|
COMMIT;
|
|
954
954
|
`).catch(e => {
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
955
|
+
console.error("prepareTriggers failed: ", e);
|
|
956
|
+
throw e;
|
|
957
|
+
});
|
|
958
958
|
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
} catch (e){
|
|
962
|
-
console.error("prepareTriggers failed: ", e);
|
|
963
|
-
throw e;
|
|
964
|
-
}
|
|
965
|
-
}
|
|
959
|
+
return true;
|
|
966
960
|
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
961
|
+
} catch (e) {
|
|
962
|
+
console.error("prepareTriggers failed: ", e);
|
|
963
|
+
throw e;
|
|
970
964
|
}
|
|
965
|
+
}
|
|
971
966
|
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
967
|
+
isReady() {
|
|
968
|
+
if (!this.postgresNotifListenManager) throw "this.postgresNotifListenManager missing";
|
|
969
|
+
return this.postgresNotifListenManager.isListening();
|
|
970
|
+
}
|
|
975
971
|
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
}
|
|
972
|
+
getSubs(table_name: string, condition: string): SubscriptionParams[] {
|
|
973
|
+
return get(this.subs, [table_name, condition, "subs"])
|
|
974
|
+
}
|
|
980
975
|
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
976
|
+
getSyncs(table_name: string, condition: string) {
|
|
977
|
+
return (this.syncs || [])
|
|
978
|
+
.filter((s: SyncParams) => s.table_name === table_name && s.condition === condition);
|
|
979
|
+
}
|
|
984
980
|
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
}
|
|
989
|
-
const dataArr = str.split(PubSubManager.DELIMITER),
|
|
990
|
-
notifType = dataArr[0];
|
|
981
|
+
/* Relay relevant data to relevant subscriptions */
|
|
982
|
+
notifListener = async (data: { payload: string }) => {
|
|
983
|
+
const str = data.payload;
|
|
991
984
|
|
|
992
|
-
|
|
985
|
+
if (!str) {
|
|
986
|
+
console.error("Empty notif?")
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
const dataArr = str.split(PubSubManager.DELIMITER),
|
|
990
|
+
notifType = dataArr[0];
|
|
993
991
|
|
|
994
|
-
|
|
995
|
-
if(this.onSchemaChange){
|
|
996
|
-
const command = dataArr[1],
|
|
997
|
-
event_type = dataArr[2],
|
|
998
|
-
query = dataArr[3];
|
|
992
|
+
log(str);
|
|
999
993
|
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
994
|
+
if (notifType === this.NOTIF_TYPE.schema) {
|
|
995
|
+
if (this.onSchemaChange) {
|
|
996
|
+
const command = dataArr[1],
|
|
997
|
+
event_type = dataArr[2],
|
|
998
|
+
query = dataArr[3];
|
|
1004
999
|
|
|
1005
|
-
|
|
1000
|
+
if (query) {
|
|
1001
|
+
this.onSchemaChange({ command, query })
|
|
1006
1002
|
}
|
|
1007
|
-
|
|
1008
|
-
if(notifType !== this.NOTIF_TYPE.data){
|
|
1009
|
-
console.error("Unexpected notif type: ", notifType);
|
|
1010
|
-
return;
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
const table_name = dataArr[1],
|
|
1014
|
-
op_name = dataArr[2],
|
|
1015
|
-
condition_ids_str = dataArr[3];
|
|
1016
|
-
|
|
1017
|
-
// 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)]);
|
|
1018
|
-
// const conditions: string[] = triggers.map(t => t.condition);
|
|
1019
|
-
|
|
1020
|
-
log("PG Trigger ->", dataArr.join("__"))
|
|
1021
|
-
if(
|
|
1022
|
-
condition_ids_str && condition_ids_str.startsWith("error") &&
|
|
1023
|
-
this._triggers && this._triggers[table_name] && this._triggers[table_name].length
|
|
1024
|
-
){
|
|
1025
|
-
const pref = "INTERNAL ERROR. Schema might have changed";
|
|
1026
|
-
console.error(`${pref}: ${condition_ids_str}`)
|
|
1027
|
-
this._triggers[table_name].map(c => {
|
|
1028
|
-
const subs = this.getSubs(table_name, c);
|
|
1029
|
-
subs.map(s => {
|
|
1030
|
-
this.pushSubData(s, pref + ". Check server logs");
|
|
1031
|
-
})
|
|
1032
|
-
});
|
|
1033
|
-
} else if(
|
|
1034
|
-
condition_ids_str &&
|
|
1035
|
-
condition_ids_str.split(",").length &&
|
|
1036
|
-
!condition_ids_str.split(",").find((c: string )=> !Number.isInteger(+c)) &&
|
|
1037
|
-
this._triggers && this._triggers[table_name] && this._triggers[table_name].length
|
|
1038
|
-
){
|
|
1039
|
-
const idxs = condition_ids_str.split(",").map(v => +v);
|
|
1040
|
-
const conditions = this._triggers[table_name].filter((c, i) => idxs.includes(i))
|
|
1041
|
-
|
|
1042
|
-
log("PG Trigger -> ",{ table_name, op_name, condition_ids_str, conditions }, this._triggers[table_name]);
|
|
1043
|
-
|
|
1044
|
-
conditions.map(condition => {
|
|
1045
|
-
|
|
1046
|
-
const subs = this.getSubs(table_name, condition);
|
|
1047
|
-
const syncs = this.getSyncs(table_name, condition);
|
|
1048
|
-
|
|
1003
|
+
}
|
|
1049
1004
|
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
this.syncData(s);
|
|
1053
|
-
});
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1054
1007
|
|
|
1055
|
-
|
|
1008
|
+
if (notifType !== this.NOTIF_TYPE.data) {
|
|
1009
|
+
console.error("Unexpected notif type: ", notifType);
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1056
1012
|
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1013
|
+
const table_name = dataArr[1],
|
|
1014
|
+
op_name = dataArr[2],
|
|
1015
|
+
condition_ids_str = dataArr[3];
|
|
1016
|
+
|
|
1017
|
+
// 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)]);
|
|
1018
|
+
// const conditions: string[] = triggers.map(t => t.condition);
|
|
1019
|
+
|
|
1020
|
+
log("PG Trigger ->", dataArr.join("__"))
|
|
1021
|
+
if (
|
|
1022
|
+
condition_ids_str && condition_ids_str.startsWith("error") &&
|
|
1023
|
+
this._triggers && this._triggers[table_name] && this._triggers[table_name].length
|
|
1024
|
+
) {
|
|
1025
|
+
const pref = "INTERNAL ERROR. Schema might have changed";
|
|
1026
|
+
console.error(`${pref}: ${condition_ids_str}`)
|
|
1027
|
+
this._triggers[table_name].map(c => {
|
|
1028
|
+
const subs = this.getSubs(table_name, c);
|
|
1029
|
+
subs.map(s => {
|
|
1030
|
+
this.pushSubData(s, pref + ". Check server logs");
|
|
1031
|
+
})
|
|
1032
|
+
});
|
|
1033
|
+
} else if (
|
|
1034
|
+
condition_ids_str &&
|
|
1035
|
+
condition_ids_str.split(",").length &&
|
|
1036
|
+
!condition_ids_str.split(",").find((c: string) => !Number.isInteger(+c)) &&
|
|
1037
|
+
this._triggers && this._triggers[table_name] && this._triggers[table_name].length
|
|
1038
|
+
) {
|
|
1039
|
+
const idxs = condition_ids_str.split(",").map(v => +v);
|
|
1040
|
+
const conditions = this._triggers[table_name].filter((c, i) => idxs.includes(i))
|
|
1041
|
+
|
|
1042
|
+
log("PG Trigger -> ", { table_name, op_name, condition_ids_str, conditions }, this._triggers[table_name]);
|
|
1043
|
+
|
|
1044
|
+
conditions.map(condition => {
|
|
1045
|
+
|
|
1046
|
+
const subs = this.getSubs(table_name, condition);
|
|
1047
|
+
const syncs = this.getSyncs(table_name, condition);
|
|
1048
|
+
|
|
1049
|
+
|
|
1050
|
+
syncs.map((s) => {
|
|
1051
|
+
log("PG Trigger -> syncData. LR: ", s.lr);
|
|
1052
|
+
this.syncData(s);
|
|
1053
|
+
});
|
|
1061
1054
|
|
|
1062
|
-
|
|
1063
|
-
for(var i = 0; i < subs.length; i++){
|
|
1064
|
-
var sub = subs[i];
|
|
1065
|
-
if(
|
|
1066
|
-
this.dbo[sub.table_name] &&
|
|
1067
|
-
sub.is_ready &&
|
|
1068
|
-
(sub.socket_id && this.sockets[sub.socket_id]) || sub.func
|
|
1069
|
-
){
|
|
1070
|
-
const throttle = sub.throttle || 0;
|
|
1071
|
-
if(sub.last_throttled <= Date.now() - throttle){
|
|
1072
|
-
|
|
1073
|
-
/* It is assumed the policy was checked before this point */
|
|
1074
|
-
this.pushSubData(sub);
|
|
1075
|
-
// sub.last_throttled = Date.now();
|
|
1076
|
-
} else if(!sub.is_throttling) {
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
log("throttling sub")
|
|
1080
|
-
sub.is_throttling = setTimeout(() => {
|
|
1081
|
-
log("throttling finished. pushSubData...")
|
|
1082
|
-
sub.is_throttling = null;
|
|
1083
|
-
this.pushSubData(sub);
|
|
1084
|
-
}, throttle);// sub.throttle);
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
});
|
|
1055
|
+
if (!subs) {
|
|
1089
1056
|
|
|
1090
|
-
|
|
1057
|
+
// console.error(`sub missing for ${table_name} ${condition}`, this.triggers);
|
|
1058
|
+
// console.log(this.subs)
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1091
1061
|
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1062
|
+
/* Throttle the subscriptions */
|
|
1063
|
+
for (var i = 0; i < subs.length; i++) {
|
|
1064
|
+
var sub = subs[i];
|
|
1065
|
+
if (
|
|
1066
|
+
this.dbo[sub.table_name] &&
|
|
1067
|
+
sub.is_ready &&
|
|
1068
|
+
(sub.socket_id && this.sockets[sub.socket_id]) || sub.func
|
|
1069
|
+
) {
|
|
1070
|
+
const throttle = sub.throttle || 0;
|
|
1071
|
+
if (sub.last_throttled <= Date.now() - throttle) {
|
|
1072
|
+
|
|
1073
|
+
/* It is assumed the policy was checked before this point */
|
|
1074
|
+
this.pushSubData(sub);
|
|
1075
|
+
// sub.last_throttled = Date.now();
|
|
1076
|
+
} else if (!sub.is_throttling) {
|
|
1077
|
+
|
|
1078
|
+
|
|
1079
|
+
log("throttling sub")
|
|
1080
|
+
sub.is_throttling = setTimeout(() => {
|
|
1081
|
+
log("throttling finished. pushSubData...")
|
|
1082
|
+
sub.is_throttling = null;
|
|
1083
|
+
this.pushSubData(sub);
|
|
1084
|
+
}, throttle);// sub.throttle);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1098
1087
|
}
|
|
1088
|
+
});
|
|
1089
|
+
|
|
1090
|
+
} else {
|
|
1091
|
+
|
|
1092
|
+
// if(!this._triggers || !this._triggers[table_name] || !this._triggers[table_name].length){
|
|
1093
|
+
// console.warn(190, "Trigger sub not found. DROPPING TRIGGER", table_name, condition_ids_str, this._triggers);
|
|
1094
|
+
// this.dropTrigger(table_name);
|
|
1095
|
+
// } else {
|
|
1096
|
+
// }
|
|
1097
|
+
console.warn(190, "Trigger sub issue: ", table_name, condition_ids_str, this._triggers);
|
|
1099
1098
|
}
|
|
1099
|
+
}
|
|
1100
1100
|
|
|
1101
1101
|
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1102
|
+
pushSubData(sub: SubscriptionParams, err?: any) {
|
|
1103
|
+
if (!sub) throw "pushSubData: invalid sub";
|
|
1104
|
+
const { table_name, filter, params, table_rules, socket_id, channel_name, func } = sub; //, subOne = false
|
|
1105
1105
|
|
|
1106
|
-
|
|
1106
|
+
sub.last_throttled = Date.now();
|
|
1107
1107
|
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1108
|
+
if (err) {
|
|
1109
|
+
if (socket_id) {
|
|
1110
|
+
this.sockets[socket_id].emit(channel_name, { err });
|
|
1111
|
+
}
|
|
1112
|
+
return true;
|
|
1113
|
+
}
|
|
1114
1114
|
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1115
|
+
return new Promise(async (resolve, reject) => {
|
|
1116
|
+
/* TODO: Retire subOne -> it's redundant */
|
|
1117
|
+
// this.dbo[table_name][subOne? "findOne" : "find"](filter, params, null, table_rules)
|
|
1118
|
+
if (!this.dbo?.[table_name]?.find) throw "1107 this.dbo[table_name].find";
|
|
1119
1119
|
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
});
|
|
1120
|
+
this.dbo?.[table_name]?.find?.(filter, params, undefined, table_rules)
|
|
1121
|
+
.then(data => {
|
|
1122
|
+
|
|
1123
|
+
if (socket_id && this.sockets[socket_id]) {
|
|
1124
|
+
log("Pushed " + data.length + " records to sub")
|
|
1125
|
+
this.sockets[socket_id].emit(channel_name, { data }, () => {
|
|
1126
|
+
resolve(data);
|
|
1127
|
+
});
|
|
1128
|
+
/* TO DO: confirm receiving data or server will unsubscribe
|
|
1129
|
+
{ data }, (cb)=> { console.log(cb) });
|
|
1130
|
+
*/
|
|
1131
|
+
} else if (func) {
|
|
1132
|
+
func(data);
|
|
1133
|
+
resolve(data);
|
|
1134
|
+
}
|
|
1135
|
+
sub.last_throttled = Date.now();
|
|
1136
|
+
}).catch(err => {
|
|
1137
|
+
const errObj = { _err_msg: err.toString(), err };
|
|
1138
|
+
if (socket_id && this.sockets[socket_id]) {
|
|
1139
|
+
this.sockets[socket_id].emit(channel_name, { err: errObj });
|
|
1140
|
+
} else if (func) {
|
|
1141
|
+
func({ err: errObj });
|
|
1142
|
+
}
|
|
1143
|
+
reject(errObj)
|
|
1145
1144
|
});
|
|
1146
|
-
}
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1147
1147
|
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
}
|
|
1148
|
+
upsertSocket(socket: any, channel_name: string) {
|
|
1149
|
+
if (socket && !this.sockets[socket.id]) {
|
|
1150
|
+
this.sockets[socket.id] = socket;
|
|
1151
|
+
socket.on("disconnect", () => this.onSocketDisconnected(socket));
|
|
1153
1152
|
}
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
syncTimeout?: ReturnType<typeof setTimeout>;
|
|
1156
|
+
async syncData(sync: SyncParams, clientData?: ClientExpressData) {
|
|
1157
|
+
return await syncData(this, sync, clientData);
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
/**
|
|
1161
|
+
* Returns a sync channel
|
|
1162
|
+
* A sync channel is unique per socket for each filter
|
|
1163
|
+
*/
|
|
1164
|
+
async addSync(syncParams: AddSyncParams) {
|
|
1165
|
+
const {
|
|
1166
|
+
socket = null, table_info = null, table_rules, synced_field = null,
|
|
1167
|
+
allow_delete = false, id_fields = [], filter = {},
|
|
1168
|
+
params, condition = "", throttle = 0
|
|
1169
|
+
} = syncParams || {};
|
|
1170
|
+
|
|
1171
|
+
let conditionParsed = this.parseCondition(condition);
|
|
1172
|
+
if (!socket || !table_info) throw "socket or table_info missing";
|
|
1173
|
+
|
|
1174
|
+
|
|
1175
|
+
const { name: table_name } = table_info,
|
|
1176
|
+
channel_name = `${this.socketChannelPreffix}.${table_name}.${JSON.stringify(filter)}.sync`;
|
|
1177
|
+
|
|
1178
|
+
if (!synced_field) throw "synced_field missing from table_rules";
|
|
1179
|
+
|
|
1180
|
+
this.upsertSocket(socket, channel_name);
|
|
1181
|
+
|
|
1182
|
+
const upsertSync = () => {
|
|
1183
|
+
let newSync = {
|
|
1184
|
+
channel_name,
|
|
1185
|
+
table_name,
|
|
1186
|
+
filter,
|
|
1187
|
+
condition: conditionParsed,
|
|
1188
|
+
synced_field,
|
|
1189
|
+
id_fields,
|
|
1190
|
+
allow_delete,
|
|
1191
|
+
table_rules,
|
|
1192
|
+
throttle: Math.max(throttle || 0, table_rules?.sync?.throttle || 0),
|
|
1193
|
+
batch_size: get(table_rules, "sync.batch_size") || DEFAULT_SYNC_BATCH_SIZE,
|
|
1194
|
+
last_throttled: 0,
|
|
1195
|
+
socket_id: socket.id,
|
|
1196
|
+
is_sync: true,
|
|
1197
|
+
last_synced: 0,
|
|
1198
|
+
lr: undefined,
|
|
1199
|
+
table_info,
|
|
1200
|
+
is_syncing: false,
|
|
1201
|
+
wal: undefined,
|
|
1202
|
+
socket,
|
|
1203
|
+
params
|
|
1204
|
+
};
|
|
1205
|
+
|
|
1206
|
+
/* Only a sync per socket per table per condition allowed */
|
|
1207
|
+
this.syncs = this.syncs || [];
|
|
1208
|
+
let existing = this.syncs.find(s => s.socket_id === socket.id && s.channel_name === channel_name);
|
|
1209
|
+
if (!existing) {
|
|
1210
|
+
this.syncs.push(newSync);
|
|
1211
|
+
// console.log("Added SYNC");
|
|
1212
|
+
|
|
1213
|
+
socket.removeAllListeners(channel_name + "unsync");
|
|
1214
|
+
socket.once(channel_name + "unsync", (_data: any, cb: BasicCallback) => {
|
|
1215
|
+
this.onSocketDisconnected(socket, channel_name);
|
|
1216
|
+
cb(null, { res: "ok" })
|
|
1217
|
+
});
|
|
1154
1218
|
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
return await syncData(this, sync, clientData);
|
|
1158
|
-
}
|
|
1219
|
+
socket.removeAllListeners(channel_name);
|
|
1220
|
+
socket.on(channel_name, (data: any, cb: BasicCallback) => {
|
|
1159
1221
|
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1222
|
+
if (!data) {
|
|
1223
|
+
cb({ err: "Unexpected request. Need data or onSyncRequest" });
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
/*
|
|
1228
|
+
*/
|
|
1229
|
+
|
|
1230
|
+
/* Server will:
|
|
1231
|
+
1. Ask for last_synced emit(onSyncRequest)
|
|
1232
|
+
2. Ask for data >= server_synced emit(onPullRequest)
|
|
1233
|
+
-> Upsert that data
|
|
1234
|
+
2. Push data >= last_synced emit(data.data)
|
|
1235
|
+
|
|
1236
|
+
Client will:
|
|
1237
|
+
1. Send last_synced on(onSyncRequest)
|
|
1238
|
+
2. Send data >= server_synced on(onPullRequest)
|
|
1239
|
+
3. Send data on CRUD emit(data.data | data.deleted)
|
|
1240
|
+
4. Upsert data.data | deleted on(data.data | data.deleted)
|
|
1241
|
+
*/
|
|
1242
|
+
|
|
1243
|
+
// if(data.data){
|
|
1244
|
+
// console.error("THIS SHOUKD NEVER FIRE !! NEW DATA FROM SYNC");
|
|
1245
|
+
// this.upsertClientData(newSync, data.data);
|
|
1246
|
+
// } else
|
|
1247
|
+
if (data.onSyncRequest) {
|
|
1248
|
+
// console.log("syncData from socket")
|
|
1249
|
+
this.syncData(newSync, data.onSyncRequest);
|
|
1250
|
+
|
|
1251
|
+
// console.log("onSyncRequest ", socket._user)
|
|
1252
|
+
} else {
|
|
1253
|
+
console.error("Unexpected sync request data from client: ", data)
|
|
1254
|
+
}
|
|
1255
|
+
});
|
|
1174
1256
|
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
const upsertSync = () => {
|
|
1183
|
-
let newSync = {
|
|
1184
|
-
channel_name,
|
|
1185
|
-
table_name,
|
|
1186
|
-
filter,
|
|
1187
|
-
condition: conditionParsed,
|
|
1188
|
-
synced_field,
|
|
1189
|
-
id_fields,
|
|
1190
|
-
allow_delete,
|
|
1191
|
-
table_rules,
|
|
1192
|
-
throttle: Math.max(throttle || 0, table_rules?.sync?.throttle || 0),
|
|
1193
|
-
batch_size: get(table_rules, "sync.batch_size") || DEFAULT_SYNC_BATCH_SIZE,
|
|
1194
|
-
last_throttled: 0,
|
|
1195
|
-
socket_id: socket.id,
|
|
1196
|
-
is_sync: true,
|
|
1197
|
-
last_synced: 0,
|
|
1198
|
-
lr: undefined,
|
|
1199
|
-
table_info,
|
|
1200
|
-
is_syncing: false,
|
|
1201
|
-
wal: undefined,
|
|
1202
|
-
socket,
|
|
1203
|
-
params
|
|
1204
|
-
};
|
|
1205
|
-
|
|
1206
|
-
/* Only a sync per socket per table per condition allowed */
|
|
1207
|
-
this.syncs = this.syncs || [];
|
|
1208
|
-
let existing = this.syncs.find(s => s.socket_id === socket.id && s.channel_name === channel_name );
|
|
1209
|
-
if(!existing){
|
|
1210
|
-
this.syncs.push(newSync);
|
|
1211
|
-
// console.log("Added SYNC");
|
|
1212
|
-
|
|
1213
|
-
socket.removeAllListeners(channel_name + "unsync");
|
|
1214
|
-
socket.once(channel_name + "unsync", (_data: any, cb: BasicCallback) => {
|
|
1215
|
-
this.onSocketDisconnected(socket, channel_name);
|
|
1216
|
-
cb(null, { res: "ok" })
|
|
1217
|
-
});
|
|
1218
|
-
|
|
1219
|
-
socket.removeAllListeners(channel_name);
|
|
1220
|
-
socket.on(channel_name, (data: any, cb: BasicCallback) => {
|
|
1221
|
-
|
|
1222
|
-
if(!data){
|
|
1223
|
-
cb({ err: "Unexpected request. Need data or onSyncRequest"});
|
|
1224
|
-
return;
|
|
1225
|
-
}
|
|
1257
|
+
// socket.emit(channel_name, { onSyncRequest: true }, (response) => {
|
|
1258
|
+
// console.log(response)
|
|
1259
|
+
// });
|
|
1260
|
+
} else {
|
|
1261
|
+
console.error("UNCLOSED DUPLICATE SYNC FOUND");
|
|
1262
|
+
}
|
|
1226
1263
|
|
|
1227
|
-
|
|
1228
|
-
|
|
1264
|
+
return newSync;
|
|
1265
|
+
};
|
|
1229
1266
|
|
|
1230
|
-
/* Server will:
|
|
1231
|
-
1. Ask for last_synced emit(onSyncRequest)
|
|
1232
|
-
2. Ask for data >= server_synced emit(onPullRequest)
|
|
1233
|
-
-> Upsert that data
|
|
1234
|
-
2. Push data >= last_synced emit(data.data)
|
|
1235
|
-
|
|
1236
|
-
Client will:
|
|
1237
|
-
1. Send last_synced on(onSyncRequest)
|
|
1238
|
-
2. Send data >= server_synced on(onPullRequest)
|
|
1239
|
-
3. Send data on CRUD emit(data.data | data.deleted)
|
|
1240
|
-
4. Upsert data.data | deleted on(data.data | data.deleted)
|
|
1241
|
-
*/
|
|
1242
1267
|
|
|
1243
|
-
|
|
1244
|
-
// console.error("THIS SHOUKD NEVER FIRE !! NEW DATA FROM SYNC");
|
|
1245
|
-
// this.upsertClientData(newSync, data.data);
|
|
1246
|
-
// } else
|
|
1247
|
-
if(data.onSyncRequest){
|
|
1248
|
-
// console.log("syncData from socket")
|
|
1249
|
-
this.syncData(newSync, data.onSyncRequest);
|
|
1250
|
-
|
|
1251
|
-
// console.log("onSyncRequest ", socket._user)
|
|
1252
|
-
} else {
|
|
1253
|
-
console.error("Unexpected sync request data from client: ", data)
|
|
1254
|
-
}
|
|
1255
|
-
});
|
|
1256
|
-
|
|
1257
|
-
// socket.emit(channel_name, { onSyncRequest: true }, (response) => {
|
|
1258
|
-
// console.log(response)
|
|
1259
|
-
// });
|
|
1260
|
-
} else {
|
|
1261
|
-
console.error("UNCLOSED DUPLICATE SYNC FOUND");
|
|
1262
|
-
}
|
|
1268
|
+
// const { min_id, max_id, count, max_synced } = params;
|
|
1263
1269
|
|
|
1264
|
-
|
|
1265
|
-
};
|
|
1266
|
-
|
|
1270
|
+
let sync = upsertSync();
|
|
1267
1271
|
|
|
1268
|
-
|
|
1272
|
+
await this.addTrigger({ table_name, condition: conditionParsed });
|
|
1269
1273
|
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
await this.addTrigger({ table_name, condition: conditionParsed });
|
|
1274
|
+
return channel_name;
|
|
1275
|
+
}
|
|
1273
1276
|
|
|
1274
|
-
|
|
1275
|
-
}
|
|
1277
|
+
parseCondition = (condition: string): string => Boolean(condition && condition.trim().length) ? condition : "TRUE"
|
|
1276
1278
|
|
|
1277
|
-
|
|
1279
|
+
/* Must return a channel for socket */
|
|
1280
|
+
/* The distinct list of channel names must have a corresponding trigger in the database */
|
|
1281
|
+
async addSub(subscriptionParams: Omit<AddSubscriptionParams, "channel_name">) {
|
|
1282
|
+
const {
|
|
1283
|
+
socket, func = null, table_info = null, table_rules, filter = {},
|
|
1284
|
+
params = {}, condition = "", throttle = 0 //subOne = false,
|
|
1285
|
+
} = subscriptionParams || {};
|
|
1278
1286
|
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
async addSub(subscriptionParams: Omit<AddSubscriptionParams, "channel_name">){
|
|
1282
|
-
const {
|
|
1283
|
-
socket, func = null, table_info = null, table_rules, filter = {},
|
|
1284
|
-
params = {}, condition = "", throttle = 0 //subOne = false,
|
|
1285
|
-
} = subscriptionParams || {};
|
|
1287
|
+
let validated_throttle = subscriptionParams.throttle || 10;
|
|
1288
|
+
if ((!socket && !func) || !table_info) throw "socket/func or table_info missing";
|
|
1286
1289
|
|
|
1287
|
-
|
|
1288
|
-
|
|
1290
|
+
const pubThrottle = get(table_rules, ["subscribe", "throttle"]) || 0;
|
|
1291
|
+
if (pubThrottle && Number.isInteger(pubThrottle) && pubThrottle > 0) {
|
|
1292
|
+
validated_throttle = pubThrottle;
|
|
1293
|
+
}
|
|
1294
|
+
if (throttle && Number.isInteger(throttle) && throttle >= pubThrottle) {
|
|
1295
|
+
validated_throttle = throttle;
|
|
1296
|
+
}
|
|
1289
1297
|
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1298
|
+
let channel_name = `${this.socketChannelPreffix}.${table_info.name}.${JSON.stringify(filter)}.${JSON.stringify(params)}.${"m"}.sub`; //.${subOne? "o" : "m"}.sub`;
|
|
1299
|
+
|
|
1300
|
+
this.upsertSocket(socket, channel_name);
|
|
1301
|
+
|
|
1302
|
+
const upsertSub = (newSubData: { table_name: string, condition: string, is_ready: boolean }) => {
|
|
1303
|
+
const { table_name, condition: _cond, is_ready = false } = newSubData,
|
|
1304
|
+
condition = this.parseCondition(_cond),
|
|
1305
|
+
newSub: SubscriptionParams = {
|
|
1306
|
+
socket,
|
|
1307
|
+
table_name: table_info.name,
|
|
1308
|
+
table_info,
|
|
1309
|
+
filter,
|
|
1310
|
+
params,
|
|
1311
|
+
table_rules,
|
|
1312
|
+
channel_name,
|
|
1313
|
+
func: func ? func : undefined,
|
|
1314
|
+
socket_id: socket?.id,
|
|
1315
|
+
throttle: validated_throttle,
|
|
1316
|
+
is_throttling: null,
|
|
1317
|
+
last_throttled: 0,
|
|
1318
|
+
is_ready,
|
|
1319
|
+
// subOne
|
|
1320
|
+
};
|
|
1321
|
+
|
|
1322
|
+
this.subs[table_name] = this.subs[table_name] || {};
|
|
1323
|
+
this.subs[table_name][condition] = this.subs[table_name][condition] || { subs: [] };
|
|
1324
|
+
this.subs[table_name][condition].subs = this.subs[table_name][condition].subs || [];
|
|
1325
|
+
|
|
1326
|
+
// console.log("1034 upsertSub", this.subs)
|
|
1327
|
+
const sub_idx = this.subs[table_name][condition].subs.findIndex(s =>
|
|
1328
|
+
s.channel_name === channel_name &&
|
|
1329
|
+
(
|
|
1330
|
+
socket && s.socket_id === socket.id ||
|
|
1331
|
+
func && s.func === func
|
|
1332
|
+
)
|
|
1333
|
+
);
|
|
1334
|
+
if (sub_idx < 0) {
|
|
1335
|
+
this.subs[table_name][condition].subs.push(newSub);
|
|
1336
|
+
if (socket) {
|
|
1337
|
+
const chnUnsub = channel_name + "unsubscribe";
|
|
1338
|
+
socket.removeAllListeners(chnUnsub);
|
|
1339
|
+
socket.once(chnUnsub, (_data: any, cb: BasicCallback) => {
|
|
1340
|
+
const res = this.onSocketDisconnected(socket, channel_name);
|
|
1341
|
+
cb(null, { res });
|
|
1342
|
+
});
|
|
1296
1343
|
}
|
|
1297
|
-
|
|
1298
|
-
|
|
1344
|
+
} else {
|
|
1345
|
+
this.subs[table_name][condition].subs[sub_idx] = newSub;
|
|
1346
|
+
}
|
|
1299
1347
|
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
condition = this.parseCondition(_cond),
|
|
1305
|
-
newSub: SubscriptionParams = {
|
|
1306
|
-
socket,
|
|
1307
|
-
table_name: table_info.name,
|
|
1308
|
-
table_info,
|
|
1309
|
-
filter,
|
|
1310
|
-
params,
|
|
1311
|
-
table_rules,
|
|
1312
|
-
channel_name,
|
|
1313
|
-
func: func? func : undefined,
|
|
1314
|
-
socket_id: socket?.id,
|
|
1315
|
-
throttle: validated_throttle,
|
|
1316
|
-
is_throttling: null,
|
|
1317
|
-
last_throttled: 0,
|
|
1318
|
-
is_ready,
|
|
1319
|
-
// subOne
|
|
1320
|
-
};
|
|
1321
|
-
|
|
1322
|
-
this.subs[table_name] = this.subs[table_name] || {};
|
|
1323
|
-
this.subs[table_name][condition] = this.subs[table_name][condition] || { subs: [] };
|
|
1324
|
-
this.subs[table_name][condition].subs = this.subs[table_name][condition].subs || [];
|
|
1325
|
-
|
|
1326
|
-
// console.log("1034 upsertSub", this.subs)
|
|
1327
|
-
const sub_idx = this.subs[table_name][condition].subs.findIndex(s =>
|
|
1328
|
-
s.channel_name === channel_name &&
|
|
1329
|
-
(
|
|
1330
|
-
socket && s.socket_id === socket.id ||
|
|
1331
|
-
func && s.func === func
|
|
1332
|
-
)
|
|
1333
|
-
);
|
|
1334
|
-
if(sub_idx < 0){
|
|
1335
|
-
this.subs[table_name][condition].subs.push(newSub);
|
|
1336
|
-
if(socket){
|
|
1337
|
-
const chnUnsub = channel_name + "unsubscribe";
|
|
1338
|
-
socket.removeAllListeners(chnUnsub);
|
|
1339
|
-
socket.once(chnUnsub, (_data: any, cb: BasicCallback) =>{
|
|
1340
|
-
const res = this.onSocketDisconnected(socket, channel_name);
|
|
1341
|
-
cb(null, { res });
|
|
1342
|
-
});
|
|
1343
|
-
}
|
|
1344
|
-
} else {
|
|
1345
|
-
this.subs[table_name][condition].subs[sub_idx] = newSub;
|
|
1346
|
-
}
|
|
1348
|
+
if (is_ready) {
|
|
1349
|
+
this.pushSubData(newSub);
|
|
1350
|
+
}
|
|
1351
|
+
};
|
|
1347
1352
|
|
|
1348
|
-
if(is_ready){
|
|
1349
|
-
this.pushSubData(newSub);
|
|
1350
|
-
}
|
|
1351
|
-
};
|
|
1352
|
-
|
|
1353
1353
|
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
let _condition = "TRUE";
|
|
1358
|
-
table_info.parent_tables.map(async table_name => {
|
|
1359
|
-
|
|
1360
|
-
upsertSub({
|
|
1361
|
-
table_name,
|
|
1362
|
-
condition: _condition,
|
|
1363
|
-
is_ready: true
|
|
1364
|
-
});
|
|
1365
|
-
|
|
1366
|
-
await this.addTrigger({
|
|
1367
|
-
table_name,
|
|
1368
|
-
condition: _condition
|
|
1369
|
-
});
|
|
1370
|
-
|
|
1371
|
-
upsertSub({
|
|
1372
|
-
table_name,
|
|
1373
|
-
condition: _condition,
|
|
1374
|
-
is_ready: true
|
|
1375
|
-
});
|
|
1376
|
-
});
|
|
1377
|
-
|
|
1378
|
-
return channel_name
|
|
1379
|
-
} else {
|
|
1380
|
-
throw "PubSubManager: view parent_tables missing";
|
|
1381
|
-
}
|
|
1382
|
-
/* */
|
|
1383
|
-
} else {
|
|
1384
|
-
/* Just a table, add table + condition trigger */
|
|
1385
|
-
// console.log(table_info, 202);
|
|
1386
|
-
|
|
1387
|
-
upsertSub({
|
|
1388
|
-
table_name: table_info.name,
|
|
1389
|
-
condition: this.parseCondition(condition),
|
|
1390
|
-
is_ready: false
|
|
1391
|
-
});
|
|
1392
|
-
await this.addTrigger({
|
|
1393
|
-
table_name: table_info.name,
|
|
1394
|
-
condition: this.parseCondition(condition),
|
|
1395
|
-
});
|
|
1396
|
-
upsertSub({
|
|
1397
|
-
table_name: table_info.name,
|
|
1398
|
-
condition: this.parseCondition(condition),
|
|
1399
|
-
is_ready: true
|
|
1400
|
-
});
|
|
1354
|
+
if (table_info.is_view && table_info.parent_tables) {
|
|
1355
|
+
if (table_info.parent_tables.length) {
|
|
1401
1356
|
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
}
|
|
1357
|
+
let _condition = "TRUE";
|
|
1358
|
+
table_info.parent_tables.map(async table_name => {
|
|
1405
1359
|
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
sub.func && sub.func === func
|
|
1412
|
-
){
|
|
1413
|
-
this.subs[table_name][cond].subs.splice(i, 1);
|
|
1414
|
-
}
|
|
1415
|
-
});
|
|
1416
|
-
} else {
|
|
1417
|
-
console.error("Could not unsubscribe. Subscription might not have initialised yet")
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1360
|
+
upsertSub({
|
|
1361
|
+
table_name,
|
|
1362
|
+
condition: _condition,
|
|
1363
|
+
is_ready: true
|
|
1364
|
+
});
|
|
1420
1365
|
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
Object.keys(this.subs || {}).map(table_name => {
|
|
1432
|
-
Object.keys(this.subs[table_name] || {}).map(condition => {
|
|
1433
|
-
if(this.subs[table_name][condition].subs.length) {
|
|
1434
|
-
upsert(table_name, condition);
|
|
1435
|
-
}
|
|
1436
|
-
});
|
|
1366
|
+
await this.addTrigger({
|
|
1367
|
+
table_name,
|
|
1368
|
+
condition: _condition
|
|
1369
|
+
});
|
|
1370
|
+
|
|
1371
|
+
upsertSub({
|
|
1372
|
+
table_name,
|
|
1373
|
+
condition: _condition,
|
|
1374
|
+
is_ready: true
|
|
1375
|
+
});
|
|
1437
1376
|
});
|
|
1438
1377
|
|
|
1439
|
-
return
|
|
1378
|
+
return channel_name
|
|
1379
|
+
} else {
|
|
1380
|
+
throw "PubSubManager: view parent_tables missing";
|
|
1381
|
+
}
|
|
1382
|
+
/* */
|
|
1383
|
+
} else {
|
|
1384
|
+
/* Just a table, add table + condition trigger */
|
|
1385
|
+
// console.log(table_info, 202);
|
|
1386
|
+
|
|
1387
|
+
upsertSub({
|
|
1388
|
+
table_name: table_info.name,
|
|
1389
|
+
condition: this.parseCondition(condition),
|
|
1390
|
+
is_ready: false
|
|
1391
|
+
});
|
|
1392
|
+
await this.addTrigger({
|
|
1393
|
+
table_name: table_info.name,
|
|
1394
|
+
condition: this.parseCondition(condition),
|
|
1395
|
+
});
|
|
1396
|
+
upsertSub({
|
|
1397
|
+
table_name: table_info.name,
|
|
1398
|
+
condition: this.parseCondition(condition),
|
|
1399
|
+
is_ready: true
|
|
1400
|
+
});
|
|
1401
|
+
|
|
1402
|
+
return channel_name
|
|
1440
1403
|
}
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
if(
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
/**
|
|
1453
|
-
* If a channel name is specified then delete triggers
|
|
1454
|
-
*/
|
|
1455
|
-
if(
|
|
1456
|
-
(socket && sub.socket_id === socket.id) &&
|
|
1457
|
-
(!channel_name || sub.channel_name === channel_name)
|
|
1458
|
-
){
|
|
1459
|
-
this.subs[table_name][condition].subs.splice(i, 1);
|
|
1460
|
-
if(!this.subs[table_name][condition].subs.length) {
|
|
1461
|
-
delete this.subs[table_name][condition];
|
|
1462
|
-
|
|
1463
|
-
if(isEmpty(this.subs[table_name])){
|
|
1464
|
-
delete this.subs[table_name];
|
|
1465
|
-
}
|
|
1466
|
-
}
|
|
1467
|
-
}
|
|
1468
|
-
});
|
|
1469
|
-
})
|
|
1470
|
-
});
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
removeLocalSub(table_name: string, condition: string, func: (items: object[]) => any) {
|
|
1407
|
+
let cond = this.parseCondition(condition);
|
|
1408
|
+
if (get(this.subs, [table_name, cond, "subs"])) {
|
|
1409
|
+
this.subs[table_name][cond].subs.map((sub, i) => {
|
|
1410
|
+
if (
|
|
1411
|
+
sub.func && sub.func === func
|
|
1412
|
+
) {
|
|
1413
|
+
this.subs[table_name][cond].subs.splice(i, 1);
|
|
1471
1414
|
}
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1415
|
+
});
|
|
1416
|
+
} else {
|
|
1417
|
+
console.error("Could not unsubscribe. Subscription might not have initialised yet")
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
getActiveListeners = (): { table_name: string; condition: string }[] => {
|
|
1422
|
+
let result: { table_name: string; condition: string }[] = [];
|
|
1423
|
+
const upsert = (t: string, c: string) => {
|
|
1424
|
+
if (!result.find(r => r.table_name === t && r.condition === c)) {
|
|
1425
|
+
result.push({ table_name: t, condition: c });
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
(this.syncs || []).map(s => {
|
|
1429
|
+
upsert(s.table_name, s.condition)
|
|
1430
|
+
});
|
|
1431
|
+
Object.keys(this.subs || {}).map(table_name => {
|
|
1432
|
+
Object.keys(this.subs[table_name] || {}).map(condition => {
|
|
1433
|
+
if (this.subs[table_name][condition].subs.length) {
|
|
1434
|
+
upsert(table_name, condition);
|
|
1435
|
+
}
|
|
1436
|
+
});
|
|
1437
|
+
});
|
|
1438
|
+
|
|
1439
|
+
return result;
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
onSocketDisconnected(socket?: PRGLIOSocket, channel_name?: string) {
|
|
1443
|
+
// process.on('warning', e => {
|
|
1444
|
+
// console.warn(e.stack)
|
|
1445
|
+
// });
|
|
1446
|
+
// console.log("onSocketDisconnected", channel_name, this.syncs)
|
|
1447
|
+
if (this.subs) {
|
|
1448
|
+
Object.keys(this.subs).map(table_name => {
|
|
1449
|
+
Object.keys(this.subs[table_name]).map(condition => {
|
|
1450
|
+
this.subs[table_name][condition].subs.map((sub, i) => {
|
|
1451
|
+
|
|
1452
|
+
/**
|
|
1453
|
+
* If a channel name is specified then delete triggers
|
|
1454
|
+
*/
|
|
1455
|
+
if (
|
|
1456
|
+
(socket && sub.socket_id === socket.id) &&
|
|
1457
|
+
(!channel_name || sub.channel_name === channel_name)
|
|
1458
|
+
) {
|
|
1459
|
+
this.subs[table_name][condition].subs.splice(i, 1);
|
|
1460
|
+
if (!this.subs[table_name][condition].subs.length) {
|
|
1461
|
+
delete this.subs[table_name][condition];
|
|
1462
|
+
|
|
1463
|
+
if (isEmpty(this.subs[table_name])) {
|
|
1464
|
+
delete this.subs[table_name];
|
|
1477
1465
|
}
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
});
|
|
1469
|
+
})
|
|
1470
|
+
});
|
|
1471
|
+
}
|
|
1478
1472
|
|
|
1479
|
-
|
|
1480
|
-
|
|
1473
|
+
if (this.syncs) {
|
|
1474
|
+
this.syncs = this.syncs.filter(s => {
|
|
1475
|
+
const matchesSocket = Boolean(socket && s.socket_id !== socket.id)
|
|
1476
|
+
if (channel_name) {
|
|
1477
|
+
return matchesSocket || s.channel_name !== channel_name
|
|
1481
1478
|
}
|
|
1482
1479
|
|
|
1483
|
-
|
|
1480
|
+
return matchesSocket;
|
|
1481
|
+
});
|
|
1482
|
+
}
|
|
1484
1483
|
|
|
1485
|
-
|
|
1486
|
-
delete this.sockets[socket.id];
|
|
1487
|
-
} else {
|
|
1488
|
-
socket.removeAllListeners(channel_name);
|
|
1489
|
-
socket.removeAllListeners(channel_name + "unsync");
|
|
1490
|
-
socket.removeAllListeners(channel_name + "unsubscribe");
|
|
1491
|
-
}
|
|
1484
|
+
if (!socket) {
|
|
1492
1485
|
|
|
1493
|
-
|
|
1486
|
+
} else if (!channel_name) {
|
|
1487
|
+
delete this.sockets[socket.id];
|
|
1488
|
+
} else {
|
|
1489
|
+
socket.removeAllListeners(channel_name);
|
|
1490
|
+
socket.removeAllListeners(channel_name + "unsync");
|
|
1491
|
+
socket.removeAllListeners(channel_name + "unsubscribe");
|
|
1494
1492
|
}
|
|
1495
1493
|
|
|
1494
|
+
return "ok";
|
|
1495
|
+
}
|
|
1496
1496
|
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1497
|
+
|
|
1498
|
+
checkIfTimescaleBug = async (table_name: string) => {
|
|
1499
|
+
const schema = "_timescaledb_catalog",
|
|
1500
|
+
res = await this.db.oneOrNone("SELECT EXISTS( \
|
|
1500
1501
|
SELECT * \
|
|
1501
1502
|
FROM information_schema.tables \
|
|
1502
1503
|
WHERE 1 = 1 \
|
|
1503
1504
|
AND table_schema = ${schema} \
|
|
1504
1505
|
AND table_name = 'hypertable' \
|
|
1505
1506
|
);", { schema });
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
}
|
|
1512
|
-
return true;
|
|
1507
|
+
if (res.exists) {
|
|
1508
|
+
let isHyperTable = await this.db.any("SELECT * FROM " + asName(schema) + ".hypertable WHERE table_name = ${table_name};", { table_name, schema });
|
|
1509
|
+
if (isHyperTable && isHyperTable.length) {
|
|
1510
|
+
throw "Triggers do not work on timescaledb hypertables due to bug:\nhttps://github.com/timescale/timescaledb/issues/1084"
|
|
1511
|
+
}
|
|
1513
1512
|
}
|
|
1513
|
+
return true;
|
|
1514
|
+
}
|
|
1514
1515
|
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1516
|
+
/*
|
|
1517
|
+
A table will only have a trigger with all conditions (for different subs)
|
|
1518
|
+
conditions = ["user_id = 1"]
|
|
1519
|
+
fields = ["user_id"]
|
|
1520
|
+
*/
|
|
1520
1521
|
|
|
1521
|
-
|
|
1522
|
-
|
|
1522
|
+
getMyTriggerQuery = async () => {
|
|
1523
|
+
return pgp.as.format(`
|
|
1523
1524
|
SELECT * --, ROW_NUMBER() OVER(PARTITION BY table_name ORDER BY table_name, condition ) - 1 as id
|
|
1524
1525
|
FROM prostgles.v_triggers
|
|
1525
1526
|
WHERE app_id = $1
|
|
1526
1527
|
ORDER BY table_name, condition
|
|
1527
1528
|
`, [this.appID]
|
|
1528
|
-
|
|
1529
|
-
|
|
1529
|
+
)
|
|
1530
|
+
}
|
|
1530
1531
|
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1532
|
+
// waitingTriggers: { [key: string]: string[] } = undefined;
|
|
1533
|
+
addingTrigger: any;
|
|
1534
|
+
addTriggerPool?: Record<string, string[]> = undefined;
|
|
1535
|
+
async addTrigger(params: { table_name: string; condition: string; }) {
|
|
1536
|
+
try {
|
|
1536
1537
|
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1538
|
+
let { table_name, condition } = { ...params }
|
|
1539
|
+
if (!table_name) throw "MISSING table_name";
|
|
1540
|
+
if (!this.appID) throw "MISSING appID";
|
|
1540
1541
|
|
|
1541
|
-
|
|
1542
|
+
if (!condition || !condition.trim().length) condition = "TRUE";
|
|
1542
1543
|
|
|
1543
|
-
|
|
1544
|
+
const app_id = this.appID;
|
|
1544
1545
|
|
|
1545
|
-
|
|
1546
|
+
// console.log(1623, { app_id, addTrigger: { table_name, condition } });
|
|
1546
1547
|
|
|
1547
|
-
|
|
1548
|
+
await this.checkIfTimescaleBug(table_name);
|
|
1548
1549
|
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1550
|
+
const trgVals = {
|
|
1551
|
+
tbl: asValue(table_name),
|
|
1552
|
+
cond: asValue(condition),
|
|
1553
|
+
}
|
|
1553
1554
|
|
|
1554
|
-
|
|
1555
|
+
await this.db.any(`
|
|
1555
1556
|
BEGIN WORK;
|
|
1556
1557
|
LOCK TABLE prostgles.app_triggers IN ACCESS EXCLUSIVE MODE;
|
|
1557
1558
|
|
|
@@ -1562,52 +1563,52 @@ export class PubSubManager {
|
|
|
1562
1563
|
COMMIT WORK;
|
|
1563
1564
|
`);
|
|
1564
1565
|
|
|
1565
|
-
|
|
1566
|
+
log("addTrigger.. ", { table_name, condition });
|
|
1566
1567
|
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1568
|
+
const triggers: {
|
|
1569
|
+
table_name: string;
|
|
1570
|
+
condition: string;
|
|
1571
|
+
}[] = await this.db.any(await this.getMyTriggerQuery());
|
|
1571
1572
|
|
|
1572
1573
|
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1574
|
+
this._triggers = {};
|
|
1575
|
+
triggers.map(t => {
|
|
1576
|
+
this._triggers = this._triggers || {};
|
|
1577
|
+
this._triggers[t.table_name] = this._triggers[t.table_name] || [];
|
|
1578
|
+
if (!this._triggers[t.table_name].includes(t.condition)) {
|
|
1579
|
+
this._triggers[t.table_name].push(t.condition)
|
|
1580
|
+
}
|
|
1581
|
+
});
|
|
1582
|
+
log("trigger added.. ", { table_name, condition });
|
|
1582
1583
|
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1584
|
+
return true;
|
|
1585
|
+
// console.log("1612", JSON.stringify(triggers, null, 2))
|
|
1586
|
+
// console.log("1613",JSON.stringify(this._triggers, null, 2))
|
|
1587
1587
|
|
|
1588
|
-
} catch(e){
|
|
1589
|
-
console.trace("Failed adding trigger", e);
|
|
1590
|
-
// throw e
|
|
1591
|
-
}
|
|
1592
1588
|
|
|
1589
|
+
} catch (e) {
|
|
1590
|
+
console.trace("Failed adding trigger", e);
|
|
1591
|
+
// throw e
|
|
1593
1592
|
}
|
|
1593
|
+
|
|
1594
|
+
}
|
|
1594
1595
|
}
|
|
1595
1596
|
|
|
1596
1597
|
|
|
1597
1598
|
/* Get only the specified properties of an object */
|
|
1598
|
-
export function filterObj(obj: AnyObject, keys: string[] = [], exclude?: string[]): AnyObject{
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1599
|
+
export function filterObj(obj: AnyObject, keys: string[] = [], exclude?: string[]): AnyObject {
|
|
1600
|
+
if (exclude && exclude.length) keys = Object.keys(obj).filter(k => !exclude.includes(k))
|
|
1601
|
+
if (!keys.length) {
|
|
1602
|
+
// console.warn("filterObj: returning empty object");
|
|
1603
|
+
return {};
|
|
1604
|
+
}
|
|
1605
|
+
if (obj && keys.length) {
|
|
1606
|
+
let res: AnyObject = {};
|
|
1607
|
+
keys.map(k => {
|
|
1608
|
+
res[k] = obj[k];
|
|
1609
|
+
});
|
|
1610
|
+
return res;
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
return obj;
|
|
1613
1614
|
}
|