@@ -78,17 +78,154 @@ public function set_charset( $dbh, $charset = null, $collate = null ) {
7878 }
7979
8080 /**
81- * Method to get the character set for the database.
82- * Hardcoded to utf8mb4 for now.
81+ * Retrieves the character set for the given column.
8382 *
84- * @param string $table The table name.
85- * @param string $column The column name.
83+ * @since 2.3.0
8684 *
87- * @return string The character set.
85+ * @param string $table Table name.
86+ * @param string $column Column name.
87+ * @return string|false Column character set as a string. False if the column has
88+ * no character set (e.g., numeric or binary columns).
8889 */
8990 public function get_col_charset ( $ table , $ column ) {
90- // Hardcoded for now.
91- return 'utf8mb4 ' ;
91+ $ table_key = strtolower ( $ table );
92+ $ column_key = strtolower ( $ column );
93+
94+ /**
95+ * Filters the column charset value before the DB is checked.
96+ *
97+ * @since 2.3.0
98+ *
99+ * @param string|null|false $charset The character set to use. Default null.
100+ * @param string $table The name of the table being checked.
101+ * @param string $column The name of the column being checked.
102+ */
103+ $ charset = apply_filters ( 'pre_get_col_charset ' , null , $ table , $ column );
104+ if ( null !== $ charset ) {
105+ return $ charset ;
106+ }
107+
108+ // Use SHOW FULL COLUMNS to get column metadata with collation info.
109+ // This works because the driver translates this to query the information schema.
110+ $ results = $ this ->get_results ( "SHOW FULL COLUMNS FROM ` {$ table_key }` " );
111+
112+ if ( ! $ results ) {
113+ return false ;
114+ }
115+
116+ // Build column metadata cache.
117+ $ columns = array ();
118+ foreach ( $ results as $ col ) {
119+ $ columns [ strtolower ( $ col ->Field ) ] = $ col ;
120+ }
121+ $ this ->col_meta [ $ table_key ] = $ columns ;
122+
123+ // Check if column exists.
124+ if ( ! isset ( $ columns [ $ column_key ] ) ) {
125+ return false ;
126+ }
127+
128+ // Return false for non-string columns (no collation).
129+ if ( empty ( $ columns [ $ column_key ]->Collation ) ) {
130+ return false ;
131+ }
132+
133+ // Extract charset from collation (e.g., 'utf8mb4_general_ci' -> 'utf8mb4').
134+ list ( $ charset ) = explode ( '_ ' , $ columns [ $ column_key ]->Collation );
135+ return $ charset ;
136+ }
137+
138+ /**
139+ * Retrieves the maximum string length allowed in a given column.
140+ *
141+ * @since 2.3.0
142+ *
143+ * @param string $table Table name.
144+ * @param string $column Column name.
145+ * @return array|false {
146+ * Array of column length information, false if the column has no length
147+ * (for example, numeric column).
148+ *
149+ * @type string $type One of 'byte' or 'char'.
150+ * @type int $length The column length.
151+ * }
152+ */
153+ public function get_col_length ( $ table , $ column ) {
154+ $ table_key = strtolower ( $ table );
155+ $ column_key = strtolower ( $ column );
156+
157+ // Check cached column metadata first.
158+ if ( isset ( $ this ->col_meta [ $ table_key ][ $ column_key ] ) ) {
159+ $ type = $ this ->col_meta [ $ table_key ][ $ column_key ]->Type ;
160+ } else {
161+ // Query column info if not cached.
162+ $ results = $ this ->get_results ( "SHOW FULL COLUMNS FROM ` {$ table_key }` " );
163+ if ( ! $ results ) {
164+ return false ;
165+ }
166+
167+ $ columns = array ();
168+ foreach ( $ results as $ col ) {
169+ $ columns [ strtolower ( $ col ->Field ) ] = $ col ;
170+ }
171+ $ this ->col_meta [ $ table_key ] = $ columns ;
172+
173+ if ( ! isset ( $ columns [ $ column_key ] ) ) {
174+ return false ;
175+ }
176+ $ type = $ columns [ $ column_key ]->Type ;
177+ }
178+
179+ // Parse the type to get length info.
180+ $ typeinfo = explode ( '( ' , $ type );
181+ $ basetype = strtolower ( $ typeinfo [0 ] );
182+
183+ if ( ! empty ( $ typeinfo [1 ] ) ) {
184+ $ length = (int ) trim ( $ typeinfo [1 ], ') ' );
185+ } else {
186+ $ length = false ;
187+ }
188+
189+ switch ( $ basetype ) {
190+ case 'char ' :
191+ case 'varchar ' :
192+ return array (
193+ 'type ' => 'char ' ,
194+ 'length ' => $ length ,
195+ );
196+ case 'binary ' :
197+ case 'varbinary ' :
198+ return array (
199+ 'type ' => 'byte ' ,
200+ 'length ' => $ length ,
201+ );
202+ case 'tinyblob ' :
203+ case 'tinytext ' :
204+ return array (
205+ 'type ' => 'byte ' ,
206+ 'length ' => 255 ,
207+ );
208+ case 'blob ' :
209+ case 'text ' :
210+ return array (
211+ 'type ' => 'byte ' ,
212+ 'length ' => 65535 ,
213+ );
214+ case 'mediumblob ' :
215+ case 'mediumtext ' :
216+ return array (
217+ 'type ' => 'byte ' ,
218+ 'length ' => 16777215 ,
219+ );
220+ case 'longblob ' :
221+ case 'longtext ' :
222+ return array (
223+ 'type ' => 'byte ' ,
224+ 'length ' => 4294967295 ,
225+ );
226+ default :
227+ return false ;
228+ }
92229 }
93230
94231 /**
@@ -145,10 +282,64 @@ public function set_sql_mode( $modes = array() ) {
145282 *
146283 * @return bool True to indicate the connection was successfully closed.
147284 */
285+ /**
286+ * Close the database connection.
287+ *
288+ * @since 2.3.0
289+ *
290+ * @return bool True if connection was closed successfully.
291+ */
148292 public function close () {
293+ if ( ! $ this ->dbh ) {
294+ return false ;
295+ }
296+
297+ $ this ->dbh = null ;
298+ $ this ->ready = false ;
299+ $ this ->has_connected = false ;
300+
149301 return true ;
150302 }
151303
304+ /**
305+ * Determines the best charset and collation for the database connection.
306+ *
307+ * This overrides wpdb::determine_charset() to handle SQLite's lack of mysqli.
308+ * WordPress expects utf8 to be upgraded to utf8mb4 when supported.
309+ *
310+ * @since 2.3.0
311+ *
312+ * @param string $charset The character set to check.
313+ * @param string $collate The collation to check.
314+ * @return array {
315+ * Array containing the determined charset and collation.
316+ *
317+ * @type string $charset The determined character set.
318+ * @type string $collate The determined collation.
319+ * }
320+ */
321+ public function determine_charset ( $ charset , $ collate ) {
322+ if ( 'utf8 ' === $ charset ) {
323+ $ charset = 'utf8mb4 ' ;
324+ }
325+
326+ if ( 'utf8mb4 ' === $ charset ) {
327+ // _general_ is outdated, so we can upgrade it to _unicode_, instead.
328+ if ( ! $ collate || 'utf8_general_ci ' === $ collate ) {
329+ $ collate = 'utf8mb4_unicode_ci ' ;
330+ } else {
331+ $ collate = str_replace ( 'utf8_ ' , 'utf8mb4_ ' , $ collate );
332+ }
333+ }
334+
335+ // _unicode_520_ is a better collation, we should use that when it's available.
336+ if ( $ this ->has_cap ( 'utf8mb4_520 ' ) && 'utf8mb4_unicode_ci ' === $ collate ) {
337+ $ collate = 'utf8mb4_unicode_520_ci ' ;
338+ }
339+
340+ return compact ( 'charset ' , 'collate ' );
341+ }
342+
152343 /**
153344 * Method to select the database connection.
154345 *
@@ -453,16 +644,27 @@ public function query( $query ) {
453644 // Save the query count before running another query.
454645 $ last_query_count = count ( $ this ->queries ?? array () );
455646
456- /*
457- * @TODO: WPDB uses "$this->check_current_query" to check table/column
458- * charset and strip all invalid characters from the query.
459- * This is an involved process that we can bypass for SQLite,
460- * if we simply strip all invalid UTF-8 characters from the query.
461- *
462- * To do so, mb_convert_encoding can be used with an optional
463- * fallback to a htmlspecialchars method. E.g.:
464- * https://github.com/nette/utils/blob/be534713c227aeef57ce1883fc17bc9f9e29eca2/src/Utils/Strings.php#L42
465- */
647+ // Check for invalid text in the query, similar to parent wpdb behavior.
648+ if ( $ this ->check_current_query && ! $ this ->check_ascii ( $ query ) ) {
649+ $ stripped_query = $ this ->strip_invalid_text_from_query ( $ query );
650+ /*
651+ * strip_invalid_text_from_query() can perform queries, so we need
652+ * to flush again, just to make sure everything is clear.
653+ */
654+ $ this ->flush ();
655+ if ( $ stripped_query !== $ query ) {
656+ $ this ->insert_id = 0 ;
657+ $ this ->last_query = $ query ;
658+
659+ wp_load_translations_early ();
660+
661+ $ this ->last_error = __ ( 'WordPress database error: Could not perform query because it contains invalid data. ' );
662+
663+ return false ;
664+ }
665+ }
666+ $ this ->check_current_query = true ;
667+
466668 $ this ->_do_query ( $ query );
467669
468670 if ( $ this ->last_error ) {
@@ -612,18 +814,31 @@ protected function load_col_info() {
612814 * Method to return what the database can do.
613815 *
614816 * This overrides wpdb::has_cap() to avoid using MySQL functions.
615- * SQLite supports subqueries, but not support collation, group_concat and set_charset .
817+ * SQLite via this driver supports all common MySQL capabilities .
616818 *
617819 * @see wpdb::has_cap()
618820 *
619821 * @param string $db_cap The feature to check for. Accepts 'collation',
620822 * 'group_concat', 'subqueries', 'set_charset',
621- * 'utf8mb4', or 'utf8mb4_520 '.
823+ * 'utf8mb4', 'utf8mb4_520', or 'identifier_placeholders '.
622824 *
623825 * @return bool Whether the database feature is supported, false otherwise.
624826 */
625827 public function has_cap ( $ db_cap ) {
626- return 'subqueries ' === strtolower ( $ db_cap );
828+ $ db_cap = strtolower ( $ db_cap );
829+
830+ switch ( $ db_cap ) {
831+ case 'collation ' :
832+ case 'group_concat ' :
833+ case 'subqueries ' :
834+ case 'set_charset ' :
835+ case 'utf8mb4 ' :
836+ case 'utf8mb4_520 ' :
837+ case 'identifier_placeholders ' :
838+ return true ;
839+ }
840+
841+ return false ;
627842 }
628843
629844 /**
0 commit comments