570 lines
16 KiB
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();
|
|
}
|
|
}
|