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; } }