forked from Limework/skript-db
		
	Added option to provide PreparedStatement-like query argument binding
This commit is contained in:
		
							parent
							
								
									52d111dbfa
								
							
						
					
					
						commit
						110f3982ad
					
				@ -25,6 +25,7 @@ import java.util.*;
 | 
				
			|||||||
import java.util.concurrent.CompletableFuture;
 | 
					import java.util.concurrent.CompletableFuture;
 | 
				
			||||||
import java.util.concurrent.ExecutorService;
 | 
					import java.util.concurrent.ExecutorService;
 | 
				
			||||||
import java.util.concurrent.Executors;
 | 
					import java.util.concurrent.Executors;
 | 
				
			||||||
 | 
					import java.util.regex.Pattern;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Executes a statement on a database and optionally stores the result in a variable. Expressions
 | 
					 * Executes a statement on a database and optionally stores the result in a variable. Expressions
 | 
				
			||||||
@ -45,17 +46,20 @@ import java.util.concurrent.Executors;
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
public class EffExecuteStatement extends Effect {
 | 
					public class EffExecuteStatement extends Effect {
 | 
				
			||||||
    private static final ExecutorService threadPool = Executors.newFixedThreadPool(SkriptDB.getInstance().getConfig().getInt("thread-pool-size", 10));
 | 
					    private static final ExecutorService threadPool = Executors.newFixedThreadPool(SkriptDB.getInstance().getConfig().getInt("thread-pool-size", 10));
 | 
				
			||||||
 | 
					    private static final Pattern ARGUMENT_PLACEHOLDER = Pattern.compile("(?<!\\\\)\\?");
 | 
				
			||||||
    static String lastError;
 | 
					    static String lastError;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static {
 | 
					    static {
 | 
				
			||||||
        Skript.registerEffect(EffExecuteStatement.class,
 | 
					        Skript.registerEffect(EffExecuteStatement.class,
 | 
				
			||||||
                "execute %string% (in|on) %datasource% " +
 | 
					                "execute %string% (in|on) %datasource% " +
 | 
				
			||||||
                        "[and store [[the] (output|result)[s]] (to|in) [the] [var[iable]] %-objects%]", "quickly execute %string% (in|on) %datasource% " +
 | 
					                        "[with arg[ument][s] %-objects%] [and store [[the] (output|result)[s]] (to|in) [the] [var[iable]] %-objects%]",
 | 
				
			||||||
                        "[and store [[the] (output|result)[s]] (to|in) [the] [var[iable]] %-objects%]");
 | 
					                "quickly execute %string% (in|on) %datasource% " +
 | 
				
			||||||
 | 
					                        "[with arg[ument][s] %-objects%] [and store [[the] (output|result)[s]] (to|in) [the] [var[iable]] %-objects%]");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private Expression<String> query;
 | 
					    private Expression<String> query;
 | 
				
			||||||
    private Expression<HikariDataSource> dataSource;
 | 
					    private Expression<HikariDataSource> dataSource;
 | 
				
			||||||
 | 
					    private Expression<Object> queryArguments;
 | 
				
			||||||
    private VariableString var;
 | 
					    private VariableString var;
 | 
				
			||||||
    private boolean isLocal;
 | 
					    private boolean isLocal;
 | 
				
			||||||
    private boolean isList;
 | 
					    private boolean isList;
 | 
				
			||||||
@ -157,142 +161,144 @@ public class EffExecuteStatement extends Effect {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private Pair<String, List<Object>> parseQuery(Event e) {
 | 
					    private Pair<String, List<Object>> parseQuery(Event e) {
 | 
				
			||||||
        if (!(query instanceof VariableString)) {
 | 
					        if (queryArguments != null) {
 | 
				
			||||||
            return new Pair<>(query.getSingle(e), null);
 | 
					            Object[] args = queryArguments.getArray(e);
 | 
				
			||||||
        }
 | 
					            String queryString = query.getSingle(e);
 | 
				
			||||||
        VariableString q = (VariableString) query;
 | 
					            int queryArgCount = (int) ARGUMENT_PLACEHOLDER.matcher(queryString).results().count();
 | 
				
			||||||
        if (q.isSimple()) {
 | 
					            if (queryArgCount != args.length) {
 | 
				
			||||||
            return new Pair<>(q.toString(e), null);
 | 
					                Skript.warning("Your query has %d question marks, but you provided %d arguments.");
 | 
				
			||||||
 | 
					                args = Arrays.copyOf(args, queryArgCount);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return new Pair<>(query.getSingle(e), List.of(args));
 | 
				
			||||||
 | 
					        } else if (query instanceof VariableString && !((VariableString) query).isSimple()) {
 | 
				
			||||||
 | 
					            return parseVariableQuery(e, (VariableString) query);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        return new Pair<>(query.getSingle(e), null);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
					    private Pair<String, List<Object>> parseVariableQuery(Event e, VariableString varQuery) {
 | 
				
			||||||
        StringBuilder sb = new StringBuilder();
 | 
					        StringBuilder sb = new StringBuilder();
 | 
				
			||||||
        List<Object> parameters = new ArrayList<>();
 | 
					        List<Object> parameters = new LinkedList<>();
 | 
				
			||||||
        Object[] objects = SkriptUtil.getTemplateString(q);
 | 
					        Object[] objects = SkriptUtil.getTemplateString(varQuery);
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        for (int i = 0; i < objects.length; i++) {
 | 
					        for (int i = 0; i < objects.length; i++) {
 | 
				
			||||||
            Object o = objects[i];
 | 
					            if (objects[i] instanceof String) {
 | 
				
			||||||
            if (o instanceof String) {
 | 
					                sb.append(objects[i]);
 | 
				
			||||||
                sb.append(o);
 | 
					 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                Expression<?> expr;
 | 
					                Expression<?> expr = objects[i] instanceof Expression ? (Expression<?>) objects[i] : SkriptUtil.getExpressionFromInfo(objects[i]);
 | 
				
			||||||
                if (o instanceof Expression)
 | 
					                boolean standaloneString = isStandaloneString(objects, i);
 | 
				
			||||||
                    expr = (Expression<?>) o;
 | 
					 | 
				
			||||||
                else
 | 
					 | 
				
			||||||
                    expr = SkriptUtil.getExpressionFromInfo(o);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                String before = getString(objects, i - 1);
 | 
					 | 
				
			||||||
                String after = getString(objects, i + 1);
 | 
					 | 
				
			||||||
                boolean standaloneString = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (before != null && after != null) {
 | 
					 | 
				
			||||||
                    if (before.endsWith("'") && after.endsWith("'")) {
 | 
					 | 
				
			||||||
                        standaloneString = true;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                Object expressionValue = expr.getSingle(e);
 | 
					                Object expressionValue = expr.getSingle(e);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (expr instanceof ExprUnsafe) {
 | 
					                Pair<String, Object> toAppend = parseExpressionQuery(expr, expressionValue, standaloneString);
 | 
				
			||||||
                    sb.append(expressionValue);
 | 
					                sb.append(toAppend.getFirst());
 | 
				
			||||||
 | 
					                if (toAppend.getSecond() != null) {
 | 
				
			||||||
                    if (standaloneString && expressionValue instanceof String) {
 | 
					                    parameters.add(toAppend.getSecond());
 | 
				
			||||||
                        String rawExpression = ((ExprUnsafe) expr).getRawExpression();
 | 
					 | 
				
			||||||
                        Skript.warning(
 | 
					 | 
				
			||||||
                                String.format("Unsafe may have been used unnecessarily. Try replacing 'unsafe %1$s' with %1$s",
 | 
					 | 
				
			||||||
                                        rawExpression));
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    parameters.add(expressionValue);
 | 
					 | 
				
			||||||
                    sb.append('?');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    if (standaloneString) {
 | 
					 | 
				
			||||||
                        Skript.warning("Do not surround expressions with quotes!");
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return new Pair<>(sb.toString(), parameters);
 | 
					        return new Pair<>(sb.toString(), parameters);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
					    private Pair<String, Object> parseExpressionQuery(Expression<?> expr, Object expressionValue, boolean standaloneString) {
 | 
				
			||||||
 | 
					        if (expr instanceof ExprUnsafe) {
 | 
				
			||||||
 | 
					            if (standaloneString && expressionValue instanceof String) {
 | 
				
			||||||
 | 
					                Skript.warning(
 | 
				
			||||||
 | 
					                        String.format("Unsafe may have been used unnecessarily. Try replacing 'unsafe %1$s' with %1$s",
 | 
				
			||||||
 | 
					                                ((ExprUnsafe) expr).getRawExpression()));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return new Pair<>((String) expressionValue, null);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            if (standaloneString) {
 | 
				
			||||||
 | 
					                Skript.warning("Do not surround expressions with quotes!");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return new Pair<>("?", expressionValue);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private Object executeStatement(DataSource ds, String baseVariable, Pair<String, List<Object>> query) {
 | 
					    private Object executeStatement(DataSource ds, String baseVariable, Pair<String, List<Object>> query) {
 | 
				
			||||||
        if (ds == null) {
 | 
					        if (ds == null) {
 | 
				
			||||||
            return "Data source is not set";
 | 
					            return "Data source is not set";
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        Map<String, Object> variableList = new HashMap<>();
 | 
					        try (Connection conn = ds.getConnection()) {
 | 
				
			||||||
        try (Connection conn = ds.getConnection();
 | 
					            try (PreparedStatement stmt = createStatement(conn, query)) {
 | 
				
			||||||
             PreparedStatement stmt = createStatement(conn, query)) {
 | 
					                boolean hasResultSet = stmt.execute();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            boolean hasResultSet = stmt.execute();
 | 
					                if (baseVariable != null) {
 | 
				
			||||||
 | 
					                    return processBaseVariable(baseVariable, stmt, hasResultSet);
 | 
				
			||||||
            if (baseVariable != null) {
 | 
					 | 
				
			||||||
                if (isList) {
 | 
					 | 
				
			||||||
                    baseVariable = baseVariable.substring(0, baseVariable.length() - 1);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (hasResultSet) {
 | 
					 | 
				
			||||||
                    CachedRowSet crs = SkriptDB.getRowSetFactory().createCachedRowSet();
 | 
					 | 
				
			||||||
                    crs.populate(stmt.getResultSet());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    if (isList) {
 | 
					 | 
				
			||||||
                        ResultSetMetaData meta = crs.getMetaData();
 | 
					 | 
				
			||||||
                        int columnCount = meta.getColumnCount();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        for (int i = 1; i <= columnCount; i++) {
 | 
					 | 
				
			||||||
                            String label = meta.getColumnLabel(i);
 | 
					 | 
				
			||||||
                            variableList.put(baseVariable + label, label);
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        int rowNumber = 1;
 | 
					 | 
				
			||||||
                        try {
 | 
					 | 
				
			||||||
                            while (crs.next()) {
 | 
					 | 
				
			||||||
                                for (int i = 1; i <= columnCount; i++) {
 | 
					 | 
				
			||||||
                                    variableList.put(baseVariable + meta.getColumnLabel(i).toLowerCase(Locale.ENGLISH)
 | 
					 | 
				
			||||||
                                            + Variable.SEPARATOR + rowNumber, crs.getObject(i));
 | 
					 | 
				
			||||||
                                }
 | 
					 | 
				
			||||||
                                rowNumber++;
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                        } catch (SQLException ex) {
 | 
					 | 
				
			||||||
                            return ex.getMessage();
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    } else {
 | 
					 | 
				
			||||||
                        crs.last();
 | 
					 | 
				
			||||||
                        variableList.put(baseVariable, crs.getRow());
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                } else if (!isList) {
 | 
					 | 
				
			||||||
                    //if no results are returned and the specified variable isn't a list variable, put the affected rows count in the variable
 | 
					 | 
				
			||||||
                    variableList.put(baseVariable, stmt.getUpdateCount());
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					                return Map.of();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } catch (SQLException ex) {
 | 
					        } catch (SQLException ex) {
 | 
				
			||||||
            return ex.getMessage();
 | 
					            return ex.getMessage();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    private Object processBaseVariable(String baseVariable, PreparedStatement stmt, boolean hasResultSet) throws SQLException {
 | 
				
			||||||
 | 
					        Map<String, Object> variableList = new HashMap<>();
 | 
				
			||||||
 | 
					        if (isList) {
 | 
				
			||||||
 | 
					            baseVariable = baseVariable.substring(0, baseVariable.length() - 1);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (hasResultSet) {
 | 
				
			||||||
 | 
					            CachedRowSet crs = SkriptDB.getRowSetFactory().createCachedRowSet();
 | 
				
			||||||
 | 
					            crs.populate(stmt.getResultSet());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (isList) {
 | 
				
			||||||
 | 
					                return fetchQueryResultSet(crs, baseVariable);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                crs.last();
 | 
				
			||||||
 | 
					                variableList.put(baseVariable, crs.getRow());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else if (!isList) {
 | 
				
			||||||
 | 
					            //if no results are returned and the specified variable isn't a list variable, put the affected rows count in the variable
 | 
				
			||||||
 | 
					            return Map.of(baseVariable, stmt.getUpdateCount());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return Map.of();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    private Map<String, Object> fetchQueryResultSet(CachedRowSet crs, String baseVariable) throws SQLException {
 | 
				
			||||||
 | 
					        Map<String, Object> variableList = new HashMap<>();
 | 
				
			||||||
 | 
					        ResultSetMetaData meta = crs.getMetaData();
 | 
				
			||||||
 | 
					        int columnCount = meta.getColumnCount();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (int i = 1; i <= columnCount; i++) {
 | 
				
			||||||
 | 
					            String label = meta.getColumnLabel(i);
 | 
				
			||||||
 | 
					            variableList.put(baseVariable + label, label);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        int rowNumber = 1;
 | 
				
			||||||
 | 
					        while (crs.next()) {
 | 
				
			||||||
 | 
					            for (int i = 1; i <= columnCount; i++) {
 | 
				
			||||||
 | 
					                variableList.put(baseVariable + meta.getColumnLabel(i).toLowerCase(Locale.ENGLISH)
 | 
				
			||||||
 | 
					                        + Variable.SEPARATOR + rowNumber, crs.getObject(i));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            rowNumber++;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        return variableList;
 | 
					        return variableList;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private PreparedStatement createStatement(Connection conn, Pair<String, List<Object>> query) throws SQLException {
 | 
					    private PreparedStatement createStatement(Connection conn, Pair<String, List<Object>> query) throws SQLException {
 | 
				
			||||||
        PreparedStatement stmt = conn.prepareStatement(query.getFirst());
 | 
					        PreparedStatement stmt = conn.prepareStatement(query.getFirst());
 | 
				
			||||||
        List<Object> parameters = query.getSecond();
 | 
					        if (query.getSecond() != null) {
 | 
				
			||||||
 | 
					            Iterator<Object> iter = query.getSecond().iterator();
 | 
				
			||||||
        if (parameters != null) {
 | 
					            for (int i = 1; iter.hasNext(); i++) {
 | 
				
			||||||
            for (int i = 0; i < parameters.size(); i++) {
 | 
					                stmt.setObject(i, iter.next());
 | 
				
			||||||
                stmt.setObject(i + 1, parameters.get(i));
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return stmt;
 | 
					        return stmt;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
					    private boolean isStandaloneString(Object[] objects, int index) {
 | 
				
			||||||
 | 
					        String before = getString(objects, index - 1);
 | 
				
			||||||
 | 
					        String after = getString(objects, index + 1);
 | 
				
			||||||
 | 
					        return before != null && before.endsWith("'") && after != null && after.endsWith("'");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private String getString(Object[] objects, int index) {
 | 
					    private String getString(Object[] objects, int index) {
 | 
				
			||||||
        if (index < 0 || index >= objects.length) {
 | 
					        if (index >= 0 && index < objects.length && objects[index] instanceof String) {
 | 
				
			||||||
            return null;
 | 
					            return (String) objects[index];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        Object object = objects[index];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (object instanceof String) {
 | 
					 | 
				
			||||||
            return (String) object;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return null;
 | 
					        return null;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -334,15 +340,21 @@ public class EffExecuteStatement extends Effect {
 | 
				
			|||||||
            return false;
 | 
					            return false;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        dataSource = (Expression<HikariDataSource>) exprs[1];
 | 
					        dataSource = (Expression<HikariDataSource>) exprs[1];
 | 
				
			||||||
        Expression<?> expr = exprs[2];
 | 
					        if (exprs[2] != null) {
 | 
				
			||||||
 | 
					            if (query instanceof VariableString && !((VariableString) query).isSimple()) {
 | 
				
			||||||
 | 
					                Skript.warning("Your query string contains expresions, but you've also provided query arguments. Consider using `unsafe` keyword before your query.");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            queryArguments = (Expression<Object>) exprs[2];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Expression<?> resultHolder = exprs[3];
 | 
				
			||||||
        quickly = matchedPattern == 1;
 | 
					        quickly = matchedPattern == 1;
 | 
				
			||||||
        if (expr instanceof Variable) {
 | 
					        if (resultHolder instanceof Variable) {
 | 
				
			||||||
            Variable<?> varExpr = (Variable<?>) expr;
 | 
					            Variable<?> varExpr = (Variable<?>) resultHolder;
 | 
				
			||||||
            var = varExpr.getName();
 | 
					            var = varExpr.getName();
 | 
				
			||||||
            isLocal = varExpr.isLocal();
 | 
					            isLocal = varExpr.isLocal();
 | 
				
			||||||
            isList = varExpr.isList();
 | 
					            isList = varExpr.isList();
 | 
				
			||||||
        } else if (expr != null) {
 | 
					        } else if (resultHolder != null) {
 | 
				
			||||||
            Skript.error(expr + " is not a variable");
 | 
					            Skript.error(resultHolder + " is not a variable");
 | 
				
			||||||
            return false;
 | 
					            return false;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return true;
 | 
					        return true;
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user