Imagine two tables:
People {
id: uuid
home_id: uuid
}
Homes {
id: uuid
}
Table People is already populated. What I would like to do, is to insert a new Home and update home_id field in the People table at the same time. Is this possible?
I ended up solving this by creating a HomeOwnership table { person_id: uuid, home_id: uuid } and removing home_id from the People table. Then I established foreign key relationships between the tables. Then the mutation would look like following:
mutation MyMutation($person_id: uuid = "some_id") {
insert_home_one(object: {home_membership: {data: {person_id: $person_id}}}) {
id
home_membership{
person{
name
}
}
}
}
Related
Let's say we have two entities.
users that has uuid, name and age
users_books that has user_uuid, book_id and recommended_age.
user_uuid was added as foreign key pointing to uuid in Users
Using user name I want to get all the books that that user reads and have recommended age equal to users age.
Following query will get me all the books that user reads
query getUserBooks($uuid: uuid!) {
users_by_pk(uuid: $uuid) {
uuid
name
age
users_books() {
book_id
recommended_age
}
}
}
And this is the query I am trying to create:
query getUserBooksWithRestrictedAge($uuid: uuid!) {
users_by_pk(uuid: $uuid) {
uuid
name
age
users_books(where:{recommended_age:{_eq: *WHAT_SHOULD_GO_HERE?*}}) {
book_id
recommended_age
}
}
}
Is this even possible?
Hasura supports columns comparison operators only when setting permissions. To accomplish what you need you have to create a view.
CREATE OR REPLACE VIEW users_books_by_age AS
SELECT * from users_books
Set the relationships between the view, user and books table. In the Hasura view permission builder filter the age comparing the columns. Something like
{
user: {
age: {
_ceq: recommended_age
}
}
}
This way you can run your query like:
query getUserBooks($uuid: uuid!) {
users_by_pk(uuid: $uuid) {
uuid
name
age
users_books_by_age {
book_id
recommended_age
}
}
}
Another solution would be to create a computed field. Something like:
CREATE FUNCTION author_full_name(user_row user)
RETURNS SETOF users_books AS $$
SELECT * FROM users_books where recommended_age = user_row.age
$$ LANGUAGE sql STABLE;
I hope that helps.
Hasura: How to establish relationship to existing record (many to many) at insert time?
I have two tables: product and category that are linked to each other in a many to many relationship based on an id column in each table and an intermediary table product_category.
I can insert records directly into postgres for both tables and link them with product_category and this works great in Hasura for queries so I know I have set things up properly.
What I want to be able to do is insert a new product and knowing the id of a category (or categories) I want to establish the relationship at insert time. Preferably without a seperate call
https://hasura.io/docs/1.0/graphql/manual/mutations/insert.html#insert-an-object-along-with-its-related-objects-through-relationships
The documentation only covers inserting both the object and related objects at the same time but what if the other one already exists?
I have tried what I would expect to work (linking this product to category with id 1):
mutation MyMutation {
insert_product_one(
object: {
name: "Champion",
category: {data: {id: 1}}
}) {
id
}
}
But that throws:
"Not-NULL violation. null value in column \"product_id\" violates not-null constraint"
How can I insert this new product and link it to one or more categories? Preferably all in one statement but even an example of retrieving the generated id and an update mutation would be not ideal, but non-the-less a solution.
Update: As a sanity check I've recreated the product and category tables as a minimal, basic example and tried both my query and the upsert conflict method xadm suggested.
The data and relations I've added some screenshots of here:
https://imgur.com/a/GUomMbe
mutation MyMutation {
insert_testproduct_one(
object: {
name: "Champion",
category: {
data: {id: 1},
on_conflict: { constraint: primarykeything , update_columns: [id] }
}
}) {
id
}
}
The error is similar: "Not-NULL violation. null value in column \"testcategory_id\" violates not-null constraint"
Note: primarykeything is the primary key on the bridge table consisting of the two ids.
Since it's a many to many relationship, you have a join table in between them. From what I can see in the screenshot you posted the id for the category in your category relationship is called testcategory_id and not id.
mutation MyMutation {
insert_testproduct_one(
object: {
name: "Champion",
category: {
data: {testcategory_id: 1}
}
}) {
id
}
}
For it to work the id in the table testproduct_testcategory has to be auto-incremented
i would like run multiple mutations in the same query.
In the example below, i create an order and after i create a product record, concerning previously created.
I must have 2 mutations.
First, i insert an order. In output, i retrieve among others, idorder.
Then, i insert an product. This product
mutation {
createOrder(input: {
order: {
ordername: "My order"
}
}) {
order {
idorder
ordername
}
},
createProduct(input: {
product: {
quantity: 3
idrefproduct: 25 # link to refProduct
idorder: XXXX # how can i retrieve idorder from output of createOrder above ? 🤔
}
}) {
product {
idproduct
}
}
}
Real example with SQL structure :
user(iduser, othersFields);
scenarios(idscenario, iduser, name, otherFields);
cultA(idcultA, idscenario, ...); // this table need of idscenario field
cultB(idcultB, idscenario, ...); // this table need of idscenario field
cultC(idcultC, idscenario, ...); // this table need of idscenario field
how can i retrieve idorder from output of createOrder above ? 🤔
It is possible ?
If i forgot some informations, don't hesitate.
Thanks in advance.
EDIT :
With PostGraphile, plugin "postgraphile-plugin-nested-mutations" or "custom mutations" (with PL PGSQL function)
Without PostGraphile, a resolver as the example of #xadm permits this particular nested mutation.
IMHO you can search for "nested mutations" - not described here, you'll easily find examples/tutorials.
Proposed DB structure (n-to-n relation):
order{orderID,lines[{orderLineID}] } >
order_line{orderLineID, productID, anount, price} >
product {productID}
... created in nested mutations (in reverse order product>order_line>order)
Product don't need orderID, but when you ask for it [in product resolver]
query product(id) {
id
orderedRecently {
orderID
date
price
}
}
... you can simply get it (or rather many - array) from orderLines and orders tables [using simple SQL query - where price will be read from orderLines]
orderedRecently resolver can get product id from parent object (usually 1st param)
Of course you can (and should) return data as order and orderLine types (to be cached separately, normalized):
query product($id: ID!) {
product(id: $id) {
id
orderedRecently {
id
date
orderLine {
id
amount
price
}
}
}
}
where type orderedRecently: [Order!] - array can be empty, not eordered yet
update
I slightly misunderstood your requirements (naming convention) ... you already have proper db structure. Mutation can be 'feeded' with complex data/input:
mutation {
createOrder(input: {
order: {
ordername: "My order"
products: [
{
quantity: 3
idrefproduct: 25
},
{
quantity: 5
idrefproduct: 28
}
]
}
}) {
order {
id
ordername
products {
id
idrefproduct
quantity
}
}
}
}
Your product is my orderLine, idrefproduct is product.
createOrder creates/inserts order and then use its id for creation of product records (order.id, idrefproduct and quantity). Resolver can return only order id or structured data (as above).
I'm using prisma2, and I don't know how to delete items having relations with other models.
This is my models.
model User {
id String #default(cuid()) #id
email String #unique
password String
name String
teams Team[]
memberships Membership[]
}
model Team {
id String #default(cuid()) #id
name String
founder User?
memberships Membership[]
}
model Membership {
id String #default(cuid()) #id
class String
owner User
team Team
}
User-Team is 1:n relationship.
Team-Membership is 1:n relationship.
And I wanna delete a Team.
I've tried this.
t.list.field("deleteTeam", {
type: "Team",
args: {
teamid: idArg()
},
resolve: (_, { teamid }, ctx) => {
return ctx.photon.teams.deleteMany({
where: { id: teamid }
});
}
});
But it doensn't work because it violates relation.
How can I delete team with disconnecting all the relations at the same time?
Deletes that have dependent relationships usually require that a cascading delete be specified.
Based on your model, I believe you need to update your graphql schemas to handle either CASCADE on SET_NULL for relations onDelete.
I know that in other systems like 8base there is a force: Boolean flag that can be specified solve this. However, here is the Prisma docs section for your problem: https://prisma-docs.netlify.com/docs/1.4/reference/prisma-api/concepts-utee3eiquo/#cascading-deletes
Say I have a table Person with attributes id and name. The GraphQL server is all setup by Postgraphile and working as I can query and create new entry. However, I could not update it. Scratching my head over and over again and I am still unable to find out the cause for this.
This is the mutation I tried that failed me every now and then.
mutation($id: Int!, $patch: PersonPatch!) {
updatePersonById(input: { id: $id, patch: $patch }) {
clientMutationId
}
}
The variables
{
id: 1,
patch: {"name": "newname"}
}
I was using Altair GraphQL client to submit the mutation request and the error message returned was "No values were updated in collection 'people' because no values were found."
The person of id = 1 does exist, confirmed by sending a query personById over to get his name. But I just couldn't update his name.
Edit #1
Below is the gql generated by Altair GraphQL Client
updatePersonById(
input: UpdatePersonByIdInput!
): UpdatePersonPayload
input UpdatePersonByIdInput {
clientMutationId: String
patch: PersonPatch!
id: Int!
}
input PersonPatch {
id: Int
name: String
}
Assuming you're using row-level security (RLS) it sounds like the row to be updated does not pass the required security policies for the currently authenticated user.
Here's a small example; you'll want to adjust it to fit your own permissions system
create table person (id serial primary key, name text);
alter table person enable row level security;
grant select, insert(name), update(name), delete on person to graphql;
create policy select_all on person for select using (true);
create policy insert_all on person for insert with check(true);
create policy update_self on person for update using (id = current_person_id());
create policy delete_self on person for delete using (id = current_person_id());
where
create function current_person_id() returns int as $$
select nullif(current_setting('jwt.claims.person_id', true), '')::int;
$$ language sql stable;
If you need more guidance, feel free to drop into the Graphile chat.