How to search M to N relationship in prisma? - orm

I'm working on project with prisma nowadays.
I designed m to n relationship table. Below is my code.
schema.prisma
model Artists {
id Int #id #default(autoincrement())
artistName String?
Albums Albums[]
Artists_Tracks Artists_Tracks[]
}
model Artists_Tracks {
id Int #id #default(autoincrement())
trackId Int?
artistId Int?
Artists Artists? #relation(fields: [artistId], references: [id])
Tracks Tracks? #relation(fields: [trackId], references: [id])
##index([artistId], name: "Artists_Tracks_artistId_foreign_idx")
##index([trackId], name: "Artists_Tracks_trackId_foreign_idx")
}
model Tracks {
id Int #id #default(autoincrement())
trackName String?
albumTrackNumber Int?
albumId Int?
Albums Albums? #relation(fields: [albumId], references: [id])
Artists_Tracks Artists_Tracks[]
##index([albumId], name: "Tracks_albumId_foreign_idx")
}
This is my prisma code. What I want to do is search by trackname and get all tracks's information with artist's name.
+edit)
What I've tried is
// first try
optObj.include = {
Albums: {
select: { cover: true },
},
Artists_Tracks: {
Albums: true
},
};
// second try
optObj.include = {
Albums: {
select: { cover: true },
},
Artists_Tracks: true,
Artists: true,
};
const result = await prisma.tracks.findMany(optObj);
and use promise.all to get artist's name for each track. Is there any way to do that in once by one query? I'm pretty sure there would be more better way to do this. Thank you in advance.

As per your schema, the include will not contain the Artist relation. It will have to be included from the Artists_Tracks relation like this:
const result = await prisma.tracks.findMany({
include: {
Albums: {
select: { cover: true },
},
Artists_Tracks: { include: { Artists: true } },
},
})
This way you can get the Artist for each track present.

Related

Prisma update with relation in WHERE property

Given these schemas:
model awayinfo {
PlayerID Int #id
IsAway Boolean
playerinfo playerinfo #relation(fields: [PlayerID], references: [ID])
}
model playerinfo {
name String #db.VarChar(15)
ID Int #id #unique #default(autoincrement())
awayinfo awayinfo?
}
How would I create a prisma SQL update for the awayinfo table if the only identifier I have would be the Name of a player and not the ID?
what I tried:
I try to pass something into the WHERE part as seen below:
const result = await prisma.awayinfo.update({
where: {
PlayerID: {
name: name
}
},
data: {
IsAway: true,
}
});
but it always gives me the Error:
Invalid `prisma.awayinfo.update()` invocation:
Argument PlayerID: Got invalid value
{
name: 'Dummy'
}
on prisma.updateOneawayinfo. Provided Json, expected Int.
I got pretty desperate and even tried wonky selects like this one
const result = await prisma.awayinfo.update({
where: {
PlayerID: {
playerinfo: {
Where: {
name: name
},
select: {ID: true},
}}
}, .....
but obviously this would not work aswell.
I wonder what I am missing here and I cannot find any example of a condition within the WHERE clause in the prisma documentation
There are two issues here, the first is in your Prisma schema/data model and the second is in your query.
Making name unique
Firstly, if you want to uniquely identify a record in the playerinfo table with just the name property, this property must be unique. Otherwise, it's not possible for your database (and Prisma) to know which player info record to update in case there are multiple records with the same name property.
Update your schema accordingly:
model playerinfo {
name String #unique #db.VarChar(15)
ID Int #id #unique #default(autoincrement())
awayinfo awayinfo?
}
Rewriting the update query
Since the where condition in your update references a property playerinfo model, that is where you should begin your query. Here's what it looks like:
const data = await prisma.playerinfo.update({
where: {
name: name
},
data: {
awayinfo: {
update: {
IsAway: true
}
}
}
})

updateMany can't find argument

I have a query:
createNotification: async (_, args, {req, res}) => {
const followedBy = await prisma.user.updateMany({
where: {
following: {
some: {
id: req.userId
},
},
},
data: {
notifications: {
create: {
message: args.message,
watched: false,
},
},
},
})
And User and Notification models:
model User {
id Int #id #default(autoincrement())
email String #unique
name String
user_name String #unique
password String
movies Movie[]
notifications Notification[]
followedBy User[] #relation("UserFollows", references: [id])
following User[] #relation("UserFollows", references: [id])
}
model Notification {
id Int #id #default(autoincrement())
link String?
movie_id Int?
message String
icon String?
thumbnail String?
user User #relation(fields: [userId], references: [id])
userId Int
watched Boolean
}
When I run my query I get an error:
Unknown arg `notifications` in data.notifications for type UserUpdateManyMutationInput. Did you mean `email`? Available args:
type UserUpdateManyMutationInput {
email?: String | StringFieldUpdateOperationsInput
name?: String | StringFieldUpdateOperationsInput
user_name?: String | StringFieldUpdateOperationsInput
password?: String | StringFieldUpdateOperationsInput
}
The strange thing is that this works:
const followedBy = await prisma.user.findUnique({
where: {id: req.userId},
include: {
followedBy: true,
},
});
followedBy.followedBy.map(async(user) => {
await prisma.user.update({
where: {id: user.id},
data: {
notifications: {
create: {
message: args.message,
watched: false,
},
},
},
});
});
But this isn't making the best of what Prisma offers.
As of September 2021, Prisma does not support mutating nested relations in a top-level updateMany query. This is what the typescript error is trying to tell you, that you can only access email, name, user_name and password fields inside data. There's an open feature request for this which you could follow if you're interested.
For the schema that you have provided, here's a possible workaround that's slightly less readable but more optimized than your current solution.
createNotification: async (_, args, {req, res}) => {
// get the followers array of req.userId
const followedBy = await prisma.user.findUnique({
where: { id: req.userId },
include: {
followedBy: true,
},
});
// array of notification objects to be created, one for each follower of req.userId
let messageDataArray = followedBy.followedBy.map((user) => {
return {
userId: user.id,
message: args.message,
watched: false,
};
});
// do a bulk createMany.
// Since it's one query, it should be more optimized than running an update for each user in a loop.
await prisma.notification.createMany({
data: messageDataArray,
});
};
If you're interested, here's the docs reference for the kinds of nested updates that are possible.

Sequelize foreign key reference composite primary key

i'm trying to create with sequelize (postgre) 'Ingredient' table, with columns normalizedName/userId where normalizedName is unique per userId.
The second table is 'IngredientQuantity', with columns ingredientId/userId/quantity.
I tried to set in 'Ingredient' normalizedName and userId as primaryKey, and to foreign key this composite PK from 'IngredientQuantity' table with ingredientId, but i saw that was impossible with sequelize, only normalizedName is used for reference in foreign key.
Whats is the best approach to do that ? I thought about id auto increment, but all id are shared among all users. For example user1 create his first ingredient with id = 1, when user2 create his first ingredient he will have id = 2. So. i don't know if it's good idea, if all users have lot of ingredients i should use bigint etc..and if they delete/add/delete/add id will grow up.
Ingredient table
module.exports = (sequelize, DataTypes) => {
var Ingredient = sequelize.define('ingredient', {
name: DataTypes.STRING,
normalizedName: { type: DataTypes.STRING, primaryKey: true },
userId: { type: DataTypes.INTEGER, primaryKey: true }
}, {
freezeTableName: true
});
Ingredient.associate = function (models) {
models.ingredient.hasMany(models.ingredientQuantity);
models.ingredient.belongsTo(models.user, {
onDelete: "CASCADE",
foreignKey: {
allowNull: false
}
});
};
return Ingredient;
};
IngredientQuantity table
module.exports = (sequelize, DataTypes) => {
var IngredientQuantity = sequelize.define('ingredientQuantity', {
quantity: DataTypes.FLOAT,
}, {
freezeTableName: true
});
IngredientQuantity.associate = function (models) {
models.ingredientQuantity.belongsTo(models.ingredient);
models.ingredientQuantity.belongsTo(models.user, {
onDelete: "CASCADE",
foreignKey: {
allowNull: false
}
});
};
return IngredientQuantity;
};
Whats is the best approach if i consider lot of data with lot of users ? Is there an other solution ? Thanks
It's totally normal to use SERIAL as autoincremented integer surrogate PK. Also you can use UUID as autogenerated PKs (in such case you should set default value as uuid_generate_v4()) if you somehow afraid that integer value range will not be enough.
Because it's a service field there is no need it to be unique only for a certain user. Usually you shouldn't rely on a PK value.

select next and previous records in Sequelize based on createdAt

What I want: fetch one record from the database based on its ID (UUID) and retrieve next and previous records too
Associations: posts N:M categories, posts 1:N comments, posts N:1 users, posts N:M tags, posts 1:N post_views
Current solution:
const post = await PostModel.findByPk(id, {
include: [
{ model: CategoryModel, attributes: ['title'] },
{ model: TagModel },
{ model: CommentModel, include: { model: UserModel } },
{ model: PostViewModel },
{ model: UserModel, attributes: ['fullname', 'id', 'avatar'] }
]
});
const nextPost = await PostModel.findOne({
where: { createdAt: {
[Op.gt]: post.createdAt
}}
const prevPost = await PostModel.findOne({
where: { createdAt: {
[Op.lt]: post.createdAt
}}
});
but I think this is not efficient and good.

GraphQL & Sequelize: Users and followers/following

I'm attempting to set up my User GraphQL model to have followers and following attributes to query on. However I'm having trouble setting up the relationship in Sequelize. I'm trying to use a Follower model as a Join Table and setup a BelongsToMany association, but haven't been able to get it working. Can anyone suggest what to do or point out what I'm doing wrong?
I've come up with a temporary solution by manually querying, which you can see in my User.model.ts, but I believe there is a better way to do it using proper configuration.
I'm using typescript wrappers around GraphQL and Sequelize, TypeGraphQL and sequelize-typescript respectively, as well as PostgreSQL.
User.model.ts
// VENDOR
import { ObjectType, Field, ID } from 'type-graphql';
import { Model, Table, Column, PrimaryKey, Unique, IsUUID, HasMany, DefaultScope, AllowNull, DataType, BelongsToMany } from 'sequelize-typescript';
// APP
import Post from '../post/post.types';
import Follower from '../follower/follower.types';
/** User model for GraphQL & Database */
#Table({ timestamps: false, tableName: 'users' }) // tell sequelize to treat class as table model
#DefaultScope(() => ({ include: [{ model: Post.scope(), as: 'posts' }] })) // tell sequelize to include posts in its default queries
#ObjectType() // tell GraphQL to treat class as GraphQL model
export default class User extends Model<User>{
#PrimaryKey
#Unique
#AllowNull(false)
#IsUUID(4)
#Column(DataType.UUID)
#Field(() => ID)
id: string;
#Unique
#AllowNull(false)
#Column
#Field()
ci_username: string;
#AllowNull(false)
#Column
#Field()
username: string;
#AllowNull(false)
#Column
#Field()
first_name: string;
#Column
#Field()
last_name: string;
#Column
#Field({ nullable: true })
profile_picture?: string;
// #BelongsToMany(() => User, { otherKey: 'user_id', as: 'followers', through: () => Follower })
// #Field(() => [User])
// followers: User[];
// MY TEMPORARY SOLUTION USING MANUAL QUERYING
#Field(() => [User])
get followers(): Promise<User[]> {
return Follower.findAll({ where: { user_id: this.id } })
.then(records => records.map(record => record.follower_id))
.then((follower_ids: string[]) => {
return User.findAll({ where: { id: follower_ids }});
})
}
// DOES NOT WORK, BUT I BELIEVE COULD POTENTIALLY LEAD TO BETTER SOLUTION
#BelongsToMany(() => User, { otherKey: 'follower_id', as: 'following', through: () => Follower })
#Field(() => [User])
following: User[];
#HasMany(() => Post)
#Field(() => [Post])
posts: Post[];
}
Follower.model.ts
// VENDOR
import { Model, Table, Column, PrimaryKey, Unique, IsUUID, AllowNull, DataType, Index, ForeignKey, AutoIncrement } from 'sequelize-typescript';
// APP
import User from '../user/user.types';
/** Follower model for Database */
#Table({ timestamps: false, tableName: 'followers' }) // tell sequelize to treat class as table model
export default class Follower extends Model<Follower>{
#PrimaryKey
#AutoIncrement
#Unique
#AllowNull(false)
#Column
id: number;
#AllowNull(false)
#IsUUID(4)
#Index
#ForeignKey(() => User)
#Column(DataType.UUID)
user_id: string;
#AllowNull(false)
#IsUUID(4)
#Index
#ForeignKey(() => User)
#Column(DataType.UUID)
follower_id: string;
}
GraphQL Query
{
users: allUsers {
id
username
first_name
last_name
following {
username
id
}
}
}
GraphQL Response / Error
{
"errors": [
{
"message": "Cannot return null for non-nullable field User.following.",
"locations": [
{
"line": 7,
"column": 5
}
],
"path": [
"users",
0,
"following"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"stacktrace": [
"Error: Cannot return null for non-nullable field User.following.",
" at completeValue (/Users/jsainz237/Projects/trueview/trueview-api/node_modules/graphql/execution/execute.js:560:13)",
" at /Users/jsainz237/Projects/trueview/trueview-api/node_modules/graphql/execution/execute.js:492:16"
]
}
}
}
],
"data": null
}
Any help is appreciated.
You need to write a #FieldResolver manually that will resolve the relation and return proper data.
Another solution is to rely on ORM capabilities and lazy relations - when the returned base entity contains a promise as a field, so when .then() is called, it automatically fetches the relation for the database.