"i", "float" => "d", "datetime" => "s", "binary" => "b", "string" => "s", "boolean" => "i", ]; protected $db; protected $st; protected $query; protected $packetSize; protected $values; protected $longs; protected $binds = ""; public function __construct(\mysqli $db, string $query, array $bindings = [], int $packetSize = 4194304) { $this->db = $db; $this->query = $query; $this->packetSize = $packetSize; $this->retypeArray($bindings); } protected function prepare(string $query): bool { $this->st = $this->db->prepare($query); if (!$this->st) { // @codeCoverageIgnore list($excClass, $excMsg, $excData) = $this->buildEngineException($this->db->errno, $this->db->error); // @codeCoverageIgnore throw new $excClass($excMsg, $excData); // @codeCoverageIgnore } return true; } public function __destruct() { try { $this->st->close(); } catch (\Throwable $e) { // @codeCoverageIgnore } unset($this->st); } public function runArray(array $values = []): \JKingWeb\Arsse\Db\Result { $this->st->reset(); // clear normalized values $this->binds = ""; $this->values = []; $this->longs = []; // prepare values and them all at once $this->bindValues($values); if ($this->values) { $this->st->bind_param($this->binds, ...$this->values); } // packetize any large values foreach ($this->longs as $pos => $data) { $this->st->send_long_data($pos, $data); unset($data); } // execute the statement $this->st->execute(); // clear normalized values $this->binds = ""; $this->values = []; $this->longs = []; // check for errors if ($this->st->sqlstate !== "00000") { if ($this->st->sqlstate === "HY000") { list($excClass, $excMsg, $excData) = $this->buildEngineException($this->st->errno, $this->st->error); } else { list($excClass, $excMsg, $excData) = $this->buildStandardException($this->st->sqlstate, $this->st->error); } throw new $excClass($excMsg, $excData); } // create a result-set instance $r = $this->st->get_result(); $changes = $this->st->affected_rows; $lastId = $this->st->insert_id; return new Result($r, [$changes, $lastId], $this); } protected function bindValue($value, string $type, int $position): bool { // this is a bit of a hack: we collect values (and MySQL bind types) here so that we can take // advantage of the work done by bindValues() even though MySQL requires everything to be bound // all at once; we also segregate large values for later packetization if (($type === "binary" && !is_null($value)) || (is_string($value) && strlen($value) > $this->packetSize)) { $this->values[] = null; $this->longs[$position - 1] = $value; $this->binds .= "b"; } else { $this->values[] = $value; $this->binds .= self::BINDINGS[$type]; } return true; } public static function mungeQuery(string $query, array $types, ...$extraData): string { $query = explode("?", $query); $out = ""; for ($b = 1; $b < sizeof($query); $b++) { $a = $b - 1; $mark = (($types[$a] ?? "") === "datetime") ? "cast(? as datetime(0))" : "?"; $out .= $query[$a].$mark; } $out .= array_pop($query); return $out; } }