Skip to content

Commit 2a72e66

Browse files
committed
fix for json parsing error
1 parent 2cbc106 commit 2a72e66

File tree

2 files changed

+176
-36
lines changed

2 files changed

+176
-36
lines changed

src/main/java/com/github/shyiko/mysql/binlog/event/deserialization/json/JsonBinary.java

Lines changed: 58 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,6 @@
8080
* <h2>Grammar</h2>
8181
* The grammar of the binary representation of JSON objects are defined in the MySQL codebase in the
8282
* <a href="https://github.com/mysql/mysql-server/blob/5.7/sql/json_binary.h">json_binary.h</a> file:
83-
* <p>
84-
*
8583
* <pre>
8684
* doc ::= type value
8785
* type ::=
@@ -178,12 +176,7 @@ public static String parseAsString(byte[] bytes) throws IOException {
178176
* @throws IOException if there is a problem reading or processing the binary representation
179177
*/
180178
public static void parse(byte[] bytes, JsonFormatter formatter) throws IOException {
181-
if (bytes.length == 0) {
182-
// When the top-level value is a JSON "null", the value in java shows up as a non-null, empty byte array
183-
formatter.valueNull();
184-
} else {
185-
new JsonBinary(bytes).parse(formatter);
186-
}
179+
new JsonBinary(bytes).parse(formatter);
187180
}
188181

189182
private final ByteArrayInputStream reader;
@@ -194,6 +187,7 @@ public JsonBinary(byte[] bytes) {
194187

195188
public JsonBinary(ByteArrayInputStream contents) {
196189
this.reader = contents;
190+
this.reader.mark(Integer.MAX_VALUE);
197191
}
198192

199193
public String getString() {
@@ -267,8 +261,6 @@ protected void parse(ValueType type, JsonFormatter formatter) throws IOException
267261
* <a href="https://github.com/mysql/mysql-server/blob/5.7/sql/json_binary.h">json_binary.h</a> file:
268262
* <h3>Grammar</h3>
269263
*
270-
* <h3>Grammar</h3>
271-
*
272264
* <pre>
273265
* value ::=
274266
* object |
@@ -323,17 +315,22 @@ protected void parse(ValueType type, JsonFormatter formatter) throws IOException
323315
* @throws IOException if there is a problem reading the JSON value
324316
*/
325317
protected void parseObject(boolean small, JsonFormatter formatter)
326-
throws IOException {
318+
throws IOException {
319+
// this is terrible, but without a decent seekable InputStream the other way seemed like
320+
// a full-on rewrite
321+
int objectOffset = this.reader.getPosition();
322+
327323
// Read the header ...
328324
int numElements = readUnsignedIndex(Integer.MAX_VALUE, small, "number of elements in");
329325
int numBytes = readUnsignedIndex(Integer.MAX_VALUE, small, "size of");
330326
int valueSize = small ? 2 : 4;
331327

332328
// Read each key-entry, consisting of the offset and length of each key ...
333-
int[] keyLengths = new int[numElements];
329+
KeyEntry[] keys = new KeyEntry[numElements];
334330
for (int i = 0; i != numElements; ++i) {
335-
readUnsignedIndex(numBytes, small, "key offset in"); // unused
336-
keyLengths[i] = readUInt16();
331+
keys[i] = new KeyEntry(
332+
readUnsignedIndex(numBytes, small, "key offset in"),
333+
readUInt16());
337334
}
338335

339336
// Read each key value value-entry
@@ -369,18 +366,23 @@ protected void parseObject(boolean small, JsonFormatter formatter)
369366
int offset = readUnsignedIndex(Integer.MAX_VALUE, small, "value offset in");
370367
if (offset >= numBytes) {
371368
throw new IOException("The offset for the value in the JSON binary document is " +
372-
offset +
373-
", which is larger than the binary form of the JSON document (" +
374-
numBytes + " bytes)");
369+
offset +
370+
", which is larger than the binary form of the JSON document (" +
371+
numBytes + " bytes)");
375372
}
376373
entries[i] = new ValueEntry(type, offset);
377374
}
378375
}
379376

380377
// Read each key ...
381-
String[] keys = new String[numElements];
382378
for (int i = 0; i != numElements; ++i) {
383-
keys[i] = reader.readString(keyLengths[i]);
379+
final int skipBytes = keys[i].index + objectOffset - reader.getPosition();
380+
// Skip to a start of a field name if the current position does not point to it
381+
// This can happen for MySQL 8
382+
if (skipBytes != 0) {
383+
reader.fastSkip(skipBytes);
384+
}
385+
keys[i].name = reader.readString(keys[i].length);
384386
}
385387

386388
// Now parse the values ...
@@ -389,7 +391,7 @@ protected void parseObject(boolean small, JsonFormatter formatter)
389391
if (i != 0) {
390392
formatter.nextEntry();
391393
}
392-
formatter.name(keys[i]);
394+
formatter.name(keys[i].name);
393395
ValueEntry entry = entries[i];
394396
if (entry.resolved) {
395397
Object value = entry.value;
@@ -402,6 +404,8 @@ protected void parseObject(boolean small, JsonFormatter formatter)
402404
}
403405
} else {
404406
// Parse the value ...
407+
this.reader.reset();
408+
this.reader.fastSkip(objectOffset + entry.index);
405409
parse(entry.type, formatter);
406410
}
407411
}
@@ -467,7 +471,9 @@ protected void parseObject(boolean small, JsonFormatter formatter)
467471
*/
468472
// checkstyle, please ignore MethodLength for the next line
469473
protected void parseArray(boolean small, JsonFormatter formatter)
470-
throws IOException {
474+
throws IOException {
475+
int arrayOffset = this.reader.getPosition();
476+
471477
// Read the header ...
472478
int numElements = readUnsignedIndex(Integer.MAX_VALUE, small, "number of elements in");
473479
int numBytes = readUnsignedIndex(Integer.MAX_VALUE, small, "size of");
@@ -506,9 +512,9 @@ protected void parseArray(boolean small, JsonFormatter formatter)
506512
int offset = readUnsignedIndex(Integer.MAX_VALUE, small, "value offset in");
507513
if (offset >= numBytes) {
508514
throw new IOException("The offset for the value in the JSON binary document is " +
509-
offset +
510-
", which is larger than the binary form of the JSON document (" +
511-
numBytes + " bytes)");
515+
offset +
516+
", which is larger than the binary form of the JSON document (" +
517+
numBytes + " bytes)");
512518
}
513519
entries[i] = new ValueEntry(type, offset);
514520
}
@@ -532,6 +538,9 @@ protected void parseArray(boolean small, JsonFormatter formatter)
532538
}
533539
} else {
534540
// Parse the value ...
541+
this.reader.reset();
542+
this.reader.fastSkip(arrayOffset + entry.index);
543+
535544
parse(entry.type, formatter);
536545
}
537546
}
@@ -655,7 +664,6 @@ protected void parseString(JsonFormatter formatter) throws IOException {
655664
* See the <a href=
656665
* "https://github.com/mysql/mysql-server/blob/e0e0ae2ea27c9bb76577664845507ef224d362e4/sql/json_binary.cc#L1034">
657666
* MySQL source code</a> for the logic used in this method.
658-
* <p>
659667
* <h3>Grammar</h3>
660668
*
661669
* <pre>
@@ -680,7 +688,7 @@ protected void parseOpaque(JsonFormatter formatter) throws IOException {
680688
ColumnType type = ColumnType.byCode(customType);
681689
if (type == null) {
682690
throw new IOException("Unknown type '" + asHex(customType) +
683-
"' in first byte of a JSON opaque value");
691+
"' in first byte of a JSON opaque value");
684692
}
685693
// Read the data length ...
686694
int length = readVariableInt();
@@ -857,7 +865,7 @@ protected void parseDecimal(int length, JsonFormatter formatter) throws IOExcept
857865
}
858866

859867
protected void parseOpaqueValue(ColumnType type, int length, JsonFormatter formatter)
860-
throws IOException {
868+
throws IOException {
861869
formatter.valueOpaque(type, reader.read(length));
862870
}
863871

@@ -879,7 +887,7 @@ protected int readUnsignedIndex(int maxValue, boolean isSmall, String desc) thro
879887
long result = isSmall ? readUInt16() : readUInt32();
880888
if (result > maxValue) {
881889
throw new IOException("The " + desc + " the JSON document is " + result +
882-
" and is too big for the binary form of the document (" + maxValue + ")");
890+
" and is too big for the binary form of the document (" + maxValue + ")");
883891
}
884892
if (result > Integer.MAX_VALUE) {
885893
throw new IOException("The " + desc + " the JSON document is " + result + " and is too big to be used");
@@ -932,7 +940,7 @@ protected long readInt64() throws IOException {
932940
long b7 = reader.read() & 0xFF;
933941
long b8 = reader.read();
934942
return b8 << 56 | (b7 << 48) | (b6 << 40) | (b5 << 32) |
935-
(b4 << 24) | (b3 << 16) | (b2 << 8) | b1;
943+
(b4 << 24) | (b3 << 16) | (b2 << 8) | b1;
936944
}
937945

938946
protected BigInteger readUInt64() throws IOException {
@@ -951,6 +959,7 @@ protected BigInteger readUInt64() throws IOException {
951959
* to 16383, and so on...
952960
*
953961
* @return the integer value
962+
* @throws IOException if we don't encounter an end-of-int marker
954963
*/
955964
protected int readVariableInt() throws IOException {
956965
int length = 0;
@@ -993,6 +1002,26 @@ protected static String asHex(int value) {
9931002
return Integer.toHexString(value);
9941003
}
9951004

1005+
/**
1006+
* Class used internally to hold key entry information.
1007+
*/
1008+
protected static final class KeyEntry {
1009+
1010+
protected final int index;
1011+
protected final int length;
1012+
protected String name;
1013+
1014+
public KeyEntry(int index, int length) {
1015+
this.index = index;
1016+
this.length = length;
1017+
}
1018+
1019+
public KeyEntry setKey(String key) {
1020+
this.name = key;
1021+
return this;
1022+
}
1023+
}
1024+
9961025
/**
9971026
* Class used internally to hold value entry information.
9981027
*/

0 commit comments

Comments
 (0)