ch1p_io_web/engine/mysql.php
2024-01-31 20:45:40 +03:00

262 lines
7.1 KiB
PHP

<?php
class mysql {
protected ?mysqli $link = null;
function __construct(
protected string $host,
protected string $user,
protected string $password,
protected string $database) {}
protected function prepareQuery(string $sql, ...$args): string {
global $config;
if (!empty($args)) {
$mark_count = substr_count($sql, '?');
$positions = array();
$last_pos = -1;
for ($i = 0; $i < $mark_count; $i++) {
$last_pos = strpos($sql, '?', $last_pos + 1);
$positions[] = $last_pos;
}
for ($i = $mark_count - 1; $i >= 0; $i--) {
$arg_val = $args[$i];
if (is_null($arg_val)) {
$v = 'NULL';
} else {
$v = '\''.$this->escape($arg_val) . '\'';
}
$sql = substr_replace($sql, $v, $positions[$i], 1);
}
}
if (!empty($config['mysql']['log']))
logDebug(__METHOD__.': ', $sql);
return $sql;
}
function insert(string $table, array $fields) {
return $this->performInsert('INSERT', $table, $fields);
}
function replace(string $table, array $fields) {
return $this->performInsert('REPLACE', $table, $fields);
}
protected function performInsert(string $command, string $table, array $fields) {
$names = [];
$values = [];
$count = 0;
foreach ($fields as $k => $v) {
$names[] = $k;
$values[] = $v;
$count++;
}
$sql = "{$command} INTO `{$table}` (`" . implode('`, `', $names) . "`) VALUES (" . implode(', ', array_fill(0, $count, '?')) . ")";
array_unshift($values, $sql);
return $this->query(...$values);
}
function update(string $table, array $rows, ...$cond) {
$fields = [];
$args = [];
foreach ($rows as $row_name => $row_value) {
$fields[] = "`{$row_name}`=?";
$args[] = $row_value;
}
$sql = "UPDATE `$table` SET ".implode(', ', $fields);
if (!empty($cond)) {
$sql .= " WHERE ".$cond[0];
if (count($cond) > 1)
$args = array_merge($args, array_slice($cond, 1));
}
return $this->query($sql, ...$args);
}
function multipleInsert(string $table, array $rows) {
list($names, $values) = $this->getMultipleInsertValues($rows);
$sql = "INSERT INTO `{$table}` (`".implode('`, `', $names)."`) VALUES ".$values;
return $this->query($sql);
}
function multipleReplace(string $table, array $rows) {
list($names, $values) = $this->getMultipleInsertValues($rows);
$sql = "REPLACE INTO `{$table}` (`".implode('`, `', $names)."`) VALUES ".$values;
return $this->query($sql);
}
protected function getMultipleInsertValues(array $rows): array {
$names = [];
$sql_rows = [];
foreach ($rows as $i => $fields) {
$row_values = [];
foreach ($fields as $field_name => $field_val) {
if ($i == 0) {
$names[] = $field_name;
}
$row_values[] = $this->escape($field_val);
}
$sql_rows[] = "('".implode("', '", $row_values)."')";
}
return [$names, implode(', ', $sql_rows)];
}
function __destruct() {
if ($this->link)
$this->link->close();
}
function connect(): bool {
$this->link = new mysqli();
$result = $this->link->real_connect($this->host, $this->user, $this->password, $this->database);
if ($result)
$this->link->set_charset('utf8mb4');
return !!$result;
}
function query(string $sql, ...$args): mysqli_result|bool {
$sql = $this->prepareQuery($sql, ...$args);
$q = $this->link->query($sql);
if (!$q)
logError(__METHOD__.': '.$this->link->error."\n$sql\n".backtrace_as_string(1));
return $q;
}
function fetch($q): ?array {
$row = $q->fetch_assoc();
if (!$row) {
$q->free();
return null;
}
return $row;
}
function fetchAll($q): ?array {
if (!$q)
return null;
$list = [];
while ($f = $q->fetch_assoc()) {
$list[] = $f;
}
$q->free();
return $list;
}
function fetchRow($q): ?array {
return $q?->fetch_row();
}
function result($q, $field = 0) {
return $q?->fetch_row()[$field];
}
function insertId(): int {
return $this->link->insert_id;
}
function numRows($q): ?int {
return $q?->num_rows;
}
function affectedRows(): ?int {
return $this->link?->affected_rows;
}
function foundRows(): int {
return (int)$this->fetch($this->query("SELECT FOUND_ROWS() AS `count`"))['count'];
}
function escape(string $s): string {
return $this->link->real_escape_string($s);
}
}
class mysql_bitfield {
private GMP $value;
private int $size;
public function __construct($value, int $size = 64) {
$this->value = gmp_init($value);
$this->size = $size;
}
public function has(int $bit): bool {
$this->validateBit($bit);
return gmp_testbit($this->value, $bit);
}
public function set(int $bit): void {
$this->validateBit($bit);
gmp_setbit($this->value, $bit);
}
public function clear(int $bit): void {
$this->validateBit($bit);
gmp_clrbit($this->value, $bit);
}
public function isEmpty(): bool {
return !gmp_cmp($this->value, 0);
}
public function __toString(): string {
$buf = '';
for ($bit = $this->size-1; $bit >= 0; --$bit)
$buf .= gmp_testbit($this->value, $bit) ? '1' : '0';
if (($pos = strpos($buf, '1')) !== false) {
$buf = substr($buf, $pos);
} else {
$buf = '0';
}
return $buf;
}
private function validateBit(int $bit): void {
if ($bit < 0 || $bit >= $this->size)
throw new Exception('invalid bit '.$bit.', allowed range: [0..'.$this->size.')');
}
}
function DB(): mysql|null {
global $config;
/** @var ?mysql $link */
static $link = null;
if (!is_null($link))
return $link;
$link = new mysql(
$config['mysql']['host'],
$config['mysql']['user'],
$config['mysql']['password'],
$config['mysql']['database']);
if (!$link->connect()) {
if (!is_cli()) {
header('HTTP/1.1 503 Service Temporarily Unavailable');
header('Status: 503 Service Temporarily Unavailable');
header('Retry-After: 300');
die('database connection failed');
} else {
fwrite(STDERR, 'database connection failed');
exit(1);
}
}
return $link;
}
function MC(): Memcached {
static $mc = null;
if ($mc === null) {
$mc = new Memcached();
$mc->addServer("127.0.0.1", 11211);
}
return $mc;
}