DB row iterator

This commit is contained in:
Roman 2022-06-08 18:37:08 +02:00
parent 8b49b26f24
commit bce59c5f92
8 changed files with 189 additions and 43 deletions

@ -134,14 +134,14 @@ namespace Api\Settings {
->from("Settings") ->from("Settings")
->where(new CondBool("readonly")) ->where(new CondBool("readonly"))
->where(new CondIn(new Column("name"), $keys)) ->where(new CondIn(new Column("name"), $keys))
->limit(1) ->first()
->execute(); ->execute();
$this->success = ($res !== FALSE); $this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError(); $this->lastError = $sql->getLastError();
if ($this->success && !empty($res)) { if ($this->success && $res !== null) {
return $res[0]["name"]; return $res["name"];
} }
return null; return null;

@ -690,21 +690,20 @@ namespace Api\User {
->from("User") ->from("User")
->where(new Compare("User.name", $username), new Compare("User.email", $username)) ->where(new Compare("User.name", $username), new Compare("User.email", $username))
->leftJoin("2FA", "2FA.uid", "User.2fa_id") ->leftJoin("2FA", "2FA.uid", "User.2fa_id")
->limit(1) ->first()
->execute(); ->execute();
$this->success = ($res !== FALSE); $this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError(); $this->lastError = $sql->getLastError();
if ($this->success) { if ($this->success) {
if (!is_array($res) || count($res) === 0) { if ($res === null) {
return $this->wrongCredentials(); return $this->wrongCredentials();
} else { } else {
$row = $res[0]; $uid = $res['uid'];
$uid = $row['uid']; $confirmed = $sql->parseBool($res["confirmed"]);
$confirmed = $sql->parseBool($row["confirmed"]); $token = $res["2fa_id"] ? TwoFactorToken::newInstance($res["2fa_type"], $res["2fa_data"], $res["2fa_id"], $sql->parseBool($res["2fa_confirmed"])) : null;
$token = $row["2fa_id"] ? TwoFactorToken::newInstance($row["2fa_type"], $row["2fa_data"], $row["2fa_id"], $sql->parseBool($row["2fa_confirmed"])) : null; if (password_verify($password, $res['password'])) {
if (password_verify($password, $row['password'])) {
if (!$confirmed) { if (!$confirmed) {
$this->result["emailConfirmed"] = false; $this->result["emailConfirmed"] = false;
return $this->createError("Your email address has not been confirmed yet."); return $this->createError("Your email address has not been confirmed yet.");

@ -134,9 +134,9 @@ class MySQL extends SQL {
return $sqlParams; return $sqlParams;
} }
protected function execute($query, $values = NULL, $returnValues = false) { protected function execute($query, $values = NULL, int $fetchType = self::FETCH_NONE) {
$resultRows = array(); $result = null;
$this->lastError = ""; $this->lastError = "";
$stmt = null; $stmt = null;
$res = null; $res = null;
@ -145,10 +145,21 @@ class MySQL extends SQL {
try { try {
if (empty($values)) { if (empty($values)) {
$res = mysqli_query($this->connection, $query); $res = mysqli_query($this->connection, $query);
$success = $res !== FALSE; $success = ($res !== FALSE);
if ($success && $returnValues) { if ($success) {
while ($row = $res->fetch_assoc()) { switch ($fetchType) {
$resultRows[] = $row; case self::FETCH_NONE:
$result = true;
break;
case self::FETCH_ONE:
$result = $res->fetch_assoc();
break;
case self::FETCH_ALL:
$result = $res->fetch_all(MYSQLI_ASSOC);
break;
case self::FETCH_ITERATIVE:
$result = new RowIteratorMySQL($res);
break;
} }
} }
} else if ($stmt = $this->connection->prepare($query)) { } else if ($stmt = $this->connection->prepare($query)) {
@ -156,18 +167,27 @@ class MySQL extends SQL {
$sqlParams = $this->getPreparedParams($values); $sqlParams = $this->getPreparedParams($values);
if ($stmt->bind_param(...$sqlParams)) { if ($stmt->bind_param(...$sqlParams)) {
if ($stmt->execute()) { if ($stmt->execute()) {
if ($returnValues) { if ($fetchType === self::FETCH_NONE) {
$result = true;
$success = true;
} else {
$res = $stmt->get_result(); $res = $stmt->get_result();
if ($res) { if ($res) {
while ($row = $res->fetch_assoc()) { switch ($fetchType) {
$resultRows[] = $row; case self::FETCH_ONE:
$result = $res->fetch_assoc();
break;
case self::FETCH_ALL:
$result = $res->fetch_all(MYSQLI_ASSOC);
break;
case self::FETCH_ITERATIVE:
$result = new RowIteratorMySQL($res);
break;
} }
$success = true; $success = true;
} else { } else {
$this->lastError = $this->logger->error("PreparedStatement::get_result failed: $stmt->error ($stmt->errno)"); $this->lastError = $this->logger->error("PreparedStatement::get_result failed: $stmt->error ($stmt->errno)");
} }
} else {
$success = true;
} }
} else { } else {
$this->lastError = $this->logger->error("PreparedStatement::execute failed: $stmt->error ($stmt->errno)"); $this->lastError = $this->logger->error("PreparedStatement::execute failed: $stmt->error ($stmt->errno)");
@ -179,16 +199,18 @@ class MySQL extends SQL {
} catch (\mysqli_sql_exception $exception) { } catch (\mysqli_sql_exception $exception) {
$this->lastError = $this->logger->error("MySQL::execute failed: $stmt->error ($stmt->errno)"); $this->lastError = $this->logger->error("MySQL::execute failed: $stmt->error ($stmt->errno)");
} finally { } finally {
if ($res !== null && !is_bool($res)) {
if ($res !== null && !is_bool($res) && $fetchType !== self::FETCH_ITERATIVE) {
$res->close(); $res->close();
} }
if ($stmt !== null && !is_bool($stmt)) { if ($stmt !== null && !is_bool($stmt)) {
$stmt->close(); $stmt->close();
} }
} }
return ($success && $returnValues) ? $resultRows : $success; return $success ? $result : false;
} }
public function getOnDuplicateStrategy(?Strategy $strategy, &$params): ?string { public function getOnDuplicateStrategy(?Strategy $strategy, &$params): ?string {
@ -440,3 +462,42 @@ class MySQL extends SQL {
} }
} }
} }
class RowIteratorMySQL extends RowIterator {
public function __construct($resultSet, bool $useCache = false) {
parent::__construct($resultSet, $useCache);
}
protected function getNumRows(): int {
return $this->resultSet->num_rows;
}
protected function fetchRow(int $index): array {
// check if we already fetched that row
if (!$this->useCache || $index >= count($this->fetchedRows)) {
// if not, fetch it from the result set
$row = $this->resultSet->fetch_assoc();
if ($this->useCache) {
$this->fetchedRows[] = $row;
}
// close result set, after everything's fetched
if ($index >= $this->numRows - 1) {
$this->resultSet->close();
}
} else {
$row = $this->fetchedRows[$index];
}
return $row;
}
public function rewind() {
if ($this->useCache) {
$this->rowIndex = 0;
} else if ($this->rowIndex !== 0) {
throw new \Exception("RowIterator::rewind() not supported, when caching is disabled");
}
}
}

@ -92,7 +92,7 @@ class PostgreSQL extends SQL {
return $lastError; return $lastError;
} }
protected function execute($query, $values = NULL, $returnValues = false) { protected function execute($query, $values = NULL, int $fetchType = self::FETCH_NONE) {
$this->lastError = ""; $this->lastError = "";
$stmt_name = uniqid(); $stmt_name = uniqid();
@ -132,17 +132,21 @@ class PostgreSQL extends SQL {
return false; return false;
} }
if ($returnValues) { switch ($fetchType) {
case self::FETCH_NONE:
return true;
case self::FETCH_ONE:
return pg_fetch_assoc($result);
case self::FETCH_ALL:
$rows = pg_fetch_all($result); $rows = pg_fetch_all($result);
if ($rows === FALSE) { if ($rows === FALSE) {
if (empty(trim($this->getLastError()))) { if (empty(trim($this->getLastError()))) {
$rows = array(); $rows = array();
} }
} }
return $rows; return $rows;
} else { case self::FETCH_ITERATIVE:
return true; return new RowIteratorPostgreSQL($result);
} }
} }
@ -182,8 +186,13 @@ class PostgreSQL extends SQL {
return $columns ? (" RETURNING " . $this->columnName($columns)) : ""; return $columns ? (" RETURNING " . $this->columnName($columns)) : "";
} }
public function executeQuery(Query $query, bool $fetchResult = false) { public function executeQuery(Query $query, int $fetchType = self::FETCH_NONE) {
return parent::executeQuery($query, $fetchResult || ($query instanceof Insert && !empty($query->getReturning())));
if ($query instanceof Insert && !empty($query->getReturning())) {
$fetchType = self::FETCH_ONE;
}
return parent::executeQuery($query, $fetchType);
} }
protected function fetchReturning($res, string $returningCol) { protected function fetchReturning($res, string $returningCol) {
@ -450,3 +459,22 @@ class PostgreSQL extends SQL {
} }
} }
} }
class RowIteratorPostgreSQL extends RowIterator {
public function __construct($resultSet, bool $useCache = false) {
parent::__construct($resultSet, false); // caching not needed
}
protected function getNumRows(): int {
return pg_num_rows($this->resultSet);
}
public function rewind() {
$this->rowIndex = 0;
}
protected function fetchRow(int $index): array {
return pg_fetch_assoc($this->resultSet, $index);
}
}

@ -15,10 +15,12 @@ class Select extends Query {
private array $joins; private array $joins;
private array $orderColumns; private array $orderColumns;
private array $groupColumns; private array $groupColumns;
private array $havings;
private bool $sortAscending; private bool $sortAscending;
private int $limit; private int $limit;
private int $offset; private int $offset;
private bool $forUpdate; private bool $forUpdate;
private int $fetchType;
public function __construct($sql, ...$selectValues) { public function __construct($sql, ...$selectValues) {
parent::__construct($sql); parent::__construct($sql);
@ -33,6 +35,7 @@ class Select extends Query {
$this->offset = 0; $this->offset = 0;
$this->sortAscending = true; $this->sortAscending = true;
$this->forUpdate = false; $this->forUpdate = false;
$this->fetchType = SQL::FETCH_ALL;
} }
public function from(...$tables): Select { public function from(...$tables): Select {
@ -95,8 +98,19 @@ class Select extends Query {
return $this; return $this;
} }
public function iterator(): Select {
$this->fetchType = SQL::FETCH_ITERATIVE;
return $this;
}
public function first(): Select {
$this->fetchType = SQL::FETCH_ONE;
$this->limit = 1;
return $this;
}
public function execute() { public function execute() {
return $this->sql->executeQuery($this, true); return $this->sql->executeQuery($this, $this->fetchType);
} }
public function getSelectValues(): array { return $this->selectValues; } public function getSelectValues(): array { return $this->selectValues; }

@ -0,0 +1,39 @@
<?php
namespace Driver\SQL;
abstract class RowIterator implements \Iterator {
protected $resultSet;
protected int $rowIndex;
protected array $fetchedRows;
protected int $numRows;
protected bool $useCache;
public function __construct($resultSet, bool $useCache = false) {
$this->resultSet = $resultSet;
$this->fetchedRows = [];
$this->rowIndex = 0;
$this->numRows = $this->getNumRows();
$this->useCache = $useCache;
}
protected abstract function getNumRows(): int;
protected abstract function fetchRow(int $index): array;
public function current() {
return $this->fetchRow($this->rowIndex);
}
public function next() {
$this->rowIndex++;
}
public function key() {
return $this->rowIndex;
}
public function valid(): bool {
return $this->rowIndex < $this->numRows;
}
}

@ -41,6 +41,11 @@ use Objects\ConnectionData;
abstract class SQL { abstract class SQL {
const FETCH_NONE = 0;
const FETCH_ONE = 1;
const FETCH_ALL = 2;
const FETCH_ITERATIVE = 3;
protected Logger $logger; protected Logger $logger;
protected string $lastError; protected string $lastError;
protected $connection; protected $connection;
@ -116,7 +121,7 @@ abstract class SQL {
public abstract function connect(); public abstract function connect();
public abstract function disconnect(); public abstract function disconnect();
public function executeQuery(Query $query, bool $fetchResult = false) { public function executeQuery(Query $query, int $fetchType = self::FETCH_NONE) {
$parameters = []; $parameters = [];
$queryStr = $query->build($parameters); $queryStr = $query->build($parameters);
@ -130,16 +135,16 @@ abstract class SQL {
return false; return false;
} }
$res = $this->execute($queryStr, $parameters, $fetchResult); $res = $this->execute($queryStr, $parameters, $fetchType);
$success = ($res !== FALSE); $success = ($res !== FALSE);
// fetch generated serial ids for Insert statements // fetch generated serial ids for Insert statements
$generatedColumn = ($query instanceof Insert ? $query->getReturning() : null); $generatedColumn = ($query instanceof Insert ? $query->getReturning() : null);
if($success && $generatedColumn) { if ($success && $generatedColumn) {
$this->fetchReturning($res, $generatedColumn); $this->fetchReturning($res, $generatedColumn);
} }
return $fetchResult ? $res : $success; return $fetchType === self::FETCH_NONE ? $success : $res;
} }
public function getWhereClause($conditions, &$params): string { public function getWhereClause($conditions, &$params): string {
@ -237,7 +242,7 @@ abstract class SQL {
} }
// Statements // Statements
protected abstract function execute($query, $values=NULL, $returnValues=false); protected abstract function execute($query, $values = NULL, int $fetchType = self::FETCH_NONE);
public function buildCondition($condition, &$params) { public function buildCondition($condition, &$params) {

@ -5,7 +5,7 @@ if (is_file($autoLoad)) {
require_once $autoLoad; require_once $autoLoad;
} }
define("WEBBASE_VERSION", "1.5.0"); define("WEBBASE_VERSION", "1.5.1");
spl_autoload_extensions(".php"); spl_autoload_extensions(".php");
spl_autoload_register(function($class) { spl_autoload_register(function($class) {