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