Skip to content

Commit f21e85e

Browse files
committed
Added support for MySQL JSON type
Added support for the JSON type added to MySQL 5.7. This required adding a new `ColumnType` and a new method to deserialize the JSON value, although the deserialization simply returns the `byte[]` containing the internal binary representation of the JSON value. In order to be useful, this binary representation needs to be parsed into something more useful. A new `JsonBinary` class was created that can perform this parsing and delegate to a `JsonFormatter` implementation when JSON objects or arrays are started/ended, when various name-value pairs are encountered in an object, when values are encountered in an array, and when scalar values are encountered (outside of a JSON object or array, which is not really standard JSON). Since many clients will simply want to obtain a JSON string representation of the JSON value, a `JsonStringFormatter` class was added, and a `JsonBinary.parseAsString` utility method added to conveniently obtain the JSON string representation. Of course, if this is not sufficient, clients can either subclass `JsonStringFormatter` to customize the behavior or implement their own `JsonFormatter` with completely custom behavior. Quite a few integration test methods were added to insert various JSON values into a table, read the corresponding `WriteRowsEventData` event for the INSERT, extract the binary JSON value from the event, and then compare the string representation of the JSON value to expected forms. Note that MySQL does allow for some values within JSON objects and arrays to be of types other than boolean, numeric, and strings (per the JSON spec). For example, MySQL `BLOB`, `DATE`, `TIME`, `DATETIME`, and `TIMESTAMP` values can be used and are encoded as "opaque types", although the `JsonBinary` parser does know how to interpret this opaque forms and call appropriate methods on the `JsonFormatter`. The `JsonStringFormatter` simply encodes the temporal types as formatted dates, times, or timestamps, while all other opaque values are represented as Base64-encoded strings. Also, MySQL does allow scalar values to be used in `JSON` columns outside of JSON objects and arrays. This does not adhere to the JSON spec, and as such the `JsonStringFormatter` simply returns the string representations of these scalar values. (Any other logic can be customized.)
1 parent 5744bdd commit f21e85e

File tree

11 files changed

+2016
-15
lines changed

11 files changed

+2016
-15
lines changed

.gitignore

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
.idea
22
.iml
33
.DS_Store
4-
target
4+
target
5+
.classpath
6+
.project
7+
.settings
8+
.vagrant

pom.xml

+3-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
4646
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
4747
<vagrant.bin>vagrant</vagrant.bin>
48-
<vagrant.integration.box>${basedir}/supplement/vagrant/mysql-5.6.12-sandbox-prepackaged</vagrant.integration.box>
48+
<vagrant.integration.box>${basedir}/supplement/vagrant/mysql-5.7.15-sandbox-prepackaged</vagrant.integration.box>
4949
</properties>
5050

5151
<dependencies>
@@ -207,6 +207,8 @@
207207
-Dvagrant.integration.box=supplement/vagrant/mysql-5.5.27-sandbox-prepackaged
208208
mvn -P coverage verify \
209209
-Dvagrant.integration.box=supplement/vagrant/mysql-5.6.12-sandbox-prepackaged
210+
mvn -P coverage verify \
211+
-Dvagrant.integration.box=supplement/vagrant/mysql-5.7.15-sandbox-prepackaged
210212

211213
# submit coverage report to coveralls
212214
mvn -P coverage coveralls:jacoco -DrepoToken=&lt;coveralls.io&gt;

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

+22-5
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,6 @@
1515
*/
1616
package com.github.shyiko.mysql.binlog.event.deserialization;
1717

18-
import com.github.shyiko.mysql.binlog.event.EventData;
19-
import com.github.shyiko.mysql.binlog.event.TableMapEventData;
20-
import com.github.shyiko.mysql.binlog.io.ByteArrayInputStream;
21-
2218
import java.io.IOException;
2319
import java.io.Serializable;
2420
import java.math.BigDecimal;
@@ -27,6 +23,10 @@
2723
import java.util.Map;
2824
import java.util.TimeZone;
2925

26+
import com.github.shyiko.mysql.binlog.event.EventData;
27+
import com.github.shyiko.mysql.binlog.event.TableMapEventData;
28+
import com.github.shyiko.mysql.binlog.io.ByteArrayInputStream;
29+
3030
/**
3131
* Whole class is basically a mix of <a href="https://code.google.com/p/open-replicator">open-replicator</a>'s
3232
* AbstractRowEventParser and MySQLUtils. Main purpose here is to ease rows deserialization.<p>
@@ -170,6 +170,8 @@ protected Serializable deserializeCell(ColumnType type, int meta, int length, By
170170
return deserializeSet(length, inputStream);
171171
case GEOMETRY:
172172
return deserializeGeometry(meta, inputStream);
173+
case JSON:
174+
return deserializeJson(meta, inputStream);
173175
default:
174176
throw new IOException("Unsupported type " + type);
175177
}
@@ -329,6 +331,21 @@ protected byte[] deserializeGeometry(int meta, ByteArrayInputStream inputStream)
329331
return inputStream.read(dataLength);
330332
}
331333

334+
/**
335+
* Deserialize the {@code JSON} value on the input stream, and return MySQL's internal binary representation
336+
* of the JSON value. See {@link com.github.shyiko.mysql.binlog.json.JsonBinary} for a utility to parse this
337+
* binary representation into something more useful, including a string representation.
338+
*
339+
* @param meta the number of bytes in which the length of the JSON value is found first on the input stream
340+
* @param inputStream the stream containing the JSON value
341+
* @return the MySQL internal binary representation of the JSON value; may be null
342+
* @throws IOException if there is a problem reading the input stream
343+
*/
344+
protected byte[] deserializeJson(int meta, ByteArrayInputStream inputStream) throws IOException {
345+
int blobLength = inputStream.readInteger(4);
346+
return inputStream.read(blobLength);
347+
}
348+
332349
// checkstyle, please ignore ParameterNumber for the next line
333350
private static Long asUnixTime(int year, int month, int day, int hour, int minute, int second, int millis) {
334351
// https://dev.mysql.com/doc/refman/5.0/en/datetime.html
@@ -376,7 +393,7 @@ private static int[] split(long value, int divider, int length) {
376393
/**
377394
* see mysql/strings/decimal.c
378395
*/
379-
private static BigDecimal asBigDecimal(int precision, int scale, byte[] value) {
396+
public static BigDecimal asBigDecimal(int precision, int scale, byte[] value) {
380397
boolean positive = (value[0] & 0x80) == 0x80;
381398
value[0] ^= 0x80;
382399
if (!positive) {

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

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public enum ColumnType {
4545
TIMESTAMP_V2(17),
4646
DATETIME_V2(18),
4747
TIME_V2(19),
48+
JSON(245),
4849
NEWDECIMAL(246),
4950
ENUM(247),
5051
SET(248),

0 commit comments

Comments
 (0)