@vlasky/zongji 0.5.9 → 0.6.0
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/.claude/settings.local.json +16 -0
- package/.mcp.json +17 -0
- package/.tap/processinfo/84de694d-068b-4ac7-b4d8-f835c2122b2d.json +892 -0
- package/.tap/processinfo/a1e9627e-66eb-412c-aaa5-079a2eb9f385.json +891 -0
- package/.tap/processinfo/abc34c6f-f4aa-4405-8f9a-947048be1df2.json +892 -0
- package/.tap/processinfo/affe6c5b-a34d-41b9-bb73-4b14d35138de.json +891 -0
- package/.tap/processinfo/bf82449d-cdeb-478b-9506-b939a9506bf2.json +883 -0
- package/.tap/processinfo/dd98c508-7067-4342-bdf5-63664b630701.json +892 -0
- package/.tap/test-results/test_await_end.js.tap +19 -0
- package/.tap/test-results/test_await_end2.js.tap +27 -0
- package/.tap/test-results/test_debug_init.js.tap +23 -0
- package/.tap/test-results/test_debug_init2.js.tap +35 -0
- package/.tap/test-results/test_invalid_host.js.tap +34 -0
- package/.tap/test-results/test_minimal.js.tap +26 -0
- package/.tap/test-results/test_queryseq.js.tap +14 -0
- package/.tap/test-results/test_timing.js.tap +17 -0
- package/.tap/test-results/test_unref.js.tap +15 -0
- package/CHANGELOG.md +55 -0
- package/CLAUDE.md +99 -0
- package/GTID.md +54 -0
- package/README.md +44 -21
- package/TODO.md +43 -0
- package/docker-compose.yml +14 -13
- package/docker-test.sh +7 -7
- package/eslint.config.js +35 -0
- package/example.js +1 -1
- package/index.d.ts +314 -0
- package/index.js +444 -272
- package/jsconfig.json +15 -0
- package/lib/binlog_event.js +296 -217
- package/lib/code_map.js +7 -4
- package/lib/common.js +35 -22
- package/lib/datetime_decode.js +147 -34
- package/lib/json_decode.js +12 -6
- package/lib/packet/binlog.js +3 -3
- package/lib/packet/combinlog.js +22 -20
- package/lib/packet/index.js +50 -50
- package/lib/reader.js +215 -109
- package/lib/rows_event.js +98 -102
- package/lib/sequence/binlog.js +142 -39
- package/notes.md +77 -0
- package/package.json +15 -10
- package/scripts/start-mysql.sh +28 -0
- package/workerthreads.md +173 -0
- package/.eslintrc +0 -19
package/index.js
CHANGED
|
@@ -1,355 +1,527 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const initBinlogClass = require('./lib/sequence/binlog');
|
|
1
|
+
import mysql from 'mysql2';
|
|
2
|
+
import { EventEmitter } from 'events';
|
|
3
|
+
import initBinlogClass from './lib/sequence/binlog.js';
|
|
5
4
|
|
|
6
5
|
const ConnectionConfigMap = {
|
|
7
6
|
'Connection': obj => obj.config,
|
|
8
7
|
'Pool': obj => obj.config.connectionConfig,
|
|
9
8
|
};
|
|
10
9
|
|
|
11
|
-
const TableInfoQueryTemplate = `SELECT
|
|
12
|
-
COLUMN_NAME, COLLATION_NAME, CHARACTER_SET_NAME,
|
|
13
|
-
COLUMN_COMMENT, COLUMN_TYPE
|
|
14
|
-
FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA='%s' AND TABLE_NAME='%s'
|
|
10
|
+
const TableInfoQueryTemplate = `SELECT
|
|
11
|
+
COLUMN_NAME, COLLATION_NAME, CHARACTER_SET_NAME,
|
|
12
|
+
COLUMN_COMMENT, COLUMN_TYPE
|
|
13
|
+
FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA='%s' AND TABLE_NAME='%s'
|
|
15
14
|
ORDER BY ORDINAL_POSITION`;
|
|
16
15
|
|
|
17
|
-
function
|
|
18
|
-
|
|
16
|
+
// Simple format function to replace %s placeholders
|
|
17
|
+
function formatSql(template, ...args) {
|
|
18
|
+
let i = 0;
|
|
19
|
+
return template.replace(/%s/g, () => args[i++]);
|
|
20
|
+
}
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
this.tableMap = {};
|
|
24
|
-
this.ready = false;
|
|
25
|
-
this.useChecksum = false;
|
|
22
|
+
class ZongJi extends EventEmitter {
|
|
23
|
+
constructor(dsn) {
|
|
24
|
+
super();
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
this._pendingErrors = [];
|
|
27
|
+
this.on('newListener', (event) => {
|
|
28
|
+
if (event !== 'error' || this._pendingErrors.length === 0) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const pending = this._pendingErrors.slice();
|
|
32
|
+
this._pendingErrors.length = 0;
|
|
33
|
+
process.nextTick(() => {
|
|
34
|
+
pending.forEach(err => this.emit('error', err));
|
|
35
|
+
});
|
|
36
|
+
});
|
|
29
37
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return connection;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const configFunc = ConnectionConfigMap[dsn.constructor.name];
|
|
45
|
-
let binlogDsn;
|
|
46
|
-
|
|
47
|
-
if (typeof dsn === 'object' && configFunc) {
|
|
48
|
-
// dsn is a pool or connection object
|
|
49
|
-
let conn = dsn; // reuse as ctrlConnection
|
|
50
|
-
this.ctrlConnection = conn;
|
|
38
|
+
this._options({});
|
|
39
|
+
this._filters({});
|
|
40
|
+
this.ctrlCallbacks = [];
|
|
41
|
+
this.tableMap = {};
|
|
42
|
+
this.ready = false;
|
|
43
|
+
this.stopped = false;
|
|
44
|
+
this.useChecksum = false;
|
|
45
|
+
|
|
46
|
+
this._dsn = dsn;
|
|
47
|
+
this.ctrlConnection = null;
|
|
48
|
+
this.connection = null;
|
|
51
49
|
this.ctrlConnectionOwner = false;
|
|
52
|
-
binlogDsn = Object.assign({}, configFunc(conn));
|
|
53
50
|
}
|
|
54
51
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
52
|
+
// dsn - can be one instance of Connection or Pool / object / url string
|
|
53
|
+
_establishConnection(dsn) {
|
|
54
|
+
const createConnection = (options) => {
|
|
55
|
+
const emitError = (err) => {
|
|
56
|
+
if (this.listenerCount('error') === 0) {
|
|
57
|
+
this._pendingErrors.push(err);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
this.emit('error', err);
|
|
61
|
+
};
|
|
62
|
+
let connection = mysql.createConnection(options);
|
|
63
|
+
connection.on('error', emitError);
|
|
64
|
+
// don't need to call connection.connect() here
|
|
65
|
+
// we use implicitly established connection
|
|
66
|
+
// see https://github.com/mysqljs/mysql#establishing-connections
|
|
67
|
+
return connection;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const configFunc = ConnectionConfigMap[dsn.constructor.name];
|
|
71
|
+
let binlogDsn;
|
|
72
|
+
const sanitizeConnectionOptions = (options) => {
|
|
73
|
+
if (!options) return options;
|
|
74
|
+
const cleaned = Object.assign({}, options);
|
|
75
|
+
delete cleaned.maxPacketSize;
|
|
76
|
+
delete cleaned.clientFlags;
|
|
77
|
+
return cleaned;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
if (typeof dsn === 'object' && configFunc) {
|
|
81
|
+
// dsn is a pool or connection object
|
|
82
|
+
let conn = dsn; // reuse as ctrlConnection
|
|
83
|
+
this.ctrlConnection = conn;
|
|
84
|
+
this.ctrlConnectionOwner = false;
|
|
85
|
+
binlogDsn = sanitizeConnectionOptions(configFunc(conn));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!binlogDsn) {
|
|
89
|
+
// assuming that the object passed is the connection settings
|
|
90
|
+
this.ctrlConnectionOwner = true;
|
|
91
|
+
this.ctrlConnection = createConnection(dsn);
|
|
92
|
+
binlogDsn = dsn;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
this.connection = createConnection(binlogDsn);
|
|
60
96
|
}
|
|
61
97
|
|
|
62
|
-
|
|
63
|
-
|
|
98
|
+
_isChecksumEnabled(next) {
|
|
99
|
+
const SelectChecksumParamSql = 'select @@GLOBAL.binlog_checksum as checksum';
|
|
100
|
+
const SetChecksumSql = 'set @master_binlog_checksum=@@global.binlog_checksum';
|
|
64
101
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
102
|
+
let done = false;
|
|
103
|
+
const finish = (err, enabled) => {
|
|
104
|
+
if (done) return;
|
|
105
|
+
done = true;
|
|
106
|
+
next(err, enabled);
|
|
107
|
+
};
|
|
68
108
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
conn.
|
|
73
|
-
|
|
109
|
+
const isConnectionReady = (conn) => {
|
|
110
|
+
return conn &&
|
|
111
|
+
conn.state !== 'disconnected' &&
|
|
112
|
+
!conn._fatalError &&
|
|
113
|
+
!conn._protocolError &&
|
|
114
|
+
!conn._closing;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const query = (conn, sql) => {
|
|
118
|
+
if (!isConnectionReady(conn)) {
|
|
119
|
+
return Promise.resolve(null);
|
|
120
|
+
}
|
|
121
|
+
return new Promise(
|
|
122
|
+
(resolve, reject) => {
|
|
123
|
+
try {
|
|
124
|
+
conn.query(sql, (err, result) => {
|
|
125
|
+
if (err) {
|
|
126
|
+
reject(err);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
resolve(result);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
} catch (err) {
|
|
74
133
|
reject(err);
|
|
75
134
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
);
|
|
82
|
-
};
|
|
135
|
+
}
|
|
136
|
+
);
|
|
137
|
+
};
|
|
83
138
|
|
|
84
|
-
|
|
139
|
+
let checksumEnabled = true;
|
|
85
140
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
141
|
+
query(this.ctrlConnection, SelectChecksumParamSql)
|
|
142
|
+
.then(rows => {
|
|
143
|
+
if (!rows) {
|
|
144
|
+
checksumEnabled = false;
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
if (rows[0].checksum === 'NONE') {
|
|
148
|
+
checksumEnabled = false;
|
|
149
|
+
return query(this.connection, 'SELECT 1');
|
|
150
|
+
}
|
|
92
151
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
152
|
+
if (checksumEnabled) {
|
|
153
|
+
return query(this.connection, SetChecksumSql);
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
.catch(err => {
|
|
157
|
+
if (err.toString().match(/ER_UNKNOWN_SYSTEM_VARIABLE/)) {
|
|
158
|
+
checksumEnabled = false;
|
|
159
|
+
// a simple query to open this.connection
|
|
160
|
+
return query(this.connection, 'SELECT 1');
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
return finish(err);
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
.then(() => {
|
|
167
|
+
finish(null, checksumEnabled);
|
|
168
|
+
})
|
|
169
|
+
.catch(err => {
|
|
170
|
+
finish(err);
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
_findBinlogEnd(next) {
|
|
175
|
+
this.ctrlConnection.query('SHOW BINARY LOGS', (err, rows) => {
|
|
176
|
+
if (err) {
|
|
177
|
+
// Errors should be emitted
|
|
178
|
+
next(err);
|
|
102
179
|
}
|
|
103
180
|
else {
|
|
104
|
-
next(
|
|
181
|
+
next(null, rows.length > 0 ? rows[rows.length - 1] : null);
|
|
105
182
|
}
|
|
106
|
-
})
|
|
107
|
-
.then(() => {
|
|
108
|
-
next(null, checksumEnabled);
|
|
109
183
|
});
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
ZongJi.prototype._findBinlogEnd = function(next) {
|
|
113
|
-
this.ctrlConnection.query('SHOW BINARY LOGS', (err, rows) => {
|
|
114
|
-
if (err) {
|
|
115
|
-
// Errors should be emitted
|
|
116
|
-
next(err);
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
next(null, rows.length > 0 ? rows[rows.length - 1] : null);
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
};
|
|
184
|
+
}
|
|
123
185
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
186
|
+
_fetchTableInfo(tableMapEvent, next) {
|
|
187
|
+
const sql = formatSql(TableInfoQueryTemplate,
|
|
188
|
+
tableMapEvent.schemaName, tableMapEvent.tableName);
|
|
127
189
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
// processed since next() will never be called
|
|
190
|
+
if (!this.ctrlConnection ||
|
|
191
|
+
this.ctrlConnection.state === 'disconnected' ||
|
|
192
|
+
this.ctrlConnection._fatalError ||
|
|
193
|
+
this.ctrlConnection._protocolError ||
|
|
194
|
+
this.ctrlConnection._closing) {
|
|
134
195
|
return;
|
|
135
196
|
}
|
|
136
197
|
|
|
137
|
-
|
|
138
|
-
this.
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
198
|
+
try {
|
|
199
|
+
this.ctrlConnection.query(sql, (err, rows) => {
|
|
200
|
+
if (err) {
|
|
201
|
+
// Errors should be emitted
|
|
202
|
+
this.emit('error', err);
|
|
203
|
+
// This is a fatal error, no additional binlog events will be
|
|
204
|
+
// processed since next() will never be called
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
145
207
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
208
|
+
if (rows.length === 0) {
|
|
209
|
+
this.emit('error', new Error(
|
|
210
|
+
'Insufficient permissions to access: ' +
|
|
211
|
+
tableMapEvent.schemaName + '.' + tableMapEvent.tableName));
|
|
212
|
+
// This is a fatal error, no additional binlog events will be
|
|
213
|
+
// processed since next() will never be called
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
151
216
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
217
|
+
this.tableMap[tableMapEvent.tableId] = {
|
|
218
|
+
columnSchemas: rows,
|
|
219
|
+
parentSchema: tableMapEvent.schemaName,
|
|
220
|
+
tableName: tableMapEvent.tableName
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
next();
|
|
224
|
+
});
|
|
225
|
+
} catch (err) {
|
|
226
|
+
this.emit('error', err);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
155
229
|
|
|
156
|
-
// #_options will reset all the options.
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
filename,
|
|
160
|
-
position,
|
|
161
|
-
startAtEnd,
|
|
162
|
-
}) {
|
|
163
|
-
this.options = {
|
|
230
|
+
// #_options will reset all the options.
|
|
231
|
+
/** @param {object} [options] */
|
|
232
|
+
_options({
|
|
164
233
|
serverId,
|
|
165
234
|
filename,
|
|
166
235
|
position,
|
|
167
236
|
startAtEnd,
|
|
168
|
-
}
|
|
169
|
-
|
|
237
|
+
} = {}) {
|
|
238
|
+
this.options = {
|
|
239
|
+
serverId,
|
|
240
|
+
filename,
|
|
241
|
+
position,
|
|
242
|
+
startAtEnd,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
170
245
|
|
|
171
|
-
// #_filters will reset all the filters.
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
excludeEvents,
|
|
175
|
-
includeSchema,
|
|
176
|
-
excludeSchema,
|
|
177
|
-
}) {
|
|
178
|
-
this.filters = {
|
|
246
|
+
// #_filters will reset all the filters.
|
|
247
|
+
/** @param {object} [options] */
|
|
248
|
+
_filters({
|
|
179
249
|
includeEvents,
|
|
180
250
|
excludeEvents,
|
|
181
251
|
includeSchema,
|
|
182
252
|
excludeSchema,
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
253
|
+
} = {}) {
|
|
254
|
+
this.filters = {
|
|
255
|
+
includeEvents,
|
|
256
|
+
excludeEvents,
|
|
257
|
+
includeSchema,
|
|
258
|
+
excludeSchema,
|
|
259
|
+
};
|
|
190
260
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
261
|
+
|
|
262
|
+
get(name) {
|
|
263
|
+
let result;
|
|
264
|
+
if (typeof name === 'string') {
|
|
265
|
+
result = this.options[name];
|
|
266
|
+
}
|
|
267
|
+
else if (Array.isArray(name)) {
|
|
268
|
+
result = name.reduce(
|
|
269
|
+
(acc, cur) => {
|
|
270
|
+
acc[cur] = this.options[cur];
|
|
271
|
+
return acc;
|
|
272
|
+
},
|
|
273
|
+
{}
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return result;
|
|
199
278
|
}
|
|
200
279
|
|
|
201
|
-
|
|
202
|
-
|
|
280
|
+
// @options contains a list options
|
|
281
|
+
// - `serverId` unique identifier
|
|
282
|
+
// - `filename`, `position` the position of binlog to begin with
|
|
283
|
+
// - `startAtEnd` if true, will update filename / position automatically
|
|
284
|
+
// - `includeEvents`, `excludeEvents`, `includeSchema`, `excludeSchema` filter different binlog events bubbling
|
|
285
|
+
start(options = {}) {
|
|
286
|
+
// If already running, just update filters (for pause/resume) and return
|
|
287
|
+
if (this.ready && !this.stopped) {
|
|
288
|
+
this._filters(options);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
203
291
|
|
|
204
|
-
|
|
205
|
-
// - `serverId` unique identifier
|
|
206
|
-
// - `filename`, `position` the position of binlog to beigin with
|
|
207
|
-
// - `startAtEnd` if true, will update filename / postion automatically
|
|
208
|
-
// - `includeEvents`, `excludeEvents`, `includeSchema`, `exludeSchema` filter different binlog events bubbling
|
|
209
|
-
ZongJi.prototype.start = function(options = {}) {
|
|
292
|
+
this.stopped = false;
|
|
210
293
|
|
|
211
|
-
|
|
212
|
-
|
|
294
|
+
if (!this.connection || !this.ctrlConnection) {
|
|
295
|
+
this._establishConnection(this._dsn);
|
|
296
|
+
}
|
|
213
297
|
|
|
214
|
-
|
|
215
|
-
this.
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
298
|
+
this._options(options);
|
|
299
|
+
this._filters(options);
|
|
300
|
+
|
|
301
|
+
const testChecksum = (resolve, reject) => {
|
|
302
|
+
this._isChecksumEnabled((err, checksumEnabled) => {
|
|
303
|
+
if (err) {
|
|
304
|
+
reject(err);
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
this.useChecksum = checksumEnabled;
|
|
308
|
+
resolve();
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
};
|
|
225
312
|
|
|
226
313
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
314
|
+
const findBinlogEnd = (resolve, reject) => {
|
|
315
|
+
this._findBinlogEnd((err, result) => {
|
|
316
|
+
if (err) {
|
|
317
|
+
return reject(err);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (result) {
|
|
321
|
+
this._options(
|
|
322
|
+
Object.assign({}, options, {
|
|
323
|
+
filename: result.Log_name,
|
|
324
|
+
position: result.File_size,
|
|
325
|
+
})
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
resolve();
|
|
330
|
+
});
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
const binlogHandler = (error, event) => {
|
|
334
|
+
if (error) {
|
|
335
|
+
return this.emit('error', error);
|
|
231
336
|
}
|
|
232
337
|
|
|
233
|
-
if
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
338
|
+
// Ignore events if connection has been stopped
|
|
339
|
+
if (this.stopped || !this.connection) return;
|
|
340
|
+
|
|
341
|
+
// Do not emit events that have been filtered out
|
|
342
|
+
if (event === undefined || event._filtered === true) return;
|
|
343
|
+
|
|
344
|
+
switch (event.getTypeName()) {
|
|
345
|
+
case 'TableMap': {
|
|
346
|
+
const tableMap = this.tableMap[event.tableId];
|
|
347
|
+
if (!tableMap || tableMap.tableName !== event.tableName || tableMap.columns.length !== event.columnCount) {
|
|
348
|
+
if (!this.connection) return;
|
|
349
|
+
this.connection.pause();
|
|
350
|
+
this._fetchTableInfo(event, () => {
|
|
351
|
+
// merge the column info with metadata
|
|
352
|
+
event.updateColumnInfo();
|
|
353
|
+
this.emit('binlog', event);
|
|
354
|
+
if (this.connection) this.connection.resume();
|
|
355
|
+
});
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
360
|
+
case 'Rotate':
|
|
361
|
+
if (this.options.filename !== event.binlogName) {
|
|
362
|
+
this.options.filename = event.binlogName;
|
|
363
|
+
}
|
|
364
|
+
break;
|
|
240
365
|
}
|
|
366
|
+
this.options.position = event.nextPosition;
|
|
367
|
+
this.emit('binlog', event);
|
|
368
|
+
};
|
|
241
369
|
|
|
242
|
-
|
|
243
|
-
});
|
|
244
|
-
};
|
|
370
|
+
let promises = [new Promise(testChecksum)];
|
|
245
371
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
return this.emit('error', error);
|
|
372
|
+
if (this.options.startAtEnd) {
|
|
373
|
+
promises.push(new Promise(findBinlogEnd));
|
|
249
374
|
}
|
|
250
375
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
case 'TableMap': {
|
|
256
|
-
const tableMap = this.tableMap[event.tableId];
|
|
257
|
-
if (!tableMap || tableMap.tableName !== event.tableName || tableMap.columns.length !== event.columnCount) {
|
|
258
|
-
this.connection.pause();
|
|
259
|
-
this._fetchTableInfo(event, () => {
|
|
260
|
-
// merge the column info with metadata
|
|
261
|
-
event.updateColumnInfo();
|
|
262
|
-
this.emit('binlog', event);
|
|
263
|
-
this.connection.resume();
|
|
264
|
-
});
|
|
376
|
+
Promise.all(promises)
|
|
377
|
+
.then(() => {
|
|
378
|
+
// Check if stop() was called while promises were pending
|
|
379
|
+
if (this.stopped) {
|
|
265
380
|
return;
|
|
266
381
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
382
|
+
|
|
383
|
+
this.BinlogClass = initBinlogClass(this);
|
|
384
|
+
this.ready = true;
|
|
385
|
+
this.emit('ready');
|
|
386
|
+
|
|
387
|
+
// Final check right before addCommand - connection may have been destroyed
|
|
388
|
+
if (this.stopped) {
|
|
389
|
+
return;
|
|
272
390
|
}
|
|
273
|
-
break;
|
|
274
|
-
}
|
|
275
|
-
this.options.position = event.nextPosition;
|
|
276
|
-
this.emit('binlog', event);
|
|
277
|
-
};
|
|
278
391
|
|
|
279
|
-
|
|
392
|
+
// When compression is enabled, patch handlePacket to sync sequence ID.
|
|
393
|
+
// MySQL resets inner packet sequence IDs within compressed chunks for
|
|
394
|
+
// binlog streams, causing mysql2's sequence validation to emit warnings.
|
|
395
|
+
// By syncing the expected sequence ID to match the incoming packet, we
|
|
396
|
+
// prevent these harmless warnings. This is safe because binlog events
|
|
397
|
+
// track position independently (binlog file + position / GTID).
|
|
398
|
+
// Only applied when compression is enabled to preserve normal error
|
|
399
|
+
// detection for uncompressed connections.
|
|
400
|
+
if (this.connection.config && this.connection.config.compress) {
|
|
401
|
+
// @ts-ignore - internal mysql2 API
|
|
402
|
+
const originalHandlePacket = this.connection.handlePacket.bind(this.connection);
|
|
403
|
+
// @ts-ignore - internal mysql2 API
|
|
404
|
+
this.connection.handlePacket = function(packet) {
|
|
405
|
+
if (packet && typeof packet.sequenceId !== 'undefined') {
|
|
406
|
+
this.sequenceId = packet.sequenceId;
|
|
407
|
+
}
|
|
408
|
+
return originalHandlePacket(packet);
|
|
409
|
+
};
|
|
410
|
+
}
|
|
280
411
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
412
|
+
// @ts-ignore - internal mysql2 API
|
|
413
|
+
this.connection.addCommand(new this.BinlogClass(binlogHandler));
|
|
414
|
+
})
|
|
415
|
+
.catch(err => {
|
|
416
|
+
this.emit('error', err);
|
|
417
|
+
});
|
|
284
418
|
|
|
285
|
-
|
|
286
|
-
.then(() => {
|
|
287
|
-
this.BinlogClass = initBinlogClass(this);
|
|
288
|
-
this.ready = true;
|
|
289
|
-
this.emit('ready');
|
|
419
|
+
}
|
|
290
420
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
})
|
|
295
|
-
.catch(err => {
|
|
296
|
-
this.emit('error', err);
|
|
297
|
-
});
|
|
421
|
+
stop() {
|
|
422
|
+
this.stopped = true;
|
|
423
|
+
this.ready = false;
|
|
298
424
|
|
|
299
|
-
|
|
425
|
+
if (!this.connection && !this.ctrlConnection) {
|
|
426
|
+
this.emit('stopped');
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
300
429
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
if (this.
|
|
430
|
+
// Binary log connection does not end with destroy()
|
|
431
|
+
let connectionThreadId = null;
|
|
432
|
+
if (this.connection) {
|
|
433
|
+
connectionThreadId = this.connection.threadId;
|
|
434
|
+
this.connection.destroy();
|
|
435
|
+
// @ts-ignore - internal mysql2 API
|
|
436
|
+
if (this.connection.stream && typeof this.connection.stream.unref === 'function') {
|
|
437
|
+
// @ts-ignore - internal mysql2 API
|
|
438
|
+
this.connection.stream.unref();
|
|
439
|
+
}
|
|
440
|
+
this.connection = null;
|
|
441
|
+
}
|
|
442
|
+
let finished = false;
|
|
443
|
+
const finish = () => {
|
|
444
|
+
if (finished) return;
|
|
445
|
+
finished = true;
|
|
446
|
+
if (this.ctrlConnectionOwner && this.ctrlConnection) {
|
|
308
447
|
this.ctrlConnection.destroy();
|
|
448
|
+
// @ts-ignore - internal mysql2 API
|
|
449
|
+
if (this.ctrlConnection.stream && typeof this.ctrlConnection.stream.unref === 'function') {
|
|
450
|
+
// @ts-ignore - internal mysql2 API
|
|
451
|
+
this.ctrlConnection.stream.unref();
|
|
452
|
+
}
|
|
453
|
+
this.ctrlConnection = null;
|
|
309
454
|
}
|
|
310
455
|
this.emit('stopped');
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
if (!this.ctrlConnection ||
|
|
459
|
+
this.ctrlConnection.state === 'disconnected' ||
|
|
460
|
+
this.ctrlConnection._fatalError ||
|
|
461
|
+
this.ctrlConnection._protocolError ||
|
|
462
|
+
this.ctrlConnection._closing) {
|
|
463
|
+
this.ctrlConnection = null;
|
|
464
|
+
return finish();
|
|
311
465
|
}
|
|
312
|
-
);
|
|
313
|
-
};
|
|
314
466
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
467
|
+
if (!connectionThreadId) {
|
|
468
|
+
return finish();
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const killTimeout = setTimeout(finish, 1000);
|
|
472
|
+
try {
|
|
473
|
+
this.ctrlConnection.query(
|
|
474
|
+
'KILL ' + connectionThreadId,
|
|
475
|
+
() => {
|
|
476
|
+
clearTimeout(killTimeout);
|
|
477
|
+
finish();
|
|
478
|
+
}
|
|
479
|
+
);
|
|
480
|
+
} catch {
|
|
481
|
+
clearTimeout(killTimeout);
|
|
482
|
+
finish();
|
|
483
|
+
}
|
|
484
|
+
}
|
|
319
485
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
486
|
+
// It includes every events by default.
|
|
487
|
+
_skipEvent(name) {
|
|
488
|
+
const includes = this.filters.includeEvents;
|
|
489
|
+
const excludes = this.filters.excludeEvents;
|
|
323
490
|
|
|
324
|
-
|
|
325
|
-
|
|
491
|
+
let included = (includes === undefined) ||
|
|
492
|
+
(Array.isArray(includes) && (includes.indexOf(name) > -1));
|
|
493
|
+
let excluded = Array.isArray(excludes) && (excludes.indexOf(name) > -1);
|
|
494
|
+
|
|
495
|
+
return excluded || !included;
|
|
496
|
+
}
|
|
326
497
|
|
|
327
|
-
// It doesn't skip any schema by default.
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
498
|
+
// It doesn't skip any schema by default.
|
|
499
|
+
_skipSchema(database, table) {
|
|
500
|
+
const includes = this.filters.includeSchema;
|
|
501
|
+
const excludes = this.filters.excludeSchema || {};
|
|
331
502
|
|
|
332
|
-
|
|
333
|
-
(
|
|
334
|
-
(database in includes) &&
|
|
503
|
+
let included = (includes === undefined) ||
|
|
335
504
|
(
|
|
336
|
-
|
|
505
|
+
(database in includes) &&
|
|
337
506
|
(
|
|
338
|
-
|
|
339
|
-
|
|
507
|
+
includes[database] === true ||
|
|
508
|
+
(
|
|
509
|
+
Array.isArray(includes[database]) &&
|
|
510
|
+
includes[database].indexOf(table) > -1
|
|
511
|
+
)
|
|
340
512
|
)
|
|
341
|
-
)
|
|
342
|
-
)
|
|
343
|
-
let excluded = (database in excludes) &&
|
|
344
|
-
(
|
|
345
|
-
excludes[database] === true ||
|
|
513
|
+
);
|
|
514
|
+
let excluded = (database in excludes) &&
|
|
346
515
|
(
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
516
|
+
excludes[database] === true ||
|
|
517
|
+
(
|
|
518
|
+
Array.isArray(excludes[database]) &&
|
|
519
|
+
excludes[database].indexOf(table) > -1
|
|
520
|
+
)
|
|
521
|
+
);
|
|
351
522
|
|
|
352
|
-
|
|
353
|
-
}
|
|
523
|
+
return excluded || !included;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
354
526
|
|
|
355
|
-
|
|
527
|
+
export default ZongJi;
|