All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dev.miku.r2dbc.mysql.codec.DurationCodec Maven / Gradle / Ivy

/*
 * Copyright 2018-2020 the original author or authors.
 *
 * 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 dev.miku.r2dbc.mysql.codec;

import dev.miku.r2dbc.mysql.Parameter;
import dev.miku.r2dbc.mysql.ParameterWriter;
import dev.miku.r2dbc.mysql.constant.DataTypes;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import reactor.core.publisher.Mono;

import java.time.Duration;

import static dev.miku.r2dbc.mysql.codec.DateTimes.*;

/**
 * Codec for {@link Duration}.
 */
final class DurationCodec extends AbstractClassedCodec {

    DurationCodec(ByteBufAllocator allocator) {
        super(allocator, Duration.class);
    }

    @Override
    public Duration decode(ByteBuf value, FieldInformation info, Class target, boolean binary, CodecContext context) {
        return binary ? decodeBinary(value) : decodeText(value);
    }

    @Override
    public boolean canEncode(Object value) {
        return value instanceof Duration;
    }

    @Override
    public Parameter encode(Object value, CodecContext context) {
        return new DurationParameter(allocator, (Duration) value);
    }

    @Override
    protected boolean doCanDecode(FieldInformation info) {
        return DataTypes.TIME == info.getType();
    }

    static void encodeTime(ParameterWriter writer, boolean isNegative, int hours, int minutes, int seconds, int micros) {
        if (isNegative) {
            writer.append('-');
        } else {
            // Time start with number, prepare writer to string mode.
            writer.startString();
        }

        if (hours < 10) {
            writer.append('0');
        }

        writer.writeInt(hours);
        writer.append(':');

        if (minutes < 10) {
            writer.append('0');
        }

        writer.writeInt(minutes);
        writer.append(':');

        if (seconds < 10) {
            writer.append('0');
        }

        writer.writeInt(seconds);

        // Must be greater than 0, can NOT use "micros != 0" here.
        // Sure, micros will never less than 0, but need to check for avoid inf loop.
        if (micros > 0) {
            writer.append('.');
            // WATCH OUT for inf loop: i from 100000 to 1, micros is greater than 0,
            // 0 < micros < 1 is impossible, so micros < 1 will be false finally,
            // then loop done. Safe.
            for (int i = 100000; micros < i; i /= 10) {
                writer.append('0');
            }
            // WATCH OUT for inf loop: micros is greater than 0, that means it least
            // contains one digit which is not 0, so micros % 10 == 0 will be false
            // finally, then loop done. Safe.
            while (micros % 10 == 0) {
                micros /= 10;
            }
            writer.writeInt(micros);
        }
    }

    private static Duration decodeText(ByteBuf buf) {
        boolean isNegative = LocalTimeCodec.readNegative(buf);
        int hour = readIntInDigits(buf);
        int minute = readIntInDigits(buf);
        int second = readIntInDigits(buf);
        int totalSeconds = hour * SECONDS_OF_HOUR + minute * SECONDS_OF_MINUTE + second;

        if (buf.isReadable()) {
            int nano = readMicroInDigits(buf) * NANOS_OF_MICRO;
            return Duration.ofSeconds(isNegative ? -totalSeconds : totalSeconds, isNegative ? -nano : nano);
        } else {
            return Duration.ofSeconds(isNegative ? -totalSeconds : totalSeconds);
        }
    }

    private static Duration decodeBinary(ByteBuf buf) {
        int bytes = buf.readableBytes();

        if (bytes < TIME_SIZE) {
            return Duration.ZERO;
        }

        boolean isNegative = buf.readBoolean();

        long day = buf.readUnsignedIntLE();
        byte hour = buf.readByte();
        byte minute = buf.readByte();
        byte second = buf.readByte();
        long totalSeconds = day * SECONDS_OF_DAY + ((long) hour) * SECONDS_OF_HOUR +
            ((long) minute) * SECONDS_OF_MINUTE + ((long) second);

        if (bytes < MICRO_TIME_SIZE) {
            return Duration.ofSeconds(isNegative ? -totalSeconds : totalSeconds);
        }

        long nanos = buf.readUnsignedIntLE() * NANOS_OF_MICRO;

        return Duration.ofSeconds(isNegative ? -totalSeconds : totalSeconds, isNegative ? -nanos : nanos);
    }

    private static final class DurationParameter extends AbstractParameter {

        private final ByteBufAllocator allocator;

        private final Duration value;

        private DurationParameter(ByteBufAllocator allocator, Duration value) {
            this.allocator = allocator;
            this.value = value;
        }

        @Override
        public Mono publishBinary() {
            return Mono.fromSupplier(() -> {
                long seconds = value.getSeconds();
                int nanos = value.getNano();

                if (seconds == 0 && nanos == 0) {
                    // It is zero of var int, not terminal.
                    return allocator.buffer(Byte.BYTES).writeByte(0);
                }

                boolean isNegative = value.isNegative();
                if (isNegative) {
                    if (nanos > 0) {
                        // Note: nanos should always be a positive integer or 0, see Duration.getNano().
                        // So if duration is negative, seconds should be humanity seconds - 1, so +1 then negate.
                        seconds = -(seconds + 1);
                        nanos = NANOS_OF_SECOND - nanos;
                    } else {
                        seconds = -seconds;
                    }
                }

                int size = nanos > 0 ? MICRO_TIME_SIZE : TIME_SIZE;

                ByteBuf buf = allocator.buffer(Byte.BYTES + size);

                try {
                    buf.writeByte(size)
                        .writeBoolean(isNegative)
                        .writeIntLE((int) (seconds / SECONDS_OF_DAY))
                        .writeByte((int) ((seconds % SECONDS_OF_DAY) / SECONDS_OF_HOUR))
                        .writeByte((int) ((seconds % SECONDS_OF_HOUR) / SECONDS_OF_MINUTE))
                        .writeByte((int) (seconds % SECONDS_OF_MINUTE));

                    if (nanos > 0) {
                        return buf.writeIntLE(nanos / NANOS_OF_MICRO);
                    }

                    return buf;
                } catch (Throwable e) {
                    buf.release();
                    throw e;
                }
            });
        }

        @Override
        public Mono publishText(ParameterWriter writer) {
            return Mono.fromRunnable(() -> encodeTo(writer));
        }

        @Override
        public short getType() {
            return DataTypes.TIME;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (!(o instanceof DurationParameter)) {
                return false;
            }

            DurationParameter that = (DurationParameter) o;

            return value.equals(that.value);
        }

        @Override
        public int hashCode() {
            return value.hashCode();
        }

        private void encodeTo(ParameterWriter writer) {
            boolean isNegative = this.value.isNegative();
            Duration abs = this.value.abs();
            long totalSeconds = abs.getSeconds();
            int hours = (int) (totalSeconds / SECONDS_OF_HOUR);
            int minutes = (int) ((totalSeconds % SECONDS_OF_HOUR) / SECONDS_OF_MINUTE);
            int seconds = (int) (totalSeconds % SECONDS_OF_MINUTE);
            int micros = abs.getNano() / NANOS_OF_MICRO;

            if (hours < 0 || minutes < 0 || seconds < 0 || micros < 0) {
                throw new IllegalStateException(String.format("Too large duration %s, abs value overflowing to %02d:%02d:%02d.%06d", value, hours, minutes, seconds, micros));
            }

            encodeTime(writer, isNegative, hours, minutes, seconds, micros);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy