Compare commits

..

No commits in common. "master" and "master" have entirely different histories.

7 changed files with 77 additions and 111 deletions

View File

@ -5,14 +5,13 @@
### 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
- If a sql query is detected to be running on non-main thread, it becomes synchronous automatically - SQL Driver is configurable
- 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)
@ -22,23 +21,15 @@
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 depending on database you are using. The url format for your database may vary! The example provided uses a MySQL database.
MariaDB/PostgreSQL users: make sure to check `config.yml` to use the correct driver.
#### Syntax #### Syntax
``` ```
[the] data(base|[ ]source) [(of|at)] %string% [with [a] [max[imum]] [connection] life[ ]time of %timespan%] [[(using|with)] [a] driver %-string%] [the] data(base|[ ]source) [(of|at)] %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"
``` ```
--- ---
@ -80,7 +71,6 @@ 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%
@ -94,18 +84,6 @@ 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.4.0</version> <version>1.3.5</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>mvnrepository</id> <id>commons-pool2</id>
<url>https://mvnrepository.com</url> <url>https://mvnrepository.com/artifact/org.apache.commons/commons-pool2</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>https://maven.sk89q.com/repo</url> <url>http://maven.sk89q.com/repo</url>
</repository> </repository>
</repositories> </repositories>
@ -56,6 +56,7 @@
<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>
@ -93,21 +94,14 @@
<dependency> <dependency>
<groupId>com.github.SkriptLang</groupId> <groupId>com.github.SkriptLang</groupId>
<artifactId>Skript</artifactId> <artifactId>Skript</artifactId>
<version>2.6.1</version> <version>2.6-beta3</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.1.2</version> <version>3.0.3</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,14 +73,8 @@ 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("# 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 you use MariaDB, its driver is shaded together with skript-db, so you can just specify:" + "\"org.mariadb.jdbc.Driver\"" + ".\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,7 +44,8 @@ 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 = Executors.newFixedThreadPool(SkriptDB.getInstance().getConfig().getInt("thread-pool-size", 10)); private static final ExecutorService threadPool =
Executors.newCachedThreadPool();
static String lastError; static String lastError;
static { static {
@ -59,8 +60,7 @@ 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 quickly; private boolean isSync;
private boolean isSync = false;
private void continueScriptExecution(Event e, Object populatedVariables) { private void continueScriptExecution(Event e, Object populatedVariables) {
lastError = null; lastError = null;
@ -82,26 +82,58 @@ 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
if (Bukkit.isPrimaryThread()) { CompletableFuture<Object> sql =
CompletableFuture<Object> sql = CompletableFuture.supplyAsync(() -> executeStatement(ds, baseVariable, query), threadPool); CompletableFuture.supplyAsync(() -> executeStatement(ds, baseVariable, query), threadPool);
//when SQL statement is completed
boolean finalSync = sync;
sql.whenComplete((res, err) -> { sql.whenComplete((res, err) -> {
if (err != null) { if (err != null) {
err.printStackTrace(); err.printStackTrace();
} }
//handle last error syntax data //handle last error syntax data
lastError = null; lastError = null;
if (res instanceof String) { if (res instanceof String) {
lastError = (String) res; 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 (!quickly) { if (!(res instanceof String)) {
//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 && getNext() != null) { if (locals != null) {
Variables.setLocalVariables(e, locals); Variables.setLocalVariables(e, locals);
} }
if (!(res instanceof String)) { if (!(res instanceof String)) {
@ -109,47 +141,20 @@ 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);
if (!quickly || !isSync) { //I think no longer needed as of 1.3.0, uncomment if something breaks
if (!isSync) {
Delay.addDelayedEvent(e); Delay.addDelayedEvent(e);
} }
execute(e); execute(e);
@ -335,7 +340,7 @@ public class EffExecuteStatement extends Effect {
} }
dataSource = (Expression<HikariDataSource>) exprs[1]; dataSource = (Expression<HikariDataSource>) exprs[1];
Expression<?> expr = exprs[2]; Expression<?> expr = exprs[2];
quickly = matchedPattern == 1; isSync = 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%] [[(using|with)] [a] driver %-string%]" * @pattern [the] data(base|[ ]source) [(of|at)] %string% [with [a] [max[imum]] [connection] life[ ]time of %timespan%]"
* @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,12 +33,11 @@ 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%]");
} }
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) {
@ -56,18 +55,11 @@ public class ExprDataSource extends SimpleExpression<HikariDataSource> {
} }
HikariDataSource ds = new HikariDataSource(); HikariDataSource ds = new HikariDataSource();
ds.setMaximumPoolSize(SkriptDB.getInstance().getConfig().getInt("thread-pool-size", 10));
// 30 minutes by default //allow specifying of own sql driver class name
ds.setMaxLifetime(SkriptDB.getInstance().getConfig().getInt("max-connection-lifetime", 1800000)); if (!SkriptDB.getInstance().getConfig().getString("sql-driver-class-name", "default").equals("default")) {
// 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) {
@ -104,7 +96,6 @@ 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,6 +30,10 @@ 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.4.0 version: 1.3.5
main: com.btk5h.skriptdb.SkriptDB main: com.btk5h.skriptdb.SkriptDB
depend: [Skript] depend: [Skript]
authors: [btk5h, FranKusmiruk, Govindas, TPGamesNL] authors: [btk5h, FranKusmiruk, Govindas, TPGamesNL]