Alternative method to provide dynamic arguments into sql query #28
							
								
								
									
										16
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										16
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -7,3 +7,19 @@ | |||||||
| out/ | out/ | ||||||
| target | target | ||||||
| compile/ | compile/ | ||||||
|  | 
 | ||||||
|  | # eclipse specific git ignore | ||||||
|  | *.pydevproject | ||||||
|  | .project | ||||||
|  | .metadata | ||||||
|  | bin/** | ||||||
|  | tmp/** | ||||||
|  | tmp/**/* | ||||||
|  | *.tmp | ||||||
|  | *.bak | ||||||
|  | *.swp | ||||||
|  | *~.nib | ||||||
|  | local.properties | ||||||
|  | .classpath | ||||||
|  | .settings/ | ||||||
|  | .loadpath | ||||||
|  | |||||||
							
								
								
									
										4
									
								
								pom.xml
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								pom.xml
									
									
									
									
									
								
							| @ -30,6 +30,10 @@ | |||||||
|             <id>sk89q</id> |             <id>sk89q</id> | ||||||
|             <url>https://maven.sk89q.com/repo</url> |             <url>https://maven.sk89q.com/repo</url> | ||||||
|         </repository> |         </repository> | ||||||
|  |         <repository> | ||||||
|  |             <id>skript</id> | ||||||
|  |             <url>https://repo.skriptlang.org/releases</url> | ||||||
|  |         </repository> | ||||||
|     </repositories> |     </repositories> | ||||||
| 
 | 
 | ||||||
|     <build> |     <build> | ||||||
|  | |||||||
| @ -1,61 +1,94 @@ | |||||||
| package com.btk5h.skriptdb; | 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.lang.reflect.Field; | ||||||
| import java.util.Arrays; | import java.util.Arrays; | ||||||
| import java.util.Optional; | 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 { | public class SkriptUtil { | ||||||
| 
 | 
 | ||||||
|   private static final Field STRING; |     private static final Field STRING; | ||||||
|   private static final Field EXPR; |     private static final Field EXPR; | ||||||
| 
 | 
 | ||||||
|   static { |     static { | ||||||
|     Field _FIELD = null; |         STRING = tryGetOldStringField() | ||||||
|     try { |                 .or(() -> tryGetNewStringField()) | ||||||
|       _FIELD = VariableString.class.getDeclaredField("string"); |                 .orElseGet(() -> { | ||||||
|       _FIELD.setAccessible(true); |                     Skript.error("Skript's 'string' field could not be resolved."); | ||||||
|     } catch (NoSuchFieldException e) { |                     return null; | ||||||
|       Skript.error("Skript's 'string' field could not be resolved."); |                 }); | ||||||
|       e.printStackTrace(); |  | ||||||
|     } |  | ||||||
|     STRING = _FIELD; |  | ||||||
| 
 | 
 | ||||||
|     try { |         Field f = null; | ||||||
|       Optional<Class<?>> expressionInfo = Arrays.stream(VariableString.class.getDeclaredClasses()) |         try { | ||||||
|           .filter(cls -> cls.getSimpleName().equals("ExpressionInfo")) |             Optional<Class<?>> expressionInfo = Arrays.stream(VariableString.class.getDeclaredClasses()) | ||||||
|           .findFirst(); |                     .filter(cls -> cls.getSimpleName().equals("ExpressionInfo")) | ||||||
|       if (expressionInfo.isPresent()) { |                     .findFirst(); | ||||||
|         Class<?> expressionInfoClass = expressionInfo.get(); |             if (expressionInfo.isPresent()) { | ||||||
|         _FIELD = expressionInfoClass.getDeclaredField("expr"); |                 Class<?> expressionInfoClass = expressionInfo.get(); | ||||||
|         _FIELD.setAccessible(true); |                 f = expressionInfoClass.getDeclaredField("expr"); | ||||||
|       } else { |                 f.setAccessible(true); | ||||||
|         Skript.error("Skript's 'ExpressionInfo' class could not be resolved."); |             } else { | ||||||
|       } |                 Skript.error("Skript's 'ExpressionInfo' class could not be resolved."); | ||||||
|     } catch (NoSuchFieldException e) { |             } | ||||||
|       e.printStackTrace(); |         } catch (NoSuchFieldException e) { | ||||||
|       Skript.error("Skript's 'expr' field could not be resolved."); |             e.printStackTrace(); | ||||||
|  |             Skript.error("Skript's 'expr' field could not be resolved."); | ||||||
|  |         } | ||||||
|  |         EXPR = f; | ||||||
|     } |     } | ||||||
|     EXPR = _FIELD; |  | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   public static Object[] getTemplateString(VariableString vs) { |     public static Object[] getTemplateString(VariableString vs) { | ||||||
|     try { |         try { | ||||||
|       return (Object[]) STRING.get(vs); |             return (Object[]) STRING.get(vs); | ||||||
|     } catch (IllegalAccessException e) { |         } catch (IllegalAccessException e) { | ||||||
|       throw new RuntimeException(e); |             throw new RuntimeException(e); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   public static Expression<?> getExpressionFromInfo(Object o) { |     public static Expression<?> getExpressionFromInfo(Object o) { | ||||||
|     try { |         try { | ||||||
|       return (Expression<?>) EXPR.get(o); |             return (Expression<?>) EXPR.get(o); | ||||||
|     } catch (IllegalAccessException e) { |         } catch (IllegalAccessException e) { | ||||||
|       throw new RuntimeException(e); |             throw new RuntimeException(e); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     @SuppressWarnings("deprecation") | ||||||
|  | 	public static boolean isCurrentEvent(Class<? extends Event> 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<Field> tryGetOldStringField() { | ||||||
|  |         try { | ||||||
|  |             Field f = VariableString.class.getDeclaredField("string"); | ||||||
|  |             f.setAccessible(true); | ||||||
|  |             return Optional.of(f); | ||||||
|  |         } catch (NoSuchFieldException e) { | ||||||
|  |             return Optional.empty(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     private static Optional<Field> tryGetNewStringField() { | ||||||
|  |         try { | ||||||
|  |             Field f = VariableString.class.getDeclaredField("strings"); | ||||||
|  |             f.setAccessible(true); | ||||||
|  |             return Optional.of(f); | ||||||
|  |         } catch (NoSuchFieldException e) { | ||||||
|  |             return Optional.empty(); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -4,32 +4,26 @@ import org.bukkit.event.Event; | |||||||
| import org.bukkit.event.HandlerList; | import org.bukkit.event.HandlerList; | ||||||
| 
 | 
 | ||||||
| public class SQLQueryCompleteEvent extends Event { | public class SQLQueryCompleteEvent extends Event { | ||||||
|     private final static HandlerList HANDLERS = new HandlerList(); |      | ||||||
|  | 	private static final HandlerList HANDLERS = new HandlerList(); | ||||||
|  | 	 | ||||||
|     private final String argument; |     private final String argument; | ||||||
| 
 | 
 | ||||||
|     public SQLQueryCompleteEvent(String argument) { |     public SQLQueryCompleteEvent(String argument) { | ||||||
|         super(true); |         super(true); | ||||||
|         this.argument = argument; |         this.argument = argument; | ||||||
|         //  this.variables = variables; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public static HandlerList getHandlerList() { |  | ||||||
|         return HANDLERS; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     public String getEventName() { |  | ||||||
|         return super.getEventName(); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public HandlerList getHandlers() { |     public HandlerList getHandlers() { | ||||||
|         return HANDLERS; |         return getHandlerList(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public String getQuery() { |     public String getQuery() { | ||||||
|         return argument; |         return argument; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     //  public String getVariables() {return;} |     public static HandlerList getHandlerList() { | ||||||
|  |         return HANDLERS; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -21,10 +21,13 @@ import java.sql.Connection; | |||||||
| import java.sql.PreparedStatement; | import java.sql.PreparedStatement; | ||||||
| import java.sql.ResultSetMetaData; | import java.sql.ResultSetMetaData; | ||||||
| import java.sql.SQLException; | import java.sql.SQLException; | ||||||
|  | import java.sql.Statement; | ||||||
| import java.util.*; | import java.util.*; | ||||||
| import java.util.concurrent.CompletableFuture; | import java.util.concurrent.CompletableFuture; | ||||||
|  | import java.util.concurrent.CompletionException; | ||||||
| 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,107 +48,86 @@ 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% " + |                 "[quickly:quickly] 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] [keys:generated keys] (output|result)[s]] (to|in) [the] [var[iable]] %-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 VariableString var; |     private Expression<Object> queryArguments; | ||||||
|  |     private VariableString resultVariableName; | ||||||
|     private boolean isLocal; |     private boolean isLocal; | ||||||
|     private boolean isList; |     private boolean isList; | ||||||
|     private boolean quickly; |     private boolean quickly; | ||||||
|  |     private boolean generatedKeys; | ||||||
|     private boolean isSync = false; |     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<String, Object>) populatedVariables).forEach((name, value) -> setVariable(e, name, value)); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         TriggerItem.walk(getNext(), e); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |     @Override | ||||||
|     protected void execute(Event e) { |     protected void execute(Event e) { | ||||||
|         DataSource ds = dataSource.getSingle(e); |         DataSource ds = dataSource.getSingle(e); | ||||||
|         Pair<String, List<Object>> query = parseQuery(e); |  | ||||||
|         String baseVariable = var != null ? var.toString(e).toLowerCase(Locale.ENGLISH) : null; |  | ||||||
|         //if data source isn't set |         //if data source isn't set | ||||||
|         if (ds == null) return; |         if (ds == null) { | ||||||
|  |         	return; | ||||||
|  |         } | ||||||
|  |         Pair<String, List<Object>> parsedQuery = parseQuery(e); | ||||||
|  |         String baseVariable = resultVariableName != null ? resultVariableName.toString(e).toLowerCase(Locale.ENGLISH) : null; | ||||||
|  |          | ||||||
|         Object locals = Variables.removeLocals(e); |         Object locals = Variables.removeLocals(e); | ||||||
| 
 | 
 | ||||||
|         //execute SQL statement |         //execute SQL statement | ||||||
|         if (Bukkit.isPrimaryThread()) { |         if (Bukkit.isPrimaryThread()) { | ||||||
|             CompletableFuture<Object> sql = CompletableFuture.supplyAsync(() -> executeStatement(ds, baseVariable, query), threadPool); |             CompletableFuture.supplyAsync(() -> executeStatement(ds, baseVariable, parsedQuery), threadPool) | ||||||
|             sql.whenComplete((res, err) -> { | 		            .whenComplete((resources, err) -> { | ||||||
|                 if (err != null) { | 		            	//handle last error syntax data | ||||||
|                     err.printStackTrace(); | 		            	resetLastSQLError(); | ||||||
|                 } | 		            	if (err instanceof CompletionException && err.getCause() instanceof SkriptDBQueryException) { | ||||||
|                 //handle last error syntax data | 	            			setLastSQLError(err.getCause().getMessage()); | ||||||
|                 lastError = null; | 	            		} | ||||||
|                 if (res instanceof String) { | 		                //if local variables are present | ||||||
|                     lastError = (String) res; | 		                //bring back local variables | ||||||
|                 } | 		                //populate SQL data into variables | ||||||
|                 //if local variables are present | 		                if (!quickly) { | ||||||
|                 //bring back local variables | 		                    Bukkit.getScheduler().runTask(SkriptDB.getInstance(), | ||||||
|                 //populate SQL data into variables | 		                    		() -> postExecution(e, locals, resources)); | ||||||
|                 if (!quickly) { | 		                } else { | ||||||
|                     Bukkit.getScheduler().runTask(SkriptDB.getInstance(), () -> { | 		                	postExecution(e, locals, resources); | ||||||
|                         if (locals != null && getNext() != null) { | 		                } | ||||||
|                             Variables.setLocalVariables(e, locals); | 		            }); | ||||||
|                         } |  | ||||||
|                         if (!(res instanceof String)) { |  | ||||||
|                             ((Map<String, Object>) 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<String, Object>) res).forEach((name, value) -> setVariable(e, name, value)); |  | ||||||
|                     } |  | ||||||
|                     TriggerItem.walk(getNext(), e); |  | ||||||
|                     //the line below is required to prevent memory leaks |  | ||||||
|                     Variables.removeLocals(e); |  | ||||||
|                 } |  | ||||||
|             }); |  | ||||||
|             // sync executed SQL query, same as above, just sync |             // sync executed SQL query, same as above, just sync | ||||||
|         } else { |         } else { | ||||||
|             isSync = true; |             isSync = true; | ||||||
|             Object resources = executeStatement(ds, baseVariable, query); |             Map<String, Object> resources = null; | ||||||
|             //handle last error syntax data |             resetLastSQLError(); | ||||||
|             lastError = null; |             try { | ||||||
|             if (resources instanceof String) { |                 resources = executeStatement(ds, baseVariable, parsedQuery); | ||||||
|                 lastError = (String) resources; | 			} catch (SkriptDBQueryException err) { | ||||||
|             } | 	            //handle last error syntax data | ||||||
|  | 				setLastSQLError(err.getMessage()); | ||||||
|  | 			} | ||||||
|             //if local variables are present |             //if local variables are present | ||||||
|             //bring back local variables |             //bring back local variables | ||||||
|             //populate SQL data into variables |             //populate SQL data into variables | ||||||
|             if (locals != null && getNext() != null) { | 			postExecution(e, locals, resources); | ||||||
|                 Variables.setLocalVariables(e, locals); |  | ||||||
|             } |  | ||||||
|             if (!(resources instanceof String)) { |  | ||||||
|                 ((Map<String, Object>) resources).forEach((name, value) -> setVariable(e, name, value)); |  | ||||||
|             } |  | ||||||
|             TriggerItem.walk(getNext(), e); |  | ||||||
|             Variables.removeLocals(e); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|      |      | ||||||
|  |     private void postExecution(Event e, Object locals, Map<String, Object> 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 |     @Override | ||||||
|     protected TriggerItem walk(Event e) { |     protected TriggerItem walk(Event e) { | ||||||
|         debug(e, true); |         debug(e, true); | ||||||
| @ -157,142 +139,151 @@ 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(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), Arrays.asList(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 Object executeStatement(DataSource ds, String baseVariable, Pair<String, List<Object>> query) { |     private Pair<String, Object> parseExpressionQuery(Expression<?> expr, Object expressionValue, boolean standaloneString) { | ||||||
|         if (ds == null) { |         if (expr instanceof ExprUnsafe) { | ||||||
|             return "Data source is not set"; |             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); | ||||||
|         } |         } | ||||||
|         Map<String, Object> variableList = new HashMap<>(); |     } | ||||||
|         try (Connection conn = ds.getConnection(); |  | ||||||
|              PreparedStatement stmt = createStatement(conn, query)) { |  | ||||||
| 
 | 
 | ||||||
|             boolean hasResultSet = stmt.execute(); |     private Map<String, Object> executeStatement(DataSource ds, String baseVariable, Pair<String, List<Object>> query) throws SkriptDBQueryException { | ||||||
|  |         if (ds == null) { | ||||||
|  |         	throw new SkriptDBQueryException("Data source is not set"); | ||||||
|  |         } | ||||||
|  |         try (Connection conn = ds.getConnection()) { | ||||||
|  |             try (PreparedStatement stmt = createStatement(conn, query)) { | ||||||
|  |                 boolean hasResultSet = stmt.execute(); | ||||||
| 
 | 
 | ||||||
|             if (baseVariable != null) { |                 if (baseVariable != null) { | ||||||
|                 if (isList) { |                     return processBaseVariable(baseVariable, stmt, hasResultSet); | ||||||
|                     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(); |             throw new SkriptDBQueryException(ex.getMessage()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     private Map<String, Object> processBaseVariable(String baseVariable, PreparedStatement stmt, boolean hasResultSet) throws SQLException { | ||||||
|  |         if (isList) { | ||||||
|  |             baseVariable = baseVariable.substring(0, baseVariable.length() - 1); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (hasResultSet) { | ||||||
|  |             CachedRowSet crs = SkriptDB.getRowSetFactory().createCachedRowSet(); | ||||||
|  |             crs.populate(generatedKeys ? stmt.getGeneratedKeys() : stmt.getResultSet()); | ||||||
|  | 
 | ||||||
|  |             if (isList) { | ||||||
|  |                 return fetchQueryResultSet(crs, baseVariable); | ||||||
|  |             } else { | ||||||
|  |                 crs.last(); | ||||||
|  |                 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 | ||||||
|  |             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 = generatedKeys ?  | ||||||
|         List<Object> parameters = query.getSecond(); |         		conn.prepareStatement(query.getFirst(), Statement.RETURN_GENERATED_KEYS) | ||||||
| 
 |         		: conn.prepareStatement(query.getFirst(), Statement.NO_GENERATED_KEYS); | ||||||
|         if (parameters != null) { |         if (query.getSecond() != null) { | ||||||
|             for (int i = 0; i < parameters.size(); i++) { |             Iterator<Object> iter = query.getSecond().iterator(); | ||||||
|                 stmt.setObject(i + 1, parameters.get(i)); |             for (int i = 1; iter.hasNext(); i++) { | ||||||
|  |                 stmt.setObject(i, iter.next()); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         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; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -300,7 +291,7 @@ public class EffExecuteStatement extends Effect { | |||||||
| 
 | 
 | ||||||
|         //fix mediumblob and similar column types, so they return a String correctly |         //fix mediumblob and similar column types, so they return a String correctly | ||||||
|         if (obj != null) { |         if (obj != null) { | ||||||
|             if (obj.getClass().getName().equals("[B")) { |             if (obj instanceof byte[]) { | ||||||
|                 obj = new String((byte[]) obj); |                 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) |                 //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) | ||||||
| @ -315,6 +306,14 @@ public class EffExecuteStatement extends Effect { | |||||||
|         Variables.setVariable(name.toLowerCase(Locale.ENGLISH), obj, e, isLocal); |         Variables.setVariable(name.toLowerCase(Locale.ENGLISH), obj, e, isLocal); | ||||||
|     } |     } | ||||||
|      |      | ||||||
|  |     private static void resetLastSQLError() { | ||||||
|  |     	lastError = null; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     private static void setLastSQLError(String error) { | ||||||
|  |     	lastError = error; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @Override |     @Override | ||||||
|     public String toString(Event e, boolean debug) { |     public String toString(Event e, boolean debug) { | ||||||
|         return "execute " + query.toString(e, debug) + " in " + dataSource.toString(e, debug); |         return "execute " + query.toString(e, debug) + " in " + dataSource.toString(e, debug); | ||||||
| @ -322,8 +321,7 @@ public class EffExecuteStatement extends Effect { | |||||||
| 
 | 
 | ||||||
|     @SuppressWarnings("unchecked") |     @SuppressWarnings("unchecked") | ||||||
|     @Override |     @Override | ||||||
|     public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, |     public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { | ||||||
|                         SkriptParser.ParseResult parseResult) { |  | ||||||
|         Expression<String> statementExpr = (Expression<String>) exprs[0]; |         Expression<String> statementExpr = (Expression<String>) exprs[0]; | ||||||
|         if (statementExpr instanceof VariableString || statementExpr instanceof ExprUnsafe) { |         if (statementExpr instanceof VariableString || statementExpr instanceof ExprUnsafe) { | ||||||
|             query = statementExpr; |             query = statementExpr; | ||||||
| @ -334,17 +332,36 @@ 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) { | ||||||
|         quickly = matchedPattern == 1; |             if (query instanceof VariableString && !((VariableString) query).isSimple()) { | ||||||
|         if (expr instanceof Variable) { |                 Skript.warning("Your query string contains expresions, but you've also provided query arguments. Consider using `unsafe` keyword before your query."); | ||||||
|             Variable<?> varExpr = (Variable<?>) expr; |             } | ||||||
|             var = varExpr.getName(); |             queryArguments = (Expression<Object>) exprs[2]; | ||||||
|  |         } | ||||||
|  |         ; | ||||||
|  |         Expression<?> resultHolder = exprs[3]; | ||||||
|  |         quickly = parseResult.hasTag("quickly"); | ||||||
|  |         if (resultHolder instanceof Variable) { | ||||||
|  |             Variable<?> varExpr = (Variable<?>) resultHolder; | ||||||
|  |             resultVariableName = varExpr.getName(); | ||||||
|             isLocal = varExpr.isLocal(); |             isLocal = varExpr.isLocal(); | ||||||
|             isList = varExpr.isList(); |             isList = varExpr.isList(); | ||||||
|         } else if (expr != null) { |             generatedKeys = parseResult.hasTag("keys"); | ||||||
|             Skript.error(expr + " is not a variable"); |         } else if (resultHolder != null) { | ||||||
|  |             Skript.error(resultHolder + " is not a variable"); | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
|  |      | ||||||
|  |     public static class SkriptDBQueryException extends RuntimeException { | ||||||
|  | 
 | ||||||
|  | 		private static final long serialVersionUID = -1869895286406538884L; | ||||||
|  | 		 | ||||||
|  | 		public SkriptDBQueryException(String message) { | ||||||
|  | 			super(message); | ||||||
|  | 		} | ||||||
|  |     	 | ||||||
|  |     } | ||||||
|  |      | ||||||
| } | } | ||||||
| @ -32,8 +32,9 @@ public class ExprDataSource extends SimpleExpression<HikariDataSource> { | |||||||
| 
 | 
 | ||||||
|     static { |     static { | ||||||
|         Skript.registerExpression(ExprDataSource.class, HikariDataSource.class, |         Skript.registerExpression(ExprDataSource.class, HikariDataSource.class, | ||||||
|                 ExpressionType.COMBINED, "[the] data(base|[ ]source) [(of|at)] %string% " + |                 ExpressionType.COMBINED, "[the] data(base|[ ]source) [(of|at)] %string% " | ||||||
|                         "[with [a] [max[imum]] [connection] life[ ]time of %-timespan%] " + "[[(using|with)] [a] driver %-string%]"); |                 		+ "[with [a] [max[imum]] [connection] life[ ]time of %-timespan%] " | ||||||
|  |                 		+ "[[(using|with)] [a] driver %-string%]"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private Expression<String> url; |     private Expression<String> url; | ||||||
| @ -100,8 +101,7 @@ public class ExprDataSource extends SimpleExpression<HikariDataSource> { | |||||||
| 
 | 
 | ||||||
|     @SuppressWarnings("unchecked") |     @SuppressWarnings("unchecked") | ||||||
|     @Override |     @Override | ||||||
|     public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, |     public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { | ||||||
|                         SkriptParser.ParseResult parseResult) { |  | ||||||
|         url = (Expression<String>) exprs[0]; |         url = (Expression<String>) exprs[0]; | ||||||
|         maxLifetime = (Expression<Timespan>) exprs[1]; |         maxLifetime = (Expression<Timespan>) exprs[1]; | ||||||
|         driver = (Expression<String>) exprs[2]; |         driver = (Expression<String>) exprs[2]; | ||||||
|  | |||||||
| @ -1,6 +1,10 @@ | |||||||
| package com.btk5h.skriptdb.skript; | 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.Skript; | ||||||
| import ch.njol.skript.lang.Expression; | import ch.njol.skript.lang.Expression; | ||||||
| import ch.njol.skript.lang.ExpressionType; | 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.lang.util.SimpleExpression; | ||||||
| import ch.njol.skript.log.ErrorQuality; | import ch.njol.skript.log.ErrorQuality; | ||||||
| import ch.njol.util.Kleenean; | 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. |  * Stores the error from the last executed statement, if there was one. | ||||||
| @ -51,7 +53,7 @@ public class ExprSQLQuery extends SimpleExpression<String> { | |||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public boolean init(final Expression<?>[] expressions, final int matchedPattern, final Kleenean isDelayed, final SkriptParser.ParseResult parseResult) { |     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); |             Skript.error("Cannot use 'sql query' outside of a complete of sql query event", ErrorQuality.SEMANTIC_ERROR); | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -53,8 +53,7 @@ public class ExprUnsafe extends SimpleExpression<String> { | |||||||
| 
 | 
 | ||||||
|     @SuppressWarnings("unchecked") |     @SuppressWarnings("unchecked") | ||||||
|     @Override |     @Override | ||||||
|     public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, |     public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { | ||||||
|                         SkriptParser.ParseResult parseResult) { |  | ||||||
|         stringExpression = (Expression<String>) exprs[0]; |         stringExpression = (Expression<String>) exprs[0]; | ||||||
|         rawExpression = parseResult.expr.substring("unsafe".length()).trim(); |         rawExpression = parseResult.expr.substring("unsafe".length()).trim(); | ||||||
|         return true; |         return true; | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user