Proposal: Include Index and Constraint Names in Prisma schema
See original GitHub issueWe propose an addition to the Prisma Schema Language syntax, please chime in if you have ideas / concerns regarding this proposal. There has been a lot of internal consideration of this proposal and we want to push forward with this, but we also want to give the community the opportunity to raise concerns or ideas for improvement. But we want to time box the comment period and are aiming for a final decision in about a week.
Status Quo
Currently the Prisma schema does not always include the names for different indexes and constraints as given in the database. The datamodel below shows the examples for these:
- single-field primary key:
@id
- single-field unique index:
@unique
- foreign key constraints:
@relation
- multi-field primary key:
@@id
- multi-field unique constraint (here the name is actually introspected currently):
@@unique
- index definition:
@@index
- default values (only on SQLServer are these constraints) :
@default
When creating these entities, in most cases they are given a name at the database level. Currently, Prisma has its own naming convention and therefore does not require or allow users to specify these names (@@unique
being the exception where it is allowed but not required).
model A {
id Int @id
name String @unique
a String @default("Test")
b String
B B[] @relation("AtoB")
@@unique([a, b], name: "compound")
@@index([a], name: "index")
}
model {
a String
b String
aId Int
A A @relation("AtoB", fields: [aId], references: [id])
@@id([a, b])
}
Problem
When interacting with constraints / indexes via migrations, often it is necessary to address them by name, e.g.
Alter Table A Drop Constraint A.a_b_unique;
Since the names are currently not part of the datamodel, Prisma migrate relies on the constraint names matching our internal naming convention. If that is not the case invalid SQL migrations can be created. This happens for example if an existing database is introspected and then a dev database created on the base of the introspected datamodel. The dev database will contain different constraint names and so will the migrations created on it. These will then fail when applied to the originally introspected database.
Two of these constraints, namely @@unique
and @@id
have meaning for the generated Prisma client. They can be used to uniquely address a row. For @@unique
we introspect the constraint name and make it part of the datamodel as the name
argument. This is then handed through to the Client and becomes a typename. In cases where the database name contains characters that result in invalid identifiers, that breaks the generated Client. Examples of invalid chars are .
and -
. Unfortunately our own naming convention includes .
.
There is currently no way using Prisma tooling to compare the names of these entities between different environments (dev and prod) for example to make sure they are identical before running a migration. Introspecting both would not help since the names are not shown in the datamodel. The suggested solution would solve this.
Suggested solution
We first propose a new naming convention since our current one is breaking our own Client generation in some cases. Additionally it is not uniform across databases. In order to not overload the datamodel when not necessary we want to NOT render the names if they match our new naming convention. Ideally, our IDE tooling will still be able to show you the underlying name as tooltip even if it is not included in the Prisma datamodel. We propose to align with the Postgres naming convention, this way not only Greenfield projects created with Prisma Migrate will be able to not render the names, but the same should be true for most autogenerated names on Postgres.
Postgres Naming conventions
{tablename}_{columnname(s)}_{suffix}
where the suffix is one of the following:
pkey for a Primary Key constraint
key for a Unique constraint
excl for an Exclusion constraint
idx for any other kind of index
fkey for a Foreign key
check for a Check constraint
seq for sequences
our own addition for SQLServer
dflt for Default Constraint
For cases where the names will be rendered in the datamodel since they do not match the naming convention we propose the following syntax:
model A {
id Int @id("A_pkey")
name String @unique("A_name_key")
a String @default("Test", map: "A_a_dflt") //just on sql server
b String
B B[] @relation("AtoB")
@@unique([a, b], name: "compound", map: "A_a_b_key")
@@index([a], map: "A_a_idx")
}
model {
a String
b String
aId Int
A A @relation("AtoB", fields: [aId], references: [id], map: "B2_aId_fkey")
@@id([a, b], name: "ab", map: "B_pkey")
}
We are reusing map
as an argument name since that is already how we are signifying the database name for Fields and Models. In cases where this would be the first and only argument like on @unique
and @id
, the name does not need to be specified.
In the datamodel above all database names are shown explicitly to explain the syntax. But since they are following our proposed naming convention, they do not need to be rendered. So the datamodel will look like the one below even while our tooling will still be able to use all the information from the one above.
model A {
id Int @id
name String @unique
a String @default("Test")
b String
B B[] @relation("AtoB")
@@unique([a, b], name: "compound")
@@index([a])
}
model {
a String
b String
aId Int
A A @relation("AtoB", fields: [aId], references: [id])
@@id([a, b], name: "compound")
}
Breaking Changes
Our own naming convention will change. This means creating migrations with a datamodel without explicit names against a database migrated before these changes are introduced can result in invalid migrations due to name mismatches. We believe this can be alleviated with our own tooling by either introspecting the target database once (and thereby fetching the explicit names into your datamodel) or by generating a migration that adjusts the names to follow our convention using Prisma migrate.
Alternatives
We also considered dynamically fetching the constraint / index names within the generated migrations based on the existing information in the Prisma datamodel. This would have alleviated the need for names, but would result in SQL that is extremely hard to audit for users and necessitate a lot of database specific dynamic SQL that might be brittle.
Additionally, there might be user use cases outside of migrations where you want to be able to know / influence the name of these entities, e.g. for Index hints.
Additional context
- https://github.com/prisma/prisma/issues/6143 -> general naming inprovements in PSL
- https://github.com/prisma/prisma/issues/4010 -> name arg for compound id missing
- https://github.com/prisma/prisma/issues/3964 -> dot in index name
- https://github.com/prisma/prisma/issues/4182 -> dash in index name
- https://github.com/prisma/prisma/issues/2393 -> dash in index name
- https://github.com/prisma/prisma/issues/6505 → migrate drift detection caused by index names
- https://github.com/prisma/prisma-engines/issues/962 -> validate name length
- https://github.com/prisma/migrations-team/issues/157 -> drift caused by index names
Issue Analytics
- State:
- Created 2 years ago
- Reactions:4
- Comments:7 (5 by maintainers)
Top GitHub Comments
We decided to make the usage of legacy usage of the name argument on
@@index
non-breaking. We will just under the hood read it as map. This way existing data models can still be parsed.You are probably aware of this, but I’d like to point out the max length for an index name in Postgres is 63 bytes. You may need to truncate the computed name, so I’d recommend moving the
suffix
between the table name and the columns so when truncation occurs, the salient suffix qualifier remains.One other alternative is to compute a hash of the generated index name using your convention (or merely the column names), and use that hash instead. It’ll be less readable by a human but can be computed easily between clients.