Increment a field to a unique integer when a field updates in Prisma - prisma

In my Prisma schema, I have a model that looks like this:
model Document {
id String #id #default(uuid())
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
draft Boolean #default(true)
publishedDocumentNumber Int? #unique()
text String?
}
When each document is created, it is in draft mode with the value set to true. It does not have a publishedDocumentNumber until it is officially published. When I publish a document, I will update the draft value like this:
prisma.draft.update({
where: {
id: req.body.id,
},
data: {
draft: false,
}
});
Since this document is no longer a draft, I want to safely auto-increment the publishedDocumentNumber value to the previous published document's publishedDocumentNumber value + 1. I don't want to do a prisma.document.count since I could accidentally run into a collision if two documents are publish simultaneously (race condition), and they have to be unique.
Is there a better way to safely do this?

Try using serial or smallserial as described in the documentation

Related

How to update document with specific version

I have a use case where my function first gets a user record, does some work on it and then updates it. To guarantee correct operation, I need to ensure that user record was not updated while such work is being done. Typically, I would do this by adding version to my user model, thus I get current version and update based on this exact version (auto incrementing it with each update), however I am getting a strange uniqueness error that I am not able to figure out:
User model
model User {
uid String #id #default(uuid())
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
version Int #default(0)
email String #unique
}
Update opperation
prisma.user.update({
where: { uid, version },
data: { ...newData, version: { increment: 1 } }
})
Version is highlighted with following error
Type '{ uid: string; version: number; }' is not assignable to type
'UserWhereUniqueInput'. Object literal may only specify known
properties, and 'version' does not exist in type
'UserWhereUniqueInput'.ts(2322)
It's because you need a unique clause to use a where clause on a simple update.
Prisma ask you to give it a unique contraint for this type of update, like for the where clause in a findUnique. Currently you unique constraint is on the email field
Here you have 2 solutions
The good practice?
Add a unique constraint between uid and version
model User {
uid String #id #default(uuid())
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
version Int #default(0)
email String #unique
// new unique constraint
##unique([uid, version])
}
and then you can do your update with the new unique constraint
prisma.user.update({
where: { uid_version: { uid, version } },
data: { ...newData, version: { increment: 1 } }
})
Easy win
If you want, you can also do an updateMany that will not need a unique constraint to filter
prisma.user.updateMany({
where: { uid, version },
data: { ...newData, version: { increment: 1 } }
})

Prisma model typing issue - expected non-nullable type "String", found incompatible value of "null"

Problem
I am using Prisma as an ORM for a psql database. When I try to delete a row from a table (Plant) that my current table (Pod) references I get the error:
Error converting field "plantId" of expected non-nullable type "String", found incompatible value of "null".
My intention is, when the underlying Plant row is deleted, the reference or foreign_key on the Pod rows that reference it, are set to null. However, I can't seem to set it up correctly. I thought by typing it with plantId: String? it would be able to handle that since the ? set's the row to either <type>|null.
I could really use some help, I'm not sure where I'm going wrong.
// Plant
model Plant {
id Int #id #default(autoincrement())
plantId String #unique #default(uuid())
name String #unique
pods Pod[]
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
}
// Pod
model Pod {
id Int #id #default(autoincrement())
podId String #unique #default(uuid())
name String #db.VarChar(200)
hasAlert Boolean
plant Plant? #relation(fields: [plantId], references: [plantId], onDelete: SetNull)
plantId String?
batch Batch #relation(fields: [batchId], references: [batchId], onDelete: Cascade)
batchId String
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
}
Prisma Studio walk through:
Pod has a foreign key pointing to Plant (named broccoli)
In Plant table, I delete the referenced Plant (broccoli) that we saw in Pod
Upon returning to the Pod table, we get the following error:

GORM foreign key doesn't seem to add proper fields

I have the following model:
type Drink struct {
gorm.Model // Adds some metadata fields to the table
ID uuid.UUID `gorm:"type:uuid;primary key"`
Name string `gorm:"index;not null;"`
Volume float64 `gorm:"not null;type:decimal(10,2)"`
ABV float64 `gorm:"not null;type:decimal(10,2);"`
Price float64 `gorm:"not null;type:decimal(10,2);"`
Location Location `gorm:"ForeignKey:DrinkID;"`
}
type Location struct {
gorm.Model // Adds some metadata fields to the table
ID uuid.UUID `gorm:"primary key;type:uuid"`
DrinkID uuid.UUID
Name string `gorm:"not null;"`
Address string `gorm:"not null;type:decimal(10,2)"`
Phone int `gorm:"not null;type:decimal(10,0);"`
}
however, when I run the program, it adds both tables, however there is no location field in the Drink table.
My database looks like this after the migrations, regardless of whether I drop the tables previously:
I have a sneaking feeling it might be because I am not using the gorm default ID, but if that's the case can anyone point me to how to override the default ID with a UUID instead of a uint the proper way? or if that's not even the issue, please, I've been working on this for a few days now and I really don't want to take the "easy" road of just using the defaults gorm provides, I actually want to understand what is going on here and how to properly do what I am trying to do. I am getting no errors when running the API, and the migration appears to run as well, it's just the fields I have defined are not actually showing up in the database, which means that the frontend won't be able to add data properly.
What I WANT to happen here is that a list of stores will be available in the front-end, and when a user adds a drink, they will have to select from that list of stores. Each drink added should only have 1 store, as the drinks prices at different stores would be different. So technically there would be many "repeated" drinks in the drink table, but connected to different Locations.
First point is as you are using custom primary key, you should not use gorm.Model as it contains ID field in it. Reference
Second point is according to your description, store (location) has one to
many relationship with drink. That means a store can have multiple
drinks but a drink should belong to only one store. In one-to-many
relationship there should be a reference or relation id in the many
side. That means in your case in drink table. Then your struct
should look like this:
MyModel Struct
type MyModel struct {
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}
Location Struct (Store)
type Location struct {
MyModel
ID uuid.UUID `gorm:"primary key;type:uuid"`
// other columns...
Drinks []Drink
}
Drink Struct
type Drink struct {
MyModel
ID uuid.UUID `gorm:"type:uuid;primary key"`
//other columns...
LocationID uuid.UUID// This is important
}
Then gorm will automatically consider LocationID in drink table will be referring the ID field of Location Table. You can also explicitly instruct this to gorm using gorm:"foreignKey:LocationID;references:ID" in Location struct's Drinks array field.
Reference

Create a record and connect it to an existing record prisma client (1 to 1 relation)

I'm making a Next JS application with prisma and postgres.
I have 2 tables: User and Profile
Their prisma schema structure is as follows:
model User {
id String #id #default(cuid())
name String?
email String? #unique
emailVerified DateTime?
image String?
// foreign keys
sessions Session[]
profile Profile?
}
model Profile {
id Int #id #default(autoincrement())
isAdmin Boolean #default(false)
firstName String
lastName String
email String #unique
phone String
address String
gender String
image Bytes
guardianName1 String
guardianPhone1 String
guardianRelation1 String
guardianName2 String?
guardianPhone2 String?
guardianRelation2 String?
guardianName3 String?
guardianPhone3 String?
guardianRelation3 String?
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
// foreign keys
user User #relation(fields: [userId], references: [id], onDelete: Cascade)
userId String #default(cuid()) // relation scalar field (used in the `#relation` attribute above)
requests Request[]
}
I'm also using next-auth for the authentication part of this application. So when a user signs up then upon his email verification, next-auth itself adds the user's record to the User table.
Till here, there's no issue.
Then, when the user opens his dashboard for the first time, then he's shown a form to fill, upon submission of that form, a record needs to be inserted in the Profile table. As the Profile and User table's are linked, they also need to be connected.
So when the user submits profile details form, I do this:
try {
const newProfileData = {
// other fields data here...
user: {
connect: { id: '1' } // where User table already has a record with - 'id': 1
}
};
const profile = await prisma.profile.create({ data: newProfileData, include: { user: true } });
if(profile) {
console.log("Created: ", profile);
res.status(200).json({ msg: 'Successfully Created Profile!' });
}
}
catch(err)
{
console.log(err);
}
But upon running this code, I get the error:
The change you are trying to make would violate the required relation 'ProfileToUser' between the `Profile` and `User` models.
...
code: 'P2014',
clientVersion: '2.30.3',
meta: {
relation_name: 'ProfileToUser',
model_a_name: 'Profile',
model_b_name: 'User'
}
How can this be solved?
I even tried it the other way (i.e. updating the existing User and creating the Profile record connected to it):
const user = await prisma.user.update({
where: {
email: req.body.email,
},
data: {
profile: {
create: {
// data fields here... (without the user field)
},
},
},
});
But this also gives the same error...
I want to understand why the error comes. Is this not the correct way to create a record for a 1 to 1 relation using prisma-client?
The fix:
I think you need to remove #default(cuid()) from the Profile's userId field definition.
model Profile {
//...
// foreign keys
user User #relation(fields: [userId], references: [id], onDelete: Cascade)
userId String // relation scalar field (used in the `#relation` attribute above)
//...
}
And also get rid of include: { user: true }:
const profile = await prisma.profile.create({ data: newProfileData});
The explanation:
Profile's user and userId fields don't directly translate to actual columns on the db but are fields that let Prisma handle the link between the relations. It ends up translated to PostgreSQL's
create table profile(
--...
userId text references user (id),
--...
);
And later Prisma will populate that field with your User's id when you issue a user:{connect:{id:'1'}}. What could've happened is when you used #default(cuid()) in userId field definition, you interfered with that process. Now the column ends up as
userId text default gen_random_uuid() references user (id)
and whenever you create a Profile, a new row gets entered without specifying your own userId (which Prisma probably attempts to do before it'll try to link your User), a random id gets generated that doesn't correspond to any existing User, which violates the reference constraint.
It's that and/or your usage of include: { user: true } messes something up spawning a separate, new user, even though you tried to link your Profile to an existing one. But I would expect that to be just an unwanted side-effect making your code spawn a useless User object and row each time you create a Profile.
Once you get rid of the #default(cuid()) you can also just spawn a standalone, unlinked Profile and then link it to the appropriate User later with an update statement.
Merge the two tables into one, something like:
model User {
id String #id #default(cuid())
name String?
email String? #unique
emailVerified DateTime?
image String?
isAdmin Boolean #default(false)
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
// foreign keys
sessions Session[]
}
If you absolutely must have a Profile relation, create a database view:
create view Profile as
select
id,
isAdmin,
name,
email,
createdAt,
updatedAt,
userId
from user
and map it as a read only relation, but I can’t see the point.

`gorm` Ignoring `sql:"index"` Tags

Why gorm is ignoring sql:"index" tags? No indexes got created.
Database in use here is PostgreSQL (importing _ "github.com/lib/pq"). This Model struct is used (because default gorm.Model uses an auto increment number - serial - as primary key and I wanted to set id myself):
type Model struct {
ID int64 `sql:"type:bigint PRIMARY KEY;default:0"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time `sql:"index"`
}
And one of actual models is:
type TUHistory struct {
Model
TUID int64 `json:"tu_id,string" gorm:"column:tu_id" sql:"index"`
}
func (x *TUHistory) TableName() string {
return "tu_history"
}
And the table is created by db.CreateTable(&TUHistory{}) which creates the table correctly except for indexes.
As a temporary work around, I do db.Model(&TUHistory{}).AddIndex("ix_tuh_tu_id", "tu_id") to create indexes.
From my experience, the db.CreateTable only creates the table and it's fields. You are better off using the AutoMigrate function with the model structure that you want to migrate:
db, err := gorm.Open("postgres", connectionString)
...
// error checking
...
db.AutoMigrate(&Model)
Also, I tried AutoMigrating the model you posted and got an error saying that multiple primary keys are not allowed, so I changed the model to:
type Model struct {
Id int64 `sql:"type:bigint;default:0"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time `sql:"index"`
}
and the AutoMigration created all PKs and indexes just fine.
Edit:
Checking the GORM's README, on this example, the Email structure goes as:
type Email struct {
ID int
UserID int `sql:"index"` // Foreign key (belongs to), tag `index` will create index for this field when using AutoMigrate
Email string `sql:"type:varchar(100);unique_index"` // Set field's sql type, tag `unique_index` will create unique index
Subscribed bool
}
Notice the comment on the UserId field saying it will create the index when using AutoMigrate.
Also, it's worth taking a look at how the AutoMigrate does it's job:
// Automating Migration
db.AutoMigrate(&User{})
db.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(&User{})
db.AutoMigrate(&User{}, &Product{}, &Order{})
// Feel free to change your struct, AutoMigrate will keep your database up-to-date.
// AutoMigrate will ONLY add *new columns* and *new indexes*,
// WON'T update current column's type or delete unused columns, to protect your data.
// If the table is not existing, AutoMigrate will create the table automatically.