prisma icon indicating copy to clipboard operation
prisma copied to clipboard

Support SQL Check constraints

Open pantharshit00 opened this issue 5 years ago • 62 comments

Right now we have no way of expressing SQL check constraints in our schema. We need a spec for this that we can implement in the future.

pantharshit00 avatar Dec 03 '19 09:12 pantharshit00

Do we have any idea of what the API could look like?

The easy way is to have a @check() attribute, and let a raw SQL string be passed as an argument. This is very similar to what typeorm does, except it might not be ergonomic to put raw SQL strings in the prisma schema.

// typeorm snippet
@Entity()
@Check(`"name" IS NULL OR "lastName" IS NOT NULL`)
@Check(`"age" >= 18`)
export class User { ... }

The first check asserts that if there is ever a value for "name" there must also be a value for "lastName". SQL conditions are very verbose, but can also be very powerful.

The second way would be also using a @check() attribute but passing in Prisma's own syntax rather than raw SQL. It could allow for the check to be done in the Prisma layer rather than in the DB layer, so this would work for any datasource.

model User {
  id        Int               @id
  customer  Customer?
  name      String
  lastName  String
  age       Int
  @@check(name || !lastName)
  @@check(age >= 18)
}

This could be a challenge, but if the syntax allows conditions as powerful as SQL's it's probably the way to go.

A last alternative would be to have a code-first way to define the schema (which I'd prefer). In that case we could easily use a programming languages tools to validate the values.

Check constraints are of extreme importance for defining the possible states of a row, so I hope there will be a solution for this by the time Prisma2 launches! I wouldn't consider it a ready for prod lib yet without that feature.

Ericnr avatar Feb 19 '20 19:02 Ericnr

I'm currently using check constraints in my database instead of postgres enums. I've been annotating my schema as follows:

model Goal {  
 id              Int            @id @default(autoincrement()) @db.Integer  
 type            GoalType       @db.Text  
 @@map("goals")  
}

enum GoalType {  
 DEBT  
 SAVING  
}  

Unfortunately, introspection removes the custom enum I defined since the column type is actually a text column:

model Goal {  
 id              Int            @id @default(autoincrement()) @db.Integer  
 type            String         @db.Text  
 @@map("goals")  
}  

Here is the CREATE TABLE expression for the goals table. Take special note that the type column is not a PostgreSQL enum, but rather a text column with a CHECK constraint.

CREATE TABLE "public"."goals" (  
   "id" int4 NOT NULL DEFAULT nextval('goals\_id\_seq'::regclass),  
   "type" text NOT NULL CHECK (type = ANY (ARRAY\['DEBT'::text, 'SAVING'::text\])),  
   CONSTRAINT PRIMARY KEY ("id")  
);  

FYI, this comment is related to an issue I had opened for the above issue: #4770

robertcoopercode avatar Dec 24 '20 20:12 robertcoopercode

This missing feature is the only reason that keeps me from switching from TypeORM to Prisma.

I would extremely appreciate to be able to create and manage my database schema completely with Prisma Migrate. Prisma's schema language is much more readable and clear than pure SQL (DDL) and has the big advantage over ORMs like TypeORM of not being "code first" and language/platform dependent.

If Prisma Migrate had support for check constraints, it could position itself as a true "database version control" tool, independent of the superior prisma client. In this way, it could outperform classics like Liquibase or Flyway in the future, even beyond the NodeJS community.

Is this feature planned?

stonymahony avatar Feb 24 '21 23:02 stonymahony

Hey, I want to also voice my interest in having this ability in the prisma schema. And I also noticed there are no plans to implement this functionality any time soon ... at least not according to the roadmap. Is this correct?

supermacro avatar Mar 11 '21 19:03 supermacro

I would like to second this. Seeing all the guides talk about pushing raw SQL to the db in order to make this happen made me very sad.

2-am-zzz avatar May 20 '21 04:05 2-am-zzz

We would like to see this as well if possible.

reubenporterjisc avatar Oct 26 '21 12:10 reubenporterjisc

I am also interested; it might be worth following #3102 as well.

FlorianCassayre avatar Jan 08 '22 09:01 FlorianCassayre

Would also be interested in this feature

aalises avatar Feb 07 '22 10:02 aalises

+1!

npwork avatar Feb 07 '22 21:02 npwork

It would be great to have check constraints right in prisma schema.

vivek7405 avatar Feb 21 '22 14:02 vivek7405

Would love to see that feature

sochacki111 avatar Mar 18 '22 11:03 sochacki111

If only this feature was included in the roadmap. This is the only killjoy in migrating to prisma

kings1ay3r avatar May 18 '22 14:05 kings1ay3r

The full generality of CHECK would obviously make it very difficult to simultaneously support it and also support non-SQL databases and the generation of validation code for APIs, &c. What if a limited number of CHECK terms were introduced? For example, @check(len <= 8), @check(isLower) and a few others like that? The full generality of CHECK would still be available the way other features without a PSL equivalent are available, via DDL (the full generality of it is dependent on the database so how could that be avoided?).

solidsnack avatar May 28 '22 18:05 solidsnack

I would appreciate if this feature is added, it would be a game changer!

Kasparov2000 avatar Jun 24 '22 09:06 Kasparov2000

This is especially important for flavors of SQL that do not support ENUMs, such as SQL Server.

YaakovR avatar Jul 18 '22 13:07 YaakovR

please provide this check constraint feature

sankalpmukim avatar Sep 17 '22 12:09 sankalpmukim

Any news on this feature please 🙏 ?

Marwen94 avatar Sep 26 '22 10:09 Marwen94

its 2022, is the technology here yet?

ravi-o4s avatar Oct 16 '22 16:10 ravi-o4s

Hi. Is there any news about this feature?

mwanago avatar Jan 09 '23 03:01 mwanago

I would also appreciate this feature

Beiri22 avatar Jan 11 '23 13:01 Beiri22

@janpio Hello, is there any update for this feature? Do you planning to add this feature in close future?

lSelectral avatar Jan 14 '23 17:01 lSelectral

If there is no message in an issue, or an entry for the issue in our roadmap at https://pris.ly/roadmap, then there is no update from us. If the issue is still open and marked as feature or improvement it means there is potential that we will tackle an issue in the future.

We will definitely do SQL checks one day - but I can not say when we will get to it. Right now it is unfortunately not a priority as we are working on other things.

janpio avatar Jan 17 '23 00:01 janpio

Heyyy, I'm back. Could you please share as many examples of check constraints SQL here as possible?

The difficult bit for this feature will definitely be to figure out if we can represent all the checks in a structured way in Prisma Schema Language (and hence potentially even execute them in Prisma Client, skipping the database roundtrip - as suggested in https://github.com/prisma/prisma/issues/1604) or if we "just" present them as magic SQL strings without understanding what they do, and "only" take care of migrating and introspecting them.

We have a few examples at https://github.com/search?q=repo%3Aprisma%2Fdatabase-schema-examples+%22+CHECK+%22&type=code - but real-world examples from our actual users would of course be so much more useful. Thanks!

janpio avatar Mar 25 '23 09:03 janpio

Some examples I found in other issues:

  • https://github.com/prisma/prisma/issues/18301 - PostgreSQL, on domains

    REATE DOMAIN language_sample AS VARCHAR(3)[]
     CONSTRAINT no_empty_element CHECK (NOT VALUE @> '{""}')
     CONSTRAINT is_uppercase CHECK (array_to_string(VALUE, ',') =
                                    upper(array_to_string(VALUE, ',')));
    

    2 more examples for that here: https://github.com/prisma/prisma/issues/1796

    REATE DOMAIN year AS integer
    CONSTRAINT year_check CHECK (((VALUE >= 1901) AND (VALUE <= 2155)));
    
    REATE DOMAIN percent_max_100
       AS DOUBLE PRECISION
       CONSTRAINT max_100 CHECK ( value <= 100 )
    
  • https://github.com/prisma/prisma/issues/16654 - MySQL, json validation

    REATE TABLE `User` (
     `permissions` json NOT NULL,
     CHECK(JSON_SCHEMA_VALID('{ "type": "array", "items": { "type": "string" }}', permissions))
    
    
  • https://github.com/prisma/prisma/issues/15173 - Postgres, simple check

    reate table appointment (
    account_id uuid not null default(current_setting('app.account')::uuid) references account(id),
    date tstzrange not null,
    constraint "CHECK_Appointment_date_lower_not_null" check (lower(date) is not null),
    constraint "CHECK_Appointment_date_lower_not_infinity" check (lower(date) > '-infinity'),
    constraint "CHECK_Appointment_date_upper_not_null" check (upper(date) is not null),
    constraint "CHECK_Appointment_date_upper_not_infinity" check (upper(date) < 'infinity')
    ;
    
  • https://github.com/prisma/prisma/issues/6823 - SQL Server, emulating an enum

    REATE TABLE dbo.Table_1
    (
    /* You could create a reusable "Enum" by creating it as a type,
    	or just adding the check constraint for a single one */
    Test dbo.TestType NULL,
    Test2 nchar(5) NOT NULL 
    	CONSTRAINT Test2_Enum CHECK (Test2 IN('USER', 'ADMIN')),
    )  ON [PRIMARY]
    O
    
  • https://github.com/prisma/prisma/issues/2505#issuecomment-1425661931 - MySQL, JSON validity check

    REATE TABLE `comments` (
     `id` bigint(20) unsigned NOT NULL,
     `body` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL CHECK (json_valid(`body`)),
     PRIMARY KEY (`id`)
    ;
    

Pretty diverse set of functionality and examples already, but we obviously need more. Please comment to help out!

janpio avatar Mar 25 '23 10:03 janpio

A few examples from my side (mySQL):

ALTER TABLE `course_model`
    ADD CONSTRAINT `weekday_check` CHECK (`weekday` < 7),
    ADD CONSTRAINT `time_start_check` CHECK (`time_start` RLIKE '^([0-1]\\d|2[0-3]):[0-5]\\d$'),
    ADD CONSTRAINT `time_end_check` CHECK (`time_end` RLIKE '^([0-1]\\d|2[0-3]):[0-5]\\d$');

ALTER TABLE `course`
    ADD CONSTRAINT `cancelation_check` CHECK ((`is_canceled` OR (`cancelation_reason` IS NULL)) AND (`is_canceled` = (`canceled_at` IS NOT NULL)));

ALTER TABLE `course_bundle_registration`
    ADD CONSTRAINT `user_cancelation_check` CHECK (`is_user_canceled` = (`canceled_at` IS NOT NULL));

FlorianCassayre avatar Mar 25 '23 10:03 FlorianCassayre

Another use case, single row tables:

CREATE TABLE "System" (
    "id" INTEGER NOT NULL PRIMARY KEY CHECK (id = 1),
    ...
);

jonchardy avatar May 16 '23 14:05 jonchardy

A few examples I've come across:

CREATE TABLE Shipment (
    # ...
    CONSTRAINT valid_status CHECK (status IN ('created', 'cancelled', 'install_ready', 'install_in_progress', 'install_completed')),
    CONSTRAINT building_id_set_when_ready CHECK (status IN ('created', 'cancelled') OR building_id IS NOT NULL)
);
CREATE TABLE ShipmentDevice (
    # ...
    CONSTRAINT required_fields_set_when_completed CHECK (NOT (completed AND (zone_id IS NULL OR installation_timestamp IS NULL)))
);

(whether these should be DB constraints or not is up for debate, just examples I've run into recently for your collection!)

RyKilleen avatar Aug 21 '23 18:08 RyKilleen

@RyKilleen If I am not mistaken, the first constraint could be expressed using a Prisma enum natively.

FlorianCassayre avatar Aug 21 '23 19:08 FlorianCassayre

+1 for this feature. Very much needed

mericano1 avatar Aug 23 '23 09:08 mericano1

Heyyy, I'm back. Could you please share as many examples of check constraints SQL here as possible?

The difficult bit for this feature will definitely be to figure out if we can represent all the checks in a structured way in Prisma Schema Language (and hence potentially even execute them in Prisma Client, skipping the database roundtrip - as suggested in #1604) or if we "just" present them as magic SQL strings without understanding what they do, and "only" take care of migrating and introspecting them.

We have a few examples at https://github.com/search?q=repo%3Aprisma%2Fdatabase-schema-examples+%22+CHECK+%22&type=code - but real-world examples from our actual users would of course be so much more useful. Thanks!

@janpio - implementing this as magic SQL strings is obviously the easier and faster option. Therefore, as an MVP, I think it would make more sense to provide this first so folks are at least able to enjoy Prisma with Checks (since as you can see it's currently a blocker for many people to migrate to Prisma altogether).

At a later iteration upgrading it to be using Prisma annotations would be great of course but providing it as magic SQL strings first would let you under-prioritize this upgrade for way down the line since it would at least not be a blocker for anyone.

eyal-confetti avatar Sep 04 '23 21:09 eyal-confetti