@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
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Maps MySQL collation ids (as they appear in TABLE_MAP_EVENT optional
|
|
2
|
+
// metadata) to their character set names, matching the
|
|
3
|
+
// INFORMATION_SCHEMA.COLUMNS CHARACTER_SET_NAME values that the
|
|
4
|
+
// metadata-fetch path produces, so both metadata sources look identical
|
|
5
|
+
// to consumers.
|
|
6
|
+
//
|
|
7
|
+
// Generated from INFORMATION_SCHEMA.COLLATIONS and CHARACTER_SETS on
|
|
8
|
+
// MySQL 8.0 and 8.4 (identical sets). Collation ids are append-only and
|
|
9
|
+
// stable across MySQL versions. Regenerate with:
|
|
10
|
+
// docker exec <mysql-container> mysql -u root -p<pw> -N -e \
|
|
11
|
+
// 'SELECT ID, CHARACTER_SET_NAME FROM information_schema.COLLATIONS ORDER BY ID'
|
|
12
|
+
// docker exec <mysql-container> mysql -u root -p<pw> -N -e \
|
|
13
|
+
// 'SELECT CHARACTER_SET_NAME, MAXLEN FROM information_schema.CHARACTER_SETS'
|
|
14
|
+
const collationCharsets = {
|
|
15
|
+
1: 'big5', 2: 'latin2', 3: 'dec8', 4: 'cp850', 5: 'latin1', 6: 'hp8',
|
|
16
|
+
7: 'koi8r', 8: 'latin1', 9: 'latin2', 10: 'swe7', 11: 'ascii', 12: 'ujis',
|
|
17
|
+
13: 'sjis', 14: 'cp1251', 15: 'latin1', 16: 'hebrew', 18: 'tis620', 19: 'euckr',
|
|
18
|
+
20: 'latin7', 21: 'latin2', 22: 'koi8u', 23: 'cp1251', 24: 'gb2312', 25: 'greek',
|
|
19
|
+
26: 'cp1250', 27: 'latin2', 28: 'gbk', 29: 'cp1257', 30: 'latin5', 31: 'latin1',
|
|
20
|
+
32: 'armscii8', 33: 'utf8mb3', 34: 'cp1250', 35: 'ucs2', 36: 'cp866', 37: 'keybcs2',
|
|
21
|
+
38: 'macce', 39: 'macroman', 40: 'cp852', 41: 'latin7', 42: 'latin7', 43: 'macce',
|
|
22
|
+
44: 'cp1250', 45: 'utf8mb4', 46: 'utf8mb4', 47: 'latin1', 48: 'latin1', 49: 'latin1',
|
|
23
|
+
50: 'cp1251', 51: 'cp1251', 52: 'cp1251', 53: 'macroman', 54: 'utf16', 55: 'utf16',
|
|
24
|
+
56: 'utf16le', 57: 'cp1256', 58: 'cp1257', 59: 'cp1257', 60: 'utf32', 61: 'utf32',
|
|
25
|
+
62: 'utf16le', 63: 'binary', 64: 'armscii8', 65: 'ascii', 66: 'cp1250', 67: 'cp1256',
|
|
26
|
+
68: 'cp866', 69: 'dec8', 70: 'greek', 71: 'hebrew', 72: 'hp8', 73: 'keybcs2',
|
|
27
|
+
74: 'koi8r', 75: 'koi8u', 76: 'utf8mb3', 77: 'latin2', 78: 'latin5', 79: 'latin7',
|
|
28
|
+
80: 'cp850', 81: 'cp852', 82: 'swe7', 83: 'utf8mb3', 84: 'big5', 85: 'euckr',
|
|
29
|
+
86: 'gb2312', 87: 'gbk', 88: 'sjis', 89: 'tis620', 90: 'ucs2', 91: 'ujis',
|
|
30
|
+
92: 'geostd8', 93: 'geostd8', 94: 'latin1', 95: 'cp932', 96: 'cp932', 97: 'eucjpms',
|
|
31
|
+
98: 'eucjpms', 99: 'cp1250', 101: 'utf16', 102: 'utf16', 103: 'utf16', 104: 'utf16',
|
|
32
|
+
105: 'utf16', 106: 'utf16', 107: 'utf16', 108: 'utf16', 109: 'utf16', 110: 'utf16',
|
|
33
|
+
111: 'utf16', 112: 'utf16', 113: 'utf16', 114: 'utf16', 115: 'utf16', 116: 'utf16',
|
|
34
|
+
117: 'utf16', 118: 'utf16', 119: 'utf16', 120: 'utf16', 121: 'utf16', 122: 'utf16',
|
|
35
|
+
123: 'utf16', 124: 'utf16', 128: 'ucs2', 129: 'ucs2', 130: 'ucs2', 131: 'ucs2',
|
|
36
|
+
132: 'ucs2', 133: 'ucs2', 134: 'ucs2', 135: 'ucs2', 136: 'ucs2', 137: 'ucs2',
|
|
37
|
+
138: 'ucs2', 139: 'ucs2', 140: 'ucs2', 141: 'ucs2', 142: 'ucs2', 143: 'ucs2',
|
|
38
|
+
144: 'ucs2', 145: 'ucs2', 146: 'ucs2', 147: 'ucs2', 148: 'ucs2', 149: 'ucs2',
|
|
39
|
+
150: 'ucs2', 151: 'ucs2', 159: 'ucs2', 160: 'utf32', 161: 'utf32', 162: 'utf32',
|
|
40
|
+
163: 'utf32', 164: 'utf32', 165: 'utf32', 166: 'utf32', 167: 'utf32', 168: 'utf32',
|
|
41
|
+
169: 'utf32', 170: 'utf32', 171: 'utf32', 172: 'utf32', 173: 'utf32', 174: 'utf32',
|
|
42
|
+
175: 'utf32', 176: 'utf32', 177: 'utf32', 178: 'utf32', 179: 'utf32', 180: 'utf32',
|
|
43
|
+
181: 'utf32', 182: 'utf32', 183: 'utf32', 192: 'utf8mb3', 193: 'utf8mb3', 194: 'utf8mb3',
|
|
44
|
+
195: 'utf8mb3', 196: 'utf8mb3', 197: 'utf8mb3', 198: 'utf8mb3', 199: 'utf8mb3', 200: 'utf8mb3',
|
|
45
|
+
201: 'utf8mb3', 202: 'utf8mb3', 203: 'utf8mb3', 204: 'utf8mb3', 205: 'utf8mb3', 206: 'utf8mb3',
|
|
46
|
+
207: 'utf8mb3', 208: 'utf8mb3', 209: 'utf8mb3', 210: 'utf8mb3', 211: 'utf8mb3', 212: 'utf8mb3',
|
|
47
|
+
213: 'utf8mb3', 214: 'utf8mb3', 215: 'utf8mb3', 223: 'utf8mb3', 224: 'utf8mb4', 225: 'utf8mb4',
|
|
48
|
+
226: 'utf8mb4', 227: 'utf8mb4', 228: 'utf8mb4', 229: 'utf8mb4', 230: 'utf8mb4', 231: 'utf8mb4',
|
|
49
|
+
232: 'utf8mb4', 233: 'utf8mb4', 234: 'utf8mb4', 235: 'utf8mb4', 236: 'utf8mb4', 237: 'utf8mb4',
|
|
50
|
+
238: 'utf8mb4', 239: 'utf8mb4', 240: 'utf8mb4', 241: 'utf8mb4', 242: 'utf8mb4', 243: 'utf8mb4',
|
|
51
|
+
244: 'utf8mb4', 245: 'utf8mb4', 246: 'utf8mb4', 247: 'utf8mb4', 248: 'gb18030', 249: 'gb18030',
|
|
52
|
+
250: 'gb18030', 255: 'utf8mb4', 256: 'utf8mb4', 257: 'utf8mb4', 258: 'utf8mb4', 259: 'utf8mb4',
|
|
53
|
+
260: 'utf8mb4', 261: 'utf8mb4', 262: 'utf8mb4', 263: 'utf8mb4', 264: 'utf8mb4', 265: 'utf8mb4',
|
|
54
|
+
266: 'utf8mb4', 267: 'utf8mb4', 268: 'utf8mb4', 269: 'utf8mb4', 270: 'utf8mb4', 271: 'utf8mb4',
|
|
55
|
+
273: 'utf8mb4', 274: 'utf8mb4', 275: 'utf8mb4', 277: 'utf8mb4', 278: 'utf8mb4', 279: 'utf8mb4',
|
|
56
|
+
280: 'utf8mb4', 281: 'utf8mb4', 282: 'utf8mb4', 283: 'utf8mb4', 284: 'utf8mb4', 285: 'utf8mb4',
|
|
57
|
+
286: 'utf8mb4', 287: 'utf8mb4', 288: 'utf8mb4', 289: 'utf8mb4', 290: 'utf8mb4', 291: 'utf8mb4',
|
|
58
|
+
292: 'utf8mb4', 293: 'utf8mb4', 294: 'utf8mb4', 296: 'utf8mb4', 297: 'utf8mb4', 298: 'utf8mb4',
|
|
59
|
+
300: 'utf8mb4', 303: 'utf8mb4', 304: 'utf8mb4', 305: 'utf8mb4', 306: 'utf8mb4', 307: 'utf8mb4',
|
|
60
|
+
308: 'utf8mb4', 309: 'utf8mb4', 310: 'utf8mb4', 311: 'utf8mb4', 312: 'utf8mb4', 313: 'utf8mb4',
|
|
61
|
+
314: 'utf8mb4', 315: 'utf8mb4', 316: 'utf8mb4', 317: 'utf8mb4', 318: 'utf8mb4', 319: 'utf8mb4',
|
|
62
|
+
320: 'utf8mb4', 321: 'utf8mb4', 322: 'utf8mb4', 323: 'utf8mb4',
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Maximum bytes per character, used to recover character display widths
|
|
66
|
+
// from the byte widths stored in binlog column metadata
|
|
67
|
+
const charsetMaxBytesPerChar = {
|
|
68
|
+
armscii8: 1, ascii: 1, big5: 2, binary: 1, cp1250: 1, cp1251: 1,
|
|
69
|
+
cp1256: 1, cp1257: 1, cp850: 1, cp852: 1, cp866: 1, cp932: 2,
|
|
70
|
+
dec8: 1, eucjpms: 3, euckr: 2, gb18030: 4, gb2312: 2, gbk: 2,
|
|
71
|
+
geostd8: 1, greek: 1, hebrew: 1, hp8: 1, keybcs2: 1, koi8r: 1,
|
|
72
|
+
koi8u: 1, latin1: 1, latin2: 1, latin5: 1, latin7: 1, macce: 1,
|
|
73
|
+
macroman: 1, sjis: 2, swe7: 1, tis620: 1, ucs2: 2, ujis: 3,
|
|
74
|
+
utf16: 4, utf16le: 4, utf32: 4, utf8mb3: 3, utf8mb4: 4,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// All collations added since MySQL 8.4 are utf8mb4 (the 0900 and uca1400
|
|
78
|
+
// series), so that is the safest guess for ids this table has not caught
|
|
79
|
+
// up with yet.
|
|
80
|
+
export function collationToCharset(id) {
|
|
81
|
+
return collationCharsets[id] || 'utf8mb4';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function charsetMaxLength(charsetName) {
|
|
85
|
+
return charsetMaxBytesPerChar[charsetName] || 4;
|
|
86
|
+
}
|
package/lib/code_map.js
CHANGED
|
@@ -43,9 +43,27 @@ const CodeEvent = [
|
|
|
43
43
|
'XA_PREPARE_LOG_EVENT',
|
|
44
44
|
'PARTIAL_UPDATE_ROWS_EVENT',
|
|
45
45
|
'TRANSACTION_PAYLOAD_EVENT',
|
|
46
|
-
'HEARTBEAT_LOG_EVENT_V2'
|
|
46
|
+
'HEARTBEAT_LOG_EVENT_V2',
|
|
47
|
+
'GTID_TAGGED_LOG_EVENT'
|
|
47
48
|
];
|
|
48
49
|
|
|
50
|
+
// MariaDB-specific event codes (sql/log_event.h enum Log_event_type).
|
|
51
|
+
// The GTID names are prefixed to avoid clashing with MySQL's
|
|
52
|
+
// GTID_LOG_EVENT (33): MariaDB's own enum calls 162/163 GTID_EVENT and
|
|
53
|
+
// GTID_LIST_EVENT.
|
|
54
|
+
CodeEvent[160] = 'ANNOTATE_ROWS_EVENT';
|
|
55
|
+
CodeEvent[161] = 'BINLOG_CHECKPOINT_EVENT';
|
|
56
|
+
CodeEvent[162] = 'MARIADB_GTID_EVENT';
|
|
57
|
+
CodeEvent[163] = 'MARIADB_GTID_LIST_EVENT';
|
|
58
|
+
CodeEvent[164] = 'START_ENCRYPTION_EVENT';
|
|
59
|
+
CodeEvent[165] = 'QUERY_COMPRESSED_EVENT';
|
|
60
|
+
CodeEvent[166] = 'WRITE_ROWS_COMPRESSED_EVENT_V1';
|
|
61
|
+
CodeEvent[167] = 'UPDATE_ROWS_COMPRESSED_EVENT_V1';
|
|
62
|
+
CodeEvent[168] = 'DELETE_ROWS_COMPRESSED_EVENT_V1';
|
|
63
|
+
CodeEvent[169] = 'WRITE_ROWS_COMPRESSED_EVENT';
|
|
64
|
+
CodeEvent[170] = 'UPDATE_ROWS_COMPRESSED_EVENT';
|
|
65
|
+
CodeEvent[171] = 'DELETE_ROWS_COMPRESSED_EVENT';
|
|
66
|
+
|
|
49
67
|
const EventClass = {
|
|
50
68
|
UNKNOWN_EVENT: events.Unknown,
|
|
51
69
|
QUERY_EVENT: events.Query,
|
|
@@ -53,7 +71,12 @@ const EventClass = {
|
|
|
53
71
|
ROTATE_EVENT: events.Rotate,
|
|
54
72
|
FORMAT_DESCRIPTION_EVENT: events.Format,
|
|
55
73
|
XID_EVENT: events.Xid,
|
|
74
|
+
HEARTBEAT_LOG_EVENT: events.Heartbeat,
|
|
56
75
|
GTID_LOG_EVENT: events.Gtid,
|
|
76
|
+
// Tagged GTIDs (MySQL 8.3+) decode inside the plain Gtid class (keyed
|
|
77
|
+
// on options.eventType), so consumers see and filter ordinary 'gtid'
|
|
78
|
+
// events; event.gtid is 'uuid:tag:gno' and event.tag is set
|
|
79
|
+
GTID_TAGGED_LOG_EVENT: events.Gtid,
|
|
57
80
|
ANONYMOUS_GTID_LOG_EVENT: events.AnonymousGtid,
|
|
58
81
|
PREVIOUS_GTIDS_LOG_EVENT: events.PreviousGtids,
|
|
59
82
|
TRANSACTION_PAYLOAD_EVENT: events.TransactionPayload,
|
|
@@ -66,6 +89,26 @@ const EventClass = {
|
|
|
66
89
|
WRITE_ROWS_EVENT_V2: rowsEvents.WriteRows,
|
|
67
90
|
UPDATE_ROWS_EVENT_V2: rowsEvents.UpdateRows,
|
|
68
91
|
DELETE_ROWS_EVENT_V2: rowsEvents.DeleteRows,
|
|
92
|
+
|
|
93
|
+
XA_PREPARE_LOG_EVENT: events.XaPrepare,
|
|
94
|
+
ROWS_QUERY_LOG_EVENT: events.RowsQuery,
|
|
95
|
+
|
|
96
|
+
// MariaDB
|
|
97
|
+
ANNOTATE_ROWS_EVENT: events.AnnotateRows,
|
|
98
|
+
BINLOG_CHECKPOINT_EVENT: events.BinlogCheckpoint,
|
|
99
|
+
MARIADB_GTID_EVENT: events.MariadbGtid,
|
|
100
|
+
MARIADB_GTID_LIST_EVENT: events.MariadbGtidList,
|
|
101
|
+
START_ENCRYPTION_EVENT: events.StartEncryption,
|
|
102
|
+
// log_bin_compress=ON variants decompress transparently inside the
|
|
103
|
+
// plain event classes (keyed on options.eventType), so consumers see
|
|
104
|
+
// and filter ordinary query/writerows/updaterows/deleterows events
|
|
105
|
+
QUERY_COMPRESSED_EVENT: events.Query,
|
|
106
|
+
WRITE_ROWS_COMPRESSED_EVENT_V1: rowsEvents.WriteRows,
|
|
107
|
+
UPDATE_ROWS_COMPRESSED_EVENT_V1: rowsEvents.UpdateRows,
|
|
108
|
+
DELETE_ROWS_COMPRESSED_EVENT_V1: rowsEvents.DeleteRows,
|
|
109
|
+
WRITE_ROWS_COMPRESSED_EVENT: rowsEvents.WriteRows,
|
|
110
|
+
UPDATE_ROWS_COMPRESSED_EVENT: rowsEvents.UpdateRows,
|
|
111
|
+
DELETE_ROWS_COMPRESSED_EVENT: rowsEvents.DeleteRows,
|
|
69
112
|
};
|
|
70
113
|
|
|
71
114
|
export function getEventClass(code) {
|
package/lib/common.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import zlib from 'zlib';
|
|
2
|
+
|
|
1
3
|
import iconv from 'iconv-lite';
|
|
2
4
|
import decodeJson, { convertBigInts, jsonToText } from './json_decode.js';
|
|
3
5
|
import * as dtDecode from './datetime_decode.js';
|
|
@@ -24,6 +26,11 @@ export const MysqlTypes = {
|
|
|
24
26
|
TIMESTAMP2: 17,
|
|
25
27
|
DATETIME2: 18,
|
|
26
28
|
TIME2: 19,
|
|
29
|
+
// MariaDB-only wire types for VARCHAR/VARBINARY/TEXT/BLOB columns
|
|
30
|
+
// declared with the COMPRESSED attribute ("only used internally for
|
|
31
|
+
// RBR", i.e. they appear in TABLE_MAP column-type arrays)
|
|
32
|
+
BLOB_COMPRESSED: 140,
|
|
33
|
+
VARCHAR_COMPRESSED: 141,
|
|
27
34
|
// JSON data type added in MySQL 5.7.7
|
|
28
35
|
JSON: 245,
|
|
29
36
|
NEWDECIMAL: 246,
|
|
@@ -158,7 +165,12 @@ const parseAnyInt = function(parser, column, columnSchema) {
|
|
|
158
165
|
result = parseUInt64(parser);
|
|
159
166
|
break;
|
|
160
167
|
}
|
|
161
|
-
|
|
168
|
+
// Binlog optional metadata provides signedness directly (UNSIGNED);
|
|
169
|
+
// otherwise infer it from the INFORMATION_SCHEMA column definition
|
|
170
|
+
const unsigned = columnSchema.UNSIGNED !== undefined ?
|
|
171
|
+
columnSchema.UNSIGNED :
|
|
172
|
+
columnSchema.COLUMN_TYPE.indexOf('unsigned') !== -1;
|
|
173
|
+
if (!unsigned) {
|
|
162
174
|
const length = size * 8;
|
|
163
175
|
const int64 = (length == 64);
|
|
164
176
|
// Flip bits on negative signed integer
|
|
@@ -353,6 +365,238 @@ const readTemporalFraction = function(parser, fractionPrecision) {
|
|
|
353
365
|
};
|
|
354
366
|
};
|
|
355
367
|
|
|
368
|
+
export function formatUuid(buffer) {
|
|
369
|
+
const hex = buffer.toString('hex');
|
|
370
|
+
return [
|
|
371
|
+
hex.slice(0, 8),
|
|
372
|
+
hex.slice(8, 12),
|
|
373
|
+
hex.slice(12, 16),
|
|
374
|
+
hex.slice(16, 20),
|
|
375
|
+
hex.slice(20)
|
|
376
|
+
].join('-');
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Undoes MariaDB's per-value column compression (VARCHAR/BLOB COMPRESSED,
|
|
380
|
+
// sql/field_comp.cc). Header byte: bits 4-7 = method (0 = stored
|
|
381
|
+
// uncompressed, 8 = zlib), and for zlib bits 0-2 = size of the big-endian
|
|
382
|
+
// original-length field that follows, bit 3 = deflate stream has no zlib
|
|
383
|
+
// wrapper (raw deflate). An empty value is stored with no header at all.
|
|
384
|
+
// Variable-length integer of MySQL's mysql::serialization framework
|
|
385
|
+
// (libs/mysql/serialization/variable_length_integers.h, used by the
|
|
386
|
+
// tagged GTID event of MySQL 8.3+). NOT protobuf varint and NOT the
|
|
387
|
+
// classic length-encoded integer: the total byte count is the number of
|
|
388
|
+
// trailing one-bits of the first byte plus one (1-9); the value is the
|
|
389
|
+
// first byte shifted right by that count, joined with the remaining
|
|
390
|
+
// bytes little-endian shifted left by (8 - count). A 9-byte encoding
|
|
391
|
+
// (first byte 0xFF) carries a full little-endian u64 unshifted.
|
|
392
|
+
// Returns a BigInt; `end` bounds the read (event end minus checksum).
|
|
393
|
+
export function parseSerializationVarint(parser, end, what) {
|
|
394
|
+
if (parser._offset + 1 > end) {
|
|
395
|
+
throw new Error(`Truncated ${what}: varint head past event end`);
|
|
396
|
+
}
|
|
397
|
+
const first = parser.parseUnsignedNumber(1);
|
|
398
|
+
let extra = 0;
|
|
399
|
+
while (extra < 8 && (first >> extra) & 1) {
|
|
400
|
+
extra++;
|
|
401
|
+
}
|
|
402
|
+
if (parser._offset + extra > end) {
|
|
403
|
+
throw new Error(`Truncated ${what}: ${extra + 1}-byte varint ` +
|
|
404
|
+
'past event end');
|
|
405
|
+
}
|
|
406
|
+
if (extra === 8) {
|
|
407
|
+
let value = 0n;
|
|
408
|
+
for (let i = 0; i < 8; i++) {
|
|
409
|
+
value |= BigInt(parser.parseUnsignedNumber(1)) << BigInt(8 * i);
|
|
410
|
+
}
|
|
411
|
+
return value;
|
|
412
|
+
}
|
|
413
|
+
let rest = 0n;
|
|
414
|
+
for (let i = 0; i < extra; i++) {
|
|
415
|
+
rest |= BigInt(parser.parseUnsignedNumber(1)) << BigInt(8 * i);
|
|
416
|
+
}
|
|
417
|
+
return (BigInt(first) >> BigInt(extra + 1)) |
|
|
418
|
+
(rest << BigInt(8 - (extra + 1)));
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Signed variant: value x >= 0 encodes as x << 1, x < 0 as
|
|
422
|
+
// ((-(x + 1)) << 1) | 1 (a zigzag-like mapping)
|
|
423
|
+
export function parseSerializationVarintSigned(parser, end, what) {
|
|
424
|
+
const u = parseSerializationVarint(parser, end, what);
|
|
425
|
+
return (u & 1n) ? -(u >> 1n) - 1n : u >> 1n;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// MySQL charset names mostly match iconv-lite encoding names, with four
|
|
429
|
+
// classes of exception: the utf8 aliases; MySQL's "latin1", which is
|
|
430
|
+
// really cp1252 (its 0x80-0x9F range holds printable characters that
|
|
431
|
+
// ISO-8859-1 reserves for controls, and the server converts them as such
|
|
432
|
+
// for query results); the Unicode charsets whose stored byte order is
|
|
433
|
+
// big-endian while the iconv-lite encodings of the same spelling default
|
|
434
|
+
// to little-endian; and names iconv-lite spells differently or lacks
|
|
435
|
+
// (ujis is EUC-JP, eucjpms is its MS-extended variant for which plain
|
|
436
|
+
// EUC-JP is the closest decoder available, macce is Mac Central
|
|
437
|
+
// European). dec8, geostd8, hp8, keybcs2 and swe7 have no iconv-lite
|
|
438
|
+
// decoder at all; those columns come through as raw Buffers.
|
|
439
|
+
const ICONV_CHARSET_OVERRIDES = {
|
|
440
|
+
utf8mb3: 'utf8',
|
|
441
|
+
utf8mb4: 'utf8',
|
|
442
|
+
latin1: 'cp1252',
|
|
443
|
+
ucs2: 'utf16-be',
|
|
444
|
+
utf16: 'utf16-be',
|
|
445
|
+
utf16le: 'utf16-le',
|
|
446
|
+
utf32: 'utf32-be',
|
|
447
|
+
ujis: 'eucjp',
|
|
448
|
+
eucjpms: 'eucjp',
|
|
449
|
+
macce: 'maccenteuro',
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
// Decodes a text column's raw bytes using the column's own character set
|
|
453
|
+
// (INFORMATION_SCHEMA CHARACTER_SET_NAME, or the charset carried by FULL
|
|
454
|
+
// binlog metadata). Returns the buffer unchanged when the charset is
|
|
455
|
+
// unknown to iconv.
|
|
456
|
+
export function decodeTextColumn(buffer, charset) {
|
|
457
|
+
const charsetName = ICONV_CHARSET_OVERRIDES[charset] || charset || 'utf8';
|
|
458
|
+
if (charsetName === 'utf8') {
|
|
459
|
+
return buffer.toString('utf8');
|
|
460
|
+
}
|
|
461
|
+
try {
|
|
462
|
+
// A leading U+FEFF is column data, not a byte-order mark: the
|
|
463
|
+
// charset's byte order is fixed, and the server preserves the
|
|
464
|
+
// character when converting for query results
|
|
465
|
+
return iconv.decode(buffer, charsetName, { stripBOM: false });
|
|
466
|
+
} catch (err) {
|
|
467
|
+
console.warn(err);
|
|
468
|
+
return buffer;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const uncompressColumnValue = function(buffer) {
|
|
473
|
+
if (buffer.length === 0) {
|
|
474
|
+
return buffer;
|
|
475
|
+
}
|
|
476
|
+
const header = buffer[0];
|
|
477
|
+
const method = header >> 4;
|
|
478
|
+
if (method === 0) {
|
|
479
|
+
return buffer.subarray(1);
|
|
480
|
+
}
|
|
481
|
+
if (method === 8) {
|
|
482
|
+
const lengthBytes = header & 0x07;
|
|
483
|
+
const compressed = buffer.subarray(1 + lengthBytes);
|
|
484
|
+
return (header & 0x08)
|
|
485
|
+
? zlib.inflateRawSync(compressed)
|
|
486
|
+
: zlib.inflateSync(compressed);
|
|
487
|
+
}
|
|
488
|
+
throw new Error(
|
|
489
|
+
`Unknown MariaDB column compression method ${method}`);
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
// Undoes the payload envelope of MariaDB's compressed binlog EVENTS
|
|
493
|
+
// (log_bin_compress=ON, event codes 165-171; sql/log_event.cc
|
|
494
|
+
// binlog_get_uncompress_len / binlog_buf_uncompress). Distinct from the
|
|
495
|
+
// per-column COMPRESSED envelope above: header byte has bit 7 always set,
|
|
496
|
+
// bits 4-6 = algorithm (only 0 = zlib is defined), bits 0-2 = size of the
|
|
497
|
+
// big-endian original-length field that follows; then a zlib-wrapped
|
|
498
|
+
// deflate stream.
|
|
499
|
+
export function uncompressBinlogEventPayload(buffer) {
|
|
500
|
+
if (buffer.length < 1) {
|
|
501
|
+
throw new Error('Empty compressed binlog event payload');
|
|
502
|
+
}
|
|
503
|
+
const header = buffer[0];
|
|
504
|
+
const algorithm = (header & 0x70) >> 4;
|
|
505
|
+
if ((header & 0x80) === 0 || algorithm !== 0) {
|
|
506
|
+
throw new Error(
|
|
507
|
+
`Unknown binlog event compression header 0x${header.toString(16)}`);
|
|
508
|
+
}
|
|
509
|
+
const lengthBytes = header & 0x07;
|
|
510
|
+
if (lengthBytes < 1 || lengthBytes > 4 ||
|
|
511
|
+
buffer.length < 1 + lengthBytes) {
|
|
512
|
+
throw new Error(
|
|
513
|
+
`Corrupt binlog event compression header 0x${header.toString(16)}`);
|
|
514
|
+
}
|
|
515
|
+
const originalLength = buffer.readUIntBE(1, lengthBytes);
|
|
516
|
+
const inflated = zlib.inflateSync(buffer.subarray(1 + lengthBytes));
|
|
517
|
+
if (inflated.length !== originalLength) {
|
|
518
|
+
throw new Error(
|
|
519
|
+
`Compressed binlog event inflated to ${inflated.length} bytes, ` +
|
|
520
|
+
`expected ${originalLength}`);
|
|
521
|
+
}
|
|
522
|
+
return inflated;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Dotted-quad text form of a MariaDB INET4 value (4 bytes, network order)
|
|
526
|
+
export function formatInet4(buffer) {
|
|
527
|
+
return `${buffer[0]}.${buffer[1]}.${buffer[2]}.${buffer[3]}`;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Canonical text form of a MariaDB INET6 value (16 bytes, network order),
|
|
531
|
+
// matching the server's own formatting: RFC 5952 (lowercase hex groups,
|
|
532
|
+
// the longest run of two or more zero groups compressed to '::', the
|
|
533
|
+
// leftmost on a tie) with IPv4-mapped (::ffff:a.b.c.d) and IPv4-compatible
|
|
534
|
+
// (::a.b.c.d, except :: and ::1) addresses in dotted form
|
|
535
|
+
export function formatInet6(buffer) {
|
|
536
|
+
const leadingZero = buffer.subarray(0, 10).every(byte => byte === 0);
|
|
537
|
+
if (leadingZero) {
|
|
538
|
+
const v4 = formatInet4(buffer.subarray(12));
|
|
539
|
+
if (buffer[10] === 0xff && buffer[11] === 0xff) {
|
|
540
|
+
return `::ffff:${v4}`;
|
|
541
|
+
}
|
|
542
|
+
if (buffer[10] === 0 && buffer[11] === 0 &&
|
|
543
|
+
buffer.readUInt32BE(12) > 1) {
|
|
544
|
+
return `::${v4}`;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const groups = [];
|
|
549
|
+
for (let i = 0; i < 16; i += 2) {
|
|
550
|
+
groups.push(buffer.readUInt16BE(i));
|
|
551
|
+
}
|
|
552
|
+
// Longest run of >=2 zero groups; leftmost wins a tie
|
|
553
|
+
let bestStart = -1, bestLength = 1;
|
|
554
|
+
for (let i = 0; i < 8;) {
|
|
555
|
+
if (groups[i] !== 0) { i++; continue; }
|
|
556
|
+
let j = i;
|
|
557
|
+
while (j < 8 && groups[j] === 0) j++;
|
|
558
|
+
if (j - i > bestLength) {
|
|
559
|
+
bestStart = i;
|
|
560
|
+
bestLength = j - i;
|
|
561
|
+
}
|
|
562
|
+
i = j;
|
|
563
|
+
}
|
|
564
|
+
const hex = (value) => value.toString(16);
|
|
565
|
+
if (bestStart === -1) {
|
|
566
|
+
return groups.map(hex).join(':');
|
|
567
|
+
}
|
|
568
|
+
const head = groups.slice(0, bestStart).map(hex).join(':');
|
|
569
|
+
const tail = groups.slice(bestStart + bestLength).map(hex).join(':');
|
|
570
|
+
return `${head}::${tail}`;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// MariaDB 5.3-era "hires" temporals (written under mysql56_temporal_format
|
|
574
|
+
// =OFF, or by tables never rebuilt since 10.0): fractional TIME/DATETIME/
|
|
575
|
+
// TIMESTAMP binlogged under the classic type codes 11/12/7 with no
|
|
576
|
+
// metadata, so the precision is only knowable from the column definition
|
|
577
|
+
// (COLUMN_TYPE reads e.g. 'datetime(3) /* mariadb-5.3 */'). MySQL never
|
|
578
|
+
// produces these codes with a precision: fractional temporals always use
|
|
579
|
+
// TIME2/DATETIME2/TIMESTAMP2.
|
|
580
|
+
const hiresPrecision = function(columnSchema) {
|
|
581
|
+
const match = /^(?:time|datetime|timestamp)\((\d)\)/
|
|
582
|
+
.exec(columnSchema.COLUMN_TYPE);
|
|
583
|
+
return match ? parseInt(match[1], 10) : 0;
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
// Same shape as readTemporalFraction, from a fraction that was stored
|
|
587
|
+
// with exactly `precision` decimal digits
|
|
588
|
+
const hiresFractionObject = function(value, precision) {
|
|
589
|
+
let milliseconds;
|
|
590
|
+
if (precision > 3) {
|
|
591
|
+
milliseconds = Math.floor(value / Math.pow(10, precision - 3));
|
|
592
|
+
} else if (precision < 3) {
|
|
593
|
+
milliseconds = value * Math.pow(10, 3 - precision);
|
|
594
|
+
} else {
|
|
595
|
+
milliseconds = value;
|
|
596
|
+
}
|
|
597
|
+
return { value, precision, milliseconds };
|
|
598
|
+
};
|
|
599
|
+
|
|
356
600
|
// This function is used to read and interpret non-null values from parser.
|
|
357
601
|
// The parser object contains the raw value, and functions useful for value
|
|
358
602
|
// interpretation, but not the underlying column type or how the value ought to
|
|
@@ -406,8 +650,11 @@ export function readMysqlValue(
|
|
|
406
650
|
low = parser.parseUnsignedNumber(column.metadata.size);
|
|
407
651
|
}
|
|
408
652
|
|
|
653
|
+
// Binlog optional metadata provides the value list directly;
|
|
654
|
+
// otherwise parse it out of the column definition.
|
|
409
655
|
// Second argument: prefixLen = 4 'set('
|
|
410
|
-
choices =
|
|
656
|
+
choices = columnSchema.SET_VALUES ||
|
|
657
|
+
parseSetEnumTypeDef(columnSchema.COLUMN_TYPE, 4);
|
|
411
658
|
result = '';
|
|
412
659
|
for (let i = 0; low >= Math.pow(2, i); i++) {
|
|
413
660
|
if (low & Math.pow(2, i)) result += choices[i] + ',';
|
|
@@ -422,27 +669,59 @@ export function readMysqlValue(
|
|
|
422
669
|
case MysqlTypes.ENUM:
|
|
423
670
|
raw = parser.parseUnsignedNumber(column.metadata.size);
|
|
424
671
|
// Second argument: prefixLen = 5 'enum('
|
|
425
|
-
choices =
|
|
672
|
+
choices = columnSchema.ENUM_VALUES ||
|
|
673
|
+
parseSetEnumTypeDef(columnSchema.COLUMN_TYPE, 5);
|
|
426
674
|
result = choices[raw - 1];
|
|
427
675
|
break;
|
|
428
676
|
case MysqlTypes.VAR_STRING:
|
|
429
677
|
// Never used?
|
|
430
678
|
result = parser.parseLengthCodedString();
|
|
431
679
|
break;
|
|
680
|
+
case MysqlTypes.VARCHAR_COMPRESSED: {
|
|
681
|
+
// MariaDB VARCHAR/VARBINARY ... COMPRESSED: length-prefixed like
|
|
682
|
+
// VARCHAR, with a per-value compression envelope around the payload
|
|
683
|
+
const prefixSize = column.metadata['max_length'] > 255 ? 2 : 1;
|
|
684
|
+
size = parser.parseUnsignedNumber(prefixSize);
|
|
685
|
+
buffer = uncompressColumnValue(parser.parseBuffer(size));
|
|
686
|
+
result = columnSchema.COLUMN_TYPE.substr(0, 6) === 'varbin' ?
|
|
687
|
+
buffer : decodeTextColumn(buffer, column.charset);
|
|
688
|
+
break;
|
|
689
|
+
}
|
|
432
690
|
case MysqlTypes.VARCHAR:
|
|
433
691
|
case MysqlTypes.STRING: {
|
|
434
692
|
const prefixSize = column.metadata['max_length'] > 255 ? 2 : 1;
|
|
435
693
|
size = parser.parseUnsignedNumber(prefixSize);
|
|
436
694
|
const def = columnSchema.COLUMN_TYPE;
|
|
437
695
|
const defPrefix = def.substr(0, 6);
|
|
438
|
-
|
|
696
|
+
// MariaDB logical types ride on STRING/VARCHAR with a binary
|
|
697
|
+
// charset; only COLUMN_TYPE distinguishes them from BINARY(n) or
|
|
698
|
+
// VARBINARY(n). Text forms match the server's own (and therefore
|
|
699
|
+
// what a mysql2 query returns); VECTOR stays a raw Buffer of
|
|
700
|
+
// little-endian float32s, as in query results. As with BINARY(n),
|
|
701
|
+
// the row image strips trailing zero bytes, so the value is
|
|
702
|
+
// re-padded to the type's fixed width before formatting.
|
|
703
|
+
const readFixedWidth = (width) => {
|
|
704
|
+
const padded = Buffer.alloc(width, 0);
|
|
705
|
+
parser.parseBuffer(size).copy(padded);
|
|
706
|
+
return padded;
|
|
707
|
+
};
|
|
708
|
+
if (def === 'uuid') {
|
|
709
|
+
result = formatUuid(readFixedWidth(16));
|
|
710
|
+
} else if (def === 'inet4') {
|
|
711
|
+
result = formatInet4(readFixedWidth(4));
|
|
712
|
+
} else if (def === 'inet6') {
|
|
713
|
+
result = formatInet6(readFixedWidth(16));
|
|
714
|
+
} else if (defPrefix === 'vector') {
|
|
715
|
+
result = parser.parseBuffer(size);
|
|
716
|
+
} else if (defPrefix === 'binary') {
|
|
439
717
|
const bufsize = parseInt(def.substr(7, def.length - 2), 10);
|
|
440
718
|
result = Buffer.alloc(bufsize, 0);
|
|
441
719
|
parser.parseBuffer(size).copy(result);
|
|
442
720
|
} else if (defPrefix === 'varbin') {
|
|
443
721
|
result = parser.parseBuffer(size);
|
|
444
722
|
} else {
|
|
445
|
-
|
|
723
|
+
// Text CHAR/VARCHAR: decode in the column's own charset
|
|
724
|
+
result = decodeTextColumn(parser.parseBuffer(size), column.charset);
|
|
446
725
|
}
|
|
447
726
|
break;
|
|
448
727
|
}
|
|
@@ -450,24 +729,20 @@ export function readMysqlValue(
|
|
|
450
729
|
case MysqlTypes.MEDIUM_BLOB:
|
|
451
730
|
case MysqlTypes.LONG_BLOB:
|
|
452
731
|
case MysqlTypes.BLOB:
|
|
732
|
+
case MysqlTypes.BLOB_COMPRESSED:
|
|
453
733
|
lengthSize = column.metadata['length_size'];
|
|
454
734
|
result = parser.parseBuffer(parser.parseUnsignedNumber(lengthSize));
|
|
735
|
+
if (column.type === MysqlTypes.BLOB_COMPRESSED) {
|
|
736
|
+
// MariaDB TEXT/BLOB ... COMPRESSED: the payload wears a per-value
|
|
737
|
+
// compression envelope; the charset handling below then applies
|
|
738
|
+
// to the inflated bytes as for a plain TEXT/BLOB
|
|
739
|
+
result = uncompressColumnValue(result);
|
|
740
|
+
}
|
|
455
741
|
|
|
456
742
|
// Blobs can be sometimes return as String instead of Buffer
|
|
457
743
|
// e.g. TINYTEXT, MEDIUMTEXT, LONGTEXT, TEXT data types
|
|
458
744
|
if (column.charset !== null) {
|
|
459
|
-
|
|
460
|
-
switch (column.charset) {
|
|
461
|
-
case 'utf8mb3':
|
|
462
|
-
case 'utf8mb4':
|
|
463
|
-
column.charset = 'utf8';
|
|
464
|
-
break;
|
|
465
|
-
}
|
|
466
|
-
try {
|
|
467
|
-
result = iconv.decode(result, column.charset);
|
|
468
|
-
} catch (err) {
|
|
469
|
-
console.warn(err);
|
|
470
|
-
}
|
|
745
|
+
result = decodeTextColumn(result, column.charset);
|
|
471
746
|
}
|
|
472
747
|
break;
|
|
473
748
|
case MysqlTypes.JSON: {
|
|
@@ -504,7 +779,32 @@ export function readMysqlValue(
|
|
|
504
779
|
zongji.connection.config.timezone
|
|
505
780
|
);
|
|
506
781
|
break;
|
|
507
|
-
case MysqlTypes.TIME:
|
|
782
|
+
case MysqlTypes.TIME: {
|
|
783
|
+
const hiresDec = hiresPrecision(columnSchema);
|
|
784
|
+
if (hiresDec > 0) {
|
|
785
|
+
// MariaDB hires TIME: one big-endian unsigned integer of signed
|
|
786
|
+
// total time units offset by zero_point = 3020400 * 10^dec
|
|
787
|
+
// (TIME_MAX_VALUE_SECONDS + 1, scaled), so '00:00:00' stores
|
|
788
|
+
// exactly zero_point and the encoding sorts as unsigned
|
|
789
|
+
const bytes = [3, 4, 4, 5, 5, 5, 6][hiresDec];
|
|
790
|
+
const scale = Math.pow(10, hiresDec);
|
|
791
|
+
let stored = parser._buffer.readUIntBE(parser._offset, bytes);
|
|
792
|
+
parser._offset += bytes;
|
|
793
|
+
stored -= 3020400 * scale;
|
|
794
|
+
isNegative = stored < 0;
|
|
795
|
+
if (isNegative) stored = -stored;
|
|
796
|
+
fraction = hiresFractionObject(stored % scale, hiresDec);
|
|
797
|
+
const totalSeconds = Math.floor(stored / scale);
|
|
798
|
+
hour = Math.floor(totalSeconds / 3600);
|
|
799
|
+
minute = Math.floor((totalSeconds % 3600) / 60);
|
|
800
|
+
second = totalSeconds % 60;
|
|
801
|
+
result = (isNegative ? '-' : '') +
|
|
802
|
+
zeroPad(hour, hour > 99 ? 3 : 2) + ':' +
|
|
803
|
+
zeroPad(minute, 2) + ':' +
|
|
804
|
+
zeroPad(second, 2) +
|
|
805
|
+
dtDecode.getFractionString(fraction);
|
|
806
|
+
break;
|
|
807
|
+
}
|
|
508
808
|
raw = parseUInt24(parser);
|
|
509
809
|
|
|
510
810
|
isNegative = (raw & (1 << 23)) !== 0;
|
|
@@ -520,6 +820,7 @@ export function readMysqlValue(
|
|
|
520
820
|
zeroPad(minute, 2) + ':' +
|
|
521
821
|
zeroPad(second, 2);
|
|
522
822
|
break;
|
|
823
|
+
}
|
|
523
824
|
case MysqlTypes.TIME2:
|
|
524
825
|
raw = readIntBE(parser._buffer, parser._offset, 3);
|
|
525
826
|
parser._offset += 3;
|
|
@@ -545,7 +846,41 @@ export function readMysqlValue(
|
|
|
545
846
|
result += dtDecode.getFractionString(fraction);
|
|
546
847
|
}
|
|
547
848
|
break;
|
|
548
|
-
case MysqlTypes.DATETIME:
|
|
849
|
+
case MysqlTypes.DATETIME: {
|
|
850
|
+
const hiresDec = hiresPrecision(columnSchema);
|
|
851
|
+
if (hiresDec > 0) {
|
|
852
|
+
// MariaDB hires DATETIME: one big-endian unsigned integer of
|
|
853
|
+
// pack_time() scaled to dec fractional digits; mixed radix, not
|
|
854
|
+
// the YYYYMMDDHHMMSS decimal of the classic format and not the
|
|
855
|
+
// DATETIME2 bit layout. Up to 8 bytes (beyond 2^53), so split in
|
|
856
|
+
// BigInt.
|
|
857
|
+
const bytes = [5, 6, 6, 7, 7, 7, 8][hiresDec];
|
|
858
|
+
let packed = 0n;
|
|
859
|
+
for (let i = 0; i < bytes; i++) {
|
|
860
|
+
packed = (packed << 8n) | BigInt(parser._buffer[parser._offset + i]);
|
|
861
|
+
}
|
|
862
|
+
parser._offset += bytes;
|
|
863
|
+
const scale = 10n ** BigInt(hiresDec);
|
|
864
|
+
fraction = hiresFractionObject(Number(packed % scale), hiresDec);
|
|
865
|
+
let units = packed / scale;
|
|
866
|
+
const secondPart = Number(units % 60n); units /= 60n;
|
|
867
|
+
const minutePart = Number(units % 60n); units /= 60n;
|
|
868
|
+
const hourPart = Number(units % 24n); units /= 24n;
|
|
869
|
+
const dayPart = Number(units % 32n); units /= 32n;
|
|
870
|
+
const monthPart = Number(units % 13n); units /= 13n;
|
|
871
|
+
result = dtDecode.getDateTime(
|
|
872
|
+
zongji.connection.config.dateStrings,
|
|
873
|
+
Number(units), // year
|
|
874
|
+
monthPart,
|
|
875
|
+
dayPart,
|
|
876
|
+
hourPart,
|
|
877
|
+
minutePart,
|
|
878
|
+
secondPart,
|
|
879
|
+
fraction,
|
|
880
|
+
zongji.connection.config.timezone
|
|
881
|
+
);
|
|
882
|
+
break;
|
|
883
|
+
}
|
|
549
884
|
raw = parseUInt64(parser);
|
|
550
885
|
date = Math.floor(raw / 1000000);
|
|
551
886
|
time = raw % 1000000;
|
|
@@ -561,6 +896,7 @@ export function readMysqlValue(
|
|
|
561
896
|
zongji.connection.config.timezone
|
|
562
897
|
);
|
|
563
898
|
break;
|
|
899
|
+
}
|
|
564
900
|
case MysqlTypes.DATETIME2: {
|
|
565
901
|
// Overlapping high-low to get all data in 32-bit numbers
|
|
566
902
|
const rawHigh = readIntBE(parser._buffer, parser._offset, 4);
|
|
@@ -582,7 +918,26 @@ export function readMysqlValue(
|
|
|
582
918
|
);
|
|
583
919
|
break;
|
|
584
920
|
}
|
|
585
|
-
case MysqlTypes.TIMESTAMP:
|
|
921
|
+
case MysqlTypes.TIMESTAMP: {
|
|
922
|
+
const hiresDec = hiresPrecision(columnSchema);
|
|
923
|
+
if (hiresDec > 0) {
|
|
924
|
+
// MariaDB hires TIMESTAMP: big-endian epoch seconds (the classic
|
|
925
|
+
// format is little-endian) followed by a big-endian fraction of
|
|
926
|
+
// exactly dec digits
|
|
927
|
+
raw = parser._buffer.readUInt32BE(parser._offset);
|
|
928
|
+
parser._offset += 4;
|
|
929
|
+
const fracBytes = [0, 1, 1, 2, 2, 3, 3][hiresDec];
|
|
930
|
+
fraction = hiresFractionObject(
|
|
931
|
+
parser._buffer.readUIntBE(parser._offset, fracBytes), hiresDec);
|
|
932
|
+
parser._offset += fracBytes;
|
|
933
|
+
result = dtDecode.getTimeStamp(
|
|
934
|
+
zongji.connection.config.dateStrings,
|
|
935
|
+
raw,
|
|
936
|
+
fraction,
|
|
937
|
+
zongji.connection.config.timezone
|
|
938
|
+
);
|
|
939
|
+
break;
|
|
940
|
+
}
|
|
586
941
|
raw = parser.parseUnsignedNumber(4);
|
|
587
942
|
result = dtDecode.getTimeStamp(
|
|
588
943
|
zongji.connection.config.dateStrings,
|
|
@@ -591,6 +946,7 @@ export function readMysqlValue(
|
|
|
591
946
|
zongji.connection.config.timezone
|
|
592
947
|
);
|
|
593
948
|
break;
|
|
949
|
+
}
|
|
594
950
|
case MysqlTypes.TIMESTAMP2:
|
|
595
951
|
raw = readIntBE(parser._buffer, parser._offset, 4);
|
|
596
952
|
parser._offset += 4;
|