Files
postgresql-for-wordpress/pg4wp/driver_pgsql_rewrite.php
2023-11-07 15:26:03 -08:00

194 lines
6.1 KiB
PHP

<?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|UPDATE|DELETE|DESCRIBE|ALTER TABLE|CREATE TABLE|DROP TABLE|SHOW INDEX|SHOW VARIABLES|SHOW TABLES|OPTIMIZE TABLE|SET NAMES|SHOW FULL COLUMNS)\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 . '_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;
}