Add pg4wp plugin and update wordpress.yaml
This commit is contained in:
1 parent
0a51ac0a31
commit
a4d8c0f418
23 files changed
+4304
-6
No files matched your search
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @package PostgreSQL_For_Wordpress
|
||||||
|
* @version $Id$
|
||||||
|
* @author Hawk__, www.hawkix.net
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file does all the initialisation tasks
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This is required by class-wpdb so we must load it first
|
||||||
|
require_once ABSPATH . '/wp-includes/version.php';
|
||||||
|
require_once ABSPATH . '/wp-includes/cache.php';
|
||||||
|
require_once ABSPATH . '/wp-includes/l10n.php';
|
||||||
|
|
||||||
|
// Load the driver defined in 'db.php'
|
||||||
|
require_once(PG4WP_ROOT . '/driver_' . DB_DRIVER . '.php');
|
||||||
|
|
||||||
|
// This loads up the wpdb class applying appropriate changes to it
|
||||||
|
$replaces = array(
|
||||||
|
'define( ' => '// define( ',
|
||||||
|
'class wpdb' => 'class wpdb2',
|
||||||
|
'new wpdb' => 'new wpdb2',
|
||||||
|
'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',
|
||||||
|
'<?php' => '',
|
||||||
|
'?>' => '',
|
||||||
|
);
|
||||||
|
|
||||||
|
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
|
||||||
|
if (!isset($wpdb) && defined('DB_USER')) {
|
||||||
|
$wpdb = new wpdb2(DB_USER, DB_PASSWORD, DB_NAME, DB_HOST);
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
Plugin Name: PostgreSQL for WordPress (PG4WP)
|
||||||
|
Plugin URI: https://github.com/PostgreSQL-For-Wordpress/postgresql-for-wordpress
|
||||||
|
Description: PG4WP is a special plugin enabling WordPress to use a PostgreSQL database.
|
||||||
|
Version: v3.0.0
|
||||||
|
Author: PostgreSQL-For-Wordpress
|
||||||
|
Author URI: https://github.com/PostgreSQL-For-Wordpress
|
||||||
|
License: GPLv2 or newer.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Ensure we only load this config once
|
||||||
|
if(!defined('PG4WP_ROOT')) {
|
||||||
|
|
||||||
|
// You can choose the driver to load here
|
||||||
|
if (!defined('DB_DRIVER')) {
|
||||||
|
define('DB_DRIVER', 'pgsql');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set this to 'true' and check that `pg4wp` is writable if you want debug logs to be written
|
||||||
|
if (!defined('PG4WP_DEBUG')) {
|
||||||
|
define('PG4WP_DEBUG', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!defined('PG4WP_LOG_ERRORS')) {
|
||||||
|
// If you just want to log queries that generate errors, leave PG4WP_DEBUG to "false"
|
||||||
|
// and set this to true
|
||||||
|
define('PG4WP_LOG_ERRORS', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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')) {
|
||||||
|
define('PG4WP_ROOT', ABSPATH . 'wp-content/pg4wp');
|
||||||
|
} elseif(file_exists(ABSPATH . 'wp-content/plugins/pg4wp')) {
|
||||||
|
define('PG4WP_ROOT', ABSPATH . 'wp-content/plugins/pg4wp');
|
||||||
|
} elseif(file_exists(ABSPATH . 'pg4wp')) {
|
||||||
|
define('PG4WP_ROOT', ABSPATH . 'pg4wp');
|
||||||
|
} else {
|
||||||
|
die('PG4WP file directory not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logs are put in the pg4wp directory
|
||||||
|
if (!defined('PG4WP_LOG')) {
|
||||||
|
define('PG4WP_LOG', PG4WP_ROOT . '/logs/');
|
||||||
|
}
|
||||||
|
// Check if the logs directory is needed and exists or create it if possible
|
||||||
|
if((PG4WP_DEBUG || PG4WP_LOG_ERRORS) && !file_exists(PG4WP_LOG) && is_writable(dirname(PG4WP_LOG))) {
|
||||||
|
mkdir(PG4WP_LOG);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here happens all the magic
|
||||||
|
require_once(PG4WP_ROOT . '/core.php');
|
||||||
|
} // Protection against multiple loading
|
||||||
@@ -0,0 +1,986 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file implements a mysql reference driver
|
||||||
|
* This driver should do nothing different vs a standard install
|
||||||
|
* This file remaps all wpsqli_* calls to mysqli_* original name
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connection Handling
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes MySQLi and returns a resource for use with mysqli_real_connect().
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_init function, which initializes and returns
|
||||||
|
* a resource for use with mysqli_real_connect(). It is part of the MySQLi extension's
|
||||||
|
* object-oriented interface and is usually employed to prepare for a secure connection
|
||||||
|
* using settings like mysqli_ssl_set() before establishing a connection to a MySQL server.
|
||||||
|
* The function does not require any parameters and will return a mysqli object on success
|
||||||
|
* or FALSE on failure.
|
||||||
|
*
|
||||||
|
* @return mysqli|false Returns an object which can be used with mysqli_real_connect() or
|
||||||
|
* FALSE on failure.
|
||||||
|
*/
|
||||||
|
function wpsqli_init()
|
||||||
|
{
|
||||||
|
return mysqli_init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a connection to a mysql server in a real context.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_real_connect function, which attempts to establish
|
||||||
|
* a connection to a MySQL server. The function takes in parameters for the host name, username,
|
||||||
|
* password, database name, port number, socket, and flags, all of which are optional except the
|
||||||
|
* mysqli object returned by mysqli_init(). The flags parameter can be used to set different
|
||||||
|
* connection options that can affect the behavior of the connection.
|
||||||
|
*
|
||||||
|
* @param mysqli $connection The mysqli object returned by mysqli_init().
|
||||||
|
* @param string|null $hostname The host name or an IP address.
|
||||||
|
* @param string|null $username The MySQL 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 MySQL 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 wpsqli_real_connect($connection, $hostname = null, $username = null, $password = null, $database = null, $port = null, $socket = null, $flags = 0)
|
||||||
|
{
|
||||||
|
return mysqli_real_connect($connection, $hostname, $username, $password, $database, $port, $socket, $flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes a previously opened database connection.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_close function. It is used to close a non-persistent
|
||||||
|
* connection to a MySQL server that was opened with mysqli_connect() or mysqli_real_connect(). It's
|
||||||
|
* important to close connections when they are no longer needed to free up resources on both the web
|
||||||
|
* server and the MySQL server. The function returns TRUE on success or FALSE on failure.
|
||||||
|
*
|
||||||
|
* @param mysqli $connection The mysqli connection resource to be closed.
|
||||||
|
* @return bool Returns TRUE on successful closure, FALSE on failure.
|
||||||
|
*/
|
||||||
|
function wpsqli_close($connection)
|
||||||
|
{
|
||||||
|
return mysqli_close($connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to establish secure connections using SSL.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_ssl_set function. It is used to set the SSL
|
||||||
|
* certificates and key files for establishing an encrypted connection between the client
|
||||||
|
* and the MySQL server. This function should be called before mysqli_real_connect(). It's
|
||||||
|
* important for security when the database server and the web server are on different hosts
|
||||||
|
* or when sensitive data is being transferred.
|
||||||
|
*
|
||||||
|
* @param mysqli $connection The mysqli 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 wpsqli_ssl_set($connection, $key, $cert, $ca, $capath, $cipher)
|
||||||
|
{
|
||||||
|
return mysqli_ssl_set($connection, $key, $cert, $ca, $capath, $cipher);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the MySQL client library version as a string.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_get_client_info function. It is used to retrieve
|
||||||
|
* the version of the client library that is used to compile the MySQLi 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 MySQL client library,
|
||||||
|
* which can be important for compatibility and functionality reasons.
|
||||||
|
*
|
||||||
|
* @return string The MySQL client library version.
|
||||||
|
*/
|
||||||
|
function wpsqli_get_client_info()
|
||||||
|
{
|
||||||
|
return mysqli_get_client_info();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the version of the MySQL server.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_get_server_info function. It returns a string
|
||||||
|
* representing the version of the MySQL server pointed to by the connection resource. This
|
||||||
|
* information can be used for a variety of purposes, such as conditional behavior for different
|
||||||
|
* MySQL versions or simply for logging and monitoring. Understanding the server version is
|
||||||
|
* essential for ensuring compatibility with specific MySQL features and syntax.
|
||||||
|
*
|
||||||
|
* @param mysqli $connection The mysqli connection resource.
|
||||||
|
* @return string The version of the MySQL server.
|
||||||
|
*/
|
||||||
|
function wpsqli_get_server_info($connection)
|
||||||
|
{
|
||||||
|
return mysqli_get_server_info($connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string representing the type of connection used.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_get_host_info function. It retrieves information about
|
||||||
|
* the type of connection that was established to the MySQL 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 MySQL server.
|
||||||
|
*
|
||||||
|
* @param mysqli $connection The mysqli connection resource.
|
||||||
|
* @return string A string describing the connection type and server host information.
|
||||||
|
*/
|
||||||
|
function wpsqli_host_info($connection)
|
||||||
|
{
|
||||||
|
return mysqli_get_host_info($connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pings a server connection, or tries to reconnect if the connection has gone down.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_ping function, which checks whether the
|
||||||
|
* connection to the server is working. If it has gone down, and the global option
|
||||||
|
* mysqli.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 mysqli $connection The mysqli connection resource.
|
||||||
|
* @return bool Returns TRUE on success or FALSE on failure.
|
||||||
|
*/
|
||||||
|
function wpsqli_ping($connection)
|
||||||
|
{
|
||||||
|
return mysqli_ping($connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the thread ID for the current connection.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_thread_id function. It retrieves the thread ID used by
|
||||||
|
* the current connection to the MySQL server. This ID can be used as an argument to the KILL
|
||||||
|
* statement to terminate a connection. It is useful for debugging and managing MySQL connections
|
||||||
|
* and can be used to uniquely identify the connection within the server's process.
|
||||||
|
*
|
||||||
|
* @param mysqli $connection The mysqli connection resource.
|
||||||
|
* @return int The thread ID for the current connection.
|
||||||
|
*/
|
||||||
|
function wpsqli_thread_id($connection)
|
||||||
|
{
|
||||||
|
return mysqli_thread_id($connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the client library is thread-safe.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_thread_safe function. It indicates whether the
|
||||||
|
* mysqli 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()
|
||||||
|
{
|
||||||
|
return mysqli_thread_safe();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current system status of the MySQL server.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_stat function. It returns a string containing
|
||||||
|
* status information about the MySQL 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 MySQL server, as well
|
||||||
|
* as for debugging purposes.
|
||||||
|
*
|
||||||
|
* @param mysqli $connection The mysqli connection resource.
|
||||||
|
* @return string A string describing the server status or FALSE on failure.
|
||||||
|
*/
|
||||||
|
function wpsqli_stat($connection)
|
||||||
|
{
|
||||||
|
return mysqli_stat($connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets extra connect options and affect behavior for a connection.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_options function. It is used to set extra options
|
||||||
|
* for a connection resource before establishing a connection using mysqli_real_connect(). These
|
||||||
|
* options can be used to control various aspects of the connection's behavior. The function should
|
||||||
|
* be called after mysqli_init() and before mysqli_real_connect(). It returns TRUE on success or
|
||||||
|
* FALSE on failure.
|
||||||
|
*
|
||||||
|
* @param mysqli $connection The mysqli 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)
|
||||||
|
{
|
||||||
|
return mysqli_options($connection, $option, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the error code from the last connection attempt.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_connect_errno function. It returns the error code from
|
||||||
|
* the last call to mysqli_connect() or mysqli_real_connect(). It is useful for error handling after
|
||||||
|
* attempting to establish a connection to a MySQL 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()
|
||||||
|
{
|
||||||
|
return mysqli_connect_errno();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string description of the last connect error.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_connect_error function. It provides a textual description
|
||||||
|
* of the error from the last connection attempt made by mysqli_connect() or mysqli_real_connect().
|
||||||
|
* Unlike mysqli_connect_errno(), which returns an error code, mysqli_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()
|
||||||
|
{
|
||||||
|
return mysqli_connect_error();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transaction Handling
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns on or off auto-commit mode on queries for the database connection.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_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 mysqli_commit() or rollback using mysqli_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 mysqli $connection The mysqli 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)
|
||||||
|
{
|
||||||
|
return mysqli_autocommit($connection, $mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a new transaction.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_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 mysqli $connection The mysqli connection resource.
|
||||||
|
* @param int $flags Optional flags for defining transaction characteristics. This should be a bitmask
|
||||||
|
* of any of the MYSQLI_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)
|
||||||
|
{
|
||||||
|
return mysqli_begin_transaction($connection, $flags, $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Commits the current transaction.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_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 mysqli $connection The mysqli connection resource.
|
||||||
|
* @param int $flags Optional flags for the commit operation. It should be a bitmask of the MYSQLI_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)
|
||||||
|
{
|
||||||
|
return mysqli_commit($connection, $flags, $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rolls back the current transaction for the database connection.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_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 mysqli $connection The mysqli connection resource.
|
||||||
|
* @param int $flags Optional flags that define how the rollback operation should be handled. It should be
|
||||||
|
* a bitmask of the MYSQLI_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)
|
||||||
|
{
|
||||||
|
return mysqli_rollback($connection, $flags, $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Database Operations
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects the default database for database queries.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_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 mysqli $connection The mysqli 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)
|
||||||
|
{
|
||||||
|
return mysqli_select_db($connection, $database);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a query against the database.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_query function. The mysqli_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. It can also be used
|
||||||
|
* to execute multiple queries if the database server supports it. The function can return FALSE
|
||||||
|
* on failure. The optional third parameter defines the result mode - whether to use a resultset
|
||||||
|
* buffering (MYSQLI_STORE_RESULT) or not (MYSQLI_USE_RESULT).
|
||||||
|
*
|
||||||
|
* @param mysqli $connection The mysqli 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 mysqli_result object for successful SELECT queries, TRUE for other
|
||||||
|
* successful queries, or FALSE on failure.
|
||||||
|
*/
|
||||||
|
function wpsqli_query($connection, $query, $result_mode = MYSQLI_STORE_RESULT)
|
||||||
|
{
|
||||||
|
return mysqli_query($connection, $query, $result_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes one or multiple queries which are concatenated by a semicolon.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_multi_query function. It allows execution of
|
||||||
|
* multiple SQL statements sent to the MySQL 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 mysqli_store_result() and mysqli_next_result(). It is important to ensure
|
||||||
|
* that any user input included in the queries is properly sanitized to avoid SQL injection.
|
||||||
|
*
|
||||||
|
* @param mysqli $connection The mysqli 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)
|
||||||
|
{
|
||||||
|
return mysqli_multi_query($connection, $query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares an SQL statement for execution.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_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 mysqli $connection The mysqli connection resource.
|
||||||
|
* @param string $query The SQL query to prepare.
|
||||||
|
* @return mysqli_stmt|false Returns a statement object on success or FALSE on failure.
|
||||||
|
*/
|
||||||
|
function wpsqli_prepare($connection, $query)
|
||||||
|
{
|
||||||
|
return mysqli_prepare($connection, $query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes a prepared Query.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_stmt_execute function. It is used to execute a statement
|
||||||
|
* that was previously prepared using the mysqli_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 mysqli_stmt $stmt The mysqli_stmt statement object.
|
||||||
|
* @return bool Returns TRUE on success or FALSE on failure.
|
||||||
|
*/
|
||||||
|
function wpsqli_stmt_execute($stmt)
|
||||||
|
{
|
||||||
|
return mysqli_stmt_execute($stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds variables to a prepared statement as parameters.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_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 mysqli_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)
|
||||||
|
{
|
||||||
|
return mysqli_stmt_bind_param($stmt, $types, ...$vars);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds variables to a prepared statement for result storage.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_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 mysqli_stmt_fetch(), which
|
||||||
|
* will populate the variables with data from the next row in the result set each time it is called.
|
||||||
|
*
|
||||||
|
* @param mysqli_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)
|
||||||
|
{
|
||||||
|
return mysqli_stmt_bind_result($stmt, ...$vars);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches results from a prepared statement into the bound variables.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_stmt_fetch function. It is used to fetch the data
|
||||||
|
* from the executed prepared statement into the variables that were bound using mysqli_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 mysqli_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)
|
||||||
|
{
|
||||||
|
return mysqli_stmt_fetch($stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes a prepared statement.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_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 mysqli_stmt $stmt The prepared statement object to be closed.
|
||||||
|
* @return bool Returns TRUE on success or FALSE on failure.
|
||||||
|
*/
|
||||||
|
function wpsqli_stmt_close($stmt)
|
||||||
|
{
|
||||||
|
return mysqli_stmt_close($stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string description for the last statement error.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_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
|
||||||
|
* MySQL database. It allows developers to output or log a descriptive error message when a MySQL
|
||||||
|
* operation on a prepared statement fails.
|
||||||
|
*
|
||||||
|
* @param mysqli_stmt $stmt The mysqli_stmt statement object.
|
||||||
|
* @return string A string that describes the error. An empty string if no error occurred.
|
||||||
|
*/
|
||||||
|
function wpsqli_stmt_error($stmt)
|
||||||
|
{
|
||||||
|
return mysqli_stmt_error($stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the error code for the most recent statement call.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_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 mysqli_stmt_error() to retrieve both
|
||||||
|
* the error code and the error message for more detailed debugging and logging.
|
||||||
|
*
|
||||||
|
* @param mysqli_stmt $stmt The mysqli_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)
|
||||||
|
{
|
||||||
|
return mysqli_stmt_errno($stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result Handling
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches a result row as an associative, a numeric array, or both.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_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 (MYSQLI_BOTH). Using MYSQLI_ASSOC will fetch as an
|
||||||
|
* associative array, and MYSQLI_NUM will fetch as a numeric array. It returns NULL when there are
|
||||||
|
* no more rows to fetch.
|
||||||
|
*
|
||||||
|
* @param mysqli_result $result The result set from a query as returned by mysqli_query.
|
||||||
|
* @param int $mode The type of array that should be produced from the current row data.
|
||||||
|
* @return array|null Returns an array of strings that corresponds to the fetched row or NULL if
|
||||||
|
* there are no more rows in result set.
|
||||||
|
*/
|
||||||
|
function wpsqli_fetch_array($result, $mode = MYSQLI_BOTH)
|
||||||
|
{
|
||||||
|
return mysqli_fetch_array($result, $mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches a result row as an object.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_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 mysqli_result $result The result set returned by mysqli_query, mysqli_store_result
|
||||||
|
* or mysqli_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 mysqli_fetch_object($result, $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 mysqli_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(mysqli_result $result): ?array
|
||||||
|
{
|
||||||
|
return mysqli_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 mysqli_fetch_row(),
|
||||||
|
* mysqli_fetch_assoc(), mysqli_fetch_array(), or mysqli_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 mysqli_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(mysqli_result $result, int $row_number): bool
|
||||||
|
{
|
||||||
|
return mysqli_data_seek($result, $row_number);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the next field in the result set.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_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 mysqli_result $result The result set returned by mysqli_query, mysqli_store_result
|
||||||
|
* or mysqli_use_result.
|
||||||
|
* @return object An object which contains field definition information or FALSE if no field information
|
||||||
|
* is available.
|
||||||
|
*/
|
||||||
|
function wpsqli_fetch_field($result)
|
||||||
|
{
|
||||||
|
return mysqli_fetch_field($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the number of fields in a result set.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_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 mysqli_result $result The result set returned by mysqli_query, mysqli_store_result
|
||||||
|
* or mysqli_use_result.
|
||||||
|
* @return int The number of fields in the specified result set.
|
||||||
|
*/
|
||||||
|
function wpsqli_num_fields($result)
|
||||||
|
{
|
||||||
|
return mysqli_num_fields($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of columns for the most recent query on the connection.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_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 mysqli $connection The mysqli connection resource.
|
||||||
|
* @return int An integer representing the number of fields in the result set.
|
||||||
|
*/
|
||||||
|
function wpsqli_field_count($connection)
|
||||||
|
{
|
||||||
|
return mysqli_field_count($connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transfers a result set from the last query.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_store_result function. It is used to transfer the
|
||||||
|
* result set from the last query executed on the given connection, which used the MYSQLI_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
|
||||||
|
* mysqli_fetch_* functions. It's particularly useful when the result set is expected to be accessed
|
||||||
|
* multiple times.
|
||||||
|
*
|
||||||
|
* @param mysqli $connection The mysqli connection resource.
|
||||||
|
* @return mysqli_result|false A buffered result object or FALSE if an error occurred.
|
||||||
|
*/
|
||||||
|
function wpsqli_store_result($connection)
|
||||||
|
{
|
||||||
|
return mysqli_store_result($connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiates the retrieval of a result set from the last query executed using the MYSQLI_USE_RESULT mode.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_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 mysqli $connection The mysqli connection resource.
|
||||||
|
* @return mysqli_result|false An unbuffered result object or FALSE if an error occurred.
|
||||||
|
*/
|
||||||
|
function wpsqli_use_result($connection)
|
||||||
|
{
|
||||||
|
return mysqli_use_result($connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frees the memory associated with a result.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_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 mysqli_result $result The result set returned by mysqli_query, mysqli_store_result
|
||||||
|
* or mysqli_use_result.
|
||||||
|
* @return void This function doesn't return any value.
|
||||||
|
*/
|
||||||
|
function wpsqli_free_result($result)
|
||||||
|
{
|
||||||
|
return mysqli_free_result($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if there are any more result sets from a multi query.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_more_results function. It is used after executing
|
||||||
|
* a multi query with mysqli_multi_query() to check if there are more result sets available.
|
||||||
|
* This is important when processing multiple SQL statements in one call, as it determines
|
||||||
|
* whether the application should keep reading results before sending more statements to the server.
|
||||||
|
* It returns TRUE if one or more result sets are available from the previous calls to
|
||||||
|
* mysqli_multi_query(), otherwise FALSE.
|
||||||
|
*
|
||||||
|
* @param mysqli $connection The mysqli connection resource.
|
||||||
|
* @return bool Returns TRUE if there are more result sets from previous multi queries and
|
||||||
|
* FALSE otherwise.
|
||||||
|
*/
|
||||||
|
function wpsqli_more_results($connection)
|
||||||
|
{
|
||||||
|
return mysqli_more_results($connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves the internal result pointer to the next result set returned from a multi query.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_next_result function, which is used in a multi query
|
||||||
|
* scenario. After executing mysqli_multi_query(), which can send multiple SQL statements to the
|
||||||
|
* server at once, mysqli_next_result() checks for more result sets and prepares the next one for
|
||||||
|
* reading. This is crucial for handling multiple operations executed with mysqli_multi_query() to
|
||||||
|
* ensure that all result sets are processed sequentially. It returns TRUE if there is another result set,
|
||||||
|
* FALSE if there are no more result sets, or FALSE with an error if there is a problem moving the result pointer.
|
||||||
|
*
|
||||||
|
* @param mysqli $connection The mysqli connection resource.
|
||||||
|
* @return bool Returns TRUE on success or FALSE on failure (no more results or an error occurred).
|
||||||
|
*/
|
||||||
|
function wpsqli_next_result($connection)
|
||||||
|
{
|
||||||
|
return mysqli_next_result($connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility Functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
function wpsqli_is_resource($object)
|
||||||
|
{
|
||||||
|
return $object !== false && $object !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the number of affected rows in the previous MySQL operation.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_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 mysqli $connection The mysqli 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)
|
||||||
|
{
|
||||||
|
return mysqli_affected_rows($connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the ID generated by a query on a table with a column having the AUTO_INCREMENT attribute.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_insert_id function, which returns the auto generated id
|
||||||
|
* used in the last query. It is typically used after an INSERT query into a table with an auto-increment
|
||||||
|
* field. The id returned is the one that was automatically generated for the AUTO_INCREMENT column in
|
||||||
|
* the affected table. If the last query wasn't an INSERT or UPDATE statement or didn't affect an
|
||||||
|
* AUTO_INCREMENT column, or if the AUTO_INCREMENT value was set to a non-positive value manually,
|
||||||
|
* this function will return zero.
|
||||||
|
*
|
||||||
|
* @param mysqli $connection The mysqli connection resource.
|
||||||
|
* @return int|string The ID generated for an AUTO_INCREMENT column by the previous query on success, 0 if the previous
|
||||||
|
* query does not generate an AUTO_INCREMENT value, or FALSE on failure.
|
||||||
|
*/
|
||||||
|
function wpsqli_insert_id($connection)
|
||||||
|
{
|
||||||
|
return mysqli_insert_id($connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the default character set to be used when sending data from and to the database server.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_set_charset function. The mysqli_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.
|
||||||
|
*
|
||||||
|
* @param mysqli $connection The mysqli connection resource.
|
||||||
|
* @param string $charset The desired character set.
|
||||||
|
* @return bool Returns TRUE on success or FALSE on failure.
|
||||||
|
*/
|
||||||
|
function wpsqli_set_charset($connection, $charset)
|
||||||
|
{
|
||||||
|
return mysqli_set_charset($connection, $charset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escapes special characters in a string for use in an SQL statement.
|
||||||
|
*
|
||||||
|
* This function serves as a wrapper for the mysqli_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 mysqli connection resource, and returns the escaped string which is safe to be included
|
||||||
|
* in SQL statements.
|
||||||
|
*
|
||||||
|
* @param mysqli $connection The mysqli connection resource.
|
||||||
|
* @param string $string The string to be escaped.
|
||||||
|
* @return string Returns the escaped string.
|
||||||
|
*/
|
||||||
|
function wpsqli_real_escape_string($connection, $string)
|
||||||
|
{
|
||||||
|
return mysqli_real_escape_string($connection, $string);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the last error description for the most recent MySQLi function call
|
||||||
|
* that can succeed or fail.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_error function, which returns a string
|
||||||
|
* describing the error from the last MySQL operation associated with the provided
|
||||||
|
* connection resource. It's an essential function for debugging and error handling
|
||||||
|
* in MySQL-related operations. When a MySQLi function fails, wpsqli_error can be
|
||||||
|
* used to fetch the corresponding error message to understand what went wrong.
|
||||||
|
*
|
||||||
|
* @param mysqli $connection The mysqli 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 mysqli_error($connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the error code for the most recent function call that failed.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_errno function. It returns the error code from
|
||||||
|
* the last error that occurred during a MySQL operation on the given connection. This code
|
||||||
|
* can be used in conjunction with mysqli_error() to provide a detailed explanation of the
|
||||||
|
* error, especially for logging or for generating user-friendly error messages.
|
||||||
|
*
|
||||||
|
* @param mysqli $connection The mysqli connection resource.
|
||||||
|
* @return int Returns an error code value representing the error from the last MySQL operation
|
||||||
|
* on the given connection, or zero if no error occurred.
|
||||||
|
*/
|
||||||
|
function wpsqli_errno($connection)
|
||||||
|
{
|
||||||
|
return mysqli_errno($connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or disables internal report functions.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_report function, which is used to set the
|
||||||
|
* reporting mode of mysqli 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 mysqli 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 MYSQLI_REPORT_* constants.
|
||||||
|
* @return bool Returns TRUE on success or FALSE on failure.
|
||||||
|
*/
|
||||||
|
function wpsqli_report($flags)
|
||||||
|
{
|
||||||
|
return mysqli_report($flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves information about the most recently executed query.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_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 mysqli $connection The mysqli connection resource.
|
||||||
|
* @return string|null A string representing information about the last query executed,
|
||||||
|
* or NULL if no information is available.
|
||||||
|
*/
|
||||||
|
function wpsqli_info($connection)
|
||||||
|
{
|
||||||
|
return mysqli_info($connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a statement and returns an object for use with mysqli_stmt_prepare.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_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 mysqli $connection The mysqli connection resource.
|
||||||
|
* @return mysqli_stmt A new statement object or FALSE on failure.
|
||||||
|
*/
|
||||||
|
function wpsqli_stmt_init($connection)
|
||||||
|
{
|
||||||
|
return mysqli_stmt_init($connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Polls connections for results.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_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 wpsqli_poll(&...$args)
|
||||||
|
{
|
||||||
|
return mysqli_poll(...$args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the result from asynchronous MySQL query.
|
||||||
|
*
|
||||||
|
* This function is a wrapper for the mysqli_reap_async_query function. It is used after initiating
|
||||||
|
* a query with mysqli_query() on a connection with the MYSQLI_ASYNC flag set. It retrieves the result
|
||||||
|
* from the query once it is complete, which can be used with mysqli_poll() to manage multiple
|
||||||
|
* asynchronous queries. It returns a mysqli_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 mysqli $connection The mysqli connection resource.
|
||||||
|
* @return mysqli_result|bool A mysqli_result object for successful SELECT queries, TRUE for other
|
||||||
|
* successful DML queries, or FALSE on failure.
|
||||||
|
*/
|
||||||
|
function wpsqli_reap_async_query($connection)
|
||||||
|
{
|
||||||
|
return mysqli_reap_async_query($connection);
|
||||||
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,193 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// Autoload function to automatically require rewriter classes from the "rewriters" folder
|
||||||
|
spl_autoload_register(function ($className) {
|
||||||
|
$filePath = PG4WP_ROOT . '/rewriters/' . $className . '.php';
|
||||||
|
if (file_exists($filePath)) {
|
||||||
|
require_once $filePath;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function createSQLRewriter(string $sql): AbstractSQLRewriter
|
||||||
|
{
|
||||||
|
$sql = trim($sql);
|
||||||
|
if (preg_match('/^(SELECT|INSERT|REPLACE INTO|UPDATE|DELETE|DESCRIBE|ALTER TABLE|CREATE TABLE|DROP TABLE|SHOW INDEX|SHOW VARIABLES|SHOW TABLES|OPTIMIZE TABLE|SET NAMES|SHOW FULL COLUMNS|SHOW TABLE STATUS)\b/i', $sql, $matches)) {
|
||||||
|
// Convert to a format suitable for class names (e.g., "SHOW TABLES" becomes "ShowTables")
|
||||||
|
$type = str_replace(' ', '', ucwords(str_replace('_', ' ', strtolower($matches[1]))));
|
||||||
|
$className = $type . 'SQLRewriter';
|
||||||
|
|
||||||
|
if (class_exists($className)) {
|
||||||
|
return new $className($sql);
|
||||||
|
} else {
|
||||||
|
throw new Exception("No class defined to handle SQL type: " . $type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Exception("Invalid or unsupported SQL statement.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function pg4wp_rewrite($sql)
|
||||||
|
{
|
||||||
|
// Note: Can be called from constructor before $wpdb is set
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$initial = $sql;
|
||||||
|
$logto = 'queries';
|
||||||
|
// The end of the query may be protected against changes
|
||||||
|
$end = '';
|
||||||
|
|
||||||
|
$rewriter = createSQLRewriter(trim($sql));
|
||||||
|
$sql = $rewriter->rewrite();
|
||||||
|
$logto = strtoupper($rewriter->type());
|
||||||
|
switch ($rewriter->type()) {
|
||||||
|
case 'Update':
|
||||||
|
// This will avoid modifications to anything following ' SET '
|
||||||
|
list($sql, $end) = explode(' SET ', $sql, 2);
|
||||||
|
$end = ' SET ' . $end;
|
||||||
|
break;
|
||||||
|
case 'Insert':
|
||||||
|
// This will avoid modifications to anything following ' VALUES'
|
||||||
|
list($sql, $end) = explode(' VALUES', $sql, 2);
|
||||||
|
$end = ' VALUES' . $end;
|
||||||
|
|
||||||
|
// When installing, the sequence for table terms has to be updated
|
||||||
|
if(defined('WP_INSTALLING') && WP_INSTALLING && false !== strpos($sql, 'INSERT INTO `' . $wpdb->terms . '`')) {
|
||||||
|
$end .= ';SELECT setval(\'' . $wpdb->terms . '_term_id_seq\', (SELECT MAX(term_id) FROM ' . $wpdb->terms . ')+1);';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'Insert':
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = correctMetaValue($sql);
|
||||||
|
$sql = handleInterval($sql);
|
||||||
|
$sql = cleanAndCapitalize($sql);
|
||||||
|
$sql = correctEmptyInStatements($sql);
|
||||||
|
$sql = correctQuoting($sql);
|
||||||
|
|
||||||
|
// Put back the end of the query if it was separated
|
||||||
|
$sql .= $end;
|
||||||
|
|
||||||
|
// For insert ID caching
|
||||||
|
if($logto == 'INSERT') {
|
||||||
|
$pattern = '/INSERT INTO "?([\w_]+)"? \(([^)]+)\)/i';
|
||||||
|
preg_match($pattern, $sql, $matches);
|
||||||
|
|
||||||
|
if (isset($matches[1])) {
|
||||||
|
$GLOBALS['pg4wp_ins_table'] = $matches[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($matches[2])) {
|
||||||
|
$columns_str = $matches[2];
|
||||||
|
$columns = explode(',', $columns_str);
|
||||||
|
$columns = array_map(function ($column) {
|
||||||
|
return trim(trim($column), '"');
|
||||||
|
}, $columns);
|
||||||
|
if (isset($columns[0])) {
|
||||||
|
$GLOBALS['pg4wp_ins_field'] = $columns[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$GLOBALS['pg4wp_last_insert'] = $sql;
|
||||||
|
} elseif(isset($GLOBALS['pg4wp_queued_query'])) {
|
||||||
|
pg_query($GLOBALS['pg4wp_queued_query']);
|
||||||
|
unset($GLOBALS['pg4wp_queued_query']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(PG4WP_DEBUG) {
|
||||||
|
if($initial != $sql) {
|
||||||
|
error_log('[' . microtime(true) . "] Converting :\n$initial\n---- to ----\n$sql\n---------------------\n", 3, PG4WP_LOG . 'pg4wp_' . $logto . '.log');
|
||||||
|
} else {
|
||||||
|
error_log('[' . microtime(true) . "] $sql\n---------------------\n", 3, PG4WP_LOG . 'pg4wp_unmodified.log');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Correct the meta_value field for WP 2.9.1 and add type cast.
|
||||||
|
*
|
||||||
|
* @param string $sql SQL query string
|
||||||
|
* @return string Modified SQL query string
|
||||||
|
*/
|
||||||
|
function correctMetaValue($sql)
|
||||||
|
{
|
||||||
|
// WP 2.9.1 uses a comparison where text data is not quoted
|
||||||
|
$sql = preg_replace('/AND meta_value = (-?\d+)/', 'AND meta_value = \'$1\'', $sql);
|
||||||
|
// Add type cast for meta_value field when it's compared to number
|
||||||
|
$sql = preg_replace('/AND meta_value < (\d+)/', 'AND meta_value::bigint < $1', $sql);
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle interval expressions in SQL query.
|
||||||
|
*
|
||||||
|
* @param string $sql SQL query string
|
||||||
|
* @return string Modified SQL query string
|
||||||
|
*/
|
||||||
|
function handleInterval($sql)
|
||||||
|
{
|
||||||
|
// Generic "INTERVAL xx YEAR|MONTH|DAY|HOUR|MINUTE|SECOND" handler
|
||||||
|
$sql = preg_replace('/INTERVAL[ ]+(\d+)[ ]+(YEAR|MONTH|DAY|HOUR|MINUTE|SECOND)/', "'\$1 \$2'::interval", $sql);
|
||||||
|
// DATE_SUB handling
|
||||||
|
$sql = preg_replace('/DATE_SUB[ ]*\(([^,]+),([^\)]+)\)/', '($1::timestamp - $2)', $sql);
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean SQL query from illegal characters and handle capitalization.
|
||||||
|
*
|
||||||
|
* @param string $sql SQL query string
|
||||||
|
* @return string Modified SQL query string
|
||||||
|
*/
|
||||||
|
function cleanAndCapitalize($sql)
|
||||||
|
{
|
||||||
|
// Remove illegal characters
|
||||||
|
$sql = str_replace('`', '', $sql);
|
||||||
|
// Field names with CAPITALS need special handling
|
||||||
|
if (false !== strpos($sql, 'ID')) {
|
||||||
|
$patterns = [
|
||||||
|
'/ID([^ ])/' => 'ID $1',
|
||||||
|
'/ID$/' => 'ID ',
|
||||||
|
'/\(ID/' => '( ID',
|
||||||
|
'/,ID/' => ', ID',
|
||||||
|
'/[0-9a-zA-Z_]+ID/' => '"$0"',
|
||||||
|
'/\.ID/' => '."ID"',
|
||||||
|
'/[\s]ID /' => ' "ID" ',
|
||||||
|
'/"ID "/' => ' "ID" '
|
||||||
|
];
|
||||||
|
foreach ($patterns as $pattern => $replacement) {
|
||||||
|
$sql = preg_replace($pattern, $replacement, $sql);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Correct empty IN statements in SQL query.
|
||||||
|
*
|
||||||
|
* @param string $sql SQL query string
|
||||||
|
* @return string Modified SQL query string
|
||||||
|
*/
|
||||||
|
function correctEmptyInStatements($sql)
|
||||||
|
{
|
||||||
|
$search = ['IN (\'\')', 'IN ( \'\' )', 'IN ()'];
|
||||||
|
$replace = 'IN (NULL)';
|
||||||
|
$sql = str_replace($search, $replace, $sql);
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Correct quoting for PostgreSQL 9.1+ compatibility.
|
||||||
|
*
|
||||||
|
* @param string $sql SQL query string
|
||||||
|
* @return string Modified SQL query string
|
||||||
|
*/
|
||||||
|
function correctQuoting($sql)
|
||||||
|
{
|
||||||
|
$sql = str_replace("\\'", "''", $sql);
|
||||||
|
$sql = str_replace('\"', '"', $sql);
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
abstract class AbstractSQLRewriter
|
||||||
|
{
|
||||||
|
protected string $originalSQL;
|
||||||
|
|
||||||
|
public function __construct(string $sql)
|
||||||
|
{
|
||||||
|
$this->originalSQL = $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract public function rewrite(): string;
|
||||||
|
|
||||||
|
public function original(): string
|
||||||
|
{
|
||||||
|
return $this->originalSQL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function type(): string
|
||||||
|
{
|
||||||
|
// Get the called class name and remove the "SQLRewriter" suffix to get the SQL type
|
||||||
|
$className = get_called_class();
|
||||||
|
$type = str_replace('SQLRewriter', '', $className);
|
||||||
|
return $type;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,290 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class AlterTableSQLRewriter extends AbstractSQLRewriter
|
||||||
|
{
|
||||||
|
private $stringReplacements = [
|
||||||
|
' tinytext' => ' text',
|
||||||
|
' mediumtext' => ' text',
|
||||||
|
' longtext' => ' text',
|
||||||
|
' unsigned' => ' ',
|
||||||
|
'gmt datetime NOT NULL default \'0000-00-00 00:00:00\'' => 'gmt timestamp NOT NULL DEFAULT timezone(\'gmt\'::text, now())',
|
||||||
|
'default \'0000-00-00 00:00:00\'' => 'DEFAULT now()',
|
||||||
|
'\'0000-00-00 00:00:00\'' => 'now()',
|
||||||
|
' datetime' => ' timestamp',
|
||||||
|
' DEFAULT CHARACTER SET utf8mb4' => '',
|
||||||
|
' DEFAULT CHARACTER SET utf8' => '',
|
||||||
|
|
||||||
|
// For WPMU (starting with WP 3.2)
|
||||||
|
" enum('0','1')" => ' smallint',
|
||||||
|
' COLLATE utf8mb4_unicode_520_ci' => '',
|
||||||
|
' COLLATE utf8_general_ci' => '',
|
||||||
|
' CHARACTER SET utf8' => '',
|
||||||
|
' DEFAULT CHARSET=utf8' => '',
|
||||||
|
|
||||||
|
// For flash-album-gallery plugin
|
||||||
|
' tinyint' => ' smallint'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function rewrite(): string
|
||||||
|
{
|
||||||
|
$sql = $this->original();
|
||||||
|
|
||||||
|
$sql = $this->rewrite_numeric_type($sql);
|
||||||
|
$sql = $this->rewrite_columns_with_protected_names($sql);
|
||||||
|
|
||||||
|
if (str_contains($sql, 'ADD INDEX') || str_contains($sql, 'ADD UNIQUE INDEX')) {
|
||||||
|
$sql = $this->rewriteAddIndex($sql);
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
if (str_contains($sql, 'CHANGE COLUMN')) {
|
||||||
|
$sql = $this->rewriteChangeColumn($sql);
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
if (str_contains($sql, 'ALTER COLUMN')) {
|
||||||
|
$sql = $this->rewriteAlterColumn($sql);
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
if (str_contains($sql, 'ADD COLUMN')) {
|
||||||
|
$sql = $this->rewriteAddColumn($sql);
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
if (str_contains($sql, 'ADD KEY') || str_contains($sql, 'ADD UNIQUE KEY')) {
|
||||||
|
$sql = $this->rewriteAddKey($sql);
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
if (str_contains($sql, 'DROP INDEX')) {
|
||||||
|
$sql = $this->rewriteDropIndex($sql);
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
if (str_contains($sql, 'DROP PRIMARY KEY')) {
|
||||||
|
$sql = $this->rewriteDropPrimaryKey($sql);
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function rewriteAddIndex(string $sql): string
|
||||||
|
{
|
||||||
|
$pattern = '/ALTER TABLE\s+(\w+)\s+ADD (UNIQUE |)INDEX\s+([^\s]+)\s+\(((?:[^\(\)]+|\([^\(\)]+\))+)\)/';
|
||||||
|
|
||||||
|
if(1 === preg_match($pattern, $sql, $matches)) {
|
||||||
|
$table = $matches[1];
|
||||||
|
$unique = $matches[2];
|
||||||
|
$index = $matches[3];
|
||||||
|
$columns = $matches[4];
|
||||||
|
|
||||||
|
// Remove prefix indexing
|
||||||
|
// Rarely used and apparently unnecessary for current uses
|
||||||
|
$columns = preg_replace('/\([^\)]*\)/', '', $columns);
|
||||||
|
|
||||||
|
// Workaround for index name duplicate
|
||||||
|
$index = $table . '_' . $index;
|
||||||
|
|
||||||
|
// Add backticks around index name and column name, and include IF NOT EXISTS clause
|
||||||
|
$sql = "CREATE {$unique}INDEX IF NOT EXISTS `{$index}` ON `{$table}` (`{$columns}`)";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function rewriteChangeColumn(string $sql): string
|
||||||
|
{
|
||||||
|
$pattern = '/ALTER TABLE\s+(\w+)\s+CHANGE COLUMN\s+([^\s]+)\s+([^\s]+)\s+([^ ]+)( unsigned|)\s*(NOT NULL|)\s*(default (.+)|)/';
|
||||||
|
|
||||||
|
if(1 === preg_match($pattern, $sql, $matches)) {
|
||||||
|
$table = $matches[1];
|
||||||
|
$col = $matches[2];
|
||||||
|
$newname = $matches[3];
|
||||||
|
$type = strtolower($matches[4]);
|
||||||
|
if(isset($this->stringReplacements[$type])) {
|
||||||
|
$type = $this->stringReplacements[$type];
|
||||||
|
}
|
||||||
|
$unsigned = $matches[5];
|
||||||
|
$notnull = $matches[6];
|
||||||
|
$default = $matches[7];
|
||||||
|
$defval = $matches[8];
|
||||||
|
if(isset($this->stringReplacements[$defval])) {
|
||||||
|
$defval = $this->stringReplacements[$defval];
|
||||||
|
}
|
||||||
|
$newq = "ALTER TABLE $table ALTER COLUMN $col TYPE $type";
|
||||||
|
if(!empty($notnull)) {
|
||||||
|
$newq .= ", ALTER COLUMN $col SET NOT NULL";
|
||||||
|
}
|
||||||
|
if(!empty($default)) {
|
||||||
|
$newq .= ", ALTER COLUMN $col SET DEFAULT $defval";
|
||||||
|
}
|
||||||
|
if($col != $newname) {
|
||||||
|
$newq .= ";ALTER TABLE $table RENAME COLUMN $col TO $newcol;";
|
||||||
|
}
|
||||||
|
$sql = $newq;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function rewriteAlterColumn(string $sql): string
|
||||||
|
{
|
||||||
|
$pattern = '/ALTER TABLE\s+(\w+)\s+ALTER COLUMN\s+/';
|
||||||
|
|
||||||
|
if(1 === preg_match($pattern, $sql)) {
|
||||||
|
// Translate default values
|
||||||
|
$sql = str_replace(
|
||||||
|
array_keys($this->stringReplacements),
|
||||||
|
array_values($this->stringReplacements),
|
||||||
|
$sql
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function rewriteAddColumn(string $sql): string
|
||||||
|
{
|
||||||
|
$pattern = '/ALTER TABLE\s+(\w+)\s+ADD COLUMN\s+([^\s]+)\s+([^ ]+)( unsigned|)\s+(NOT NULL|)\s*(default (.+)|)/';
|
||||||
|
|
||||||
|
if(1 === preg_match($pattern, $sql, $matches)) {
|
||||||
|
$table = $matches[1];
|
||||||
|
$col = $matches[2];
|
||||||
|
$type = strtolower($matches[3]);
|
||||||
|
if(isset($this->stringReplacements[$type])) {
|
||||||
|
$type = $this->stringReplacements[$type];
|
||||||
|
}
|
||||||
|
$unsigned = $matches[4];
|
||||||
|
$notnull = $matches[5];
|
||||||
|
$default = $matches[6];
|
||||||
|
$defval = $matches[7];
|
||||||
|
if(isset($this->stringReplacements[$defval])) {
|
||||||
|
$defval = $this->stringReplacements[$defval];
|
||||||
|
}
|
||||||
|
$newq = "ALTER TABLE $table ADD COLUMN $col $type";
|
||||||
|
if(!empty($default)) {
|
||||||
|
$newq .= " DEFAULT $defval";
|
||||||
|
}
|
||||||
|
if(!empty($notnull)) {
|
||||||
|
$newq .= " NOT NULL";
|
||||||
|
}
|
||||||
|
$sql = $newq;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function rewriteAddKey(string $sql): string
|
||||||
|
{
|
||||||
|
$pattern = '/ALTER TABLE\s+(\w+)\s+ADD (UNIQUE |)KEY\s+([^\s]+)\s+\(((?:[^\(\)]+|\([^\(\)]+\))+)\)/';
|
||||||
|
|
||||||
|
if(1 === preg_match($pattern, $sql, $matches)) {
|
||||||
|
$table = $matches[1];
|
||||||
|
$unique = $matches[2];
|
||||||
|
$index = $matches[3];
|
||||||
|
$columns = $matches[4];
|
||||||
|
|
||||||
|
// Remove prefix indexing
|
||||||
|
// Rarely used and apparently unnecessary for current uses
|
||||||
|
$columns = preg_replace('/\([^\)]*\)/', '', $columns);
|
||||||
|
|
||||||
|
// Workaround for index name duplicate
|
||||||
|
$index = $table . '_' . $index;
|
||||||
|
$sql = "CREATE {$unique}INDEX $index ON $table ($columns)";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function rewriteDropIndex(string $sql): string
|
||||||
|
{
|
||||||
|
$pattern = '/ALTER TABLE\s+(\w+)\s+DROP INDEX\s+([^\s]+)/';
|
||||||
|
|
||||||
|
if(1 === preg_match($pattern, $sql, $matches)) {
|
||||||
|
$table = $matches[1];
|
||||||
|
$index = $matches[2];
|
||||||
|
$sql = "DROP INDEX {$table}_{$index}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function rewriteDropPrimaryKey(string $sql): string
|
||||||
|
{
|
||||||
|
$pattern = '/ALTER TABLE\s+(\w+)\s+DROP PRIMARY KEY/';
|
||||||
|
|
||||||
|
if(1 === preg_match($pattern, $sql, $matches)) {
|
||||||
|
$table = $matches[1];
|
||||||
|
$sql = "ALTER TABLE {$table} DROP CONSTRAINT {$table}_pkey";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function rewrite_numeric_type($sql)
|
||||||
|
{
|
||||||
|
// Numeric types in MySQL which need to be rewritten
|
||||||
|
$numeric_types = ["bigint", "int", "integer", "smallint", "mediumint", "tinyint", "double", "decimal"];
|
||||||
|
$numeric_types_imploded = implode('|', $numeric_types);
|
||||||
|
|
||||||
|
// Prepare regex pattern to match 'type(x)'
|
||||||
|
$pattern = "/(" . $numeric_types_imploded . ")\(\d+\)/";
|
||||||
|
|
||||||
|
// Execute type find & replace
|
||||||
|
$sql = preg_replace_callback($pattern, function ($matches) {
|
||||||
|
return $matches[1];
|
||||||
|
}, $sql);
|
||||||
|
|
||||||
|
// bigint
|
||||||
|
$pattern = '/bigint(\(\d+\))?([ ]*NOT NULL)?[ ]*auto_increment/i';
|
||||||
|
preg_match($pattern, $sql, $matches);
|
||||||
|
if($matches) {
|
||||||
|
$sql = preg_replace($pattern, 'bigserial', $sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
// int
|
||||||
|
$pattern = '/int(\(\d+\))?([ ]*NOT NULL)?[ ]*auto_increment/i';
|
||||||
|
preg_match($pattern, $sql, $matches);
|
||||||
|
if($matches) {
|
||||||
|
$sql = preg_replace($pattern, 'serial', $sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
// smallint
|
||||||
|
$pattern = '/smallint(\(\d+\))?([ ]*NOT NULL)?[ ]*auto_increment/i';
|
||||||
|
preg_match($pattern, $sql, $matches);
|
||||||
|
if($matches) {
|
||||||
|
$sql = preg_replace($pattern, 'smallserial', $sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle for numeric and decimal -- being replaced with serial
|
||||||
|
$numeric_patterns = ['/numeric(\(\d+\))?([ ]*NOT NULL)?[ ]*auto_increment/i', '/decimal(\(\d+\))?([ ]*NOT NULL)?[ ]*auto_increment/i'];
|
||||||
|
foreach($numeric_patterns as $pattern) {
|
||||||
|
preg_match($pattern, $sql, $matches);
|
||||||
|
if($matches) {
|
||||||
|
$sql = preg_replace($pattern, 'serial', $sql);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function rewrite_columns_with_protected_names($sql)
|
||||||
|
{
|
||||||
|
// Splitting the SQL statement into parts before "(", inside "(", and after ")"
|
||||||
|
if (preg_match('/^(CREATE TABLE IF NOT EXISTS|CREATE TABLE|ALTER TABLE)\s+([^\s]+)\s*\((.*)\)(.*)$/is', $sql, $matches)) {
|
||||||
|
$prefix = $matches[1] . ' ' . $matches[2] . ' (';
|
||||||
|
$columnsAndKeys = $matches[3];
|
||||||
|
$suffix = ')' . $matches[4];
|
||||||
|
|
||||||
|
$regex = '/(?:^|\s*,\s*)(\b(?:timestamp|date|time|default)\b)\s*(?=\s+\w+)/i';
|
||||||
|
|
||||||
|
// Callback function to add quotes around protected column names
|
||||||
|
$callback = function($matches) {
|
||||||
|
$whitespace = str_replace($matches[1], "", $matches[0]);
|
||||||
|
return $whitespace . '"' . $matches[1] . '"';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Replace protected column names with quoted versions within columns and keys part
|
||||||
|
$columnsAndKeys = preg_replace_callback($regex, $callback, $columnsAndKeys);
|
||||||
|
return $prefix . $columnsAndKeys . $suffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class CreateTableSQLRewriter extends AbstractSQLRewriter
|
||||||
|
{
|
||||||
|
private $stringReplacements = [
|
||||||
|
' tinytext' => ' text',
|
||||||
|
' mediumtext' => ' text',
|
||||||
|
' longtext' => ' text',
|
||||||
|
' unsigned' => ' ',
|
||||||
|
'gmt datetime NOT NULL default \'0000-00-00 00:00:00\'' => 'gmt timestamp NOT NULL DEFAULT timezone(\'gmt\'::text, now())',
|
||||||
|
'default \'0000-00-00 00:00:00\'' => 'DEFAULT now()',
|
||||||
|
'\'0000-00-00 00:00:00\'' => 'now()',
|
||||||
|
' datetime' => ' timestamp',
|
||||||
|
' DEFAULT CHARACTER SET utf8mb4' => '',
|
||||||
|
' DEFAULT CHARACTER SET utf8' => '',
|
||||||
|
|
||||||
|
// For WPMU (starting with WP 3.2)
|
||||||
|
" enum('0','1')" => ' smallint',
|
||||||
|
' COLLATE utf8mb4_unicode_520_ci' => '',
|
||||||
|
' COLLATE utf8_general_ci' => '',
|
||||||
|
' CHARACTER SET utf8' => '',
|
||||||
|
' DEFAULT CHARSET=utf8' => '',
|
||||||
|
|
||||||
|
' tinyint' => ' smallint',
|
||||||
|
' mediumint' => ' integer'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function rewrite(): string
|
||||||
|
{
|
||||||
|
$sql = $this->original();
|
||||||
|
|
||||||
|
$tableSQL = str_replace('CREATE TABLE IF NOT EXISTS ', 'CREATE TABLE ', $sql);
|
||||||
|
$pattern = '/CREATE TABLE [`]?(\w+)[`]?/';
|
||||||
|
preg_match($pattern, $tableSQL, $matches);
|
||||||
|
$table = $matches[1];
|
||||||
|
|
||||||
|
// change all creates into create if not exists
|
||||||
|
$pattern = "/CREATE TABLE (IF NOT EXISTS )?(\w+)\s*\(/i";
|
||||||
|
$replacement = 'CREATE TABLE IF NOT EXISTS $2 (';
|
||||||
|
$sql = preg_replace($pattern, $replacement, $sql);
|
||||||
|
|
||||||
|
// Remove trailing spaces
|
||||||
|
$sql = trim($sql);
|
||||||
|
|
||||||
|
// Add a slash if needed
|
||||||
|
if (substr($sql, strlen($sql) - 1, 1) != ";") {
|
||||||
|
$sql = $sql . ";";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate types and some other replacements
|
||||||
|
$sql = str_ireplace(
|
||||||
|
array_keys($this->stringReplacements),
|
||||||
|
array_values($this->stringReplacements),
|
||||||
|
$sql
|
||||||
|
);
|
||||||
|
|
||||||
|
$sql = $this->rewrite_numeric_type($sql);
|
||||||
|
$sql = $this->rewrite_columns_with_protected_names($sql);
|
||||||
|
|
||||||
|
// Support for UNIQUE INDEX creation
|
||||||
|
$pattern = '/,\s*(UNIQUE |)KEY\s+(`[^`]+`|\w+)\s+\(((?:[^()]|\([^)]*\))*)\)/';
|
||||||
|
if(preg_match_all($pattern, $sql, $matches, PREG_SET_ORDER)) {
|
||||||
|
foreach($matches as $match) {
|
||||||
|
$unique = $match[1];
|
||||||
|
$index = $match[2];
|
||||||
|
$columns = $match[3];
|
||||||
|
|
||||||
|
// Removing backticks from the index names
|
||||||
|
$index = str_replace('`', '', $index);
|
||||||
|
|
||||||
|
// Removing backticks and key length constraints from the columns
|
||||||
|
$columns = preg_replace(["/`/", "/\(\d+\)/"], '', $columns);
|
||||||
|
|
||||||
|
// Creating a unique index name
|
||||||
|
$indexName = $table . '_' . $index;
|
||||||
|
|
||||||
|
// Appending the CREATE INDEX statement to SQL
|
||||||
|
$sql .= "\nCREATE {$unique}INDEX IF NOT EXISTS $indexName ON $table ($columns);";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Now remove handled indexes
|
||||||
|
$sql = preg_replace($pattern, '', $sql);
|
||||||
|
|
||||||
|
|
||||||
|
// Rewrite unique keys to just be UNIQUE without a key name
|
||||||
|
$pattern = "/(,\s*)?UNIQUE KEY\s+[a-zA-Z0-9_]+\s+(\([a-zA-Z0-9_,\s]+\))/";
|
||||||
|
$replacement = "$1UNIQUE $2";
|
||||||
|
$sql = preg_replace($pattern, $replacement, $sql);
|
||||||
|
|
||||||
|
// Rewrite Primary keys to not have a key name
|
||||||
|
$pattern = '/PRIMARY KEY\s+\w+\s*\((.*?)\)/';
|
||||||
|
$replacement = 'PRIMARY KEY ($1)';
|
||||||
|
$sql = preg_replace($pattern, $replacement, $sql);
|
||||||
|
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function rewrite_numeric_type($sql)
|
||||||
|
{
|
||||||
|
// Numeric types in MySQL which need to be rewritten
|
||||||
|
$numeric_types = ["bigint", "int", "integer", "smallint", "mediumint", "tinyint", "double", "decimal"];
|
||||||
|
$numeric_types_imploded = implode('|', $numeric_types);
|
||||||
|
|
||||||
|
// Prepare regex pattern to match 'type(x)'
|
||||||
|
$pattern = "/(" . $numeric_types_imploded . ")\(\d+\)/i";
|
||||||
|
|
||||||
|
// Execute type find & replace
|
||||||
|
$sql = preg_replace_callback($pattern, function ($matches) {
|
||||||
|
return $matches[1];
|
||||||
|
}, $sql);
|
||||||
|
|
||||||
|
// bigint
|
||||||
|
$pattern = '/bigint(\(\d+\))?([ ]*NOT NULL)?[ ]*auto_increment/i';
|
||||||
|
preg_match($pattern, $sql, $matches);
|
||||||
|
if($matches) {
|
||||||
|
$sql = preg_replace($pattern, 'bigserial', $sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
// int
|
||||||
|
$pattern = '/int(\(\d+\))?([ ]*NOT NULL)?[ ]*auto_increment/i';
|
||||||
|
preg_match($pattern, $sql, $matches);
|
||||||
|
if($matches) {
|
||||||
|
$sql = preg_replace($pattern, 'serial', $sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
// smallint
|
||||||
|
$pattern = '/smallint(\(\d+\))?([ ]*NOT NULL)?[ ]*auto_increment/i';
|
||||||
|
preg_match($pattern, $sql, $matches);
|
||||||
|
if($matches) {
|
||||||
|
$sql = preg_replace($pattern, 'smallserial', $sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle for numeric and decimal -- being replaced with serial
|
||||||
|
$numeric_patterns = ['/numeric(\(\d+\))?([ ]*NOT NULL)?[ ]*auto_increment/i', '/decimal(\(\d+\))?([ ]*NOT NULL)?[ ]*auto_increment/i'];
|
||||||
|
foreach($numeric_patterns as $pattern) {
|
||||||
|
preg_match($pattern, $sql, $matches);
|
||||||
|
if($matches) {
|
||||||
|
$sql = preg_replace($pattern, 'serial', $sql);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function rewrite_columns_with_protected_names($sql)
|
||||||
|
{
|
||||||
|
// Splitting the SQL statement into parts before "(", inside "(", and after ")"
|
||||||
|
if (preg_match('/^(CREATE TABLE IF NOT EXISTS|CREATE TABLE|ALTER TABLE)\s+([^\s]+)\s*\((.*)\)(.*)$/is', $sql, $matches)) {
|
||||||
|
$prefix = $matches[1] . ' ' . $matches[2] . ' (';
|
||||||
|
$columnsAndKeys = $matches[3];
|
||||||
|
$suffix = ')' . $matches[4];
|
||||||
|
|
||||||
|
$regex = '/(?:^|\s*,\s*)(\b(?:timestamp|date|time|default|end)\b)\s*(?=\s+\w+)/i';
|
||||||
|
|
||||||
|
// Callback function to add quotes around protected column names
|
||||||
|
$callback = function($matches) {
|
||||||
|
$whitespace = str_replace($matches[1], "", $matches[0]);
|
||||||
|
return $whitespace . '"' . $matches[1] . '"';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Replace protected column names with quoted versions within columns and keys part
|
||||||
|
$columnsAndKeys = preg_replace_callback($regex, $callback, $columnsAndKeys);
|
||||||
|
return $prefix . $columnsAndKeys . $suffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class DeleteSQLRewriter extends AbstractSQLRewriter
|
||||||
|
{
|
||||||
|
public function rewrite(): string
|
||||||
|
{
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$sql = $this->original();
|
||||||
|
|
||||||
|
// ORDER BY is not supported in DELETE queries, and not required
|
||||||
|
// when LIMIT is not present
|
||||||
|
if(false !== strpos($sql, 'ORDER BY') && false === strpos($sql, 'LIMIT')) {
|
||||||
|
$pattern = '/ORDER BY \S+ (ASC|DESC)?/';
|
||||||
|
$sql = preg_replace($pattern, '', $sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
// LIMIT is not allowed in DELETE queries
|
||||||
|
$sql = str_replace('LIMIT 1', '', $sql);
|
||||||
|
$sql = str_replace(' REGEXP ', ' ~ ', $sql);
|
||||||
|
|
||||||
|
// Get the WordPress table prefix
|
||||||
|
$prefix = $wpdb->prefix;
|
||||||
|
|
||||||
|
// This handles removal of duplicate entries in table options
|
||||||
|
if(false !== strpos($sql, 'DELETE o1 FROM ')) {
|
||||||
|
$sql = "DELETE FROM $wpdb->options WHERE option_id IN " .
|
||||||
|
"(SELECT o1.option_id FROM $wpdb->options AS o1, $wpdb->options AS o2 " .
|
||||||
|
"WHERE o1.option_name = o2.option_name " .
|
||||||
|
"AND o1.option_id < o2.option_id)";
|
||||||
|
}
|
||||||
|
// Rewrite _transient_timeout multi-table delete query with dynamic prefix for options table
|
||||||
|
elseif(preg_match('/DELETE a, b FROM ' . preg_quote($prefix, '/') . 'options a, ' . preg_quote($prefix, '/') . 'options b/', $sql)) {
|
||||||
|
$where = substr($sql, strpos($sql, 'WHERE ') + 6);
|
||||||
|
$where = rtrim($where, " \t\n\r;");
|
||||||
|
// Fix string/number comparison by adding check and cast
|
||||||
|
$where = str_replace('AND b.option_value', 'AND b.option_value ~ \'^[0-9]+$\' AND CAST(b.option_value AS BIGINT)', $where);
|
||||||
|
// Mirror WHERE clause to delete both sides of self-join.
|
||||||
|
$where2 = strtr($where, array('a.' => 'b.', 'b.' => 'a.'));
|
||||||
|
$sql = "DELETE FROM {$wpdb->options} a USING {$wpdb->options} b WHERE " .
|
||||||
|
'(' . $where . ') OR (' . $where2 . ');';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rewrite _transient_timeout multi-table delete query with dynamic prefix for sitemeta table
|
||||||
|
elseif(preg_match('/DELETE a, b FROM ' . preg_quote($prefix, '/') . 'sitemeta a, ' . preg_quote($prefix, '/') . 'sitemeta b/', $sql)) {
|
||||||
|
$where = substr($sql, strpos($sql, 'WHERE ') + 6);
|
||||||
|
$where = rtrim($where, " \t\n\r;");
|
||||||
|
// Fix string/number comparison by adding check and cast
|
||||||
|
$where = str_replace('AND b.meta_value', 'AND b.meta_value ~ \'^[0-9]+$\' AND CAST(b.meta_value AS BIGINT)', $where);
|
||||||
|
// Mirror WHERE clause to delete both sides of self-join.
|
||||||
|
$where2 = strtr($where, array('a.' => 'b.', 'b.' => 'a.'));
|
||||||
|
// Use $wpdb's sitemeta table name which should already have the correct prefix
|
||||||
|
if(isset($wpdb->sitemeta)) {
|
||||||
|
$sql = "DELETE FROM {$wpdb->sitemeta} a USING {$wpdb->sitemeta} b WHERE " .
|
||||||
|
'(' . $where . ') OR (' . $where2 . ');';
|
||||||
|
} else {
|
||||||
|
// Fallback if $wpdb->sitemeta is not available
|
||||||
|
$sql = "DELETE FROM {$prefix}sitemeta a USING {$prefix}sitemeta b WHERE " .
|
||||||
|
'(' . $where . ') OR (' . $where2 . ');';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a more general pattern to handle multi-table DELETE with aliases and dynamic table names
|
||||||
|
elseif(preg_match('/DELETE\s+([a-zA-Z0-9_]+),\s*([a-zA-Z0-9_]+)\s+FROM\s+([a-zA-Z0-9_' . preg_quote($prefix, '/') . ']+)\s+([a-zA-Z0-9_]+),\s*([a-zA-Z0-9_' . preg_quote($prefix, '/') . ']+)\s+([a-zA-Z0-9_]+)\s+WHERE/i', $sql, $matches)) {
|
||||||
|
// Extract aliases and table names
|
||||||
|
$firstAlias = $matches[1];
|
||||||
|
$secondAlias = $matches[2];
|
||||||
|
$firstTable = $matches[3];
|
||||||
|
$firstTableAlias = $matches[4];
|
||||||
|
$secondTable = $matches[5];
|
||||||
|
$secondTableAlias = $matches[6];
|
||||||
|
|
||||||
|
// Extract WHERE clause
|
||||||
|
$where = substr($sql, strpos($sql, 'WHERE ') + 6);
|
||||||
|
$where = rtrim($where, " \t\n\r;");
|
||||||
|
|
||||||
|
// Check if the table names are known WordPress tables and replace with dynamic property references
|
||||||
|
foreach([$firstTable, $secondTable] as $index => $tableName) {
|
||||||
|
// Strip prefix if it exists to get the base table name
|
||||||
|
$baseTableName = preg_replace('/^' . preg_quote($prefix, '/') . '/', '', $tableName);
|
||||||
|
|
||||||
|
// Check if $wpdb has a property for this table
|
||||||
|
if(isset($wpdb->$baseTableName)) {
|
||||||
|
// Replace the hardcoded table name with the dynamic property
|
||||||
|
if($index === 0) {
|
||||||
|
$firstTable = $wpdb->$baseTableName;
|
||||||
|
} else {
|
||||||
|
$secondTable = $wpdb->$baseTableName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate PostgreSQL DELETE...USING syntax
|
||||||
|
$sql = "DELETE FROM $firstTable $firstTableAlias USING $secondTable $secondTableAlias WHERE $where;";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Akismet sometimes doesn't write 'comment_ID' with 'ID' in capitals where needed ...
|
||||||
|
if(false !== strpos($sql, $wpdb->comments)) {
|
||||||
|
$sql = str_replace(' comment_id ', ' comment_ID ', $sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class DescribeSQLRewriter extends AbstractSQLRewriter
|
||||||
|
{
|
||||||
|
public function rewrite(): string
|
||||||
|
{
|
||||||
|
$sql = $this->original();
|
||||||
|
$table = $this->extractTableName($sql);
|
||||||
|
return $this->generatePostgresDescribeTable($table);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts table name from a "DESCRIBE" SQL statement.
|
||||||
|
*
|
||||||
|
* @param string $sql The SQL statement
|
||||||
|
* @return string|null The table name if found, or null otherwise
|
||||||
|
*/
|
||||||
|
protected function extractTableName($sql)
|
||||||
|
{
|
||||||
|
$pattern = "/DESCRIBE ['\"`]?([^'\"`]+)['\"`]?/i";
|
||||||
|
if (preg_match($pattern, $sql, $matches)) {
|
||||||
|
return $matches[1];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a PostgreSQL-compatible SQL query to mimic MySQL's "DESCRIBE".
|
||||||
|
*
|
||||||
|
* @param string $tableName The table name
|
||||||
|
* @param string $schema The schema name
|
||||||
|
* @return string The generated SQL query
|
||||||
|
*/
|
||||||
|
public function generatePostgresDescribeTable($tableName, $schema = "public")
|
||||||
|
{
|
||||||
|
$sql = <<<SQL
|
||||||
|
SELECT
|
||||||
|
f.attnum AS number,
|
||||||
|
f.attname AS name,
|
||||||
|
f.attnum,
|
||||||
|
f.attnotnull AS notnull,
|
||||||
|
pg_catalog.format_type(f.atttypid,f.atttypmod) AS type,
|
||||||
|
CASE
|
||||||
|
WHEN p.contype = 'p' THEN 't'
|
||||||
|
ELSE 'f'
|
||||||
|
END AS primarykey,
|
||||||
|
CASE
|
||||||
|
WHEN p.contype = 'u' THEN 't'
|
||||||
|
ELSE 'f'
|
||||||
|
END AS uniquekey,
|
||||||
|
CASE
|
||||||
|
WHEN p.contype = 'f' THEN g.relname
|
||||||
|
END AS foreignkey,
|
||||||
|
CASE
|
||||||
|
WHEN p.contype = 'f' THEN p.confkey
|
||||||
|
END AS foreignkey_fieldnum,
|
||||||
|
CASE
|
||||||
|
WHEN p.contype = 'f' THEN g.relname
|
||||||
|
END AS foreignkey,
|
||||||
|
CASE
|
||||||
|
WHEN p.contype = 'f' THEN p.conkey
|
||||||
|
END AS foreignkey_connnum,
|
||||||
|
CASE
|
||||||
|
WHEN f.atthasdef = 't' THEN pg_get_expr(d.adbin, d.adrelid)
|
||||||
|
END AS default
|
||||||
|
FROM pg_attribute f
|
||||||
|
JOIN pg_class c ON c.oid = f.attrelid
|
||||||
|
JOIN pg_type t ON t.oid = f.atttypid
|
||||||
|
LEFT JOIN pg_attrdef d ON d.adrelid = c.oid AND d.adnum = f.attnum
|
||||||
|
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
|
||||||
|
LEFT JOIN pg_constraint p ON p.conrelid = c.oid AND f.attnum = ANY (p.conkey)
|
||||||
|
LEFT JOIN pg_class AS g ON p.confrelid = g.oid
|
||||||
|
WHERE c.relkind = 'r'::char
|
||||||
|
AND n.nspname = '$schema'
|
||||||
|
AND c.relname = '$tableName'
|
||||||
|
AND f.attnum > 0 ORDER BY number
|
||||||
|
SQL;
|
||||||
|
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class DropTableSQLRewriter extends AbstractSQLRewriter
|
||||||
|
{
|
||||||
|
public function rewrite(): string
|
||||||
|
{
|
||||||
|
$sql = $this->original();
|
||||||
|
|
||||||
|
$pattern = '/DROP TABLE.+ [`]?(\w+)[`]?$/';
|
||||||
|
preg_match($pattern, $sql, $matches);
|
||||||
|
$table = $matches[1];
|
||||||
|
$seq = $table . '_seq';
|
||||||
|
$sql .= ";\nDROP SEQUENCE IF EXISTS $seq;";
|
||||||
|
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class InsertSQLRewriter extends AbstractSQLRewriter
|
||||||
|
{
|
||||||
|
public function rewrite(): string
|
||||||
|
{
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$sql = $this->original();
|
||||||
|
|
||||||
|
// Those are used when we need to set the date to now() in gmt time
|
||||||
|
$sql = str_replace("'0000-00-00 00:00:00'", 'now() AT TIME ZONE \'gmt\'', $sql);
|
||||||
|
|
||||||
|
$sql = str_replace("utc_timestamp()", "CURRENT_TIMESTAMP AT TIME ZONE 'UTC'", $sql);
|
||||||
|
|
||||||
|
// Multiple values group when calling INSERT INTO don't always work
|
||||||
|
if(false !== strpos($sql, $wpdb->options) && false !== strpos($sql, '), (')) {
|
||||||
|
$pattern = '/INSERT INTO.+VALUES/';
|
||||||
|
preg_match($pattern, $sql, $matches);
|
||||||
|
$insert = $matches[0];
|
||||||
|
$sql = str_replace('), (', ');' . $insert . '(', $sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap ON DUPLICATE KEY SYNTAX
|
||||||
|
if(false !== $pos = strpos($sql, 'ON DUPLICATE KEY UPDATE')) {
|
||||||
|
$splitStatements = function (string $sql): array {
|
||||||
|
$statements = [];
|
||||||
|
$buffer = '';
|
||||||
|
$quote = null;
|
||||||
|
|
||||||
|
for ($i = 0, $len = strlen($sql); $i < $len; $i++) {
|
||||||
|
$char = $sql[$i];
|
||||||
|
|
||||||
|
if ($quote) {
|
||||||
|
if ($char === $quote && $sql[$i - 1] !== '\\') {
|
||||||
|
$quote = null;
|
||||||
|
}
|
||||||
|
} elseif ($char === '"' || $char === "'") {
|
||||||
|
$quote = $char;
|
||||||
|
} elseif ($char === ';') {
|
||||||
|
$statements[] = $buffer . ';';
|
||||||
|
$buffer = '';
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$buffer .= $char;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($buffer)) {
|
||||||
|
$statements[] = $buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $statements;
|
||||||
|
};
|
||||||
|
|
||||||
|
$statements = $splitStatements($sql);
|
||||||
|
|
||||||
|
foreach ($statements as $statement) {
|
||||||
|
$statement = trim($statement);
|
||||||
|
|
||||||
|
// Skip empty statements
|
||||||
|
if (empty($statement)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace backticks with double quotes for PostgreSQL compatibility
|
||||||
|
$statement = str_replace('`', '"', $statement);
|
||||||
|
|
||||||
|
// Find index positions for the SQL components
|
||||||
|
$insertIndex = strpos($statement, 'INSERT INTO');
|
||||||
|
$valuesIndex = strpos($statement, 'VALUES');
|
||||||
|
$onDuplicateKeyIndex = strpos($statement, 'ON DUPLICATE KEY UPDATE');
|
||||||
|
|
||||||
|
// Extract SQL components
|
||||||
|
$tableSection = trim(substr($statement, $insertIndex, $valuesIndex - $insertIndex));
|
||||||
|
$valuesSection = trim(substr($statement, $valuesIndex, $onDuplicateKeyIndex - $valuesIndex));
|
||||||
|
$updateSection = trim(str_replace('ON DUPLICATE KEY UPDATE', '', substr($statement, $onDuplicateKeyIndex)));
|
||||||
|
|
||||||
|
// Extract and clean up column names from the update section
|
||||||
|
$updateCols = explode(',', $updateSection);
|
||||||
|
$updateCols = array_map(function ($col) {
|
||||||
|
return trim(explode('=', $col)[0]);
|
||||||
|
}, $updateCols);
|
||||||
|
|
||||||
|
// Choose a primary key for ON CONFLICT
|
||||||
|
$primaryKey = 'option_name';
|
||||||
|
if (!in_array($primaryKey, $updateCols)) {
|
||||||
|
$primaryKey = 'meta_name';
|
||||||
|
if (!in_array($primaryKey, $updateCols)) {
|
||||||
|
$primaryKey = $updateCols[0] ?? '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct the PostgreSQL ON CONFLICT DO UPDATE section
|
||||||
|
$updateSection = implode(', ', array_map(fn ($col) => "$col = EXCLUDED.$col", $updateCols));
|
||||||
|
|
||||||
|
// Construct the PostgreSQL query
|
||||||
|
$postgresSQL = sprintf('%s %s ON CONFLICT (%s) DO UPDATE SET %s', $tableSection, $valuesSection, $primaryKey, $updateSection);
|
||||||
|
|
||||||
|
// Append to the converted statements list
|
||||||
|
$convertedStatements[] = $postgresSQL;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = implode('; ', $convertedStatements);
|
||||||
|
} elseif(0 === strpos($sql, 'INSERT IGNORE')) {
|
||||||
|
// Note: Requires PostgreSQL 9.5
|
||||||
|
$sql = 'INSERT' . substr($sql, 13) . ' ON CONFLICT DO NOTHING';
|
||||||
|
}
|
||||||
|
|
||||||
|
// To avoid Encoding errors when inserting data coming from outside
|
||||||
|
if(preg_match('/^.{1}/us', $sql, $ar) != 1) {
|
||||||
|
$sql = utf8_encode($sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(false === strpos($sql, 'RETURNING')) {
|
||||||
|
$end_of_statement = $this->findSemicolon($sql);
|
||||||
|
if ($end_of_statement !== false) {
|
||||||
|
// Create the substrings up to and after the semicolon
|
||||||
|
$sql_before_semicolon = substr($sql, 0, $end_of_statement);
|
||||||
|
$sql_after_semicolon = substr($sql, $end_of_statement, strlen($sql));
|
||||||
|
|
||||||
|
// Splice the SQL string together with 'RETURNING *'
|
||||||
|
$sql = $sql_before_semicolon . ' RETURNING *' . $sql_after_semicolon;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$sql = $sql .= " RETURNING *";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
// finds semicolons that aren't in variables
|
||||||
|
private function findSemicolon($sql)
|
||||||
|
{
|
||||||
|
$quoteOpened = false;
|
||||||
|
$parenthesisDepth = 0;
|
||||||
|
|
||||||
|
$sqlAsArray = str_split($sql);
|
||||||
|
for($i = 0; $i < count($sqlAsArray); $i++) {
|
||||||
|
if(($sqlAsArray[$i] == '"' || $sqlAsArray[$i] == "'") && ($i == 0 || $sqlAsArray[$i - 1] != '\\')) {
|
||||||
|
$quoteOpened = !$quoteOpened;
|
||||||
|
} elseif($sqlAsArray[$i] == '(' && !$quoteOpened) {
|
||||||
|
$parenthesisDepth++;
|
||||||
|
} elseif($sqlAsArray[$i] == ')' && !$quoteOpened) {
|
||||||
|
$parenthesisDepth--;
|
||||||
|
} elseif($sqlAsArray[$i] == ';' && !$quoteOpened && $parenthesisDepth == 0) {
|
||||||
|
return $i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class OptimizeTableSQLRewriter extends AbstractSQLRewriter
|
||||||
|
{
|
||||||
|
public function rewrite(): string
|
||||||
|
{
|
||||||
|
$sql = $this->original();
|
||||||
|
return str_replace('OPTIMIZE TABLE', 'VACUUM', $sql);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class ReplaceIntoSQLRewriter extends AbstractSQLRewriter
|
||||||
|
{
|
||||||
|
public function rewrite(): string
|
||||||
|
{
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$sql = $this->original();
|
||||||
|
|
||||||
|
$splitStatements = function (string $sql): array {
|
||||||
|
$statements = [];
|
||||||
|
$buffer = '';
|
||||||
|
$quote = null;
|
||||||
|
|
||||||
|
for ($i = 0, $len = strlen($sql); $i < $len; $i++) {
|
||||||
|
$char = $sql[$i];
|
||||||
|
|
||||||
|
if ($quote) {
|
||||||
|
if ($char === $quote && $sql[$i - 1] !== '\\') {
|
||||||
|
$quote = null;
|
||||||
|
}
|
||||||
|
} elseif ($char === '"' || $char === "'") {
|
||||||
|
$quote = $char;
|
||||||
|
} elseif ($char === ';') {
|
||||||
|
$statements[] = $buffer . ';';
|
||||||
|
$buffer = '';
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$buffer .= $char;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($buffer)) {
|
||||||
|
$statements[] = $buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $statements;
|
||||||
|
};
|
||||||
|
|
||||||
|
$statements = $splitStatements($sql);
|
||||||
|
|
||||||
|
foreach ($statements as $statement) {
|
||||||
|
$statement = trim($statement);
|
||||||
|
|
||||||
|
// Skip empty statements
|
||||||
|
if (empty($statement)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace backticks with double quotes for PostgreSQL compatibility
|
||||||
|
$statement = str_replace('`', '"', $statement);
|
||||||
|
|
||||||
|
// Find index positions for the SQL components
|
||||||
|
$insertIndex = strpos($statement, 'REPLACE INTO');
|
||||||
|
$columnsStartIndex = strpos($statement, "(");
|
||||||
|
$columnsEndIndex = strpos($statement, ")");
|
||||||
|
$valuesIndex = strpos($statement, 'VALUES');
|
||||||
|
$onDuplicateKeyIndex = strpos($statement, 'ON DUPLICATE KEY UPDATE');
|
||||||
|
|
||||||
|
// Extract SQL components
|
||||||
|
$tableSection = trim(substr($statement, $insertIndex, $columnsStartIndex - $insertIndex));
|
||||||
|
$valuesSection = trim(substr($statement, $valuesIndex, strlen($statement) - $valuesIndex));
|
||||||
|
$columnsSection = trim(substr($statement, $columnsStartIndex, $columnsEndIndex - $columnsStartIndex + 1));
|
||||||
|
|
||||||
|
// Extract and clean up column names from the update section
|
||||||
|
$updateCols = explode(',', substr($columnsSection, 1, strlen($columnsSection) - 2));
|
||||||
|
$updateCols = array_map(function ($col) {
|
||||||
|
return trim($col);
|
||||||
|
}, $updateCols);
|
||||||
|
|
||||||
|
// Choose a primary key for ON CONFLICT
|
||||||
|
$primaryKey = 'option_name';
|
||||||
|
if (!in_array($primaryKey, $updateCols)) {
|
||||||
|
$primaryKey = 'meta_name';
|
||||||
|
if (!in_array($primaryKey, $updateCols)) {
|
||||||
|
$primaryKey = $updateCols[0] ?? '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SWAP REPLACE INTO for INSERT INTO
|
||||||
|
$tableSection = str_replace("REPLACE INTO", "INSERT INTO", $tableSection);
|
||||||
|
|
||||||
|
// Construct the PostgreSQL ON CONFLICT DO UPDATE section
|
||||||
|
$updateSection = "";
|
||||||
|
foreach($updateCols as $col) {
|
||||||
|
if ($col !== $primaryKey) {
|
||||||
|
$updateSection .= ", ";
|
||||||
|
$updateSection .= "$col = EXCLUDED.$col";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// trim any preceding commas
|
||||||
|
$updateSection = ltrim($updateSection, ", ");
|
||||||
|
|
||||||
|
// Construct the PostgreSQL query
|
||||||
|
$postgresSQL = sprintf('%s %s %s ON CONFLICT (%s) DO UPDATE SET %s', $tableSection, $columnsSection, $valuesSection, $primaryKey, $updateSection);
|
||||||
|
|
||||||
|
if(false === strpos($postgresSQL, 'RETURNING')) {
|
||||||
|
$end_of_statement = $this->findSemicolon($postgresSQL);
|
||||||
|
if ($end_of_statement !== false) {
|
||||||
|
// Create the substrings up to and after the semicolon
|
||||||
|
$sql_before_semicolon = substr($postgresSQL, 0, $end_of_statement);
|
||||||
|
$sql_after_semicolon = substr($postgresSQL, $end_of_statement, strlen($postgresSQL));
|
||||||
|
|
||||||
|
// Splice the SQL string together with 'RETURNING *'
|
||||||
|
$postgresSQL = $sql_before_semicolon . ' RETURNING *' . $sql_after_semicolon;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$postgresSQL = $postgresSQL .= " RETURNING *";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append to the converted statements list
|
||||||
|
$convertedStatements[] = $postgresSQL;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = implode('; ', $convertedStatements);
|
||||||
|
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
// finds semicolons that aren't in variables
|
||||||
|
private function findSemicolon($sql)
|
||||||
|
{
|
||||||
|
$quoteOpened = false;
|
||||||
|
$parenthesisDepth = 0;
|
||||||
|
|
||||||
|
$sqlAsArray = str_split($sql);
|
||||||
|
for($i = 0; $i < count($sqlAsArray); $i++) {
|
||||||
|
if(($sqlAsArray[$i] == '"' || $sqlAsArray[$i] == "'") && ($i == 0 || $sqlAsArray[$i - 1] != '\\')) {
|
||||||
|
$quoteOpened = !$quoteOpened;
|
||||||
|
} elseif($sqlAsArray[$i] == '(' && !$quoteOpened) {
|
||||||
|
$parenthesisDepth++;
|
||||||
|
} elseif($sqlAsArray[$i] == ')' && !$quoteOpened) {
|
||||||
|
$parenthesisDepth--;
|
||||||
|
} elseif($sqlAsArray[$i] == ';' && !$quoteOpened && $parenthesisDepth == 0) {
|
||||||
|
return $i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,400 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class SelectSQLRewriter extends AbstractSQLRewriter
|
||||||
|
{
|
||||||
|
public function rewrite(): string
|
||||||
|
{
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$sql = $this->original();
|
||||||
|
|
||||||
|
// SQL_CALC_FOUND_ROWS doesn't exist in PostgreSQL but it's needed for correct paging
|
||||||
|
if(false !== strpos($sql, 'SQL_CALC_FOUND_ROWS')) {
|
||||||
|
$sql = str_replace('SQL_CALC_FOUND_ROWS', '', $sql);
|
||||||
|
$GLOBALS['pg4wp_numrows_query'] = $sql;
|
||||||
|
if(PG4WP_DEBUG) {
|
||||||
|
error_log('[' . microtime(true) . "] Number of rows required for :\n$sql\n---------------------\n", 3, PG4WP_LOG . 'pg4wp_NUMROWS.log');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(false !== strpos($sql, 'FOUND_ROWS()')) {
|
||||||
|
// Here we convert the latest query into a COUNT query
|
||||||
|
$sql = $GLOBALS['pg4wp_numrows_query'];
|
||||||
|
|
||||||
|
// Remove the LIMIT clause if it exists
|
||||||
|
$sql = preg_replace('/\s+LIMIT\s+\d+(\s*,\s*\d+)?/i', '', $sql);
|
||||||
|
|
||||||
|
// Remove the ORDER BY containing case / end clause if it exists
|
||||||
|
$sql = preg_replace('/\s+ORDER\s+BY\s+.+END\),[^)]+/is', '', $sql);
|
||||||
|
|
||||||
|
// Remove the ORDER BY clause if it exists
|
||||||
|
$sql = preg_replace('/\s+ORDER\s+BY\s+[^)]+/i', '', $sql);
|
||||||
|
|
||||||
|
// Replace the fields in the SELECT clause with COUNT(*)
|
||||||
|
$sql = preg_replace('/SELECT\s+.*?\s+FROM\s+/is', 'SELECT COUNT(*) FROM ', $sql, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(false !== strpos($sql, 'information_schema')) {
|
||||||
|
// WP Site Health rewrites
|
||||||
|
if (false !== strpos($sql, "SELECT TABLE_NAME AS 'table', TABLE_ROWS AS 'rows', SUM(data_length + index_length)")) {
|
||||||
|
$sql = $this->postgresTableSizeRewrite();
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception("Unsupported call to information_schema, this probably won't work correctly and needs to be specifically handled, open a github issue with the SQL");
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = $this->ensureOrderByInSelect($sql);
|
||||||
|
|
||||||
|
// Handle +0 casting in order by
|
||||||
|
// Regular expression to match the "ORDER BY" pattern
|
||||||
|
$pattern = '/ORDER BY\s+([a-zA-Z0-9_]+)\.meta_value\s*\+\s*0/i';
|
||||||
|
$replacement = 'ORDER BY CAST($1.meta_value AS SIGNED)';
|
||||||
|
$sql = preg_replace($pattern, $replacement, $sql);
|
||||||
|
|
||||||
|
// Convert CONVERT to CAST
|
||||||
|
$pattern = '/CONVERT\(([^()]*(\(((?>[^()]+)|(?-2))*\))?[^()]*),\s*([^\s]+)\)/x';
|
||||||
|
$sql = preg_replace($pattern, 'CAST($1 AS $4)', $sql);
|
||||||
|
|
||||||
|
// Handle CAST( ... AS CHAR)
|
||||||
|
$sql = preg_replace('/CAST\((.+) AS CHAR\)/', 'CAST($1 AS TEXT)', $sql);
|
||||||
|
|
||||||
|
// Handle CAST( ... AS SIGNED)
|
||||||
|
$sql = preg_replace('/CAST\((.+) AS SIGNED\)/', 'CAST($1 AS INTEGER)', $sql);
|
||||||
|
|
||||||
|
// Handle COUNT(*)...ORDER BY...
|
||||||
|
$sql = preg_replace('/COUNT(.+)ORDER BY.+/s', 'COUNT$1', $sql);
|
||||||
|
|
||||||
|
// HANDLE REGEXP
|
||||||
|
$sql = preg_replace('/REGEXP/', '~', $sql);
|
||||||
|
|
||||||
|
$sql = str_replace("utc_timestamp()", "CURRENT_TIMESTAMP AT TIME ZONE 'UTC'", $sql);
|
||||||
|
|
||||||
|
// In order for users counting to work...
|
||||||
|
$matches = array();
|
||||||
|
if(preg_match_all('/COUNT[^C]+\),/', $sql, $matches)) {
|
||||||
|
foreach($matches[0] as $num => $one) {
|
||||||
|
$sub = substr($one, 0, -1);
|
||||||
|
$sql = str_replace($sub, $sub . ' AS count' . $num, $sql);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = $this->convertToPostgresLimitSyntax($sql);
|
||||||
|
$sql = $this->ensureGroupByOrAggregate($sql);
|
||||||
|
|
||||||
|
$pattern = '/DATE_ADD[ ]*\(([^,]+),([^\)]+)\)/';
|
||||||
|
$sql = preg_replace($pattern, '($1 + $2)', $sql);
|
||||||
|
|
||||||
|
// Convert MySQL FIELD function to CASE statement
|
||||||
|
$pattern = '/FIELD[ ]*\(([^\),]+),([^\)]+)\)/';
|
||||||
|
// https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_field
|
||||||
|
// Other implementations: https://stackoverflow.com/q/1309624
|
||||||
|
$sql = preg_replace_callback($pattern, function ($matches) {
|
||||||
|
$case = 'CASE ' . trim($matches[1]);
|
||||||
|
$comparands = explode(',', $matches[2]);
|
||||||
|
foreach($comparands as $i => $comparand) {
|
||||||
|
$case .= ' WHEN ' . trim($comparand) . ' THEN ' . ($i + 1);
|
||||||
|
}
|
||||||
|
$case .= ' ELSE 0 END';
|
||||||
|
return $case;
|
||||||
|
}, $sql);
|
||||||
|
|
||||||
|
$pattern = '/GROUP_CONCAT\(([^()]*(\(((?>[^()]+)|(?-2))*\))?[^()]*)\)/x';
|
||||||
|
$sql = preg_replace($pattern, "string_agg($1, ',')", $sql);
|
||||||
|
|
||||||
|
// Convert MySQL RAND function to PostgreSQL RANDOM function
|
||||||
|
$pattern = '/RAND[ ]*\([ ]*\)/';
|
||||||
|
$sql = preg_replace($pattern, 'RANDOM()', $sql);
|
||||||
|
|
||||||
|
// UNIX_TIMESTAMP in MYSQL returns an integer
|
||||||
|
$pattern = '/UNIX_TIMESTAMP\(([^\)]+)\)/';
|
||||||
|
$sql = preg_replace($pattern, 'ROUND(DATE_PART(\'epoch\',$1))', $sql);
|
||||||
|
|
||||||
|
$date_funcs = array(
|
||||||
|
'DAYOFMONTH(' => 'EXTRACT(DAY FROM ',
|
||||||
|
'YEAR(' => 'EXTRACT(YEAR FROM ',
|
||||||
|
'MONTH(' => 'EXTRACT(MONTH FROM ',
|
||||||
|
'DAY(' => 'EXTRACT(DAY FROM ',
|
||||||
|
);
|
||||||
|
|
||||||
|
$sql = str_replace('ORDER BY post_date DESC', 'ORDER BY YEAR(post_date) DESC, MONTH(post_date) DESC', $sql);
|
||||||
|
$sql = str_replace('ORDER BY post_date ASC', 'ORDER BY YEAR(post_date) ASC, MONTH(post_date) ASC', $sql);
|
||||||
|
$sql = str_replace(array_keys($date_funcs), array_values($date_funcs), $sql);
|
||||||
|
$curryear = date('Y');
|
||||||
|
$sql = str_replace('FROM \'' . $curryear, 'FROM TIMESTAMP \'' . $curryear, $sql);
|
||||||
|
|
||||||
|
// MySQL 'IF' conversion - Note : NULLIF doesn't need to be corrected
|
||||||
|
$pattern = '/ (?<!NULL)IF[ ]*\(([^,]+),([^,]+),([^\)]+)\)/';
|
||||||
|
$sql = preg_replace($pattern, ' CASE WHEN $1 THEN $2 ELSE $3 END', $sql);
|
||||||
|
|
||||||
|
// Act like MySQL default configuration, where sql_mode is ""
|
||||||
|
$pattern = '/@@SESSION.sql_mode/';
|
||||||
|
$sql = preg_replace($pattern, "''", $sql);
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: this seems wrong but if we remove it we get failures with XYZ is not part of the group By
|
||||||
|
if(isset($wpdb)) {
|
||||||
|
$sql = str_replace('GROUP BY ' . $wpdb->prefix . 'posts.ID', '', $sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
// MySQL 'LIKE' is case insensitive by default, whereas PostgreSQL 'LIKE' is
|
||||||
|
$sql = str_replace(' LIKE ', ' ILIKE ', $sql);
|
||||||
|
|
||||||
|
// INDEXES are not yet supported
|
||||||
|
if(false !== strpos($sql, 'USE INDEX (comment_date_gmt)')) {
|
||||||
|
$sql = str_replace('USE INDEX (comment_date_gmt)', '', $sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
// HB : timestamp fix for permalinks
|
||||||
|
$sql = str_replace('post_date_gmt > 1970', 'post_date_gmt > to_timestamp (\'1970\')', $sql);
|
||||||
|
|
||||||
|
// Akismet sometimes doesn't write 'comment_ID' with 'ID' in capitals where needed ...
|
||||||
|
if(isset($wpdb) && $wpdb->comments && false !== strpos($sql, $wpdb->comments)) {
|
||||||
|
$sql = str_replace(' comment_id ', ' comment_ID ', $sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
// MySQL treats a HAVING clause without GROUP BY like WHERE
|
||||||
|
if(false !== strpos($sql, 'HAVING') && false === strpos($sql, 'GROUP BY')) {
|
||||||
|
if(false === strpos($sql, 'WHERE')) {
|
||||||
|
$sql = str_replace('HAVING', 'WHERE', $sql);
|
||||||
|
} else {
|
||||||
|
$pattern = '/WHERE\s+(.*?)\s+HAVING\s+(.*?)(\s*(?:ORDER|LIMIT|PROCEDURE|INTO|FOR|LOCK|$))/';
|
||||||
|
$sql = preg_replace($pattern, 'WHERE ($1) AND ($2) $3', $sql);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MySQL allows integers to be used as boolean expressions
|
||||||
|
// where 0 is false and all other values are true.
|
||||||
|
//
|
||||||
|
// Although this could occur anywhere with any number, so far it
|
||||||
|
// has only been observed as top-level expressions in the WHERE
|
||||||
|
// clause and only with 0. For performance, limit current
|
||||||
|
// replacements to that.
|
||||||
|
$pattern_after_where = '(?:\s*$|\s+(GROUP|HAVING|ORDER|LIMIT|PROCEDURE|INTO|FOR|LOCK))';
|
||||||
|
$pattern = '/(WHERE\s+)0(\s+AND|\s+OR|' . $pattern_after_where . ')/';
|
||||||
|
$sql = preg_replace($pattern, '$1false$2', $sql);
|
||||||
|
|
||||||
|
$pattern = '/(AND\s+|OR\s+)0(' . $pattern_after_where . ')/';
|
||||||
|
$sql = preg_replace($pattern, '$1false$2', $sql);
|
||||||
|
|
||||||
|
// MySQL supports strings as names, PostgreSQL needs identifiers.
|
||||||
|
// Limit to after closing parenthesis to reduce false-positives
|
||||||
|
// Currently only an issue for nextgen-gallery plugin
|
||||||
|
$pattern = '/\) AS \'([^\']+)\'/';
|
||||||
|
$sql = preg_replace($pattern, ') AS "$1"', $sql);
|
||||||
|
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure the columns used in the ORDER BY clause are also present in the SELECT clause.
|
||||||
|
*
|
||||||
|
* @param string $sql Original SQL query string.
|
||||||
|
* @return string Modified SQL query string.
|
||||||
|
*/
|
||||||
|
protected function ensureOrderByInSelect(string $sql): string
|
||||||
|
{
|
||||||
|
// Extract the SELECT and ORDER BY clauses
|
||||||
|
preg_match('/SELECT\s+(.*?)\s+FROM/si', $sql, $selectMatches);
|
||||||
|
preg_match('/ORDER BY(.*?)(ASC|DESC|$)/si', $sql, $orderMatches);
|
||||||
|
preg_match('/GROUP BY(.*?)(ASC|DESC|$)/si', $sql, $groupMatches);
|
||||||
|
|
||||||
|
// If the SELECT clause is missing, return the original query
|
||||||
|
if (!$selectMatches) {
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If both ORDER BY and GROUP BY clauses are missing, return the original query
|
||||||
|
if (!$orderMatches && !$groupMatches) {
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
$selectClause = trim($selectMatches[1]);
|
||||||
|
$orderByClause = $orderMatches ? trim($orderMatches[1]) : null;
|
||||||
|
$groupClause = $groupMatches ? trim($groupMatches[1]) : null;
|
||||||
|
$modified = false;
|
||||||
|
|
||||||
|
// Check for wildcard in SELECT
|
||||||
|
if (strpos($selectClause, '*') !== false) {
|
||||||
|
return $sql; // Cannot handle wildcards, return original query
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle ORDER BY columns
|
||||||
|
if ($orderByClause) {
|
||||||
|
$orderByColumns = explode(',', $orderByClause);
|
||||||
|
foreach ($orderByColumns as $col) {
|
||||||
|
$col = trim($col);
|
||||||
|
if (strpos($selectClause, $col) === false) {
|
||||||
|
$selectClause .= ', ' . $col;
|
||||||
|
$modified = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle GROUP BY columns
|
||||||
|
if ($groupClause && !$modified) {
|
||||||
|
$groupColumns = explode(',', $groupClause);
|
||||||
|
foreach ($groupColumns as $col) {
|
||||||
|
$col = trim($col);
|
||||||
|
if (strpos($selectClause, $col) === false) {
|
||||||
|
$selectClause .= ', ' . $col;
|
||||||
|
$modified = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$modified) {
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the exact position for the replacement
|
||||||
|
$selectStartPos = strpos($sql, $selectMatches[1]);
|
||||||
|
if ($selectStartPos === false) {
|
||||||
|
return $sql; // If for some reason the exact match is not found, return the original query
|
||||||
|
}
|
||||||
|
$postgresSql = substr_replace($sql, $selectClause, $selectStartPos, strlen($selectMatches[1]));
|
||||||
|
|
||||||
|
return $postgresSql;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms a given SQL query to include a GROUP BY clause if the SELECT statement has both aggregate
|
||||||
|
* and non-aggregate columns. This function is specifically designed to work with PostgreSQL.
|
||||||
|
*
|
||||||
|
* In PostgreSQL, a query that uses aggregate functions must group by all columns in the SELECT list that
|
||||||
|
* are not part of the aggregate functions. Failing to do so results in a syntax error. This function
|
||||||
|
* automatically adds a GROUP BY clause to meet this PostgreSQL requirement when both aggregate (COUNT, SUM,
|
||||||
|
* AVG, MIN, MAX) and non-aggregate columns are present.
|
||||||
|
*
|
||||||
|
* @param string $sql The SQL query string to be transformed.
|
||||||
|
*
|
||||||
|
* @return string The transformed SQL query string with appropriate GROUP BY clause if required.
|
||||||
|
*
|
||||||
|
* @throws Exception If the SQL query cannot be parsed or modified.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* Input: SELECT COUNT(id), username FROM users;
|
||||||
|
* Output: SELECT COUNT(id), username FROM users GROUP BY username;
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected function ensureGroupByOrAggregate(string $sql): string
|
||||||
|
{
|
||||||
|
// Check for system or session variables
|
||||||
|
if (preg_match('/@@[a-zA-Z0-9_]+/', $sql)) {
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regular expression to capture main SQL components.
|
||||||
|
$regex = '/(SELECT\s+)(.*?)(\s+FROM\s+)([^ ]+)(\s+WHERE\s+.*?(?= ORDER BY | GROUP BY | LIMIT |$))?(ORDER BY.*?(?= LIMIT |$))?(LIMIT.*?$)?/is';
|
||||||
|
|
||||||
|
// Capture main SQL components using regex
|
||||||
|
if (!preg_match($regex, $sql, $matches)) {
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
$selectClause = trim($matches[2] ?? '');
|
||||||
|
$fromClause = trim($matches[4] ?? '');
|
||||||
|
$whereClause = trim($matches[5] ?? '');
|
||||||
|
$orderClause = trim($matches[6] ?? '');
|
||||||
|
$limitClause = trim($matches[7] ?? '');
|
||||||
|
|
||||||
|
if (empty($selectClause) || empty($fromClause)) {
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regular expression to match commas not within parentheses
|
||||||
|
$pattern = '/,(?![^\(]*\))/';
|
||||||
|
// Split columns using a comma, and then trim each element
|
||||||
|
$columns = array_map('trim', preg_split($pattern, $selectClause));
|
||||||
|
|
||||||
|
$aggregateColumns = [];
|
||||||
|
$nonAggregateColumns = [];
|
||||||
|
|
||||||
|
foreach ($columns as $col) {
|
||||||
|
// Check for aggregate functions in the column
|
||||||
|
if (preg_match('/(COUNT|SUM|AVG|MIN|MAX)\s*?\(/i', $col)) {
|
||||||
|
$aggregateColumns[] = $col;
|
||||||
|
} else {
|
||||||
|
$nonAggregateColumns[] = $col;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only add a GROUP BY clause if there are both aggregate and non-aggregate columns in SELECT
|
||||||
|
if (empty($aggregateColumns) || empty($nonAggregateColumns)) {
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Assemble new SQL query
|
||||||
|
$postgresSql = "SELECT $selectClause FROM $fromClause";
|
||||||
|
|
||||||
|
if (!empty($whereClause)) {
|
||||||
|
$postgresSql .= ' ' . $whereClause;
|
||||||
|
}
|
||||||
|
|
||||||
|
$groupByClause = "GROUP BY " . implode(", ", $nonAggregateColumns);
|
||||||
|
if (!empty($groupByClause)) {
|
||||||
|
$postgresSql .= ' ' . $groupByClause;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($orderClause)) {
|
||||||
|
$postgresSql .= ' ' . $orderClause;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($limitClause)) {
|
||||||
|
$postgresSql .= ' ' . $limitClause;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $postgresSql;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert MySQL LIMIT syntax to PostgreSQL LIMIT syntax
|
||||||
|
*
|
||||||
|
* @param string $sql MySQL query string
|
||||||
|
* @return string PostgreSQL query string
|
||||||
|
*/
|
||||||
|
protected function convertToPostgresLimitSyntax($sql)
|
||||||
|
{
|
||||||
|
// Use regex to find "LIMIT m, n" syntax in query
|
||||||
|
if (preg_match('/LIMIT\s+(\d+),\s*(\d+)/i', $sql, $matches)) {
|
||||||
|
$offset = $matches[1];
|
||||||
|
$limit = $matches[2];
|
||||||
|
|
||||||
|
// Replace MySQL LIMIT syntax with PostgreSQL LIMIT syntax
|
||||||
|
$postgresLimitSyntax = "LIMIT $limit OFFSET $offset";
|
||||||
|
$postgresSql = preg_replace('/LIMIT\s+\d+,\s*\d+/i', $postgresLimitSyntax, $sql);
|
||||||
|
|
||||||
|
return $postgresSql;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return original query if no MySQL LIMIT syntax is found
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method is specifically to handle should_suggest_persistent_object_cache in wp site health
|
||||||
|
protected function postgresTableSizeRewrite($schema = 'public')
|
||||||
|
{
|
||||||
|
|
||||||
|
$sql = <<<SQL
|
||||||
|
SELECT
|
||||||
|
C.relname AS "table",
|
||||||
|
S.n_live_tup AS "rows",
|
||||||
|
pg_total_relation_size(C.oid) AS "bytes"
|
||||||
|
FROM
|
||||||
|
pg_class C
|
||||||
|
LEFT JOIN
|
||||||
|
pg_namespace N ON (N.oid = C.relnamespace)
|
||||||
|
INNER JOIN
|
||||||
|
pg_stat_user_tables S ON (S.relid = C.oid)
|
||||||
|
WHERE
|
||||||
|
N.nspname = '$schema' AND
|
||||||
|
C.relname IN ('wp_comments','wp_options','wp_posts','wp_terms','wp_users')
|
||||||
|
GROUP BY
|
||||||
|
C.relname, pg_total_relation_size(C.oid), S.n_live_tup;
|
||||||
|
SQL;
|
||||||
|
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class SetNamesSQLRewriter extends AbstractSQLRewriter
|
||||||
|
{
|
||||||
|
public function rewrite(): string
|
||||||
|
{
|
||||||
|
return "SET NAMES 'utf8'";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class ShowFullColumnsSQLRewriter extends AbstractSQLRewriter
|
||||||
|
{
|
||||||
|
public function rewrite(): string
|
||||||
|
{
|
||||||
|
$sql = $this->original();
|
||||||
|
$table = $this->extractTableNameFromShowColumns($sql);
|
||||||
|
return $this->generatePostgresShowColumns($table);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts table name from a "SHOW FULL COLUMNS" SQL statement.
|
||||||
|
*
|
||||||
|
* @param string $sql The SQL statement
|
||||||
|
* @return string|null The table name if found, or null otherwise
|
||||||
|
*/
|
||||||
|
protected function extractTableNameFromShowColumns($sql)
|
||||||
|
{
|
||||||
|
$pattern = "/SHOW FULL COLUMNS FROM ['\"`]?([^'\"`]+)['\"`]?/i";
|
||||||
|
if (preg_match($pattern, $sql, $matches)) {
|
||||||
|
return $matches[1];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a PostgreSQL-compatible SQL query to mimic MySQL's "SHOW FULL COLUMNS".
|
||||||
|
*
|
||||||
|
* @param string $tableName The table name
|
||||||
|
* @param string $schema The schema name
|
||||||
|
* @return string The generated SQL query
|
||||||
|
*/
|
||||||
|
public function generatePostgresShowColumns($tableName, $schema = "public")
|
||||||
|
{
|
||||||
|
$sql = <<<SQL
|
||||||
|
SELECT
|
||||||
|
a.attname AS "Field",
|
||||||
|
pg_catalog.format_type(a.atttypid, a.atttypmod) AS "Type",
|
||||||
|
(CASE
|
||||||
|
WHEN a.attnotnull THEN 'NO'
|
||||||
|
ELSE 'YES'
|
||||||
|
END) AS "Null",
|
||||||
|
(CASE
|
||||||
|
WHEN i.indisprimary THEN 'PRI'
|
||||||
|
WHEN i.indisunique THEN 'UNI'
|
||||||
|
ELSE ''
|
||||||
|
END) AS "Key",
|
||||||
|
pg_catalog.pg_get_expr(ad.adbin, ad.adrelid) AS "Default",
|
||||||
|
'' AS "Extra",
|
||||||
|
'select,insert,update,references' AS "Privileges",
|
||||||
|
d.description AS "Comment"
|
||||||
|
FROM
|
||||||
|
pg_catalog.pg_attribute a
|
||||||
|
LEFT JOIN pg_catalog.pg_description d ON (a.attrelid = d.objoid AND a.attnum = d.objsubid)
|
||||||
|
LEFT JOIN pg_catalog.pg_attrdef ad ON (a.attrelid = ad.adrelid AND a.attnum = ad.adnum)
|
||||||
|
LEFT JOIN pg_catalog.pg_index i ON (a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey))
|
||||||
|
WHERE
|
||||||
|
a.attnum > 0
|
||||||
|
AND NOT a.attisdropped
|
||||||
|
AND a.attrelid = (
|
||||||
|
SELECT c.oid
|
||||||
|
FROM pg_catalog.pg_class c
|
||||||
|
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
||||||
|
WHERE c.relname = '$tableName'
|
||||||
|
AND n.nspname = '$schema'
|
||||||
|
)
|
||||||
|
ORDER BY
|
||||||
|
a.attnum;
|
||||||
|
SQL;
|
||||||
|
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class ShowIndexSQLRewriter extends AbstractSQLRewriter
|
||||||
|
{
|
||||||
|
public function rewrite(): string
|
||||||
|
{
|
||||||
|
$sql = $this->original();
|
||||||
|
$table = $this->extractTableNameFromShowIndex($sql);
|
||||||
|
return $this->generatePostgresShowIndexFrom($table);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts table name from a "SHOW FULL COLUMNS" SQL statement.
|
||||||
|
*
|
||||||
|
* @param string $sql The SQL statement
|
||||||
|
* @return string|null The table name if found, or null otherwise
|
||||||
|
*/
|
||||||
|
protected function extractVariableName($sql)
|
||||||
|
{
|
||||||
|
$pattern = "/SHOW INDEX FROM ['\"`]?([^'\"`]+)['\"`]?/i";
|
||||||
|
if (preg_match($pattern, $sql, $matches)) {
|
||||||
|
return $matches[1];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a PostgreSQL-compatible SQL query to mimic MySQL's "SHOW INDEX FROM".
|
||||||
|
*
|
||||||
|
* @param string $tableName The table name
|
||||||
|
* @return string The generated SQL query
|
||||||
|
*/
|
||||||
|
public function generatePostgresShowIndexFrom($tableName)
|
||||||
|
{
|
||||||
|
$sql = <<<SQL
|
||||||
|
SELECT bc.relname AS "Table",
|
||||||
|
CASE WHEN i.indisunique THEN '0' ELSE '1' END AS "Non_unique",
|
||||||
|
CASE WHEN i.indisprimary THEN 'PRIMARY' WHEN bc.relname LIKE '%usermeta' AND ic.relname = 'umeta_key'
|
||||||
|
THEN 'meta_key' ELSE REPLACE( ic.relname, '' . $table . '_', '') END AS "Key_name",
|
||||||
|
a.attname AS "Column_name",
|
||||||
|
NULL AS "Sub_part"
|
||||||
|
FROM pg_class bc, pg_class ic, pg_index i, pg_attribute a
|
||||||
|
WHERE bc.oid = i.indrelid
|
||||||
|
AND ic.oid = i.indexrelid
|
||||||
|
AND (i.indkey[0] = a.attnum OR i.indkey[1] = a.attnum OR i.indkey[2] = a.attnum OR i.indkey[3] = a.attnum OR i.indkey[4] = a.attnum OR i.indkey[5] = a.attnum OR i.indkey[6] = a.attnum OR i.indkey[7] = a.attnum)
|
||||||
|
AND a.attrelid = bc.oid
|
||||||
|
AND bc.relname = '' . $tableName . ''
|
||||||
|
ORDER BY "Key_name", CASE a.attnum
|
||||||
|
WHEN i.indkey[0] THEN 0
|
||||||
|
WHEN i.indkey[1] THEN 1
|
||||||
|
WHEN i.indkey[2] THEN 2
|
||||||
|
WHEN i.indkey[3] THEN 3
|
||||||
|
WHEN i.indkey[4] THEN 4
|
||||||
|
WHEN i.indkey[5] THEN 5
|
||||||
|
WHEN i.indkey[6] THEN 6
|
||||||
|
WHEN i.indkey[7] THEN 7
|
||||||
|
END
|
||||||
|
SQL;
|
||||||
|
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class ShowTableStatusSQLRewriter extends AbstractSQLRewriter
|
||||||
|
{
|
||||||
|
public function rewrite(): string
|
||||||
|
{
|
||||||
|
$sql = $this->original();
|
||||||
|
return $this->generatePostgresShowTableStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a PostgreSQL-compatible SQL query to mimic MySQL's "SHOW TABLE STATUS".
|
||||||
|
*
|
||||||
|
* @return string The generated SQL query
|
||||||
|
*/
|
||||||
|
public function generatePostgresShowTableStatus($schema = "public")
|
||||||
|
{
|
||||||
|
$sql = <<<SQL
|
||||||
|
SELECT
|
||||||
|
'Postgres' AS Engine,
|
||||||
|
cls.relname AS TableName,
|
||||||
|
NULL AS Version,
|
||||||
|
NULL AS Row_format,
|
||||||
|
cls.reltuples AS Rows,
|
||||||
|
NULL AS Avg_row_length,
|
||||||
|
pg_size_pretty(pg_relation_size(cls.oid)) AS Data_length,
|
||||||
|
NULL AS Max_data_length,
|
||||||
|
pg_size_pretty(pg_indexes_size(cls.oid)) AS Index_length,
|
||||||
|
NULL AS Data_free,
|
||||||
|
NULL AS Auto_increment,
|
||||||
|
NULL AS Create_time,
|
||||||
|
NULL AS Update_time,
|
||||||
|
NULL AS Check_time,
|
||||||
|
'UTF8' AS Table_collation,
|
||||||
|
NULL AS Checksum,
|
||||||
|
NULL AS Create_options,
|
||||||
|
obj_description(cls.oid) AS Comment
|
||||||
|
FROM
|
||||||
|
pg_class cls
|
||||||
|
JOIN
|
||||||
|
pg_namespace nsp ON cls.relnamespace = nsp.oid
|
||||||
|
WHERE
|
||||||
|
cls.relkind = 'r'
|
||||||
|
AND nsp.nspname NOT LIKE 'pg_%'
|
||||||
|
AND nsp.nspname != 'information_schema'
|
||||||
|
AND nsp.nspname = '$schema'
|
||||||
|
ORDER BY
|
||||||
|
cls.relname ASC;
|
||||||
|
SQL;
|
||||||
|
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class ShowTablesSQLRewriter extends AbstractSQLRewriter
|
||||||
|
{
|
||||||
|
public function rewrite(): string
|
||||||
|
{
|
||||||
|
$schema = "public";
|
||||||
|
return 'SELECT tablename FROM pg_tables WHERE schemaname = \'$schema\';';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class ShowVariablesSQLRewriter extends AbstractSQLRewriter
|
||||||
|
{
|
||||||
|
public function rewrite(): string
|
||||||
|
{
|
||||||
|
$sql = $this->original();
|
||||||
|
$variableName = $this->extractVariableName($sql);
|
||||||
|
return $this->generatePostgres($sql, $variableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts Variable name from a "SHOW VARIABLES LIKE " SQL statement.
|
||||||
|
*
|
||||||
|
* @param string $sql The SQL statement
|
||||||
|
* @return string|null The table name if found, or null otherwise
|
||||||
|
*/
|
||||||
|
protected function extractVariableName($sql)
|
||||||
|
{
|
||||||
|
$pattern = "/SHOW VARIABLES LIKE ['\"`]?([^'\"`]+)['\"`]?/i";
|
||||||
|
if (preg_match($pattern, $sql, $matches)) {
|
||||||
|
return $matches[1];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a PostgreSQL-compatible SQL query to mimic MySQL's "SHOW VARIABLES".
|
||||||
|
*
|
||||||
|
* @param string $tableName The table name
|
||||||
|
* @return string The generated SQL query
|
||||||
|
*/
|
||||||
|
public function generatePostgres($sql, $variableName)
|
||||||
|
{
|
||||||
|
if ($variableName == "sql_mode") {
|
||||||
|
// Act like MySQL default configuration, where sql_mode is ""
|
||||||
|
return "SELECT '$variableName' AS \"Variable_name\", '' AS \"Value\";";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($variableName == "max_allowed_packet") {
|
||||||
|
// Act like 1GB packet size, in practice this limit doesn't actually exist for postgres, we just want to fool WP
|
||||||
|
return "SELECT '$variableName' AS \"Variable_name\", '1073741824' AS \"Value\";";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "SELECT name as \"Variable_name\", setting as \"Value\" FROM pg_settings WHERE name = '$variableName';";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class UpdateSQLRewriter extends AbstractSQLRewriter
|
||||||
|
{
|
||||||
|
public function rewrite(): string
|
||||||
|
{
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$sql = $this->original();
|
||||||
|
|
||||||
|
$pattern = '/LIMIT[ ]+\d+/';
|
||||||
|
$sql = preg_replace($pattern, '', $sql);
|
||||||
|
|
||||||
|
// Fix update wp_options
|
||||||
|
$pattern = "/UPDATE `wp_options` SET `option_value` = NULL WHERE `option_name` = '(.+)'/";
|
||||||
|
$match = "UPDATE `wp_options` SET `option_value` = '' WHERE `option_name` = '$1'";
|
||||||
|
$sql = preg_replace($pattern, $match, $sql);
|
||||||
|
|
||||||
|
// For correct bactick removal
|
||||||
|
$pattern = '/[ ]*`([^` ]+)`[ ]*=/';
|
||||||
|
$sql = preg_replace($pattern, ' $1 =', $sql);
|
||||||
|
|
||||||
|
// Those are used when we need to set the date to now() in gmt time
|
||||||
|
$sql = str_replace("'0000-00-00 00:00:00'", 'now() AT TIME ZONE \'gmt\'', $sql);
|
||||||
|
|
||||||
|
// For correct ID quoting
|
||||||
|
$pattern = '/(,|\s)[ ]*([^ \']*ID[^ \']*)[ ]*=/';
|
||||||
|
$sql = preg_replace($pattern, '$1 "$2" =', $sql);
|
||||||
|
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
-6
@@ -15,15 +15,16 @@ spec:
|
|||||||
spec:
|
spec:
|
||||||
initContainers:
|
initContainers:
|
||||||
- name: init-pg4wp
|
- name: init-pg4wp
|
||||||
image: hub.hamdocker.ir/library/busybox:latest
|
image: hub.hamdocker.ir/library/alpine:latest
|
||||||
command:
|
command:
|
||||||
- sh
|
- sh
|
||||||
- -c
|
- -c
|
||||||
- |
|
- |
|
||||||
wget -O /tmp/pg4wp.zip https://github.com/kevinoid/postgresql-for-wordpress/archive/refs/heads/master.zip
|
apk add --no-cache git
|
||||||
unzip /tmp/pg4wp.zip -d /var/www/html/wp-content/plugins/
|
git clone http://gitea-http.gitea.svc.cluster.local:3000/gitea_admin/apps.git /tmp/repo
|
||||||
mv /var/www/html/wp-content/plugins/postgresql-for-wordpress-master /var/www/html/wp-content/plugins/pg4wp
|
mkdir -p /var/www/html/wp-content/plugins/pg4wp
|
||||||
cp /var/www/html/wp-content/plugins/pg4wp/db.php /var/www/html/wp-content/db.php
|
cp -r /tmp/repo/pg4wp/* /var/www/html/wp-content/plugins/pg4wp/
|
||||||
|
cp /tmp/repo/pg4wp/db.php /var/www/html/wp-content/db.php
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: wordpress-data
|
- name: wordpress-data
|
||||||
mountPath: /var/www/html
|
mountPath: /var/www/html
|
||||||
@@ -83,4 +84,4 @@ spec:
|
|||||||
service:
|
service:
|
||||||
name: wordpress
|
name: wordpress
|
||||||
port:
|
port:
|
||||||
number: 80
|
number: 80
|
||||||
Reference in new issue
Block a user