Skip to content

Commit 891fe0a

Browse files
committed
TIME/DATETIME/TIMESTAMP microseconds precision support (#136)
1 parent 72c881e commit 891fe0a

File tree

3 files changed

+74
-10
lines changed

3 files changed

+74
-10
lines changed

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

+22-6
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ public abstract class AbstractRowsEventDataDeserializer<T extends EventData> imp
7272
private final Map<Long, TableMapEventData> tableMapEventByTableId;
7373

7474
private boolean deserializeDateAndTimeAsLong;
75+
private boolean microsecondsPrecision;
7576
private boolean deserializeCharAndBinaryAsByteArray;
7677

7778
public AbstractRowsEventDataDeserializer(Map<Long, TableMapEventData> tableMapEventByTableId) {
@@ -82,6 +83,10 @@ void setDeserializeDateAndTimeAsLong(boolean value) {
8283
this.deserializeDateAndTimeAsLong = value;
8384
}
8485

86+
void setMicrosecondsPrecision(boolean value) {
87+
this.microsecondsPrecision = value;
88+
}
89+
8590
void setDeserializeCharAndBinaryAsByteArray(boolean value) {
8691
this.deserializeCharAndBinaryAsByteArray = value;
8792
}
@@ -265,13 +270,17 @@ protected Serializable deserializeTimeV2(int meta, ByteArrayInputStream inputStr
265270
+ fractional-seconds storage (size depends on meta)
266271
*/
267272
long time = bigEndianLong(inputStream.read(3), 0, 3);
273+
int fsp = deserializeFractionalSeconds(meta, inputStream);
268274
Long timestamp = asUnixTime(1970, 1, 1,
269275
bitSlice(time, 2, 10, 24),
270276
bitSlice(time, 12, 6, 24),
271277
bitSlice(time, 18, 6, 24),
272-
deserializeFractionalSeconds(meta, inputStream)
278+
fsp / 1000
273279
);
274280
if (deserializeDateAndTimeAsLong) {
281+
if (microsecondsPrecision) {
282+
timestamp = timestamp * 1000 + fsp % 1000;
283+
}
275284
return timestamp;
276285
}
277286
return timestamp != null ? new java.sql.Time(timestamp) : null;
@@ -286,9 +295,12 @@ protected Serializable deserializeTimestamp(ByteArrayInputStream inputStream) th
286295
}
287296

288297
protected Serializable deserializeTimestampV2(int meta, ByteArrayInputStream inputStream) throws IOException {
289-
long timestamp = bigEndianLong(inputStream.read(4), 0, 4) * 1000 +
290-
deserializeFractionalSeconds(meta, inputStream);
298+
int fsp = deserializeFractionalSeconds(meta, inputStream);
299+
long timestamp = bigEndianLong(inputStream.read(4), 0, 4) * 1000 + fsp / 1000;
291300
if (deserializeDateAndTimeAsLong) {
301+
if (microsecondsPrecision) {
302+
timestamp = timestamp * 1000 + fsp % 1000;
303+
}
292304
return timestamp;
293305
}
294306
return new java.sql.Timestamp(timestamp);
@@ -320,16 +332,20 @@ protected Serializable deserializeDatetimeV2(int meta, ByteArrayInputStream inpu
320332
*/
321333
long datetime = bigEndianLong(inputStream.read(5), 0, 5);
322334
int yearMonth = bitSlice(datetime, 1, 17, 40);
335+
int fsp = deserializeFractionalSeconds(meta, inputStream);
323336
Long timestamp = asUnixTime(
324337
yearMonth / 13,
325338
yearMonth % 13,
326339
bitSlice(datetime, 18, 5, 40),
327340
bitSlice(datetime, 23, 5, 40),
328341
bitSlice(datetime, 28, 6, 40),
329342
bitSlice(datetime, 34, 6, 40),
330-
deserializeFractionalSeconds(meta, inputStream)
343+
fsp / 1000
331344
);
332345
if (deserializeDateAndTimeAsLong) {
346+
if (microsecondsPrecision) {
347+
timestamp = timestamp * 1000 + fsp % 1000;
348+
}
333349
return timestamp;
334350
}
335351
return timestamp != null ? new java.util.Date(timestamp) : null;
@@ -402,8 +418,8 @@ protected Long asUnixTime(int year, int month, int day, int hour, int minute, in
402418
protected int deserializeFractionalSeconds(int meta, ByteArrayInputStream inputStream) throws IOException {
403419
int length = (meta + 1) / 2;
404420
if (length > 0) {
405-
long fraction = bigEndianLong(inputStream.read(length), 0, length);
406-
return (int) (fraction / (0.1 * Math.pow(100, length - 1)));
421+
int fraction = bigEndianInteger(inputStream.read(length), 0, length);
422+
return fraction * (int) Math.pow(100, 3 - length);
407423
}
408424
return 0;
409425
}

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

+13-2
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,15 @@ private void ensureCompatibility(EventDataDeserializer eventDataDeserializer) {
153153
AbstractRowsEventDataDeserializer deserializer =
154154
(AbstractRowsEventDataDeserializer) eventDataDeserializer;
155155
deserializer.setDeserializeDateAndTimeAsLong(
156-
compatibilitySet.contains(CompatibilityMode.DATE_AND_TIME_AS_LONG));
156+
compatibilitySet.contains(CompatibilityMode.DATE_AND_TIME_AS_LONG) ||
157+
compatibilitySet.contains(CompatibilityMode.DATE_AND_TIME_AS_LONG_MICRO)
158+
);
159+
deserializer.setMicrosecondsPrecision(
160+
compatibilitySet.contains(CompatibilityMode.DATE_AND_TIME_AS_LONG_MICRO)
161+
);
157162
deserializer.setDeserializeCharAndBinaryAsByteArray(
158-
compatibilitySet.contains(CompatibilityMode.CHAR_AND_BINARY_AS_BYTE_ARRAY));
163+
compatibilitySet.contains(CompatibilityMode.CHAR_AND_BINARY_AS_BYTE_ARRAY)
164+
);
159165
}
160166
}
161167

@@ -215,6 +221,7 @@ public EventDataDeserializer getEventDataDeserializer(EventType eventType) {
215221

216222
/**
217223
* @see CompatibilityMode#DATE_AND_TIME_AS_LONG
224+
* @see CompatibilityMode#DATE_AND_TIME_AS_LONG_MICRO
218225
* @see CompatibilityMode#CHAR_AND_BINARY_AS_BYTE_ARRAY
219226
*/
220227
public enum CompatibilityMode {
@@ -226,6 +233,10 @@ public enum CompatibilityMode {
226233
* <p>This option is going to be enabled by default starting from mysql-binlog-connector-java@1.0.0.
227234
*/
228235
DATE_AND_TIME_AS_LONG,
236+
/**
237+
* Same as {@link CompatibilityMode#DATE_AND_TIME_AS_LONG} but values are returned in microseconds.
238+
*/
239+
DATE_AND_TIME_AS_LONG_MICRO,
229240
/**
230241
* Return CHAR/VARCHAR/BINARY/VARBINARY values as byte[]|s (instead of String|s).
231242
*

src/test/java/com/github/shyiko/mysql/binlog/BinaryLogClientIntegrationTest.java

+39-2
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
import java.util.logging.Level;
7878
import java.util.logging.Logger;
7979

80+
import static com.github.shyiko.mysql.binlog.event.deserialization.EventDeserializer.CompatibilityMode;
8081
import static org.mockito.Matchers.any;
8182
import static org.mockito.Matchers.eq;
8283
import static org.mockito.Mockito.inOrder;
@@ -292,6 +293,38 @@ public void execute(Statement statement) throws SQLException {
292293
new java.util.Date(generateTime(1989, 3, 21, 1, 2, 3, 777))});
293294
}
294295

296+
@Test
297+
public void testDeserializationOfDateAndTimeAsLong() throws Exception {
298+
final BinaryLogClient client = new BinaryLogClient(slave.hostname, slave.port,
299+
slave.username, slave.password);
300+
EventDeserializer eventDeserializer = new EventDeserializer();
301+
eventDeserializer.setCompatibilityMode(CompatibilityMode.DATE_AND_TIME_AS_LONG);
302+
client.setEventDeserializer(eventDeserializer);
303+
client.connect(DEFAULT_TIMEOUT);
304+
try {
305+
assertEquals(writeAndCaptureRow(client, "datetime(6)", "'1989-03-21 01:02:03.123456'"), new Serializable[]{
306+
generateTime(1989, 3, 21, 1, 2, 3, 123)});
307+
} catch (Exception e) {
308+
client.disconnect();
309+
}
310+
}
311+
312+
@Test
313+
public void testDeserializationOfDateAndTimeAsLongMicrosecondsPrecision() throws Exception {
314+
final BinaryLogClient client = new BinaryLogClient(slave.hostname, slave.port,
315+
slave.username, slave.password);
316+
EventDeserializer eventDeserializer = new EventDeserializer();
317+
eventDeserializer.setCompatibilityMode(CompatibilityMode.DATE_AND_TIME_AS_LONG_MICRO);
318+
client.setEventDeserializer(eventDeserializer);
319+
client.connect(DEFAULT_TIMEOUT);
320+
try {
321+
assertEquals(writeAndCaptureRow(client, "datetime(6)", "'1989-03-21 01:02:03.123456'"), new Serializable[]{
322+
generateTime(1989, 3, 21, 1, 2, 3, 123) * 1000 + 456});
323+
} catch (Exception e) {
324+
client.disconnect();
325+
}
326+
}
327+
295328
private BitSet bitSet(int... bitsToSetTrue) {
296329
BitSet result = new BitSet(bitsToSetTrue.length);
297330
for (int bit : bitsToSetTrue) {
@@ -313,8 +346,12 @@ private long generateTime(int year, int month, int day, int hour, int minute, in
313346
return instance.getTimeInMillis();
314347
}
315348

316-
private Serializable[] writeAndCaptureRow(final String columnDefinition, final String... values)
317-
throws Exception {
349+
private Serializable[] writeAndCaptureRow(final String columnDefinition, final String... values) throws Exception {
350+
return writeAndCaptureRow(client, columnDefinition, values);
351+
}
352+
353+
private Serializable[] writeAndCaptureRow(BinaryLogClient client, final String columnDefinition,
354+
final String... values) throws Exception {
318355
CapturingEventListener capturingEventListener = new CapturingEventListener();
319356
client.registerEventListener(capturingEventListener);
320357
try {

0 commit comments

Comments
 (0)