Skip to content
This repository was archived by the owner on Jun 2, 2025. It is now read-only.

Database configuration and migration #42

Merged
merged 39 commits into from
May 5, 2025
Merged

Database configuration and migration #42

merged 39 commits into from
May 5, 2025

Conversation

JanJakes
Copy link
Contributor

@JanJakes JanJakes commented Apr 15, 2025

This PR extracts existing SQLite database configuration to a WP_SQLite_Configurator and implements WP_SQLite_Information_Schema_Reconstructor with the ability to backfill missing table data in information schema.

How it works:

  1. The configurator checks the SQLite driver version against a version stored in the database, and if the driver is ahead, it will execute the database (re)configuration (see WP_SQLite_Configurator::ensure_database_configured()), while acquiring an EXCLUSIVE lock to prevent race conditions (multiple requests triggering the same migration).
  2. When the configurator runs, it ensures that any internal tables are created (global variables, information schema), and then it proceeds to the WP_SQLite_Information_Schema_Reconstructor.
  3. The reconstructor then compares existing SQLite tables against tables recorded in the information schema, backfills those that are missing, and removes those that no longer exist.
  4. To backfill a table when in WordPress, and it is a WordPress table, the definition is taken from wp_get_db_schema().
  5. To backfill a non-WordPress table, the information schema data is derived from _mysql_data_types_cache, if it exists, otherwise directly from the SQLite schema using SQLite column affinity rules.

@JanJakes JanJakes force-pushed the driver-migration branch 2 times, most recently from 6148a0b to 2250ed3 Compare April 15, 2025 15:13
@JanJakes JanJakes force-pushed the driver-migration branch 4 times, most recently from 7d2c0c1 to 6e94ae8 Compare April 17, 2025 13:42
@JanJakes JanJakes requested a review from adamziel April 17, 2025 14:51
@adamziel
Copy link
Contributor

adamziel commented Apr 17, 2025

Great start! And also a good idea to use a cascade of data sources for backfilling. There's some more way to go, though — I've commented on a bunch of potential issues

@@ -1234,7 +1233,10 @@ private function execute_create_table_statement( WP_Parser_Node $node ): void {
if ( $subnode->has_child_node( 'ifNotExists' ) ) {
$tables_table = $this->information_schema_builder->get_table_name( $table_is_temporary, 'tables' );
$table_exists = $this->execute_sqlite_query(
"SELECT 1 FROM $tables_table WHERE table_schema = ? AND table_name = ?",
sprintf(
Copy link
Contributor

@adamziel adamziel Apr 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the purposes of this PR it's lovely. Thank you for all the thorough escaping!

Future-wise, we should start a separate discussion about query formatting. Having two formatting systems is not ideal, even if only because the sprintf() caller may accidentally pull an extra ? into the query. CC @dmsnell

That being said, I wouldn't worry about that too much here. We have to stop somewhere and that seems like a reasonable boundary.

* wrapper that leaks some of the PDO APIs (returns PDOStatement values, etc.).
* In the future, we may abstract it away from PDO and support SQLite3 as well.
*/
class WP_SQLite_Connection {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about extends \PDO as a way of enforcing the interface? Then, we can preface this with if(!class_exists('\PDO')) { class \PDO {} } to prevent fatal errors on PDO-less installations.

Copy link
Contributor

@adamziel adamziel Apr 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also wonder about clarity. Since this is SQLite-specific, in the future we could have a separate WP_MySQL_Over_SQLite_Connection class that will expect a different SQL dialect and would expose MySQL-specific quote and quote_identifier implementation. Nothing actionable here, I'm just thinking out loud.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adamziel My thinking here was making an "internal" wrapper of PDO\SQLite or SQLite3 (or both) for connection to the SQLite database. The part that will need to expose a unified API (e.g., PDO-like), is the current WP_SQLite_Driver, I think.

But I see making this adhere to the PDO API may make be useful as well, especially to avoid reinventing the APIs.

As for the clarity of the APIs, maybe we could use the DSN prefix as PDO does? Something like:

$connection = new WP_PDO('mysql:host=localhost;dbname=wordpress'); // MySQL
$connection = new WP_PDO('sqlite:dbname=wordpress'); // SQLite -- this is the one we're discussing now
$connection = new WP_PDO('mysql-on-sqlite:dbname=wordpress'); // MySQL on SQLite -- this is the new driver
$connection = new WP_PDO('pgsql:host=localhost;dbname=wordpress'); // Postgres
$connection = new WP_PDO('pgsql-on-sqlite:dbname=wordpress'); // Postgres on SQLite

Copy link
Contributor

@adamziel adamziel Apr 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At first, it felt wrong, but the more I think about it the more I like it. It's the most connection interface we can possibly offer. Really nice idea. If I'm to nitpick, maybe I'd do WP_PDO::connect() instead of new WP_PDO just to return the correct class instance right away and save on WP_PDO proxy where each method just calls $this->actual_driver->method()

@adamziel
Copy link
Contributor

This is looking really good. It seems like the only blocking thing is string escaping in the DEFAULT part of CREATE TABLE and there may be an easy way out of that problem. Is it possible we're almost there?! 🤞

@JanJakes JanJakes force-pushed the driver-migration branch 4 times, most recently from 911962f to 98bc3d7 Compare May 1, 2025 14:01
@JanJakes JanJakes force-pushed the driver-migration branch from eb959e8 to 2a13304 Compare May 2, 2025 08:03
@JanJakes JanJakes marked this pull request as ready for review May 2, 2025 13:13
@JanJakes
Copy link
Contributor Author

JanJakes commented May 2, 2025

@adamziel This should be ready for a final review now. There are some comments that may require moving to separate tickets, but for the core migration functionality, I'd consider this complete.

@JanJakes JanJakes requested a review from adamziel May 2, 2025 13:22
Copy link
Contributor

@adamziel adamziel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beautiful work @JanJakes! Thank you for bearing with my nitpicks and persevering on this journey ❤️ I think this PR is good to go. Was this the last missing bit before enabling the new driver by default? If yes, this marks a profound milestone. We should do a P2 announcement, encourage folks to try to use and break the new driver, and ask for their overall feedback.

@wojtekn once we bundle a new version of the plugin, would you try it with a few Studio sites before a wider rollout? Better safe than sorry.

@JanJakes JanJakes merged commit 82eed09 into develop May 5, 2025
12 checks passed
@JanJakes JanJakes deleted the driver-migration branch May 5, 2025 07:19
@wojtekn
Copy link
Contributor

wojtekn commented May 5, 2025

@wojtekn once we bundle a new version of the plugin, would you try it with a few Studio sites before a wider rollout? Better safe than sorry.

Does the plugin gracefully support existing sites? Does it support a migration path? Should we implement a migration path in Studio?

@JanJakes
Copy link
Contributor Author

JanJakes commented May 5, 2025

Does the plugin gracefully support existing sites? Does it support a migration path? Should we implement a migration pat in Studio?

With these changes, it does support all of that and there is no need to implement anything specific in Studio, apart from using define( 'WP_SQLITE_AST_DRIVER', true );, while the feature flag is still in place. We can also consider setting it to true by default now.

This was not tagged yet, so we may coordinate on that. We'll definitely need to test it more thoroughly with existing sites.

@wojtekn
Copy link
Contributor

wojtekn commented May 7, 2025

Thanks for confirming that @JanJakes, that sounds great.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants