Skip to content

Commit 2d5ac1c

Browse files
WordPress PHPUnit test fails: charset detection, length validation, capabilities | Amends from review
1 parent a17fa51 commit 2d5ac1c

1 file changed

Lines changed: 64 additions & 156 deletions

File tree

wp-includes/sqlite/class-wp-sqlite-db.php

Lines changed: 64 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -73,151 +73,52 @@ public function set_charset( $dbh, $charset = null, $collate = null ) {
7373
/**
7474
* Retrieves the character set for the given column.
7575
*
76-
* @since 2.3.0
76+
* This overrides wpdb::get_col_charset() to enable the parent's implementation
77+
* for SQLite by temporarily setting the is_mysql flag.
78+
*
79+
* @see wpdb::get_col_charset()
7780
*
7881
* @param string $table Table name.
7982
* @param string $column Column name.
80-
* @return string|false Column character set as a string. False if the column has
81-
* no character set (e.g., numeric or binary columns).
83+
* @return string|false|WP_Error Column character set as a string. False if the column has
84+
* no character set. WP_Error object on failure.
8285
*/
8386
public function get_col_charset( $table, $column ) {
84-
$table_key = strtolower( $table );
85-
$column_key = strtolower( $column );
86-
87-
/**
88-
* Filters the column charset value before the DB is checked.
89-
*
90-
* @since 2.3.0
91-
*
92-
* @param string|null|false $charset The character set to use. Default null.
93-
* @param string $table The name of the table being checked.
94-
* @param string $column The name of the column being checked.
87+
/*
88+
* The parent method returns early when `$this->is_mysql` is falsy.
89+
* Since SQLite doesn't set this flag, we enable it temporarily so
90+
* the parent can run its full logic — querying column metadata via
91+
* SHOW FULL COLUMNS (which the SQLite driver translates) and
92+
* populating the `$this->col_meta` cache.
9593
*/
96-
$charset = apply_filters( 'pre_get_col_charset', null, $table, $column );
97-
if ( null !== $charset ) {
98-
return $charset;
99-
}
100-
101-
// Use SHOW FULL COLUMNS to get column metadata with collation info.
102-
// This works because the driver translates this to query the information schema.
103-
$results = $this->get_results( "SHOW FULL COLUMNS FROM `{$table_key}`" );
104-
105-
if ( ! $results ) {
106-
return false;
107-
}
108-
109-
// Build column metadata cache.
110-
$columns = array();
111-
foreach ( $results as $col ) {
112-
$columns[ strtolower( $col->Field ) ] = $col;
113-
}
114-
$this->col_meta[ $table_key ] = $columns;
115-
116-
// Check if column exists.
117-
if ( ! isset( $columns[ $column_key ] ) ) {
118-
return false;
119-
}
120-
121-
// Return false for non-string columns (no collation).
122-
if ( empty( $columns[ $column_key ]->Collation ) ) {
123-
return false;
94+
try {
95+
$this->is_mysql = true;
96+
return parent::get_col_charset( $table, $column );
97+
} finally {
98+
$this->is_mysql = null;
12499
}
125-
126-
// Extract charset from collation (e.g., 'utf8mb4_general_ci' -> 'utf8mb4').
127-
list( $charset ) = explode( '_', $columns[ $column_key ]->Collation );
128-
return $charset;
129100
}
130101

131102
/**
132103
* Retrieves the maximum string length allowed in a given column.
133104
*
134-
* @since 2.3.0
105+
* This overrides wpdb::get_col_length() to enable the parent's implementation
106+
* for SQLite by temporarily setting the is_mysql flag.
107+
*
108+
* @see wpdb::get_col_length()
135109
*
136110
* @param string $table Table name.
137111
* @param string $column Column name.
138-
* @return array|false {
139-
* Array of column length information, false if the column has no length
140-
* (for example, numeric column).
141-
*
142-
* @type string $type One of 'byte' or 'char'.
143-
* @type int $length The column length.
144-
* }
112+
* @return array|false|WP_Error Column length information, false if the column has
113+
* no length. WP_Error object on failure.
145114
*/
146115
public function get_col_length( $table, $column ) {
147-
$table_key = strtolower( $table );
148-
$column_key = strtolower( $column );
149-
150-
// Check cached column metadata first.
151-
if ( isset( $this->col_meta[ $table_key ][ $column_key ] ) ) {
152-
$type = $this->col_meta[ $table_key ][ $column_key ]->Type;
153-
} else {
154-
// Query column info if not cached.
155-
$results = $this->get_results( "SHOW FULL COLUMNS FROM `{$table_key}`" );
156-
if ( ! $results ) {
157-
return false;
158-
}
159-
160-
$columns = array();
161-
foreach ( $results as $col ) {
162-
$columns[ strtolower( $col->Field ) ] = $col;
163-
}
164-
$this->col_meta[ $table_key ] = $columns;
165-
166-
if ( ! isset( $columns[ $column_key ] ) ) {
167-
return false;
168-
}
169-
$type = $columns[ $column_key ]->Type;
170-
}
171-
172-
// Parse the type to get length info.
173-
$typeinfo = explode( '(', $type );
174-
$basetype = strtolower( $typeinfo[0] );
175-
176-
if ( ! empty( $typeinfo[1] ) ) {
177-
$length = (int) trim( $typeinfo[1], ')' );
178-
} else {
179-
$length = false;
180-
}
181-
182-
switch ( $basetype ) {
183-
case 'char':
184-
case 'varchar':
185-
return array(
186-
'type' => 'char',
187-
'length' => $length,
188-
);
189-
case 'binary':
190-
case 'varbinary':
191-
return array(
192-
'type' => 'byte',
193-
'length' => $length,
194-
);
195-
case 'tinyblob':
196-
case 'tinytext':
197-
return array(
198-
'type' => 'byte',
199-
'length' => 255,
200-
);
201-
case 'blob':
202-
case 'text':
203-
return array(
204-
'type' => 'byte',
205-
'length' => 65535,
206-
);
207-
case 'mediumblob':
208-
case 'mediumtext':
209-
return array(
210-
'type' => 'byte',
211-
'length' => 16777215,
212-
);
213-
case 'longblob':
214-
case 'longtext':
215-
return array(
216-
'type' => 'byte',
217-
'length' => 4294967295,
218-
);
219-
default:
220-
return false;
116+
// See get_col_charset() for an explanation of the is_mysql flag.
117+
try {
118+
$this->is_mysql = true;
119+
return parent::get_col_length( $table, $column );
120+
} finally {
121+
$this->is_mysql = null;
221122
}
222123
}
223124

@@ -271,16 +172,13 @@ public function set_sql_mode( $modes = array() ) {
271172

272173
/**
273174
* Closes the current database connection.
274-
* Noop in SQLite.
275175
*
276-
* @return bool True to indicate the connection was successfully closed.
277-
*/
278-
/**
279-
* Close the database connection.
176+
* This overrides wpdb::close() while closely mirroring its implementation.
280177
*
281-
* @since 2.3.0
178+
* @see wpdb::close()
282179
*
283-
* @return bool True if connection was closed successfully.
180+
* @return bool True if the connection was successfully closed,
181+
* false if it wasn't, or if the connection doesn't exist.
284182
*/
285183
public function close() {
286184
if ( ! $this->dbh ) {
@@ -295,20 +193,20 @@ public function close() {
295193
}
296194

297195
/**
298-
* Determines the best charset and collation for the database connection.
196+
* Determines the best charset and collation to use given a charset and collation.
299197
*
300-
* This overrides wpdb::determine_charset() to handle SQLite's lack of mysqli.
301-
* WordPress expects utf8 to be upgraded to utf8mb4 when supported.
198+
* For example, when able, utf8mb4 should be used instead of utf8.
302199
*
303-
* @since 2.3.0
200+
* This overrides wpdb::determine_charset() while closely mirroring its implementation.
201+
* The override is needed because the parent checks for a mysqli connection object.
304202
*
305203
* @param string $charset The character set to check.
306204
* @param string $collate The collation to check.
307205
* @return array {
308-
* Array containing the determined charset and collation.
206+
* The most appropriate character set and collation to use.
309207
*
310-
* @type string $charset The determined character set.
311-
* @type string $collate The determined collation.
208+
* @type string $charset Character set.
209+
* @type string $collate Collation.
312210
* }
313211
*/
314212
public function determine_charset( $charset, $collate ) {
@@ -634,14 +532,23 @@ public function query( $query ) {
634532
// Save the query count before running another query.
635533
$last_query_count = count( $this->queries ?? array() );
636534

637-
// Check for invalid text in the query, similar to parent wpdb behavior.
535+
/*
536+
* Strip invalid UTF-8 characters from non-ASCII queries.
537+
*
538+
* SQLite stores all text as UTF-8, so we simply ensure the query
539+
* contains only valid UTF-8 sequences rather than using the parent's
540+
* MySQL-specific charset detection pipeline.
541+
*/
638542
if ( $this->check_current_query && ! $this->check_ascii( $query ) ) {
639-
$stripped_query = $this->strip_invalid_text_from_query( $query );
640-
/*
641-
* strip_invalid_text_from_query() can perform queries, so we need
642-
* to flush again, just to make sure everything is clear.
643-
*/
644-
$this->flush();
543+
if ( function_exists( 'mb_convert_encoding' ) ) {
544+
$stripped_query = mb_convert_encoding( $query, 'UTF-8', 'UTF-8' );
545+
} else {
546+
$stripped_query = htmlspecialchars_decode(
547+
htmlspecialchars( $query, ENT_NOQUOTES | ENT_SUBSTITUTE, 'UTF-8' ),
548+
ENT_NOQUOTES
549+
);
550+
}
551+
645552
if ( $stripped_query !== $query ) {
646553
$this->insert_id = 0;
647554
$this->last_query = $query;
@@ -801,17 +708,18 @@ protected function load_col_info() {
801708
}
802709

803710
/**
804-
* Method to return what the database can do.
711+
* Determines whether the database supports a given feature.
805712
*
806-
* This overrides wpdb::has_cap() to avoid using MySQL functions.
807-
* SQLite via this driver supports all common MySQL capabilities.
713+
* This overrides wpdb::has_cap() while closely mirroring its implementation.
714+
* The override is needed because the parent's 'utf8mb4' capability check calls
715+
* mysqli_get_client_info(), which is environment-dependent and not applicable
716+
* for SQLite.
808717
*
809718
* @see wpdb::has_cap()
810719
*
811720
* @param string $db_cap The feature to check for. Accepts 'collation',
812721
* 'group_concat', 'subqueries', 'set_charset',
813-
* 'utf8mb4', 'utf8mb4_520', or 'identifier_placeholders'.
814-
*
722+
* 'utf8mb4', or 'utf8mb4_520'.
815723
* @return bool Whether the database feature is supported, false otherwise.
816724
*/
817725
public function has_cap( $db_cap ) {
@@ -823,9 +731,9 @@ public function has_cap( $db_cap ) {
823731
case 'subqueries':
824732
case 'set_charset':
825733
case 'utf8mb4':
826-
case 'utf8mb4_520':
827-
case 'identifier_placeholders':
828734
return true;
735+
case 'utf8mb4_520':
736+
return version_compare( $GLOBALS['wp_version'], '4.6', '>=' );
829737
}
830738

831739
return false;

0 commit comments

Comments
 (0)