Skip to content

Commit 79c53fa

Browse files
committed
Translate MySQL CONVERT() expressions to SQLite
SQLite doesn't support the CONVERT() function. Translate its two forms: - CONVERT(expr, type) is equivalent to CAST(expr AS type). - CONVERT(expr USING charset) is a character set conversion that is a no-op in SQLite, as all text is stored as UTF-8. Fixes #344
1 parent da8a89e commit 79c53fa

4 files changed

Lines changed: 139 additions & 3 deletions

File tree

.github/workflows/wp-tests-phpunit-run.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,6 @@ const expectedFailures = [
5858
'Tests_DB_Charset::test_strip_invalid_text with data set #32',
5959
'Tests_DB_Charset::test_strip_invalid_text with data set #33',
6060
'Tests_DB_Charset::test_strip_invalid_text with data set #34',
61-
'Tests_DB_Charset::test_strip_invalid_text with data set #35',
62-
'Tests_DB_Charset::test_strip_invalid_text with data set #36',
63-
'Tests_DB_Charset::test_strip_invalid_text with data set #37',
6461
'Tests_DB_Charset::test_strip_invalid_text with data set #39',
6562
'Tests_DB_Charset::test_strip_invalid_text with data set #40',
6663
'Tests_DB_Charset::test_strip_invalid_text with data set #41',

packages/mysql-on-sqlite/src/sqlite/class-wp-pdo-mysql-on-sqlite.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4221,6 +4221,27 @@ private function translate_simple_expr_body( WP_Parser_Node $node ): string {
42214221
);
42224222
}
42234223

4224+
/**
4225+
* Translate MySQL CONVERT() expression.
4226+
*
4227+
* MySQL supports two forms of CONVERT():
4228+
* 1. CONVERT(expr, type): Equivalent to CAST(expr AS type).
4229+
* 2. CONVERT(expr USING charset): Converts the character set.
4230+
*/
4231+
if ( null !== $token && WP_MySQL_Lexer::CONVERT_SYMBOL === $token->id ) {
4232+
$expr = $this->translate( $node->get_first_child_node( 'expr' ) );
4233+
$cast_type = $node->get_first_child_node( 'castType' );
4234+
4235+
if ( null !== $cast_type ) {
4236+
// CONVERT(expr, type): Translate to cast expression.
4237+
return sprintf( 'CAST(%s AS %s)', $expr, $this->translate( $cast_type ) );
4238+
} else {
4239+
// CONVERT(expr USING charset): Keep "expr" as is (no SQLite support).
4240+
// TODO: Consider rejecting UTF-8-incompatible charasets.
4241+
return $expr;
4242+
}
4243+
}
4244+
42244245
return $this->translate_sequence( $node->get_children() );
42254246
}
42264247

packages/mysql-on-sqlite/tests/WP_SQLite_Driver_Tests.php

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9986,6 +9986,89 @@ public function testCastExpression(): void {
99869986
);
99879987
}
99889988

9989+
public function testConvertExpression(): void {
9990+
// CONVERT(expr, type) should behave like CAST(expr AS type).
9991+
$result = $this->assertQuery(
9992+
"SELECT
9993+
CONVERT('abc', BINARY) AS expr_1,
9994+
CONVERT('abc', CHAR) AS expr_2,
9995+
CONVERT('-10', SIGNED) AS expr_3,
9996+
CONVERT('-10', UNSIGNED) AS expr_4,
9997+
CONVERT('123.456', DECIMAL) AS expr_5,
9998+
CONVERT('2025-10-05', DATE) AS expr_6
9999+
"
10000+
);
10001+
10002+
$this->assertEquals(
10003+
array(
10004+
(object) array(
10005+
'expr_1' => 'abc',
10006+
'expr_2' => 'abc',
10007+
'expr_3' => '-10',
10008+
'expr_4' => '-10',
10009+
'expr_5' => '123.456',
10010+
'expr_6' => '2025-10-05',
10011+
),
10012+
),
10013+
$result
10014+
);
10015+
}
10016+
10017+
public function testConvertUsingExpression(): void {
10018+
// CONVERT(expr USING charset) converts character set.
10019+
// In SQLite, all text is UTF-8 — the conversion is a no-op.
10020+
$result = $this->assertQuery(
10021+
"SELECT
10022+
CONVERT('Customer' USING utf8mb4) AS expr_1,
10023+
CONVERT('test' USING utf8) AS expr_2,
10024+
CONVERT('data' USING latin1) AS expr_3
10025+
"
10026+
);
10027+
10028+
$this->assertEquals(
10029+
array(
10030+
(object) array(
10031+
'expr_1' => 'Customer',
10032+
'expr_2' => 'test',
10033+
'expr_3' => 'data',
10034+
),
10035+
),
10036+
$result
10037+
);
10038+
}
10039+
10040+
public function testConvertUsingWithCollate(): void {
10041+
$result = $this->assertQuery(
10042+
"SELECT CONVERT('Customer' USING utf8mb4) COLLATE utf8mb4_bin AS val"
10043+
);
10044+
10045+
$this->assertEquals(
10046+
array(
10047+
(object) array( 'val' => 'Customer' ),
10048+
),
10049+
$result
10050+
);
10051+
}
10052+
10053+
public function testConvertWithColumnReferences(): void {
10054+
$this->assertQuery( 'CREATE TABLE t (val VARCHAR(255), num VARCHAR(255))' );
10055+
$this->assertQuery( "INSERT INTO t (val, num) VALUES ('hello', '-42')" );
10056+
10057+
$result = $this->assertQuery(
10058+
'SELECT CONVERT(val, BINARY) AS v1, CONVERT(val USING utf8mb4) AS v2
10059+
FROM t WHERE CONVERT(num, SIGNED) < 0 ORDER BY CONVERT(val USING utf8mb4)'
10060+
);
10061+
$this->assertEquals(
10062+
array(
10063+
(object) array(
10064+
'v1' => 'hello',
10065+
'v2' => 'hello',
10066+
),
10067+
),
10068+
$result
10069+
);
10070+
}
10071+
998910072
public function testInsertWithoutInto(): void {
999010073
$this->assertQuery( 'CREATE TABLE t (id INT PRIMARY KEY, name VARCHAR(255))' );
999110074

packages/mysql-on-sqlite/tests/WP_SQLite_Driver_Translation_Tests.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,41 @@ public function testSelect(): void {
101101
);
102102
}
103103

104+
public function testConvert(): void {
105+
// CONVERT(expr, type) → CAST(expr AS type)
106+
$this->assertQuery(
107+
"SELECT CAST('abc' AS BLOB) AS `CONVERT('abc', BINARY)`",
108+
"SELECT CONVERT('abc', BINARY)"
109+
);
110+
111+
$this->assertQuery(
112+
"SELECT CAST('abc' AS TEXT) AS `CONVERT('abc', CHAR)`",
113+
"SELECT CONVERT('abc', CHAR)"
114+
);
115+
116+
$this->assertQuery(
117+
"SELECT CAST('-10' AS INTEGER) AS `CONVERT('-10', SIGNED)`",
118+
"SELECT CONVERT('-10', SIGNED)"
119+
);
120+
121+
// CONVERT(expr USING charset) → expr
122+
$this->assertQuery(
123+
"SELECT 'Customer' AS `Customer`",
124+
"SELECT CONVERT('Customer' USING utf8mb4)"
125+
);
126+
127+
$this->assertQuery(
128+
"SELECT 'test' AS `test`",
129+
"SELECT CONVERT('test' USING utf8)"
130+
);
131+
132+
// CONVERT(expr USING charset) COLLATE collation → expr COLLATE collation
133+
$this->assertQuery(
134+
"SELECT 'Customer' COLLATE `utf8mb4_bin` AS `CONVERT('Customer' USING utf8mb4) COLLATE utf8mb4_bin`",
135+
"SELECT CONVERT('Customer' USING utf8mb4) COLLATE utf8mb4_bin"
136+
);
137+
}
138+
104139
public function testInsert(): void {
105140
$this->driver->query( 'CREATE TABLE t (c INT, c1 INT, c2 INT)' );
106141
$this->driver->query( 'CREATE TABLE t1 (c1 INT, c2 INT)' );

0 commit comments

Comments
 (0)