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

Support for CHECK constraints #358

Open
luxint opened this issue Dec 16, 2021 · 7 comments
Open

Support for CHECK constraints #358

luxint opened this issue Dec 16, 2021 · 7 comments
Labels
enhancement New feature or request

Comments

@luxint
Copy link

luxint commented Dec 16, 2021

Hi,

I noticed the transform.table() method doesn't have an option to add/change or drop a check constraint (see https://sqlite.org/lang_createtable.html -> 3.7 Check Constraints. would be great to have this as an option!

@simonw simonw changed the title table.transform missing the ability to add/change/drop check constraint Support for CHECK constraints Dec 16, 2021
@simonw
Copy link
Owner

simonw commented Dec 16, 2021

This goes beyond the transform() method - the curious methods that create new SQL tables could benefit from the ability to add CHECK constraints too.

I haven't used these myself, do you have any CREATE TABLE examples that use them that you can share?

@luxint
Copy link
Author

luxint commented Dec 17, 2021

This goes beyond the transform() method - the curious methods that create new SQL tables could benefit from the ability to add CHECK constraints too.

I haven't used these myself, do you have any CREATE TABLE examples that use them that you can share?

I'm using them myself for the first time as well, this is a tutorial of how to use (and change) them in sqlite: https://www.sqlitetutorial.net/sqlite-check-constraint/

@simonw simonw added the enhancement New feature or request label Dec 17, 2021
@simonw
Copy link
Owner

simonw commented Sep 22, 2022

I've been thinking about this more recently.

I think the first place to explore these will be in the create-table command (and underlying APIs).

Relevant docs: https://www.sqlite.org/lang_createtable.html#check_constraints

A CHECK constraint may be attached to a column definition or specified as a table constraint. In practice it makes no difference. Each time a new row is inserted into the table or an existing row is updated, the expression associated with each CHECK constraint is evaluated and cast to a NUMERIC value in the same way as a CAST expression. If the result is zero (integer value 0 or real value 0.0), then a constraint violation has occurred. If the CHECK expression evaluates to NULL, or any other non-zero value, it is not a constraint violation. The expression of a CHECK constraint may not contain a subquery.

Something like this:

sqlite-utils create-table data.db entries id integer title text tags text --pk id --check tags:json

Where --check tags:json uses a pre-baked recipe for using the SQLite JSON function to check that the content is valid JSON and reject it otherwise.

Then can bundle a bunch of other pre-baked recipes, but also support the following:

--check 'x > 3' --check 'length(phone) >= 10'

The besign reason for the column:recipe format here is to reuse --check for both pre-defined recipes that affect a single column AND for freeform expressions that get added to the end of the table.

Detecting column name:recipe with a regex feels safe to me.

@simonw
Copy link
Owner

simonw commented Sep 22, 2022

Quick demo of a check constraint for JSON validation:

sqlite> create table test (id integer primary key, tags text, check (json(tags) is not null));
sqlite> sqlite> insert into test (tags ('["one", "two"]');
sqlite> insert into test (tags) values ('["one", "two"');
Error: stepping, malformed JSON (1)

@simonw
Copy link
Owner

simonw commented Sep 22, 2022

A few other recipes off the top of my head:

  • title:maxlength:20 - set a max length, length(title) <= 20
  • created:date - check for yyyy-mm-dd date, select :date == date(:date) is not null (demo)
  • age:positiveint - check age is a positive integer, printf('%', age) = age and age > 0 (untested)

@simonw
Copy link
Owner

simonw commented Sep 22, 2022

Make me think also that sqlite-utils create-table should have an option to dump out the SQL without actually creating the table.

@luxint
Copy link
Author

luxint commented Sep 25, 2022

HI Simon,

looks good, I noticed you wanted to use a regex to detect it, you might be interested in github.com/iafisher/sqliteparser which creates an ast of the create table statement, not every option supported yet but i forked it and am adding all the possible options in a create table (and create index) statement.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants