@vlasky/zongji 0.7.1 → 0.8.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/CHANGELOG.md +14 -0
- package/README.md +71 -12
- package/index.d.ts +284 -7
- package/index.js +350 -28
- package/lib/binlog_event.js +879 -24
- package/lib/charset_map.js +86 -0
- package/lib/code_map.js +44 -1
- package/lib/common.js +376 -20
- package/lib/gtid_set.js +237 -0
- package/lib/mariadb_gtid.js +107 -0
- package/lib/packet/binlog.js +7 -1
- package/lib/rows_event.js +39 -7
- package/lib/sequence/binlog.js +178 -26
- package/package.json +6 -3
package/lib/gtid_set.js
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
// A MySQL GTID set: which transactions (per source) are already
|
|
2
|
+
// executed. String form matches @@gtid_executed / Previous_gtids, e.g.
|
|
3
|
+
// 3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5:11-18,
|
|
4
|
+
// 2C256447-3F0D-431B-9A25-BDDF1F1F6EF6:1-27
|
|
5
|
+
// MySQL 8.3+ GTIDs may carry a tag; a (uuid, tag) pair is its own
|
|
6
|
+
// source, printed merged into the uuid's block after the untagged
|
|
7
|
+
// intervals: 'uuid:1-5:tag_a:1:tag_b:1' (tags lowercase, sorted).
|
|
8
|
+
// Intervals are stored internally as [start, end] inclusive, kept sorted
|
|
9
|
+
// and coalesced. GNOs are JavaScript Numbers: MySQL allows up to 2^63-1,
|
|
10
|
+
// but real-world transaction counts sit comfortably inside 2^53.
|
|
11
|
+
class GtidSet {
|
|
12
|
+
constructor() {
|
|
13
|
+
// uuid (lowercase, dashed) -> Map of tag ('' = untagged) ->
|
|
14
|
+
// array of [start, end] inclusive
|
|
15
|
+
this._sids = new Map();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Accepts '', 'uuid:1-5', 'uuid:1-5:8,uuid2:3',
|
|
19
|
+
// 'uuid:1-5:tag_a:1:tag_b:2-3' (whitespace tolerated). Within a uuid
|
|
20
|
+
// block a non-numeric item is a tag naming the source of the interval
|
|
21
|
+
// items that follow it, per the Gtid_set::add_gtid_text grammar.
|
|
22
|
+
static parse(text) {
|
|
23
|
+
const set = new GtidSet();
|
|
24
|
+
if (!text) {
|
|
25
|
+
return set;
|
|
26
|
+
}
|
|
27
|
+
for (const entry of String(text).split(',')) {
|
|
28
|
+
const parts = entry.trim().split(':');
|
|
29
|
+
if (parts.length < 2) {
|
|
30
|
+
throw new Error(`Invalid GTID set entry: '${entry.trim()}'`);
|
|
31
|
+
}
|
|
32
|
+
const uuid = normaliseUuid(parts[0]);
|
|
33
|
+
let tag = '';
|
|
34
|
+
let intervals = 0;
|
|
35
|
+
// A tag names the interval items that follow it, so a tag section
|
|
36
|
+
// with none is malformed (the leading untagged section may be
|
|
37
|
+
// empty: 'uuid:tag_a:1' has no untagged intervals)
|
|
38
|
+
let sectionEmpty = false;
|
|
39
|
+
for (const item of parts.slice(1)) {
|
|
40
|
+
const trimmed = item.trim();
|
|
41
|
+
if (TAG_REGEX.test(trimmed)) {
|
|
42
|
+
if (sectionEmpty) {
|
|
43
|
+
throw new Error(`GTID tag without intervals: '${entry.trim()}'`);
|
|
44
|
+
}
|
|
45
|
+
tag = trimmed.toLowerCase();
|
|
46
|
+
sectionEmpty = true;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const bounds = trimmed.split('-');
|
|
50
|
+
const start = parsePositiveInt(bounds[0], entry);
|
|
51
|
+
const end = bounds.length > 1 ?
|
|
52
|
+
parsePositiveInt(bounds[1], entry) : start;
|
|
53
|
+
if (bounds.length > 2 || end < start) {
|
|
54
|
+
throw new Error(`Invalid GTID interval: '${entry.trim()}'`);
|
|
55
|
+
}
|
|
56
|
+
set._addInterval(uuid, tag, start, end);
|
|
57
|
+
sectionEmpty = false;
|
|
58
|
+
intervals++;
|
|
59
|
+
}
|
|
60
|
+
if (intervals === 0 || sectionEmpty) {
|
|
61
|
+
throw new Error(`Invalid GTID set entry: '${entry.trim()}'`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return set;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Adds a single transaction from a 'uuid:gno' or 'uuid:tag:gno' string
|
|
68
|
+
add(gtid) {
|
|
69
|
+
const { uuid, tag, gno } = parseSingleGtid(gtid);
|
|
70
|
+
this._addInterval(uuid, tag, gno, gno);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Whether a single transaction ('uuid:gno' or 'uuid:tag:gno', e.g. an
|
|
74
|
+
// event.gtid) is already contained in the set. null/undefined (an
|
|
75
|
+
// anonymous transaction's event.gtid) is never contained; a malformed
|
|
76
|
+
// string throws, as it would corrupt rather than answer.
|
|
77
|
+
contains(gtid) {
|
|
78
|
+
if (gtid === undefined || gtid === null) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
const { uuid, tag, gno } = parseSingleGtid(gtid);
|
|
82
|
+
const tags = this._sids.get(uuid);
|
|
83
|
+
const intervals = tags && tags.get(tag);
|
|
84
|
+
if (!intervals) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
return intervals.some(([start, end]) => gno >= start && gno <= end);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Adds [start, end] inclusive for a (uuid, tag) source, keeping
|
|
91
|
+
// intervals sorted and coalesced
|
|
92
|
+
_addInterval(uuid, tag, start, end) {
|
|
93
|
+
let tags = this._sids.get(uuid);
|
|
94
|
+
if (!tags) {
|
|
95
|
+
tags = new Map();
|
|
96
|
+
this._sids.set(uuid, tags);
|
|
97
|
+
}
|
|
98
|
+
const existing = tags.get(tag) || [];
|
|
99
|
+
existing.push([start, end]);
|
|
100
|
+
existing.sort((a, b) => a[0] - b[0]);
|
|
101
|
+
const merged = [existing[0]];
|
|
102
|
+
for (let i = 1; i < existing.length; i++) {
|
|
103
|
+
const last = merged[merged.length - 1];
|
|
104
|
+
const next = existing[i];
|
|
105
|
+
if (next[0] <= last[1] + 1) {
|
|
106
|
+
last[1] = Math.max(last[1], next[1]);
|
|
107
|
+
} else {
|
|
108
|
+
merged.push(next);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
tags.set(tag, merged);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
isEmpty() {
|
|
115
|
+
return this._sids.size === 0;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Sorted (uuid, then tag, untagged first) flat list of
|
|
119
|
+
// [uuid, tag, intervals] entries: the order both the text form and
|
|
120
|
+
// the wire encoding require
|
|
121
|
+
_sortedEntries() {
|
|
122
|
+
return [...this._sids.entries()]
|
|
123
|
+
.sort((a, b) => a[0] < b[0] ? -1 : 1)
|
|
124
|
+
.flatMap(([uuid, tags]) => [...tags.entries()]
|
|
125
|
+
.sort((a, b) => a[0] < b[0] ? -1 : 1)
|
|
126
|
+
.map(([tag, intervals]) => [uuid, tag, intervals]));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
toString() {
|
|
130
|
+
let text = '';
|
|
131
|
+
let lastUuid = null;
|
|
132
|
+
for (const [uuid, tag, intervals] of this._sortedEntries()) {
|
|
133
|
+
const ranges = intervals
|
|
134
|
+
.map(([start, end]) => start === end ? `${start}` : `${start}-${end}`)
|
|
135
|
+
.join(':');
|
|
136
|
+
if (uuid === lastUuid) {
|
|
137
|
+
text += `:${tag}:${ranges}`;
|
|
138
|
+
} else {
|
|
139
|
+
text += (text ? ',' : '') + uuid +
|
|
140
|
+
(tag ? `:${tag}` : '') + `:${ranges}`;
|
|
141
|
+
lastUuid = uuid;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return text;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Wire encoding used by COM_BINLOG_DUMP_GTID (and Previous_gtids).
|
|
148
|
+
// Untagged sets use the classic layout accepted by every server
|
|
149
|
+
// version: n_sids u64le, then per sid: 16-byte binary UUID,
|
|
150
|
+
// n_intervals u64le, then per interval: start u64le, end-exclusive
|
|
151
|
+
// u64le. A set containing any tagged source switches the whole
|
|
152
|
+
// payload to the tagged format (MySQL 8.3+, sql/rpl_gtid_set.cc
|
|
153
|
+
// encode_nsids_format): the first u64 becomes
|
|
154
|
+
// (1 << 56) | (n_entries << 8) | 1, and every entry carries a tag
|
|
155
|
+
// field (one length byte = length << 1, then the lowercase tag
|
|
156
|
+
// bytes; 0x00 for untagged) between the UUID and the interval count.
|
|
157
|
+
// Servers before 8.3 reject the tagged format as malformed.
|
|
158
|
+
encode() {
|
|
159
|
+
const entries = this._sortedEntries();
|
|
160
|
+
const tagged = entries.some(([, tag]) => tag !== '');
|
|
161
|
+
let length = 8;
|
|
162
|
+
for (const [, tag, intervals] of entries) {
|
|
163
|
+
length += 16 + (tagged ? 1 + tag.length : 0) + 8 +
|
|
164
|
+
intervals.length * 16;
|
|
165
|
+
}
|
|
166
|
+
const buffer = Buffer.alloc(length);
|
|
167
|
+
let offset = 0;
|
|
168
|
+
const nSidsField = tagged ?
|
|
169
|
+
(1n << 56n) | (BigInt(entries.length) << 8n) | 1n :
|
|
170
|
+
BigInt(entries.length);
|
|
171
|
+
offset = buffer.writeBigUInt64LE(nSidsField, offset);
|
|
172
|
+
for (const [uuid, tag, intervals] of entries) {
|
|
173
|
+
offset += Buffer.from(uuid.replace(/-/g, ''), 'hex')
|
|
174
|
+
.copy(buffer, offset);
|
|
175
|
+
if (tagged) {
|
|
176
|
+
offset = buffer.writeUInt8(tag.length << 1, offset);
|
|
177
|
+
offset += buffer.write(tag, offset, 'ascii');
|
|
178
|
+
}
|
|
179
|
+
offset = buffer.writeBigUInt64LE(BigInt(intervals.length), offset);
|
|
180
|
+
for (const [start, end] of intervals) {
|
|
181
|
+
offset = buffer.writeBigUInt64LE(BigInt(start), offset);
|
|
182
|
+
offset = buffer.writeBigUInt64LE(BigInt(end) + 1n, offset);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return buffer;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const UUID_REGEX =
|
|
190
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
191
|
+
|
|
192
|
+
// Tag grammar per libs/mysql/gtid/tag.cpp: a letter or underscore, then
|
|
193
|
+
// letters, digits or underscores, at most 32 characters, case-folded to
|
|
194
|
+
// lowercase. Cannot be confused with an interval (those start with a
|
|
195
|
+
// digit).
|
|
196
|
+
const TAG_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]{0,31}$/;
|
|
197
|
+
|
|
198
|
+
function parseSingleGtid(gtid) {
|
|
199
|
+
const parts = String(gtid).split(':');
|
|
200
|
+
if (parts.length !== 2 && parts.length !== 3) {
|
|
201
|
+
throw new Error(`Invalid GTID: '${gtid}'`);
|
|
202
|
+
}
|
|
203
|
+
const uuid = normaliseUuid(parts[0]);
|
|
204
|
+
let tag = '';
|
|
205
|
+
if (parts.length === 3) {
|
|
206
|
+
const candidate = parts[1].trim();
|
|
207
|
+
if (!TAG_REGEX.test(candidate)) {
|
|
208
|
+
throw new Error(`Invalid GTID tag in '${gtid}'`);
|
|
209
|
+
}
|
|
210
|
+
tag = candidate.toLowerCase();
|
|
211
|
+
}
|
|
212
|
+
const gno = parsePositiveInt(parts[parts.length - 1].trim(), gtid);
|
|
213
|
+
return { uuid, tag, gno };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function normaliseUuid(text) {
|
|
217
|
+
const uuid = text.trim().toLowerCase();
|
|
218
|
+
if (!UUID_REGEX.test(uuid)) {
|
|
219
|
+
throw new Error(`Invalid GTID source UUID: '${text.trim()}'`);
|
|
220
|
+
}
|
|
221
|
+
return uuid;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function parsePositiveInt(text, context) {
|
|
225
|
+
// Decimal digits only, per the MySQL GTID grammar: Number() alone would
|
|
226
|
+
// also accept '1e3', '0x10' or '1.0' and silently reinterpret them
|
|
227
|
+
if (!/^\d+$/.test(text)) {
|
|
228
|
+
throw new Error(`Invalid GTID number in '${context}': '${text}'`);
|
|
229
|
+
}
|
|
230
|
+
const value = Number(text);
|
|
231
|
+
if (!Number.isSafeInteger(value) || value < 1) {
|
|
232
|
+
throw new Error(`Invalid GTID number in '${context}': '${text}'`);
|
|
233
|
+
}
|
|
234
|
+
return value;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export { GtidSet };
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// A MariaDB GTID position: the last transaction processed per replication
|
|
2
|
+
// domain. String form matches @@gtid_binlog_pos / @@gtid_current_pos /
|
|
3
|
+
// @slave_connect_state, e.g. '0-1-1234,1-3-45'. Unlike MySQL GTID sets
|
|
4
|
+
// there are no intervals: within a domain the binlog order is total, so a
|
|
5
|
+
// single domain-server-sequence triple identifies everything before it,
|
|
6
|
+
// and a position holds at most one entry per domain. The server resumes a
|
|
7
|
+
// client by skipping event groups in each listed domain until it passes
|
|
8
|
+
// the given sequence number from the given server id; domains not listed
|
|
9
|
+
// are streamed from the beginning.
|
|
10
|
+
class MariadbGtidPosition {
|
|
11
|
+
constructor() {
|
|
12
|
+
// domainId (number) -> { domainId, serverId, seqNo }
|
|
13
|
+
this._domains = new Map();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Accepts '' or 'D-S-N[,D-S-N...]' (whitespace tolerated). Rejects two
|
|
17
|
+
// entries for one domain, as the server would (ER_DUPLICATE_GTID_DOMAIN)
|
|
18
|
+
static parse(text) {
|
|
19
|
+
const position = new MariadbGtidPosition();
|
|
20
|
+
if (!text) {
|
|
21
|
+
return position;
|
|
22
|
+
}
|
|
23
|
+
for (const entry of String(text).split(',')) {
|
|
24
|
+
const parts = entry.trim().split('-');
|
|
25
|
+
if (parts.length !== 3) {
|
|
26
|
+
throw new Error(`Invalid MariaDB GTID: '${entry.trim()}'`);
|
|
27
|
+
}
|
|
28
|
+
const domainId = parseUInt32(parts[0], entry);
|
|
29
|
+
const serverId = parseUInt32(parts[1], entry);
|
|
30
|
+
const seqNo = parseSeqNo(parts[2], entry);
|
|
31
|
+
if (position._domains.has(domainId)) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`Duplicate replication domain ${domainId} in GTID position ` +
|
|
34
|
+
`'${text}'`);
|
|
35
|
+
}
|
|
36
|
+
position._domains.set(domainId, { domainId, serverId, seqNo });
|
|
37
|
+
}
|
|
38
|
+
return position;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Builds the position implied by a GTID_LIST_EVENT: the event may hold
|
|
42
|
+
// several entries per domain (one per server id); the last entry of a
|
|
43
|
+
// domain's run is the most recent, so plain per-domain overwrite in
|
|
44
|
+
// event order yields the right watermark
|
|
45
|
+
static fromGtidList(entries) {
|
|
46
|
+
const position = new MariadbGtidPosition();
|
|
47
|
+
for (const entry of entries) {
|
|
48
|
+
position.add(entry);
|
|
49
|
+
}
|
|
50
|
+
return position;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Records { domainId, serverId, seqNo } as the last transaction
|
|
54
|
+
// processed in its domain. Deliberately an overwrite, not a max: after
|
|
55
|
+
// a failover within a domain the newest transaction can carry a lower
|
|
56
|
+
// sequence number under a different server id, and "last processed" is
|
|
57
|
+
// what the server's skip rule keys on
|
|
58
|
+
add(gtid) {
|
|
59
|
+
this._domains.set(gtid.domainId, {
|
|
60
|
+
domainId: gtid.domainId,
|
|
61
|
+
serverId: gtid.serverId,
|
|
62
|
+
seqNo: gtid.seqNo,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
isEmpty() {
|
|
67
|
+
return this._domains.size === 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
toString() {
|
|
71
|
+
return [...this._domains.values()]
|
|
72
|
+
.sort((a, b) => a.domainId - b.domainId)
|
|
73
|
+
.map(({ domainId, serverId, seqNo }) =>
|
|
74
|
+
`${domainId}-${serverId}-${seqNo}`)
|
|
75
|
+
.join(',');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function parseUInt32(text, context) {
|
|
80
|
+
// Decimal digits only: Number() alone would also accept '1e3' or '0x10'
|
|
81
|
+
if (!/^\d+$/.test(text)) {
|
|
82
|
+
throw new Error(`Invalid MariaDB GTID in '${context}': '${text}'`);
|
|
83
|
+
}
|
|
84
|
+
const value = Number(text);
|
|
85
|
+
if (!Number.isSafeInteger(value) || value > 0xffffffff) {
|
|
86
|
+
throw new Error(`Invalid MariaDB GTID in '${context}': '${text}'`);
|
|
87
|
+
}
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function parseSeqNo(text, context) {
|
|
92
|
+
if (!/^\d+$/.test(text)) {
|
|
93
|
+
throw new Error(`Invalid MariaDB GTID in '${context}': '${text}'`);
|
|
94
|
+
}
|
|
95
|
+
// seq_no is u64; keep the Number-or-exact-string convention used for
|
|
96
|
+
// other 64-bit values
|
|
97
|
+
const value = Number(text);
|
|
98
|
+
if (Number.isSafeInteger(value)) {
|
|
99
|
+
return value;
|
|
100
|
+
}
|
|
101
|
+
if (BigInt(text) > 0xffffffffffffffffn) {
|
|
102
|
+
throw new Error(`Invalid MariaDB GTID in '${context}': '${text}'`);
|
|
103
|
+
}
|
|
104
|
+
return text;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export { MariadbGtidPosition };
|
package/lib/packet/binlog.js
CHANGED
|
@@ -17,7 +17,7 @@ export default function initBinlogPacketClass(zongji) {
|
|
|
17
17
|
|
|
18
18
|
const timestamp = parser.parseUnsignedNumber(4) * 1000;
|
|
19
19
|
const eventType = parser.parseUnsignedNumber(1);
|
|
20
|
-
parser.parseUnsignedNumber(4);
|
|
20
|
+
const serverId = parser.parseUnsignedNumber(4);
|
|
21
21
|
const eventLength = parser.parseUnsignedNumber(4);
|
|
22
22
|
const nextPosition = parser.parseUnsignedNumber(4);
|
|
23
23
|
parser.parseUnsignedNumber(2); // flags
|
|
@@ -27,7 +27,13 @@ export default function initBinlogPacketClass(zongji) {
|
|
|
27
27
|
nextPosition: nextPosition,
|
|
28
28
|
size: eventLength - BinlogPacket.Length,
|
|
29
29
|
eventType: eventType,
|
|
30
|
+
// MariaDB GTID events need it: the GTID's server component lives
|
|
31
|
+
// in the common header
|
|
32
|
+
serverId: serverId,
|
|
30
33
|
};
|
|
34
|
+
// Exposed for bookkeeping on events that are filtered out before
|
|
35
|
+
// being parsed (the event object never exists for those)
|
|
36
|
+
this.nextPosition = nextPosition;
|
|
31
37
|
|
|
32
38
|
const EventClass = getEventClass(eventType);
|
|
33
39
|
this.eventName = EventClass.name;
|
package/lib/rows_event.js
CHANGED
|
@@ -1,10 +1,26 @@
|
|
|
1
1
|
import { BinlogEvent } from './binlog_event.js';
|
|
2
2
|
import * as Common from './common.js';
|
|
3
|
+
import { Parser } from './reader.js';
|
|
3
4
|
|
|
4
5
|
const Version2Events = [
|
|
5
6
|
0x1e, // WRITE_ROWS_EVENT_V2,
|
|
6
7
|
0x1f, // UPDATE_ROWS_EVENT_V2,
|
|
7
8
|
0x20, // DELETE_ROWS_EVENT_V2
|
|
9
|
+
0xa9, // WRITE_ROWS_COMPRESSED_EVENT (MariaDB, v2 layout)
|
|
10
|
+
0xaa, // UPDATE_ROWS_COMPRESSED_EVENT
|
|
11
|
+
0xab, // DELETE_ROWS_COMPRESSED_EVENT
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
// MariaDB log_bin_compress=ON variants: identical to the corresponding
|
|
15
|
+
// plain event through the columns-present bitmap(s); the row-image area
|
|
16
|
+
// (from the first row's null bitmap) is a compressed-payload envelope
|
|
17
|
+
const CompressedEvents = [
|
|
18
|
+
0xa6, // WRITE_ROWS_COMPRESSED_EVENT_V1
|
|
19
|
+
0xa7, // UPDATE_ROWS_COMPRESSED_EVENT_V1
|
|
20
|
+
0xa8, // DELETE_ROWS_COMPRESSED_EVENT_V1
|
|
21
|
+
0xa9, // WRITE_ROWS_COMPRESSED_EVENT
|
|
22
|
+
0xaa, // UPDATE_ROWS_COMPRESSED_EVENT
|
|
23
|
+
0xab, // DELETE_ROWS_COMPRESSED_EVENT
|
|
8
24
|
];
|
|
9
25
|
|
|
10
26
|
const CHECKSUM_SIZE = 4;
|
|
@@ -72,20 +88,36 @@ class RowsEvent extends BinlogEvent {
|
|
|
72
88
|
this.columns_present_bitmap2 = parser.parseBuffer(columnsPresentBitmapSize);
|
|
73
89
|
}
|
|
74
90
|
|
|
75
|
-
|
|
91
|
+
let rowsParser = parser;
|
|
92
|
+
let trimChecksum = this.useChecksum;
|
|
93
|
+
if (CompressedEvents.indexOf(options.eventType) !== -1) {
|
|
94
|
+
// Inflate the row-image area and read the rows from the inflated
|
|
95
|
+
// bytes; the event's CRC covers the compressed bytes and stays on
|
|
96
|
+
// the original parser
|
|
97
|
+
const end =
|
|
98
|
+
parser._packetEnd - (this.useChecksum ? CHECKSUM_SIZE : 0);
|
|
99
|
+
const inflated = Common.uncompressBinlogEventPayload(
|
|
100
|
+
parser.parseBuffer(end - parser._offset));
|
|
101
|
+
parser._offset = parser._packetEnd;
|
|
102
|
+
rowsParser = new Parser();
|
|
103
|
+
rowsParser.append(inflated);
|
|
104
|
+
trimChecksum = false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (trimChecksum) {
|
|
76
108
|
// Ignore the checksum at the end of this packet
|
|
77
|
-
|
|
109
|
+
rowsParser._packetEnd -= CHECKSUM_SIZE;
|
|
78
110
|
}
|
|
79
111
|
|
|
80
112
|
this.rows = [];
|
|
81
|
-
while (!
|
|
82
|
-
this.rows.push(this._fetchOneRow(
|
|
113
|
+
while (!rowsParser.reachedPacketEnd()) {
|
|
114
|
+
this.rows.push(this._fetchOneRow(rowsParser));
|
|
83
115
|
}
|
|
84
116
|
|
|
85
|
-
if (
|
|
117
|
+
if (trimChecksum) {
|
|
86
118
|
// Skip past the checksum at the end of the packet
|
|
87
|
-
|
|
88
|
-
|
|
119
|
+
rowsParser._packetEnd += CHECKSUM_SIZE;
|
|
120
|
+
rowsParser._offset += CHECKSUM_SIZE;
|
|
89
121
|
}
|
|
90
122
|
}
|
|
91
123
|
}
|