Skip to content

Commit

Permalink
Merge pull request #93 from PostgreSQL-For-Wordpress/rewrite-fixes
Browse files Browse the repository at this point in the history
Improve string replacements
  • Loading branch information
mattbucci authored Feb 29, 2024
2 parents 226ed1f + 28fb016 commit 6df70e3
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 10 deletions.
66 changes: 63 additions & 3 deletions pg4wp/rewriters/AlterTableSQLRewriter.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 (.+)|)/';
Expand Down Expand Up @@ -164,7 +200,7 @@ private function rewriteDropIndex(string $sql): string
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;
Expand All @@ -176,7 +212,7 @@ private function rewriteDropPrimaryKey(string $sql): string

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;
Expand Down Expand Up @@ -227,4 +263,28 @@ private function rewrite_numeric_type($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;
}
}
25 changes: 25 additions & 0 deletions pg4wp/rewriters/CreateTableSQLRewriter.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public function rewrite(): string
);

$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+\(((?:[^()]|\([^)]*\))*)\)/';
Expand Down Expand Up @@ -133,4 +134,28 @@ private function rewrite_numeric_type($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;
}
}
1 change: 0 additions & 1 deletion pg4wp/rewriters/SelectSQLRewriter.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ public function rewrite(): string
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);
Expand Down
5 changes: 4 additions & 1 deletion phpunit.xml
Original file line number Diff line number Diff line change
@@ -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>
Expand Down
138 changes: 133 additions & 5 deletions tests/rewriteTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ public function test_it_handles_keys()
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)
);
Expand Down Expand Up @@ -184,7 +184,7 @@ public function test_it_does_not_remove_if_not_exists()
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)
);
Expand Down Expand Up @@ -222,8 +222,8 @@ public function test_it_removes_character_sets()
"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),
Expand Down Expand Up @@ -264,7 +264,7 @@ public function test_it_handles_multiple_keys()
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)
Expand Down Expand Up @@ -479,8 +479,136 @@ public function test_it_can_handle_insert_sql_containing_nested_parathesis_with_
$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
{
Expand Down

0 comments on commit 6df70e3

Please sign in to comment.