@vlasky/zongji 0.7.1 → 0.9.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 +21 -0
- package/README.md +83 -12
- package/index.d.ts +359 -9
- package/index.js +355 -28
- package/lib/binlog_event.js +892 -27
- package/lib/charset_map.js +86 -0
- package/lib/code_map.js +44 -1
- package/lib/common.js +384 -20
- package/lib/gtid_set.js +244 -0
- package/lib/mariadb_gtid.js +148 -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,
|
|
@@ -51,6 +58,14 @@ export function unsignedInt64ToNumberOrString(high, low) {
|
|
|
51
58
|
return value <= MAX_SAFE_BIGINT ? Number(value) : value.toString();
|
|
52
59
|
}
|
|
53
60
|
|
|
61
|
+
// An exact BigInt to the Number-or-exact-string convention used for
|
|
62
|
+
// 64-bit values: a Number within the safe integer range, otherwise an
|
|
63
|
+
// exact decimal string.
|
|
64
|
+
export function bigintToNumberOrString(value) {
|
|
65
|
+
return (value >= MIN_SAFE_BIGINT && value <= MAX_SAFE_BIGINT) ?
|
|
66
|
+
Number(value) : value.toString();
|
|
67
|
+
}
|
|
68
|
+
|
|
54
69
|
// Combine two unsigned 32-bit halves into an exact signed (two's
|
|
55
70
|
// complement) 64-bit value. Returns a Number when within the safe integer
|
|
56
71
|
// range, otherwise an exact string.
|
|
@@ -158,7 +173,12 @@ const parseAnyInt = function(parser, column, columnSchema) {
|
|
|
158
173
|
result = parseUInt64(parser);
|
|
159
174
|
break;
|
|
160
175
|
}
|
|
161
|
-
|
|
176
|
+
// Binlog optional metadata provides signedness directly (UNSIGNED);
|
|
177
|
+
// otherwise infer it from the INFORMATION_SCHEMA column definition
|
|
178
|
+
const unsigned = columnSchema.UNSIGNED !== undefined ?
|
|
179
|
+
columnSchema.UNSIGNED :
|
|
180
|
+
columnSchema.COLUMN_TYPE.indexOf('unsigned') !== -1;
|
|
181
|
+
if (!unsigned) {
|
|
162
182
|
const length = size * 8;
|
|
163
183
|
const int64 = (length == 64);
|
|
164
184
|
// Flip bits on negative signed integer
|
|
@@ -353,6 +373,238 @@ const readTemporalFraction = function(parser, fractionPrecision) {
|
|
|
353
373
|
};
|
|
354
374
|
};
|
|
355
375
|
|
|
376
|
+
export function formatUuid(buffer) {
|
|
377
|
+
const hex = buffer.toString('hex');
|
|
378
|
+
return [
|
|
379
|
+
hex.slice(0, 8),
|
|
380
|
+
hex.slice(8, 12),
|
|
381
|
+
hex.slice(12, 16),
|
|
382
|
+
hex.slice(16, 20),
|
|
383
|
+
hex.slice(20)
|
|
384
|
+
].join('-');
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Undoes MariaDB's per-value column compression (VARCHAR/BLOB COMPRESSED,
|
|
388
|
+
// sql/field_comp.cc). Header byte: bits 4-7 = method (0 = stored
|
|
389
|
+
// uncompressed, 8 = zlib), and for zlib bits 0-2 = size of the big-endian
|
|
390
|
+
// original-length field that follows, bit 3 = deflate stream has no zlib
|
|
391
|
+
// wrapper (raw deflate). An empty value is stored with no header at all.
|
|
392
|
+
// Variable-length integer of MySQL's mysql::serialization framework
|
|
393
|
+
// (libs/mysql/serialization/variable_length_integers.h, used by the
|
|
394
|
+
// tagged GTID event of MySQL 8.3+). NOT protobuf varint and NOT the
|
|
395
|
+
// classic length-encoded integer: the total byte count is the number of
|
|
396
|
+
// trailing one-bits of the first byte plus one (1-9); the value is the
|
|
397
|
+
// first byte shifted right by that count, joined with the remaining
|
|
398
|
+
// bytes little-endian shifted left by (8 - count). A 9-byte encoding
|
|
399
|
+
// (first byte 0xFF) carries a full little-endian u64 unshifted.
|
|
400
|
+
// Returns a BigInt; `end` bounds the read (event end minus checksum).
|
|
401
|
+
export function parseSerializationVarint(parser, end, what) {
|
|
402
|
+
if (parser._offset + 1 > end) {
|
|
403
|
+
throw new Error(`Truncated ${what}: varint head past event end`);
|
|
404
|
+
}
|
|
405
|
+
const first = parser.parseUnsignedNumber(1);
|
|
406
|
+
let extra = 0;
|
|
407
|
+
while (extra < 8 && (first >> extra) & 1) {
|
|
408
|
+
extra++;
|
|
409
|
+
}
|
|
410
|
+
if (parser._offset + extra > end) {
|
|
411
|
+
throw new Error(`Truncated ${what}: ${extra + 1}-byte varint ` +
|
|
412
|
+
'past event end');
|
|
413
|
+
}
|
|
414
|
+
if (extra === 8) {
|
|
415
|
+
let value = 0n;
|
|
416
|
+
for (let i = 0; i < 8; i++) {
|
|
417
|
+
value |= BigInt(parser.parseUnsignedNumber(1)) << BigInt(8 * i);
|
|
418
|
+
}
|
|
419
|
+
return value;
|
|
420
|
+
}
|
|
421
|
+
let rest = 0n;
|
|
422
|
+
for (let i = 0; i < extra; i++) {
|
|
423
|
+
rest |= BigInt(parser.parseUnsignedNumber(1)) << BigInt(8 * i);
|
|
424
|
+
}
|
|
425
|
+
return (BigInt(first) >> BigInt(extra + 1)) |
|
|
426
|
+
(rest << BigInt(8 - (extra + 1)));
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Signed variant: value x >= 0 encodes as x << 1, x < 0 as
|
|
430
|
+
// ((-(x + 1)) << 1) | 1 (a zigzag-like mapping)
|
|
431
|
+
export function parseSerializationVarintSigned(parser, end, what) {
|
|
432
|
+
const u = parseSerializationVarint(parser, end, what);
|
|
433
|
+
return (u & 1n) ? -(u >> 1n) - 1n : u >> 1n;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// MySQL charset names mostly match iconv-lite encoding names, with four
|
|
437
|
+
// classes of exception: the utf8 aliases; MySQL's "latin1", which is
|
|
438
|
+
// really cp1252 (its 0x80-0x9F range holds printable characters that
|
|
439
|
+
// ISO-8859-1 reserves for controls, and the server converts them as such
|
|
440
|
+
// for query results); the Unicode charsets whose stored byte order is
|
|
441
|
+
// big-endian while the iconv-lite encodings of the same spelling default
|
|
442
|
+
// to little-endian; and names iconv-lite spells differently or lacks
|
|
443
|
+
// (ujis is EUC-JP, eucjpms is its MS-extended variant for which plain
|
|
444
|
+
// EUC-JP is the closest decoder available, macce is Mac Central
|
|
445
|
+
// European). dec8, geostd8, hp8, keybcs2 and swe7 have no iconv-lite
|
|
446
|
+
// decoder at all; those columns come through as raw Buffers.
|
|
447
|
+
const ICONV_CHARSET_OVERRIDES = {
|
|
448
|
+
utf8mb3: 'utf8',
|
|
449
|
+
utf8mb4: 'utf8',
|
|
450
|
+
latin1: 'cp1252',
|
|
451
|
+
ucs2: 'utf16-be',
|
|
452
|
+
utf16: 'utf16-be',
|
|
453
|
+
utf16le: 'utf16-le',
|
|
454
|
+
utf32: 'utf32-be',
|
|
455
|
+
ujis: 'eucjp',
|
|
456
|
+
eucjpms: 'eucjp',
|
|
457
|
+
macce: 'maccenteuro',
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
// Decodes a text column's raw bytes using the column's own character set
|
|
461
|
+
// (INFORMATION_SCHEMA CHARACTER_SET_NAME, or the charset carried by FULL
|
|
462
|
+
// binlog metadata). Returns the buffer unchanged when the charset is
|
|
463
|
+
// unknown to iconv.
|
|
464
|
+
export function decodeTextColumn(buffer, charset) {
|
|
465
|
+
const charsetName = ICONV_CHARSET_OVERRIDES[charset] || charset || 'utf8';
|
|
466
|
+
if (charsetName === 'utf8') {
|
|
467
|
+
return buffer.toString('utf8');
|
|
468
|
+
}
|
|
469
|
+
try {
|
|
470
|
+
// A leading U+FEFF is column data, not a byte-order mark: the
|
|
471
|
+
// charset's byte order is fixed, and the server preserves the
|
|
472
|
+
// character when converting for query results
|
|
473
|
+
return iconv.decode(buffer, charsetName, { stripBOM: false });
|
|
474
|
+
} catch (err) {
|
|
475
|
+
console.warn(err);
|
|
476
|
+
return buffer;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const uncompressColumnValue = function(buffer) {
|
|
481
|
+
if (buffer.length === 0) {
|
|
482
|
+
return buffer;
|
|
483
|
+
}
|
|
484
|
+
const header = buffer[0];
|
|
485
|
+
const method = header >> 4;
|
|
486
|
+
if (method === 0) {
|
|
487
|
+
return buffer.subarray(1);
|
|
488
|
+
}
|
|
489
|
+
if (method === 8) {
|
|
490
|
+
const lengthBytes = header & 0x07;
|
|
491
|
+
const compressed = buffer.subarray(1 + lengthBytes);
|
|
492
|
+
return (header & 0x08)
|
|
493
|
+
? zlib.inflateRawSync(compressed)
|
|
494
|
+
: zlib.inflateSync(compressed);
|
|
495
|
+
}
|
|
496
|
+
throw new Error(
|
|
497
|
+
`Unknown MariaDB column compression method ${method}`);
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
// Undoes the payload envelope of MariaDB's compressed binlog EVENTS
|
|
501
|
+
// (log_bin_compress=ON, event codes 165-171; sql/log_event.cc
|
|
502
|
+
// binlog_get_uncompress_len / binlog_buf_uncompress). Distinct from the
|
|
503
|
+
// per-column COMPRESSED envelope above: header byte has bit 7 always set,
|
|
504
|
+
// bits 4-6 = algorithm (only 0 = zlib is defined), bits 0-2 = size of the
|
|
505
|
+
// big-endian original-length field that follows; then a zlib-wrapped
|
|
506
|
+
// deflate stream.
|
|
507
|
+
export function uncompressBinlogEventPayload(buffer) {
|
|
508
|
+
if (buffer.length < 1) {
|
|
509
|
+
throw new Error('Empty compressed binlog event payload');
|
|
510
|
+
}
|
|
511
|
+
const header = buffer[0];
|
|
512
|
+
const algorithm = (header & 0x70) >> 4;
|
|
513
|
+
if ((header & 0x80) === 0 || algorithm !== 0) {
|
|
514
|
+
throw new Error(
|
|
515
|
+
`Unknown binlog event compression header 0x${header.toString(16)}`);
|
|
516
|
+
}
|
|
517
|
+
const lengthBytes = header & 0x07;
|
|
518
|
+
if (lengthBytes < 1 || lengthBytes > 4 ||
|
|
519
|
+
buffer.length < 1 + lengthBytes) {
|
|
520
|
+
throw new Error(
|
|
521
|
+
`Corrupt binlog event compression header 0x${header.toString(16)}`);
|
|
522
|
+
}
|
|
523
|
+
const originalLength = buffer.readUIntBE(1, lengthBytes);
|
|
524
|
+
const inflated = zlib.inflateSync(buffer.subarray(1 + lengthBytes));
|
|
525
|
+
if (inflated.length !== originalLength) {
|
|
526
|
+
throw new Error(
|
|
527
|
+
`Compressed binlog event inflated to ${inflated.length} bytes, ` +
|
|
528
|
+
`expected ${originalLength}`);
|
|
529
|
+
}
|
|
530
|
+
return inflated;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Dotted-quad text form of a MariaDB INET4 value (4 bytes, network order)
|
|
534
|
+
export function formatInet4(buffer) {
|
|
535
|
+
return `${buffer[0]}.${buffer[1]}.${buffer[2]}.${buffer[3]}`;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Canonical text form of a MariaDB INET6 value (16 bytes, network order),
|
|
539
|
+
// matching the server's own formatting: RFC 5952 (lowercase hex groups,
|
|
540
|
+
// the longest run of two or more zero groups compressed to '::', the
|
|
541
|
+
// leftmost on a tie) with IPv4-mapped (::ffff:a.b.c.d) and IPv4-compatible
|
|
542
|
+
// (::a.b.c.d, except :: and ::1) addresses in dotted form
|
|
543
|
+
export function formatInet6(buffer) {
|
|
544
|
+
const leadingZero = buffer.subarray(0, 10).every(byte => byte === 0);
|
|
545
|
+
if (leadingZero) {
|
|
546
|
+
const v4 = formatInet4(buffer.subarray(12));
|
|
547
|
+
if (buffer[10] === 0xff && buffer[11] === 0xff) {
|
|
548
|
+
return `::ffff:${v4}`;
|
|
549
|
+
}
|
|
550
|
+
if (buffer[10] === 0 && buffer[11] === 0 &&
|
|
551
|
+
buffer.readUInt32BE(12) > 1) {
|
|
552
|
+
return `::${v4}`;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
const groups = [];
|
|
557
|
+
for (let i = 0; i < 16; i += 2) {
|
|
558
|
+
groups.push(buffer.readUInt16BE(i));
|
|
559
|
+
}
|
|
560
|
+
// Longest run of >=2 zero groups; leftmost wins a tie
|
|
561
|
+
let bestStart = -1, bestLength = 1;
|
|
562
|
+
for (let i = 0; i < 8;) {
|
|
563
|
+
if (groups[i] !== 0) { i++; continue; }
|
|
564
|
+
let j = i;
|
|
565
|
+
while (j < 8 && groups[j] === 0) j++;
|
|
566
|
+
if (j - i > bestLength) {
|
|
567
|
+
bestStart = i;
|
|
568
|
+
bestLength = j - i;
|
|
569
|
+
}
|
|
570
|
+
i = j;
|
|
571
|
+
}
|
|
572
|
+
const hex = (value) => value.toString(16);
|
|
573
|
+
if (bestStart === -1) {
|
|
574
|
+
return groups.map(hex).join(':');
|
|
575
|
+
}
|
|
576
|
+
const head = groups.slice(0, bestStart).map(hex).join(':');
|
|
577
|
+
const tail = groups.slice(bestStart + bestLength).map(hex).join(':');
|
|
578
|
+
return `${head}::${tail}`;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// MariaDB 5.3-era "hires" temporals (written under mysql56_temporal_format
|
|
582
|
+
// =OFF, or by tables never rebuilt since 10.0): fractional TIME/DATETIME/
|
|
583
|
+
// TIMESTAMP binlogged under the classic type codes 11/12/7 with no
|
|
584
|
+
// metadata, so the precision is only knowable from the column definition
|
|
585
|
+
// (COLUMN_TYPE reads e.g. 'datetime(3) /* mariadb-5.3 */'). MySQL never
|
|
586
|
+
// produces these codes with a precision: fractional temporals always use
|
|
587
|
+
// TIME2/DATETIME2/TIMESTAMP2.
|
|
588
|
+
const hiresPrecision = function(columnSchema) {
|
|
589
|
+
const match = /^(?:time|datetime|timestamp)\((\d)\)/
|
|
590
|
+
.exec(columnSchema.COLUMN_TYPE);
|
|
591
|
+
return match ? parseInt(match[1], 10) : 0;
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
// Same shape as readTemporalFraction, from a fraction that was stored
|
|
595
|
+
// with exactly `precision` decimal digits
|
|
596
|
+
const hiresFractionObject = function(value, precision) {
|
|
597
|
+
let milliseconds;
|
|
598
|
+
if (precision > 3) {
|
|
599
|
+
milliseconds = Math.floor(value / Math.pow(10, precision - 3));
|
|
600
|
+
} else if (precision < 3) {
|
|
601
|
+
milliseconds = value * Math.pow(10, 3 - precision);
|
|
602
|
+
} else {
|
|
603
|
+
milliseconds = value;
|
|
604
|
+
}
|
|
605
|
+
return { value, precision, milliseconds };
|
|
606
|
+
};
|
|
607
|
+
|
|
356
608
|
// This function is used to read and interpret non-null values from parser.
|
|
357
609
|
// The parser object contains the raw value, and functions useful for value
|
|
358
610
|
// interpretation, but not the underlying column type or how the value ought to
|
|
@@ -406,8 +658,11 @@ export function readMysqlValue(
|
|
|
406
658
|
low = parser.parseUnsignedNumber(column.metadata.size);
|
|
407
659
|
}
|
|
408
660
|
|
|
661
|
+
// Binlog optional metadata provides the value list directly;
|
|
662
|
+
// otherwise parse it out of the column definition.
|
|
409
663
|
// Second argument: prefixLen = 4 'set('
|
|
410
|
-
choices =
|
|
664
|
+
choices = columnSchema.SET_VALUES ||
|
|
665
|
+
parseSetEnumTypeDef(columnSchema.COLUMN_TYPE, 4);
|
|
411
666
|
result = '';
|
|
412
667
|
for (let i = 0; low >= Math.pow(2, i); i++) {
|
|
413
668
|
if (low & Math.pow(2, i)) result += choices[i] + ',';
|
|
@@ -422,27 +677,59 @@ export function readMysqlValue(
|
|
|
422
677
|
case MysqlTypes.ENUM:
|
|
423
678
|
raw = parser.parseUnsignedNumber(column.metadata.size);
|
|
424
679
|
// Second argument: prefixLen = 5 'enum('
|
|
425
|
-
choices =
|
|
680
|
+
choices = columnSchema.ENUM_VALUES ||
|
|
681
|
+
parseSetEnumTypeDef(columnSchema.COLUMN_TYPE, 5);
|
|
426
682
|
result = choices[raw - 1];
|
|
427
683
|
break;
|
|
428
684
|
case MysqlTypes.VAR_STRING:
|
|
429
685
|
// Never used?
|
|
430
686
|
result = parser.parseLengthCodedString();
|
|
431
687
|
break;
|
|
688
|
+
case MysqlTypes.VARCHAR_COMPRESSED: {
|
|
689
|
+
// MariaDB VARCHAR/VARBINARY ... COMPRESSED: length-prefixed like
|
|
690
|
+
// VARCHAR, with a per-value compression envelope around the payload
|
|
691
|
+
const prefixSize = column.metadata['max_length'] > 255 ? 2 : 1;
|
|
692
|
+
size = parser.parseUnsignedNumber(prefixSize);
|
|
693
|
+
buffer = uncompressColumnValue(parser.parseBuffer(size));
|
|
694
|
+
result = columnSchema.COLUMN_TYPE.substr(0, 6) === 'varbin' ?
|
|
695
|
+
buffer : decodeTextColumn(buffer, column.charset);
|
|
696
|
+
break;
|
|
697
|
+
}
|
|
432
698
|
case MysqlTypes.VARCHAR:
|
|
433
699
|
case MysqlTypes.STRING: {
|
|
434
700
|
const prefixSize = column.metadata['max_length'] > 255 ? 2 : 1;
|
|
435
701
|
size = parser.parseUnsignedNumber(prefixSize);
|
|
436
702
|
const def = columnSchema.COLUMN_TYPE;
|
|
437
703
|
const defPrefix = def.substr(0, 6);
|
|
438
|
-
|
|
704
|
+
// MariaDB logical types ride on STRING/VARCHAR with a binary
|
|
705
|
+
// charset; only COLUMN_TYPE distinguishes them from BINARY(n) or
|
|
706
|
+
// VARBINARY(n). Text forms match the server's own (and therefore
|
|
707
|
+
// what a mysql2 query returns); VECTOR stays a raw Buffer of
|
|
708
|
+
// little-endian float32s, as in query results. As with BINARY(n),
|
|
709
|
+
// the row image strips trailing zero bytes, so the value is
|
|
710
|
+
// re-padded to the type's fixed width before formatting.
|
|
711
|
+
const readFixedWidth = (width) => {
|
|
712
|
+
const padded = Buffer.alloc(width, 0);
|
|
713
|
+
parser.parseBuffer(size).copy(padded);
|
|
714
|
+
return padded;
|
|
715
|
+
};
|
|
716
|
+
if (def === 'uuid') {
|
|
717
|
+
result = formatUuid(readFixedWidth(16));
|
|
718
|
+
} else if (def === 'inet4') {
|
|
719
|
+
result = formatInet4(readFixedWidth(4));
|
|
720
|
+
} else if (def === 'inet6') {
|
|
721
|
+
result = formatInet6(readFixedWidth(16));
|
|
722
|
+
} else if (defPrefix === 'vector') {
|
|
723
|
+
result = parser.parseBuffer(size);
|
|
724
|
+
} else if (defPrefix === 'binary') {
|
|
439
725
|
const bufsize = parseInt(def.substr(7, def.length - 2), 10);
|
|
440
726
|
result = Buffer.alloc(bufsize, 0);
|
|
441
727
|
parser.parseBuffer(size).copy(result);
|
|
442
728
|
} else if (defPrefix === 'varbin') {
|
|
443
729
|
result = parser.parseBuffer(size);
|
|
444
730
|
} else {
|
|
445
|
-
|
|
731
|
+
// Text CHAR/VARCHAR: decode in the column's own charset
|
|
732
|
+
result = decodeTextColumn(parser.parseBuffer(size), column.charset);
|
|
446
733
|
}
|
|
447
734
|
break;
|
|
448
735
|
}
|
|
@@ -450,24 +737,20 @@ export function readMysqlValue(
|
|
|
450
737
|
case MysqlTypes.MEDIUM_BLOB:
|
|
451
738
|
case MysqlTypes.LONG_BLOB:
|
|
452
739
|
case MysqlTypes.BLOB:
|
|
740
|
+
case MysqlTypes.BLOB_COMPRESSED:
|
|
453
741
|
lengthSize = column.metadata['length_size'];
|
|
454
742
|
result = parser.parseBuffer(parser.parseUnsignedNumber(lengthSize));
|
|
743
|
+
if (column.type === MysqlTypes.BLOB_COMPRESSED) {
|
|
744
|
+
// MariaDB TEXT/BLOB ... COMPRESSED: the payload wears a per-value
|
|
745
|
+
// compression envelope; the charset handling below then applies
|
|
746
|
+
// to the inflated bytes as for a plain TEXT/BLOB
|
|
747
|
+
result = uncompressColumnValue(result);
|
|
748
|
+
}
|
|
455
749
|
|
|
456
750
|
// Blobs can be sometimes return as String instead of Buffer
|
|
457
751
|
// e.g. TINYTEXT, MEDIUMTEXT, LONGTEXT, TEXT data types
|
|
458
752
|
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
|
-
}
|
|
753
|
+
result = decodeTextColumn(result, column.charset);
|
|
471
754
|
}
|
|
472
755
|
break;
|
|
473
756
|
case MysqlTypes.JSON: {
|
|
@@ -504,7 +787,32 @@ export function readMysqlValue(
|
|
|
504
787
|
zongji.connection.config.timezone
|
|
505
788
|
);
|
|
506
789
|
break;
|
|
507
|
-
case MysqlTypes.TIME:
|
|
790
|
+
case MysqlTypes.TIME: {
|
|
791
|
+
const hiresDec = hiresPrecision(columnSchema);
|
|
792
|
+
if (hiresDec > 0) {
|
|
793
|
+
// MariaDB hires TIME: one big-endian unsigned integer of signed
|
|
794
|
+
// total time units offset by zero_point = 3020400 * 10^dec
|
|
795
|
+
// (TIME_MAX_VALUE_SECONDS + 1, scaled), so '00:00:00' stores
|
|
796
|
+
// exactly zero_point and the encoding sorts as unsigned
|
|
797
|
+
const bytes = [3, 4, 4, 5, 5, 5, 6][hiresDec];
|
|
798
|
+
const scale = Math.pow(10, hiresDec);
|
|
799
|
+
let stored = parser._buffer.readUIntBE(parser._offset, bytes);
|
|
800
|
+
parser._offset += bytes;
|
|
801
|
+
stored -= 3020400 * scale;
|
|
802
|
+
isNegative = stored < 0;
|
|
803
|
+
if (isNegative) stored = -stored;
|
|
804
|
+
fraction = hiresFractionObject(stored % scale, hiresDec);
|
|
805
|
+
const totalSeconds = Math.floor(stored / scale);
|
|
806
|
+
hour = Math.floor(totalSeconds / 3600);
|
|
807
|
+
minute = Math.floor((totalSeconds % 3600) / 60);
|
|
808
|
+
second = totalSeconds % 60;
|
|
809
|
+
result = (isNegative ? '-' : '') +
|
|
810
|
+
zeroPad(hour, hour > 99 ? 3 : 2) + ':' +
|
|
811
|
+
zeroPad(minute, 2) + ':' +
|
|
812
|
+
zeroPad(second, 2) +
|
|
813
|
+
dtDecode.getFractionString(fraction);
|
|
814
|
+
break;
|
|
815
|
+
}
|
|
508
816
|
raw = parseUInt24(parser);
|
|
509
817
|
|
|
510
818
|
isNegative = (raw & (1 << 23)) !== 0;
|
|
@@ -520,6 +828,7 @@ export function readMysqlValue(
|
|
|
520
828
|
zeroPad(minute, 2) + ':' +
|
|
521
829
|
zeroPad(second, 2);
|
|
522
830
|
break;
|
|
831
|
+
}
|
|
523
832
|
case MysqlTypes.TIME2:
|
|
524
833
|
raw = readIntBE(parser._buffer, parser._offset, 3);
|
|
525
834
|
parser._offset += 3;
|
|
@@ -545,7 +854,41 @@ export function readMysqlValue(
|
|
|
545
854
|
result += dtDecode.getFractionString(fraction);
|
|
546
855
|
}
|
|
547
856
|
break;
|
|
548
|
-
case MysqlTypes.DATETIME:
|
|
857
|
+
case MysqlTypes.DATETIME: {
|
|
858
|
+
const hiresDec = hiresPrecision(columnSchema);
|
|
859
|
+
if (hiresDec > 0) {
|
|
860
|
+
// MariaDB hires DATETIME: one big-endian unsigned integer of
|
|
861
|
+
// pack_time() scaled to dec fractional digits; mixed radix, not
|
|
862
|
+
// the YYYYMMDDHHMMSS decimal of the classic format and not the
|
|
863
|
+
// DATETIME2 bit layout. Up to 8 bytes (beyond 2^53), so split in
|
|
864
|
+
// BigInt.
|
|
865
|
+
const bytes = [5, 6, 6, 7, 7, 7, 8][hiresDec];
|
|
866
|
+
let packed = 0n;
|
|
867
|
+
for (let i = 0; i < bytes; i++) {
|
|
868
|
+
packed = (packed << 8n) | BigInt(parser._buffer[parser._offset + i]);
|
|
869
|
+
}
|
|
870
|
+
parser._offset += bytes;
|
|
871
|
+
const scale = 10n ** BigInt(hiresDec);
|
|
872
|
+
fraction = hiresFractionObject(Number(packed % scale), hiresDec);
|
|
873
|
+
let units = packed / scale;
|
|
874
|
+
const secondPart = Number(units % 60n); units /= 60n;
|
|
875
|
+
const minutePart = Number(units % 60n); units /= 60n;
|
|
876
|
+
const hourPart = Number(units % 24n); units /= 24n;
|
|
877
|
+
const dayPart = Number(units % 32n); units /= 32n;
|
|
878
|
+
const monthPart = Number(units % 13n); units /= 13n;
|
|
879
|
+
result = dtDecode.getDateTime(
|
|
880
|
+
zongji.connection.config.dateStrings,
|
|
881
|
+
Number(units), // year
|
|
882
|
+
monthPart,
|
|
883
|
+
dayPart,
|
|
884
|
+
hourPart,
|
|
885
|
+
minutePart,
|
|
886
|
+
secondPart,
|
|
887
|
+
fraction,
|
|
888
|
+
zongji.connection.config.timezone
|
|
889
|
+
);
|
|
890
|
+
break;
|
|
891
|
+
}
|
|
549
892
|
raw = parseUInt64(parser);
|
|
550
893
|
date = Math.floor(raw / 1000000);
|
|
551
894
|
time = raw % 1000000;
|
|
@@ -561,6 +904,7 @@ export function readMysqlValue(
|
|
|
561
904
|
zongji.connection.config.timezone
|
|
562
905
|
);
|
|
563
906
|
break;
|
|
907
|
+
}
|
|
564
908
|
case MysqlTypes.DATETIME2: {
|
|
565
909
|
// Overlapping high-low to get all data in 32-bit numbers
|
|
566
910
|
const rawHigh = readIntBE(parser._buffer, parser._offset, 4);
|
|
@@ -582,7 +926,26 @@ export function readMysqlValue(
|
|
|
582
926
|
);
|
|
583
927
|
break;
|
|
584
928
|
}
|
|
585
|
-
case MysqlTypes.TIMESTAMP:
|
|
929
|
+
case MysqlTypes.TIMESTAMP: {
|
|
930
|
+
const hiresDec = hiresPrecision(columnSchema);
|
|
931
|
+
if (hiresDec > 0) {
|
|
932
|
+
// MariaDB hires TIMESTAMP: big-endian epoch seconds (the classic
|
|
933
|
+
// format is little-endian) followed by a big-endian fraction of
|
|
934
|
+
// exactly dec digits
|
|
935
|
+
raw = parser._buffer.readUInt32BE(parser._offset);
|
|
936
|
+
parser._offset += 4;
|
|
937
|
+
const fracBytes = [0, 1, 1, 2, 2, 3, 3][hiresDec];
|
|
938
|
+
fraction = hiresFractionObject(
|
|
939
|
+
parser._buffer.readUIntBE(parser._offset, fracBytes), hiresDec);
|
|
940
|
+
parser._offset += fracBytes;
|
|
941
|
+
result = dtDecode.getTimeStamp(
|
|
942
|
+
zongji.connection.config.dateStrings,
|
|
943
|
+
raw,
|
|
944
|
+
fraction,
|
|
945
|
+
zongji.connection.config.timezone
|
|
946
|
+
);
|
|
947
|
+
break;
|
|
948
|
+
}
|
|
586
949
|
raw = parser.parseUnsignedNumber(4);
|
|
587
950
|
result = dtDecode.getTimeStamp(
|
|
588
951
|
zongji.connection.config.dateStrings,
|
|
@@ -591,6 +954,7 @@ export function readMysqlValue(
|
|
|
591
954
|
zongji.connection.config.timezone
|
|
592
955
|
);
|
|
593
956
|
break;
|
|
957
|
+
}
|
|
594
958
|
case MysqlTypes.TIMESTAMP2:
|
|
595
959
|
raw = readIntBE(parser._buffer, parser._offset, 4);
|
|
596
960
|
parser._offset += 4;
|