index/src/Data/SQLite/SQLiteConnection.php

570 lines
16 KiB
PHP

<?php
// SQLiteConnection.php
// Created: 2021-05-02
// Updated: 2022-02-27
namespace Index\Data\SQLite;
use SQLite3;
use Index\Data\BeginTransactionFailedException;
use Index\Data\DataException;
use Index\Data\IDbConnection;
use Index\Data\IDbTransactions;
use Index\Data\IDbStatement;
use Index\Data\IDbResult;
use Index\Data\CommitFailedException;
use Index\Data\ReleaseSavePointFailedException;
use Index\Data\RollbackFailedException;
use Index\Data\SavePointFailedException;
use Index\Data\QueryExecuteException;
use Index\IO\GenericStream;
use Index\IO\Stream;
/**
* Represents a client for an SQLite database.
*/
class SQLiteConnection implements IDbConnection, IDbTransactions {
/**
* CREATE INDEX authorizer.
*
* Second parameter will be the name of the index.
* Third parameter will be the name of the table.
*
* @var int
*/
public const CREATE_INDEX = SQLite3::CREATE_INDEX;
/**
* CREATE TABLE authorizer.
*
* Second parameter will be the name of the table.
*
* @var int
*/
public const CREATE_TABLE = SQLite3::CREATE_TABLE;
/**
* CREATE TEMP INDEX authorizer.
*
* Second parameter will be the name of the index.
* Third parameter will be the name of the table.
*
* @var int
*/
public const CREATE_TEMP_INDEX = SQLite3::CREATE_TEMP_INDEX;
/**
* CREATE TEMP TABLE authorizer.
*
* Second parameter will be the name of the table.
*
* @var int
*/
public const CREATE_TEMP_TABLE = SQLite3::CREATE_TEMP_TABLE;
/**
* CREATE TEMP TRIGGER authorizer.
*
* Second parameter will be the name of the trigger.
* Third parameter will be the name of the table.
*
* @var int
*/
public const CREATE_TEMP_TRIGGER = SQLite3::CREATE_TEMP_TRIGGER;
/**
* CREATE TEMP VIEW authorizer.
*
* Second parameter will be the name of the view.
*
* @var int
*/
public const CREATE_TEMP_VIEW = SQLite3::CREATE_TEMP_VIEW;
/**
* CREATE TRIGGER authorizer.
*
* Second parameter will be the name of the trigger.
* Third parameter will be the name of the table.
*
* @var int
*/
public const CREATE_TRIGGER = SQLite3::CREATE_TRIGGER;
/**
* CREATE VIEW authorizer.
*
* Second parameter will be the name of the view.
*
* @var int
*/
public const CREATE_VIEW = SQLite3::CREATE_VIEW;
/**
* DELETE authorizer.
*
* Second parameter will be the name of the table.
*
* @var int
*/
public const DELETE = SQLite3::DELETE;
/**
* DROP INDEX authorizer.
*
* Second parameter will be the name of the index.
* Third parameter will be the name of the table.
*
* @var int
*/
public const DROP_INDEX = SQLite3::DROP_INDEX;
/**
* DROP TABLE authorizer.
*
* Second parameter will be the name of the table.
*
* @var int
*/
public const DROP_TABLE = SQLite3::DROP_TABLE;
/**
* DROP TEMP INDEX authorizer.
*
* Second parameter will be the name of the index.
* Third parameter will be the name of the table.
*
* @var int
*/
public const DROP_TEMP_INDEX = SQLite3::DROP_TEMP_INDEX;
/**
* DROP TEMP TABLE authorizer.
*
* Second parameter will be the name of the table.
*
* @var int
*/
public const DROP_TEMP_TABLE = SQLite3::DROP_TEMP_TABLE;
/**
* DROP TEMP TRIGGER authorizer.
*
* Second parameter will be the name of the trigger.
* Third parameter will be the name of the table.
*
* @var int
*/
public const DROP_TEMP_TRIGGER = SQLite3::DROP_TEMP_TRIGGER;
/**
* DROP TEMP VIEW authorizer.
*
* Second parameter will be the name of the view.
*
* @var int
*/
public const DROP_TEMP_VIEW = SQLite3::DROP_TEMP_VIEW;
/**
* DROP TRIGGER authorizer.
*
* Second parameter will be the name of the trigger.
* Third parameter will be the name of the table.
*
* @var int
*/
public const DROP_TRIGGER = SQLite3::DROP_TRIGGER;
/**
* DROP VIEW authorizer.
*
* Second parameter will be the name of the view.
*
* @var int
*/
public const DROP_VIEW = SQLite3::DROP_VIEW;
/**
* INSERT authorizer.
*
* Second parameter will be the name of the table.
*
* @var int
*/
public const INSERT = SQLite3::INSERT;
/**
* PRAGMA authorizer.
*
* Second parameter will be the name of the pragma.
* Third parameter may be the first argument passed to the pragma.
*
* @var int
*/
public const PRAGMA = SQLite3::PRAGMA;
/**
* READ authorizer.
*
* Second parameter will be the name of the table.
* Third parameter will be the name of the column.
*
* @var int
*/
public const READ = SQLite3::READ;
/**
* SELECT authorizer.
*
* @var int
*/
public const SELECT = SQLite3::SELECT;
/**
* TRANSACTION authorizer.
*
* Second parameter will be the operation.
*
* @var int
*/
public const TRANSACTION = SQLite3::TRANSACTION;
/**
* UPDATE authorizer.
*
* Second parameter will be the name of the table.
* Third parameter will be the name of the column.
*
* @var int
*/
public const UPDATE = SQLite3::UPDATE;
/**
* ATTACH authorizer.
*
* Second parameter will be the filename.
*
* @var int
*/
public const ATTACH = SQLite3::ATTACH;
/**
* DETACH authorizer.
*
* Second parameter will be the name of the database.
*
* @var int
*/
public const DETACH = SQLite3::DETACH;
/**
* ALTER TABLE authorizer.
*
* Second parameter will be the name of the database.
* Third parameter will be the name of the table.
*
* @var int
*/
public const ALTER_TABLE = SQLite3::ALTER_TABLE;
/**
* REINDEX authorizer.
*
* Second parameter will be the name of the index.
*
* @var int
*/
public const REINDEX = SQLite3::REINDEX;
/**
* ANALYZE authorizer.
*
* Second parameter will be the name of the table.
*
* @var int
*/
public const ANALYZE = SQLite3::ANALYZE;
/**
* CREATE VTABLE authorizer.
*
* Second parameter will be the name of the table.
* Third parameter will be the name of the module.
*
* @var int
*/
public const CREATE_VTABLE = SQLite3::CREATE_VTABLE;
/**
* DROP VTABLE authorizer.
*
* Second parameter will be the name of the table.
* Third parameter will be the name of the module.
*
* @var int
*/
public const DROP_VTABLE = SQLite3::DROP_VTABLE;
/**
* FUNCTION authorizer.
*
* Third parameter will be the name of the function.
*
* @var int
*/
public const FUNCTION = SQLite3::FUNCTION;
/**
* SAVEPOINT authorizer.
*
* Second parameter will be the operation.
* Third parameter will be the name of the savepoint.
*
* @var int
*/
public const SAVEPOINT = SQLite3::SAVEPOINT;
/**
* RECURSIVE authorizer.
*
* @var int
*/
public const RECURSIVE = SQLite3::RECURSIVE;
/**
* DETERMINISTIC flag for SQLite registered functions.
*
* When specified the function will always return the same result given the same inputs within a single SQL statement.
*
* @var int
*/
public const DETERMINISTIC = SQLITE3_DETERMINISTIC;
/**
* OK authorization status.
*
* @var int
*/
public const OK = SQLite3::OK;
/**
* DENY authorization status.
*
* @var int
*/
public const DENY = SQLite3::DENY;
/**
* IGNORE authorization status.
*
* @var int
*/
public const IGNORE = SQLite3::IGNORE;
private SQLite3 $connection;
private bool $autoCommit = true;
/**
* Creates a new SQLite client.
*
* @param SQLiteConnectionInfo $connectionInfo Information about the client.
* @return SQLiteConnection A new SQLite client.
*/
public function __construct(SQLiteConnectionInfo $connectionInfo) {
$flags = 0;
if($connectionInfo->shouldReadOnly())
$flags |= SQLITE3_OPEN_READONLY;
else
$flags |= SQLITE3_OPEN_READWRITE;
if($connectionInfo->shouldCreate())
$flags |= SQLITE3_OPEN_CREATE;
$this->connection = new SQLite3(
$connectionInfo->getFileName(),
$flags,
$connectionInfo->getEncryptionKey()
);
$this->connection->enableExceptions(true);
}
/**
* Gets the number of rows affected by the last operation.
*
* @return int|string Number of rows affected by the last operation.
*/
public function getAffectedRows(): int|string {
return $this->connection->changes();
}
/**
* Registers a PHP function for use as an SQL aggregate function.
*
* @see https://www.php.net/manual/en/sqlite3.createaggregate.php
* @param string $name Name of the SQL aggregate.
* @param callable $stepCallback Callback function to be called for each row of the result set.
* Definition: step(mixed $context, int $rowNumber, mixed $value, mixed ...$values): mixed
* @param callable $finalCallback Callback function to be called to finalize the aggregate operation and return the value.
* Definition: fini(mixed $content, int $rowNumber): mixed
* @param int $argCount Number of arguments this aggregate function takes, -1 for any amount.
* @return bool true if the aggregate function was created successfully, false if not.
*/
public function createAggregate(string $name, callable $stepCallback, callable $finalCallback, int $argCount = -1): bool {
return $this->connection->createAggregate($name, $stepCallback, $finalCallback, $argCount);
}
/**
* Registers a PHP function for use as an SQL collating function.
*
* @see https://www.php.net/manual/en/sqlite3.createcollation.php
* @param string $name Name of the SQL collating function.
* @param callable $callback Callback function that returns -1, 0 or 1 to indicate sort order.
* Definition: collation(mixed $value1, mixed $value2): int
* @return bool true if the collating function was registered, false if not.
*/
public function createCollation(string $name, callable $callback): bool {
return $this->connection->createCollation($name, $callback);
}
/**
* Register a PHP function for use as an SQL scalar function.
*
* @see https://www.php.net/manual/en/sqlite3.createfunction.php
* @param string $name Name of the SQL function.
* @param callable $callback Callback function to register.
* Definition: callback(mixed $value, mixed ...$values): mixed
* @param int $argCount Number of arguments this aggregate function takes, -1 for any amount.
* @param int $flags A bitset of flags.
*/
public function createFunction(string $name, callable $callback, int $argCount = -1, int $flags = 0): bool {
return $this->connection->createFunction($name, $callback, $argCount, $flags);
}
/**
* Sets a callback to be used as an authorizer to limit what statements can do.
*
* @param callable|null $callback An authorizer callback or null to remove the previous authorizer.
* Definition: authorizer(int $actionCode, mixed $param2, mixed $param3, string $database, mixed $trigger): int
* $param2 and $param3 differ based on $actionCode.
* Must return one of the OK, DENY or IGNORE constants.
* @return bool true if the action was successful, false if not.
*/
public function setAuthorizer(callable|null $callback): bool {
return $this->connection->setAuthorizer($callback);
}
/**
* Gets the last error string.
*
* @return string Last error string.
*/
public function getLastErrorString(): string {
return $this->connection->lastErrorMsg();
}
/**
* Gets the last error code.
*
* @return int Last error code.
*/
public function getLastErrorCode(): int {
return $this->connection->lastErrorCode();
}
/**
* Opens a BLOB field as a Stream.
*
* To be used instead of getStream on SQLiteResult.
*
* @param string $table Name of the source table.
* @param string $column Name of the source column.
* @param int $rowId ID of the source row.
* @throws DataException If opening the BLOB failed.
* @return Stream BLOB field as a Stream.
*/
public function getBlobStream(string $table, string $column, int $rowId): Stream {
$handle = $this->connection->openBlob($table, $column, $rowId);
if(!is_resource($handle))
throw new DataException((string)$this->getLastErrorString(), $this->getLastErrorCode());
return new GenericStream($this->connection->openBlob($table, $column, $rowId));
}
public function getLastInsertId(): int|string {
return $this->connection->lastInsertRowID();
}
public function prepare(string $query): IDbStatement {
$statement = $this->connection->prepare($query);
if($statement === false)
throw new QueryExecuteException((string)$this->getLastErrorString(), $this->getLastErrorCode());
return new SQLiteStatement($this, $statement);
}
public function query(string $query): IDbResult {
$result = $this->connection->query($query);
if($result === false)
throw new QueryExecuteException($this->getLastErrorString(), $this->getLastErrorCode());
return new SQLiteResult($result);
}
public function execute(string $query): int|string {
if(!$this->connection->exec($query))
throw new QueryExecuteException($this->getLastErrorString(), $this->getLastErrorCode());
return $this->connection->changes();
}
public function setAutoCommit(bool $state): void {
// there's not really a completely reliable way to set a persistent auto commit disable state
if($state === $this->autoCommit)
return;
$this->autoCommit = $state;
if($state) {
$this->commit();
} else {
$this->beginTransaction();
}
}
public function beginTransaction(): void {
if(!$this->connection->exec('BEGIN;'))
throw new BeginTransactionFailedException((string)$this->getLastErrorString(), $this->getLastErrorCode());
}
public function commit(): void {
if(!$this->connection->exec('COMMIT;'))
throw new CommitFailedException((string)$this->getLastErrorString(), $this->getLastErrorCode());
if(!$this->autoCommit)
$this->beginTransaction();
}
public function rollback(?string $name = null): void {
if(!$this->connection->exec(empty($name) ? 'ROLLBACK;' : "ROLLBACK TO {$name};"))
throw new CommitFailedException((string)$this->getLastErrorString(), $this->getLastErrorCode());
if(!$this->autoCommit)
$this->beginTransaction();
}
public function savePoint(string $name): void {
if(!$this->connection->exec("SAVEPOINT {$name};"))
throw new SavePointFailedException((string)$this->getLastErrorString(), $this->getLastErrorCode());
}
public function releaseSavePoint(string $name): void {
if(!$this->connection->exec("RELEASE SAVEPOINT {$name};"))
throw new ReleaseSavePointFailedException((string)$this->getLastErrorString(), $this->getLastErrorCode());
}
public function close(): void {
$this->connection->close();
}
/**
* Closes the connection and associated resources.
*/
public function __destruct() {
$this->close();
}
}