diff --git a/docs/images/pg4wp_design.png b/docs/images/pg4wp_design.png new file mode 100644 index 0000000..a76f979 Binary files /dev/null and b/docs/images/pg4wp_design.png differ diff --git a/pg4wp/core.php b/pg4wp/core.php index e77cb36..4f8f932 100644 --- a/pg4wp/core.php +++ b/pg4wp/core.php @@ -14,13 +14,6 @@ require_once ABSPATH . '/wp-includes/version.php'; require_once ABSPATH . '/wp-includes/cache.php'; require_once ABSPATH . '/wp-includes/l10n.php'; -if (!function_exists('wpsql_is_resource')) { - function wpsql_is_resource($object) - { - return $object !== false && $object !== null; - } -} - // Load the driver defined in 'db.php' require_once(PG4WP_ROOT . '/driver_' . DB_DRIVER . '.php'); @@ -29,20 +22,15 @@ $replaces = array( 'define( ' => '// define( ', 'class wpdb' => 'class wpdb2', 'new wpdb' => 'new wpdb2', - 'mysql_' => 'wpsql_', - 'is_resource' => 'wpsql_is_resource', + 'instanceof mysqli_result' => 'instanceof \PgSql\Result', + 'instanceof mysqli' => 'instanceof \PgSql\Connection', + '$this->dbh->connect_errno' => 'wpsqli_connect_error()', + 'mysqli_' => 'wpsqli_', + 'is_resource' => 'wpsqli_is_resource', ' '', '?>' => '', ); -// Ensure class uses the replaced mysql_ functions rather than mysqli_ -if (!defined('WP_USE_EXT_MYSQL')) { - define('WP_USE_EXT_MYSQL', true); -} -if (WP_USE_EXT_MYSQL != true) { - throw new \Exception("PG4SQL CANNOT BE ENABLED WITH MYSQLI, REMOVE ANY WP_USE_EXT_MYSQL configuration"); -} - eval(str_replace(array_keys($replaces), array_values($replaces), file_get_contents(ABSPATH . '/wp-includes/class-wpdb.php'))); // Create wpdb object if not already done diff --git a/pg4wp/db.php b/pg4wp/db.php index 27a8916..b5cca53 100644 --- a/pg4wp/db.php +++ b/pg4wp/db.php @@ -28,12 +28,6 @@ if(!defined('PG4WP_ROOT')) { define('PG4WP_LOG_ERRORS', true); } - if (!defined('PG4WP_INSECURE')) { - // If you want to allow insecure configuration (from the author point of view) to work with PG4WP, - // change this to true - define('PG4WP_INSECURE', false); - } - // This defines the directory where PG4WP files are loaded from // 3 places checked : wp-content, wp-content/plugins and the base directory if(file_exists(ABSPATH . 'wp-content/pg4wp')) { diff --git a/pg4wp/driver_mysql.php b/pg4wp/driver_mysql.php index 846a5ef..1a6233c 100644 --- a/pg4wp/driver_mysql.php +++ b/pg4wp/driver_mysql.php @@ -1,91 +1,986 @@ If you want to do it anyway, please set "PG4WP_INSECURE" to true in your "db.php" file.'); - } - - // Must connect to a specific database unlike MySQL - $dbname = defined('DB_NAME') && DB_NAME ? DB_NAME : 'template1'; - return pg_connect($GLOBALS['pg4wp_connstr'] . ' dbname=' . $dbname); + return new class { + public $sslkey; + public $sslcert; + public $sslca; + public $sslcapath; + public $sslcipher; + }; } /** - * Establishes a connection to a PostgreSQL database. + * Opens a connection to a PostgreSQL server in a real context. * - * This function connects to a PostgreSQL database by creating a connection string and using - * pg_connect. If the connection is successful, it stores the PostgreSQL server version into - * a global variable. The function also handles early transmitted SQL commands and initializes - * the connection with pg4wp_init(). + * This function is a wrapper for the pg_connect function, which attempts to establish + * a connection to a PostgreSQL server. The function takes in parameters for the host name, username, + * password, database name, port number, socket, and flags, The flags parameter can be used to set different + * connection options that can affect the behavior of the connection. * - * Note: Unlike the MySQL equivalent, pg_connect will return an existing connection if one - * exists with the same connection string. - * - * @param string $dbname The name of the database to connect to. - * @param int $connection_id The connection ID (Unused in this function). - * - * @return resource|bool The connection resource on success, or FALSE on failure. + * @param $connection dummy parameter just for compatibility with mysqli + * @param string|null $hostname The host name or an IP address. + * @param string|null $username The PostgreSQL user name. + * @param string|null $password The password associated with the username. + * @param string|null $database The default database to be used when performing queries. + * @param int|null $port The port number to attempt to connect to the PostgreSQL server. + * @param string|null $socket The socket or named pipe that should be used. + * @param int $flags Client connection flags. + * @return bool Returns TRUE on success or FALSE on failure. */ -function wpsql_select_db($dbname, $connection_id = 0) +function wpsqli_real_connect(&$connection, $hostname = null, $username = null, $password = null, $database = null, $port = null, $socket = null, $flags = 0) { - $pg_connstr = $GLOBALS['pg4wp_connstr'] . ' dbname=' . $dbname; + $GLOBALS['pg4wp_connstr'] = ''; - // pg_connect returns existing connection for same connection string - $GLOBALS['pg4wp_conn'] = $conn = pg_connect($pg_connstr); + if (!empty($hostname)) { + $GLOBALS['pg4wp_connstr'] .= ' host=' . $hostname; + } + + if (!empty($port)) { + $GLOBALS['pg4wp_connstr'] .= ' port=' . $port; + } + + if (!empty($username)) { + $GLOBALS['pg4wp_connstr'] .= ' user=' . $username; + } + + if (!empty($password)) { + $GLOBALS['pg4wp_connstr'] .= ' password=' . $password; + } + + // SSL parameters + if (!empty($connection->sslkey)) { + $GLOBALS['pg4wp_connstr'] .= ' sslkey=' . $connection->sslkey; + } + + if (!empty($connection->sslcert)) { + $GLOBALS['pg4wp_connstr'] .= ' sslcert=' . $connection->sslcert; + } + + if (!empty($connection->sslca)) { + $GLOBALS['pg4wp_connstr'] .= ' sslrootcert=' . $connection->sslca; + } + + if (!empty($connection->sslcapath)) { + $GLOBALS['pg4wp_connstr'] .= ' sslcapath=' . $connection->sslcapath; + } + + if (!empty($connection->sslcipher)) { + $GLOBALS['pg4wp_connstr'] .= ' sslcipher=' . $connection->sslcipher; + } + + // Must connect to a specific database unlike MySQL + $dbname = defined('DB_NAME') && DB_NAME ? DB_NAME : $database; + $pg_connstr = $GLOBALS['pg4wp_connstr'] . ' dbname=' . $database; + $GLOBALS['pg4wp_conn'] = $connection = pg_connect($pg_connstr); + + return $connection; +} + +/** +* Database Operations +*/ + +/** + * Selects the default database for database queries. + * + * This function is a wrapper for the pg_select_db function, which is used to change the default + * database for the connection. This is useful when performing multiple operations across different + * databases without having to establish a new connection for each one. If the function succeeds, + * it will return TRUE, indicating the database was successfully selected, or FALSE if it fails. + * + * @param PgSql\Connection $connection The pg connection resource. + * @param string $database The name of the database to select. + * @return bool Returns TRUE on success or FALSE on failure. + */ +function wpsqli_select_db(&$connection, $database) +{ + $pg_connstr = $GLOBALS['pg4wp_connstr'] . ' dbname=' . $database; + $GLOBALS['pg4wp_conn'] = $connection = pg_connect($pg_connstr); // Return FALSE if connection failed - if(!$conn) { - return $conn; + if(!$connection) { + return $connection; } // Get and store PostgreSQL server version - $ver = pg_version($conn); + $ver = pg_version($connection); if(isset($ver['server'])) { $GLOBALS['pg4wp_version'] = $ver['server']; } @@ -118,105 +147,360 @@ function wpsql_select_db($dbname, $connection_id = 0) // Execute any pre-defined SQL commands if(!empty($GLOBALS['pg4wp_pre_sql'])) { foreach($GLOBALS['pg4wp_pre_sql'] as $sql2run) { - wpsql_query($sql2run); + wpsqli_query($sql2run); } } - // Initialize connection with custom function - pg4wp_init($conn); - - return $conn; + return $connection; } /** - * Initializes the database environment for pg4wp. + * Closes a previously opened database connection. * - * This function sets up a MySQL-compatible `field` function in PostgreSQL. - * Note: In MySQL, the field function accepts arguments of heterogeneous types, - * but in PostgreSQL, it may not. + * This function is a wrapper for the pg_close function. + * // mysqli_close => pg_close (resource $connection): bool + * It's important to close connections when they are no longer needed to free up resources on both the web + * server and the PostgreSQL server. The function returns TRUE on success or FALSE on failure. * - * Note: ROW_NUMBER()+unnest approach is used for performance but might not guarantee order. - * Refer to https://stackoverflow.com/a/8767450 if it breaks. + * @param PgSql\Connection $connection The pg connection resource to be closed. + * @return bool Returns TRUE on successful closure, FALSE on failure. */ -function pg4wp_init() +function wpsqli_close(&$connection) { - // Database connection - $connection = $GLOBALS['pg4wp_conn']; - - /** - * SQL query to create or replace a PostgreSQL function named "field" - * which imitates MySQL's FIELD() function behavior. - * - * - ROW_NUMBER() is a window function in Postgres, used to assign a unique integer to rows. - * - unnest() is a Postgres function that takes an array and returns a set of rows. - * - * The function takes anyelement as the first parameter and anyarray as the second. - * It returns a BIGINT. - * - * SQL is used as the procedural language, and the function is marked as IMMUTABLE. - */ - $sql = <<<'SQL' - CREATE OR REPLACE FUNCTION field(anyelement, VARIADIC anyarray) - RETURNS BIGINT AS $$ - SELECT rownum - FROM ( - SELECT ROW_NUMBER() OVER () AS rownum, elem - FROM unnest($2) elem - ) AS numbered - WHERE numbered.elem = $1 - UNION ALL - SELECT 0 - $$ - LANGUAGE SQL IMMUTABLE; - SQL; - - // Execute the SQL query - $result = pg_query($connection, $sql); - - if ((PG4WP_DEBUG || PG4WP_LOG_ERRORS) && $result === false) { - $error = pg_last_error($connection); - error_log("[" . microtime(true) . "] Error creating MySQL-compatible field function: $error\n", 3, PG4WP_LOG . 'pg4wp_errors.log'); - } + // Closing a connection in PostgreSQL is straightforward. + return pg_close($connection); } /** - * Executes a SQL query on a Postgres database. + * Used to establish secure connections using SSL. * - * This function handles the SQL query by executing it on a Postgres database, - * if the global connection is available. Otherwise, it stores the SQL - * statement for later execution. It also handles errors and debugging. + * This function sets up variables on the fake pg connection class which are used when + * connecting to the postgres database with pg_connect * - * @param string $sql The SQL query string. - * @return resource|bool The query result resource on success, or FALSE on failure. - * - * Differences from MySQL equivalents: - * - Uses pg_query() instead of mysql_query() for executing the query. - * - Error handling is done using pg_last_error() instead of mysql_error(). - * - SQL rewriting is performed by the pg4wp_rewrite() function. + * @param PgSql\Connection $connection The pg connection resource. + * @param string $key The path to the key file. + * @param string $cert The path to the certificate file. + * @param string $ca The path to the certificate authority file. + * @param string $capath The pathname to a directory that contains trusted SSL CA certificates + * in PEM format. + * @param string $cipher A list of allowable ciphers to use for SSL encryption. + * @return bool Returns TRUE on success or FALSE on failure. */ -function wpsql_query($sql) +function wpsqli_ssl_set(&$connection, $key, $cert, $ca, $capath, $cipher) +{ + $connection->sslkey = $key; + $connection->sslcert = $cert; + $connection->sslca = $ca; + $connection->sslcapath = $capath; + $connection->sslcipher = $cipher; + return $connection; +} + +/** + * Returns the PostgreSQL client library version as a string. + * + * This function is used to retrieve the version of the client library that is used to compile the pg extension. The function + * does not require any parameters and can be called statically. It is helpful for debugging + * and ensuring that the PHP environment is using the correct version of the PostgreSQL client library, + * which can be important for compatibility and functionality reasons. + * + * @return string The PostgreSQL client library version. + */ +function wpsqli_get_client_info() +{ + // mysqli_get_client_info => No direct equivalent. + // Information can be derived from phpinfo() or phpversion(). + return '8.0.35'; // Just want to fool wordpress ... +} + +/** + * Retrieves the version of the PostgreSQL server. + * + * This function returns a string representing the version of the PostgreSQL server pointed to by the connection resource. This + * information can be used for a variety of purposes, such as conditional behavior for different + * PostgreSQL versions or simply for logging and monitoring. Understanding the server version is + * essential for ensuring compatibility with specific PostgreSQL features and syntax. + * + * @param PgSql\Connection $connection The pg connection resource. + * @return string The version of the PostgreSQL server. + */ +function wpsqli_get_server_info(&$connection) +{ + // mysqli_get_server_info => pg_version (resource $connection): array + // This function retrieves an array that includes server version. + // pg_version($connection); + return '8.0.35'; // Just want to fool wordpress ... +} + +/** + * Returns a string representing the type of connection used. + * + * This function is a wrapper for the pg_get_host_info function. It retrieves information about + * the type of connection that was established to the PostgreSQL server and the host server information. + * This includes the host name and the connection type, such as TCP/IP or a UNIX socket. It's useful + * for debugging and for understanding how PHP is communicating with the PostgreSQL server. + * + * @param PgSql\Connection $connection The pg connection resource. + * @return string A string describing the connection type and server host information. + */ +function wpsqli_host_info(&$connection) +{ + throw new \Exception("PG4WP: Not Yet Implemented"); + // mysqli_get_host_info => No direct equivalent. Host information is part of the connection string in PostgreSQL. +} + +/** + * Pings a server connection, or tries to reconnect if the connection has gone down. + * + * This function is a wrapper for the pg_ping function, which checks whether the + * connection to the server is working. If it has gone down, and the global option + * pg.reconnect is enabled, it will attempt to reconnect. This is useful to ensure + * that a connection is still alive and if not, to re-establish it before proceeding + * with further operations. It returns TRUE if the connection is alive or if it was + * successfully re-established, and FALSE if the connection is not established and + * cannot be re-established. + * + * @param PgSql\Connection $connection The pg connection resource. + * @return bool Returns TRUE on success or FALSE on failure. + */ +function wpsqli_ping(&$connection) +{ + return pg_ping($connection); +} + +/** + * Returns the thread ID for the current connection. + * + * This function is a wrapper for the pg_thread_id function. It retrieves the thread ID used by + * the current connection to the PostgreSQL server. This ID can be used as an argument to the KILL + * statement to terminate a connection. It is useful for debugging and managing PostgreSQL connections + * and can be used to uniquely identify the connection within the server's process. + * + * @param PgSql\Connection $connection The pg connection resource. + * @return int The thread ID for the current connection. + */ +function wpsqli_thread_id(&$connection) +{ + throw new \Exception("PG4WP: Not Yet Implemented"); + // mysqli_thread_id => No direct equivalent. PostgreSQL does not provide thread ID in the same manner as MySQL. +} + +/** + * Returns whether the client library is thread-safe. + * + * This function is a wrapper for the pg_thread_safe function. It indicates whether the + * pg client library that PHP is using is thread-safe. This is important information when + * running PHP in a multi-threaded environment such as with the worker MPM in Apache or when + * using multi-threading extensions in PHP. + * + * @return bool Returns TRUE if the client library is thread-safe, FALSE otherwise. + */ +function wpsqli_thread_safe() +{ + throw new \Exception("PG4WP: Not Yet Implemented"); + // mysqli_thread_safe => No direct equivalent. PostgreSQL's thread safety is dependent on PHP's thread safety. +} + +/** + * Gets the current system status of the PostgreSQL server. + * + * This function is a wrapper for the pg_stat function. It returns a string containing + * status information about the PostgreSQL server to which it's connected. The information includes + * uptime, threads, queries, open tables, and flush tables, among other status indicators. + * This can be useful for monitoring the health and performance of the PostgreSQL server, as well + * as for debugging purposes. + * + * @param PgSql\Connection $connection The pg connection resource. + * @return string A string describing the server status or FALSE on failure. + */ +function wpsqli_stat(&$connection) +{ + throw new \Exception("PG4WP: Not Yet Implemented"); + // mysqli_stat => No direct equivalent +} + +/** + * Sets extra connect options and affect behavior for a connection. + * + * This function is a wrapper for the pg_options function. It is used to set extra options + * for a connection resource before establishing a connection using pg_real_connect(). These + * options can be used to control various aspects of the connection's behavior. The function should + * be called after pg_init() and before pg_real_connect(). It returns TRUE on success or + * FALSE on failure. + * + * @param PgSql\Connection $connection The pg connection resource. + * @param int $option The specific option that is to be set. + * @param mixed $value The value for the specified option. + * @return bool Returns TRUE on success or FALSE on failure. + */ +function wpsqli_options(&$connection, $option, $value) +{ + throw new \Exception("PG4WP: Not Yet Implemented"); + // mysqli_options => No direct equivalent. Options are set in the connection string or via set_config in PostgreSQL. +} + +/** + * Returns the error code from the last connection attempt. + * + * This function is a wrapper for the pg_connect_errno function. It returns the error code from + * the last call to pg_connect() or pg_real_connect(). It is useful for error handling after + * attempting to establish a connection to a PostgreSQL server, allowing the script to respond appropriately + * to specific error conditions. The function does not take any parameters and returns an integer error + * code. If no error occurred during the last connection attempt, it will return zero. + * + * @return int The error code from the last connection attempt. + */ +function wpsqli_connect_errno() +{ + throw new \Exception("PG4WP: Not Yet Implemented"); +} + +/** + * Returns a string description of the last connect error. + * + * This function is a wrapper for the pg_connect_error function. It provides a textual description + * of the error from the last connection attempt made by pg_connect() or pg_real_connect(). + * Unlike pg_connect_errno(), which returns an error code, pg_connect_error() returns a string + * describing the error. This is useful for error handling, providing more detailed context about + * connection problems. + * + * @return string|null A string that describes the error from the last connection attempt, or NULL + * if no error occurred. + */ +function wpsqli_connect_error() +{ + if($GLOBALS['pg4wp_conn']) { + return pg_last_error($GLOBALS['pg4wp_conn']); + } + + return ''; +} + +/** +* Transaction Handling +*/ + +/** + * Turns on or off auto-commit mode on queries for the database connection. + * + * This function is a wrapper for the pg_autocommit function. When turned on, each query + * that you execute will automatically commit to the database. When turned off, you will need to + * manually commit transactions using pg_commit() or rollback using pg_rollback(). This + * function is particularly useful for transactions that require multiple steps and you don't want + * to commit until all steps are successful. + * + * @param PgSql\Connection $connection The pg connection resource. + * @param bool $mode Whether to turn on auto-commit mode or not. Pass TRUE to turn on auto-commit + * mode and FALSE to turn it off. + * @return bool Returns TRUE on success or FALSE on failure. + */ +function wpsqli_autocommit(&$connection, $mode) +{ + // mysqli_autocommit => pg_autocommit (resource $connection, bool $mode): bool + // PostgreSQL autocommit behavior is typically managed at the transaction level. + pg_query($connection, "SET AUTOCOMMIT TO ON"); +} + +/** + * Starts a new transaction. + * + * This function is a wrapper for the pg_begin_transaction function. It starts a new transaction + * with the provided connection and with the specified flags. Transactions allow multiple changes to + * be made to the database atomically - they will all be applied, or none will be, which can be controlled + * by committing or rolling back the transaction. This function can also set a name for the transaction, + * which can be used for savepoints. + * + * @param PgSql\Connection $connection The pg connection resource. + * @param int $flags Optional flags for defining transaction characteristics. This should be a bitmask + * of any of the pg_TRANS_START_* constants. + * @param string|null $name Optional name for the transaction, used for savepoint names. + * @return bool Returns TRUE on success or FALSE on failure. + */ +function wpsqli_begin_transaction(&$connection, $flags = 0, $name = null) +{ + // mysqli_begin_transaction => pg_query (resource $connection, string $query): resource + // PostgreSQL uses standard BEGIN or START TRANSACTION queries. + pg_query($connection, "BEGIN"); +} + +/** + * Commits the current transaction. + * + * This function is a wrapper for the pg_commit function. It is used to commit the current transaction + * for the database connection. Committing a transaction means that all the operations performed since the + * start of the transaction are permanently saved to the database. This function can also take optional flags + * and a name, the latter being used if the commit should be associated with a named savepoint. + * + * @param PgSql\Connection $connection The pg connection resource. + * @param int $flags Optional flags for the commit operation. It should be a bitmask of the pg_TRANS_COR_* constants. + * @param string|null $name Optional name for the savepoint that should be committed. + * @return bool Returns TRUE on success or FALSE on failure. + */ +function wpsqli_commit(&$connection, $flags = 0, $name = null) +{ + // mysqli_commit => pg_query (resource $connection, string $query): resource + // Commits are standard SQL in PostgreSQL. + pg_query($connection, "COMMIT"); +} + +/** + * Rolls back the current transaction for the database connection. + * + * This function is a wrapper for the pg_rollback function. It rolls back the current transaction, + * undoing all changes made to the database in the current transaction. This is an essential feature + * for maintaining data integrity, especially in situations where a series of database operations need + * to be treated as an atomic unit. The function can also accept optional flags and a name, which can be + * used to rollback to a named savepoint within the transaction rather than rolling back the entire transaction. + * + * @param PgSql\Connection $connection The pg connection resource. + * @param int $flags Optional flags that define how the rollback operation should be handled. It should be + * a bitmask of the pg_TRANS_COR_* constants. + * @param string|null $name Optional name of the savepoint to which the rollback operation should be directed. + * @return bool Returns TRUE on success or FALSE on failure. + */ +function wpsqli_rollback(&$connection, $flags = 0, $name = null) +{ + // mysqli_rollback => pg_query (resource $connection, string $query): resource + // Rollbacks are standard SQL in PostgreSQL. + pg_query($connection, "ROLLBACK"); +} + +/** + * Performs a query against the database. + * + * This function is a wrapper for the pg_query function. The pg_query function performs + * a query against the database and returns a result set for successful SELECT queries, or TRUE + * for other successful DML queries such as INSERT, UPDATE, DELETE, etc. + * + * @param PgSql\Connection $connection The pg connection resource. + * @param string $query The SQL query to be executed. + * @param int $result_mode The optional mode for storing result set. + * @return mixed Returns a pg_result object for successful SELECT queries, TRUE for other + * successful queries, or FALSE on failure. + */ +function wpsqli_query(&$connection, $query, $result_mode = 0) { // Check if a connection to Postgres database is established - if (!$GLOBALS['pg4wp_conn']) { + if (!$connection) { // Store SQL query for later execution when connection is available $GLOBALS['pg4wp_pre_sql'][] = $sql; return true; } // Store the initial SQL query - $initial = $sql; + $initial = $query; // Rewrite the SQL query for compatibility with Postgres - $sql = pg4wp_rewrite($sql); + $sql = pg4wp_rewrite($query); // Execute the SQL query and store the result if (PG4WP_DEBUG) { - $GLOBALS['pg4wp_result'] = pg_query($GLOBALS['pg4wp_conn'], $sql); + $result = pg_query($connection, $sql); } else { - $GLOBALS['pg4wp_result'] = @pg_query($GLOBALS['pg4wp_conn'], $sql); + $result = @pg_query($connection, $sql); } // Handle errors and logging - if ((PG4WP_DEBUG || PG4WP_LOG_ERRORS) && $GLOBALS['pg4wp_result'] === false && $err = pg_last_error($GLOBALS['pg4wp_conn'])) { + if ((PG4WP_DEBUG || PG4WP_LOG_ERRORS) && $result === false && $err = pg_last_error($connection)) { $ignore = false; // Ignore errors if WordPress is in the installation process if (defined('WP_INSTALLING') && WP_INSTALLING) { @@ -228,27 +512,271 @@ function wpsql_query($sql) } } - // Return the query result - return $GLOBALS['pg4wp_result']; + $GLOBALS['pg4wp_conn'] = $connection; + $GLOBALS['pg4wp_result'] = $result; + + return $result; } /** - * Fetches a result row as an associative and numeric array from a Postgres query result. + * Executes one or multiple queries which are concatenated by a semicolon. + * + * This function is a wrapper for the pg_multi_query function. It allows execution of + * multiple SQL statements sent to the PostgreSQL server in a single call. This can be useful to + * perform a batch of SQL operations such as an atomic transaction that should either complete + * entirely or not at all. After calling this function, the results of the queries can be + * processed using pg_store_result() and pg_next_result(). It is important to ensure + * that any user input included in the queries is properly sanitized to avoid SQL injection. + * + * @param PgSql\Connection $connection The pg connection resource. + * @param string $query The queries to execute, concatenated by semicolons. + * @return bool Returns TRUE on success or FALSE on the first error that occurred. + * If the first query succeeds, the function will return TRUE even if + * a subsequent query fails. + */ +function wpsqli_multi_query(&$connection, $query) +{ + // Store the initial SQL query + $initial = $query; + // Rewrite the SQL query for compatibility with Postgres + $sql = pg4wp_rewrite($query); + throw new \Exception("PG4WP: Not Yet Implemented"); + // mysqli_multi_query => No direct equivalent. Multiple queries must be executed separately in PostgreSQL. +} + +/** + * Prepares an SQL statement for execution. + * + * This function is a wrapper for the pg_prepare function. It prepares the SQL statement + * and returns a statement object used for further operations on the statement. The statement + * preparation is used to efficiently execute repeated queries with high efficiency and to avoid + * SQL injection vulnerabilities by separating the query structure from its data. It is especially + * useful when the same statement is executed multiple times with different parameters. + * + * @param PgSql\Connection $connection The pg connection resource. + * @param string $query The SQL query to prepare. + * @return class|false Returns a statement object on success or FALSE on failure. + */ +function wpsqli_prepare(&$connection, $query) +{ + // Store the initial SQL query + $initial = $query; + // Rewrite the SQL query for compatibility with Postgres + $sql = pg4wp_rewrite($query); + throw new \Exception("PG4WP: Not Yet Implemented"); + // mysqli_prepare => pg_prepare (resource $connection, string $stmtname, string $query): resource + // pg_prepare($connection, "my_query", "SELECT * FROM my_table WHERE id = $1"); +} + +/** + * Executes a prepared Query. + * + * This function is a wrapper for the pg_stmt_execute function. It is used to execute a statement + * that was previously prepared using the pg_prepare function. The execution will take place with + * the current bound parameters in the statement object. This is commonly used in database operations + * to execute the same statement repeatedly with high efficiency and to mitigate the risk of SQL injection + * by separating SQL logic from the data being input. + * + * @param pg_stmt $stmt The pg_stmt statement object. + * @return bool Returns TRUE on success or FALSE on failure. + */ +function wpsqli_stmt_execute($stmt) +{ + // Store the initial SQL query + $initial = $stmt; + // Rewrite the SQL query for compatibility with Postgres + $sql = pg4wp_rewrite($stmt); + throw new \Exception("PG4WP: Not Yet Implemented"); + // mysqli_stmt_execute => pg_execute (resource $connection, string $stmtname, array $params): resource + // Executes a previously prepared statement. + // pg_execute($connection, "my_query", array("my_id")); +} + +/** + * Binds variables to a prepared statement as parameters. + * + * This function is a wrapper for the pg_stmt_bind_param function. It binds variables to the + * placeholders of a prepared statement, which is represented by the `$stmt` parameter. The `$types` + * parameter is a string that contains one character for each variable in `$vars`, indicating the type + * of the variable. The supported types are 'i' for integer, 'd' for double, 's' for string, and 'b' for + * blob. By using this function, the values of the variables are bound to the statement as it is executed, + * which can be used to safely execute the statement with user-supplied input. + * + * @param pg_stmt $stmt The prepared statement to which the variables are bound. + * @param string $types A string that contains a type specification char for each variable in `$vars`. + * @param mixed ...$vars The variables to bind to the prepared statement. + * @return bool Returns TRUE on success or FALSE on failure. + */ +function wpsqli_stmt_bind_param($stmt, $types, ...$vars) +{ + // Store the initial SQL query + $initial = $stmt; + // Rewrite the SQL query for compatibility with Postgres + $sql = pg4wp_rewrite($stmt); + throw new \Exception("PG4WP: Not Yet Implemented"); + // The remaining mysqli_stmt_* functions do not have direct equivalents in PostgreSQL. Prepared statements work differently. + // PostgreSQL uses pg_prepare() and pg_execute() for prepared statements. Results are then fetched with pg_fetch_* functions. +} + +/** + * Binds variables to a prepared statement for result storage. + * + * This function is a wrapper for the pg_stmt_bind_result function. It binds variables to the + * prepared statement `$stmt` to store the result of the statement once it is executed. The bound + * variables are passed by reference and will be set to the values of the corresponding columns in + * the result set. This function is typically used in conjunction with pg_stmt_fetch(), which + * will populate the variables with data from the next row in the result set each time it is called. + * + * @param pg_stmt $stmt The statement object that executed a query with a result set. + * @param mixed &...$vars The variables to which the result set columns will be bound. + * @return bool Returns TRUE on success or FALSE on failure. + */ +function wpsqli_stmt_bind_result($stmt, &...$vars) +{ + // Store the initial SQL query + $initial = $stmt; + // Rewrite the SQL query for compatibility with Postgres + $sql = pg4wp_rewrite($stmt); + throw new \Exception("PG4WP: Not Yet Implemented"); + // The remaining mysqli_stmt_* functions do not have direct equivalents in PostgreSQL. Prepared statements work differently. + // PostgreSQL uses pg_prepare() and pg_execute() for prepared statements. Results are then fetched with pg_fetch_* functions. +} + +/** + * Fetches results from a prepared statement into the bound variables. + * + * This function is a wrapper for the pg_stmt_fetch function. It is used to fetch the data + * from the executed prepared statement into the variables that were bound using pg_stmt_bind_result(). + * The function will return TRUE for every row fetched successfully. When there are no more rows to fetch, + * it will return NULL, and if there is an error it will return FALSE. + * + * @param pg_stmt $stmt The prepared statement object from which results are to be fetched. + * @return bool|null Returns TRUE on success, NULL if there are no more rows to fetch, or FALSE on error. + */ +function wpsqli_stmt_fetch($stmt) +{ + // Store the initial SQL query + $initial = $query; + // Rewrite the SQL query for compatibility with Postgres + $sql = pg4wp_rewrite($query); + throw new \Exception("PG4WP: Not Yet Implemented"); + // The remaining mysqli_stmt_* functions do not have direct equivalents in PostgreSQL. Prepared statements work differently. + // PostgreSQL uses pg_prepare() and pg_execute() for prepared statements. Results are then fetched with pg_fetch_* functions. +} + +/** + * Initializes a statement and returns an object for use with pg_stmt_prepare. + * + * This function is a wrapper for the pg_stmt_init function. It creates and returns a new statement + * object associated with the specified database connection. This statement object can then be used + * to prepare a SQL statement for execution. It's particularly useful when you need to execute a + * prepared statement multiple times with different parameters, providing benefits such as improved + * query performance and protection against SQL injection attacks. + * + * @param PgSql\Connection $connection The pg connection resource. + * @return class A new statement object or FALSE on failure. + */ +function wpsqli_stmt_init(&$connection) +{ + throw new \Exception("PG4WP: Not Yet Implemented"); + // mysqli_stmt_init => No direct equivalent in PostgreSQL. + // In PostgreSQL, prepared statements are created directly with pg_prepare, not initialized separately. +} + + +/** + * Closes a prepared statement. + * + * This function is a wrapper for the pg_stmt_close function. It deallocates the statement + * and cleans up the memory associated with the statement object. This is an important step in + * resource management, as it frees up server resources and allows other statements to be executed. + * It should always be called after all the results have been fetched and the statement is no longer needed. + * + * @param pg_stmt $stmt The prepared statement object to be closed. + * @return bool Returns TRUE on success or FALSE on failure. + */ +function wpsqli_stmt_close($stmt) +{ + // Store the initial SQL query + $initial = $stmt; + // Rewrite the SQL query for compatibility with Postgres + $sql = pg4wp_rewrite($stmt); + throw new \Exception("PG4WP: Not Yet Implemented"); + // The remaining mysqli_stmt_* functions do not have direct equivalents in PostgreSQL. Prepared statements work differently. + // PostgreSQL uses pg_prepare() and pg_execute() for prepared statements. Results are then fetched with pg_fetch_* functions. +} + +/** + * Returns a string description for the last statement error. + * + * This function is a wrapper for the pg_stmt_error function. It returns a string describing + * the error for the most recent statement operation that generated an error. This is useful for + * debugging and error handling in applications that use prepared statements to interact with the + * PostgreSQL database. It allows developers to output or log a descriptive error message when a PostgreSQL + * operation on a prepared statement fails. + * + * @param pg_stmt $stmt The pg_stmt statement object. + * @return string A string that describes the error. An empty string if no error occurred. + */ +function wpsqli_stmt_error($stmt) +{ + // Store the initial SQL query + $initial = $stmt; + // Rewrite the SQL query for compatibility with Postgres + $sql = pg4wp_rewrite($stmt); + throw new \Exception("PG4WP: Not Yet Implemented"); + // The remaining mysqli_stmt_* functions do not have direct equivalents in PostgreSQL. Prepared statements work differently. + // PostgreSQL uses pg_prepare() and pg_execute() for prepared statements. Results are then fetched with pg_fetch_* functions. +} + +/** + * Returns the error code for the most recent statement call. + * + * This function is a wrapper for the pg_stmt_errno function. It returns the error code from + * the last operation performed on the specified statement. This is useful for error handling, + * particularly in database operations where you need to react differently based on the specific + * error that occurred. It can be used in conjunction with pg_stmt_error() to retrieve both + * the error code and the error message for more detailed debugging and logging. + * + * @param pg_stmt $stmt The pg_stmt statement object. + * @return int An error code value for the last error that occurred, or zero if no error occurred. + */ +function wpsqli_stmt_errno($stmt) +{ + // Store the initial SQL query + $initial = $stmt; + // Rewrite the SQL query for compatibility with Postgres + $sql = pg4wp_rewrite($stmt); + throw new \Exception("PG4WP: Not Yet Implemented"); + // The remaining mysqli_stmt_* functions do not have direct equivalents in PostgreSQL. Prepared statements work differently. + // PostgreSQL uses pg_prepare() and pg_execute() for prepared statements. Results are then fetched with pg_fetch_* functions. +} + +/** +* Result Handling +*/ + +/** + * Fetches a result row as an associative, a numeric array, or both. + * + * This function is a wrapper for the pg_fetch_array function, which is used to fetch a single + * row of data from the result set obtained from executing a SELECT query. The data can be fetched + * as an associative array, a numeric array, or both, depending on the `mode` specified. By default, + * it fetches as both associative and numeric (PGSQL_BOTH). Using PGSQL_ASSOC will fetch as an + * associative array, and PGSQL_NUM will fetch as a numeric array. It returns NULL when there are + * no more rows to fetch. * * This function calls the pg_fetch_array() function to fetch the next row from a Postgres result set * and trims any leading or trailing whitespace from each of the values. * - * Differences from MySQL Equivalent: - * 1. It uses pg_fetch_array() instead of mysql_fetch_array(). - * 2. It trims the values of the resulting array, which is specific to this implementation. - * * @param resource $result The result resource returned by a Postgres query. + * @param int $mode The type of array that should be produced from the current row data. * @return array|bool An array of the next row in the result set, or false if there are no more rows. */ -function wpsql_fetch_array($result) +function wpsqli_fetch_array($result, $mode = PGSQL_BOTH) { // Fetch the next row as an array - $res = pg_fetch_array($result); + $res = pg_fetch_array($result, null, $mode); // Check if the result is an array and trim its values if (is_array($res)) { @@ -261,10 +789,260 @@ function wpsql_fetch_array($result) return $res; } +/** + * Fetches a result row as an object. + * + * This function is a wrapper for the pg_fetch_object function. It retrieves the current row + * of a result set as an object where the attributes correspond to the fetched row's column names. + * This function can instantiate an object of a specified class, and pass parameters to its constructor, + * allowing for custom objects based on the rows of the result set. If no class is specified, it defaults + * to a stdClass object. If the class does not exist or the specified class's constructor requires more + * arguments than are given, an exception is thrown. + * + * @param \PgSql\Result $result The result set returned by pg_query, pg_store_result + * or pg_use_result. + * @param string $class The name of the class to instantiate, set the properties of which + * correspond to the fetched row's column names. + * @param array $constructor_args An optional array of parameters to pass to the constructor + * for the class name defined by the class parameter. + * @return object|false An instance of the specified class with property names that correspond + * to the column names returned in the result set, or FALSE on failure. + */ +function wpsqli_fetch_object($result, $class = "stdClass", $constructor_args = []) +{ + return pg_fetch_object($result, null, $class, $constructor_args); +} + +/** + * Fetches one row of data from the result set and returns it as an enumerated array. + * Each call to this function will retrieve the next row in the result set, so it's typically + * used in a loop to process multiple rows. + * + * This function is particularly useful when you need to retrieve a row as a simple array + * where each column is accessed by an integer index starting at 0. It does not include + * column names as keys, which can be marginally faster and less memory intensive than + * associative arrays if the column names are not required. + * + * @param \PgSql\Result $result The result set returned by a query against the database. + * + * @return array|null Returns an enumerated array of strings representing the fetched row, + * or NULL if there are no more rows in the result set. + */ +function wpsqli_fetch_row($result): ?array +{ + return pg_fetch_row($result); +} + +/** + * Adjusts the result pointer to an arbitrary row in the result set represented by the + * $result object. This function can be used in conjunction with pg_fetch_row(), + * pg_fetch_assoc(), pg_fetch_array(), or pg_fetch_object() to navigate between + * rows in result sets, especially when using buffered result sets. + * + * This is an important function for situations where you need to access a specific row + * directly without iterating over all preceding rows, which can be useful for pagination + * or when looking up specific rows by row number. + * + * @param \PgSql\Result $result The result set returned by a query against the database. + * @param int $row_number The desired row number to seek to. Row numbers are zero-indexed. + * + * @return bool Returns TRUE on success or FALSE on failure. If the row number is out of range, + * it returns FALSE. + */ +function wpsqli_data_seek($result, int $row_number): bool +{ + return pg_result_seek($result, $row_number); +} + +/** + * Returns the next field in the result set. + * + * This function is a wrapper for the pg_fetch_field function, which retrieves information about + * the next field in the result set represented by the `$result` parameter. It can be used in a loop + * to obtain information about each field in the result set, such as name, table, max length, flags, + * and type. This is useful for dynamically generating table structures or processing query results + * when the structure of the result set is not known in advance or changes. + * + * @param \PgSql\Result $result The result set returned by pg_query, pg_store_result + * or pg_use_result. + * @return object An object which contains field definition information or FALSE if no field information + * is available. + */ +function wpsqli_fetch_field($result) +{ + throw new \Exception("PG4WP: Not Yet Implemented"); + // mysqli_fetch_field => pg_field_table (resource $result, int $field_number, bool $oid_only = false): mixed + // Returns the name or oid of the table of the field. There's no direct function to mimic mysqli_fetch_field completely. + //pg_field_table($result, $field_number); +} + +/** + * Gets the number of fields in a result set. + * + * This function is a wrapper for the pg_num_fields function. It returns the number + * of fields (columns) in a result set. This is particularly useful when you need to + * dynamically process a query result without knowing the schema of the returned data, + * as it allows the script to iterate over all fields in each row of the result set. + * + * @param \PgSql\Result $result The result set returned by pg_query, pg_store_result + * or pg_use_result. + * @return int The number of fields in the specified result set. + */ +function wpsqli_num_fields($result) +{ + // mysqli_num_fields => pg_num_fields (resource $result): int + // Returns the number of fields (columns) in a result. + return pg_num_fields($result); +} + +/** + * Returns the number of columns for the most recent query on the connection. + * + * This function is a wrapper for the pg_field_count function. It retrieves the number of + * columns obtained from the most recent query executed on the given database connection. This + * can be particularly useful when you need to know how many columns will be returned by a + * SELECT statement before fetching data, which can help in dynamically processing result sets. + * + * @param PgSql\Connection $connection The pg connection resource. + * @return int An integer representing the number of fields in the result set. + */ +function wpsqli_field_count(&$connection) +{ + // mysqli_field_count => pg_num_fields (resource $result): int + // Use pg_num_fields to get the number of fields (columns) in a result. + return pg_num_fields($result); +} + +/** + * Transfers a result set from the last query. + * + * This function is a wrapper for the pg_store_result function. It is used to transfer the + * result set from the last query executed on the given connection, which used the pg_STORE_RESULT + * flag. This function must be called after executing a query that returns a result set (like SELECT). + * It allows the complete result set to be transferred to the client and then utilized via the + * pg_fetch_* functions. It's particularly useful when the result set is expected to be accessed + * multiple times. + * + * @param PgSql\Connection $connection The pg connection resource. + * @return \PgSql\Result|false A buffered result object or FALSE if an error occurred. + */ +function wpsqli_store_result(&$connection) +{ + throw new \Exception("PG4WP: Not Yet Implemented"); + // mysqli_store_result => Not needed in PostgreSQL. + // PostgreSQL's pg_query automatically stores the results without a separate call. + //return true; +} + +/** + * Initiates the retrieval of a result set from the last query executed using the pg_USE_RESULT mode. + * + * This function is a wrapper for the pg_use_result function. It is used to initiate the retrieval + * of a result set from the last query executed on the given connection, without storing the entire result + * set in the buffer. This is particularly useful for handling large result sets that could potentially + * exceed the available PHP memory. The data is fetched row-by-row, reducing the immediate memory footprint. + * However, it requires the connection to remain open, and no other operations can be performed on the + * connection until the result set is fully processed. + * + * @param PgSql\Connection $connection The pg connection resource. + * @return \PgSql\Result|false An unbuffered result object or FALSE if an error occurred. + */ +function wpsqli_use_result(&$connection) +{ + throw new \Exception("PG4WP: Not Yet Implemented"); + // mysqli_use_result => Not needed in PostgreSQL. + // PostgreSQL does not differentiate between unbuffered and buffered queries like MySQL does. +} + +/** + * Frees the memory associated with a result. + * + * This function is a wrapper for the pg_free_result function. It's used to free the memory + * allocated for a result set obtained from a query. When the result data is not needed anymore, + * it's a good practice to free the associated resources, especially when dealing with large + * datasets that can consume significant amounts of memory. It is an important aspect of resource + * management and helps to keep the application's memory footprint minimal. + * + * @param \PgSql\Result $result The result set returned by pg_query, pg_store_result + * or pg_use_result. + * @return void This function doesn't return any value. + */ +function wpsqli_free_result($result) +{ + // mysqli_free_result => pg_free_result (resource $result): bool + // Frees memory associated with a result. + return pg_free_result($result); +} + +/** + * Checks if there are any more result sets from a multi query. + * + * mysqli_more_results => No direct equivalent in PostgreSQL. + * PostgreSQL does not support multiple results like MySQL's multi_query function. + * It returns TRUE if one or more result sets are available from the previous calls to + * pg_multi_query(), otherwise FALSE. + * + * @param PgSql\Connection $connection The pg connection resource. + * @return bool Returns TRUE if there are more result sets from previous multi queries and + * FALSE otherwise. + */ +function wpsqli_more_results(&$connection) +{ + // mysqli_more_results => No direct equivalent in PostgreSQL. + // PostgreSQL does not have a built-in function to check for more results from a batch of queries. + return false; +} + +/** + * Moves the internal result pointer to the next result set returned from a multi query. + * + * mysqli_next_result => No direct equivalent in PostgreSQL. + * PostgreSQL does not support multiple results like MySQL's multi_query function. + * FALSE if there are no more result sets, or FALSE with an error if there is a problem moving the result pointer. + * + * @param PgSql\Connection $connection The pg connection resource. + * @return bool Returns TRUE on success or FALSE on failure (no more results or an error occurred). + */ +function wpsqli_next_result(&$connection) +{ + // mysqli_next_result => No direct equivalent in PostgreSQL. + // PostgreSQL does not support multiple results like MySQL's multi_query function. + return false; +} + +/** +* Utility Functions +*/ + +function wpsqli_is_resource($object) +{ + return $object !== false && $object !== null; +} + +/** + * Gets the number of affected rows in the previous PostgreSQL operation. + * + * This function is a wrapper for the pg_affected_rows function, which is used to determine + * the number of rows affected by the last INSERT, UPDATE, REPLACE or DELETE query executed on + * the given connection. It is an important function for understanding the impact of such queries, + * allowing the developer to verify that the expected number of rows were altered. It returns an + * integer indicating the number of rows affected or -1 if the last query failed. + * + * @param PgSql\Connection $connection The pg connection resource. + * @return int The number of affected rows in the previous operation, or -1 if the last operation failed. + */ +function wpsqli_affected_rows(&$connection) +{ + $result = $GLOBALS['pg4wp_result']; + // mysqli_affected_rows => pg_affected_rows (resource $result): int + // Returns the number of rows affected by INSERT, UPDATE, or DELETE query. + return pg_affected_rows($result); +} + /** * Fetches the ID generated for an AUTO_INCREMENT column by the previous INSERT query. * - * @param resource|null $lnk A PostgreSQL connection resource. Default is `null`. + * @param resource|null $connection A PostgreSQL connection resource. Default is `null`. * * @return mixed The ID generated for an AUTO_INCREMENT column by the previous INSERT query on success; `false` on failure. * @@ -272,7 +1050,7 @@ function wpsql_fetch_array($result) * 1. In PostgreSQL, this function uses CURRVAL() on the appropriate sequence to get the last inserted ID. * 2. In MySQL, last inserted ID is generally fetched using mysql_insert_id() or mysqli_insert_id(). */ -function wpsql_insert_id($lnk = null) +function wpsqli_insert_id(&$connection = null) { global $wpdb; $data = null; @@ -285,7 +1063,7 @@ function wpsql_insert_id($lnk = null) if ($table == $wpdb->term_relationships) { // PostgreSQL: Using CURRVAL() to get the current value of the sequence. $sql = "SELECT CURRVAL('$seq')"; - $res = pg_query($sql); + $res = pg_query($connection, $sql); if (false !== $res) { $data = pg_fetch_result($res, 0, 0); } @@ -303,12 +1081,12 @@ function wpsql_insert_id($lnk = null) } else { // PostgreSQL: Using CURRVAL() to get the current value of the sequence. $sql = "SELECT CURRVAL('$seq')"; - $res = pg_query($GLOBALS['pg4wp_conn'], $sql); + $res = pg_query($connection, $sql); if (false !== $res) { $data = pg_fetch_result($res, 0, 0); } elseif (PG4WP_DEBUG || PG4WP_LOG) { - $log = '[' . microtime(true) . "] wpsql_insert_id() was called with '$table' and '$ins_field'" . - " and returned the error:\n" . pg_last_error($GLOBALS['pg4wp_conn']) . + $log = '[' . microtime(true) . "] wpsqli_insert_id() was called with '$table' and '$ins_field'" . + " and returned the error:\n" . pg_last_error($connection) . "\nFor the query:\n" . $sql . "\nThe latest INSERT query was :\n'$lastq'\n"; error_log($log, 3, PG4WP_LOG . 'pg4wp_errors.log'); @@ -319,33 +1097,70 @@ function wpsql_insert_id($lnk = null) error_log('[' . microtime(true) . "] Getting inserted ID for '$table' ('$ins_field') : $sql => $data\n", 3, PG4WP_LOG . 'pg4wp_insertid.log'); } + $GLOBALS['pg4wp_conn'] = $connection; + return $data; } + + /** - * Fetch a specific result row's field value from a PostgreSQL result resource. - * Quick fix for wpsql_result() error and missing wpsql_errno() function - * Source : http://vitoriodelage.wordpress.com/2014/06/06/add-missing-wpsql_errno-in-pg4wp-plugin/ + * Sets the default character set to be used when sending data from and to the database server. * - * @param resource $result The Postgres query result resource. - * @param int $i The row number from which to get the value (0-based). - * @param string|null $fieldname Optional. The field name to fetch. + * This function is a wrapper for the pg_set_charset function. The pg_set_charset function + * is used to set the character set to be used when sending data from and to the database server. + * This is particularly important to ensure that data is properly encoded and decoded when stored + * and retrieved from the database, avoiding character encoding issues. * - * @return mixed Field value or false if no such row exists. - * - * Note: - * 1. This function uses `pg_fetch_result` to get a single field's value from a row. - * 2. In MySQL, you could use `mysql_result` to accomplish something similar. + * @param PgSql\Connection $connection The pg connection resource. + * @param string $charset The desired character set. + * @return bool Returns TRUE on success or FALSE on failure. */ -function wpsql_result($result, $i, $fieldname = null) +function wpsqli_set_charset(&$connection, $charset) { - if (is_resource($result)) { - if ($fieldname) { - return pg_fetch_result($result, $i, $fieldname); - } else { - return pg_fetch_result($result, $i); - } - } + // mysqli_set_charset => pg_set_client_encoding (resource $connection, string $encoding): int + // Sets the client encoding. + return pg_set_client_encoding($connection, "UTF8"); +} + +/** + * Escapes special characters in a string for use in an SQL statement. + * + * This function serves as a wrapper for the pg_real_escape_string function, which is + * utilized to escape potentially dangerous special characters within a string. This is a + * critical security measure to prevent SQL injection vulnerabilities by ensuring that user + * input can be safely used in an SQL query. The function takes a string to be escaped and + * the pg connection resource, and returns the escaped string which is safe to be included + * in SQL statements. + * + * @param PgSql\Connection $connection The pg connection resource. + * @param string $string The string to be escaped. + * @return string Returns the escaped string. + */ +function wpsqli_real_escape_string(&$connection, $string) +{ + // mysqli_real_escape_string => pg_escape_string (resource $connection, string $data): string + // Escapes a string for safe use in database queries. + return pg_escape_string($connection, $string); +} + +/** + * Retrieves the last error description for the most recent pg function call + * that can succeed or fail. + * + * This function is a wrapper for the pg_last_error function, which returns a string + * describing the error from the last PostgreSQL operation associated with the provided + * connection resource. It's an essential function for debugging and error handling + * in PostgreSQL-related operations. When a pg function fails, wpsqli_error can be + * used to fetch the corresponding error message to understand what went wrong. + * + * @param PgSql\Connection $connection The pg connection resource. + * @return string Returns a string with the error message for the most recent function call + * if it has failed, or an empty string if no error has occurred. + */ +function wpsqli_error(&$connection) +{ + return pg_last_error($connection); } /** @@ -361,7 +1176,7 @@ function wpsql_result($result, $i, $fieldname = null) * 3. `pg_result_error_field` is used to get the SQLSTATE error code. * 4. In MySQL, you could use `mysqli_errno` to get the error code directly. */ -function wpsql_errno($connection) +function wpsqli_errno(&$connection) { $result = pg_get_result($connection); if ($result === false) { @@ -372,190 +1187,88 @@ function wpsql_errno($connection) return pg_result_error_field($result_status, PGSQL_DIAG_SQLSTATE); } - /** - * Checks if a connection to a PostgreSQL database is alive. + * Enables or disables internal report functions. * - * @param resource $conn The PostgreSQL connection resource. - * @return bool Returns true if the connection is alive, false otherwise. + * This function is a wrapper for the pg_report function, which is used to set the + * reporting mode of pg errors. This is useful for defining whether errors should be + * reported as exceptions, warnings, or silent (no report). It's important for configuring + * the error reporting behavior of the pg extension to suit the needs of your application, + * particularly in a development environment where more verbose error reporting is beneficial. + * + * @param int $flags A bit-mask constructed from the pg_REPORT_* constants. + * @return bool Returns TRUE on success or FALSE on failure. */ -function wpsql_ping($conn) +function wpsqli_report($flags) { - return pg_ping($conn); + // mysqli_report => No direct equivalent in PostgreSQL. + // MySQL's mysqli_report function is used to set what MySQLi should report, which doesn't have a direct equivalent in PostgreSQL's PHP functions. + return true; } /** - * Gets the number of rows in a PostgreSQL result. + * Retrieves information about the most recently executed query. * - * @param resource $result The PostgreSQL result resource. - * @return int Returns the number of rows. - */ -function wpsql_num_rows($result) -{ - return pg_num_rows($result); -} - -/** - * Alias for wpsql_num_rows. Gets the number of rows in a PostgreSQL result. + * This function is a wrapper for the pg_info function. It provides a string containing + * information about the most recently executed query on the given connection resource. This + * can include information such as the number of rows affected by an INSERT, UPDATE, REPLACE, + * or DELETE query, as well as the number of rows matched and changed. It is valuable for + * obtaining detailed insights into the execution of database operations. * - * @param resource $result The PostgreSQL result resource. - * @return int Returns the number of rows. + * @param PgSql\Connection $connection The pg connection resource. + * @return string|null A string representing information about the last query executed, + * or NULL if no information is available. */ -function wpsql_numrows($result) +function wpsqli_info(&$connection) { - return pg_num_rows($result); -} - -/** - * Gets the number of fields in a PostgreSQL result. - * - * @param resource $result The PostgreSQL result resource. - * @return int Returns the number of fields. - */ -function wpsql_num_fields($result) -{ - return pg_num_fields($result); -} - -/** - * Mock function to mimic MySQL's fetch_field function. - * - * @param resource $result The PostgreSQL result resource. - * @return string Returns 'tablename' as a placeholder. - */ -function wpsql_fetch_field($result) -{ - return 'tablename'; -} - -/** - * Fetches one row from a PostgreSQL result as an object. - * - * @param resource $result The PostgreSQL result resource. - * @return object Returns an object containing the data. - */ -function wpsql_fetch_object($result) -{ - return pg_fetch_object($result); -} - -/** - * Frees a PostgreSQL result resource. - * - * @param resource $result The PostgreSQL result resource. - * @return bool Returns true on success, false otherwise. - */ -function wpsql_free_result($result) -{ - if ($result === null) { - return true; - } - - return pg_free_result($result); -} - -/** - * Gets the number of affected rows by the last PostgreSQL query. - * - * @return int Returns the number of affected rows. - */ -function wpsql_affected_rows() -{ - if($GLOBALS['pg4wp_result'] === false) { - return 0; - } - - return pg_affected_rows($GLOBALS['pg4wp_result']); -} - -/** - * Fetches one row from a PostgreSQL result as an enumerated array. - * - * @param resource $result The PostgreSQL result resource. - * @return array Returns an array containing the row data. - */ -function wpsql_fetch_row($result) -{ - return pg_fetch_row($result); -} - -/** - * Sets the row offset for a PostgreSQL result resource. - * - * @param resource $result The PostgreSQL result resource. - * @param int $offset The row offset. - * @return bool Returns true on success, false otherwise. - */ -function wpsql_data_seek($result, $offset) -{ - return pg_result_seek($result, $offset); -} - -/** - * Gets the last error message from a PostgreSQL connection. - * - * @return string Returns the error message. - */ -function wpsql_error() -{ - if($GLOBALS['pg4wp_conn']) { - return pg_last_error($GLOBALS['pg4wp_conn']); - } - - return ''; -} - -/** - * Fetches one row from a PostgreSQL result as an associative array. - * - * @param resource $result The PostgreSQL result resource. - * @return array Returns an associative array containing the row data. - */ -function wpsql_fetch_assoc($result) -{ - return pg_fetch_assoc($result); -} - -/** - * Escapes a string for use in a PostgreSQL query. - * - * @param string $s The string to escape. - * @return string Returns the escaped string. - */ -function wpsql_escape_string($s) -{ - return pg_escape_string($GLOBALS['pg4wp_conn'], $s); -} - -/** - * Escapes a string for use in a PostgreSQL query with a specified connection. - * - * @param string $s The string to escape. - * @param resource $c The PostgreSQL connection resource. - * @return string Returns the escaped string. - */ -function wpsql_real_escape_string($s, $c = null) -{ - return pg_escape_string($c, $s); -} - -/** - * Mock function to mimic MySQL's get_server_info function. - * - * @return string Returns '8.0.35' as a placeholder. - */ -function wpsql_get_server_info() -{ - return '8.0.35'; // Just want to fool wordpress ... + throw new \Exception("PG4WP: Not Yet Implemented"); + // mysqli_info => No direct equivalent in PostgreSQL. + // This function retrieves information about the most recently executed query, which is not provided by PostgreSQL's PHP functions. } /** - * Mock function to mimic MySQL's get_client_info function. + * Polls connections for results. * - * @return string Returns '8.0.35' as a placeholder. + * This function is a wrapper for the pg_poll function. It can be used to poll multiple + * connections to check if one or more of the connections have results available for client-side + * processing. It is useful when you have multiple asynchronous queries running and need to handle + * them as soon as their results become available. The function takes variable references for read, + * error, and reject arrays, and modifies them to indicate which connections have results, which + * have errors, and which were rejected respectively. + * + * @param array &$read Array of connections to check for outstanding results that can be read. + * @param array &$error Array of connections on which an error occurred. + * @param array &$reject Array of connections rejected because no asynchronous query + * has been run on them. + * @param int $sec Number of seconds to wait, must be non-negative. + * @param int $usec Number of microseconds to wait, must be non-negative. + * @return int|false Number of ready connections upon success, FALSE otherwise. */ -function wpsql_get_client_info() +function wpsqli_poll(&...$args) { - return '8.0.35'; // Just want to fool wordpress ... + throw new \Exception("PG4WP: Not Yet Implemented"); + // mysqli_poll => No direct equivalent in PostgreSQL. + // Polling for result availability is not a concept that is directly exposed in PostgreSQL's PHP functions. + // Asynchronous query handling in PHP with PostgreSQL typically involves using separate processes or coroutines. } + +/** + * Gets the result from asynchronous PostgreSQL query. + * + * This function is a wrapper for the pg_reap_async_query function. It is used after initiating + * a query with pg_query() on a connection with the pg_ASYNC flag set. It retrieves the result + * from the query once it is complete, which can be used with pg_poll() to manage multiple + * asynchronous queries. It returns a pg_result object for successful SELECT queries, or TRUE for + * other DML queries (INSERT, UPDATE, DELETE, etc.) if the operation was successful, or FALSE on failure. + * + * @param PgSql\Connection $connection The pg connection resource. + * @return \PgSql\Result|bool A pg_result object for successful SELECT queries, TRUE for other + * successful DML queries, or FALSE on failure. + */ +function wpsqli_reap_async_query(&$connection) +{ + throw new \Exception("PG4WP: Not Yet Implemented"); + // mysqli_reap_async_query => No direct equivalent in PostgreSQL. + // Asynchronous queries can be executed in PostgreSQL using pg_send_query and retrieved using pg_get_result. +} \ No newline at end of file diff --git a/readme.md b/readme.md index 0f99a17..0e79ede 100644 --- a/readme.md +++ b/readme.md @@ -1,23 +1,26 @@ ## PostgreSQL for WordPress (PG4WP) -### Contributors -Code originally by Hawk__ (http://www.hawkix.net/) -Modifications by @kevinoid and @mattbucci - -PostgreSQL for WordPress is a special 'plugin' enabling WordPress to be used with a PostgreSQL database. - ### Description PostgreSQL for WordPress (PG4WP) gives you the possibility to install and use WordPress with a PostgreSQL database as a backend. -It works by replacing calls to MySQL specific functions with generic calls that maps them to another database functions and rewriting SQL queries on the fly when needed. -If you want to use this plugin, you should be aware of the following : -- WordPress with PG4WP is expected to be slower than the original WordPress with MySQL because PG4WP does much SQL rewriting for any page view -- Some WordPress plugins should work 'out of the box' but many plugins won't because they would need specific code in PG4WP +#### Use Cases + +- Run Wordpress on your Existing Postgres Cluster +- Run Wordpress with Georeplication with a Multi-Active Postgres instalation such as [EDB Postgres Distributed](https://www.enterprisedb.com/products/edb-postgres-distributed), or [CockroachDB](https://www.cockroachlabs.com/serverless/) for a [highly available](https://www.cockroachlabs.com/blog/brief-history-high-availability/) and resiliant Wordpress Infrastructure + +### Design + +PostgreSQL for Wordpress works by intercepting calls to the mysqli_ driver in wordpress's wpdb class. +it replaces calls to mysqli_ with wpsqli_ which then are implemented by the driver files found in this plugin. + +![PG4WP Design](docs/images/pg4wp_design.png) ### Supported Wordpress Versions -This plugin has been tested against Wordpress 6.3.2 +This plugin has been tested against +- Wordpress 6.4.1 (v3 branch) +- Wordpress 6.3.2 (v2 branch) ### Supported PHP versions @@ -41,6 +44,7 @@ PG4WP 2.0 will have a mechanism to add plugin support. | Theme | Version | Working | | ----------- | ----------- | --------- | +| Twenty Twenty-Four | 1.0 | Confirmed | | Twenty Twenty-Three | 1.2 | Confirmed | | Twenty Twenty-Two | 1.5 | Confirmed | | Twenty Twenty-One | 1.9 | Confirmed | @@ -79,3 +83,7 @@ If you find a failing scenario please add a test for it, A PR which fixes a scen PG4WP is provided "as-is" with no warranty in the hope it can be useful. PG4WP is licensed under the [GNU GPL](http://www.gnu.org/licenses/gpl.html "GNU GPL") v2 or any newer version at your choice. + +### Contributors +Code originally by Hawk__ (http://www.hawkix.net/) +Modifications by @kevinoid and @mattbucci \ No newline at end of file