Skip to content

Instantly share code, notes, and snippets.

@adamziel
Last active November 14, 2025 12:54
Show Gist options
  • Select an option

  • Save adamziel/51ac62858a97937a84a4c4b9e8dea463 to your computer and use it in GitHub Desktop.

Select an option

Save adamziel/51ac62858a97937a84a4c4b9e8dea463 to your computer and use it in GitHub Desktop.
Ship mysqli functions with sqlite
{
"plugins": ["wp-staging"],
"steps": [
{
"step": "writeFile",
"path": "/wordpress/wp-content/mu-plugins/shim_mysqli.php",
"data": {
"resource": "url",
"url": "https://gist.githubusercontent.com/adamziel/51ac62858a97937a84a4c4b9e8dea463/raw/9185e69fc9e7d36f276e72aba92944107fd94bed/shim_mysqli.php"
}
}
]
}
<?php
/**
* Plugin Name: SQLite MySQLi Shim (AST)
* Description: Shims mysqli_* on top of sqlite-database-integration (AST driver).
*/
// Abort if not WordPress.
if ( ! defined( 'ABSPATH' ) ) {
// return;
}
// If real mysqli is present, do nothing.
if ( extension_loaded( 'mysqli' ) || function_exists( 'mysqli_connect' ) ) {
// return;
}
global $wpdb;
// Only run when the SQLite integration is actually the DB driver.
if ( ! isset( $wpdb ) || ! class_exists( 'WP_SQLite_DB' ) || ! ( $wpdb instanceof WP_SQLite_DB ) ) {
// return;
}
// ---- Basic constants --------------------------------------------------------
if ( ! defined( 'MYSQLI_ASSOC' ) ) {
define( 'MYSQLI_ASSOC', 1 );
}
if ( ! defined( 'MYSQLI_NUM' ) ) {
define( 'MYSQLI_NUM', 2 );
}
if ( ! defined( 'MYSQLI_BOTH' ) ) {
define( 'MYSQLI_BOTH', 3 );
}
if ( ! defined( 'MYSQLI_STORE_RESULT' ) ) {
define( 'MYSQLI_STORE_RESULT', 0 );
}
if ( ! defined( 'MYSQLI_USE_RESULT' ) ) {
define( 'MYSQLI_USE_RESULT', 1 );
}
// ---- Helper functions -------------------------------------------------------
/**
* Get the underlying WPDB / WP_SQLite_DB instance for a mysqli link.
*
* @param mixed $link
* @return WP_SQLite_DB
*/
function sqlite_mysqli_shim_wpdb( $link = null ) {
global $wpdb;
if ( $link instanceof mysqli && isset( $link->wpdb ) ) {
return $link->wpdb;
}
return $wpdb;
}
/**
* Rough check if SQL is a result-returning statement.
*
* @param string $sql
* @return bool
*/
function sqlite_mysqli_shim_is_select( $sql ) {
return (bool) preg_match( '/^\s*(SELECT|SHOW|DESCRIBE|EXPLAIN)\s/i', ltrim( $sql ) );
}
/**
* Escape a value like mysqli_real_escape_string would.
*
* @param mixed $value
* @return string
*/
function sqlite_mysqli_shim_escape( $value ) {
if ( is_null( $value ) ) {
return 'NULL';
}
// Use WordPress' esc_sql if available.
if ( function_exists( 'esc_sql' ) ) {
return "'" . esc_sql( (string) $value ) . "'";
}
return "'" . addslashes( (string) $value ) . "'";
}
// ---- mysqli OO classes ------------------------------------------------------
class mysqli {
/** @var WP_SQLite_DB */
public $wpdb;
/** @var int */
public $connect_errno = 0;
/** @var string */
public $connect_error = '';
/** @var int */
public $errno = 0;
/** @var string */
public $error = '';
/** @var int */
public $affected_rows = 0;
/** @var int|string */
public $insert_id = 0;
/**
* mysqli constructor.
*
* All connection params are ignored; connection is WordPress' `$wpdb`.
*/
public function __construct(
?string $host = null,
?string $username = null,
?string $passwd = null,
?string $dbname = null,
?int $port = null,
?string $socket = null
) {
$this->wpdb = sqlite_mysqli_shim_wpdb();
$this->connect_errno = 0;
$this->connect_error = '';
}
public function query( string $query, int $resultmode = MYSQLI_STORE_RESULT ) {
$result = mysqli_query( $this, $query, $resultmode );
$this->errno = mysqli_errno( $this );
$this->error = mysqli_error( $this );
$this->affected_rows = mysqli_affected_rows( $this );
$this->insert_id = mysqli_insert_id( $this );
return $result;
}
public function real_escape_string( string $string ): string {
return mysqli_real_escape_string( $this, $string );
}
public function close(): bool {
return mysqli_close( $this );
}
public function set_charset( string $charset ): bool {
return mysqli_set_charset( $this, $charset );
}
public function select_db( string $dbname ): bool {
return mysqli_select_db( $this, $dbname );
}
public function ping(): bool {
return mysqli_ping( $this );
}
public function prepare( string $query ) {
return mysqli_prepare( $this, $query );
}
}
class mysqli_result {
/** @var array<int,array<string,mixed>> */
public $rows = array();
/** @var int */
public $current_row = 0;
/** @var int */
public $num_rows = 0;
/**
* @param array<int,array<string,mixed>> $rows
*/
public function __construct( array $rows ) {
$this->rows = array_values( $rows );
$this->num_rows = count( $this->rows );
$this->current_row = 0;
}
public function fetch_assoc() {
return mysqli_fetch_assoc( $this );
}
public function fetch_array( int $resulttype = MYSQLI_BOTH ) {
return mysqli_fetch_array( $this, $resulttype );
}
public function fetch_row() {
return mysqli_fetch_row( $this );
}
public function fetch_object( string $class_name = 'stdClass', array $params = array() ) {
return mysqli_fetch_object( $this, $class_name, $params );
}
public function data_seek( int $offset ): bool {
if ( $offset < 0 || $offset >= $this->num_rows ) {
return false;
}
$this->current_row = $offset;
return true;
}
public function free(): void {
$this->rows = array();
$this->num_rows = 0;
$this->current_row = 0;
}
}
class mysqli_stmt {
/** @var mysqli */
public $mysqli;
/** @var string */
public $query;
/** @var string */
public $types = '';
/** @var array<int,mixed> */
public $params = array();
/** @var int */
public $affected_rows = 0;
/** @var int|string */
public $insert_id = 0;
/** @var mysqli_result|null */
public $result = null;
/** @var int */
public $errno = 0;
/** @var string */
public $error = '';
public function __construct( mysqli $link, string $query ) {
$this->mysqli = $link;
$this->query = $query;
}
public function bind_param( string $types, &...$vars ): bool {
$this->types = $types;
$this->params = &$vars;
return true;
}
/**
* Very simple emulation: translate "?" placeholders to %s/%d and use $wpdb->prepare().
*/
protected function build_query(): string {
$query = $this->query;
$placeholders = array();
$args = array();
$len = strlen( $this->types );
for ( $i = 0; $i < $len; $i++ ) {
$t = $this->types[ $i ];
$placeholders[] = ( $t === 'i' || $t === 'd' ) ? '%d' : '%s';
if ( array_key_exists( $i, $this->params ) ) {
$args[] = $this->params[ $i ];
}
}
$parts = explode( '?', $query );
if ( count( $parts ) - 1 !== count( $placeholders ) ) {
// Fallback: just let WPDB deal with any existing % placeholders.
return $query;
}
$rebuilt = array_shift( $parts );
foreach ( $placeholders as $idx => $ph ) {
$rebuilt .= $ph . $parts[ $idx ];
}
$wpdb = sqlite_mysqli_shim_wpdb();
array_unshift( $args, $rebuilt );
return $wpdb->prepare( ...$args );
}
public function execute(): bool {
$sql = $this->build_query();
$result = mysqli_query( $this->mysqli, $sql );
$this->errno = mysqli_errno( $this->mysqli );
$this->error = mysqli_error( $this->mysqli );
$this->affected_rows = mysqli_affected_rows( $this->mysqli );
$this->insert_id = mysqli_insert_id( $this->mysqli );
if ( $result instanceof mysqli_result ) {
$this->result = $result;
} else {
$this->result = null;
}
return $this->errno === 0;
}
public function get_result(): ?mysqli_result {
return $this->result;
}
public function close(): bool {
$this->result = null;
return true;
}
}
// ---- Procedural mysqli_* API -----------------------------------------------
// Connection / basic info -----------------------------------------------------
if ( ! function_exists( 'mysqli_connect' ) ) {
function mysqli_connect(
$host = null,
$username = null,
$passwd = null,
$dbname = null,
$port = null,
$socket = null
) {
return new mysqli( $host, $username, $passwd, $dbname, $port, $socket );
}
}
if ( ! function_exists( 'mysqli_close' ) ) {
function mysqli_close( $link ): bool {
// Nothing real to close, SQLite connection is shared.
return true;
}
}
if ( ! function_exists( 'mysqli_connect_errno' ) ) {
function mysqli_connect_errno(): int {
// We don't have a real remote connection; treat as always ok.
return 0;
}
}
if ( ! function_exists( 'mysqli_connect_error' ) ) {
function mysqli_connect_error(): ?string {
return null;
}
}
if ( ! function_exists( 'mysqli_select_db' ) ) {
function mysqli_select_db( $link, $dbname ): bool {
// Single SQLite DB, nothing to switch.
return true;
}
}
// Query execution -------------------------------------------------------------
if ( ! function_exists( 'mysqli_query' ) ) {
function mysqli_query( $link, string $query, int $resultmode = MYSQLI_STORE_RESULT ) {
$wpdb = sqlite_mysqli_shim_wpdb( $link );
if ( method_exists( $wpdb, 'db_connect' ) ) {
$wpdb->db_connect();
}
if ( sqlite_mysqli_shim_is_select( $query ) ) {
$rows = $wpdb->get_results( $query, ARRAY_A );
if ( $rows === null ) {
return false;
}
return new mysqli_result( $rows );
}
$res = $wpdb->query( $query );
if ( false === $res ) {
return false;
}
return true;
}
}
if ( ! function_exists( 'mysqli_real_query' ) ) {
function mysqli_real_query( $link, string $query ): bool {
$result = mysqli_query( $link, $query, MYSQLI_STORE_RESULT );
return $result !== false;
}
}
// Very naive multi-query: split on ';' and run sequentially.
$GLOBALS['sqlite_mysqli_multi_last_result'] = null;
if ( ! function_exists( 'mysqli_multi_query' ) ) {
function mysqli_multi_query( $link, string $query ): bool {
$wpdb = sqlite_mysqli_shim_wpdb( $link );
$queries = array_filter( array_map( 'trim', explode( ';', $query ) ) );
$last_result = null;
foreach ( $queries as $sql ) {
if ( $sql === '' ) {
continue;
}
if ( sqlite_mysqli_shim_is_select( $sql ) ) {
$rows = $wpdb->get_results( $sql, ARRAY_A );
if ( $rows === null ) {
return false;
}
$last_result = new mysqli_result( $rows );
} else {
$res = $wpdb->query( $sql );
if ( false === $res ) {
return false;
}
$last_result = true;
}
}
$GLOBALS['sqlite_mysqli_multi_last_result'] = $last_result;
return true;
}
}
if ( ! function_exists( 'mysqli_store_result' ) ) {
function mysqli_store_result( $link = null ) {
$res = $GLOBALS['sqlite_mysqli_multi_last_result'] ?? null;
return $res instanceof mysqli_result ? $res : null;
}
}
if ( ! function_exists( 'mysqli_use_result' ) ) {
function mysqli_use_result( $link = null ) {
return mysqli_store_result( $link );
}
}
if ( ! function_exists( 'mysqli_next_result' ) ) {
function mysqli_next_result( $link = null ): bool {
// We don't track multiple result sets, so always false.
return false;
}
}
// Result fetching -------------------------------------------------------------
if ( ! function_exists( 'mysqli_num_rows' ) ) {
function mysqli_num_rows( $result ): int {
if ( $result instanceof mysqli_result ) {
return $result->num_rows;
}
return 0;
}
}
if ( ! function_exists( 'mysqli_fetch_assoc' ) ) {
function mysqli_fetch_assoc( $result ) {
if ( ! ( $result instanceof mysqli_result ) ) {
return null;
}
if ( $result->current_row >= $result->num_rows ) {
return null;
}
$row = $result->rows[ $result->current_row ];
$result->current_row++;
return $row;
}
}
if ( ! function_exists( 'mysqli_fetch_row' ) ) {
function mysqli_fetch_row( $result ) {
$assoc = mysqli_fetch_assoc( $result );
if ( $assoc === null ) {
return null;
}
return array_values( $assoc );
}
}
if ( ! function_exists( 'mysqli_fetch_array' ) ) {
function mysqli_fetch_array( $result, int $resulttype = MYSQLI_BOTH ) {
$assoc = mysqli_fetch_assoc( $result );
if ( $assoc === null ) {
return null;
}
if ( MYSQLI_ASSOC === $resulttype ) {
return $assoc;
}
$num = array_values( $assoc );
if ( MYSQLI_NUM === $resulttype ) {
return $num;
}
// MYSQLI_BOTH.
$both = $num;
foreach ( $assoc as $key => $value ) {
$both[ $key ] = $value;
}
return $both;
}
}
if ( ! function_exists( 'mysqli_fetch_object' ) ) {
function mysqli_fetch_object( $result, string $class_name = 'stdClass', array $params = array() ) {
$assoc = mysqli_fetch_assoc( $result );
if ( $assoc === null ) {
return null;
}
if ( 'stdClass' === $class_name && empty( $params ) ) {
return (object) $assoc;
}
$obj = new $class_name( ...$params );
foreach ( $assoc as $k => $v ) {
$obj->{$k} = $v;
}
return $obj;
}
}
if ( ! function_exists( 'mysqli_fetch_all' ) ) {
function mysqli_fetch_all( $result, int $resulttype = MYSQLI_BOTH ): array {
$rows = array();
while ( null !== ( $row = mysqli_fetch_array( $result, $resulttype ) ) ) {
$rows[] = $row;
}
return $rows;
}
}
if ( ! function_exists( 'mysqli_free_result' ) ) {
function mysqli_free_result( $result ): void {
if ( $result instanceof mysqli_result ) {
$result->free();
}
}
}
if ( ! function_exists( 'mysqli_data_seek' ) ) {
function mysqli_data_seek( $result, int $offset ): bool {
if ( $result instanceof mysqli_result ) {
return $result->data_seek( $offset );
}
return false;
}
}
// Error / info ---------------------------------------------------------------
if ( ! function_exists( 'mysqli_error' ) ) {
function mysqli_error( $link = null ): string {
$wpdb = sqlite_mysqli_shim_wpdb( $link );
return (string) ( $wpdb->last_error ?? '' );
}
}
if ( ! function_exists( 'mysqli_errno' ) ) {
function mysqli_errno( $link = null ): int {
$wpdb = sqlite_mysqli_shim_wpdb( $link );
if ( ! empty( $wpdb->last_error ) ) {
// Generic client-side error code.
return 2000;
}
return 0;
}
}
if ( ! function_exists( 'mysqli_insert_id' ) ) {
function mysqli_insert_id( $link = null ) {
$wpdb = sqlite_mysqli_shim_wpdb( $link );
return $wpdb->insert_id;
}
}
if ( ! function_exists( 'mysqli_affected_rows' ) ) {
function mysqli_affected_rows( $link = null ): int {
$wpdb = sqlite_mysqli_shim_wpdb( $link );
return (int) $wpdb->rows_affected;
}
}
if ( ! function_exists( 'mysqli_field_count' ) ) {
function mysqli_field_count( $link = null ): int {
$wpdb = sqlite_mysqli_shim_wpdb( $link );
if ( isset( $wpdb->dbh ) && $wpdb->dbh instanceof WP_SQLite_Driver ) {
return $wpdb->dbh->get_last_column_count();
}
if ( is_array( $wpdb->last_result ) && ! empty( $wpdb->last_result ) ) {
$first = $wpdb->last_result[0];
return count( get_object_vars( $first ) );
}
return 0;
}
}
// Escaping / charset ---------------------------------------------------------
if ( ! function_exists( 'mysqli_real_escape_string' ) ) {
function mysqli_real_escape_string( $link, string $string ): string {
// Escape only, don't wrap in quotes.
if ( function_exists( 'esc_sql' ) ) {
return esc_sql( $string );
}
return addslashes( $string );
}
}
if ( ! function_exists( 'mysqli_set_charset' ) ) {
function mysqli_set_charset( $link, string $charset ): bool {
$wpdb = sqlite_mysqli_shim_wpdb( $link );
if ( method_exists( $wpdb, 'set_charset' ) ) {
$wpdb->set_charset( null, $charset );
}
return true;
}
}
// Server / client info --------------------------------------------------------
if ( ! function_exists( 'mysqli_get_server_info' ) ) {
function mysqli_get_server_info( $link = null ): string {
$wpdb = sqlite_mysqli_shim_wpdb( $link );
// WP_SQLite_DB::db_version() returns a MySQL-like version string.
return method_exists( $wpdb, 'db_version' ) ? $wpdb->db_version() : '8.0';
}
}
if ( ! function_exists( 'mysqli_get_client_info' ) ) {
function mysqli_get_client_info( $link = null ): string {
// Arbitrary string; some libs only display it.
return 'SQLite via sqlite-database-integration (AST)';
}
}
if ( ! function_exists( 'mysqli_get_host_info' ) ) {
function mysqli_get_host_info( $link = null ): string {
return 'SQLite (file-based) via sqlite-database-integration';
}
}
// Ping / transactions (best-effort / mostly no-op) ---------------------------
if ( ! function_exists( 'mysqli_ping' ) ) {
function mysqli_ping( $link = null ): bool {
// SQLite connection is local process, assume it's fine.
return true;
}
}
if ( ! function_exists( 'mysqli_autocommit' ) ) {
function mysqli_autocommit( $link, bool $mode ): bool {
// AST driver manages its own transactions; we don't mirror autocommit.
return true;
}
}
if ( ! function_exists( 'mysqli_begin_transaction' ) ) {
function mysqli_begin_transaction( $link, int $flags = 0, ?string $name = null ): bool {
$wpdb = sqlite_mysqli_shim_wpdb( $link );
$wpdb->query( 'START TRANSACTION' );
return true;
}
}
if ( ! function_exists( 'mysqli_commit' ) ) {
function mysqli_commit( $link, int $flags = 0, ?string $name = null ): bool {
$wpdb = sqlite_mysqli_shim_wpdb( $link );
$wpdb->query( 'COMMIT' );
return true;
}
}
if ( ! function_exists( 'mysqli_rollback' ) ) {
function mysqli_rollback( $link, int $flags = 0, ?string $name = null ): bool {
$wpdb = sqlite_mysqli_shim_wpdb( $link );
$wpdb->query( 'ROLLBACK' );
return true;
}
}
// Prepared statements (procedural) -------------------------------------------
if ( ! function_exists( 'mysqli_prepare' ) ) {
function mysqli_prepare( $link, string $query ) {
$link = $link instanceof mysqli ? $link : new mysqli();
return new mysqli_stmt( $link, $query );
}
}
if ( ! function_exists( 'mysqli_stmt_bind_param' ) ) {
function mysqli_stmt_bind_param( $stmt, string $types, &...$vars ): bool {
if ( ! ( $stmt instanceof mysqli_stmt ) ) {
return false;
}
return $stmt->bind_param( $types, ...$vars );
}
}
if ( ! function_exists( 'mysqli_stmt_execute' ) ) {
function mysqli_stmt_execute( $stmt ): bool {
if ( ! ( $stmt instanceof mysqli_stmt ) ) {
return false;
}
return $stmt->execute();
}
}
if ( ! function_exists( 'mysqli_stmt_get_result' ) ) {
function mysqli_stmt_get_result( $stmt ) {
if ( ! ( $stmt instanceof mysqli_stmt ) ) {
return false;
}
return $stmt->get_result();
}
}
if ( ! function_exists( 'mysqli_stmt_close' ) ) {
function mysqli_stmt_close( $stmt ): bool {
if ( ! ( $stmt instanceof mysqli_stmt ) ) {
return false;
}
return $stmt->close();
}
}
if ( ! function_exists( 'mysqli_stmt_errno' ) ) {
function mysqli_stmt_errno( $stmt ): int {
if ( ! ( $stmt instanceof mysqli_stmt ) ) {
return 0;
}
return $stmt->errno;
}
}
if ( ! function_exists( 'mysqli_stmt_error' ) ) {
function mysqli_stmt_error( $stmt ): string {
if ( ! ( $stmt instanceof mysqli_stmt ) ) {
return '';
}
return $stmt->error;
}
}
if ( ! function_exists( 'mysqli_stmt_affected_rows' ) ) {
function mysqli_stmt_affected_rows( $stmt ): int {
if ( ! ( $stmt instanceof mysqli_stmt ) ) {
return 0;
}
return (int) $stmt->affected_rows;
}
}
if ( ! function_exists( 'mysqli_stmt_insert_id' ) ) {
function mysqli_stmt_insert_id( $stmt ) {
if ( ! ( $stmt instanceof mysqli_stmt ) ) {
return 0;
}
return $stmt->insert_id;
}
}
// Everything else (debug, stats, etc.) can be added here as needed, either
// as thin wrappers or safe no-ops to satisfy plugins expecting them.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment