Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v2.1.12 #131

Merged
merged 11 commits into from
Jul 29, 2024
Merged

v2.1.12 #131

merged 11 commits into from
Jul 29, 2024

Conversation

aristath
Copy link
Member

No description provided.

adamziel and others added 11 commits June 6, 2024 01:25
Fixes a series of crashes encountered when

* SHOW TABLES now returns a flat list, not an array of objects – I'm not
sure here. It makes `$tables = $wpdb->get_results("SHOW TABLES LIKE
'{$prefix}%'", ARRAY_N);` work as expected
[here](https://github.com/WordPress/playground-tools/blob/128123e84e25cf0421770f6885c6f26aaf0b05dc/packages/playground/src/playground-db.php#L90-L91),
but is the right thing to return, or is this more about correct handling
of the fetch mode, like ARRAY_N?
* `SHOW tables` isn't all uppercase
* There's no table tho SHOW CREATE: `SHOW CREATE TABLE _no_such_table;`
* The table identifier passed to SHOW CREATE is quoted:

```
'SHOW CREATETABLE `_tmp_table`;'
```

This PR makes the [Sandbox site
plugin](https://wordpress.org/plugins/playground/) work in Playground

cc @bgrgicak @brandonpayton @wojtekn
There's more nuance to the type of the returned value than #117
antitipated. With that PR in, WooCommerce wouldn't work:

```
[06-Jun-2024 16:21:26 UTC] PHP Fatal error:  Uncaught TypeError: get_object_vars(): Argument #1 ($object) must be of type object, string given in /wordpress/wp-includes/class-wpdb.php:3
Stack trace:
#0 /wordpress/wp-includes/class-wpdb.php(3): get_object_vars('wp_wc_product_a...')
#1 /wordpress/wp-content/plugins/woocommerce/src/Internal/ProductAttributesLookup/LookupDataStore.php(69): wpdb->get_var('SHOW TABLES LIK...')
#2 /wordpress/wp-content/plugins/woocommerce/src/Internal/ProductAttributesLookup/LookupDataStore.php(650): Automattic\WooCommerce\Internal\ProductAttributesLookup\LookupDataStore->check_lookup_table_exists()
```

Let's revert and revisit later on

cc @bgrgicak @wojtekn
…e` (#126)

Fixes: #122 

This fix will skip over tokens until it finds the closing bracket of the
type modifier `)` instead of consuming exactly 3. This should account
for data types that support more than one value as a modifier, such as:
enum, float, decimal, double.

Additionally, I've added support for enums. There are plugins that use
enum fields and it makes sense to convert those to text in SQLite.

## Testing instructions
Confirm the tests pass
In a table like
```sql
CREATE TABLE `test` (
	`id` INT,
	`name` VARCHAR(255),
	`other` VARCHAR(255),
	PRIMARY KEY (id),
	UNIQUE KEY (name)
)
```

the internal name for the unique key would be `(` because typically a
key would be defined with
```
UNIQUE KEY `name` (name)
```

Usually this wrong key name is not a problem unless a `INSERT .. ON
DUPLICATE KEY` statement is used where the resulting `ON CONFLICT(
"keyname" )` would be transformed to `ON CONFLICT( "" )` which then
fails.

To fix this, this now derives a key name in case it is omitted..

cc @costasovo
Fixes: #123 

In this PR i propose a refactor of the handling of `show create table`
queries.

The main issue was that it is possible for a primary key to be composite
(more than one column). The existing implementation only supports a
single PK which causes the `CREATE TABLE` statement to be invalid. An
example of such a table is the `wp_term_relationships` found in
WordPress:

```mysql
-- original MySQL statement
CREATE TABLE `wp_term_relationships` (
 `object_id` bigint(20) unsigned NOT NULL DEFAULT 0,
 `term_taxonomy_id` bigint(20) unsigned NOT NULL DEFAULT 0,
 `term_order` int(11) NOT NULL DEFAULT 0,
 PRIMARY KEY (`object_id`,`term_taxonomy_id`),
 KEY `term_taxonomy_id` (`term_taxonomy_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

-- statement produced by SQLite database integration
CREATE TABLE wp_term_relationships (
	`object_id` bigint(20) unsigned PRIMARY KEY AUTO_INCREMENT NOT NULL,
	`term_taxonomy_id` bigint(20) unsigned NOT NULL,
	`term_order` int(11) NOT NULL,
	KEY wp_term_relationships__term_taxonomy_id (term_taxonomy_id),
	UNIQUE KEY sqlite_autoindex_wp_term_relationships_1 (object_id, term_taxonomy_id)
);
```
SQLite also creates a unique key for any primary key (single or
composite) which is something that is not necessary for MySQL and should
be skipped from the final create statement.

## In this PR

- Refactor the code to separate things into smaller methods.
- Add the PK definition before the keys and remove it from the column
definitions.
- Use backticks around table names, column names and key identifiers.
This is to prevent issues when names of identifiers are using reserved
keywords, ensure case sensitivity and allow them to contain special
chars or spaces.
- Improve detection of AUTO_INCREMENT column. Now we falsely mark
columns as AUTO_INCREMENT just because they happen to be the primary key
and be of type integer.
- Fix creating an invalid UNIQUE KEY for the PK. When we find that the
unique key is identical to the primary key, we can skip the unique key
as it is redundant.

The final query output for `show create wp_term_relationships` looks as
follows:

```mysql
DROP TABLE IF EXISTS `wp_term_relationships`;
CREATE TABLE `wp_term_relationships` (
	`object_id` bigint(20) unsigned NOT NULL DEFAULT 0,
	`term_taxonomy_id` bigint(20) unsigned NOT NULL DEFAULT 0,
	`term_order` int(11) NOT NULL DEFAULT 0,
	PRIMARY KEY (`object_id`, `term_taxonomy_id`),
	KEY `wp_term_relationships__idx_term_taxonomy_id_x` (`term_taxonomy_id`)
);
```

## Testing

- Run the unit tests
- Create a site with SQLite and then run `$wpdb->query('show create
wp_term_relationships')` - The result should be a valid MySQL compatible
query as shown above.
In this PR I propose that we always check if a wp core function is
defined before attempting to use it, [as we do in the constructor of the
Translator
class](https://github.com/WordPress/sqlite-database-integration/blob/main/wp-includes/sqlite/class-wp-sqlite-translator.php#L564).

Several hooks (do_action, apply_filters) are used in the
`WP_SQLite_Translator` class. The class may be used in a context where
WordPress is not loaded.

We are currently working on adding support for SQLite to the WP Cli
import and export commands and intend to use the translator to execute
various SQL statements. Those commands run in a context where WordPress
has not been loaded so `do_action` and `apply_filters` are unavailable.
Fixes: #124 

This PR introduces some changes to how we determine the name of an
index. SQLite requires that each index is uniquely named across the
database (not just the table). This causes issues when multiple tables
have the same key names when importing from MySQL. To avoid these
issues, the index name is prefixed with the table name when it is
created. This however results in the indexes having names that are too
long for MySQL so when we run `show create table` the create statements
have these prefixed keys that are too long.

To solve this, we keep adding the table name as a prefix but we remove
it when we generate the create statement. Furthermore, we ensure that
the table name itself has no double underscores in it so we can be sure
that we can split on `__` safely. The result is that each create table
statement will have the original index name and prevents it from being
too long.
See:
#133 (comment)

When creating new tables in SQLite, original index names are prefixed
with the table name. This is because unlike MySQL, SQLite does not allow
identical index names across the database. Adding a table prefix ensures
the index names are unique.

When running a `show create table` query, we restore the original index
name by removing the table prefix. We split on double underscores but we
did not take into account that the original index name can also contain
`__`. This PR fixes an issue where we explode without a limit. We only
want to explode on the first occurrence of `__`. Unit tests have been
added to ensure this behavior.
@aristath aristath merged commit a6d85a6 into main Jul 29, 2024
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants