Skip to content

Conversation

@JanJakes
Copy link
Member

@JanJakes JanJakes commented Oct 2, 2025

Implements support for CHECK constraints:

CREATE TABLE t (
	id INT NOT NULL CHECK (id > 0),
	name VARCHAR(255) NOT NULL CHECK (name != ''),
	score DOUBLE NOT NULL CHECK (score > 0 AND score < 100),
	data JSON CHECK (json_valid(data)),
	start_timestamp TIMESTAMP NOT NULL,
	end_timestamp TIMESTAMP NOT NULL,
	CONSTRAINT c1 CHECK (id < 10),
	CONSTRAINT c2 CHECK (start_timestamp < end_timestamp),
	CONSTRAINT c3 CHECK (length(data) < 20)
)

This includes support for:

  1. CREATE TABLE with inline CHECK (...) for column definitions.
  2. CREATE TABLE with CHECK (...) and CONSTRAINT ... CHECK (...) definitions.
  3. ALTER TABLE ... ADD CHECK (...) and ALTER TABLE ... ADD CONSTRAINT ... CHECK (...) statements.
  4. ALTER TABLE ... DROP CONSTRAINT ... statements.
  5. ALTER TABLE ... DROP CHECK ... statements.
  6. CHECK constraints with the NOT ENFORCED clause.

Resolves #251.

@JanJakes JanJakes marked this pull request as ready for review October 10, 2025 08:51
@JanJakes JanJakes requested review from a team and adamziel October 10, 2025 08:51
Copy link

@sejas sejas left a comment

Choose a reason for hiding this comment

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

I'm not able to run the create table with check constraint. Maybe I'm doing something wrong during my testing steps.

I tested the PR by applying these changes to an existing Studio site mu-plugin, and when I run a CREATE TABLE, I'm getting the following error:

Next WP_SQLite_Driver_Exception: SQLSTATE[HY000]: General error: 1 no such table: _wp_sqlite_mysql_information_schema_check_constraints in /wordpress/wp-content/mu-plugins/sqlite-database-integration/wp-includes/sqlite-ast/class-wp-sqlite-driver.php:5446
Stack trace:
#0 /wordpress/wp-content/mu-plugins/sqlite-database-integration/wp-includes/sqlite-ast/class-wp-sqlite-driver.php(847): WP_SQLite_Driver->new_driver_exception('SQLSTATE[HY000]...', 'HY000', Object(PDOException))
#1 /tmp/sqlite-command/src/Import.php(54): WP_SQLite_Driver->query('CREATE TABLE `m...')
#2 /tmp/sqlite-command/src/Import.php(38): Automattic\WP_CLI\SQLite\Import->execute_statements('studio-backup-s...')
#3 /tmp/sqlite-command/src/SQLite_Command.php(45): Automattic\WP_CLI\SQLite\Import->run('studio-backup-s...', Array)
#4 [internal function]: Automattic\WP_CLI\SQLite\SQLite_Command->import(Array, Array)
#5 /tmp/sqlite-command/vendor/wp-cli/wp-cli/php/WP_CLI/Dispatcher/CommandFactory.php(100): call_user_func(Array, Array, Array)
#6 [internal function]: WP_CLI\Dispatcher\CommandFactory::WP_CLI\Dispatcher\{closure}(Array, Array)
#7 /tmp/sqlite-command/vendor/wp-cli/wp-cli/php/WP_CLI/Dispatcher/Subcommand.php(497): call_user_func(Object(Closure), Array, Array)
#8 phar:///tmp/wp-cli.phar/vendor/wp-cli/wp-cli/php/WP_CLI/Runner.php(470): WP_CLI\Dispatcher\Subcommand->invoke(Array, Array, Array)
#9 phar:///tmp/wp-cli.phar/vendor/wp-cli/wp-cli/php/WP_CLI/Runner.php(493): WP_CLI\Runner->run_command(Array, Array)
#10 phar:///tmp/wp-cli.phar/vendor/wp-cli/wp-cli/php/WP_CLI/Runner.php(136): WP_CLI\Runner->run_command_and_exit()
#11 phar:///tmp/wp-cli.phar/vendor/wp-cli/wp-cli/php/WP_CLI/Runner.php(1349): WP_CLI\Runner->do_early_invoke('after_wp_config...')
#12 phar:///tmp/wp-cli.phar/vendor/wp-cli/wp-cli/php/WP_CLI/Runner.php(1293): WP_CLI\Runner->load_wordpress()
#13 phar:///tmp/wp-cli.phar/vendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/LaunchRunner.php(28): WP_CLI\Runner->start()
#14 phar:///tmp/wp-cli.phar/vendor/wp-cli/wp-cli/php/bootstrap.php(84): WP_CLI\Bootstrap\LaunchRunner->process(Object(WP_CLI\Bootstrap\BootstrapState))
#15 phar:///tmp/wp-cli.phar/vendor/wp-cli/wp-cli/php/wp-cli.php(35): WP_CLI\bootstrap()
#16 phar:///tmp/wp-cli.phar/php/boot-phar.php(20): include('phar:///tmp/wp-...')
#17 /tmp/wp-cli.phar(4): include('phar:///tmp/wp-...')
#18 /tmp/run-cli.php(25): require('/tmp/wp-cli.pha...')
#19 {main}
  thrown in /wordpress/wp-content/mu-plugins/sqlite-database-integration/wp-includes/sqlite-ast/class-wp-sqlite-driver.php on line 5446

    at SQLImporter.importDatabase (/Users/macbookpro/Documents/projects-m3.nosync/studio/dist/main/index.js:14464:17)
    at async SQLImporter.import (/Users/macbookpro/Documents/projects-m3.nosync/studio/dist/main/index.js:14711:7)
    at async importBackup (/Users/macbookpro/Documents/projects-m3.nosync/studio/dist/main/index.js:15970:12)
    at async importSite (/Users/macbookpro/Documents/projects-m3.nosync/studio/dist/main/index.js:16427:20)
    at async Session.<anonymous> (node:electron/js2c/browser_init:2:107024)

Note the difference between the original error vs when I used the code from this PR:

- Next WP_SQLite_Driver_Exception: SQLSTATE[23000]: Integrity constraint violation: 19 NOT NULL constraint failed: _wp_sqlite_mysql_information_schema_table_constraints.CONSTRAINT_NAME in /wordpress/wp-content/mu-plugins/sqlite-database-integration/wp-includes/sqlite-ast/class-wp-sqlite-driver.php:5376
+ Next WP_SQLite_Driver_Exception: SQLSTATE[HY000]: General error: 1 no such table: _wp_sqlite_mysql_information_schema_check_constraints in /wordpress/wp-content/mu-plugins/sqlite-database-integration/wp-includes/sqlite-ast/class-wp-sqlite-driver.php:5446

}

// Translate the check clause from MySQL to SQLite.
$ast = $this->create_parser( 'SELECT ' . $check_constraint['CHECK_CLAUSE'] )->parse();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Smart! I wonder if we could modify the grammar and replace a check clause terminal with the column definition non-terminal 🤔 Nothing blocking here, though, this is cool

Copy link
Member Author

Choose a reason for hiding this comment

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

@adamziel This actually comes from the information_schema.check_constraints table that stores the full MySQL CHECK expression. So the $check_constraint['CHECK_CLAUSE'] is the CHECK_CLAUSE column. We read it from there, translate it with this "hack" to SQLite, and then apply that to SQLite.

A slight difference is—MySQL normalizes the expression a bit; we keep it as it was written.

@adamziel
Copy link
Collaborator

adamziel commented Oct 10, 2025

The code looks great @JanJakes! We just need to create the new table and column on any site upgrading to this plugin version. Is it time to add a simple migrations system that knows which schema version we're using and upgrades us step by step? I know WordPress has one, but I'm not sure we can use it here.

@JanJakes
Copy link
Member Author

The code looks great @JanJakes! We just need to create the new table and column on any site upgrading to this plugin version.

@adamziel That will happen once we tag the release, and the DB configurator will detect that the version was bumped. The call to ensure_information_schema_tables() creates tables that are missing.

@sejas That also explains why you're running into the issue. Try to update the version in these three files, and it should work. And thanks for testing!

@JanJakes JanJakes merged commit 39dd489 into develop Oct 10, 2025
14 checks passed
@JanJakes JanJakes deleted the check-constraints branch October 10, 2025 10:46
@sejas
Copy link

sejas commented Oct 10, 2025

@sejas That also explains why you're running into the issue. Try to update the version in these three files, and it should work. And thanks for testing!

Cool! Now that I've upgraded the plugin version, I confirm that the table is created and works as expected. Great work!.
I executed the created table with the CHECK constraint using the queries in this PR and from this issue: #251.

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.

The query with CHECK constraint fails

4 participants