How to update document with specific version - prisma

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 } }
})

Related

How can I select in prisma the elements with a condition on a 1-N relation?

I have the following prisma schema
model User {
id Int #id #default(autoincrement())
userName string #unique
complaints Complaint[]
}
model Complaint {
id Int #id #default(autoincrement())
user User #relation(fields: [userId], references: [id])
creationTime DateTime #default(now())
userId Int
priority ComplaintPriority
}
enum ComplaintPriority {
HIGH
MEDIUM
LOW
}
I need to select the users that have last complaint (as last I mean the complaint with latest creationTime) with priority value HIGH.
In other words:
if an user has 3 complaints and the last of these complaints has high priority the user should be part of the result (ignoring previous complaints)
If an user has 8 complaints (maybe some of those with high priority) and the last one has low priority the user should not be part of the results
If the user has no complaints at all the user should not be part of the results
I didn't find the prisma syntax for this operation. Does anybody has any idea how to do it?
I looked into this a bit, unfortunately, I don't think there's a way to create a query exactly as you have in mind (as of version 3.5.0 of Prisma).
Here's a workaround that you could perhaps consider:
Fetch all user records that have at least one complaint with HIGH priority. Include complaint records and order them by creationTime.
Manually filter through the list in Node.js to keep appropriate user records.
let users = await prisma.user.findMany({
where: {
complaints: {
some: {
priority: "HIGH"
}
}
},
include: {
complaints: {
orderBy: {
creationTime: "desc"
}
}
}
})
users = users.filter(user => user.complaints[0].priority == "HIGH")
It should be noted though, this isn't perfectly optimal if there are really high number of user records. In such a case, I would consider creating a raw SQL query using rawQuery.

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.

Updating many-to-many relations in Prisma

I have a group of checkboxes for skin concerns. Users can check/uncheck them before submitting, which means the set of skin concerns submitted can be different every time.
I modeled it in Prisma schema as an 'explicit' many-to-many relation.
model User {
id String #id #default(cuid())
name String?
nickname String? #unique
...
skinConcerns SkinConcernsForUsers[]
...
}
model SkinConcern {
id Int #id #default(autoincrement())
name String #unique
user SkinConcernsForUsers[]
}
model SkinConcernsForUsers {
user User #relation(fields: [userId], references: [id])
userId String
skinConcern SkinConcern #relation(fields: [skinConcernId], references: [id])
skinConcernId Int
##id([userId, skinConcernId])
}
Then, SkinConcerns table is seeded with the following values, using prisma.skinConcern.createMany:
"ACNE",
"DRYNESS",
"OILY_SKIN",
"PIGMENTATION",
"REDNESS",
"WRINKLES",
SkinConcerns in Update mutation input comes in the form of array of strings, e.g. ["PIGMENTATION", "REDNESS"].
I want to update the skin concerns for users (SkinConcernsForUsers) from the prisma.user.update query, but it's tricky, since I'm not merely creating SkinConcerns, but have to connect to existing set of skin concerns.
I've tried directly setting skinConcerns in user, like
await prisma.user.update({
where: { nickname },
data: {
// ... other user data
skinConcerns: {
set: [
{
skinConcern: {
connect: { name: "PIGMENTATION" },
},
},
{
skinConcern: {
connect: { name: "REDNESS" },
},
},
],
},
// ... other user data
}
});
among many other things, but of course this is not a correct argument and fails with error
Unknown arg `connect` in data.skinConcerns.update.0.where.connect for type SkinConcernsForUsersWhereUniqueInput. Did you mean `select`?
Argument data for data.skinConcerns.update.0.data is missing.
Unknown arg `connect` in data.skinConcerns.update.1.where.connect for type SkinConcernsForUsersWhereUniqueInput. Did you mean `select`?
Argument data for data.skinConcerns.update.1.data is missing.
Is there a way to do this? Is it even possible to update this in prisma.user.update?
I guess I could directly update SkinConcernsForUsers. In that case, should I just delete all rows associated to the user that are not in the user input ["PIGMENTATION", "REDNESS"], then create rows that don't already exist? What will it look like in prisma code?
First I would change your schema for SkinConcern. The id field is not necessary and will create complications in queries (you would needlessly need to map each name to id when trying to connect/disconnect records.
The name field is sufficient as the primary key, as it is always unique for a certain record.
The changed schema looks like this
model SkinConcern {
name String #id // name is the new #id.
user SkinConcernsForUsers[]
}
model SkinConcernsForUsers {
user User #relation(fields: [userId], references: [id])
userId String
skinConcern SkinConcern #relation(fields: [skinConcernName], references: [name])
skinConcernName String
##id([userId, skinConcernName])
}
The query you want to do can be executed in two steps with the SkinConcernsForUsers model.
Step 1: Remove existing SkinConcernsForUsers records a user is connected to. These are no longer relevant, as you want to overwrite the previous selection.
Step 2: Create new SkinConcernsForUsers records with the new choices.
Here is what the code looks like
// step 1
await prisma.skinConcernsForUsers.deleteMany({
where: {
userId: "1",
},
});
// step 2
await prisma.skinConcernsForUsers.createMany({
data: [
{
userId: "1",
skinConcernName: "REDNESS",
},
{
userId: "1",
skinConcernName: "PIGMENTATION",
},
],
});

Increment a field to a unique integer when a field updates in 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

How do you do UUID in Golangs Gorm?

I have the following model...
type User struct {
ID string `sql:"type:uuid;primary_key;default:uuid_generate_v4()"`
FirstName string `form:"first_name" json:"first_name,omitempty"`
LastName string `form:"last_name" json:"last_name,omitempty"`
Password string `form:"password" json:"password" bindind:"required"`
Email string `gorm:"type:varchar(110);unique_index" form:"email" json:"email,omitempty" binding:"required"`
Location string `form:"location" json:"location,omitempty"`
Avatar string `form:"avatar" json:"avatar,omitempty"`
BgImg string `form:"bg_img" json:"bg_img,omitempty"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt time.Time
}
I've tried several different ways, but this way throws (pq: relation "users" does not exist). I have no related models, it's literally just that one model.
I've tried using...
func (user *User) BeforeCreate(scope *gorm.Scope) error {
scope.SetColumn("ID", uuid.NewV4())
return nil
}
Along with a uuid lib, but had no luck with that either.
Turns out I was trying to store the UUID as the wrong type, I was doing...
func (user *User) BeforeCreate(scope *gorm.Scope) error {
scope.SetColumn("ID", uuid.NewV4())
return nil
}
When it needed to be...
func (user *User) BeforeCreate(scope *gorm.Scope) error {
scope.SetColumn("ID", uuid.NewV4().String())
return nil
}
For postgresql, here is what I did:
go get github.com/google/uuid
Use uuid.UUID (from "github.com/google/uuid"), as type,
e.gID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4()"`
Add uuid-ossp extension for postgres database,
e.g
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
Then, when you call DB's Create() method, the uuid is generated automatically.
Update: pg14+ gen_random_uuid()
(as mentioned in Doron Segal's comment)
pg 14 has built-in function gen_random_uuid() to generate uuid v4, e.g:
create table:
create table uuid_test (uid text default gen_random_uuid());
insert a row:
insert into uuid_test(uid) values (DEFAULT);
Then uid column is generated automatically.
Similiar, in go you can use the function as defaul value I think, e.g:
ID uuid.UUID gorm:"type:uuid;default:gen_random_uuid()"
BTW, the gen_random_uuid() function only support uuid v4 now, to use other versions, you still need uuid-ossp extension.
For this you will need gorm and go.uuid
go get github.com/jinzhu/gorm
go get github.com/satori/go.uuid
Try creating your own model base model in place of gorm.Model like so:
type Base struct {
ID string `sql:"type:uuid;primary_key;default:uuid_generate_v4()"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt *time.Time `sql:"index" json:"deleted_at"`
}
You would then populate this field using a method called before creation of any record, like so:
func (base *Base) BeforeCreate(scope *gorm.Scope) error {
id, err := uuid.NewV4()
if err != nil {
return err
}
return scope.SetColumn("ID", uuid.String())
}
Therefore, for your particular case, you would have:
type User struct {
Base
FirstName string `form:"first_name" json:"first_name,omitempty"`
LastName string `form:"last_name" json:"last_name,omitempty"`
Password string `form:"password" json:"password" bindind:"required"`
Email string `gorm:"type:varchar(110);unique_index" form:"email" json:"email,omitempty" binding:"required"`
Location string `form:"location" json:"location,omitempty"`
Avatar string `form:"avatar" json:"avatar,omitempty"`
BgImg string `form:"bg_img" json:"bg_img,omitempty"`
}
More details on this can be found here
This was my solution for Gorm v1.21
go get gorm.io/gorm
go get gorm.io/driver/postgres
go get github.com/google/uuid
import (
"gorm.io/gorm"
"github.com/google/uuid"
)
type User struct {
Id: string `gorm:"primaryKey"`
}
// Note: Gorm will fail if the function signature
// does not include `*gorm.DB` and `error`
func (user *User) BeforeCreate(tx *gorm.DB) (err error) {
// UUID version 4
user.Id = uuid.NewString()
return
}
Notes:
For the Google UUID package, the methods uuid.New() and uuid.NewString() use UUID version 4. This is not clearly stated in the documentation (http://pkg.go.dev/github.com/google/uuid), but by looking into the source code, you can see that these are wrappers around uuid.NewRandom() which is stated as being UUID version 4.
While some recommend the Satori UUID package (https://github.com/satori/go.uuid), benchmarks show that it has 3.3x lower performance than the Google UUID package
(https://gist.github.com/mattes/69a4ab7027b9e8ee952b5843e7ca6955)
The error (pq: relation "users" does not exist) usually means that, the table users does not exists in the database. It has nothing to do with the relationship between two models.
So basically, You first need to create the table in the database (Or auto migrate the database As per #Apin suggest). And try to re-run the same code.
None of these worked for me using gorm v1.21.
Here was my solution. Note that I'm using the satori/go.uuid library for generating UUID, but code with google's library is near identical.
type UUIDBaseModel struct {
ID uuid.UUID `gorm:"primary_key" json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt *time.Time `sql:"index" json:"deleted_at"`
}
func (base *UUIDBaseModel) BeforeCreate(tx *gorm.DB) error {
uuid := uuid.NewV4().String()
tx.Statement.SetColumn("ID", uuid)
return nil
}
Now I used Gorm 2.0 and this worked:
go get github.com/satori/go.uuid
type Tablename struct {
ID string `sql:"type:uuid;primary_key;default:uuid_generate_v4()"`
}