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

se.fortnox.reactivewizard.db.query.ParameterizedQuery Maven / Gradle / Ivy

There is a newer version: 24.6.0
Show newest version
package se.fortnox.reactivewizard.db.query;

import se.fortnox.reactivewizard.db.query.parts.CollectionOptionsQueryPart;
import se.fortnox.reactivewizard.db.query.parts.DynamicQueryPart;
import se.fortnox.reactivewizard.db.query.parts.ParamQueryPart;
import se.fortnox.reactivewizard.db.query.parts.QueryPart;
import se.fortnox.reactivewizard.db.query.parts.StaticQueryPart;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.PreparedStatement;
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.regex.Matcher;
import java.util.regex.Pattern;

public class ParameterizedQuery {

    public static final Pattern NAMED_PARAMETER_PATTERN = Pattern.compile("(? queryParts;
    private final String          sql;

    private final String methodName;

    public ParameterizedQuery(String sql, Method method) throws SQLException {
        this.sql = sql;
        this.queryParts = createQueryParts(sql, method);
        this.methodName =  String.format("%s::%s", method.getDeclaringClass().getPackage().getName(), method.getName());
    }

    private List createQueryParts(String sqlInput, Method method) throws SQLException {
        List               parts          = new ArrayList<>();
        Map queryArguments = createQueryArguments(method);

        sqlInput = sqlPreProcess(sqlInput);

        if (sqlInput.indexOf('?') != -1) {
            throw new RuntimeException("Unnamed parameters are not supported: " + sqlInput);
        }

        int     pos     = 0;
        Matcher matcher = NAMED_PARAMETER_PATTERN.matcher(sqlInput);
        if (matcher.find()) {
            do {
                parts.add(new StaticQueryPart(sqlInput.substring(pos, matcher.start(1) - 1)));
                String paramName = matcher.group(1);
                parts.add(getDynamicQueryPart(paramName, queryArguments));
                pos = matcher.end(1);
            }
            while (matcher.find());
        }
        parts.add(new StaticQueryPart(sqlInput.substring(pos, sqlInput.length())));
        parts.add(new CollectionOptionsQueryPart(method));

        return parts;
    }

    private Map createQueryArguments(Method method) throws SQLException {
        Type[]                        parameterTypes = method.getGenericParameterTypes();
        Parameter[]                   parameters     = method.getParameters();
        Map queryArguments = new LinkedHashMap<>();
        for (int i = 0; i < parameters.length; i++) {
            String name = parameters[i].isNamePresent() ? parameters[i].getName() : "param" + i;
            queryArguments.put(name, createParamQueryPart(i, parameterTypes[i]));
        }
        return queryArguments;
    }

    protected DynamicQueryPart createParamQueryPart(int parameterIndex, Type parameterType) throws SQLException {
        return new ParamQueryPart(parameterIndex, parameterType);
    }

    protected DynamicQueryPart getDynamicQueryPart(String paramName, Map queryArguments) throws SQLException {
        String[] paramNameParts = paramName.split("\\.");
        String[] subPath        = Arrays.copyOfRange(paramNameParts, 1, paramNameParts.length);

        DynamicQueryPart queryPart = queryArguments.get(paramNameParts[0]);
        if (queryPart == null) {
            throw new RuntimeException("Query contains placeholder \"" + paramNameParts[0] + "\" but method noes not have such argument");
        }

        if (subPath.length > 0) {
            queryPart = queryPart.subPath(subPath);
        }

        return queryPart;
    }

    public PreparedStatement createStatement(Connection connection, Object[] arguments) throws SQLException {
        return createStatement(connection, arguments, null);
    }

    /**
     * Create prepared statement.
     * @param connection the connection
     * @param arguments the arguments
     * @param options the options
     * @return the prepared statement
     * @throws SQLException on error
     */
    public PreparedStatement createStatement(Connection connection, Object[] arguments, Integer options)
        throws SQLException {
        StringBuilder sql = new StringBuilder();
        for (QueryPart part : queryParts) {
            part.visit(sql, arguments);
        }

        return createPreparedStatement(connection, options, sql.toString());
    }

    /**
     * Add parameters from prepared statement.
     * @param args the arguments
     * @param preparedStatement the prepared statement
     * @throws SQLException on error
     */
    public void addParameters(Object[] args, PreparedStatement preparedStatement) throws SQLException {
        PreparedStatementParameters parameters = new PreparedStatementParameters(preparedStatement);
        for (QueryPart part : queryParts) {
            part.addParams(parameters, args);
        }
    }

    private PreparedStatement createPreparedStatement(Connection connection, Integer options, String sql) throws SQLException {
        if (options == null) {
            return connection.prepareStatement(sql);
        }
        return connection.prepareStatement(sql, options);
    }

    protected String sqlPreProcess(String sqlInp) {
        return sqlInp
            .replaceAll("(?i) NOT IN\\s*\\(", " !=ALL\\(")
            .replaceAll("(?i) IN\\s*\\(", " =ANY\\(");
    }

    @Override
    public String toString() {
        return sql;
    }

    public String getMethodName() {
        return methodName;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy