Skip to content

Commit

Permalink
fix: m2m filtering on joined queries (#4549)
Browse files Browse the repository at this point in the history
  • Loading branch information
Weakky authored Dec 11, 2023
1 parent e7a7287 commit 47a3fbd
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,40 @@
use query_engine_tests::*;

#[test_suite(schema(schemas::posts_categories))]
#[test_suite(schema(schema))]
mod m2m {
use query_engine_tests::assert_query;

fn schema() -> String {
let schema = indoc! {
r#"model Post {
#id(id, Int, @id)
title String
content String @default("Wip")
#m2m(categories, Category[], id, Int)
}
model Category {
#id(id, Int, @id)
name String
#m2m(posts, Post[], id, Int)
tags Tag[]
}
model Tag {
#id(id, Int, @id)
name String
categoryId Int
category Category @relation(fields: [categoryId], references: [id])
}
"#
};

schema.to_owned()
}

#[connector_test]
async fn fetch_only_associated(runner: Runner) -> TestResult<()> {
test_data(&runner).await?;
Expand All @@ -25,6 +56,100 @@ mod m2m {
Ok(())
}

#[connector_test]
async fn filtering_ordering(runner: Runner) -> TestResult<()> {
test_data(&runner).await?;

insta::assert_snapshot!(
run_query!(&runner, r#"{
findUniquePost(where: { id: 1 }) {
categories(
where: {
OR: [
{ id: { in: [1] } },
{ tags: { some: { name: "Cinema" } } }
]
},
orderBy: { name: asc }
) {
id
name
}
}
}"#),
@r###"{"data":{"findUniquePost":{"categories":[{"id":2,"name":"Fiction"},{"id":1,"name":"Marketing"}]}}}"###
);

Ok(())
}

#[connector_test]
async fn basic_pagination(runner: Runner) -> TestResult<()> {
test_data(&runner).await?;

insta::assert_snapshot!(
run_query!(&runner, r#"{
findUniquePost(where: { id: 1 }) {
categories(
take: 1,
orderBy: { name: desc }
) {
id
name
}
}
}"#),
@r###"{"data":{"findUniquePost":{"categories":[{"id":1,"name":"Marketing"}]}}}"###
);

insta::assert_snapshot!(
run_query!(&runner, r#"{
findUniquePost(where: { id: 1 }) {
categories(
take: 1,
orderBy: { name: asc }
) {
id
name
}
}
}"#),
@r###"{"data":{"findUniquePost":{"categories":[{"id":2,"name":"Fiction"}]}}}"###
);

insta::assert_snapshot!(
run_query!(&runner, r#"{
findUniquePost(where: { id: 1 }) {
categories(
skip: 1,
orderBy: { name: desc }
) {
id
name
}
}
}"#),
@r###"{"data":{"findUniquePost":{"categories":[{"id":2,"name":"Fiction"}]}}}"###
);

insta::assert_snapshot!(
run_query!(&runner, r#"{
findUniquePost(where: { id: 1 }) {
categories(
skip: 1,
orderBy: { name: asc }
) {
id
name
}
}
}"#),
@r###"{"data":{"findUniquePost":{"categories":[{"id":1,"name":"Marketing"}]}}}"###
);

Ok(())
}

fn m2m_sharing_same_row_schema() -> String {
let schema = indoc! {
r#"model User {
Expand Down Expand Up @@ -69,7 +194,7 @@ mod m2m {
Ok(())
}

fn schema() -> String {
fn schema_16390() -> String {
let schema = indoc! {
r#"model Item {
id Int @id @default(autoincrement())
Expand All @@ -90,7 +215,7 @@ mod m2m {
}

// https://github.com/prisma/prisma/issues/16390
#[connector_test(schema(schema), relation_mode = "prisma", only(Postgres))]
#[connector_test(schema(schema_16390), relation_mode = "prisma", only(Postgres))]
async fn repro_16390(runner: Runner) -> TestResult<()> {
run_query!(&runner, r#"mutation { createOneCategory(data: {}) { id } }"#);
run_query!(
Expand Down Expand Up @@ -124,11 +249,13 @@ mod m2m {
create: [
{
id: 1,
name: "Marketing"
name: "Marketing",
tags: { create: { id: 1, name: "Business" } }
},
{
id: 2,
name: "Fiction"
name: "Fiction",
tags: { create: { id: 2, name: "Cinema" } }
}
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,12 @@ impl SelectBuilder {
let linking_fields = rs.field.related_field().linking_fields();

if rs.field.relation().is_many_to_many() {
let selection: Vec<Column<'_>> = FieldSelection::union(vec![order_by_selection(rs), linking_fields])
.into_projection()
.as_columns(ctx)
.map(|c| c.table(root_alias.to_table_string()))
.collect();
let selection: Vec<Column<'_>> =
FieldSelection::union(vec![order_by_selection(rs), linking_fields, filtering_selection(rs)])
.into_projection()
.as_columns(ctx)
.map(|c| c.table(root_alias.to_table_string()))
.collect();

// SELECT <foreign_keys>, <orderby columns>
inner.with_columns(selection.into())
Expand Down Expand Up @@ -152,8 +153,9 @@ impl SelectBuilder {

fn build_m2m_join<'a>(&mut self, rs: &RelationSelection, parent_alias: Alias, ctx: &Context<'_>) -> JoinData<'a> {
let rf = rs.field.clone();
let m2m_alias = m2m_join_alias_name(&rf);
let m2m_table_alias = self.next_alias();
let m2m_join_alias = self.next_alias();
let outer_alias = self.next_alias();

let left_columns = rf.related_field().m2m_columns(ctx);
let right_columns = ModelProjection::from(rf.model().primary_identifier()).as_columns(ctx);
Expand All @@ -174,24 +176,27 @@ impl SelectBuilder {
.unwrap();

let m2m_join_data = Table::from(self.build_related_query_select(rs, m2m_table_alias, ctx))
.alias(join_alias_name(&rf))
.alias(m2m_join_alias.to_table_string())
.on(ConditionTree::single(true.raw()))
.lateral();

let child_table = rf.as_table(ctx).alias(m2m_table_alias.to_table_string());

let inner = Select::from_table(child_table)
.value(Column::from((join_alias_name(&rf), JSON_AGG_IDENT)))
.value(Column::from((m2m_join_alias.to_table_string(), JSON_AGG_IDENT)))
.left_join(m2m_join_data) // join m2m table
.and_where(join_conditions) // adds join condition to the child table
.with_ordering(&rs.args, Some(join_alias_name(&rs.field)), ctx) // adds ordering stmts
.with_filters(rs.args.filter.clone(), None, ctx) // adds query filters // TODO: avoid clone filter
.with_pagination(rs.args.take_abs(), rs.args.skip); // adds pagination
.with_ordering(&rs.args, Some(m2m_join_alias.to_table_string()), ctx) // adds ordering stmts
.with_filters(rs.args.filter.clone(), Some(m2m_join_alias), ctx) // adds query filters // TODO: avoid clone filter
.with_pagination(rs.args.take_abs(), rs.args.skip)
.comment("inner"); // adds pagination

let outer = Select::from_table(Table::from(inner).alias(format!("{}_1", m2m_alias))).value(json_agg());
let outer = Select::from_table(Table::from(inner).alias(outer_alias.to_table_string()))
.value(json_agg())
.comment("outer");

Table::from(outer)
.alias(m2m_alias)
.alias(m2m_join_alias_name(&rf))
.on(ConditionTree::single(true.raw()))
.lateral()
}
Expand Down

0 comments on commit 47a3fbd

Please sign in to comment.