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

com.yandex.ydb.jdbc.impl.YdbPreparedStatementImpl Maven / Gradle / Ivy

There is a newer version: 1.45.6
Show newest version
package com.yandex.ydb.jdbc.impl;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

import javax.annotation.Nullable;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.yandex.ydb.jdbc.YdbParameterMetaData;
import com.yandex.ydb.jdbc.YdbTypes;
import com.yandex.ydb.jdbc.exception.YdbExecutionException;
import com.yandex.ydb.jdbc.settings.YdbOperationProperties;
import com.yandex.ydb.table.Session;
import com.yandex.ydb.table.query.Params;
import com.yandex.ydb.table.values.ListType;
import com.yandex.ydb.table.values.ListValue;
import com.yandex.ydb.table.values.StructType;
import com.yandex.ydb.table.values.Type;
import com.yandex.ydb.table.values.Value;

import static com.yandex.ydb.jdbc.YdbConst.BATCH_INVALID;
import static com.yandex.ydb.jdbc.YdbConst.DEFAULT_BATCH_PARAMETER;
import static com.yandex.ydb.jdbc.YdbConst.INDEXED_PARAMETER_PREFIX;
import static com.yandex.ydb.jdbc.YdbConst.PARAMETER_TYPE_UNKNOWN;
import static com.yandex.ydb.jdbc.YdbConst.TRY_EXECUTE_ON_BATCH_STATEMENT;
import static com.yandex.ydb.jdbc.YdbConst.UNKNOWN_PARAMETER_IN_BATCH;
import static com.yandex.ydb.jdbc.YdbConst.UNSUPPORTED_QUERY_TYPE_IN_PS;
import static com.yandex.ydb.jdbc.YdbConst.VARIABLE_PARAMETER_PREFIX;

// This implementation support all possible scenarios with batched and simple execution mode
// It's a default configuration for all queries
public class YdbPreparedStatementImpl extends AbstractYdbPreparedStatementImpl {

    private final YdbStandardSqlTranslator sqlTranslator = new YdbStandardSqlTranslator();
    private final MutableState state = new MutableState();
    private final boolean enforceVariablePrefix;
    private final boolean transformStandardJdbcQueries;
    private final Cache queryCache;

    public YdbPreparedStatementImpl(YdbConnectionImpl connection,
                                    int resultSetType,
                                    String query) throws SQLException {
        super(connection, resultSetType, query);

        YdbOperationProperties properties = connection.getYdbProperties();
        this.enforceVariablePrefix = properties.isEnforceVariablePrefix();
        this.transformStandardJdbcQueries = properties.isTransformStandardJdbcQueries();
        int cacheSize = properties.getTransformedJdbcQueriesCache();
        if (transformStandardJdbcQueries && cacheSize > 0) {
            this.queryCache = CacheBuilder.newBuilder()
                    .maximumSize(cacheSize)
                    .build();
        } else {
            this.queryCache = null;
        }
        this.clearParameters();
    }

    @Override
    public void clearParameters() {
        this.state.params = new LinkedHashMap<>(this.state.descriptions.size());
    }

    @Override
    protected void afterExecute() {
        this.clearBatch();
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    @Override
    public void addBatch() throws SQLException {
        QueryType queryType = getQueryType();
        if (queryType != QueryType.DATA_QUERY) {
            throw new SQLException(BATCH_INVALID + queryType);
        }
        initBatchStruct();

        // All values are passed in sorted order
        state.batch.add(state.batchStruct.newValue((Map) state.params));
        this.clearParameters();
    }

    @Override
    public void clearBatch() {
        state.batch.clear();
        state.executeBatch = false;
        this.clearParameters();
    }

    @Override
    public int[] executeBatch() throws SQLException {
        int batchSize = state.batch.size();
        if (batchSize == 0) {
            return new int[0];
        }
        state.executeBatch = true;
        super.execute();
        int[] ret = new int[batchSize];
        Arrays.fill(ret, SUCCESS_NO_INFO);
        return ret;
    }

    @Override
    public YdbParameterMetaData getParameterMetaData() {
        if (enforceVariablePrefix) {
            Map descriptions = new LinkedHashMap<>(state.descriptions.size());
            for (Map.Entry entry : state.descriptions.entrySet()) {
                String name = entry.getKey();
                String parameterName;
                if (!name.startsWith(VARIABLE_PARAMETER_PREFIX)) {
                    parameterName = VARIABLE_PARAMETER_PREFIX + name;
                } else {
                    parameterName = name;
                }
                descriptions.put(parameterName, entry.getValue());
            }
            return new YdbParameterMetaDataImpl(descriptions);
        } else {
            return new YdbParameterMetaDataImpl(state.descriptions);
        }
    }

    @Override
    protected void setImpl(String parameterName, @Nullable Object value,
                           int sqlType, @Nullable String typeName, @Nullable Type type)
            throws SQLException {
        TypeDescription description = getParameter(parameterName, value, sqlType, typeName, type);
        Value ydbValue = getValue(parameterName, description, value);
        state.params.put(parameterName, ydbValue);
    }

    @Override
    protected void setImpl(int parameterIndex, @Nullable Object value,
                           int sqlType, @Nullable String typeName, @Nullable Type type)
            throws SQLException {
        setImpl(INDEXED_PARAMETER_PREFIX + parameterIndex, value, sqlType, typeName, type);
    }

    //

    @Override
    protected Params getParams() throws SQLException {
        if (state.batch.isEmpty()) {
            // Single params
            if (enforceVariablePrefix) {
                Params params = Params.create(state.params.size());
                for (Map.Entry> entry : state.params.entrySet()) {
                    String name = entry.getKey();
                    String parameterName;
                    if (!name.startsWith(VARIABLE_PARAMETER_PREFIX)) {
                        parameterName = VARIABLE_PARAMETER_PREFIX + name;
                    } else {
                        parameterName = name;
                    }
                    params.put(parameterName, entry.getValue());
                }
                return params;
            } else {
                return Params.copyOf(state.params);
            }

        } else {
            if (!state.executeBatch) {
                throw new YdbExecutionException(TRY_EXECUTE_ON_BATCH_STATEMENT);
            }

            // Batch params
            ListValue listValue = state.batchList.newValue(state.batch);
            return Params.of(DEFAULT_BATCH_PARAMETER, listValue);
        }
    }

    @Override
    protected boolean executeImpl() throws SQLException {
        QueryType queryType = getQueryType();
        switch (queryType) {
            case DATA_QUERY:
                Params sqlParams = getParams();
                String sql = transformStandardJdbcStatement(getQuery(), sqlParams);
                Session session = getConnection().getYdbSession();
                return executeDataQueryImpl(
                        sqlParams,
                        params -> QueryType.DATA_QUERY + " >>\n" + sql +
                                "\n\nParams: " + paramsToString(params),
                        (tx, params, execParams) -> session.executeDataQuery(sql, tx, params, execParams));
            case SCAN_QUERY:
                return executeScanQueryImpl();
            default:
                throw new SQLException(UNSUPPORTED_QUERY_TYPE_IN_PS + queryType);
        }
    }

    //

    private String transformStandardJdbcStatement(String sql, Params params) throws SQLException {
        // Make an automatic transformation of given prepared statement, replacing all '?' symbols with parameters
        // Supports both standard and batched queries
        if (!transformStandardJdbcQueries) {
            return sql;
        }
        if (queryCache != null) {
            try {
                return queryCache.get(new Key(sql, params), () -> sqlTranslator.translate(sql, params));
            } catch (ExecutionException e) {
                throw new SQLException("Unexpected exception when building standard JDBC statement: " +
                        e.getMessage(), e);
            }
        } else {
            return sqlTranslator.translate(sql, params);
        }
    }

    //

    private TypeDescription getParameter(String name,
                                         @Nullable Object value,
                                         int sqlType,
                                         @Nullable String typeName,
                                         @Nullable Type type) throws YdbExecutionException {
        TypeDescription description = state.descriptions.get(name);
        if (description != null) {
            return description;
        }
        if (!state.batch.isEmpty()) {
            throw new YdbExecutionException(UNKNOWN_PARAMETER_IN_BATCH + name);
        }
        Type actualType = toYdbType(name, value, sqlType, typeName, type);
        TypeDescription newDescription = TypeDescription.of(actualType);
        state.descriptions.put(name, newDescription);
        return newDescription;

    }

    private Type toYdbType(String parameterName,
                           @Nullable Object value,
                           int sqlType,
                           @Nullable String typeName,
                           @Nullable Type type)
            throws YdbExecutionException {
        if (value instanceof Value) {
            Value actualValue = (Value) value;
            return actualValue.getType();
        }
        if (type != null) {
            return type;
        }

        YdbTypes types = getConnection().getYdbTypes();
        Type actualType = null;
        if (typeName != null) {
            actualType = types.toYdbType(typeName);
        }
        if (actualType == null) {
            actualType = types.toYdbType(sqlType);
        }
        if (actualType == null && value != null) {
            actualType = types.toYdbType(value.getClass());
        }
        // All types are optional when detected from sqlType
        if (actualType == null) {
            throw new YdbExecutionException(String.format(PARAMETER_TYPE_UNKNOWN,
                    sqlType, typeName, parameterName));
        }
        return actualType.makeOptional();
    }

    private void initBatchStruct() {
        if (state.batchStruct == null) {
            Map typeMap = new LinkedHashMap<>(state.params.size());
            for (Map.Entry> entry : state.params.entrySet()) {
                typeMap.put(entry.getKey(), entry.getValue().getType());
            }
            state.batchStruct = StructType.of(typeMap);
            state.batchList = ListType.of(state.batchStruct);
        }
    }

    @SuppressWarnings("rawtypes")
    private static class MutableState {
        // Batches
        private final List batch = new ArrayList<>();
        private StructType batchStruct;
        private ListType batchList;
        private boolean executeBatch;

        // All known descriptions
        private final Map descriptions = new LinkedHashMap<>();
        private Map> params;
    }


    private static class Key {
        private final String query;
        private final List types;

        private Key(String query, Params params) {
            this.query = query;
            this.types = params.values().values().stream()
                    .map(Value::getType)
                    .collect(Collectors.toList());
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (!(o instanceof Key)) {
                return false;
            }
            Key key = (Key) o;
            return Objects.equals(query, key.query) &&
                    Objects.equals(types, key.types);
        }

        @Override
        public int hashCode() {
            return Objects.hash(query, types);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy