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

com.clickhouse.jdbc.internal.StreamBasedPreparedStatement Maven / Gradle / Ivy

There is a newer version: 0.7.1-patch1
Show newest version
package com.clickhouse.jdbc.internal;

import java.io.File;
import java.io.InputStream;
import java.math.BigDecimal;
import java.sql.Array;
import java.sql.Date;
import java.sql.ParameterMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

import com.clickhouse.client.ClickHouseClient;
import com.clickhouse.client.ClickHouseRequest;
import com.clickhouse.data.ClickHouseColumn;
import com.clickhouse.data.ClickHouseDataStreamFactory;
import com.clickhouse.data.ClickHouseDataType;
import com.clickhouse.data.ClickHouseInputStream;
import com.clickhouse.data.ClickHouseOutputStream;
import com.clickhouse.data.ClickHousePassThruStream;
import com.clickhouse.data.ClickHousePipedOutputStream;
import com.clickhouse.data.ClickHouseValues;
import com.clickhouse.data.ClickHouseWriter;
import com.clickhouse.logging.Logger;
import com.clickhouse.logging.LoggerFactory;
import com.clickhouse.jdbc.ClickHousePreparedStatement;
import com.clickhouse.jdbc.SqlExceptionUtils;
import com.clickhouse.jdbc.parser.ClickHouseSqlStatement;

public class StreamBasedPreparedStatement extends AbstractPreparedStatement implements ClickHousePreparedStatement {
    private static final Logger log = LoggerFactory.getLogger(StreamBasedPreparedStatement.class);

    private static final String ERROR_SET_PARAM = "Please use setString()/setBytes()/setInputStream() or pass String/InputStream/ClickHouseInputStream to setObject() method instead";
    private static final String DEFAULT_KEY = "pipe";
    private static final List DEFAULT_PARAMS = Collections
            .singletonList(ClickHouseColumn.of("data", ClickHouseDataType.String, false));

    private final ClickHouseSqlStatement parsedStmt;
    private final ClickHouseParameterMetaData paramMetaData;

    private final List batch;

    private ClickHouseInputStream value;

    protected StreamBasedPreparedStatement(ClickHouseConnectionImpl connection, ClickHouseRequest request,
            ClickHouseSqlStatement parsedStmt, int resultSetType, int resultSetConcurrency, int resultSetHoldability)
            throws SQLException {
        super(connection, request, resultSetType, resultSetConcurrency, resultSetHoldability);

        this.parsedStmt = parsedStmt;
        this.value = null;
        paramMetaData = new ClickHouseParameterMetaData(DEFAULT_PARAMS, mapper, connection.getTypeMap());
        batch = new LinkedList<>();
    }

    protected void ensureParams() throws SQLException {
        if (value == null) {
            throw SqlExceptionUtils.clientError("Missing input stream");
        }
    }

    @Override
    protected long[] executeAny(boolean asBatch) throws SQLException {
        ensureOpen();
        boolean continueOnError = false;
        if (asBatch) {
            if (batch.isEmpty()) {
                return ClickHouseValues.EMPTY_LONG_ARRAY;
            }
            continueOnError = getConnection().getJdbcConfig().isContinueBatchOnError();
        } else {
            try {
                if (!batch.isEmpty()) {
                    throw SqlExceptionUtils.undeterminedExecutionError();
                }
                addBatch();
            } catch (SQLException e) {
                clearBatch();
                throw e;
            }
        }

        long[] results = new long[batch.size()];
        int count = 0;
        String sql = getRequest().getStatements(false).get(0);
        try {
            for (ClickHouseInputStream in : batch) {
                @SuppressWarnings("unchecked")
                final CompletableFuture future = (CompletableFuture) in.removeUserData(DEFAULT_KEY);
                results[count++] = executeInsert(sql, in);
                if (future != null) {
                    future.get();
                }
            }
        } catch (Exception e) {
            if (e instanceof InterruptedException) {
                Thread.currentThread().interrupt();
            }

            if (!asBatch) {
                throw SqlExceptionUtils.handle(e);
            }

            if (!continueOnError) {
                throw SqlExceptionUtils.batchUpdateError(e, results);
            }
            log.error("Failed to execute batch insert of %d records", count + 1, e);
        } finally {
            clearBatch();
        }

        return results;
    }

    @Override
    protected int getMaxParameterIndex() {
        return 1;
    }

    protected String getSql() {
        // why? because request can be modified so it might not always same as
        // parsedStmt.getSQL()
        return getRequest().getStatements(false).get(0);
    }

    @Override
    public ResultSet executeQuery() throws SQLException {
        ensureParams();
        try {
            executeAny(false);
        } catch (SQLException e) {
            if (e.getSQLState() != null) {
                throw e;
            } else {
                throw new SQLException("Query failed", SqlExceptionUtils.SQL_STATE_SQL_ERROR, e.getCause());
            }
        }

        ResultSet rs = getResultSet();
        if (rs != null) { // should not happen
            try {
                rs.close();
            } catch (Exception e) {
                // ignore
            }
        }
        return newEmptyResultSet();
    }

    @Override
    public long executeLargeUpdate() throws SQLException {
        ensureParams();
        try {
            executeAny(false);
        } catch (SQLException e) {
            if (e.getSQLState() != null) {
                throw e;
            } else {
                throw new SQLException("Update failed", SqlExceptionUtils.SQL_STATE_SQL_ERROR, e.getCause());
            }
        }
        long row = getLargeUpdateCount();
        return row > 0L ? row : 0L;
    }

    @Override
    public void setByte(int parameterIndex, byte x) throws SQLException {
        throw SqlExceptionUtils.clientError(ERROR_SET_PARAM);
    }

    @Override
    public void setShort(int parameterIndex, short x) throws SQLException {
        throw SqlExceptionUtils.clientError(ERROR_SET_PARAM);
    }

    @Override
    public void setInt(int parameterIndex, int x) throws SQLException {
        throw SqlExceptionUtils.clientError(ERROR_SET_PARAM);
    }

    @Override
    public void setLong(int parameterIndex, long x) throws SQLException {
        throw SqlExceptionUtils.clientError(ERROR_SET_PARAM);
    }

    @Override
    public void setFloat(int parameterIndex, float x) throws SQLException {
        throw SqlExceptionUtils.clientError(ERROR_SET_PARAM);
    }

    @Override
    public void setDouble(int parameterIndex, double x) throws SQLException {
        throw SqlExceptionUtils.clientError(ERROR_SET_PARAM);
    }

    @Override
    public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException {
        throw SqlExceptionUtils.clientError(ERROR_SET_PARAM);
    }

    @Override
    public void setString(int parameterIndex, String x) throws SQLException {
        ensureOpen();

        value = ClickHouseInputStream.of(x);
    }

    @Override
    public void setBytes(int parameterIndex, byte[] x) throws SQLException {
        ensureOpen();

        value = ClickHouseInputStream.of(x);
    }

    @Override
    public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException {
        ensureOpen();

        value = ClickHouseInputStream.of(x);
    }

    @Override
    public void clearParameters() throws SQLException {
        ensureOpen();

        value = null;
    }

    @Override
    public void setObject(int parameterIndex, Object x) throws SQLException {
        ensureOpen();

        if (x instanceof ClickHousePassThruStream) {
            ClickHousePassThruStream stream = (ClickHousePassThruStream) x;
            if (!stream.hasInput()) {
                throw SqlExceptionUtils.clientError("No input available in the given pass-thru stream");
            }
            value = stream.newInputStream(getConfig().getWriteBufferSize(), null);
        } else if (x instanceof ClickHouseWriter) {
            final ClickHouseWriter writer = (ClickHouseWriter) x;
            final ClickHousePipedOutputStream stream = ClickHouseDataStreamFactory.getInstance() // NOSONAR
                    .createPipedOutputStream(getConfig());
            value = stream.getInputStream();

            // always run in async mode or it will not work
            value.setUserData(DEFAULT_KEY, ClickHouseClient.submit(() -> {
                try (ClickHouseOutputStream out = stream) {
                    writer.write(out);
                }
                return true;
            }));
        } else if (x instanceof InputStream) {
            value = ClickHouseInputStream.of((InputStream) x);
        } else if (x instanceof String) {
            value = ClickHouseInputStream.of((String) x);
        } else if (x instanceof byte[]) {
            value = ClickHouseInputStream.of((byte[]) x);
        } else if (x instanceof File) {
            value = ClickHouseInputStream.of((File) x);
        } else {
            throw SqlExceptionUtils
                    .clientError(
                            "Only byte[], String, File, InputStream, ClickHousePassThruStream, and ClickHouseWriter are supported");
        }
    }

    @Override
    public boolean execute() throws SQLException {
        ensureParams();
        if (!batch.isEmpty()) {
            throw SqlExceptionUtils.undeterminedExecutionError();
        }

        final String sql = getSql();
        @SuppressWarnings("unchecked")
        final CompletableFuture future = (CompletableFuture) value.removeUserData(DEFAULT_KEY);
        executeInsert(sql, value);
        if (future != null) {
            try {
                future.get();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                log.warn("Execution of query was interrupted: %s", sql);
            } catch (ExecutionException e) {
                throw SqlExceptionUtils.handle(e.getCause());
            }
        }
        return false;
    }

    @Override
    public void addBatch() throws SQLException {
        ensureOpen();

        ensureParams();
        batch.add(value);
        clearParameters();
    }

    @Override
    public void clearBatch() throws SQLException {
        ensureOpen();

        this.batch.clear();
    }

    @Override
    public void setArray(int parameterIndex, Array x) throws SQLException {
        throw SqlExceptionUtils.clientError(ERROR_SET_PARAM);
    }

    @Override
    public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException {
        throw SqlExceptionUtils.clientError(ERROR_SET_PARAM);
    }

    @Override
    public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException {
        throw SqlExceptionUtils.clientError(ERROR_SET_PARAM);
    }

    @Override
    public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException {
        throw SqlExceptionUtils.clientError(ERROR_SET_PARAM);
    }

    @Override
    public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException {
        ensureOpen();

        value = ClickHouseInputStream.empty();
    }

    @Override
    public ParameterMetaData getParameterMetaData() throws SQLException {
        return paramMetaData;
    }

    @Override
    public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) throws SQLException {
        setObject(parameterIndex, x);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy