Merge pull request #93 from PostgreSQL-For-Wordpress/rewrite-fixes

Improve string replacements
This commit is contained in:
Matthew Bucci
2024-02-29 00:04:36 -08:00
committed by GitHub
5 changed files with 225 additions and 10 deletions

View File

@ -28,31 +28,67 @@ class AlterTableSQLRewriter extends AbstractSQLRewriter
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 (.+)|)/';
@ -164,7 +200,7 @@ class AlterTableSQLRewriter extends AbstractSQLRewriter
if(1 === preg_match($pattern, $sql, $matches)) {
$table = $matches[1];
$index = $matches[2];
$sql = "DROP INDEX ${table}_${index}";
$sql = "DROP INDEX {$table}_{$index}";
}
return $sql;
@ -176,7 +212,7 @@ class AlterTableSQLRewriter extends AbstractSQLRewriter
if(1 === preg_match($pattern, $sql, $matches)) {
$table = $matches[1];
$sql = "ALTER TABLE ${table} DROP CONSTRAINT ${table}_pkey";
$sql = "ALTER TABLE {$table} DROP CONSTRAINT {$table}_pkey";
}
return $sql;
@ -227,4 +263,28 @@ class AlterTableSQLRewriter extends AbstractSQLRewriter
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;
}
}

View File

@ -55,6 +55,7 @@ class CreateTableSQLRewriter extends AbstractSQLRewriter
);
$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+\(((?:[^()]|\([^)]*\))*)\)/';
@ -133,4 +134,28 @@ class CreateTableSQLRewriter extends AbstractSQLRewriter
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;
}
}

View File

@ -123,7 +123,6 @@ class SelectSQLRewriter extends AbstractSQLRewriter
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);

View File

@ -1,9 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
displayDetailsOnTestsThatTriggerDeprecations="true"
displayDetailsOnTestsThatTriggerErrors="true"
displayDetailsOnTestsThatTriggerNotices="true"
displayDetailsOnTestsThatTriggerWarnings="true"
colors="true">
<testsuites>
<testsuites>
<testsuite name="tests">
<directory>tests</directory>
</testsuite>

View File

@ -114,7 +114,7 @@ final class rewriteTest extends TestCase
CREATE TABLE IF NOT EXISTS wp_itsec_dashboard_lockouts (
id serial,
ip varchar(40),
time timestamp NOT NULL,
"time" timestamp NOT NULL,
count int NOT NULL,
PRIMARY KEY (id)
);
@ -184,7 +184,7 @@ final class rewriteTest extends TestCase
CREATE TABLE IF NOT EXISTS wp_itsec_dashboard_lockouts (
id serial,
ip varchar(40),
time timestamp NOT NULL,
"time" timestamp NOT NULL,
count int NOT NULL,
PRIMARY KEY (id)
);
@ -222,8 +222,8 @@ final class rewriteTest extends TestCase
"ID" bigserial,
ip varchar(60) NOT NULL,
created int,
timestamp int NOT NULL,
date timestamp NOT NULL,
"timestamp" int NOT NULL,
"date" timestamp NOT NULL,
referred text NOT NULL,
agent varchar(255) NOT NULL,
platform varchar(255),
@ -264,7 +264,7 @@ final class rewriteTest extends TestCase
page_id bigserial,
uri varchar(190) NOT NULL,
type varchar(180) NOT NULL,
date date NOT NULL,
"date" date NOT NULL,
count int NOT NULL,
id int NOT NULL,
PRIMARY KEY (page_id)
@ -479,8 +479,136 @@ final class rewriteTest extends TestCase
$this->assertSame(trim($expected), trim($postgresql));
}
public function test_it_doesnt_rewrite_when_it_doesnt_need_to()
{
$sql = <<<SQL
SELECT p.ID FROM wp_posts p
WHERE post_type='scheduled-action'
AND p.post_status IN ('pending')
AND p.post_modified_gmt <= '2023-11-27 14:23:34'
AND p.post_password != '' ORDER BY p.post_date_gmt ASC LIMIT 0, 20
SQL;
$expected = <<<SQL
SELECT p."ID" , p.post_date_gmt FROM wp_posts p
WHERE post_type='scheduled-action'
AND p.post_status IN ('pending')
AND p.post_modified_gmt <= '2023-11-27 14:23:34'
AND p.post_password != '' ORDER BY p.post_date_gmt ASC LIMIT 20 OFFSET 0
SQL;
$postgresql = pg4wp_rewrite($sql);
$this->assertSame(trim($expected), trim($postgresql));
}
public function test_it_handles_alter_tables_with_indexes()
{
$sql = <<<SQL
ALTER TABLE wp_e_events ADD INDEX `created_at_index` (`created_at`)
SQL;
$expected = <<<SQL
CREATE INDEX IF NOT EXISTS wp_e_events_created_at_index ON wp_e_events (created_at)
SQL;
$postgresql = pg4wp_rewrite($sql);
$this->assertSame(trim($expected), trim($postgresql));
}
public function test_it_handles_alter_tables_with_unique_indexes()
{
$sql = <<<SQL
ALTER TABLE wp_e_events ADD UNIQUE INDEX `created_at_index` (`created_at`)
SQL;
$expected = <<<SQL
CREATE UNIQUE INDEX IF NOT EXISTS wp_e_events_created_at_index ON wp_e_events (created_at)
SQL;
$postgresql = pg4wp_rewrite($sql);
$this->assertSame(trim($expected), trim($postgresql));
}
public function test_it_rewrites_protected_column_names()
{
$sql = <<<SQL
CREATE TABLE wp_cmplz_cookiebanners (
"ID" int NOT NULL DEFAULT nextval('wp_cmplz_cookiebanners_seq'::text),
banner_version int NOT NULL,
default int NOT NULL
);
SQL;
$expected = <<<SQL
CREATE TABLE IF NOT EXISTS wp_cmplz_cookiebanners (
"ID" int NOT NULL DEFAULT nextval('wp_cmplz_cookiebanners_seq'::text),
banner_version int NOT NULL,
"default" int NOT NULL
);
SQL;
$postgresql = pg4wp_rewrite($sql);
$this->assertSame(trim($expected), trim($postgresql));
}
public function test_it_rewrites_advanced_protected_column_names()
{
$sql = <<<SQL
CREATE TABLE wp_statistics_pages (
page_id BIGINT(20) NOT NULL AUTO_INCREMENT,
uri varchar(190) NOT NULL,
type varchar(180) NOT NULL,
date date NOT NULL,
count int(11) NOT NULL,
id int(11) NOT NULL,
UNIQUE KEY date_2 (date,uri),
KEY url (uri),
KEY date (date),
KEY id (id),
KEY `uri` (`uri`,`count`,`id`),
PRIMARY KEY (`page_id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci
SQL;
$expected = <<<SQL
CREATE TABLE IF NOT EXISTS wp_statistics_pages (
page_id bigserial,
uri varchar(190) NOT NULL,
type varchar(180) NOT NULL,
"date" date NOT NULL,
count int NOT NULL,
id int NOT NULL,
PRIMARY KEY (page_id)
);
CREATE UNIQUE INDEX IF NOT EXISTS wp_statistics_pages_date_2 ON wp_statistics_pages (date,uri);
CREATE INDEX IF NOT EXISTS wp_statistics_pages_url ON wp_statistics_pages (uri);
CREATE INDEX IF NOT EXISTS wp_statistics_pages_date ON wp_statistics_pages (date);
CREATE INDEX IF NOT EXISTS wp_statistics_pages_id ON wp_statistics_pages (id);
CREATE INDEX IF NOT EXISTS wp_statistics_pages_uri ON wp_statistics_pages (uri,count,id);
SQL;
$postgresql = pg4wp_rewrite($sql);
$this->assertSame(trim($expected), trim($postgresql));
}
public function test_it_doesnt_remove_single_quotes()
{
$sql = <<<SQL
SELECT COUNT(*) FROM wp_comments WHERE user_id = 5 AND comment_approved = '1'
SQL;
$expected = <<<SQL
SELECT COUNT(*) FROM wp_comments WHERE user_id = 5 AND comment_approved = '1'
SQL;
$postgresql = pg4wp_rewrite($sql);
$this->assertSame(trim($expected), trim($postgresql));
}
protected function setUp(): void
{