Polymorphic refactor + improvements to select sql rewriting
This commit is contained in:
File diff suppressed because it is too large
Load Diff
204
pg4wp/driver_pgsql_rewrite.php
Normal file
204
pg4wp/driver_pgsql_rewrite.php
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
<?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|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 = loadInstallFunctions($sql, $logto);
|
||||||
|
$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 catching
|
||||||
|
if($logto == 'INSERT') {
|
||||||
|
$pattern = '/INSERT INTO (\w+)\s+\([ a-zA-Z_"]+/';
|
||||||
|
preg_match($pattern, $sql, $matches);
|
||||||
|
$GLOBALS['pg4wp_ins_table'] = $matches[1];
|
||||||
|
$match_list = explode(' ', $matches[0]);
|
||||||
|
if($GLOBALS['pg4wp_ins_table']) {
|
||||||
|
$GLOBALS['pg4wp_ins_field'] = trim($match_list[3], ' () ');
|
||||||
|
if(!$GLOBALS['pg4wp_ins_field']) {
|
||||||
|
$GLOBALS['pg4wp_ins_field'] = trim($match_list[4], ' () ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load upgrade and install functions as required.
|
||||||
|
*
|
||||||
|
* @param string $sql SQL query string
|
||||||
|
* @param string $logto Logging type
|
||||||
|
* @return string Modified SQL query string
|
||||||
|
*/
|
||||||
|
function loadInstallFunctions($sql, &$logto)
|
||||||
|
{
|
||||||
|
$begin = strtoupper(substr($sql, 0, 3));
|
||||||
|
$search = array('SHO', 'ALT', 'DES', 'CRE', 'DRO');
|
||||||
|
if (in_array($begin, $search)) {
|
||||||
|
require_once(PG4WP_ROOT . '/driver_pgsql_install.php');
|
||||||
|
$sql = pg4wp_installing($sql, $logto);
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
26
pg4wp/rewriters/AbstractSQLRewriter.php
Normal file
26
pg4wp/rewriters/AbstractSQLRewriter.php
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
60
pg4wp/rewriters/DeleteSQLRewriter.php
Normal file
60
pg4wp/rewriters/DeleteSQLRewriter.php
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<?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);
|
||||||
|
|
||||||
|
// 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
|
||||||
|
elseif(0 === strpos($sql, 'DELETE a, b FROM wp_options a, wp_options b')) {
|
||||||
|
$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 wp_options a USING wp_options b WHERE ' .
|
||||||
|
'(' . $where . ') OR (' . $where2 . ');';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rewrite _transient_timeout multi-table delete query
|
||||||
|
elseif(0 === strpos($sql, 'DELETE a, b FROM wp_sitemeta a, wp_sitemeta b')) {
|
||||||
|
$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.'));
|
||||||
|
$sql = 'DELETE FROM wp_sitemeta a USING wp_sitemeta b WHERE ' .
|
||||||
|
'(' . $where . ') OR (' . $where2 . ');';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
124
pg4wp/rewriters/InsertSQLRewriter.php
Normal file
124
pg4wp/rewriters/InsertSQLRewriter.php
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class InsertSQLRewriter extends AbstractSQLRewriter
|
||||||
|
{
|
||||||
|
public function rewrite(): string
|
||||||
|
{
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$sql = $this->original();
|
||||||
|
|
||||||
|
$sql = str_replace('(0,', "('0',", $sql);
|
||||||
|
$sql = str_replace('(1,', "('1',", $sql);
|
||||||
|
|
||||||
|
// Fix inserts into wp_categories
|
||||||
|
if(false !== strpos($sql, 'INSERT INTO ' . $wpdb->categories)) {
|
||||||
|
$sql = str_replace('"cat_ID",', '', $sql);
|
||||||
|
$sql = str_replace("VALUES ('0',", "VALUES(", $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);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
}
|
10
pg4wp/rewriters/OptimizeTableSQLRewriter.php
Normal file
10
pg4wp/rewriters/OptimizeTableSQLRewriter.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class OptimizeTableSQLRewriter extends AbstractSQLRewriter
|
||||||
|
{
|
||||||
|
public function rewrite(): string
|
||||||
|
{
|
||||||
|
$sql = $this->original();
|
||||||
|
return str_replace('OPTIMIZE TABLE', 'VACUUM', $sql);
|
||||||
|
}
|
||||||
|
}
|
324
pg4wp/rewriters/SelectSQLRewriter.php
Normal file
324
pg4wp/rewriters/SelectSQLRewriter.php
Normal file
@ -0,0 +1,324 @@
|
|||||||
|
<?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 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = $this->ensureOrderByInSelect($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);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
if(isset($wpdb)) {
|
||||||
|
$sql = str_replace('GROUP BY ' . $wpdb->prefix . 'posts.ID', '', $sql);
|
||||||
|
}
|
||||||
|
$sql = str_replace("!= ''", '<> 0', $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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = $matches[2] ?? '';
|
||||||
|
$fromClause = $matches[4] ?? '';
|
||||||
|
$whereClause = $matches[5] ?? '';
|
||||||
|
$orderClause = $matches[6] ?? '';
|
||||||
|
$limitClause = $matches[7] ?? '';
|
||||||
|
|
||||||
|
if (empty($selectClause) || empty($fromClause)) {
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
$columns = explode(',', $selectClause);
|
||||||
|
$aggregateColumns = [];
|
||||||
|
$nonAggregateColumns = [];
|
||||||
|
|
||||||
|
foreach ($columns as $col) {
|
||||||
|
$col = trim($col);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
$groupByClause = "GROUP BY " . implode(", ", $nonAggregateColumns);
|
||||||
|
|
||||||
|
// Assemble new SQL query
|
||||||
|
$postgresSql = "SELECT $selectClause FROM $fromClause";
|
||||||
|
|
||||||
|
if (!empty(trim($whereClause))) {
|
||||||
|
$postgresSql .= " $whereClause";
|
||||||
|
}
|
||||||
|
|
||||||
|
$postgresSql .= " $groupByClause";
|
||||||
|
|
||||||
|
if (!empty(trim($orderClause))) {
|
||||||
|
$postgresSql .= " $orderClause";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty(trim($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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
9
pg4wp/rewriters/SetNamesSQLRewriter.php
Normal file
9
pg4wp/rewriters/SetNamesSQLRewriter.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class SetNamesSQLRewriter extends AbstractSQLRewriter
|
||||||
|
{
|
||||||
|
public function rewrite(): string
|
||||||
|
{
|
||||||
|
return "SET NAMES 'utf8'";
|
||||||
|
}
|
||||||
|
}
|
71
pg4wp/rewriters/ShowFullColumnsSQLRewriter.php
Normal file
71
pg4wp/rewriters/ShowFullColumnsSQLRewriter.php
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<?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
|
||||||
|
* @return string The generated SQL query
|
||||||
|
*/
|
||||||
|
function generatePostgresShowColumns($tableName) {
|
||||||
|
$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 = 'public'
|
||||||
|
)
|
||||||
|
ORDER BY
|
||||||
|
a.attnum;
|
||||||
|
SQL;
|
||||||
|
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
}
|
9
pg4wp/rewriters/ShowTablesSQLRewriter.php
Normal file
9
pg4wp/rewriters/ShowTablesSQLRewriter.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class ShowTablesSQLRewriter extends AbstractSQLRewriter
|
||||||
|
{
|
||||||
|
public function rewrite(): string
|
||||||
|
{
|
||||||
|
return 'SELECT tablename FROM pg_tables WHERE schemaname = \'public\';';
|
||||||
|
}
|
||||||
|
}
|
32
pg4wp/rewriters/UpdateSQLRewriter.php
Normal file
32
pg4wp/rewriters/UpdateSQLRewriter.php
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user