blob: d0f235f8742a1385b505509cc81502dad6ff0dd3 [file] [log] [blame]
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (c) 2012-2014 Monty Program Ab
// Copyright (c) 2015-2021 MariaDB Corporation Ab
package org.mariadb.jdbc.plugin.codec;
import java.io.IOException;
import java.sql.SQLDataException;
import java.time.Duration;
import java.util.Calendar;
import java.util.EnumSet;
import org.mariadb.jdbc.client.*;
import org.mariadb.jdbc.client.socket.Writer;
import org.mariadb.jdbc.plugin.Codec;
/** Duration codec */
public class DurationCodec implements Codec<Duration> {
/** default instance */
public static final DurationCodec INSTANCE = new DurationCodec();
private static final EnumSet<DataType> COMPATIBLE_TYPES =
EnumSet.of(
DataType.TIME,
DataType.DATETIME,
DataType.TIMESTAMP,
DataType.VARSTRING,
DataType.VARCHAR,
DataType.STRING,
DataType.BLOB,
DataType.TINYBLOB,
DataType.MEDIUMBLOB,
DataType.LONGBLOB);
public String className() {
return Duration.class.getName();
}
public boolean canDecode(ColumnDecoder column, Class<?> type) {
return COMPATIBLE_TYPES.contains(column.getType()) && type.isAssignableFrom(Duration.class);
}
public boolean canEncode(Object value) {
return value instanceof Duration;
}
@Override
@SuppressWarnings("fallthrough")
public Duration decodeText(ReadableByteBuf buf, int length, ColumnDecoder column, Calendar cal)
throws SQLDataException {
int[] parts;
switch (column.getType()) {
case TIMESTAMP:
case DATETIME:
parts = LocalDateTimeCodec.parseTimestamp(buf.readAscii(length));
if (parts == null) return null;
return Duration.ZERO
.plusDays(parts[2] - 1)
.plusHours(parts[3])
.plusMinutes(parts[4])
.plusSeconds(parts[5])
.plusNanos(parts[6]);
case BLOB:
case TINYBLOB:
case MEDIUMBLOB:
case LONGBLOB:
if (column.isBinary()) {
buf.skip(length);
throw new SQLDataException(
String.format("Data type %s cannot be decoded as Duration", column.getType()));
}
// expected fallthrough
// BLOB is considered as String if it has a collation (this is TEXT column)
case TIME:
case VARCHAR:
case VARSTRING:
case STRING:
parts = LocalTimeCodec.parseTime(buf, length, column);
Duration d =
Duration.ZERO
.plusHours(parts[1])
.plusMinutes(parts[2])
.plusSeconds(parts[3])
.plusNanos(parts[4]);
if (parts[0] == -1) return d.negated();
return d;
default:
buf.skip(length);
throw new SQLDataException(
String.format("Data type %s cannot be decoded as Duration", column.getType()));
}
}
@Override
@SuppressWarnings("fallthrough")
public Duration decodeBinary(ReadableByteBuf buf, int length, ColumnDecoder column, Calendar cal)
throws SQLDataException {
long days = 0;
int hours = 0;
int minutes = 0;
int seconds = 0;
long microseconds = 0;
switch (column.getType()) {
case TIME:
boolean negate = false;
if (length > 0) {
negate = buf.readUnsignedByte() == 0x01;
if (length > 4) {
days = buf.readUnsignedInt();
if (length > 7) {
hours = buf.readByte();
minutes = buf.readByte();
seconds = buf.readByte();
if (length > 8) {
microseconds = buf.readInt();
}
}
}
}
Duration duration =
Duration.ZERO
.plusDays(days)
.plusHours(hours)
.plusMinutes(minutes)
.plusSeconds(seconds)
.plusNanos(microseconds * 1000);
if (negate) return duration.negated();
return duration;
case TIMESTAMP:
case DATETIME:
if (length == 0) return null;
int year = buf.readUnsignedShort();
int month = buf.readByte();
days = buf.readByte();
if (length > 4) {
hours = buf.readByte();
minutes = buf.readByte();
seconds = buf.readByte();
if (length > 7) {
microseconds = buf.readUnsignedInt();
}
}
// xpand workaround https://jira.mariadb.org/browse/XPT-274
if (year == 0 && month == 0 && days == 0 && hours == 0 && minutes == 0 && seconds == 0)
return null;
return Duration.ZERO
.plusDays(days - 1)
.plusHours(hours)
.plusMinutes(minutes)
.plusSeconds(seconds)
.plusNanos(microseconds * 1000);
case VARCHAR:
case VARSTRING:
case STRING:
int[] parts = LocalTimeCodec.parseTime(buf, length, column);
Duration d =
Duration.ZERO
.plusHours(parts[1])
.plusMinutes(parts[2])
.plusSeconds(parts[3])
.plusNanos(parts[4]);
if (parts[0] == -1) return d.negated();
return d;
default:
buf.skip(length);
throw new SQLDataException(
String.format("Data type %s cannot be decoded as Duration", column.getType()));
}
}
@Override
public void encodeText(Writer encoder, Context context, Object val, Calendar cal, Long maxLen)
throws IOException {
long s = ((Duration) val).getSeconds();
long microSecond = ((Duration) val).getNano() / 1000;
encoder.writeByte('\'');
if (microSecond != 0) {
encoder.writeAscii(
String.format("%d:%02d:%02d.%06d", s / 3600, (s % 3600) / 60, (s % 60), microSecond));
} else {
encoder.writeAscii(String.format("%d:%02d:%02d", s / 3600, (s % 3600) / 60, (s % 60)));
}
encoder.writeByte('\'');
}
@Override
public void encodeBinary(Writer encoder, Object val, Calendar cal, Long maxLength)
throws IOException {
int nano = ((Duration) val).getNano();
if (nano > 0) {
encoder.writeByte((byte) 12);
encodeDuration(encoder, ((Duration) val));
encoder.writeInt(nano / 1000);
} else {
encoder.writeByte((byte) 8);
encodeDuration(encoder, ((Duration) val));
}
}
private void encodeDuration(Writer encoder, Duration value) throws IOException {
encoder.writeByte((byte) (value.isNegative() ? 1 : 0));
encoder.writeInt((int) value.toDays());
encoder.writeByte((byte) (value.toHours() - 24 * value.toDays()));
encoder.writeByte((byte) (value.toMinutes() - 60 * value.toHours()));
encoder.writeByte((byte) (value.getSeconds() - 60 * value.toMinutes()));
}
public int getBinaryEncodeType() {
return DataType.TIME.get();
}
}