diff --git a/pom.xml b/pom.xml
index a3f78dcb..0a854a90 100644
--- a/pom.xml
+++ b/pom.xml
@@ -65,7 +65,7 @@
@@ -319,6 +333,8 @@ protected void parse(ValueType type, JsonFormatter formatter) throws IOException
*/
protected void parseObject(boolean small, JsonFormatter formatter)
throws IOException {
+ int objectPosition = reader.getPosition(); // object start position
+
// Read the header ...
int numElements = readUnsignedIndex(Integer.MAX_VALUE, small, "number of elements in");
int numBytes = readUnsignedIndex(Integer.MAX_VALUE, small, "size of");
@@ -326,8 +342,9 @@ protected void parseObject(boolean small, JsonFormatter formatter)
// Read each key-entry, consisting of the offset and length of each key ...
int[] keyLengths = new int[numElements];
+ int[] keyOffsets = new int[numElements];
for (int i = 0; i != numElements; ++i) {
- readUnsignedIndex(numBytes, small, "key offset in"); // unused
+ keyOffsets[i] = readUnsignedIndex(numBytes, small, "key offset in");
keyLengths[i] = readUInt16();
}
@@ -368,13 +385,14 @@ protected void parseObject(boolean small, JsonFormatter formatter)
", which is larger than the binary form of the JSON document (" +
numBytes + " bytes)");
}
- entries[i] = new ValueEntry(type, offset);
+ entries[i] = new ValueEntry(type, objectPosition, offset);
}
}
// Read each key ...
String[] keys = new String[numElements];
for (int i = 0; i != numElements; ++i) {
+ ensureOffset(objectPosition + keyOffsets[i]);
keys[i] = reader.readString(keyLengths[i]);
}
@@ -397,7 +415,7 @@ protected void parseObject(boolean small, JsonFormatter formatter)
}
} else {
// Parse the value ...
- parse(entry.type, formatter);
+ parse(entry, formatter);
}
}
formatter.endObject();
@@ -462,7 +480,9 @@ protected void parseObject(boolean small, JsonFormatter formatter)
*/
// checkstyle, please ignore MethodLength for the next line
protected void parseArray(boolean small, JsonFormatter formatter)
- throws IOException {
+ throws IOException {
+ int objectPosition = reader.getPosition(); // array object start position
+
// Read the header ...
int numElements = readUnsignedIndex(Integer.MAX_VALUE, small, "number of elements in");
int numBytes = readUnsignedIndex(Integer.MAX_VALUE, small, "size of");
@@ -505,7 +525,7 @@ protected void parseArray(boolean small, JsonFormatter formatter)
", which is larger than the binary form of the JSON document (" +
numBytes + " bytes)");
}
- entries[i] = new ValueEntry(type, offset);
+ entries[i] = new ValueEntry(type, objectPosition, offset);
}
}
@@ -527,7 +547,7 @@ protected void parseArray(boolean small, JsonFormatter formatter)
}
} else {
// Parse the value ...
- parse(entry.type, formatter);
+ parse(entry, formatter);
}
}
formatter.endArray();
@@ -995,17 +1015,20 @@ protected static final class ValueEntry {
protected final ValueType type;
protected final int index;
+ protected final int objectIndex;
protected Object value;
protected boolean resolved;
public ValueEntry(ValueType type) {
this.type = type;
this.index = 0;
+ this.objectIndex = 0;
}
- public ValueEntry(ValueType type, int index) {
+ public ValueEntry(ValueType type, int objectIndex, int index) {
this.type = type;
this.index = index;
+ this.objectIndex = objectIndex;
}
public ValueEntry setValue(Object value) {
@@ -1013,5 +1036,9 @@ public ValueEntry setValue(Object value) {
this.resolved = true;
return this;
}
+
+ public int absoluteOffset() {
+ return this.objectIndex + this.index;
+ }
}
}
diff --git a/src/main/java/com/github/shyiko/mysql/binlog/io/JsonByteArrayInputStream.java b/src/main/java/com/github/shyiko/mysql/binlog/io/JsonByteArrayInputStream.java
new file mode 100644
index 00000000..b9dd55ea
--- /dev/null
+++ b/src/main/java/com/github/shyiko/mysql/binlog/io/JsonByteArrayInputStream.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2013 Stanley Shyiko
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.github.shyiko.mysql.binlog.io;
+
+import java.io.ByteArrayInputStream;
+import java.io.EOFException;
+import java.io.IOException;
+
+/**
+ * this byte array input stream is easy to parse json binary.
+ * @author Stanley Shyiko
+ */
+public class JsonByteArrayInputStream extends ByteArrayInputStream {
+
+ public JsonByteArrayInputStream(byte[] bytes) {
+ super(bytes);
+ }
+
+ /**
+ * Read fixed length string.
+ */
+ public String readString(int length) throws IOException {
+ return new String(read(length));
+ }
+
+ public byte[] read(int length) throws IOException {
+ byte[] bytes = new byte[length];
+ fill(bytes, 0, length);
+ return bytes;
+ }
+
+ public void fill(byte[] bytes, int offset, int length) throws IOException {
+ int remaining = length;
+ while (remaining != 0) {
+ int read = read(bytes, offset + length - remaining, remaining);
+ if (read == -1) {
+ throw new EOFException();
+ }
+ remaining -= read;
+ }
+ }
+
+ /**
+ * return current cursor position
+ * @return current cursor
+ */
+ public int getPosition() {
+ return super.pos;
+ }
+}
diff --git a/src/test/java/com/github/shyiko/mysql/binlog/event/deserialization/json/JsonBinaryValueIntegrationTest.java b/src/test/java/com/github/shyiko/mysql/binlog/event/deserialization/json/JsonBinaryValueIntegrationTest.java
index cdf314f7..eedf3607 100644
--- a/src/test/java/com/github/shyiko/mysql/binlog/event/deserialization/json/JsonBinaryValueIntegrationTest.java
+++ b/src/test/java/com/github/shyiko/mysql/binlog/event/deserialization/json/JsonBinaryValueIntegrationTest.java
@@ -15,22 +15,8 @@
*/
package com.github.shyiko.mysql.binlog.event.deserialization.json;
-import com.github.shyiko.mysql.binlog.BinaryLogClient;
-import com.github.shyiko.mysql.binlog.BinaryLogClientIntegrationTest;
-import com.github.shyiko.mysql.binlog.CapturingEventListener;
-import com.github.shyiko.mysql.binlog.CountDownEventListener;
-import com.github.shyiko.mysql.binlog.TraceEventListener;
-import com.github.shyiko.mysql.binlog.TraceLifecycleListener;
-import com.github.shyiko.mysql.binlog.event.Event;
-import com.github.shyiko.mysql.binlog.event.EventData;
-import com.github.shyiko.mysql.binlog.event.EventType;
-import com.github.shyiko.mysql.binlog.event.QueryEventData;
-import com.github.shyiko.mysql.binlog.event.WriteRowsEventData;
-import org.skyscreamer.jsonassert.JSONAssert;
-import org.testng.annotations.AfterClass;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.Test;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
import java.io.Serializable;
import java.sql.SQLException;
@@ -44,8 +30,24 @@
import java.util.logging.Level;
import java.util.logging.Logger;
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertTrue;
+import org.skyscreamer.jsonassert.JSONAssert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.github.shyiko.mysql.binlog.BinaryLogClient;
+import com.github.shyiko.mysql.binlog.BinaryLogClientIntegrationTest;
+import com.github.shyiko.mysql.binlog.CapturingEventListener;
+import com.github.shyiko.mysql.binlog.CountDownEventListener;
+import com.github.shyiko.mysql.binlog.TraceEventListener;
+import com.github.shyiko.mysql.binlog.TraceLifecycleListener;
+import com.github.shyiko.mysql.binlog.event.Event;
+import com.github.shyiko.mysql.binlog.event.EventData;
+import com.github.shyiko.mysql.binlog.event.EventType;
+import com.github.shyiko.mysql.binlog.event.QueryEventData;
+import com.github.shyiko.mysql.binlog.event.UpdateRowsEventData;
+import com.github.shyiko.mysql.binlog.event.WriteRowsEventData;
/**
* @author Randall Hauch
@@ -104,6 +106,72 @@ public void execute(Statement statement) throws SQLException {
eventListener.reset();
}
+ @Test
+ public void testMysql8JsonSetPartialUpdateWithHoles() throws Exception {
+ CountDownEventListener eventListener = new CountDownEventListener();
+ client.registerEventListener(eventListener);
+ CapturingEventListener capturingEventListener = new CapturingEventListener();
+ client.registerEventListener(capturingEventListener);
+ String json = "{\"age\":22,\"addr\":{\"code\":100,\"detail\":{\"ab\":\"970785C8-C299\"}},\"name\":\"Alice\"}";
+ master.execute("DROP TABLE IF EXISTS json_test", "create table json_test (j JSON)",
+ "INSERT INTO json_test VALUES ('" + json + "')",
+ "UPDATE json_test SET j = JSON_SET(j, '$.addr.detail.ab', '970785C8')");
+ eventListener.waitFor(WriteRowsEventData.class, 1, DEFAULT_TIMEOUT);
+ eventListener.waitFor(UpdateRowsEventData.class, 1, DEFAULT_TIMEOUT);
+ List