DB row iterator
This commit is contained in:
parent
8b49b26f24
commit
bce59c5f92
@ -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) {
|
||||||
$rows = pg_fetch_all($result);
|
case self::FETCH_NONE:
|
||||||
if ($rows === FALSE) {
|
return true;
|
||||||
if (empty(trim($this->getLastError()))) {
|
case self::FETCH_ONE:
|
||||||
$rows = array();
|
return pg_fetch_assoc($result);
|
||||||
|
case self::FETCH_ALL:
|
||||||
|
$rows = pg_fetch_all($result);
|
||||||
|
if ($rows === FALSE) {
|
||||||
|
if (empty(trim($this->getLastError()))) {
|
||||||
|
$rows = array();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
return $rows;
|
||||||
|
case self::FETCH_ITERATIVE:
|
||||||
return $rows;
|
return new RowIteratorPostgreSQL($result);
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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) {
|
||||||
@ -449,4 +458,23 @@ class PostgreSQL extends SQL {
|
|||||||
return parent::createExpression($exp, $params);
|
return parent::createExpression($exp, $params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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; }
|
||||||
|
39
core/Driver/SQL/RowIterator.class.php
Normal file
39
core/Driver/SQL/RowIterator.class.php
Normal file
@ -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) {
|
||||||
|
Loading…
Reference in New Issue
Block a user