Skip to content
This repository was archived by the owner on Jun 2, 2025. It is now read-only.

Commit 5004c07

Browse files
committed
Fix and improve default value formatting
1 parent 9b21ab3 commit 5004c07

File tree

3 files changed

+70
-9
lines changed

3 files changed

+70
-9
lines changed

tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,47 @@ public function testDefaultValues(): void {
265265
);
266266
}
267267

268+
public function testDefaultValueEscaping(): void {
269+
//$this->assertSame("abc". chr( 8 ) . "xyz", "" );
270+
$this->engine->get_connection()->query(
271+
"
272+
CREATE TABLE t (
273+
col1 text DEFAULT 'abc''xyz',
274+
col2 text DEFAULT 'abc\"xyz',
275+
col3 text DEFAULT 'abc`xyz',
276+
col4 text DEFAULT 'abc\\xyz',
277+
col5 text DEFAULT 'abc\nxyz',
278+
col6 text DEFAULT 'abc\rxyz',
279+
col7 text DEFAULT 'abc\txyz',
280+
col8 text DEFAULT 'abc" . chr( 8 ) . "xyz', -- backspace
281+
col9 text DEFAULT 'abc" . chr( 26 ) . "xyz' -- control-Z
282+
)
283+
"
284+
);
285+
286+
$this->reconstructor->ensure_correct_information_schema();
287+
$result = $this->assertQuery( 'SHOW CREATE TABLE t' );
288+
$this->assertSame(
289+
implode(
290+
"\n",
291+
array(
292+
'CREATE TABLE `t` (',
293+
" `col1` varchar(65535) DEFAULT 'abc''xyz',",
294+
" `col2` varchar(65535) DEFAULT 'abc\"xyz',",
295+
" `col3` varchar(65535) DEFAULT 'abc`xyz',",
296+
" `col4` varchar(65535) DEFAULT 'abc\\\\xyz',",
297+
" `col5` varchar(65535) DEFAULT 'abc\\nxyz',",
298+
" `col6` varchar(65535) DEFAULT 'abc\\rxyz',",
299+
" `col7` varchar(65535) DEFAULT 'abc xyz',", // tab is preserved
300+
" `col8` varchar(65535) DEFAULT 'abc" . chr( 8 ) . "xyz',", // backspace is preserved
301+
" `col9` varchar(65535) DEFAULT 'abc" . chr( 26 ) . "xyz'", // control-Z is preserved
302+
') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci',
303+
)
304+
),
305+
$result[0]->{'Create Table'}
306+
);
307+
}
308+
268309
private function assertQuery( $sql ) {
269310
$retval = $this->engine->query( $sql );
270311
$this->assertNotFalse( $retval );

wp-includes/sqlite-ast/class-wp-sqlite-information-schema-builder.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1867,8 +1867,8 @@ private function get_value( WP_Parser_Node $node ): string {
18671867
} elseif ( WP_MySQL_Lexer::SINGLE_QUOTED_TEXT === $child->id ) {
18681868
$value = $child->get_value();
18691869
$value = substr( $value, 1, -1 );
1870-
$value = str_replace( '\"', '"', $value );
1871-
$value = str_replace( '""', '"', $value );
1870+
$value = str_replace( "\'", "'", $value );
1871+
$value = str_replace( "''", "'", $value );
18721872
} elseif ( WP_MySQL_Lexer::DOUBLE_QUOTED_TEXT === $child->id ) {
18731873
$value = $child->get_value();
18741874
$value = substr( $value, 1, -1 );

wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -495,10 +495,12 @@ private function generate_column_default( string $mysql_type, ?string $default_v
495495
// Quoted string literal. E.g.: 'abc', "abc", `abc`
496496
$first_byte = $default_value[0] ?? null;
497497
if ( '"' === $first_byte || "'" === $first_byte || '`' === $first_byte ) {
498-
return $this->escape_mysql_string_literal( substr( $default_value, 1, -1 ) );
498+
$value = substr( $default_value, 1, -1 );
499+
$value = str_replace( $first_byte . $first_byte, $first_byte, $value );
500+
return $this->format_mysql_string_literal( $value );
499501
}
500502

501-
// Normalize the default value to for easier comparison.
503+
// Normalize the default value for easier comparison.
502504
$uppercase_default_value = strtoupper( $default_value );
503505

504506
// NULL, TRUE, FALSE.
@@ -542,7 +544,7 @@ private function generate_column_default( string $mysql_type, ?string $default_v
542544
}
543545

544546
// Unquoted string literal. E.g.: abc
545-
return $this->escape_mysql_string_literal( $default_value );
547+
return $this->format_mysql_string_literal( $default_value );
546548
}
547549

548550
/**
@@ -642,14 +644,32 @@ private function get_mysql_column_type( string $column_type ): string {
642644
}
643645

644646
/**
645-
* Escape a string literal for MySQL DEFAULT values.
647+
* Format a MySQL string literal for output in a SHOW statement.
648+
*
649+
* We expect UTF-8 strings coming from SQLite. The only characters that need
650+
* to be escaped in a single-quoted string for a UTF-8 MySQL dump are ' and \.
651+
*
652+
* MySQL's SHOW command also escapes \0 (for the mysql CLI), \n (for logs and
653+
* readability), and \r (for readability). Let's these characters as well.
654+
*
655+
* See:
656+
* - https://github.com/mysql/mysql-server/blob/ff05628a530696bc6851ba6540ac250c7a059aa7/sql/sql_show.cc#L1799
657+
* - https://github.com/mysql/mysql-server/blob/ff05628a530696bc6851ba6540ac250c7a059aa7/sql/table.cc#L3525
658+
*
659+
* Unfortunately, SQLite doesn't validate the UTF-8 encoding of strings, so
660+
* other byte sequences may come from SQLite as well.
661+
*
662+
* See: https://www.sqlite.org/invalidutf.html
663+
*
664+
* TODO: We may consider stripping invalid UTF-8 characters, but that's likely
665+
* to be a bigger project, as these can appear also in other contexts.
646666
*
647667
* @param string $literal The string literal to escape.
648668
* @return string The escaped string literal.
649669
*/
650-
private function escape_mysql_string_literal( string $literal ): string {
651-
// See: https://www.php.net/manual/en/mysqli.real-escape-string.php
652-
return "'" . addcslashes( $literal, "\0\n\r'\"\Z" ) . "'";
670+
private function format_mysql_string_literal( string $literal ): string {
671+
$value = addcslashes( $literal, "\0\n\r\\" );
672+
return "'" . str_replace( "'", "''", $value ) . "'";
653673
}
654674

655675
/**

0 commit comments

Comments
 (0)