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 +125 -346 trunk2.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,16 +399,9 @@
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 - $this->check_current_query = true;
567 404 return false;
568 405 }
569 406
570 407 $query = apply_filters( 'query', $query );
@@ -578,32 +415,21 @@
578 415
579 416 // Log how the function was called.
580 417 $this->func_call = "\$db->query(\"$query\")";
581 418
582 - /*
583 - * Mirror wpdb's query text validation.
584 - * TODO: Add full charset enforcement to MySQL on SQLite, where column
585 - * types and SQL mode are known, so all callers are protected.
586 - */
587 - if ( $this->check_current_query && ! $this->check_ascii( $query ) ) {
588 - $stripped_query = $this->strip_invalid_text_from_query( $query );
589 - // Charset discovery can run queries, so clear their results.
590 - $this->flush();
591 - if ( $stripped_query !== $query ) {
592 - $this->insert_id = 0;
593 - $this->last_query = $query;
594 - wp_load_translations_early();
595 - $this->last_error = __( 'WordPress database error: Could not perform query because it contains invalid data.' );
596 - return false;
597 - }
598 - }
599 - $this->check_current_query = true;
600 -
601 419 // Keep track of the last query for debug.
602 420 $this->last_query = $query;
603 421
604 - // Save the query count after any charset discovery queries.
605 - $last_query_count = count( $this->queries ?? array() );
422 + /*
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.
427 + *
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
431 + */
606 432 $this->_do_query( $query );
607 433
608 434 if ( $this->last_error ) {
609 435 // Clear insert_id on a subsequent failed insert.
@@ -617,13 +443,17 @@
617 443
618 444 if ( preg_match( '/^\s*(create|alter|truncate|drop)\s/i', $query ) ) {
619 445 $return_val = true;
620 446 } elseif ( preg_match( '/^\s*(insert|delete|update|replace)\s/i', $query ) ) {
621 - $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 + }
622 452
623 453 // Take note of the insert_id.
624 454 if ( preg_match( '/^\s*(insert|replace)\s/i', $query ) ) {
625 - $this->insert_id = (int) $this->dbh->lastInsertId();
455 + $this->insert_id = $this->dbh->get_insert_id();
626 456 }
627 457
628 458 // Return number of rows affected.
629 459 $return_val = $this->rows_affected;
@@ -629,11 +459,11 @@
629 459 $return_val = $this->rows_affected;
630 460 } else {
631 461 $num_rows = 0;
632 462
633 - if ( $this->result->columnCount() > 0 ) {
634 - $this->last_result = $this->result->fetchAll();
635 - $num_rows = count( $this->last_result );
463 + if ( is_array( $this->result ) ) {
464 + $this->last_result = $this->result;
465 + $num_rows = count( $this->result );
636 466 }
637 467
638 468 // Log and return the number of rows selected.
639 469 $this->num_rows = $num_rows;
@@ -639,33 +469,8 @@
639 469 $this->num_rows = $num_rows;
640 470 $return_val = $num_rows;
641 471 }
642 472
643 - // Query monitor integration:
644 - if ( $query_monitor_active && class_exists( 'QM_Backtrace' ) ) {
645 - if ( did_action( 'qm/cease' ) ) {
646 - $this->queries = array();
647 - }
648 -
649 - $i = $last_query_count;
650 - if ( ! isset( $this->queries[ $i ] ) ) {
651 - return $return_val;
652 - }
653 -
654 - $this->queries[ $i ]['trace'] = new QM_Backtrace();
655 - if ( ! isset( $this->queries[ $i ][3] ) ) {
656 - $this->queries[ $i ][3] = $this->time_start;
657 - }
658 -
659 - if ( $this->last_error && ! $this->suppress_errors ) {
660 - $this->queries[ $i ]['result'] = new WP_Error( 'qmdb', $this->last_error );
661 - } else {
662 - $this->queries[ $i ]['result'] = (int) $return_val;
663 - }
664 -
665 - // Add SQLite query data.
666 - $this->queries[ $i ]['sqlite_queries'] = $this->dbh->get_last_sqlite_queries();
667 - }
668 473 return $return_val;
669 474 }
670 475
671 476 /**
@@ -682,13 +487,17 @@
682 487 $this->timer_start();
683 488 }
684 489
685 490 try {
686 - $this->result = $this->dbh->query( $query, PDO::FETCH_OBJ ); // phpcs:ignore WordPress.DB.RestrictedClasses.mysql__PDO
491 + $this->result = $this->dbh->query( $query );
687 492 } catch ( Throwable $e ) {
688 493 $this->last_error = $this->format_error_message( $e );
689 494 }
690 495
496 + if ( $this->dbh instanceof WP_SQLite_Translator ) {
497 + $this->last_error = $this->dbh->get_error_message();
498 + }
499 +
691 500 ++$this->num_queries;
692 501
693 502 if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) {
694 503 $this->log_query(
@@ -711,79 +520,49 @@
711 520 protected function load_col_info() {
712 521 if ( $this->col_info ) {
713 522 return;
714 523 }
715 - $this->col_info = array();
716 - if ( null === $this->result ) {
717 - return;
718 - }
719 - for ( $i = 0; $i < $this->result->columnCount(); $i++ ) {
720 - $column = $this->result->getColumnMeta( $i );
721 - $this->col_info[] = (object) array(
722 - 'name' => $column['name'],
723 - 'orgname' => $column['mysqli:orgname'],
724 - 'table' => $column['table'],
725 - 'orgtable' => $column['mysqli:orgtable'],
726 - 'def' => '', // Unused, always ''.
727 - 'db' => $column['mysqli:db'],
728 - 'catalog' => 'def', // Unused, always 'def'.
729 - 'max_length' => 0, // As of PHP 8.1, this is always 0.
730 - 'length' => $column['len'],
731 - 'charsetnr' => $column['mysqli:charsetnr'],
732 - 'flags' => $column['mysqli:flags'],
733 - 'type' => $column['mysqli:type'],
734 - 'decimals' => $column['precision'],
735 - );
736 - }
524 + $this->col_info = $this->dbh->get_columns();
737 525 }
738 526
739 527 /**
740 - * Determines whether the database supports a given feature.
528 + * Method to return what the database can do.
741 529 *
742 - * The utf8mb4 check is handled here because older WordPress versions inspect
743 - * 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.
744 532 *
745 533 * @see wpdb::has_cap()
746 534 *
747 - * @param string $db_cap The feature to check for.
748 - * @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.
749 540 */
750 541 public function has_cap( $db_cap ) {
751 - if ( 'utf8mb4' === strtolower( $db_cap ) ) {
752 - return true;
753 - }
754 -
755 - return parent::has_cap( $db_cap );
542 + return 'subqueries' === strtolower( $db_cap );
756 543 }
757 544
758 545 /**
759 - * Retrieves the emulated database server version number.
546 + * Method to return database version number.
760 547 *
761 - * This mirrors wpdb::db_version(), but must also be defined here because
762 - * WordPress 5.4 and older fetch server information directly from the MySQL
763 - * 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.
764 551 *
765 552 * @see wpdb::db_version()
766 - *
767 - * @return string Version number on success, or an empty string while disconnected.
768 553 */
769 554 public function db_version() {
770 - return preg_replace( '/[^0-9.].*/', '', $this->db_server_info() );
555 + return '8.0';
771 556 }
772 557
773 558 /**
774 - * Returns the raw version string of the emulated MySQL server.
559 + * Returns the version of the SQLite engine.
775 560 *
776 - * @see wpdb::db_server_info()
777 - *
778 - * @return string Emulated MySQL server version, or an empty string while disconnected.
561 + * @return string SQLite engine version as a string.
779 562 */
780 563 public function db_server_info() {
781 - if ( ! $this->dbh ) {
782 - return '';
783 - }
784 -
785 - return $this->dbh->getAttribute( PDO::ATTR_SERVER_VERSION ); // phpcs:ignore WordPress.DB.RestrictedClasses.mysql__PDO
564 + return $this->dbh->get_sqlite_version();
786 565 }
787 566
788 567 /**
789 568 * Make sure the SQLite database directory exists and is writable.
@@ -833,9 +612,9 @@
833 612 }
834 613
835 614
836 615 /**
837 - * Format MySQL-on-SQLite driver error message.
616 + * Format SQLite driver error message.
838 617 *
839 618 * @return string
840 619 */
841 620 private function format_error_message( Throwable $e ) {
@@ -841,10 +620,10 @@
841 620 private function format_error_message( Throwable $e ) {
842 621 $output = '<div style="clear:both">&nbsp;</div>' . PHP_EOL;
843 622
844 623 // Queries.
845 - if ( $e instanceof WP_MySQL_On_SQLite_Exception ) {
846 - $driver = $e->get_driver();
624 + if ( $e instanceof WP_SQLite_Driver_Exception ) {
625 + $driver = $e->getDriver();
847 626
848 627 $output .= '<div class="queries" style="clear:both;margin-bottom:2px;border:red dotted thin;">' . PHP_EOL;
849 628 $output .= '<p>MySQL query:</p>' . PHP_EOL;
850 629 $output .= '<p>' . $driver->get_last_mysql_query() . '</p>' . PHP_EOL;