From 2dc1f70f4fa2511d2d496638739a6020d28e4272 Mon Sep 17 00:00:00 2001 From: szumielxd <43210079+szumielxd@users.noreply.github.com> Date: Wed, 24 Jul 2024 14:08:08 +0200 Subject: [PATCH 1/4] Removed some duplicated code and improved readability --- .../skriptdb/skript/EffExecuteStatement.java | 153 +++++++++--------- 1 file changed, 73 insertions(+), 80 deletions(-) diff --git a/src/main/java/com/btk5h/skriptdb/skript/EffExecuteStatement.java b/src/main/java/com/btk5h/skriptdb/skript/EffExecuteStatement.java index 9a935b2..70ca721 100644 --- a/src/main/java/com/btk5h/skriptdb/skript/EffExecuteStatement.java +++ b/src/main/java/com/btk5h/skriptdb/skript/EffExecuteStatement.java @@ -51,104 +51,79 @@ public class EffExecuteStatement extends Effect { static { Skript.registerEffect(EffExecuteStatement.class, - "execute %string% (in|on) %datasource% " + - "[with arg[ument][s] %-objects%] [and store [[the] (output|result)[s]] (to|in) [the] [var[iable]] %-objects%]", - "quickly execute %string% (in|on) %datasource% " + + "[quickly: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 query; private Expression dataSource; private Expression queryArguments; - private VariableString var; + private VariableString resultVariableName; private boolean isLocal; private boolean isList; private boolean quickly; private boolean isSync = false; - private void continueScriptExecution(Event e, Object populatedVariables) { - lastError = null; - if (populatedVariables instanceof String) { - lastError = (String) populatedVariables; - } else { - - if (getNext() != null) { - ((Map) populatedVariables).forEach((name, value) -> setVariable(e, name, value)); - } - } - TriggerItem.walk(getNext(), e); - } - @Override protected void execute(Event e) { DataSource ds = dataSource.getSingle(e); - Pair> query = parseQuery(e); - String baseVariable = var != null ? var.toString(e).toLowerCase(Locale.ENGLISH) : null; //if data source isn't set - if (ds == null) return; + if (ds == null) { + return; + } + Pair> parsedQuery = parseQuery(e); + String baseVariable = resultVariableName != null ? resultVariableName.toString(e).toLowerCase(Locale.ENGLISH) : null; + Object locals = Variables.removeLocals(e); //execute SQL statement if (Bukkit.isPrimaryThread()) { - CompletableFuture sql = CompletableFuture.supplyAsync(() -> executeStatement(ds, baseVariable, query), threadPool); - sql.whenComplete((res, err) -> { - if (err != null) { - err.printStackTrace(); - } - //handle last error syntax data - lastError = null; - if (res instanceof String) { - lastError = (String) res; - } - //if local variables are present - //bring back local variables - //populate SQL data into variables - if (!quickly) { - Bukkit.getScheduler().runTask(SkriptDB.getInstance(), () -> { - if (locals != null && getNext() != null) { - Variables.setLocalVariables(e, locals); - } - if (!(res instanceof String)) { - ((Map) res).forEach((name, value) -> setVariable(e, name, value)); - } - TriggerItem.walk(getNext(), e); - //the line below is required to prevent memory leaks - Variables.removeLocals(e); - }); - } else { - if (locals != null && getNext() != null) { - Variables.setLocalVariables(e, locals); - } - if (!(res instanceof String)) { - ((Map) res).forEach((name, value) -> setVariable(e, name, value)); - } - TriggerItem.walk(getNext(), e); - //the line below is required to prevent memory leaks - Variables.removeLocals(e); - } - }); + CompletableFuture.supplyAsync(() -> executeStatement(ds, baseVariable, parsedQuery), threadPool) + .whenComplete((resources, err) -> { + //handle last error syntax data + resetLastSQLError(); + if (err instanceof SkriptDBQueryException) { + setLastSQLError(err.getMessage()); + } + //if local variables are present + //bring back local variables + //populate SQL data into variables + if (!quickly) { + Bukkit.getScheduler().runTask(SkriptDB.getInstance(), + () -> postExecution(e, locals, resources)); + } else { + postExecution(e, locals, resources); + } + }); // sync executed SQL query, same as above, just sync } else { isSync = true; - Object resources = executeStatement(ds, baseVariable, query); - //handle last error syntax data - lastError = null; - if (resources instanceof String) { - lastError = (String) resources; - } + Map resources = null; + try { + resources = executeStatement(ds, baseVariable, parsedQuery); + resetLastSQLError(); + } catch (SkriptDBQueryException err) { + //handle last error syntax data + setLastSQLError(err.getMessage()); + } //if local variables are present //bring back local variables //populate SQL data into variables - if (locals != null && getNext() != null) { - Variables.setLocalVariables(e, locals); - } - if (!(resources instanceof String)) { - ((Map) resources).forEach((name, value) -> setVariable(e, name, value)); - } - TriggerItem.walk(getNext(), e); - Variables.removeLocals(e); + postExecution(e, locals, resources); } } + + private void postExecution(Event e, Object locals, Map resources) { + if (locals != null && getNext() != null) { + Variables.setLocalVariables(e, locals); + } + if (resources != null) { + resources.forEach((name, value) -> setVariable(e, name, value)); + } + TriggerItem.walk(getNext(), e); + //the line below is required to prevent memory leaks + Variables.removeLocals(e); + } @Override protected TriggerItem walk(Event e) { @@ -215,9 +190,9 @@ public class EffExecuteStatement extends Effect { } } - private Object executeStatement(DataSource ds, String baseVariable, Pair> query) { + private Map executeStatement(DataSource ds, String baseVariable, Pair> query) throws SkriptDBQueryException { if (ds == null) { - return "Data source is not set"; + throw new SkriptDBQueryException("Data source is not set"); } try (Connection conn = ds.getConnection()) { try (PreparedStatement stmt = createStatement(conn, query)) { @@ -229,11 +204,11 @@ public class EffExecuteStatement extends Effect { return Map.of(); } } catch (SQLException ex) { - return ex.getMessage(); + throw new SkriptDBQueryException(ex.getMessage()); } } - private Object processBaseVariable(String baseVariable, PreparedStatement stmt, boolean hasResultSet) throws SQLException { + private Map processBaseVariable(String baseVariable, PreparedStatement stmt, boolean hasResultSet) throws SQLException { Map variableList = new HashMap<>(); if (isList) { baseVariable = baseVariable.substring(0, baseVariable.length() - 1); @@ -306,7 +281,7 @@ public class EffExecuteStatement extends Effect { //fix mediumblob and similar column types, so they return a String correctly if (obj != null) { - if (obj.getClass().getName().equals("[B")) { + if (obj instanceof byte[]) { obj = new String((byte[]) obj); //in some servers instead of being byte array, it appears as SerialBlob (depends on mc version, 1.12.2 is bvte array, 1.16.5 SerialBlob) @@ -320,6 +295,14 @@ public class EffExecuteStatement extends Effect { } Variables.setVariable(name.toLowerCase(Locale.ENGLISH), obj, e, isLocal); } + + private static void resetLastSQLError() { + lastError = null; + } + + private static void setLastSQLError(String error) { + lastError = error; + } @Override public String toString(Event e, boolean debug) { @@ -328,8 +311,7 @@ public class EffExecuteStatement extends Effect { @SuppressWarnings("unchecked") @Override - public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, - SkriptParser.ParseResult parseResult) { + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { Expression statementExpr = (Expression) exprs[0]; if (statementExpr instanceof VariableString || statementExpr instanceof ExprUnsafe) { query = statementExpr; @@ -347,10 +329,10 @@ public class EffExecuteStatement extends Effect { queryArguments = (Expression) exprs[2]; } Expression resultHolder = exprs[3]; - quickly = matchedPattern == 1; + quickly = parseResult.hasTag("quickly"); if (resultHolder instanceof Variable) { Variable varExpr = (Variable) resultHolder; - var = varExpr.getName(); + resultVariableName = varExpr.getName(); isLocal = varExpr.isLocal(); isList = varExpr.isList(); } else if (resultHolder != null) { @@ -359,4 +341,15 @@ public class EffExecuteStatement extends Effect { } return true; } + + public static class SkriptDBQueryException extends RuntimeException { + + private static final long serialVersionUID = -1869895286406538884L; + + public SkriptDBQueryException(String message) { + super(message); + } + + } + } \ No newline at end of file From 41881dbb7d54a3347e75154e62933fc8548be0e9 Mon Sep 17 00:00:00 2001 From: szumielxd <43210079+szumielxd@users.noreply.github.com> Date: Wed, 24 Jul 2024 14:31:49 +0200 Subject: [PATCH 2/4] Fixed missing 'string' field in Skript 2.8 --- .../java/com/btk5h/skriptdb/SkriptUtil.java | 98 +++++++++++-------- 1 file changed, 58 insertions(+), 40 deletions(-) diff --git a/src/main/java/com/btk5h/skriptdb/SkriptUtil.java b/src/main/java/com/btk5h/skriptdb/SkriptUtil.java index 86075cb..c95da2e 100644 --- a/src/main/java/com/btk5h/skriptdb/SkriptUtil.java +++ b/src/main/java/com/btk5h/skriptdb/SkriptUtil.java @@ -10,52 +10,70 @@ import java.util.Optional; public class SkriptUtil { - private static final Field STRING; - private static final Field EXPR; + private static final Field STRING; + private static final Field EXPR; - static { - Field _FIELD = null; - try { - _FIELD = VariableString.class.getDeclaredField("string"); - _FIELD.setAccessible(true); - } catch (NoSuchFieldException e) { - Skript.error("Skript's 'string' field could not be resolved."); - e.printStackTrace(); - } - STRING = _FIELD; + static { + STRING = tryGetOldStringField() + .or(() -> tryGetNewStringField()) + .orElseGet(() -> { + Skript.error("Skript's 'string' field could not be resolved."); + return null; + }); - try { - Optional> expressionInfo = Arrays.stream(VariableString.class.getDeclaredClasses()) - .filter(cls -> cls.getSimpleName().equals("ExpressionInfo")) - .findFirst(); - if (expressionInfo.isPresent()) { - Class expressionInfoClass = expressionInfo.get(); - _FIELD = expressionInfoClass.getDeclaredField("expr"); - _FIELD.setAccessible(true); - } else { - Skript.error("Skript's 'ExpressionInfo' class could not be resolved."); - } - } catch (NoSuchFieldException e) { - e.printStackTrace(); - Skript.error("Skript's 'expr' field could not be resolved."); + Field f = null; + try { + Optional> expressionInfo = Arrays.stream(VariableString.class.getDeclaredClasses()) + .filter(cls -> cls.getSimpleName().equals("ExpressionInfo")) + .findFirst(); + if (expressionInfo.isPresent()) { + Class expressionInfoClass = expressionInfo.get(); + f = expressionInfoClass.getDeclaredField("expr"); + f.setAccessible(true); + } else { + Skript.error("Skript's 'ExpressionInfo' class could not be resolved."); + } + } catch (NoSuchFieldException e) { + e.printStackTrace(); + Skript.error("Skript's 'expr' field could not be resolved."); + } + EXPR = f; } - EXPR = _FIELD; - } - public static Object[] getTemplateString(VariableString vs) { - try { - return (Object[]) STRING.get(vs); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); + public static Object[] getTemplateString(VariableString vs) { + try { + return (Object[]) STRING.get(vs); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } } - } - public static Expression getExpressionFromInfo(Object o) { - try { - return (Expression) EXPR.get(o); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); + public static Expression getExpressionFromInfo(Object o) { + try { + return (Expression) EXPR.get(o); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + private static Optional tryGetOldStringField() { + try { + Field f = VariableString.class.getDeclaredField("string"); + f.setAccessible(true); + return Optional.of(f); + } catch (NoSuchFieldException e) { + return Optional.empty(); + } + } + + private static Optional tryGetNewStringField() { + try { + Field f = VariableString.class.getDeclaredField("strings"); + f.setAccessible(true); + return Optional.of(f); + } catch (NoSuchFieldException e) { + return Optional.empty(); + } } - } } From 0670f88a4e0b23923f2e3cae9460d051d2796de6 Mon Sep 17 00:00:00 2001 From: szumielxd <43210079+szumielxd@users.noreply.github.com> Date: Wed, 24 Jul 2024 16:01:44 +0200 Subject: [PATCH 3/4] Code cleanup --- .../java/com/btk5h/skriptdb/SkriptUtil.java | 23 +++++++++++++++---- .../events/SQLQueryCompleteEvent.java | 20 ++++++---------- .../btk5h/skriptdb/skript/ExprDataSource.java | 8 +++---- .../btk5h/skriptdb/skript/ExprSQLQuery.java | 10 ++++---- .../com/btk5h/skriptdb/skript/ExprUnsafe.java | 3 +-- 5 files changed, 37 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/btk5h/skriptdb/SkriptUtil.java b/src/main/java/com/btk5h/skriptdb/SkriptUtil.java index c95da2e..f6bbf94 100644 --- a/src/main/java/com/btk5h/skriptdb/SkriptUtil.java +++ b/src/main/java/com/btk5h/skriptdb/SkriptUtil.java @@ -1,13 +1,17 @@ package com.btk5h.skriptdb; -import ch.njol.skript.Skript; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.VariableString; - import java.lang.reflect.Field; import java.util.Arrays; import java.util.Optional; +import org.bukkit.event.Event; + +import ch.njol.skript.ScriptLoader; +import ch.njol.skript.Skript; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.VariableString; +import ch.njol.skript.lang.parser.ParserInstance; + public class SkriptUtil { private static final Field STRING; @@ -56,6 +60,17 @@ public class SkriptUtil { } } + @SuppressWarnings("deprecation") + public static boolean isCurrentEvent(Class event) { + try { + Class.forName("ch.njol.skript.lang.parser.ParserInstance"); + return ParserInstance.get().isCurrentEvent(event); + } catch (ClassNotFoundException e) { + return ScriptLoader.isCurrentEvent(event); + } + } + + private static Optional tryGetOldStringField() { try { Field f = VariableString.class.getDeclaredField("string"); diff --git a/src/main/java/com/btk5h/skriptdb/events/SQLQueryCompleteEvent.java b/src/main/java/com/btk5h/skriptdb/events/SQLQueryCompleteEvent.java index 969582f..511bd28 100644 --- a/src/main/java/com/btk5h/skriptdb/events/SQLQueryCompleteEvent.java +++ b/src/main/java/com/btk5h/skriptdb/events/SQLQueryCompleteEvent.java @@ -4,32 +4,26 @@ import org.bukkit.event.Event; import org.bukkit.event.HandlerList; public class SQLQueryCompleteEvent extends Event { - private final static HandlerList HANDLERS = new HandlerList(); + + private static final HandlerList HANDLERS = new HandlerList(); + private final String argument; public SQLQueryCompleteEvent(String argument) { super(true); this.argument = argument; - // this.variables = variables; - } - - public static HandlerList getHandlerList() { - return HANDLERS; - } - - @Override - public String getEventName() { - return super.getEventName(); } @Override public HandlerList getHandlers() { - return HANDLERS; + return getHandlerList(); } public String getQuery() { return argument; } - // public String getVariables() {return;} + public static HandlerList getHandlerList() { + return HANDLERS; + } } diff --git a/src/main/java/com/btk5h/skriptdb/skript/ExprDataSource.java b/src/main/java/com/btk5h/skriptdb/skript/ExprDataSource.java index 945db22..22dec95 100644 --- a/src/main/java/com/btk5h/skriptdb/skript/ExprDataSource.java +++ b/src/main/java/com/btk5h/skriptdb/skript/ExprDataSource.java @@ -32,8 +32,9 @@ public class ExprDataSource extends SimpleExpression { static { Skript.registerExpression(ExprDataSource.class, HikariDataSource.class, - ExpressionType.COMBINED, "[the] data(base|[ ]source) [(of|at)] %string% " + - "[with [a] [max[imum]] [connection] life[ ]time of %-timespan%] " + "[[(using|with)] [a] driver %-string%]"); + ExpressionType.COMBINED, "[the] data(base|[ ]source) [(of|at)] %string% " + + "[with [a] [max[imum]] [connection] life[ ]time of %-timespan%] " + + "[[(using|with)] [a] driver %-string%]"); } private Expression url; @@ -100,8 +101,7 @@ public class ExprDataSource extends SimpleExpression { @SuppressWarnings("unchecked") @Override - public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, - SkriptParser.ParseResult parseResult) { + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { url = (Expression) exprs[0]; maxLifetime = (Expression) exprs[1]; driver = (Expression) exprs[2]; diff --git a/src/main/java/com/btk5h/skriptdb/skript/ExprSQLQuery.java b/src/main/java/com/btk5h/skriptdb/skript/ExprSQLQuery.java index b44e47b..96be03d 100644 --- a/src/main/java/com/btk5h/skriptdb/skript/ExprSQLQuery.java +++ b/src/main/java/com/btk5h/skriptdb/skript/ExprSQLQuery.java @@ -1,6 +1,10 @@ package com.btk5h.skriptdb.skript; -import ch.njol.skript.ScriptLoader; +import org.bukkit.event.Event; + +import com.btk5h.skriptdb.SkriptUtil; +import com.btk5h.skriptdb.events.SQLQueryCompleteEvent; + import ch.njol.skript.Skript; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; @@ -8,8 +12,6 @@ import ch.njol.skript.lang.SkriptParser; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.skript.log.ErrorQuality; import ch.njol.util.Kleenean; -import com.btk5h.skriptdb.events.SQLQueryCompleteEvent; -import org.bukkit.event.Event; /** * Stores the error from the last executed statement, if there was one. @@ -51,7 +53,7 @@ public class ExprSQLQuery extends SimpleExpression { @Override public boolean init(final Expression[] expressions, final int matchedPattern, final Kleenean isDelayed, final SkriptParser.ParseResult parseResult) { - if (!ScriptLoader.isCurrentEvent(SQLQueryCompleteEvent.class)) { + if (!SkriptUtil.isCurrentEvent(SQLQueryCompleteEvent.class)) { Skript.error("Cannot use 'sql query' outside of a complete of sql query event", ErrorQuality.SEMANTIC_ERROR); return false; } diff --git a/src/main/java/com/btk5h/skriptdb/skript/ExprUnsafe.java b/src/main/java/com/btk5h/skriptdb/skript/ExprUnsafe.java index 44602ab..70df358 100644 --- a/src/main/java/com/btk5h/skriptdb/skript/ExprUnsafe.java +++ b/src/main/java/com/btk5h/skriptdb/skript/ExprUnsafe.java @@ -53,8 +53,7 @@ public class ExprUnsafe extends SimpleExpression { @SuppressWarnings("unchecked") @Override - public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, - SkriptParser.ParseResult parseResult) { + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { stringExpression = (Expression) exprs[0]; rawExpression = parseResult.expr.substring("unsafe".length()).trim(); return true; From 99be47d4d301b48cc6fdd795d89f42d1f0fb2c84 Mon Sep 17 00:00:00 2001 From: szumielxd <43210079+szumielxd@users.noreply.github.com> Date: Thu, 30 Jan 2025 02:41:32 +0100 Subject: [PATCH 4/4] Added additional arguments info in prepared statement warning --- .../skriptdb/skript/EffExecuteStatement.java | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/btk5h/skriptdb/skript/EffExecuteStatement.java b/src/main/java/com/btk5h/skriptdb/skript/EffExecuteStatement.java index 70ca721..ad6bfb5 100644 --- a/src/main/java/com/btk5h/skriptdb/skript/EffExecuteStatement.java +++ b/src/main/java/com/btk5h/skriptdb/skript/EffExecuteStatement.java @@ -21,8 +21,10 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSetMetaData; import java.sql.SQLException; +import java.sql.Statement; import java.util.*; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.regex.Pattern; @@ -52,7 +54,7 @@ public class EffExecuteStatement extends Effect { static { Skript.registerEffect(EffExecuteStatement.class, "[quickly:quickly] execute %string% (in|on) %datasource% " + - "[with arg[ument][s] %-objects%] [and store [[the] (output|result)[s]] (to|in) [the] [var[iable]] %-objects%]"); + "[with arg[ument][s] %-objects%] [and store [[the] [keys:generated keys] (output|result)[s]] (to|in) [the] [var[iable]] %-objects%]"); } private Expression query; @@ -62,6 +64,7 @@ public class EffExecuteStatement extends Effect { private boolean isLocal; private boolean isList; private boolean quickly; + private boolean generatedKeys; private boolean isSync = false; @Override @@ -82,8 +85,8 @@ public class EffExecuteStatement extends Effect { .whenComplete((resources, err) -> { //handle last error syntax data resetLastSQLError(); - if (err instanceof SkriptDBQueryException) { - setLastSQLError(err.getMessage()); + if (err instanceof CompletionException && err.getCause() instanceof SkriptDBQueryException) { + setLastSQLError(err.getCause().getMessage()); } //if local variables are present //bring back local variables @@ -99,9 +102,9 @@ public class EffExecuteStatement extends Effect { } else { isSync = true; Map resources = null; - try { + resetLastSQLError(); + try { resources = executeStatement(ds, baseVariable, parsedQuery); - resetLastSQLError(); } catch (SkriptDBQueryException err) { //handle last error syntax data setLastSQLError(err.getMessage()); @@ -141,10 +144,16 @@ public class EffExecuteStatement extends Effect { String queryString = query.getSingle(e); int queryArgCount = (int) ARGUMENT_PLACEHOLDER.matcher(queryString).results().count(); if (queryArgCount != args.length) { - Skript.warning("Your query has %d question marks, but you provided %d arguments."); + Skript.warning(String.format("Your query has %d question marks, but you provided %d arguments. (%s) [%s]", + queryArgCount, + args.length, + queryArguments.toString(e, true), + Optional.ofNullable(getTrigger()) + .map(Trigger::getDebugLabel) + .orElse("unknown"))); args = Arrays.copyOf(args, queryArgCount); } - return new Pair<>(query.getSingle(e), List.of(args)); + return new Pair<>(query.getSingle(e), Arrays.asList(args)); } else if (query instanceof VariableString && !((VariableString) query).isSimple()) { return parseVariableQuery(e, (VariableString) query); } @@ -209,20 +218,19 @@ public class EffExecuteStatement extends Effect { } private Map processBaseVariable(String baseVariable, PreparedStatement stmt, boolean hasResultSet) throws SQLException { - Map variableList = new HashMap<>(); if (isList) { baseVariable = baseVariable.substring(0, baseVariable.length() - 1); } if (hasResultSet) { CachedRowSet crs = SkriptDB.getRowSetFactory().createCachedRowSet(); - crs.populate(stmt.getResultSet()); + crs.populate(generatedKeys ? stmt.getGeneratedKeys() : stmt.getResultSet()); if (isList) { return fetchQueryResultSet(crs, baseVariable); } else { crs.last(); - variableList.put(baseVariable, crs.getRow()); + return Map.of(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 @@ -253,7 +261,9 @@ public class EffExecuteStatement extends Effect { } private PreparedStatement createStatement(Connection conn, Pair> query) throws SQLException { - PreparedStatement stmt = conn.prepareStatement(query.getFirst()); + PreparedStatement stmt = generatedKeys ? + conn.prepareStatement(query.getFirst(), Statement.RETURN_GENERATED_KEYS) + : conn.prepareStatement(query.getFirst(), Statement.NO_GENERATED_KEYS); if (query.getSecond() != null) { Iterator iter = query.getSecond().iterator(); for (int i = 1; iter.hasNext(); i++) { @@ -328,6 +338,7 @@ public class EffExecuteStatement extends Effect { } queryArguments = (Expression) exprs[2]; } + ; Expression resultHolder = exprs[3]; quickly = parseResult.hasTag("quickly"); if (resultHolder instanceof Variable) { @@ -335,6 +346,7 @@ public class EffExecuteStatement extends Effect { resultVariableName = varExpr.getName(); isLocal = varExpr.isLocal(); isList = varExpr.isList(); + generatedKeys = parseResult.hasTag("keys"); } else if (resultHolder != null) { Skript.error(resultHolder + " is not a variable"); return false;