Compare commits

...

12 Commits

7 changed files with 111 additions and 77 deletions

View File

@ -5,13 +5,14 @@
### Difference from original skript-db ### Difference from original skript-db
- Fixed local variables disappearance in newer Skript versions (very hacky fix, but it works, so that's good!) - Fixed local variables disappearance in newer Skript versions (very hacky fix, but it works, so that's good!)
- Thread-pool size is now automatically increasing on demand with use of CachedThreadPool, instead of a fixed hard-coded number
- Uses newer versions of dependencies (Increased performance and security) - Uses newer versions of dependencies (Increased performance and security)
- Replaced `synchronously execute` with `quickly execute`, which allows to speed up queries by 50ms with some risk - Replaced `synchronously execute` with `quickly execute`, which allows to speed up queries by 50ms with some risk
- SQL Driver is configurable - If a sql query is detected to be running on non-main thread, it becomes synchronous automatically
- SQL Driver is configurable both in config and in database connection, comes with shaded MariaDB and PostgreSQL drivers
- A few variable type related bugs fixed - A few variable type related bugs fixed
- Uses Java 11 instead of Java 8 - Uses Java 11 instead of Java 8
### Installation ### Installation
1. Use 1.8+ Minecraft server version. 1. Use 1.8+ Minecraft server version.
2. Use Skript 2.5+ (1.8 Skript fork is needed if you're using 1.8) 2. Use Skript 2.5+ (1.8 Skript fork is needed if you're using 1.8)
@ -21,15 +22,23 @@
Stores the connection information for a data source. This should be saved to a variable in a Stores the connection information for a data source. This should be saved to a variable in a
`script load` event or manually through an effect command. `script load` event or manually through an effect command.
The url format for your database may vary! The example provided uses a MySQL database. The url format for your database may vary depending on database you are using.
MariaDB/PostgreSQL users: make sure to check `config.yml` to use the correct driver.
#### Syntax #### Syntax
``` ```
[the] data(base|[ ]source) [(of|at)] %string% [the] data(base|[ ]source) [(of|at)] %string% [with [a] [max[imum]] [connection] life[ ]time of %timespan%] [[(using|with)] [a] driver %-string%]
``` ```
#### Examples #### Examples
``` ```
set {sql} to the database "mysql://localhost:3306/mydatabase?user=admin&password=12345&useSSL=false" set {sql} to the database "mysql://localhost:3306/mydatabase?user=admin&password=12345&useSSL=false"
set {sql} to the database "mariadb://localhost:3306/mydatabase?user=admin&password=12345&useSSL=false"
set {sql} to the database "postgresql://localhost:3306/mydatabase?user=admin&password=12345&ssl=false"
set {sql} to the database "sqlite:database.db"
# Extra parameters:
set {sql} to the database "postgresql://localhost:3306/mydatabase?user=admin&password=12345&ssl=false" with a maximum connection lifetime of 30 minutes
set {sql} to the database "postgresql://localhost:3306/mydatabase?user=admin&password=12345&ssl=false" with a maximum connection lifetime of 30 minutes using driver "org.postgresql.Driver"
``` ```
--- ---
@ -71,6 +80,7 @@ Stores the error from the last executed statement, if there was one.
### Expression `Unsafe Expression` => `text` ### Expression `Unsafe Expression` => `text`
Opts out of automatic SQL injection protection for a specific expression in a statement. Opts out of automatic SQL injection protection for a specific expression in a statement.
Note: If using PostgreSQL, this will always be needed, due to skript-db not supporting SQL injection protection for PostgreSQL currently.
#### Syntax #### Syntax
``` ```
unsafe %text% unsafe %text%
@ -84,6 +94,18 @@ execute "select %unsafe {columns variable}% from %{table variable}%" in {sql}
execute unsafe {fully dynamic query} in {sql} execute unsafe {fully dynamic query} in {sql}
``` ```
#### FAQ: How to return sql data in a function?
You can't because functions don't allow delays, but you can use skript-reflect sections for this:
```
on load:
create new section stored in {-section::getPlayersFromDatabase}:
execute "SELECT uuid FROM table" in {-sql} and store the result in {_result::*}
return {_result::uuid::*}
command /showplayers [<text>]:
trigger:
run section {-section::getPlayersFromDatabase} async and store result in {_uuids::*} and wait
send "%{_uuids::*}%"
```
--- ---
### Configuration ### Configuration
plugins/skript-db/config.yml plugins/skript-db/config.yml

20
pom.xml
View File

@ -6,7 +6,7 @@
<groupId>com.btk5h</groupId> <groupId>com.btk5h</groupId>
<artifactId>skript-db</artifactId> <artifactId>skript-db</artifactId>
<version>1.3.5</version> <version>1.4.0</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<repositories> <repositories>
@ -19,8 +19,8 @@
<url>https://jitpack.io</url> <url>https://jitpack.io</url>
</repository> </repository>
<repository> <repository>
<id>commons-pool2</id> <id>mvnrepository</id>
<url>https://mvnrepository.com/artifact/org.apache.commons/commons-pool2</url> <url>https://mvnrepository.com</url>
</repository> </repository>
<repository> <repository>
<id>PaperMC</id> <id>PaperMC</id>
@ -28,7 +28,7 @@
</repository> </repository>
<repository> <repository>
<id>sk89q</id> <id>sk89q</id>
<url>http://maven.sk89q.com/repo</url> <url>https://maven.sk89q.com/repo</url>
</repository> </repository>
</repositories> </repositories>
@ -56,7 +56,6 @@
<version>3.2.3</version> <version>3.2.3</version>
<configuration> <configuration>
<createDependencyReducedPom>false</createDependencyReducedPom> <createDependencyReducedPom>false</createDependencyReducedPom>
<minimizeJar>true</minimizeJar>
</configuration> </configuration>
<executions> <executions>
<execution> <execution>
@ -94,14 +93,21 @@
<dependency> <dependency>
<groupId>com.github.SkriptLang</groupId> <groupId>com.github.SkriptLang</groupId>
<artifactId>Skript</artifactId> <artifactId>Skript</artifactId>
<version>2.6-beta3</version> <version>2.6.1</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/org.mariadb.jdbc/mariadb-java-client --> <!-- https://mvnrepository.com/artifact/org.mariadb.jdbc/mariadb-java-client -->
<dependency> <dependency>
<groupId>org.mariadb.jdbc</groupId> <groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId> <artifactId>mariadb-java-client</artifactId>
<version>3.0.3</version> <version>3.1.2</version>
<scope>compile</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.postgresql/postgresql -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.6.0</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>

View File

@ -73,8 +73,14 @@ public final class SkriptDB extends JavaPlugin {
} }
try { try {
if (out == null) return; if (out == null) return;
out.write("# Only change this if you wish to use a different driver than Java's default, like MariaDB driver.\n");
out.write("# If you use MariaDB, its driver is shaded together with skript-db, so you can just specify:" + "\"org.mariadb.jdbc.Driver\"" + ".\n"); out.write("# How many connections can be awaited for simultaneously, may be useful to increase if SQL database is hosted on a separate machine to account for ping.\n");
out.write("# If it is hosted within the same machine, set it to the count of cores your processor has or the count of threads your processor can process at once.\n");
out.write("thread-pool-size: " + (Runtime.getRuntime().availableProcessors() + 2) + "\n");
out.write("# How long SQL connections should be kept alive in HikariCP. Default: 1800000 (30 minutes)");
out.write("max-connection-lifetime: 1800000");
out.write("# Only change this if you wish to use a different driver than Java's default, like MariaDB/PostgreSQL driver.\n");
out.write("# If you use MariaDB or PostgreSQL, its driver is shaded together with skript-db, so you can just specify:" + "\"org.mariadb.jdbc.Driver\"" + " or " + "\"org.postgresql.Driver\"" + ".\n");
out.write("sql-driver-class-name: " + "\"default\"" + "\n"); out.write("sql-driver-class-name: " + "\"default\"" + "\n");
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();

View File

@ -44,8 +44,7 @@ import java.util.concurrent.Executors;
* @since 0.1.0 * @since 0.1.0
*/ */
public class EffExecuteStatement extends Effect { public class EffExecuteStatement extends Effect {
private static final ExecutorService threadPool = private static final ExecutorService threadPool = Executors.newFixedThreadPool(SkriptDB.getInstance().getConfig().getInt("thread-pool-size", 10));
Executors.newCachedThreadPool();
static String lastError; static String lastError;
static { static {
@ -60,7 +59,8 @@ public class EffExecuteStatement extends Effect {
private VariableString var; private VariableString var;
private boolean isLocal; private boolean isLocal;
private boolean isList; private boolean isList;
private boolean isSync; private boolean quickly;
private boolean isSync = false;
private void continueScriptExecution(Event e, Object populatedVariables) { private void continueScriptExecution(Event e, Object populatedVariables) {
lastError = null; lastError = null;
@ -82,58 +82,26 @@ public class EffExecuteStatement extends Effect {
String baseVariable = var != null ? var.toString(e).toLowerCase(Locale.ENGLISH) : null; 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;
boolean sync = !Bukkit.isPrimaryThread();
//if current thread is not main thread, then make this query to not have delays
Object locals = Variables.removeLocals(e); Object locals = Variables.removeLocals(e);
//execute SQL statement //execute SQL statement
CompletableFuture<Object> sql = if (Bukkit.isPrimaryThread()) {
CompletableFuture.supplyAsync(() -> executeStatement(ds, baseVariable, query), threadPool); CompletableFuture<Object> sql = CompletableFuture.supplyAsync(() -> executeStatement(ds, baseVariable, query), threadPool);
sql.whenComplete((res, err) -> {
//when SQL statement is completed if (err != null) {
boolean finalSync = sync; err.printStackTrace();
sql.whenComplete((res, err) -> { }
if (err != null) { //handle last error syntax data
err.printStackTrace(); lastError = null;
} if (res instanceof String) {
lastError = (String) res;
//handle last error syntax data }
lastError = null;
if (res instanceof String) {
lastError = (String) res;
}
if (getNext() != null) {
//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 (!(res instanceof String)) { if (!quickly) {
//also set variables in the sql query complete event
//TEMPORARILY DISABLED, AS THIS WOULD WORSEN PERFORMANCE OF THE QUERIES AND NOT BE USED BY MOST PEOPLE.
//I may add config option to enable this later?
//SQLQueryCompleteEvent event = new SQLQueryCompleteEvent(this.query.getSingle(e));
//((Map<String, Object>) res).forEach((name, value) -> setVariable(event, name, value));
//SkriptDB.getPlugin(SkriptDB.class).getServer().getPluginManager().callEvent(event);
}
if (isSync || finalSync) {
if (locals != null) {
Variables.setLocalVariables(e, locals);
}
if (!(res instanceof String)) {
((Map<String, Object>) res).forEach((name, value) -> setVariable(e, name, value));
}
TriggerItem.walk(getNext(), e);
Variables.removeLocals(e);
} else {
Bukkit.getScheduler().runTask(SkriptDB.getInstance(), () -> { Bukkit.getScheduler().runTask(SkriptDB.getInstance(), () -> {
if (locals != null) { if (locals != null && getNext() != null) {
Variables.setLocalVariables(e, locals); Variables.setLocalVariables(e, locals);
} }
if (!(res instanceof String)) { if (!(res instanceof String)) {
@ -141,20 +109,47 @@ public class EffExecuteStatement extends Effect {
} }
TriggerItem.walk(getNext(), e); TriggerItem.walk(getNext(), e);
//the line below is required to prevent memory leaks //the line below is required to prevent memory leaks
//no functionality difference notice with it being removed from my test, but the memory gets filled with leaks
//so it must be kept
Variables.removeLocals(e); 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
} else {
isSync = true;
Object resources = executeStatement(ds, baseVariable, query);
//handle last error syntax data
lastError = null;
if (resources instanceof String) {
lastError = (String) resources;
} }
}); //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<String, Object>) resources).forEach((name, value) -> setVariable(e, name, value));
}
TriggerItem.walk(getNext(), e);
Variables.removeLocals(e);
}
} }
@Override @Override
protected TriggerItem walk(Event e) { protected TriggerItem walk(Event e) {
debug(e, true); debug(e, true);
//I think no longer needed as of 1.3.0, uncomment if something breaks if (!quickly || !isSync) {
if (!isSync) {
Delay.addDelayedEvent(e); Delay.addDelayedEvent(e);
} }
execute(e); execute(e);
@ -340,7 +335,7 @@ public class EffExecuteStatement extends Effect {
} }
dataSource = (Expression<HikariDataSource>) exprs[1]; dataSource = (Expression<HikariDataSource>) exprs[1];
Expression<?> expr = exprs[2]; Expression<?> expr = exprs[2];
isSync = matchedPattern == 1; quickly = matchedPattern == 1;
if (expr instanceof Variable) { if (expr instanceof Variable) {
Variable<?> varExpr = (Variable<?>) expr; Variable<?> varExpr = (Variable<?>) expr;
var = varExpr.getName(); var = varExpr.getName();

View File

@ -22,7 +22,7 @@ import java.util.Map;
* *
* @name Data Source * @name Data Source
* @index -1 * @index -1
* @pattern [the] data(base|[ ]source) [(of|at)] %string% [with [a] [max[imum]] [connection] life[ ]time of %timespan%]" * @pattern [the] data(base|[ ]source) [(of|at)] %string% [with [a] [max[imum]] [connection] life[ ]time of %timespan%] [[(using|with)] [a] driver %-string%]"
* @return datasource * @return datasource
* @example set {sql} to the database "mysql://localhost:3306/mydatabase?user=admin&password=12345&useSSL=false" * @example set {sql} to the database "mysql://localhost:3306/mydatabase?user=admin&password=12345&useSSL=false"
* @since 0.1.0 * @since 0.1.0
@ -33,11 +33,12 @@ 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%]"); "[with [a] [max[imum]] [connection] life[ ]time of %-timespan%] " + "[[(using|with)] [a] driver %-string%]");
} }
private Expression<String> url; private Expression<String> url;
private Expression<Timespan> maxLifetime; private Expression<Timespan> maxLifetime;
private Expression<String> driver;
@Override @Override
protected HikariDataSource[] get(Event e) { protected HikariDataSource[] get(Event e) {
@ -55,11 +56,18 @@ public class ExprDataSource extends SimpleExpression<HikariDataSource> {
} }
HikariDataSource ds = new HikariDataSource(); HikariDataSource ds = new HikariDataSource();
ds.setMaximumPoolSize(SkriptDB.getInstance().getConfig().getInt("thread-pool-size", 10));
//allow specifying of own sql driver class name // 30 minutes by default
if (!SkriptDB.getInstance().getConfig().getString("sql-driver-class-name", "default").equals("default")) { ds.setMaxLifetime(SkriptDB.getInstance().getConfig().getInt("max-connection-lifetime", 1800000));
// Allow specifying of own sql driver class name
if (driver != null && driver.getSingle(e) != null) {
ds.setDriverClassName(driver.getSingle(e));
} else if (!SkriptDB.getInstance().getConfig().getString("sql-driver-class-name", "default").equals("default")) {
ds.setDriverClassName(SkriptDB.getInstance().getConfig().getString("sql-driver-class-name")); ds.setDriverClassName(SkriptDB.getInstance().getConfig().getString("sql-driver-class-name"));
} }
ds.setJdbcUrl(jdbcUrl); ds.setJdbcUrl(jdbcUrl);
if (maxLifetime != null) { if (maxLifetime != null) {
@ -96,6 +104,7 @@ public class ExprDataSource extends SimpleExpression<HikariDataSource> {
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];
return true; return true;
} }
} }

View File

@ -30,10 +30,6 @@ public class Types {
return o.getJdbcUrl(); return o.getJdbcUrl();
} }
@Override
public String getVariableNamePattern() {
return "jdbc:.+";
}
}) })
.serializer(new Serializer<HikariDataSource>() { .serializer(new Serializer<HikariDataSource>() {
@Override @Override

View File

@ -1,5 +1,5 @@
name: skript-db name: skript-db
version: 1.3.5 version: 1.4.0
main: com.btk5h.skriptdb.SkriptDB main: com.btk5h.skriptdb.SkriptDB
depend: [Skript] depend: [Skript]
authors: [btk5h, FranKusmiruk, Govindas, TPGamesNL] authors: [btk5h, FranKusmiruk, Govindas, TPGamesNL]