Files
hub-insurance/wp-content/mu-plugins/sqlite-database-integration/wp-includes/database/sqlite/class-wp-sqlite-connection.php
T
2026-07-02 15:54:39 -06:00

211 lines
6.5 KiB
PHP

<?php declare(strict_types = 1);
/*
* The SQLite connection uses PDO. Enable PDO function calls:
* phpcs:disable WordPress.DB.RestrictedClasses.mysql__PDO
*/
/**
* SQLite connection.
*
* This class configures and encapsulates the connection to an SQLite database.
* It requires PDO with the SQLite driver, and currently, it is only a simple
* wrapper that leaks some of the PDO APIs (returns PDOStatement values, etc.).
* In the future, we may abstract it away from PDO and support SQLite3 as well.
*/
class WP_SQLite_Connection {
/**
* The default timeout in seconds for SQLite to wait for a writable lock.
*/
const DEFAULT_SQLITE_TIMEOUT = 10;
/**
* The supported SQLite journal modes.
*
* See: https://www.sqlite.org/pragma.html#pragma_journal_mode
*/
const SQLITE_JOURNAL_MODES = array(
'DELETE',
'TRUNCATE',
'PERSIST',
'MEMORY',
'WAL',
'OFF',
);
/**
* The PDO connection for SQLite.
*
* @var PDO
*/
private $pdo;
/**
* A query logger callback.
*
* @var callable(string, array): void
*/
private $query_logger;
/**
* Constructor.
*
* Set up an SQLite connection.
*
* @param array $options {
* An array of options.
*
* @type string|null $path Optional. SQLite database path.
* For in-memory database, use ':memory:'.
* Must be set when PDO instance is not provided.
* @type PDO|null $pdo Optional. PDO instance with SQLite connection.
* If not provided, a new PDO instance will be created.
* @type int|null $timeout Optional. SQLite timeout in seconds.
* The time to wait for a writable lock.
* @type string|null $journal_mode Optional. SQLite journal mode.
* }
*
* @throws InvalidArgumentException When some connection options are invalid.
* @throws PDOException When the driver initialization fails.
*/
public function __construct( array $options ) {
// Setup PDO connection.
if ( isset( $options['pdo'] ) && $options['pdo'] instanceof PDO ) {
$this->pdo = $options['pdo'];
} else {
if ( ! isset( $options['path'] ) || ! is_string( $options['path'] ) ) {
throw new InvalidArgumentException( 'Option "path" is required when "connection" is not provided.' );
}
$pdo_class = PHP_VERSION_ID >= 80400 ? PDO\SQLite::class : PDO::class;
$this->pdo = new $pdo_class( 'sqlite:' . $options['path'] );
}
// Throw exceptions on error.
$this->pdo->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
// Configure SQLite timeout.
if ( isset( $options['timeout'] ) && is_int( $options['timeout'] ) ) {
$timeout = $options['timeout'];
} else {
$timeout = self::DEFAULT_SQLITE_TIMEOUT;
}
$this->pdo->setAttribute( PDO::ATTR_TIMEOUT, $timeout );
// Configure SQLite journal mode.
$journal_mode = $options['journal_mode'] ?? null;
if ( $journal_mode && in_array( $journal_mode, self::SQLITE_JOURNAL_MODES, true ) ) {
$this->query( 'PRAGMA journal_mode = ' . $journal_mode );
}
}
/**
* Execute a query in SQLite.
*
* @param string $sql The query to execute.
* @param array $params The query parameters.
* @throws PDOException When the query execution fails.
* @return PDOStatement The PDO statement object.
*/
public function query( string $sql, array $params = array() ): PDOStatement {
if ( $this->query_logger ) {
( $this->query_logger )( $sql, $params );
}
$stmt = $this->pdo->prepare( $sql );
$stmt->execute( $params );
return $stmt;
}
/**
* Prepare a SQLite query for execution.
*
* @param string $sql The query to prepare.
* @return PDOStatement The prepared statement.
* @throws PDOException When the query preparation fails.
*/
public function prepare( string $sql ): PDOStatement {
if ( $this->query_logger ) {
( $this->query_logger )( $sql, array() );
}
return $this->pdo->prepare( $sql );
}
/**
* Returns the ID of the last inserted row.
*
* @return string The ID of the last inserted row.
*/
public function get_last_insert_id(): string {
return $this->pdo->lastInsertId();
}
/**
* Quote a value for use in a query.
*
* @param mixed $value The value to quote.
* @param int $type The type of the value.
* @return string The quoted value.
*/
public function quote( $value, int $type = PDO::PARAM_STR ): string {
return $this->pdo->quote( $value, $type );
}
/**
* Quote an SQLite identifier.
*
* Wraps the identifier in backticks and escapes backtick characters within.
*
* ---
*
* Quoted identifiers in SQLite are represented by string constants:
*
* A string constant is formed by enclosing the string in single quotes (').
* A single quote within the string can be encoded by putting two single
* quotes in a row - as in Pascal. C-style escapes using the backslash
* character are not supported because they are not standard SQL.
*
* See: https://www.sqlite.org/lang_expr.html#literal_values_constants_
*
* Although sparsely documented, this applies to backtick and double quoted
* string constants as well, so only the quote character needs to be escaped.
*
* For more details, see the grammar for SQLite table and column names:
*
* - https://github.com/sqlite/sqlite/blob/873fc5dff2a781251f2c9bd2c791a5fac45b7a2b/src/tokenize.c#L395-L419
* - https://github.com/sqlite/sqlite/blob/873fc5dff2a781251f2c9bd2c791a5fac45b7a2b/src/parse.y#L321-L338
*
* ---
*
* We use backtick quotes instead of the SQL standard double quotes, due to
* an SQLite quirk causing double-quoted strings to be accepted as literals:
*
* This misfeature means that a misspelled double-quoted identifier will
* be interpreted as a string literal, rather than generating an error.
*
* See: https://www.sqlite.org/quirks.html#double_quoted_string_literals_are_accepted
*
* @param string $unquoted_identifier The unquoted identifier value.
* @return string The quoted identifier value.
*/
public function quote_identifier( string $unquoted_identifier ): string {
return '`' . str_replace( '`', '``', $unquoted_identifier ) . '`';
}
/**
* Get the PDO object.
*
* @return PDO
*/
public function get_pdo(): PDO {
return $this->pdo;
}
/**
* Set a logger for the queries.
*
* @param callable(string, array): void $logger A query logger callback.
*/
public function set_query_logger( callable $logger ): void {
$this->query_logger = $logger;
}
}