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

com.clickhouse.client.api.internal.ClickHouseLZ4InputStream Maven / Gradle / Ivy

There is a newer version: 0.7.0
Show newest version
package com.clickhouse.client.api.internal;

import com.clickhouse.client.api.ClientException;
import com.clickhouse.data.ClickHouseByteUtils;
import com.clickhouse.data.ClickHouseCityHash;
import com.clickhouse.data.ClickHouseUtils;
import net.jpountz.lz4.LZ4FastDecompressor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;

public class ClickHouseLZ4InputStream extends InputStream {

    private static Logger LOG = LoggerFactory.getLogger(ClickHouseLZ4InputStream.class);
    private final LZ4FastDecompressor decompressor;

    private final InputStream in;

    private ByteBuffer buffer;

    private byte[] tmpBuffer = new byte[1];


    public ClickHouseLZ4InputStream(InputStream in, LZ4FastDecompressor decompressor, int bufferSize) {
        super();
        this.decompressor = decompressor;
        this.in = in;
        this.buffer = ByteBuffer.allocate(bufferSize);
        this.buffer.limit(0);
    }

    @Override
    public int read() throws IOException {
        int n = read(tmpBuffer, 0, 1);
        return n == -1 ? -1 : tmpBuffer[0] & 0xFF;
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        if (b == null) {
            throw new NullPointerException("b is null");
        } else if (off < 0) {
            throw new IndexOutOfBoundsException("off is negative");
        } else if (len < 0) {
            throw new IndexOutOfBoundsException("len is negative");
        } else if (off + len > b.length) {
            throw new IndexOutOfBoundsException("off + len is greater than b.length");
        } else if (len == 0) {
            return 0;
        }

        int readBytes = 0;
        do {
            int remaining = Math.min(len - readBytes, buffer.remaining());
            buffer.get(b, off + readBytes, remaining);
            readBytes += remaining;
        } while (readBytes < len && refill() != -1);

        return readBytes == 0 ? -1 : readBytes;
    }


    static final byte MAGIC = (byte) 0x82;
    static final int HEADER_LENGTH = 25;

    final byte[] headerBuff = new byte[HEADER_LENGTH];

    /**
     * Method ensures to read all bytes from the input stream.
     * In case of network connection it may be a case when not all bytes are read at once.
     * @throws IOException
     */
    private boolean readFully(byte[] b, int off, int len) throws IOException {
        int n = 0;
        while (n < len) {
            int count = in.read(b, off + n, len - n);
            if (count < 0) {
                if (n == 0) {
                    return false;
                }
                throw new IOException(ClickHouseUtils.format("Incomplete read: {0} of {1}", n, len));
            }
            n += count;
        }

        return true;
    }

    private int refill() throws IOException {

        // read header
        boolean readFully = readFully(headerBuff, 0, HEADER_LENGTH);
        if (!readFully) {
            return -1;
        }

        if (headerBuff[16] != MAGIC) {
            // 1 byte - 0x82 (shows this is LZ4)
            throw new ClientException("Invalid LZ4 magic byte: '" + headerBuff[16] + "'");
        }

        // 4 bytes - size of the compressed data including 9 bytes of the header
        int compressedSizeWithHeader = getInt32(headerBuff, 17);
        // 4 bytes - size of uncompressed data
        int uncompressedSize = getInt32(headerBuff, 21);

        int offset = 9;
        final byte[] block =  new byte[compressedSizeWithHeader];
        block[0] = MAGIC;
        setInt32(block, 1, compressedSizeWithHeader);
        setInt32(block, 5, uncompressedSize);
        // compressed data: compressed_size - 9 bytes
        int remaining = compressedSizeWithHeader - offset;

        readFully = readFully(block, offset, remaining);
        if (!readFully) {
            throw new EOFException("Unexpected end of stream");
        }

        long[] real = ClickHouseCityHash.cityHash128(block, 0, compressedSizeWithHeader);
        if (real[0] != getInt64(headerBuff, 0) || real[1] != ClickHouseByteUtils.getInt64(headerBuff, 8)) {
            throw new ClientException("Corrupted stream: checksum mismatch");
        }

        if (buffer.capacity() < uncompressedSize) {
            buffer = ByteBuffer.allocate(uncompressedSize);
            LOG.warn("Buffer size is too small, reallocate buffer with size: " + uncompressedSize);
        }
        decompressor.decompress(ByteBuffer.wrap(block), offset,  buffer, 0, uncompressedSize);
        buffer.position(0);
        buffer.limit(uncompressedSize);
        return uncompressedSize;
    }

    /**
     * Read int32 Little Endian
     * @param bytes
     * @param offset
     * @return
     */
    static int getInt32(byte[] bytes, int offset) {
        return (0xFF & bytes[offset]) | ((0xFF & bytes[offset + 1]) << 8) | ((0xFF & bytes[offset + 2]) << 16)
                | ((0xFF & bytes[offset + 3]) << 24);
    }

    /**
     * Read int64 Little Endian
     * @param bytes
     * @param offset
     * @return
     */
    static long getInt64(byte[] bytes, int offset) {
        return (0xFFL & bytes[offset]) | ((0xFFL & bytes[offset + 1]) << 8) | ((0xFFL & bytes[offset + 2]) << 16)
                | ((0xFFL & bytes[offset + 3]) << 24) | ((0xFFL & bytes[offset + 4]) << 32)
                | ((0xFFL & bytes[offset + 5]) << 40) | ((0xFFL & bytes[offset + 6]) << 48)
                | ((0xFFL & bytes[offset + 7]) << 56);
    }

    static void setInt32(byte[] bytes, int offset, int value) {
        bytes[offset] = (byte) (0xFF & value);
        bytes[offset + 1] = (byte) (0xFF & (value >> 8));
        bytes[offset + 2] = (byte) (0xFF & (value >> 16));
        bytes[offset + 3] = (byte) (0xFF & (value >> 24));
    }

    @Override
    public void close() throws IOException {
        super.close();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy