Giter Site home page Giter Site logo

poggit / libasynql Goto Github PK

View Code? Open in Web Editor NEW
130.0 14.0 44.0 3.09 MB

Asynchronous MySQL access library for PocketMine plugins.

Home Page: https://poggit.github.io/libasynql

License: Apache License 2.0

PHP 100.00%
poggit pocketmine-plugins mysql-access mysqli php asynctask virion sqlite3 async-pool poggit-virion

libasynql's Introduction

libasynql

Asynchronous SQL access library for PocketMine plugins.

Why should I use this library and what does asynchronous mean?

When executing a SQL query on the main thread, there will be a delay to wait for the MySQL server or SQLite for interacting with the file system. The delay will block the main thread and cause lag to the server.

Libasynql uses different threads for executing the queries so the main thread will not lag!

Look in here as well if you want to learn more about threading.

Usage

The basic use of libasynql has 5 steps:

  1. Add default database settings in your config.yml.
  2. Write down all the SQL queries you will use in a resource file
  3. Initialize the database in onEnable().
  4. Finalize the database in onDisable().
  5. Obviously, and most importantly, use libasynql in your code.

Configuration

To let the user choose what database to use, copy the following into your default config.yml. Remember to change the default schema name under mysql.

database:
  # The database type. "sqlite" and "mysql" are supported.
  type: sqlite

  # Edit these settings only if you choose "sqlite".
  sqlite:
    # The file name of the database in the plugin data folder.
    # You can also put an absolute path here.
    file: data.sqlite
  # Edit these settings only if you choose "mysql".
  mysql:
    host: 127.0.0.1
    # Avoid using the "root" user for security reasons.
    username: root
    password: ""
    schema: your_schema
  # The maximum number of simultaneous SQL queries
  # Recommended: 1 for sqlite, 2 for MySQL. You may want to further increase this value if your MySQL connection is very slow.
  worker-limit: 1

Initialization and Finalization

libasynql simplifies the process of initializing a database into a single function call.

use pocketmine\plugin\PluginBase;
use poggit\libasynql\libasynql;

class Main extends PluginBase{
    private $database;

    public function onEnable(){
        $this->saveDefaultConfig();
        $this->database = libasynql::create($this, $this->getConfig()->get("database"), [
            "sqlite" => "sqlite.sql",
            "mysql" => "mysql.sql"
        ]);
    }

    public function onDisable(){
        if(isset($this->database)) $this->database->close();
    }
}

The \poggit\libasynql\libasynql::create() method accepts 3 parameters:

  • Your plugin main (basically $this if the code runs in onEnable())
  • The config entry where the database settings should be found (read the example from above)
  • An array for your SQL files. For each SQL dialect you are supporting, use it as the key, and use the path (or array of paths, relative to the resources folder) of the SQL files as the value. We are going to create them in the next step.

It returns a \poggit\libasynql\DataConnector object, which is the main query interface. You may store this object in a property for later use, $this->database for example.

In case of error, a ConfigException or an SqlError will be thrown. If not caught by the plugin, this will go straight out of onEnable() and disable the plugin. Therefore, make sure to check isset($this->database) before calling $this->database->close() in onDisable().

Creating SQL files

In the resources file, create one file for each SQL dialect you are supporting, e.g. resources/sqlite.sql and resources/mysql.sql.

Do I save the SQL files to the plugin data folder?

No, you do not have to copy the SQL files to the plugin data folder (i.e. do not add $this->saveResource("db.sql")). The files are read by libasynql from the phar resources directly.

Write down all the queries you are going to use in each file, using the Prepared Statement File format.

Calling libasynql functions

Finally, we are prepared to use libasynql in code!

There are 4 query modes you can ues: GENERIC, CHANGE, INSERT and SELECT.

  • GENERIC: You don't want to know anything about the query except whether it is successful. You may want to use this in CREATE TABLE statements.
  • CHANGE: Your query modifies the database, and you want to know how many rows are changed. Useful in UPDATE/DELETE statements.
  • INSERT: Your query is an INSERT INTO query for a table with an AUTO_INCREMENT key. You will receive the auto-incremented row ID.
  • SELECT: Your query expects a result set, e.g. a SELECT statement, or reflection queries like EXPLAIN and SHOW TABLES. You will receive a SqlSelectResult object that represents the columns and rows returned.

They have their respective methods in DataConnector: executeGeneric, executeChange, executeInsert, executeSelect. They require the same parameters:

  • The name of the prepared statement
  • The variables for the query, in the form of an associative array "variable name (without the leading colon)" => value
  • An optional callable triggered if the query succeeded, accepting different arguments:
    • GENERIC: no arguments
    • CHANGE: function(int $affectedRows)
    • INSERT: function(int $insertId, int $affectedRows)
    • SELECT: function(array $rows)
  • An optional callable triggered if an error occurred. Can accept an SqlError object.

Prepared Statement File Format

A Prepared Statement File (PSF) contains the queries that a plugin uses. The content is valid SQL, so it is OK to edit with a normal SQL editor.

The PSF is annotated by "command lines", which start with -- #, followed by the command symbol, then the arguments. Between the # and the command symbol, there can be zero to infinite spaces or tabs; between the command symbol and the arguments, there can also be zero to infinite spaces or tabs. Between every two arguments, one to infinite spaces or tabs are required.

Dialect declaration

A PSF always starts with a dialect declaration.

Symbol

!

Arguments

DIALECT

Possible values: mysql, sqlite

Example

-- #! mysql

Group declaration

Queries may be organized by groups. Each group has an identifier name, and a group can be stacked under another. Groups and queries under a group will be prepended the parent group's identifier plus a period in their own identifiers.

For example, if a parent group declares an identifier foo, and the child group/query declares an identifier bar, the real identifier for the child group/query is foo.bar.

Duplicate group identifier declarations are allowed, as long as the resultant queries do not have identical full identifiers.

Symbol

  • Start: {
  • End: }

Arguments (Start)

IDENTIFIER_NAME

The name of this group.

All characters except spaces and tabs are allowed, including periods.

Example

-- #{ group.name.here
	-- #{ child.name
		-- the identifier of the child group is "group.name.here.child.name"
	-- #}
-- #}

Note that PSF is insensitive about spaces and tabs, so this variant is equivalent:

-- #{ group.name.here
-- #    { child.name
		-- the identifier of the child group is still "group.name.here.child.name"
-- #    }
-- #}

Query declaration

A query is declared like a group. A query does not need to belong to a group, because the query can declare the periods in its own identifier, which has equivalent effect as groups.

Child groups are not allowed in a query declaration. In other words, a {} pair either has other group/query declarations inside, or has query text (and optionally variable declarations) inside. It cannot have both.

Symbol

  • Start: { (same as group declaration)
  • End: }

Arguments

Same arguments as a group declaration.

Variable declaration

A variable declaration declares the required and optional variables for this query. It is only allowed inside a query declaration.

Symbol

  • :

Arguments

VAR_NAME

The name of the variable. Any characters apart from spaces, tabs and colons are allowed. However, to comply with ordinary SQL editors, using "normal" symbols (e.g. variable names in other programming languages) is recommended.

VAR_TYPE

The variable type. Possible values:

  • string
  • int
  • float
  • bool
VAR_DEFAULT

If the variable is optional, it declares a default value.

This argument is not affected by spaces. It starts from the first non-space non-tab character after VAR_TYPE, and ends before the trailing space/tab characters of the line

string default

There are two modes, literal string and JSON string.

If the argument starts with a " and ends with a ", the whole argument will be parsed in JSON. Otherwise, the whole string is taken literally.

int default

A numeric value that can be parsed by (int) cast, equivalent to intval.

float default

A numeric value that can be parsed by (float) cast, equivalent to floatval.

bool default

true, on, yes or 1 will result in true. Other values, as long as there is something, will result default false. (If there is nothing, the variable will not be optional)

Example of using variables

SQL file
-- #! sqlite
-- #{ example
-- #    { insert
-- # 	  :foo string
-- # 	  :bar int
INSERT INTO example(
	foo_column
	bar_column
) VALUES (
	:foo,
	:bar
);
-- #    }
-- #    { select
-- # 	  :foo string
-- # 	  :bar int
SELECT * FROM example
WHERE foo_column = :foo
LIMIT :bar;
-- #    }
-- #}
Code
// Example of using variable in insert statements
$this->database->executeInsert("example.insert", ["foo" => "sample text", "bar" => 123]);

// Example of using variable in select statements
$this->database->executeSelect("example.select", ["foo" => "sample text", "bar" => 1], function(array $rows) : void {
  foreach ($rows as $result) {
    echo $result["bar_column"];
  }
});

Query text

Query text is not a command, but the non-commented part between the start and end commands of a query declaration.

Variables are interpolated in query text using the :var format. Note that libasynql uses a homebrew algorithm for identifying the variable positions, so they might be inaccurate.

-- #{ query.declarartion
SELECT * FROM example;
-- The line above is a query text
-- #}

Things to beware

Race condition

public $foo = 'bar';

public function setFoo() : void {
	$this->foo = 'foo';
}

public function getFoo() : string {
	return $this->foo;
}
$this->database->executeGeneric("beware.of.race_condition", [], function() : void {
	$this->setFoo();
});
echo $this->getFoo();

The result will be bar because the queries are run asynchronously. The code on the main thread will run earlier than it.

To make the code give a correct result, you have to ensure $this->setFoo() runs before echo $this->getFoo(). The appropriate way is to move getFoo() into the callback function, just like below:

$this->database->executeGeneric("beware.of.race_condition", [], function() : void {
	$this->setFoo();
	echo $this->getFoo();
});

Returning the result from callback

Due to race condition (as explained in the section above), it is not viable to return the result nor use it beyond the scope of the callback. If you are aiming to create an API function or seek to share the results with other parts of the code, it is advisable to convert the code into callbacks as well:

public function myAPI(\Closure $userCallback)
    $this->database->executeSelect("beware.of.return_from_callback", [], function($result) use ($userCallback) : void {
	    $userCallback($result);
    });

    // Simpler versions:
    $this->database->executeSelect("beware.of.return_from_callback", [], fn($result) => $userCallback($result));
    $this->database->executeSelect("beware.of.return_from_callback", [], $userCallback);
}

Callbacks will clutter your code

While using callbacks might be one straightforward solution to your problems, there exists a significant trade off โ€” you must sacrifice the code readability. Hence, we recommend learning the async/await code style and use it to reduce the mess.

Featured examples

libasynql's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

libasynql's Issues

SQLite3 Parameters

Describe the bug
A clear and concise description of what the bug is.
poggit/libasynql/sqlite3/Sqlite3Thread.php on line 83, SQLite3Stmt->bindValue(), third parameter is not set (type), the default is null, therefor every value of the keys in the array would be null
Sample code

$this->connector->executeInsert(
    Queries::STATS_SET_TEST,
    [":name" => $player->getName()]
);

Expected behavior
A clear and concise description of what you expected to happen.
Parameters should be binded properly and not always set to null
Environment
OS:
Win10
PocketMine version:
3.7.0

Leverage query preparation

Queries that do not change dynamically (basically all queries without list: parameters) can be prepared into a mysqli_stmt/Sqlite3Stmt in advance. This reduces the CPU load and roundtrip time.

Abolish the use of parameter escapes completely.

Currently, all parameters except MySQL strings are formatted on the main thread and interpolated into the query.

This is dangerous as it increases the risk of SQL injection attacks. Although there is no immediately known impact, this increases the risk in the future and relies on the correct implementation of integer formatting code, which is not guaranteed to be secure.

Furthermore, bound parameters do not need a formatting-parsing roundtrip and is likely to bring better performance for the query sending process.

Type juggling is not documented

libasynql attempts to fix "reasonable" types for column entries, but "reasonable" is still not explicit and should be documented.

Add SQL Transaction

Is your feature request related to a problem? Please describe.
I tried to make Payment System like EconomyAPI (/pay).
A: doing pay to B
B: receive pay from A
It need 2 query (
Reduce money A
Add money B )
, but when 1 query executed the server crash, A money reduced and B money not added.

I need startTransaction function and callback if query failed.

What is this error?

Describe the bug
I execute on display this error.

*Sample code
InvalidArgumentException: "The query players.init has not been loaded" (EXCEPTION) in "virions/libasynql.phar/src/poggit/libasynql/base/DataConnectorImpl" at line 174

Environment
OS: Windows 10
PocketMine version: lastest

Server crashes on PHP 8.1 due to not declaring return type of JsonSerializable::jsonSerialize()

Describe the bug
A clear and concise description of what the bug is.

Fatal error: During inheritance of JsonSerializable: Uncaught ErrorException: Return type of poggit\libasynql\generic\GenericStatementImpl::jsonSerialize() should either be compatible with JsonSerializable::jsonSerialize(): mixe
d, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in C:\Users\minjae\Desktop\Projects\MCBE\MCBE\PocketMine-MP-Projects\CrushDev\virions\libasynql\src\poggit\libasynql\generic\GenericStatementImpl.php:246

Sample code
N/A
Expected behavior
The server should not crash

Environment
OS: Windows
PocketMine version: 4.10.0
PHP: 8.1

Select queries (using variables) taking a very long time.

Describe the bug
Select queries (using variables) taking a very long time.

Prepared statement that I'm using:

-- #{ get.players
-- # :player string
SELECT * FROM players WHERE player=:player;
-- #}

Code

Main::getInstance()->database->executeSelect(
            "skyblock.get.players",
            ["player" => $player->getName()],
            function(array $rows) : void {
                var_dump("give me something i beg");
            },
            function($e) : void {
                var_dump("gelp");
            }
        );

SQLite File: https://anonfiles.com/7bw2D4J4yc/data_sqlite
Prepared Statements File: https://anonfiles.com/Zdx8DfJ8y0/sqlite_sql

Expected behavior
Either "give me something i beg" or "gelp" gets printed to the console.

Neither gets printed, even after waiting 5 minutes.

Server does not freeze or lag.

The IGN is in the database

Environment
OS: Windows 10 (Version 10.0.19042)
PocketMine version: 4.10.1

Cannot insert UUID into database

Describe the bug
I tried inserting my uuid into my database but it failed. I got this error:

[16:55:49] [Server thread/CRITICAL]: RuntimeException: "Unsupported variable type" (EXCEPTION) in "C:/Users/levi/Documents/pocketmine/virions/libasynql_dev-97.phar/src/poggit/libasynql/generic/MysqlStateme
ntImpl" at line 106
code:
-- # {create
-- # {players
-- # :uuid UUID
INSERT INTO players(UUID, first_joined)
VALUES(:uuid, CURRENT_TIMESTAMP
);
-- # }
-- # }
-- # }
and when i change UUID into string it gives me the following error.

SQL PREPARE error: Cannot bind value of type object, for query INSERT INTO players
Expected behavior
No error. Able to insert uuid into database
Environment
OS: Windows 10
PocketMine version: PocketMine-MP 3.6.2 for Minecraft: Bedrock Edition v1.9.0 (protocol version 332)

Sqlite crashes when `strlen(file) === 1`

Describe the bug
Only on Windows, the libasynql::create function crashes with the following config

type: "sqlite"
sqlite:
    file: "a" #NOTICE: `file` is a single character

with this error

ErrorException: "Uninitialized string offset 1" (EXCEPTION) in "C:/<pocketmine-dir>/virions/libasynql_dev-174.phar/src/poggit/libasynql/libasynql" at line 162

libasynql.php The comment marks the ErrorException.

private static function resolvePath(string $folder, string $path) : string{
    if($path[0] === "/"){
        return $path;
    }
    if(Utils::getOS() === "win"){
        if($path[0] === "\\" || $path[1] === ":"){
            // ErrorException thrown Above! ^^^^^^^ 
            return $path;
        }
    }
    return $folder . $path;
}

Another Bug
Furthermore, having file: "" throws an error on all platforms, but that config is invalid anyways. It's arguable if that should be fixed too.

Expected behavior
This should not crash.

Environment
OS: "win"
PocketMine version: "4.2.0"

The variable `foo` is not used anywhere in the query! Check for typos.

Stack Error Message

Error: The variable kills is not used anywhere in the query! Check for typos.
File: virions/src/poggit/libasynql/generic/GenericStatementImpl
Line: 187

Table

-- #{ setKills
-- #	:player_name string
-- #    :kills int
INSERT OR IGNORE INTO Practice (player_name, kills) VALUES (:player_name, 0);
-- #&
UPDATE Practice SET kills = :kills WHERE player_name = :player_name;
-- #}
-- #{ getKills
-- #	:player_name string
SELECT kills FROM Practice WHERE player_name = :player_name;โ€Š
-- #}

Code

<?php

declare(strict_types=1);

namespace PrideCore\Utils;

use poggit\libasynql\libasynql;
use poggit\libasynql\DataConnector;
use pocketmine\utils\SingletonTrait;
use pocketmine\utils\Config;
use PrideCore\Core;

class Database {
    use SingletonTrait;
    
    public function getDatabase() : DataConnector{
        return libasynql::create(Core::getInstance(), $this->getConfig()->get("database"), [
			"sqlite" => "sqlite.sql",
			"mysql" => "mysql.sql",
		]);
    }
    
    public function getConfig() : Config{
        return new Config(Core::getInstance()->getDataFolder() . "database.yml", Config::YAML);
    }
    
    public function init() : void{
        $this->getDatabase()->executeGeneric("init");
        $this->getDatabase()->waitAll();
    }
}

Screenshots or Links

Discord Support Images

People who are helping me to indentify and fix the bug:
Screenshot_2022_0912_221124

Screenshot_2022_0912_220952

High CPU on Linux hosts only.

Describe the bug
Servers running Linux have High CPU usage when a server containing this is running.

Expected behavior
CPU to stay at a lower level than what the allocated CPU is for the instances.

Other Info
Me and matcracker looked into this issue on his plugin BedCoreProtect and found that the viron only affects Linux hosts.

Environment
OS: Linux Ubuntu 16.04, 18.04, 19.04 CentOS 7
PocketMine versions: 3.0.0 - 3.11.1

Sync initial connection can cause *very* long waits

Describe the bug
During initial construction of the libasynql if you have a internet connection issue or DB issue you can expect waits of up to 3 minutes PER plugin that uses libasynql.

Sample code

$this->db = libasynql::create($plugin, @yaml_parse_file($plugin->getDataFolder()."mysql.yml"), [
	"sqlite" => "sqlite.sql",
	"mysql" => "mysql.sql"
]);

Expected behavior
Smaller timeout or connection made async (not sure how its done backend)

Environment
OS: Windows 10
PocketMine version: 3.19.0
PHP: 8.0.3

Log

[17:22:26] [Server thread/INFO]: Loading FusionCore v1.0.0
[17:22:26] [Asynchronous Worker #0 thread/DEBUG]: Set memory limit to 256 MB
status
[17:24:49] [Server thread/CRITICAL]: FusionCraft\Core\vendor9a5a9370\poggit\libasynql\SqlError: "SQL CONNECT error: MySQL server has gone away" (EXCEPTION) in "plugins/FusionCore.phar/src/FusionCraft/Core/vendor9a5a9370/p
oggit/libasynql/libasynql" at line 141
[17:24:49] [Server thread/CRITICAL]: #0 plugins/FusionCore.phar/src/FusionCraft/Core/SharedDatabase(29): FusionCraft\Core\vendor9a5a9370\poggit\libasynql\libasynql::create(string[31] object(FusionCraft\Core\Plugin), array
[4], array[2])
[17:24:49] [Server thread/CRITICAL]: #1 plugins/FusionCore.phar/src/FusionCraft/Core/Plugin(24): FusionCraft\Core\SharedDatabase->__construct(string[31] object(FusionCraft\Core\Plugin))
[17:24:49] [Server thread/CRITICAL]: #2 pmsrc/src/pocketmine/plugin/PluginManager(182): FusionCraft\Core\Plugin->onLoad()
[17:24:49] [Server thread/CRITICAL]: #3 pmsrc/src/pocketmine/plugin/PluginManager(346): pocketmine\plugin\PluginManager->loadPlugin(string[81] C:\Users\Jack\Documents\PocketMine\FusionCraft-PMMP3-PHP8\plugins\FusionCore.p
ha, array[2])
[17:24:49] [Server thread/CRITICAL]: #4 pmsrc/src/pocketmine/Server(1529): pocketmine\plugin\PluginManager->loadPlugins(string[66] C:\Users\Jack\Documents\PocketMine\FusionCraft-PMMP3-PHP8\plugins\)
[17:24:49] [Server thread/CRITICAL]: #5 pmsrc/src/pocketmine/PocketMine(291): pocketmine\Server->__construct(string[23] object(BaseClassLoader), string[35] object(pocketmine\utils\MainLogger), string[58] C:\Users\Jack\Doc
uments\PocketMine\FusionCraft-PMMP3-PHP8\, string[66] C:\Users\Jack\Documents\PocketMine\FusionCraft-PMMP3-PHP8\plugins\)
[17:24:49] [Server thread/CRITICAL]: #6 pmsrc/src/pocketmine/PocketMine(321): pocketmine\server()
[17:24:49] [Server thread/CRITICAL]: #7 pmsrc(11): require(string[113] phar://C:/Users/Jack/Documents/PocketMine/FusionCraft-PMMP3-PHP8/PocketMine-MP.p)
[17:24:49] [Server thread/CRITICAL]: Could not load plugin 'FusionCore'

Take note of timestamps, if several plugins were to have libasynql and they all failed like above during initial connection that could be an extremely long wait before the user can interact with the server (stop command etc)

Repeating block

The problem

It is common to need to generate SQL queries dynamically. This matters when there is a dynamic number of arguments. There are four common use patterns:

-- IN lists
SELECT name FROM players WHERE id IN (:id1, :id2, ...);

-- Multiple INSERT values
INSERT INTO player_ips (name, ip, time) VALUES
('SOFe', '127.0.0.1', '2021-12-11T14:37:15'),
('SOFe', '::', '2021-12-11T14:37:16');

-- Multiple UPDATE cases
UPDATE player_money
SET money = CASE
  WHEN name = 'SOFe' THEN 10
  WHEN name = 'PotterHarry' THEN 8
END CASE
WHERE name IN ('SOFe', 'PotterHarry');

-- Multiple JOIN tables
SELECT id FROM labels t0
  INNER JOIN labels t1 USING (id)
  INNER JOIN labels t2 USING (id)
WHERE
  t0.name = 'name' AND t0.value = 'SOFe'
  AND t1.name = 'type'
  AND t2.name = 'currency' AND t2.value = 'dollar';

Current workarounds

Currently libasynql provides two hacks.

The first hack is the list:T type, which accepts arguments as a list of scalar values, and dynamically generates (?, ?, ?, ...) depending on the number of arguments in the list. This only solves the first case, but does not help with the 3 other cases, whilst complicating the internal query formatter.

The second hack is executeXxxRaw, which accepts a raw query string and argument list. However, it suffers from several problems:

  • The methods are not documented at all. It is unclear how the argument placeholders should look like.
  • Argument placeholders are actually dialect-dependent, which defeats the point of libasynql.
    • @matcracker how did you even manage to make it work with BedcoreProtect?

Proposed solution

Inspired by the Rust decl-macro syntax, I would like to introduce the + ("foreach") PSF command for repetitions. The formal BNF definition is as follows:

foreach_command := foreach_command_start | foreach_command_end

foreach_command_start := "+" WHITESPACE* "(" WHITESPACE* (label WHITESPACE+)? argument_list
label := [A-Za-z_][A-Za-z0-9_]*
argument_list := ":" argument_identifier WHITESPACE* range_specifier? (WHITESPACE+ ":" argument_identifier WHITESPACE* range_specifier?)*
argument_identifier := [A-Za-z_][A-Za-z0-9_]*
range_specifier := WHITESPACE* "[" WHITESPACE* start_index? WHITESPACE* ".." WHITESPACE* end_index? WHITESPACE* "]"
start_index := -?[0-9]+
end_index := -?[0-9]+

foreach_command_end := "+" WHITESPACE* ")" WHITESPACE* delimiter
delimiter := [^ \t]*

Examples for each of the four use cases above:

-- #{ in_lists
-- #    :id list:string
SELECT name FROM players WHERE id IN (
-- #    +( :id
:id
-- #    +) ,
);
-- #}

-- #{ multiple_insert_values
-- #    :name string
-- #    :ip list:string
-- #    :time list:string
INSERT INTO player_ips (name, ip, time) VALUES
-- #    +( :ip :time
(:name, :ip, :time)
-- #    +) ,
;
-- #}

-- #{ multiple_update_cases
-- #    :name list:string
-- #    :money list:int
UPDATE player_money
SET money = CASE
-- #    +( :name :money
  WHEN name = :name THEN :money
-- #    +)
END CASE
WHERE name IN ('SOFe', 'PotterHarry');
-- #}
-- #{ multiple_join_tables
-- #    :name list:string
-- #    :value list:optional:string
SELECT id FROM labels t0
-- #    +( :name[1..]
  INNER JOIN labels t$$name$$ USING (id)
-- #    +)
WHERE
-- #    +( nv_pairs :name :value
  t##nv_pairs##.name = :name
-- #        +( :value
    AND t##nv_pairs##.value
-- #        +)
-- #    +) AND
  INNER JOIN labels t2 USING (id)
WHERE
  t0.name = 'name' AND t0.value = 'SOFe'
  AND t1.name = 'type' AND t1.value = 'cash'
  AND t2.name = 'currency' AND t2.value = 'dollar';
-- #}

Zipping parameters

In the multiple_update_cases example, +( :name :money zips the lists name and money together, equivalent to

if(count($nameList) !== count($moneyList)) throw new Exception;
for($i = 0; $i < count($nameList); $i++) {
    $name = $nameList[$i];
    $money = $moneyList[$i];
    write_body($name, $money);
}

Ranges

Four types of ranges can be specified.

  • Bounded ranges
    • [1..3] is equivalent to array_slice($list, 1, 3), which only repeats for $list[1] and $list[2]. Note that the right bound is exclusive.
    • Negative bounds are also allowed. See array_slice for the precise description.
  • Left-bounded ranges
    • [1..] is equivalent to array_slice($list, 1), which simply skips the first item.
    • Negative bounds are also allowed.
  • Right-bounded ranges
    • [..-1] is equivalent to [0..-1].
  • Unbounded ranges
    • [..] includes the full list. This is actually useless, because it is equivalent to not writing it. This syntax is reserved for possible syntax enhancements in the future.

##label## and $$label:argument$$

##label## is a special symbol that gets replaced with the iteration order, starting from 0, regardless of range specifiers. It is replaced blindly regardless of context, so it can be used inside other identifiers, like the t## above. For unlabelled loops, use three hashes ###.

$$label:argument$$ is similar to ##label##, but it takes the range index of a specific argument. It is also replaced blindly regardless of context. Only $$argument$$ is required if the loop is unlabelled, in which case the innermost loop of :argumentis used for the index (this should cause an error if the innermost loop of:argument` is labelled).

Nesting

Foreach commands can be nested, for different parameters or for the same parameter. This means lists of lists are allowed.

optional:

I also propose the optional: type modifier, which is similar to list:, but transforms a nullable value to a list through fn($x) => $x === null ? [] : [$x].

not saving onDisable() saves on crash

so i save my data to sqlite onDisable() and I noticed that when servers crash unexpectedly, the data don't save at onDisable(). is ondisable ignored when pocketmine server crash?

Making sure everything has been executed before closing the server.

I have this in my onDisable()

$this->getMySQLProvider()->getConnector()->executeChange(Queries::UPDATE_ALL, [
"uuid" => $this->uuid,
"name" => $this->name,
"balance" => $this->balance,
]);
$this->getMySQLProvider()->getConnector()->waitAll();

However, the data doesn't update when I close the sever properly with /stop. Is there a way to make sure every player's data has been updated before fully closing the server? I've even used the waitAll() function.

Warning: Use of unshaded libasynql detected. Debug mode is enabled. This may lead to major performance drop. Please use a shaded package in production. See https://poggit.pmmp.io/virion for more information.

    /** @var Config */
    public $database;`
` libasynql::create($this, $this->database->get("database"), [
            "mysql" => "mysql.sql",
            "sqlite" => "sqlite3.sql"
        ]);

config

database:
  type: mysql
  sqlite:
    file: data.sqlite
  mysql:
    host: 127.0.0.1
    username: root
    password: ""
    schema: mcpe_test # remember to change this!
worker-limit: 2`
error

[20:53:44] [Server thread/CRITICAL]: Error: "Call to a member function get() on null" (EXCEPTION) in "core/src/gay/MainClass" at line 102

line 102 on onEnable()
    libasynql::create($this, $this->database->get("database"), [
        "mysql" => "mysql.sql",
        "sqlite" => "sqlite3.sql"
    ]);

Race condition when creating a connection

Describe the bug
Due to the recent PR @CortexPE made, there's a race condition that can cause the server to crash on startup or when launching another worker.

Sample code
Code:

libasynql::create($plugin, array('type' => 'mysql', 'mysql' => ['host' => $credentials[0], 'username' => $credentials[1], 'password' => $credentials[2], 'schema' => $credentials[3]], 'worker-limit' => 4), [ 'mysql' => 'mysql.sql' ]);

Sometimes returns:

Fatal error: Uncaught TypeError: unserialize() expects parameter 1 to be string, null given in phar:///home/plugins/NGEssentials.phar/src/_48ad7499cbf87afcbea6poggit/libasynql/mysqli/MysqliThread.php:72

Stack trace:
#1 phar:///home/plugins/NGEssentials.phar/src/_48ad7499cbf87afcbea6poggit/libasynql/base/SqlSlaveThread.php(69): _48ad7499cbf87afcbea6poggit\libasynql\mysqli\MysqliThread->createConn(NULL),#0 phar:///home/plugins/NGEssentials.phar/src/_48ad7499cbf87afcbea6poggit/libasynql/mysqli/MysqliThread.php(72): unserialize(NULL)

#2 [internal function]: _48ad7499cbf87afcbea6poggit\libasynql\base\SqlSlaveThread->run()

#3 {main} thrown in phar:///home/plugins/NGEssentials.phar/src/_48ad7499cbf87afcbea6poggit/libasynql/mysqli/MysqliThread.php on line 72

Expected behavior
No race conditions.

Environment
OS: debian:latest
PocketMine version: https://github.com/pmmp/PocketMine-MP/releases/tag/3.12.0

SQLite3 column type unavailable before first fetch

Describe the bug
Sqlite files dont load, including piggyauctions and chestshop.

Sample code

Undefined offset: 0 in phar:///home/container/plugins/ChestShop.phar/src/muqsit/chestshop/libs/poggit/libasynql/sqlite3/Sqlite3Thread.php on line 120
Fatal error: Uncaught TypeError: Argument 2 passed to muqsit\chestshop\libs\poggit\libasynql\result\SqlColumnInfo::__construct() must be of the type string, null given, called in phar:///home/container/plugins/ChestShop.phar/src/muqsit/chestshop/libs/poggit/libasynql/sqlite3/Sqlite3Thread.php on line 120 and defined in phar:///home/container/plugins/ChestShop.phar/src/muqsit/chestshop/libs/poggit/libasynql/result/SqlColumnInfo.php:37

Expected behavior
The sqlite file loading and storing data.

Environment
OS: Linux
PocketMine version: 1.12.0

Error: "Unsupported variable type" when type is valid.

Description:
I am executing a 'Insert' query with 2 string variables. Valid variable types are being passed to those variables, but I am getting a "Unsupported variable type" exception.

Sample code

public function registerPlayer($player, string $setting): void{
        if($player instanceof Player) $player = $player->getName();
        if(!$this->isPlayerRegistered($player)){
            $this->playerData[strtolower($player)] = ["username" => $player, "setting" => $setting];
            $this->database->executeInsert("manage.register", ["player" => $player, "setting" => $setting]);
        }
    }
-- #    { register
-- #      :player string
-- #      :setting string
INSERT INTO player_form_settings
VALUES
(
username = :player,
setting = :setting
);
-- #    }
$this->registerPlayer("Dapro5173", "formui");

Expected behavior
A row is inserted into the table: username -> Dapro5173, setting -> formui

Environment
OS: Windows 10
PocketMine version: 3.18.0

Add curl options

hello, I open this issue to ask you if in the next update it would be possible to add a curl option like that the developers using the discord/google/facebook api do not lag because for example I use my propore api to retrieve data from my database (security issue :))

Thank you in advance for your reply

waitAll();

I use waitAll on plugin disable.
should i call this before query or after?

More than two queries can not access the same data.

Hi! After a lot of tests in my plugins, I can assert that more than two queries can't access the same data.
I'll explain.

Code Structure:

  • PlayerData class
//This method allow to set player data to database.
public function setData(Player $player, array $data){
       $this->provider->setPlayerData($player, $data);
}

public function getData(Player $player) : array{
       $this->provider->getPlayerData($player);
}
  • Provider class
//This method allow to set player data to database.
public function setPlayerData(Player $player, array $data){
       $data1 = $data["Foo"];
       $data2 = $data["Bar"];
       $query = "INSERT INTO tblName (username, data1, data2) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE data1=VALUES(data1), data2 = VALUES(data2)";
       $task = new DirectQueryMysqlTask($this->credentials, $query, [["s", $player->getName()], ["s", $data1], ["s", $data2]]);
       $this->plugin->getServer()->getScheduler()->scheduleAsyncTask($task);
}

public function getPlayerData(Player $player){
        $data = array();
        //Requesting data from database, etc...
        return $data;
}
  • Main class
public function onPlayerJoin(PlayerJoinEvent $event){ //It's only an example but it can be any method or event
     $p = $event->getPlayer();
     $pd = new PlayerData();
     $arr_data1 = $pd->getData($p);
     $arr_data1["Foo"] = "Hello";
     $pd->setData($p, $arr_data1);
     $arr_data2 = $pd->getData($p);
     $arr_data2["Bar"] = "world!";
     $pd->setData($p, $arr_data2);
}

Table structure

  • Table with already data inserted
Player Foo Bar
matcracker foo bar
  • What should happen
Player Foo Bar
matcracker Hello world!
  • What really happens
Player Foo Bar
matcracker foo world!

Issue

This isn't the real plugin code but I can confirm you that is what really happen.

So, how you can see from the tables, $arr_data2 isn't immediatly updated from $arr_data1. But using prepared statement as this library do, shouldn't the queries join an execution queue?
So, until $arr_data1 isn't executed, $arr_data2 will wait. But this isn't happening, why?

Thank you for you help, Matt.

Warning: Unterminated comment starting line 31 in phar:///home/minecraft/plugins/DanBamPrison_v1.0.0.phar/src/hybrid/libs/poggit/libasynql/result/SqlChangeResult.php on line 31

Warning: Unterminated comment starting line 31 in phar:///home/minecraft/plugins/DanBamPrison_v1.0.0.phar/src/hybrid/libs/poggit/libasynql/result/SqlChangeResult.php on line 31
 License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

declare(strict_types=1);

namespace hybrid\libs\poggit\libasynql\result;

use hybrid\libs\poggit\libasynql\SqlResult;

class SqlChangeResult extends SqlResult{
        private $affectedRows;

        public function __construct(int $affectedRows){
                $this->affectedRows = $affectedRows;
        }

        public function getAffectedRows() : int{
                return $this->affectedRows;
        }
}

Fatal error: Uncaught Error: Class 'hybrid\libs\poggit\libasynql\result\SqlChangeResult' not found in phar:///home/minecraft/plugins/DanBamPrison_v1.0.0.phar/src/hybrid/libs/poggit/libasynql/sqlite3/Sqlite3Thread.php:100
Stack trace:
#0 phar:///home/minecraft/plugins/DanBamPrison_v1.0.0.phar/src/hybrid/libs/poggit/libasynql/base/SqlSlaveThread.php(85): hybrid\libs\poggit\libasynql\sqlite3\Sqlite3Thread->executeQuery(Object(SQLite3), 1, 'UPDATE HybridPE...', Array)
#1 [internal function]: hybrid\libs\poggit\libasynql\base\SqlSlaveThread->run()
#2 {main}
  thrown in phar:///home/minecraft/plugins/DanBamPrison_v1.0.0.phar/src/hybrid/libs/poggit/libasynql/sqlite3/Sqlite3Thread.php on line 100

Thread crashing instead of returning errors.

Describe the bug

Stack trace:
#0 [internal function]: pocketmine\errorhandler\ErrorToExceptionHandler::handle(2, 'SQLite3Stmt::ex...', 'D:\\projects\\poc...', 89)
#1 D:\projects\pocketmine-dev-env-new\virions\libasynql\src\poggit\libasynql\sqlite3\Sqlite3Thread.php(89): SQLite3Stmt->execute()
#2 D:\projects\pocketmine-dev-env-new\virions\libasynql\src\poggit\libasynql\base\SqlSlaveThread.php(88): poggit\libasynql\sqlite3\Sqlite3Thread->executeQuery(Object(SQLite3), 2, 'INSERT\nINTO pla...', Array)
#3 phar://D:/projects/pocketmine-dev-env-new/PocketMine-MP.phar/src/thread/CommonThreadPartsTrait.php(93): poggit\libasynql\base\SqlSlaveThread->onRun()
#4 [internal function]: pocketmine\thread\Thread->run()
#5 {main}
  thrown in D:\projects\pocketmine-dev-env-new\virions\libasynql\src\poggit\libasynql\sqlite3\Sqlite3Thread.php on line 89

Sample code
Query:

INSERT
INTO players (xuid, ip) 
VALUES (:xuid, :ip)

Table:

CREATE TABLE IF NOT EXISTS players (
	xuid VARCHAR(255) NOT NULL PRIMARY KEY ON CONFLICT ABORT,
	ip VARCHAR(255) NOT NULL,
	permBanned BOOLEAN NOT NULL DEFAULT FALSE,
	unbanAt INT NOT NULL DEFAULT 0,
	warns INT NOT NULL DEFAULT 0
)

Expected behavior
Errors would be returned in the onError function.

Environment
OS: Windows
PocketMine version: 4.7.0 / 1.19.20

New Class Loader changes

Describe the bug
Class Loader was changed to support registering multiple ClassLoaders to a thread.
Sample code
pmmp/PocketMine-MP@5fbc768
Expected behavior
The plugin should work, not crash.

Environment
OS: Windows 10
PocketMine version: 4.0.0+dev

Cannot register stored procedure

Describe the bug
Cannot register stored procedure

Sample code

$this->db->executeGeneric("coral_reef.init.functions.values.add");
-- #!mysql
-- #{ coral_reef
-- #    { init
-- #        { functions
-- #            { values
-- #                { add
DELIMITER //
CREATE PROCEDURE "add_value"(IN _xuid BIGINT, IN _type VARCHAR(99), IN _subtype VARCHAR(99), IN _value INT)
BEGIN
    SELECT value
    INTO @get_value
    FROM VIRTUAL_VALUES
    WHERE xuid = _xuid
      AND type = _type
      AND subtype = _subtype;

    SET @int_value = CAST(@get_value AS SIGNED) + _value;

    INSERT INTO VIRTUAL_VALUES
    VALUES (_xuid, _type, _subtype, @int_value)
    ON DUPLICATE KEY UPDATE value = @int_value;
END //
DELIMITER ;
-- #                }
-- #            }
-- #        }
-- #    }
-- #}

Expected behavior
register stored procedure

Environment
OS: docker(debian)
PocketMine version: 3.25.0

Error contents

SQL EXECUTION error: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'DELIMITER //
CREATE PROCEDURE "add_value"(IN _xuid BIGINT, IN _type VARCHAR(9...' at line 1, for query DELIMITER //
CREATE PROCEDURE "add_value"(IN _xuid BIGINT, IN _type VARCHAR(99), IN _subtype VARCHAR(99), IN _value INT)
BEGIN
SELECT value
INTO @get_value
FROM VIRTUAL_VALUES
WHERE xuid = _xuid
AND type = _type
AND subtype = _subtype;
SET @int_value = CAST(@get_value AS SIGNED) + _value;
INSERT INTO VIRTUAL_VALUES
VALUES (_xuid, _type, _subtype, @int_value)
ON DUPLICATE KEY UPDATE value = @int_value;
END //
DELIMITER ; | []

When I ran this query directly, it registered successfully
image

bccomp() type error on certain MySQL column types

Describe the bug

image

Fatal error: Uncaught TypeError: bccomp() expects parameter 1 to be string, int given in phar:///home/container/virions/libasynql.phar/src/poggit/libasynql/mysqli/MysqliThread.php:186
Stack trace:
#0 phar:///home/container/virions/libasynql.phar/src/poggit/libasynql/mysqli/MysqliThread.php(186): bccomp(1, '922337203685477...')
#1 phar:///home/container/virions/libasynql.phar/src/poggit/libasynql/mysqli/MysqliThread.php(224): poggit\libasynql\mysqli\MysqliThread->poggit\libasynql\mysqli\{closure}(1)
#2 phar:///home/container/virions/libasynql.phar/src/poggit/libasynql/mysqli/MysqliThread.php(155): poggit\libasynql\mysqli\MysqliThread->toSelectResult(Object(mysqli_result))
#3 phar:///home/container/virions/libasynql.phar/src/poggit/libasynql/base/SqlSlaveThread.php(85): poggit\libasynql\mysqli\MysqliThread->executeQuery(Object(mysqli), 3, 'SELECT record_i...', Array)
#4 [internal function]: poggit\libasynql\base\SqlSlaveThread->run()
#5 {main}
thrown in phar:///home/container/virions/libasynql.phar/src/poggit/libasynql/mysqli/MysqliThread.php on line 186

Sample code

-- # 			{ pre_registered
-- # 			  :username string
SELECT record_id
FROM pre_registered
WHERE username = :username
LIMIT 1;
-- # 			}

Expected behavior

record_id is a unsigned bigint, the error didn't occur when no result is selected

Environment

OS: linux and idk other stuffs (Same result on Windows10)
PocketMine version: 3.14.2
Libasynql version: 3.3.1

Server does not cascade shutdown or recover when SQL workers crash

Describe the bug
A clear and concise description of what the bug is.

I accidentally changed the type of $queries for executeSelectRaw from list<string> to array{list<string>}. The query was serialized and unserialized in the worker send queue as-is, and only caused a crash when actually running in MysqliThread::executeQuery. This only crashes the worker thread, but the server keeps on running. If this happens in production scenario, all SQL workers will get stuck and all queries will end up taking forever to run, and server admin has no way to know what's wrong except by reading the crash output on the stdout, which does not even get sent to the PocketMine logger.

Environment
hub.docker.com/pmmp/pocketmine-mp:4.0.2
OS: Ubuntu
PocketMine version: 4.0.2

PM5

Describe the bug
bug with use asyncInsert

Error: "Class "SOFe\AwaitGenerator\Await" not found" (EXCEPTION) in "D:/Codage/Codes/minecraft/pm5/pm5-aetherium/virions/libasynql/src/poggit/libasynql/base/DataConnectorImpl" at line 162

Bug

I Think I Got A Bug

Fatal error: Allowed memory size of 1073741824 bytes exhausted (tried to allocate 20480 bytes) in phar:///home/container/plugins/LunarSkyblock.phar/src/Vecnavium/SkyBlocksPM/libs/poggit/libasynql/base/DataConnectorImpl.php on line 166

Data Loss in rows

Describe the bug
it didn't return the full rows but returned the full ColumnInfo

Sample code

$this->dataConnector->executeSelect(SQLQueries::SELECT_FACTION_ID_DATA, [
				"id" => $id], $closure);
});

Expected behavior
return the rows

trace

#2 plugins/Factions/src/Ali/Factions/provider/mysql/SQLFaction(35): Ali\Factions\faction\FactionsFactory->Ali\Factions\faction\{closure}(array[1])
#3 plugins/NetworkCore/src/Ali/NetworkCore/libraries/libasynql/base/DataConnectorImpl(174): Ali\Factions\provider\SQLProvider->Ali\Factions\provider\mysql\{closure}(array[1], array[9])
#4 plugins/NetworkCore/src/Ali/NetworkCore/libraries/libasynql/base/DataConnectorImpl(181): Ali\NetworkCore\libraries\libasynql\base\DataConnectorImpl::Ali\NetworkCore\libraries\libasynql\base\{closure}(object Ali\NetworkCore\libraries\libasynql\result\SqlSelectResult#131153)
#5 plugins/NetworkCore/src/Ali/NetworkCore/libraries/libasynql/base/DataConnectorImpl(239): Ali\NetworkCore\libraries\libasynql\base\DataConnectorImpl::Ali\NetworkCore\libraries\libasynql\base\{closure}(array[1])
#6 plugins/NetworkCore/src/Ali/NetworkCore/libraries/libasynql/base/SqlThreadPool(112): Ali\NetworkCore\libraries\libasynql\base\DataConnectorImpl->Ali\NetworkCore\libraries\libasynql\base\{closure}(array[1])
#7 plugins/NetworkCore/src/Ali/NetworkCore/libraries/libasynql/base/DataConnectorImpl(323): Ali\NetworkCore\libraries\libasynql\base\SqlThreadPool->readResults(array[1])
#8 plugins/NetworkCore/src/Ali/NetworkCore/libraries/libasynql/base/SqlThreadPool(65): Ali\NetworkCore\libraries\libasynql\base\DataConnectorImpl->checkResults()
#9 pmsrc/vendor/pocketmine/snooze/src/SleeperHandler(123): Ali\NetworkCore\libraries\libasynql\base\SqlThreadPool->Ali\NetworkCore\libraries\libasynql\base\{closure}()
#10 pmsrc/vendor/pocketmine/snooze/src/SleeperHandler(82): pocketmine\snooze\SleeperHandler->processNotifications()
#11 pmsrc/src/Server(1661): pocketmine\snooze\SleeperHandler->sleepUntil(double 1652777010.4874)
#12 (): --- below is the original stack trace ---()
#13 plugins/NetworkCore/src/Ali/NetworkCore/libraries/libasynql/base/DataConnectorImpl(223): Ali\NetworkCore\libraries\libasynql\base\DataConnectorImpl->executeImplRaw(array[1], array[1], array[1], object Closure#129776, NULL )
#14 plugins/NetworkCore/src/Ali/NetworkCore/libraries/libasynql/base/DataConnectorImpl(180): Ali\NetworkCore\libraries\libasynql\base\DataConnectorImpl->executeImpl(string[20] core.faction-data-id, array[1], integer 3, object Closure#129776, NULL )
#15 plugins/NetworkCore/src/Ali/NetworkCore/libraries/libasynql/base/DataConnectorImpl(172): Ali\NetworkCore\libraries\libasynql\base\DataConnectorImpl->executeImplLast(string[20] core.faction-data-id, array[1], integer 3, object Closure#129843, NULL )
#16 plugins/Factions/src/Ali/Factions/provider/mysql/SQLFaction(36): Ali\NetworkCore\libraries\libasynql\base\DataConnectorImpl->executeSelect(string[20] core.faction-data-id, array[1], object Closure#129875)
#17 plugins/NetworkCore/src/Ali/NetworkCore/libraries/libasynql/base/DataConnectorImpl(174): Ali\Factions\provider\SQLProvider->Ali\Factions\provider\mysql\{closure}(array[1], array[1])
#18 plugins/NetworkCore/src/Ali/NetworkCore/libraries/libasynql/base/DataConnectorImpl(181): Ali\NetworkCore\libraries\libasynql\base\DataConnectorImpl::Ali\NetworkCore\libraries\libasynql\base\{closure}(object Ali\NetworkCore\libraries\libasynql\result\SqlSelectResult#129844)
#19 plugins/NetworkCore/src/Ali/NetworkCore/libraries/libasynql/base/DataConnectorImpl(239): Ali\NetworkCore\libraries\libasynql\base\DataConnectorImpl::Ali\NetworkCore\libraries\libasynql\base\{closure}(array[1])
#20 plugins/NetworkCore/src/Ali/NetworkCore/libraries/libasynql/base/SqlThreadPool(112): Ali\NetworkCore\libraries\libasynql\base\DataConnectorImpl->Ali\NetworkCore\libraries\libasynql\base\{closure}(array[1])
#21 plugins/NetworkCore/src/Ali/NetworkCore/libraries/libasynql/base/DataConnectorImpl(323): Ali\NetworkCore\libraries\libasynql\base\SqlThreadPool->readResults(array[1])
#22 plugins/NetworkCore/src/Ali/NetworkCore/libraries/libasynql/base/SqlThreadPool(65): Ali\NetworkCore\libraries\libasynql\base\DataConnectorImpl->checkResults()
#23 pmsrc/vendor/pocketmine/snooze/src/SleeperHandler(123): Ali\NetworkCore\libraries\libasynql\base\SqlThreadPool->Ali\NetworkCore\libraries\libasynql\base\{closure}()
#24 pmsrc/vendor/pocketmine/snooze/src/SleeperHandler(82): pocketmine\snooze\SleeperHandler->processNotifications()
#25 pmsrc/src/Server(1661): pocketmine\snooze\SleeperHandler->sleepUntil(double 1652777009.8874)

Environment
OS: Windows 10
PocketMine version: 4.3.1 (git hash: f7dce4af8802ce28a8286e1c8002af19cf986c62)

Still causing lag

I use libasynql by using the sample code why is it still lag? even though I have set it in config worker-limit to 5.

Environment
OS: Ubuntu 18.04
PocketMine version: 4.3.1

libasynql\base\QueueShutdownException: "You cannot schedule a query on an invalidated queue."

Describe the bug

A clear and concise description of what the bug is.

> mw unload lobby-1
[14:53:24] [Server thread/INFO]: Unloading world "endermanbugzjfc"
[14:53:24] [Server thread/INFO]: [MultiWorld] World unloaded.
> mw load lobby-1
[14:53:27] [Server thread/INFO]: Preparing world "endermanbugzjfc"
[14:53:27] [Server thread/CRITICAL]: poggit\libasynql\base\QueueShutdownException: "You cannot schedule a query on an invalidated queue." (EXCEPTION) in "/home/container/virions/libasynql.phar/src/poggit/libasynql/base/QuerySendQueue" at line 40

Sample code

Level unload event:

public function onPMMPLevelUnloadEvent(\pocketmine\event\level\LevelUnloadEvent $e) : void {
	$w = $e->getLevel();
	if (substr($w->getFolderName(), 0, 5) !== 'lobby') return;
	if ($this->lobbydb instanceof DataConnector) $this->lobbydb->close();
}

Level load event:

public function onPMMPLevelLoadEvent(\pocketmine\event\level\LevelLoadEvent $e) : void {
	$w = $e->getLevel();
	if (substr($w->getFolderName(), 0, 5) !== 'lobby') return;
	$w->stopTime();
	$this->lobbydb = libasynql::create($this->main, self::CONNECTION, self::SQL_FILE);
	$this->lobbydb->executeGeneric(self::SQL_SCRIPTS['init']['parkour']);
	$this->lobbydb->waitAll();
}

Expected behavior
I have a sqlite for the lobby level. So basically I want to make it when I load the level the database will be connected also when the level unloads, it closes. However, it only worked at the first load. Although I added the code above to close the connection, the second time it loads, it just gave me the error above.

Environment
OS: Linux
PocketMine version: 3.15.0

Pocketmine 4.0

Add Pocketmine 4.0 support. There is a 4.0 branch but it does not support pocketmine 4.0.

MySQL server has gone away

Getting this error:
SQL PREPARE error: MySQL server has gone away, for query SELECT
Environment
OS: Linux

Calling libasynql functions won't execute until we call another one.

Describe the bug
When you call any libasynql function, let's say executeSelect, the onSelect function will never executed until another libasynql function called. and the second libasynql function well never get it's results until another function is called...

Something I noticed is that the bug does not exist in older versions. (before DEV-182 f0d668c)

Sample code
https://github.com/Aboshxm2/libasyncql_query_bug

sqlite.sql

-- #!sqlite
-- #{ libasyncql_query_bug.init
CREATE TABLE IF NOT EXISTS example_table (playerName TEXT PRIMARY KEY, kills INTEGER);
-- #}
-- #{ libasyncql_query_bug.select
-- # :playerName string
SELECT * FROM example_table WHERE playerName=:playerName;
-- #}
-- #{ libasyncql_query_bug.insert
-- # :playerName string
-- # :kills int
INSERT INTO example_table (playerName, kills) VALUES (:playerName, :kills)
-- #}

Main.php

    private DataConnector $database;

    protected function onEnable(): void
    {
        $this->database = libasynql::create($this, [
            "type" => "sqlite",
            "sqlite" => ["file" => "data.sqlite"],
            "worker-limit" => 1
        ], [
            "sqlite" => "sqlite.sql"
        ]);

        $this->database->executeGeneric("libasyncql_query_bug.init", [], null, function (SqlError $error): void {
            throw new $error;
        });
    }

    public function onCommand(CommandSender $sender, Command $command, string $label, array $args): bool
    {
        if(!isset($args[0])) {
            $sender->sendMessage("/test (insert|select)");
            return true;
        }

        if($args[0] === "insert") {

            if(count($args) < 3) {
                $sender->sendMessage("/test insert (playerName) (kills)");
                return true;
            }

            $this->database->executeInsert("libasyncql_query_bug.insert", ["playerName" => $args[1], "kills" => (int)$args[2]], function (int $_) {
                var_dump("insert query executed successfully.");
            }, function (SqlError $error): void {
                throw new $error;
            });

        }elseif($args[0] === "select") {

            if(count($args) < 2) {
                $sender->sendMessage("/test select (playerName)");
                return true;
            }

            $this->database->executeSelect("libasyncql_query_bug.select", ["playerName" => $args[1]], function (array $rows) {
                var_dump("select query executed successfully.");
                var_dump($rows);
            }, function (SqlError $error): void {
                throw new $error;
            });
        }

Expected behavior
When I do /test insert player1 10, It should display "insert query executed successfully." in the console.
but it doesn't. until I send another command /test insert player2 10

Tests
1: /test insert player1 10.
resaults:
nothing.

2: /test insert player1 10 then /test select player1.
resaults:

"insert query executed successfully.".

3: /test insert player1 10 then /test select player1 then /test select player2
resaults:

"insert query executed successfully."`
"select query executed successfully."
array(1) {
  [0]=>
  array(2) {
    ["playerName"]=>
    string(2) "player1"
    ["kills"]=>
    int(1)
  }
}

4: /test insert player1 10 then /test select player2 then /test select player1
resaults:

"insert query executed successfully."`
"select query executed successfully."
array(0) {
}

Environment
OS: linux
PocketMine version: 4.12.2
PHP version: 8.0.17

I am sorry if there any misspelling. English is not my main language.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.