diff options
Diffstat (limited to 'engine/mysql.php')
-rw-r--r-- | engine/mysql.php | 261 |
1 files changed, 261 insertions, 0 deletions
diff --git a/engine/mysql.php b/engine/mysql.php new file mode 100644 index 0000000..bab8048 --- /dev/null +++ b/engine/mysql.php @@ -0,0 +1,261 @@ +<?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; +} |