PluginProbe
SQLite Database Integration / 2.2.0
SQLite Database Integration v2.2.0
3.0.2 3.0.1 trunk 2.1.13 2.1.14 2.1.15 2.1.16 2.2.0 2.2.1 2.2.10 2.2.11 2.2.12 2.2.13 2.2.14 2.2.15 2.2.16 2.2.17 2.2.18 2.2.19 2.2.2 2.2.20 2.2.21 2.2.22 2.2.23 2.2.3 All 32 releases
← All changes | wp-includes/sqlite/class-wp-sqlite-db.php +122 -333 3.0.12.2.0 View file →
@@ -1,7 +1,10 @@
1 1 <?php
2 2 /**
3 3 * Extend and replace the wpdb class.
4 + *
5 + * @package wp-sqlite-integration
6 + * @since 1.0.0
4 7 */
5 8
6 9 /**
7 10 * This class extends wpdb and replaces it.
@@ -12,9 +15,9 @@
12 15
13 16 /**
14 17 * Database Handle
15 18 *
16 - * @var WP_MySQL_On_SQLite
19 + * @var WP_SQLite_Translator
17 20 */
18 21 protected $dbh;
19 22
20 23 /**
@@ -53,22 +56,8 @@
53 56 $this->charset = 'utf8mb4';
54 57 }
55 58
56 59 /**
57 - * Returns the active MySQL-on-SQLite driver.
58 - *
59 - * @return WP_MySQL_On_SQLite The active driver.
60 - * @throws RuntimeException When there is no active database connection.
61 - */
62 - public function get_driver(): WP_MySQL_On_SQLite {
63 - if ( ! $this->dbh ) {
64 - throw new RuntimeException( 'Cannot access the driver without an active database connection.' );
65 - }
66 -
67 - return $this->dbh;
68 - }
69 -
70 - /**
71 60 * Method to set character set for the database.
72 61 *
73 62 * This overrides wpdb::set_charset(), only to dummy out the MySQL function.
74 63 *
@@ -81,64 +70,22 @@
81 70 public function set_charset( $dbh, $charset = null, $collate = null ) {
82 71 }
83 72
84 73 /**
85 - * Retrieves the character set for the given column.
74 + * Method to get the character set for the database.
75 + * Hardcoded to utf8mb4 for now.
86 76 *
87 - * This overrides wpdb::get_col_charset() to enable the parent's implementation
88 - * for SQLite by temporarily setting the is_mysql flag.
77 + * @param string $table The table name.
78 + * @param string $column The column name.
89 79 *
90 - * @see wpdb::get_col_charset()
91 - *
92 - * @param string $table Table name.
93 - * @param string $column Column name.
94 - * @return string|false|WP_Error Column character set as a string. False if the column has
95 - * no character set. WP_Error object on failure.
80 + * @return string The character set.
96 81 */
97 82 public function get_col_charset( $table, $column ) {
98 - $original_is_mysql = $this->is_mysql ?? null;
99 -
100 - /*
101 - * The parent method returns early when `$this->is_mysql` is falsy.
102 - * Since SQLite doesn't set this flag, we enable it temporarily so
103 - * the parent can run its full logic — querying column metadata via
104 - * SHOW FULL COLUMNS (which the SQLite driver translates) and
105 - * populating the `$this->col_meta` cache.
106 - */
107 - try {
108 - $this->is_mysql = true;
109 - return parent::get_col_charset( $table, $column );
110 - } finally {
111 - $this->is_mysql = $original_is_mysql;
112 - }
83 + // Hardcoded for now.
84 + return 'utf8mb4';
113 85 }
114 86
115 87 /**
116 - * Retrieves the maximum string length allowed in a given column.
117 - *
118 - * This overrides wpdb::get_col_length() to enable the parent's implementation
119 - * for SQLite by temporarily setting the is_mysql flag.
120 - *
121 - * @see wpdb::get_col_length()
122 - *
123 - * @param string $table Table name.
124 - * @param string $column Column name.
125 - * @return array|false|WP_Error Column length information, false if the column has
126 - * no length. WP_Error object on failure.
127 - */
128 - public function get_col_length( $table, $column ) {
129 - $original_is_mysql = $this->is_mysql ?? null;
130 -
131 - // See get_col_charset() for an explanation of the is_mysql flag.
132 - try {
133 - $this->is_mysql = true;
134 - return parent::get_col_length( $table, $column );
135 - } finally {
136 - $this->is_mysql = $original_is_mysql;
137 - }
138 - }
139 -
140 - /**
141 88 * Changes the current SQL mode, and ensures its WordPress compatibility.
142 89 *
143 90 * If no modes are passed, it will ensure the current MySQL server modes are compatible.
144 91 *
@@ -146,10 +93,14 @@
146 93 *
147 94 * @param array $modes Optional. A list of SQL modes to set. Default empty array.
148 95 */
149 96 public function set_sql_mode( $modes = array() ) {
97 + if ( ! $this->dbh instanceof WP_SQLite_Driver ) {
98 + return;
99 + }
100 +
150 101 if ( empty( $modes ) ) {
151 - $result = $this->dbh->query( 'SELECT @@SESSION.sql_mode' )->fetchAll( PDO::FETCH_OBJ ); // phpcs:ignore WordPress.DB.RestrictedClasses.mysql__PDO
102 + $result = $this->dbh->query( 'SELECT @@SESSION.sql_mode' );
152 103 if ( ! isset( $result[0] ) ) {
153 104 return;
154 105 }
155 106
@@ -182,101 +133,17 @@
182 133 }
183 134
184 135 /**
185 136 * Closes the current database connection.
137 + * Noop in SQLite.
186 138 *
187 - * This overrides wpdb::close() while closely mirroring its implementation.
188 - *
189 - * @see wpdb::close()
190 - *
191 - * @return bool True if the connection was successfully closed,
192 - * false if it wasn't, or if the connection doesn't exist.
139 + * @return bool True to indicate the connection was successfully closed.
193 140 */
194 141 public function close() {
195 - if ( ! $this->dbh ) {
196 - return false;
197 - }
198 -
199 - $connection = $this->dbh->get_connection();
200 - $pdo = $connection->get_pdo();
201 -
202 - try {
203 - if ( $this->dbh->inTransaction() ) {
204 - $this->dbh->rollBack();
205 - } elseif ( $pdo->inTransaction() ) {
206 - $pdo->rollBack();
207 - } else {
208 - /*
209 - * On PHP < 8.4, PDO cannot detect transactions started via SQL.
210 - * A savepoint ensures ROLLBACK succeeds with or without one.
211 - */
212 - $pdo->exec( 'SAVEPOINT wp_sqlite_db_close' );
213 - $pdo->exec( 'ROLLBACK' );
214 - }
215 - } catch ( Throwable $e ) {
216 - return false;
217 - }
218 -
219 - if (
220 - isset( $GLOBALS['@pdo'] )
221 - && $GLOBALS['@pdo'] === $pdo
222 - ) {
223 - unset( $GLOBALS['@pdo'] );
224 - }
225 -
226 - $connection->set_query_logger( null );
227 - $this->result = null;
228 - $this->dbh = null;
229 - $this->ready = false;
230 - $this->has_connected = false;
231 -
232 142 return true;
233 143 }
234 144
235 145 /**
236 - * Determines the best charset and collation to use given a charset and collation.
237 - *
238 - * For example, when able, utf8mb4 should be used instead of utf8.
239 - *
240 - * This overrides wpdb::determine_charset() while closely mirroring its implementation.
241 - * The override is needed because the parent checks for a mysqli connection object.
242 - *
243 - * @param string $charset The character set to check.
244 - * @param string $collate The collation to check.
245 - * @return array {
246 - * The most appropriate character set and collation to use.
247 - *
248 - * @type string $charset Character set.
249 - * @type string $collate Collation.
250 - * }
251 - */
252 - public function determine_charset( $charset, $collate ) {
253 - if ( ! $this->dbh ) {
254 - return compact( 'charset', 'collate' );
255 - }
256 -
257 - if ( 'utf8' === $charset ) {
258 - $charset = 'utf8mb4';
259 - }
260 -
261 - if ( 'utf8mb4' === $charset ) {
262 - // _general_ is outdated, so we can upgrade it to _unicode_, instead.
263 - if ( ! $collate || 'utf8_general_ci' === $collate ) {
264 - $collate = 'utf8mb4_unicode_ci';
265 - } else {
266 - $collate = str_replace( 'utf8_', 'utf8mb4_', $collate );
267 - }
268 - }
269 -
270 - // _unicode_520_ is a better collation, we should use that when it's available.
271 - if ( $this->has_cap( 'utf8mb4_520' ) && 'utf8mb4_unicode_ci' === $collate ) {
272 - $collate = 'utf8mb4_unicode_520_ci';
273 - }
274 -
275 - return compact( 'charset', 'collate' );
276 - }
277 -
278 - /**
279 146 * Method to select the database connection.
280 147 *
281 148 * This overrides wpdb::select(), only to dummy out the MySQL function.
282 149 *
@@ -298,23 +165,37 @@
298 165 *
299 166 * @param string $data The string to escape.
300 167 *
301 168 * @return string escaped
302 - * @throws RuntimeException When the database connection is not initialized.
303 169 */
304 170 public function _real_escape( $data ) {
305 171 if ( ! is_scalar( $data ) ) {
306 172 return '';
307 173 }
174 + $escaped = addslashes( $data );
175 + return $this->add_placeholder_escape( $escaped );
176 + }
308 177
309 - if ( ! $this->dbh ) {
310 - throw new RuntimeException( 'Cannot escape data without an active database connection.' );
178 + /**
179 + * Method to dummy out wpdb::esc_like() function.
180 + *
181 + * WordPress 4.0.0 introduced esc_like() function that adds backslashes to %,
182 + * underscore and backslash, which is not interpreted as escape character
183 + * by SQLite. So we override it and dummy out this function.
184 + *
185 + * @param string $text The raw text to be escaped. The input typed by the user should have no
186 + * extra or deleted slashes.
187 + *
188 + * @return string Text in the form of a LIKE phrase. The output is not SQL safe. Call $wpdb::prepare()
189 + * or real_escape next.
190 + */
191 + public function esc_like( $text ) {
192 + // The new driver adds "ESCAPE '\\'" to every LIKE expression by default.
193 + // We only need to overload this function to a no-op for the old driver.
194 + if ( $this->dbh instanceof WP_SQLite_Driver ) {
195 + return parent::esc_like( $text );
311 196 }
312 -
313 - // Escape the string without bounding quotes to mirror mysqli_real_escape_string().
314 - $quoted = $this->dbh->quote( (string) $data );
315 - $escaped = substr( $quoted, 1, -1 );
316 - return $this->add_placeholder_escape( $escaped );
197 + return $text;
317 198 }
318 199
319 200 /**
320 201 * Prints SQL/DB error.
@@ -412,108 +293,71 @@
412 293 *
413 294 * @see wpdb::db_connect()
414 295 *
415 296 * @param bool $allow_bail Not used.
416 - * @return bool True on a successful connection, false on failure.
297 + * @return void
417 298 */
418 299 public function db_connect( $allow_bail = true ) {
419 300 if ( $this->dbh ) {
420 - return $this->ready;
301 + return;
421 302 }
303 + $this->init_charset();
422 304
423 - $this->last_error = '';
305 + $pdo = null;
424 306 if ( isset( $GLOBALS['@pdo'] ) ) {
425 - trigger_error(
426 - 'PDO injection via $GLOBALS[\'@pdo\'] is no longer supported. The existing PDO will be ignored and a new connection will be created.',
427 - E_USER_WARNING
428 - );
307 + $pdo = $GLOBALS['@pdo'];
429 308 }
309 + if ( defined( 'WP_SQLITE_AST_DRIVER' ) && WP_SQLITE_AST_DRIVER ) {
310 + require_once __DIR__ . '/../../wp-includes/parser/class-wp-parser-grammar.php';
311 + require_once __DIR__ . '/../../wp-includes/parser/class-wp-parser.php';
312 + require_once __DIR__ . '/../../wp-includes/parser/class-wp-parser-node.php';
313 + require_once __DIR__ . '/../../wp-includes/parser/class-wp-parser-token.php';
314 + require_once __DIR__ . '/../../wp-includes/mysql/class-wp-mysql-token.php';
315 + require_once __DIR__ . '/../../wp-includes/mysql/class-wp-mysql-lexer.php';
316 + require_once __DIR__ . '/../../wp-includes/mysql/class-wp-mysql-parser.php';
317 + require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-connection.php';
318 + require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-configurator.php';
319 + require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-driver.php';
320 + require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-driver-exception.php';
321 + require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-information-schema-builder.php';
322 + require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-information-schema-exception.php';
323 + require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php';
324 + $this->ensure_database_directory( FQDB );
430 325
431 - if ( ! isset( $this->charset ) ) {
432 - $this->init_charset();
433 - }
434 -
435 - // Migrate the database file from a legacy path, if it exists.
436 - if ( ! defined( 'DB_FILE' ) && ! file_exists( FQDB ) ) {
437 - $old_db_path = FQDBDIR . '.ht.sqlite.php';
438 -
439 - if ( file_exists( $old_db_path ) ) {
440 - if ( ! rename( $old_db_path, FQDB ) ) {
441 - wp_die( 'Failed to rename database file.', 'Error!' );
442 - }
443 -
444 - foreach ( array( '-wal', '-shm', '-journal' ) as $suffix ) {
445 - if ( file_exists( $old_db_path . $suffix ) ) {
446 - if ( ! rename( $old_db_path . $suffix, FQDB . $suffix ) ) {
447 - wp_die( 'Failed to rename database file.', 'Error!' );
448 - }
449 - }
450 - }
326 + try {
327 + $connection = new WP_SQLite_Connection(
328 + array(
329 + 'pdo' => $pdo,
330 + 'path' => FQDB,
331 + 'journal_mode' => defined( 'SQLITE_JOURNAL_MODE' ) ? SQLITE_JOURNAL_MODE : null,
332 + )
333 + );
334 + $this->dbh = new WP_SQLite_Driver( $connection, $this->dbname );
335 + $GLOBALS['@pdo'] = $this->dbh->get_connection()->get_pdo();
336 + } catch ( Throwable $e ) {
337 + $this->last_error = $this->format_error_message( $e );
451 338 }
339 + } else {
340 + $this->dbh = new WP_SQLite_Translator( $pdo );
341 + $this->last_error = $this->dbh->get_error_message();
342 + $GLOBALS['@pdo'] = $this->dbh->get_pdo();
452 343 }
453 -
454 - if ( null === $this->dbname || '' === $this->dbname ) {
455 - $this->bail(
456 - 'The database name was not set. The SQLite driver requires a database name to be set to emulate MySQL information schema tables.',
457 - 'db_connect_fail'
458 - );
459 - return false;
460 - }
461 -
462 - $this->ensure_database_directory( FQDB );
463 -
464 - try {
465 - $options = array(
466 - 'sqlite_journal_mode' => defined( 'SQLITE_JOURNAL_MODE' ) ? SQLITE_JOURNAL_MODE : null,
467 - );
468 - $dbh = new WP_MySQL_On_SQLite(
469 - sprintf(
470 - 'mysql-on-sqlite:path=%s;dbname=%s',
471 - str_replace( ';', ';;', FQDB ),
472 - str_replace( ';', ';;', $this->dbname )
473 - ),
474 - null,
475 - null,
476 - $options
477 - );
478 - $dbh->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true ); // phpcs:ignore WordPress.DB.RestrictedClasses.mysql__PDO
479 - $this->dbh = $dbh;
480 -
481 - /**
482 - * Exposes the underlying PDO SQLite connection for backward compatibility.
483 - *
484 - * @deprecated 3.0.0 Use WP_SQLite_DB::get_driver() with
485 - * WP_MySQL_On_SQLite::get_sqlite_pdo() instead.
486 - */
487 - $GLOBALS['@pdo'] = $dbh->get_sqlite_pdo();
488 - } catch ( Throwable $e ) {
489 - $this->last_error = $this->format_error_message( $e );
490 - }
491 344 if ( $this->last_error ) {
492 345 return false;
493 346 }
494 -
495 - $this->has_connected = true;
496 - $this->set_charset( $this->dbh );
497 -
498 347 $this->ready = true;
499 348 $this->set_sql_mode();
500 - return true;
501 349 }
502 350
503 351 /**
504 - * Checks that the database connection is available.
352 + * Method to dummy out wpdb::check_connection()
505 353 *
506 354 * @param bool $allow_bail Not used.
507 355 *
508 - * @return bool True when the connection is available, false otherwise.
356 + * @return bool
509 357 */
510 358 public function check_connection( $allow_bail = true ) {
511 - if ( $this->dbh ) {
512 - return true;
513 - }
514 -
515 - return $this->db_connect( $allow_bail );
359 + return true;
516 360 }
517 361
518 362 /**
519 363 * Prepares a SQL query for safe execution.
@@ -555,14 +399,8 @@
555 399 * @return int|bool Boolean true for CREATE, ALTER, TRUNCATE and DROP queries. Number of rows
556 400 * affected/selected for all other queries. Boolean false on error.
557 401 */
558 402 public function query( $query ) {
559 - // Query Monitor integration:
560 - $query_monitor_active = defined( 'SQLITE_QUERY_MONITOR_LOADED' ) && SQLITE_QUERY_MONITOR_LOADED;
561 - if ( $query_monitor_active && $this->show_errors ) {
562 - $this->hide_errors();
563 - }
564 -
565 403 if ( ! $this->ready ) {
566 404 return false;
567 405 }
568 406
@@ -580,19 +418,17 @@
580 418
581 419 // Keep track of the last query for debug.
582 420 $this->last_query = $query;
583 421
584 - // Save the query count before running another query.
585 - $last_query_count = count( $this->queries ?? array() );
586 -
587 422 /*
588 - * @TODO: wpdb uses "$this->check_current_query" and table metadata to
589 - * reject queries containing invalid text. Implement equivalent handling
590 - * for SQLite without relying on the MySQL-specific conversion pipeline.
423 + * @TODO: WPDB uses "$this->check_current_query" to check table/column
424 + * charset and strip all invalid characters from the query.
425 + * This is an involved process that we can bypass for SQLite,
426 + * if we simply strip all invalid UTF-8 characters from the query.
591 427 *
592 - * PCRE's "u" modifier can validate UTF-8 without constructing a converted
593 - * query copy: 1 === preg_match( '//u', $query ). The implementation must
594 - * preserve wpdb's exemptions for prevalidated and binary data.
428 + * To do so, mb_convert_encoding can be used with an optional
429 + * fallback to a htmlspecialchars method. E.g.:
430 + * https://github.com/nette/utils/blob/be534713c227aeef57ce1883fc17bc9f9e29eca2/src/Utils/Strings.php#L42
595 431 */
596 432 $this->_do_query( $query );
597 433
598 434 if ( $this->last_error ) {
@@ -607,13 +443,17 @@
607 443
608 444 if ( preg_match( '/^\s*(create|alter|truncate|drop)\s/i', $query ) ) {
609 445 $return_val = true;
610 446 } elseif ( preg_match( '/^\s*(insert|delete|update|replace)\s/i', $query ) ) {
611 - $this->rows_affected = $this->result->rowCount();
447 + if ( $this->dbh instanceof WP_SQLite_Driver ) {
448 + $this->rows_affected = $this->dbh->get_last_return_value();
449 + } else {
450 + $this->rows_affected = $this->dbh->get_affected_rows();
451 + }
612 452
613 453 // Take note of the insert_id.
614 454 if ( preg_match( '/^\s*(insert|replace)\s/i', $query ) ) {
615 - $this->insert_id = (int) $this->dbh->lastInsertId();
455 + $this->insert_id = $this->dbh->get_insert_id();
616 456 }
617 457
618 458 // Return number of rows affected.
619 459 $return_val = $this->rows_affected;
@@ -619,11 +459,11 @@
619 459 $return_val = $this->rows_affected;
620 460 } else {
621 461 $num_rows = 0;
622 462
623 - if ( $this->result->columnCount() > 0 ) {
624 - $this->last_result = $this->result->fetchAll();
625 - $num_rows = count( $this->last_result );
463 + if ( is_array( $this->result ) ) {
464 + $this->last_result = $this->result;
465 + $num_rows = count( $this->result );
626 466 }
627 467
628 468 // Log and return the number of rows selected.
629 469 $this->num_rows = $num_rows;
@@ -629,33 +469,8 @@
629 469 $this->num_rows = $num_rows;
630 470 $return_val = $num_rows;
631 471 }
632 472
633 - // Query monitor integration:
634 - if ( $query_monitor_active && class_exists( 'QM_Backtrace' ) ) {
635 - if ( did_action( 'qm/cease' ) ) {
636 - $this->queries = array();
637 - }
638 -
639 - $i = $last_query_count;
640 - if ( ! isset( $this->queries[ $i ] ) ) {
641 - return $return_val;
642 - }
643 -
644 - $this->queries[ $i ]['trace'] = new QM_Backtrace();
645 - if ( ! isset( $this->queries[ $i ][3] ) ) {
646 - $this->queries[ $i ][3] = $this->time_start;
647 - }
648 -
649 - if ( $this->last_error && ! $this->suppress_errors ) {
650 - $this->queries[ $i ]['result'] = new WP_Error( 'qmdb', $this->last_error );
651 - } else {
652 - $this->queries[ $i ]['result'] = (int) $return_val;
653 - }
654 -
655 - // Add SQLite query data.
656 - $this->queries[ $i ]['sqlite_queries'] = $this->dbh->get_last_sqlite_queries();
657 - }
658 473 return $return_val;
659 474 }
660 475
661 476 /**
@@ -672,13 +487,17 @@
672 487 $this->timer_start();
673 488 }
674 489
675 490 try {
676 - $this->result = $this->dbh->query( $query, PDO::FETCH_OBJ ); // phpcs:ignore WordPress.DB.RestrictedClasses.mysql__PDO
491 + $this->result = $this->dbh->query( $query );
677 492 } catch ( Throwable $e ) {
678 493 $this->last_error = $this->format_error_message( $e );
679 494 }
680 495
496 + if ( $this->dbh instanceof WP_SQLite_Translator ) {
497 + $this->last_error = $this->dbh->get_error_message();
498 + }
499 +
681 500 ++$this->num_queries;
682 501
683 502 if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) {
684 503 $this->log_query(
@@ -701,79 +520,49 @@
701 520 protected function load_col_info() {
702 521 if ( $this->col_info ) {
703 522 return;
704 523 }
705 - $this->col_info = array();
706 - if ( null === $this->result ) {
707 - return;
708 - }
709 - for ( $i = 0; $i < $this->result->columnCount(); $i++ ) {
710 - $column = $this->result->getColumnMeta( $i );
711 - $this->col_info[] = (object) array(
712 - 'name' => $column['name'],
713 - 'orgname' => $column['mysqli:orgname'],
714 - 'table' => $column['table'],
715 - 'orgtable' => $column['mysqli:orgtable'],
716 - 'def' => '', // Unused, always ''.
717 - 'db' => $column['mysqli:db'],
718 - 'catalog' => 'def', // Unused, always 'def'.
719 - 'max_length' => 0, // As of PHP 8.1, this is always 0.
720 - 'length' => $column['len'],
721 - 'charsetnr' => $column['mysqli:charsetnr'],
722 - 'flags' => $column['mysqli:flags'],
723 - 'type' => $column['mysqli:type'],
724 - 'decimals' => $column['precision'],
725 - );
726 - }
524 + $this->col_info = $this->dbh->get_columns();
727 525 }
728 526
729 527 /**
730 - * Determines whether the database supports a given feature.
528 + * Method to return what the database can do.
731 529 *
732 - * The utf8mb4 check is handled here because older WordPress versions inspect
733 - * the MySQL client library. All other capabilities use the parent logic.
530 + * This overrides wpdb::has_cap() to avoid using MySQL functions.
531 + * SQLite supports subqueries, but not support collation, group_concat and set_charset.
734 532 *
735 533 * @see wpdb::has_cap()
736 534 *
737 - * @param string $db_cap The feature to check for.
738 - * @return bool True when the database feature is supported, false otherwise.
535 + * @param string $db_cap The feature to check for. Accepts 'collation',
536 + * 'group_concat', 'subqueries', 'set_charset',
537 + * 'utf8mb4', or 'utf8mb4_520'.
538 + *
539 + * @return bool Whether the database feature is supported, false otherwise.
739 540 */
740 541 public function has_cap( $db_cap ) {
741 - if ( 'utf8mb4' === strtolower( $db_cap ) ) {
742 - return true;
743 - }
744 -
745 - return parent::has_cap( $db_cap );
542 + return 'subqueries' === strtolower( $db_cap );
746 543 }
747 544
748 545 /**
749 - * Retrieves the emulated database server version number.
546 + * Method to return database version number.
750 547 *
751 - * This mirrors wpdb::db_version(), but must also be defined here because
752 - * WordPress 5.4 and older fetch server information directly from the MySQL
753 - * extension instead of delegating to wpdb::db_server_info().
548 + * This overrides wpdb::db_version() to avoid using MySQL function.
549 + * It returns mysql version number, but it means nothing for SQLite.
550 + * So it return the newest mysql version.
754 551 *
755 552 * @see wpdb::db_version()
756 - *
757 - * @return string Version number on success, or an empty string while disconnected.
758 553 */
759 554 public function db_version() {
760 - return preg_replace( '/[^0-9.].*/', '', $this->db_server_info() );
555 + return '8.0';
761 556 }
762 557
763 558 /**
764 - * Returns the raw version string of the emulated MySQL server.
559 + * Returns the version of the SQLite engine.
765 560 *
766 - * @see wpdb::db_server_info()
767 - *
768 - * @return string Emulated MySQL server version, or an empty string while disconnected.
561 + * @return string SQLite engine version as a string.
769 562 */
770 563 public function db_server_info() {
771 - if ( ! $this->dbh ) {
772 - return '';
773 - }
774 -
775 - return $this->dbh->getAttribute( PDO::ATTR_SERVER_VERSION ); // phpcs:ignore WordPress.DB.RestrictedClasses.mysql__PDO
564 + return $this->dbh->get_sqlite_version();
776 565 }
777 566
778 567 /**
779 568 * Make sure the SQLite database directory exists and is writable.
@@ -823,9 +612,9 @@
823 612 }
824 613
825 614
826 615 /**
827 - * Format MySQL-on-SQLite driver error message.
616 + * Format SQLite driver error message.
828 617 *
829 618 * @return string
830 619 */
831 620 private function format_error_message( Throwable $e ) {
@@ -831,10 +620,10 @@
831 620 private function format_error_message( Throwable $e ) {
832 621 $output = '<div style="clear:both">&nbsp;</div>' . PHP_EOL;
833 622
834 623 // Queries.
835 - if ( $e instanceof WP_MySQL_On_SQLite_Exception ) {
836 - $driver = $e->get_driver();
624 + if ( $e instanceof WP_SQLite_Driver_Exception ) {
625 + $driver = $e->getDriver();
837 626
838 627 $output .= '<div class="queries" style="clear:both;margin-bottom:2px;border:red dotted thin;">' . PHP_EOL;
839 628 $output .= '<p>MySQL query:</p>' . PHP_EOL;
840 629 $output .= '<p>' . $driver->get_last_mysql_query() . '</p>' . PHP_EOL;