├── .gitignore ├── README.md ├── composer.json ├── composer.lock ├── src └── VenityNetwork │ └── MysqlLib │ ├── MysqlConnection.php │ ├── MysqlCredentials.php │ ├── MysqlException.php │ ├── MysqlLib.php │ ├── MysqlRequest.php │ ├── MysqlResponse.php │ ├── MysqlThread.php │ ├── Utils.php │ ├── query │ ├── Query.php │ ├── RawChangeQuery.php │ ├── RawGenericQuery.php │ ├── RawInsertQuery.php │ └── RawSelectQuery.php │ └── result │ ├── ChangeResult.php │ ├── InsertResult.php │ ├── Result.php │ └── SelectResult.php └── virion.yml /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | vendor -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MysqlLib 2 | A Mysql library for PocketMine-MP. 3 | - Asynchronous 4 | - Custom Query 5 | - Multiple Threads/Connections 6 | ## Usage 7 | 1. Initialization 8 | ```php 9 | $threads = 2; // threads count, increase this if the query is slow and prevent blocking query 10 | $db = MysqlLib::init($plugin, new MysqlCredentials( 11 | host: "127.0.0.1", 12 | user: "root", 13 | password: "", 14 | db: "database", 15 | port: 3306 16 | ), $threads); 17 | ``` 18 | 2. Create a first query 19 | ```php 20 | class GetEmailQuery extends MysqlQuery{ 21 | 22 | public function execute(MysqlConnection $conn, array $params): mixed{ 23 | $result = $conn->select('SELECT email FROM users WHERE name=? LIMIT 1', $params["name"]); 24 | $row = $result->getOneRow(); 25 | if($row !== null) { 26 | return $row["email"]; 27 | } 28 | return null; 29 | } 30 | } 31 | ``` 32 | 3. Call the query 33 | ```php 34 | $db->query(GetEmailQuery::class, ["name" => "John_Doe"], function($email) { 35 | var_dump($email); 36 | }, 37 | function() { 38 | var_dump("ERROR"); 39 | } 40 | ); 41 | ``` 42 | ## Simple Usage 43 | - Select (Return: rows) 44 | ```php 45 | /** @var \VenityNetwork\MysqlLib\MysqlLib $db */ 46 | $db->rawSelect("SELECT * FROM players WHERE xuid=?", [$xuid], function(?array $rows) { 47 | var_dump($rows); 48 | }, function(string $errorMessage) { 49 | var_dump("Error: {$errorMessage}"); 50 | }); 51 | ``` 52 | - Insert (Returns: affected rows, last insert id) 53 | ```php 54 | /** @var \VenityNetwork\MysqlLib\MysqlLib $db */ 55 | $db->rawInsert("INSERT INTO players (xuid) VALUES (?)", [$xuid], function(int $affected_rows, int $insert_id) { 56 | var_dump("Affected Rows: {$affected_rows}, Insert ID: {$insert_id}"); 57 | }, function(string $errorMessage) { 58 | // todo 59 | }); 60 | ``` 61 | - Update / Delete (Return: affected rows) 62 | ```php 63 | /** @var \VenityNetwork\MysqlLib\MysqlLib $db */ 64 | $db->rawChange("UPDATE players SET money=? WHERE xuid=?", [$money, $xuid], function(int $affected_rows) { 65 | var_dump("Affected Rows: {$affected_rows}"); 66 | }, function(string $errorMessage) { 67 | // todo 68 | }); 69 | ``` 70 | - Generic (No result returned) 71 | ```php 72 | /** @var \VenityNetwork\MysqlLib\MysqlLib $db */ 73 | $db->rawChange("CREATE TABLE `players` (`xuid` VARCHAR(16), `money` BIGINT) PRIMARY KEY (`xuid`)", function(bool $success) { 74 | // todo 75 | }, function(string $errorMessage) { 76 | // todo 77 | }); 78 | ``` -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "venitynetwork/mysqllib", 3 | "version": "5.0.0", 4 | "autoload": { 5 | "psr-4": { 6 | "VenityNetwork\\MysqlLib\\": "src/VenityNetwork/MysqlLib" 7 | } 8 | }, 9 | "require": { 10 | "ext-sockets": "*", 11 | "ext-pmmpthread": "*", 12 | "ext-mysqli": "*", 13 | "ext-yaml": "*", 14 | "ext-zlib": "*", 15 | "ext-gd": "*", 16 | "ext-igbinary": "*" 17 | } 18 | } -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "b509fcfd7af16b3c1ceaaff726d19ec5", 8 | "packages": [], 9 | "packages-dev": [], 10 | "aliases": [], 11 | "minimum-stability": "stable", 12 | "stability-flags": [], 13 | "prefer-stable": false, 14 | "prefer-lowest": false, 15 | "platform": [], 16 | "platform-dev": [], 17 | "plugin-api-version": "2.0.0" 18 | } 19 | -------------------------------------------------------------------------------- /src/VenityNetwork/MysqlLib/MysqlConnection.php: -------------------------------------------------------------------------------- 1 | thread->getLogger(); 36 | } 37 | 38 | public function connect(): void{ 39 | $this->close(); 40 | $this->mysqli = @new mysqli($this->host, $this->user, $this->password, $this->db, $this->port); 41 | if($this->mysqli->connect_error) { 42 | throw new MysqlException("Connection Error: {$this->mysqli->connect_error} [{$this->mysqli->connect_errno}]"); 43 | } 44 | } 45 | 46 | private function tryPing(): bool{ 47 | try{ 48 | $result = $this->mysqli->query("SELECT 'ping' as v"); 49 | if(is_bool($result)){ 50 | return $result; 51 | } 52 | $row = $result->fetch_assoc(); 53 | if($row === null || !isset($row["v"]) || $row["v"] !== "ping"){ 54 | $result->close(); 55 | return false; 56 | } 57 | $result->close(); 58 | return true; 59 | }catch(\Throwable $t){ 60 | return false; 61 | } 62 | } 63 | 64 | public function checkConnection(): void{ 65 | if(!isset($this->mysqli) || !$this->tryPing()) { 66 | $this->connect(); 67 | } 68 | } 69 | 70 | public function close(): void{ 71 | if(isset($this->mysqli)){ 72 | @$this->mysqli->close(); 73 | unset($this->mysqli); 74 | } 75 | } 76 | 77 | public function getMysqli(): mysqli{ 78 | return $this->mysqli; 79 | } 80 | 81 | 82 | /** 83 | * @param string $query 84 | * @param int|string|float ...$args 85 | * @return InsertResult 86 | * @throws MysqlException 87 | */ 88 | public function insert(string $query, ...$args): InsertResult{ 89 | $ret = $this->query(self::MODE_INSERT, $query, ...$args); 90 | if(!$ret instanceof InsertResult) { 91 | throw new MysqlException("Expected InsertResult got " . serialize($ret)); 92 | } 93 | return $ret; 94 | } 95 | 96 | /** 97 | * @param string $query 98 | * @param int|string|float ...$args 99 | * @return SelectResult 100 | * @throws MysqlException 101 | */ 102 | public function select(string $query, ...$args): SelectResult{ 103 | $ret = $this->query(self::MODE_SELECT, $query, ...$args); 104 | if(!$ret instanceof SelectResult) { 105 | throw new MysqlException("Expected SelectResult got " . serialize($ret)); 106 | } 107 | return $ret; 108 | } 109 | 110 | /** 111 | * @param string $query 112 | * @param int|string|float ...$args 113 | * @return ChangeResult 114 | * @throws MysqlException 115 | */ 116 | public function change(string $query, ...$args): ChangeResult{ 117 | $ret = $this->query(self::MODE_CHANGE, $query, ...$args); 118 | if(!$ret instanceof ChangeResult) { 119 | throw new MysqlException("Expected ChangeResult got " . serialize($ret)); 120 | } 121 | return $ret; 122 | } 123 | 124 | /** 125 | * @param string $query 126 | * @return Result 127 | * @throws MysqlException 128 | */ 129 | public function generic(string $query): Result{ 130 | $ret = $this->query(self::MODE_GENERIC, $query); 131 | if(!$ret instanceof Result) { 132 | throw new MysqlException("Expected Result got " . serialize($ret)); 133 | } 134 | return $ret; 135 | } 136 | 137 | /** 138 | * @param int $mode 139 | * @param string $query 140 | * @param int|string|float ...$args 141 | * @return bool|ChangeResult|InsertResult|Result|SelectResult 142 | * @throws MysqlException 143 | */ 144 | public function query(int $mode, string $query, ...$args) { 145 | $this->checkConnection(); 146 | if(count($args) === 0) { 147 | $result = $this->mysqli->query($query); 148 | switch($mode) { 149 | case self::MODE_SELECT: 150 | $ret = new SelectResult(Utils::mysqliResultToArray($result)); 151 | $result->close(); 152 | return $ret; 153 | case self::MODE_INSERT: 154 | case self::MODE_CHANGE: 155 | case self::MODE_GENERIC: 156 | if($result instanceof mysqli_result) { 157 | $result->close(); 158 | } 159 | if($mode === self::MODE_INSERT) { 160 | return new InsertResult($this->mysqli->affected_rows, $this->mysqli->insert_id); 161 | } 162 | if($mode === self::MODE_CHANGE) { 163 | return new ChangeResult($this->mysqli->affected_rows); 164 | } 165 | return new Result(); 166 | } 167 | $ar = Utils::argsToString($args); 168 | throw new MysqlException("Query Error: {$this->mysqli->error} [{$this->mysqli->errno}] (query=`{$query}`,args={$ar})"); 169 | } else { 170 | $types = Utils::getTypesFromArray($args); 171 | $stmt = $this->mysqli->prepare($query); 172 | if($stmt === false) { 173 | $ar = Utils::argsToString($args); 174 | throw new MysqlException("Prepare Statement Error: {$this->mysqli->error} [{$this->mysqli->errno}] (query=`{$query}`,types={$types},args={$ar})"); 175 | } 176 | if(!$stmt->bind_param($types, ...$args)) { 177 | $ar = Utils::argsToString($args); 178 | $err = new MysqlException("Prepare Statement bind_param Error: {$stmt->error} [{$stmt->errno}] (query=`{$query}`,types={$types},args={$ar})"); 179 | $stmt->close(); 180 | throw $err; 181 | } 182 | if(!$stmt->execute()) { 183 | $ar = Utils::argsToString($args); 184 | $err = new MysqlException("Prepare Statement execute Error: {$stmt->error} [{$stmt->errno}] (query=`{$query}`,types={$types},args={$ar})"); 185 | $stmt->close(); 186 | throw $err; 187 | } 188 | $result = $stmt->get_result(); 189 | switch($mode) { 190 | case self::MODE_SELECT: 191 | $ret = new SelectResult(Utils::mysqliResultToArray($result)); 192 | $stmt->close(); 193 | return $ret; 194 | case self::MODE_INSERT: 195 | $ret = new InsertResult($stmt->affected_rows, $stmt->insert_id); 196 | $stmt->close(); 197 | return $ret; 198 | case self::MODE_CHANGE: 199 | $ret = new ChangeResult($stmt->affected_rows); 200 | $stmt->close(); 201 | return $ret; 202 | case self::MODE_GENERIC: 203 | $ret = new Result(); 204 | $stmt->close(); 205 | return $ret; 206 | } 207 | $stmt->close(); 208 | return $result; 209 | } 210 | } 211 | } -------------------------------------------------------------------------------- /src/VenityNetwork/MysqlLib/MysqlCredentials.php: -------------------------------------------------------------------------------- 1 | host; 20 | } 21 | 22 | public function getUser(): string{ 23 | return $this->user; 24 | } 25 | 26 | public function getPassword(): string{ 27 | return $this->password; 28 | } 29 | 30 | public function getDb(): string{ 31 | return $this->db; 32 | } 33 | 34 | public function getPort(): int{ 35 | return $this->port; 36 | } 37 | 38 | public function __toString() { 39 | return "host={$this->host},user={$this->user},db={$this->db},port={$this->port}"; 40 | } 41 | } -------------------------------------------------------------------------------- /src/VenityNetwork/MysqlLib/MysqlException.php: -------------------------------------------------------------------------------- 1 | getTickSleeper()->addNotifier(function() use ($i) { 35 | $this->handleResponse($i); 36 | }); 37 | $t = new MysqlThread(Server::getInstance()->getLogger(), $sleeperEntry, $credentials); 38 | $t->start(); 39 | while(!$t->running) { 40 | usleep(1000); 41 | } 42 | Server::getInstance()->getLogger()->debug("Started MysqlThread (".($i+1) . "/" . $threads . ")"); 43 | $this->thread[$i] = $t; 44 | $this->threadTasksCount[$i] = 0; 45 | } 46 | $this->checkVersion(); 47 | $this->plugin->getScheduler()->scheduleRepeatingTask(new ClosureTask(function() : void { 48 | $this->triggerGarbageCollector(); 49 | }), 20 * 1800); 50 | 51 | // register event when plugin is enabled 52 | $this->plugin->getScheduler()->scheduleDelayedTask(new ClosureTask(function(): void{ 53 | $this->plugin->getServer()->getPluginManager()->registerEvent(LowMemoryEvent::class, function(LowMemoryEvent $event): void{ 54 | $this->triggerGarbageCollector(); 55 | }, EventPriority::NORMAL, $this->plugin); 56 | }), 0); 57 | } 58 | 59 | public function triggerGarbageCollector(): void{ 60 | foreach($this->thread as $t) { 61 | $t->triggerGarbageCollector(); 62 | } 63 | } 64 | 65 | public function waitAll(): void{ 66 | foreach($this->thread as $k => $thread) { 67 | while(($this->threadTasksCount[$k]) > 0) { 68 | $this->handleResponse($k); 69 | usleep(1000); 70 | } 71 | } 72 | } 73 | 74 | public function close(): void{ 75 | $this->waitAll(); 76 | foreach($this->thread as $thread){ 77 | $thread->close(); 78 | Server::getInstance()->getTickSleeper()->removeNotifier($thread->getSleeperEntry()->getNotifierId()); 79 | } 80 | } 81 | 82 | private function checkVersion(): void{ 83 | $this->rawSelect("SELECT VERSION() as v", [], static function(array $rows) { 84 | Server::getInstance()->getLogger()->notice("DB Version = " . $rows[0]["v"]); 85 | }, static function(string $error) { 86 | Server::getInstance()->getLogger()->error("Mysql Error: " . $error); 87 | }); 88 | } 89 | 90 | private function selectThread() : int { 91 | $thread = null; 92 | $currentTask = -1; 93 | foreach($this->threadTasksCount as $k => $v) { 94 | if($this->thread[$k]->isBusy()){ 95 | continue; 96 | } 97 | if($v >= $currentTask && $this->previousThread !== $k) { 98 | $thread = $k; 99 | $currentTask = $v; 100 | } 101 | } 102 | if($thread === null) { 103 | foreach($this->threadTasksCount as $k => $v) { 104 | if($v > $currentTask) { 105 | $thread = $k; 106 | $currentTask = $v; 107 | } 108 | } 109 | } 110 | $this->previousThread = $thread; 111 | return $thread; 112 | } 113 | 114 | private function handleResponse(int $thread): void{ 115 | while(($response = $this->thread[$thread]->fetchResponse()) !== null) { 116 | $this->threadTasksCount[$thread]--; 117 | $response = igbinary_unserialize($response); 118 | if($response instanceof MysqlResponse) { 119 | $id = $response->getId(); 120 | try{ 121 | if($response->isError()) { 122 | if(isset($this->onFail[$id])) { 123 | if($this->ignoreError) { 124 | $this->ignoreError(fn() => ($this->onFail[$id])($response->getErrorMessage())); 125 | }else{ 126 | ($this->onFail[$id])($response->getErrorMessage()); 127 | } 128 | } 129 | } else { 130 | if(isset($this->onSuccess[$id])) { 131 | if($this->ignoreError) { 132 | $this->ignoreError(fn() => ($this->onSuccess[$id])($response->getResult())); 133 | }else{ 134 | ($this->onSuccess[$id])($response->getResult()); 135 | } 136 | } 137 | } 138 | }finally{ 139 | unset($this->onSuccess[$id]); 140 | unset($this->onFail[$id]); 141 | } 142 | } 143 | } 144 | } 145 | 146 | /** 147 | * @param string $query 148 | * @param array $args 149 | * @param callable|null $onSuccess - function(mixed $result) : void {} 150 | * @param callable|null $onFail - function(string $errorMessage) : void {} 151 | * @return void 152 | */ 153 | public function query(string $query, array $args = [], ?callable $onSuccess = null, ?callable $onFail = null): void{ 154 | $this->nextId++; 155 | if($onSuccess !== null) { 156 | $this->onSuccess[$this->nextId] = $onSuccess; 157 | } 158 | if($onFail !== null) { 159 | $this->onFail[$this->nextId] = $onFail; 160 | } 161 | $t = $this->selectThread(); 162 | $this->thread[$t]->sendRequest(new MysqlRequest($this->nextId, $query, $args)); 163 | $this->threadTasksCount[$t]++; 164 | } 165 | 166 | /** 167 | * @param string $query 168 | * @param array $args 169 | * @param callable|null $onSuccess 170 | * @param callable|null $onFail 171 | * @return void 172 | */ 173 | public function rawSelect(string $query, array $args = [], ?callable $onSuccess = null, ?callable $onFail = null): void{ 174 | $this->query(RawSelectQuery::class, [$query, $args], $onSuccess, $onFail); 175 | } 176 | 177 | public function rawSelectOne(string $query, array $args = [], ?callable $onSuccess = null, ?callable $onFail = null): void{ 178 | if($onSuccess !== null) { 179 | $onSuccess = static function(array $rows) use ($onSuccess) { 180 | $onSuccess($rows[0] ?? null); 181 | }; 182 | } 183 | $this->rawSelect($query, $args, $onSuccess, $onFail); 184 | } 185 | 186 | /** 187 | * @param string $query 188 | * @param callable|null $onSuccess 189 | * @param callable|null $onFail 190 | * @return void 191 | */ 192 | public function rawGeneric(string $query, ?callable $onSuccess = null, ?callable $onFail = null): void{ 193 | $this->query(RawGenericQuery::class, [$query], $onSuccess, $onFail); 194 | } 195 | 196 | /** 197 | * @param string $query 198 | * @param array $args 199 | * @param callable|null $onSuccess 200 | * @param callable|null $onFail 201 | * @return void 202 | */ 203 | public function rawChange(string $query, array $args = [], ?callable $onSuccess = null, ?callable $onFail = null): void{ 204 | $this->query(RawChangeQuery::class, [$query, $args], $onSuccess, $onFail); 205 | } 206 | 207 | /** 208 | * @param string $query 209 | * @param array $args 210 | * @param callable|null $onSuccess - function(int $affected_rows, int $insert_id) : void {} 211 | * @param callable|null $onFail 212 | * @return void 213 | */ 214 | public function rawInsert(string $query, array $args = [], ?callable $onSuccess = null, ?callable $onFail = null): void{ 215 | if($onSuccess !== null) { 216 | $onSuccess = static function(array $result) use ($onSuccess) { 217 | $onSuccess($result[0], $result[1]); 218 | }; 219 | } 220 | $this->query(RawInsertQuery::class, [$query, $args], $onSuccess, $onFail); 221 | } 222 | 223 | private function ignoreError(callable $cb): void{ 224 | try { 225 | $cb(); 226 | }catch(\Throwable $t) { 227 | Server::getInstance()->getLogger()->logException($t); 228 | } 229 | } 230 | } -------------------------------------------------------------------------------- /src/VenityNetwork/MysqlLib/MysqlRequest.php: -------------------------------------------------------------------------------- 1 | id; 18 | } 19 | 20 | public function getQuery(): string{ 21 | return $this->query; 22 | } 23 | 24 | public function getParams(): array{ 25 | return $this->params; 26 | } 27 | } -------------------------------------------------------------------------------- /src/VenityNetwork/MysqlLib/MysqlResponse.php: -------------------------------------------------------------------------------- 1 | id; 19 | } 20 | 21 | public function getResult(): mixed{ 22 | return $this->result; 23 | } 24 | 25 | public function isError(): bool{ 26 | return $this->error; 27 | } 28 | 29 | public function getErrorMessage(): string{ 30 | return $this->errorMessage; 31 | } 32 | } -------------------------------------------------------------------------------- /src/VenityNetwork/MysqlLib/MysqlThread.php: -------------------------------------------------------------------------------- 1 | requests = new ThreadSafeArray(); 38 | $this->responses = new ThreadSafeArray(); 39 | $this->credentials = igbinary_serialize($credentials); 40 | } 41 | 42 | public function getLogger(): AttachableThreadSafeLogger{ 43 | return $this->logger; 44 | } 45 | 46 | public function onRun(): void{ 47 | ini_set("memory_limit", "256M"); 48 | gc_enable(); 49 | $notifier = $this->sleeperEntry->createNotifier(); 50 | /** @var MysqlCredentials $cred */ 51 | $cred = igbinary_unserialize($this->credentials); 52 | $connection = new MysqlConnection($cred->getHost(), $cred->getUser(), $cred->getPassword(), $cred->getDb(), $cred->getPort(), $this); 53 | while($this->running){ 54 | if(!$this->checkConnection($connection)) { 55 | sleep(5); 56 | continue; 57 | } 58 | $this->busy = true; 59 | try{ 60 | $this->processRequests($connection, $notifier); 61 | } catch(Throwable $t) { 62 | $this->logger->logException($t); 63 | $connection->close(); 64 | } 65 | $this->busy = false; 66 | $this->synchronized(function() { 67 | $this->wait(); 68 | }); 69 | } 70 | $this->logger->info("MysqlThread closed."); 71 | $connection->close(); 72 | $this->synchronized(function() { 73 | $this->running = false; 74 | }); 75 | } 76 | 77 | private function checkConnection(MysqlConnection $connection): bool{ 78 | try{ 79 | $connection->checkConnection(); 80 | return true; 81 | }catch(Throwable $e){ 82 | $this->logger->logException($e); 83 | } 84 | return false; 85 | } 86 | 87 | private function readRequests() : ?string{ 88 | return $this->synchronized(function() : ?string{ 89 | return $this->requests->shift(); 90 | }); 91 | } 92 | 93 | private function processRequests(MysqlConnection $connection, SleeperNotifier $notifier): void{ 94 | while(($request = $this->readRequests()) !== null) { 95 | $request = igbinary_unserialize($request); 96 | if($request instanceof MysqlRequest) { 97 | $start = microtime(true); 98 | $ar = Utils::argsToString($request->getParams()); 99 | $queryClass = $request->getQuery(); 100 | if(class_exists($queryClass)){ 101 | try{ 102 | $query = new ($queryClass)(); 103 | if($query instanceof Query){ 104 | $result = $query->execute($connection, $request->getParams()); 105 | if(is_array($result)){ 106 | $resultDebug = "(array) " . Utils::argsToString($result); 107 | }else{ 108 | $resultDebug = "(" . gettype($result) . ")" . (is_bool($result) ? ($result ? "TRUE" : "FALSE") : $result); 109 | } 110 | $this->logger->debug("Query succeed in " . floor((microtime(true) - $start) * 1000) . "ms (query={$request->getQuery()},id={$request->getId()},params=$ar,result=`$resultDebug`)"); 111 | 112 | $this->sendResponse($notifier, $request->getId(), $result); 113 | continue; 114 | } 115 | throw new MysqlException("Query must instanceof ".Query::class." got " . $query::class); 116 | } catch(Throwable $t) { 117 | $this->sendResponse($notifier, $request->getId(), null, true, $t->getMessage()); 118 | $this->logger->error("Query error (query={$queryClass},id={$request->getId()},params=$ar)"); 119 | $this->logger->logException($t); 120 | // reconnect when error to avoid deadlock transaction 121 | $connection->close(); 122 | return; 123 | } 124 | } 125 | $this->sendResponse($notifier, $request->getId(), null, true, "Unknown query={$request->getQuery()}"); 126 | }elseif($request === self::GC_CODE){ 127 | gc_enable(); 128 | gc_collect_cycles(); 129 | gc_mem_caches(); 130 | } 131 | } 132 | } 133 | 134 | public function sendRequest(MysqlRequest $request): void{ 135 | $this->synchronized(function() use ($request) { 136 | $this->requests[] = igbinary_serialize($request); 137 | $this->notifyOne(); 138 | }); 139 | } 140 | 141 | public function fetchResponse() : ?string { 142 | return $this->synchronized(function() : ?string { 143 | return $this->responses->shift(); 144 | }); 145 | } 146 | 147 | private function sendResponse(SleeperNotifier $notifier, int $id, mixed $response, bool $error = false, string $errorMessage = ""): void{ 148 | $this->synchronized(function() use ($notifier, $id, $response, $error, $errorMessage) : void { 149 | $this->responses[] = igbinary_serialize(new MysqlResponse($id, $response, $error, $errorMessage)); 150 | $notifier->wakeupSleeper(); 151 | }); 152 | } 153 | 154 | public function getThreadName(): string{ 155 | return "MysqlLib"; 156 | } 157 | 158 | public function close(): void{ 159 | if(!$this->running) { 160 | return; 161 | } 162 | $this->synchronized(function() { 163 | $this->running = false; 164 | $this->notify(); 165 | }); 166 | $this->quit(); 167 | } 168 | 169 | public function quit(): void{ 170 | $this->close(); 171 | parent::quit(); 172 | } 173 | 174 | public function triggerGarbageCollector(): void{ 175 | $this->synchronized(function() : void { 176 | $this->requests[] = igbinary_serialize(self::GC_CODE); 177 | $this->notifyOne(); 178 | }); 179 | } 180 | 181 | public function getSleeperEntry(): SleeperHandlerEntry{ 182 | return $this->sleeperEntry; 183 | } 184 | 185 | public function isBusy(): bool{ 186 | return $this->busy; 187 | } 188 | } -------------------------------------------------------------------------------- /src/VenityNetwork/MysqlLib/Utils.php: -------------------------------------------------------------------------------- 1 | fetch_assoc()) !== null) { 29 | $rows[] = $row; 30 | } 31 | return $rows; 32 | } 33 | 34 | public static function getTypesFromArray(array $param): string{ 35 | $ret = ""; 36 | foreach($param as $p) { 37 | $ret .= self::getType($p); 38 | } 39 | return $ret; 40 | } 41 | 42 | /** 43 | * @throws MysqlException 44 | */ 45 | public static function getType($param): string{ 46 | if(is_string($param)){ 47 | return "s"; 48 | } 49 | if(is_float($param)){ 50 | return "d"; 51 | } 52 | if(is_int($param)){ 53 | return "i"; 54 | } 55 | throw new MysqlException("Unsupported type: " . gettype($param)); 56 | } 57 | 58 | public static function addBacktick(string $str): string{ 59 | return implode(".", array_map(fn($c) => "`" . $c . "`", explode(".", $str))); 60 | } 61 | } -------------------------------------------------------------------------------- /src/VenityNetwork/MysqlLib/query/Query.php: -------------------------------------------------------------------------------- 1 | change($params[0], ...$params[1])->getAffectedRows(); 13 | } 14 | } -------------------------------------------------------------------------------- /src/VenityNetwork/MysqlLib/query/RawGenericQuery.php: -------------------------------------------------------------------------------- 1 | generic($params[0]); 13 | return true; 14 | } 15 | } -------------------------------------------------------------------------------- /src/VenityNetwork/MysqlLib/query/RawInsertQuery.php: -------------------------------------------------------------------------------- 1 | insert($params[0], ...$params[1]); 13 | return [$result->getAffectedRows(), $result->getInsertId()]; 14 | } 15 | } -------------------------------------------------------------------------------- /src/VenityNetwork/MysqlLib/query/RawSelectQuery.php: -------------------------------------------------------------------------------- 1 | select($params[0], ...$params[1])->getRows(); 13 | } 14 | } -------------------------------------------------------------------------------- /src/VenityNetwork/MysqlLib/result/ChangeResult.php: -------------------------------------------------------------------------------- 1 | affected_rows; 15 | } 16 | } -------------------------------------------------------------------------------- /src/VenityNetwork/MysqlLib/result/InsertResult.php: -------------------------------------------------------------------------------- 1 | affected_rows; 16 | } 17 | 18 | public function getInsertId(): int{ 19 | return $this->insert_id; 20 | } 21 | } -------------------------------------------------------------------------------- /src/VenityNetwork/MysqlLib/result/Result.php: -------------------------------------------------------------------------------- 1 | rows; 14 | } 15 | 16 | public function getOneRow(): ?array{ 17 | return $this->rows[0] ?? null; 18 | } 19 | 20 | public function toArray(): array{ 21 | return $this->rows; 22 | } 23 | } -------------------------------------------------------------------------------- /virion.yml: -------------------------------------------------------------------------------- 1 | name: MysqlLib 2 | authors: 3 | - AkmalFairuz 4 | antigen: VenityNetwork\MysqlLib 5 | version: 5.0.0 6 | api: 7 | - 5.0.0 --------------------------------------------------------------------------------