Skip to content

Commit

Permalink
Fix for polymorphic fields in SqlMapping
Browse files Browse the repository at this point in the history
  • Loading branch information
milessabin committed Aug 17, 2024
1 parent d3d94ef commit 8eb8158
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 2 deletions.
6 changes: 6 additions & 0 deletions modules/core/src/main/scala/mapping.scala
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,12 @@ abstract class Mapping[F[_]] {
}
}

def fieldIsPolymorphic(context: Context, fieldName: String): Boolean =
rawFieldMapping(context, fieldName).exists {
case _: PolymorphicFieldMapping => true
case _ => false
}

/** Yields the `FieldMapping` directly or ancestrally associated with `fieldName` in `context`, if any. */
def ancestralFieldMapping(context: Context, fieldName: String): Option[FieldMapping] =
fieldMapping(context, fieldName).orElse {
Expand Down
47 changes: 47 additions & 0 deletions modules/sql/shared/src/main/scala/SqlMapping.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2826,6 +2826,53 @@ trait SqlMappingLike[F[_]] extends CirceMappingLike[F] with SqlModule[F] { self
/** Compile the given GraphQL query to SQL in the given `Context` */
def apply(q: Query, context: Context): Result[MappedQuery] = {
def loop(q: Query, context: Context, parentConstraints: List[List[(SqlColumn, SqlColumn)]], exposeJoins: Boolean): Result[SqlQuery] = {

object TypeCase {
def unapply(q: Query): Option[(Query, List[Narrow])] = {
def isPolySelect(q: Query): Boolean =
q match {
case Select(fieldName, _, _) =>
typeMappings.fieldIsPolymorphic(context, fieldName)
case _ => false
}

def branch(q: Query): Option[TypeRef] =
q match {
case Narrow(subtpe, _) => Some(subtpe)
case _ => None
}

val ungrouped = ungroup(q).flatMap {
case sel@Select(fieldName, _, _) if isPolySelect(sel) =>
typeMappings.rawFieldMapping(context, fieldName) match {
case Some(TypeMappings.PolymorphicFieldMapping(cands)) =>
cands.map { case (pred, _) => Narrow(schema.uncheckedRef(pred.tpe), q) }
case _ => Seq(sel)
}

case other => Seq(other)
}

val grouped = ungrouped.groupBy(branch).toList
val (default0, narrows0) = grouped.partition(_._1.isEmpty)
if (narrows0.isEmpty) None
else {
val default = default0.flatMap(_._2) match {
case Nil => Empty
case children => Group(children)
}
val narrows = narrows0.collect {
case (Some(subtpe), narrows) =>
narrows.collect { case Narrow(_, child) => child } match {
case List(child) => Narrow(subtpe, child)
case children => Narrow(subtpe, Group(children))
}
}
Some((default, narrows))
}
}
}

def group(queries: List[Query]): Result[SqlQuery] = {
queries.foldLeft(List.empty[SqlQuery].success) {
case (nodes, q) =>
Expand Down
15 changes: 13 additions & 2 deletions modules/sql/shared/src/test/scala/SqlInterfacesMapping.scala
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,14 @@ trait SqlInterfacesMapping[F[_]] extends SqlTestMapping[F] { self =>
entityType: EntityType!
title: String
synopses: Synopses
imageUrl: String
}
type Film implements Entity {
id: ID!
entityType: EntityType!
title: String
synopses: Synopses
imageUrl: String
rating: String
label: Int
}
Expand All @@ -73,6 +75,7 @@ trait SqlInterfacesMapping[F[_]] extends SqlTestMapping[F] { self =>
entityType: EntityType!
title: String
synopses: Synopses
imageUrl: String
numberOfEpisodes: Int
episodes: [Episode!]!
label: String
Expand Down Expand Up @@ -126,7 +129,8 @@ trait SqlInterfacesMapping[F[_]] extends SqlTestMapping[F] { self =>
fieldMappings =
List(
SqlField("rating", entities.filmRating),
SqlField("label", entities.filmLabel)
SqlField("label", entities.filmLabel),
CursorField("imageUrl", mkFilmImageUrl, List("title"))
)
),
ObjectMapping(
Expand All @@ -135,7 +139,8 @@ trait SqlInterfacesMapping[F[_]] extends SqlTestMapping[F] { self =>
List(
SqlField("numberOfEpisodes", entities.seriesNumberOfEpisodes),
SqlObject("episodes", Join(entities.id, episodes.seriesId)),
SqlField("label", entities.seriesLabel)
SqlField("label", entities.seriesLabel),
CursorField("imageUrl", mkSeriesImageUrl, List("title"))
)
),
ObjectMapping(
Expand Down Expand Up @@ -209,6 +214,12 @@ trait SqlInterfacesMapping[F[_]] extends SqlTestMapping[F] { self =>
}
}

def mkFilmImageUrl(c: Cursor): Result[Option[String]] =
c.fieldAs[Option[String]]("title").map(_.map(title => s"http://example.com/film/$title.jpg"))

def mkSeriesImageUrl(c: Cursor): Result[Option[String]] =
c.fieldAs[Option[String]]("title").map(_.map(title => s"http://example.com/series/$title.jpg"))

override val selectElaborator = SelectElaborator {
case (QueryType, "films", Nil) =>
Elab.transformChild(child => Filter(Eql[EntityType](FilmType / "entityType", Const(EntityType.Film)), child))
Expand Down
101 changes: 101 additions & 0 deletions modules/sql/shared/src/test/scala/SqlInterfacesSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -805,4 +805,105 @@ trait SqlInterfacesSuite extends CatsEffectSuite {

assertWeaklyEqualIO(res, expected)
}

test("interface query with polymorphic cursor field (1)") {
val query = """
query {
entities {
id
... on Film {
imageUrl
}
... on Series {
imageUrl
}
}
}
"""

val expected = json"""
{
"data" : {
"entities" : [
{
"id" : "4",
"imageUrl" : "http://example.com/series/Series 1.jpg"
},
{
"id" : "5",
"imageUrl" : "http://example.com/series/Series 2.jpg"
},
{
"id" : "2",
"imageUrl" : "http://example.com/film/Film 2.jpg"
},
{
"id" : "3",
"imageUrl" : "http://example.com/film/Film 3.jpg"
},
{
"id" : "6",
"imageUrl" : "http://example.com/series/Series 3.jpg"
},
{
"id" : "1",
"imageUrl" : "http://example.com/film/Film 1.jpg"
}
]
}
}
"""

val res = mapping.compileAndRun(query)

assertWeaklyEqualIO(res, expected)
}

test("interface query with polymorphic cursor field (2)") {
val query = """
query {
entities {
id
imageUrl
}
}
"""

val expected = json"""
{
"data" : {
"entities" : [
{
"id" : "4",
"imageUrl" : "http://example.com/series/Series 1.jpg"
},
{
"id" : "5",
"imageUrl" : "http://example.com/series/Series 2.jpg"
},
{
"id" : "2",
"imageUrl" : "http://example.com/film/Film 2.jpg"
},
{
"id" : "3",
"imageUrl" : "http://example.com/film/Film 3.jpg"
},
{
"id" : "6",
"imageUrl" : "http://example.com/series/Series 3.jpg"
},
{
"id" : "1",
"imageUrl" : "http://example.com/film/Film 1.jpg"
}
]
}
}
"""

val res = mapping.compileAndRun(query)

assertWeaklyEqualIO(res, expected)
}
}

0 comments on commit 8eb8158

Please sign in to comment.