gemini-hlsw / clue

GraphQL client for Scala.
18 stars 5 forks source link

Subqueries and interfaces, not working at least in one case #467

Open rpiaggio opened 1 year ago

rpiaggio commented 1 year ago

In the ODB we have in the schema:

type Gcal implements StepConfig {
  continuum: GcalContinuum
  arcs: [GcalArc!]!
  filter: GcalFilter!
  diffuser: GcalDiffuser!
  shutter: GcalShutter!
  stepType: StepType!
}

If we define a subquery:

@GraphQL
abstract class GcalStepConfigSubquery extends GraphQLSubquery[ObservationDB]("Gcal"):
  override val subquery: String = """
        {
          continuum
          arcs
          filter
          diffuser
          shutter
        }
      """

@GraphQLStub
object GcalStepConfigSubquery

and then query:

query($$obsId: ObservationId!) {
  observation(observationId: $$obsId) {
    execution {
      executionConfig {
        ... on GmosSouthExecutionConfig {
          acquisition {
            nextAtom {
              steps {
                stepConfig {
                  ... on Gcal $GcalStepConfigSubquery
                }
              }
            }
          }
        }
      }
    }
  }
}

This results in:

[error] scalafix.internal.v1.FileException: unexpected error processing file /Users/rpiaggio/gemini/explore/common-graphql/src/main/scala/queries/common/GeneratedSequenceSQL.scala
[error] Caused by: java.lang.Exception: Could not resolve type for field [subquery10] - Is this a valid field present in the schema?
[error]         at clue.gen.QueryGen.$anonfun$resolveData$9(QueryGen.scala:270)
[error]         at scala.Option.fold(Option.scala:263)
[error]         at clue.gen.QueryGen.clue$gen$QueryGen$$go$1(QueryGen.scala:272)
[error]         at clue.gen.QueryGen$$anonfun$$nestedInanonfun$resolveData$19$1.applyOrElse(QueryGen.scala:327)
[error]         at clue.gen.QueryGen$$anonfun$$nestedInanonfun$resolveData$19$1.applyOrElse(QueryGen.scala:326)
[error]         at scala.collection.immutable.List.collect(List.scala:267)
[error]         at clue.gen.QueryGen.$anonfun$resolveData$19(QueryGen.scala:326)
[error]         at scala.collection.immutable.List.map(List.scala:250)
[error]         at clue.gen.QueryGen.clue$gen$QueryGen$$go$1(QueryGen.scala:323)
[error]         at clue.gen.QueryGen.$anonfun$resolveData$11(QueryGen.scala:274)
[error]         at scala.Option.map(Option.scala:242)
[error]         at clue.gen.QueryGen.$anonfun$resolveData$10(QueryGen.scala:274)
[error]         at scala.Option.fold(Option.scala:263)
[error]         at clue.gen.QueryGen.clue$gen$QueryGen$$go$1(QueryGen.scala:272)
[error]         at clue.gen.QueryGen.$anonfun$resolveData$20(QueryGen.scala:331)
[error]         at scala.collection.immutable.List.map(List.scala:250)
[error]         at clue.gen.QueryGen.$anonfun$resolveData$19(QueryGen.scala:331)
[error]         at scala.collection.immutable.List.map(List.scala:246)
[error]         at clue.gen.QueryGen.clue$gen$QueryGen$$go$1(QueryGen.scala:323)
[error]         at clue.gen.QueryGen.$anonfun$resolveData$11(QueryGen.scala:274)
[error]         at scala.Option.map(Option.scala:242)
[error]         at clue.gen.QueryGen.$anonfun$resolveData$10(QueryGen.scala:274)
[error]         at scala.Option.fold(Option.scala:263)
[error]         at clue.gen.QueryGen.clue$gen$QueryGen$$go$1(QueryGen.scala:272)
[error]         at clue.gen.QueryGen$$anonfun$$nestedInanonfun$resolveData$19$1.applyOrElse(QueryGen.scala:327)
[error]         at clue.gen.QueryGen$$anonfun$$nestedInanonfun$resolveData$19$1.applyOrElse(QueryGen.scala:326)
[error]         at scala.collection.immutable.List.collect(List.scala:275)
[error]         at clue.gen.QueryGen.$anonfun$resolveData$19(QueryGen.scala:326)
[error]         at scala.collection.immutable.List.map(List.scala:246)
[error]         at clue.gen.QueryGen.clue$gen$QueryGen$$go$1(QueryGen.scala:323)
[error]         at clue.gen.QueryGen.$anonfun$resolveData$11(QueryGen.scala:274)
[error]         at scala.Option.map(Option.scala:242)
[error]         at clue.gen.QueryGen.$anonfun$resolveData$10(QueryGen.scala:274)
[error]         at scala.Option.fold(Option.scala:263)
[error]         at clue.gen.QueryGen.clue$gen$QueryGen$$go$1(QueryGen.scala:272)
[error]         at clue.gen.QueryGen.$anonfun$resolveData$20(QueryGen.scala:331)
[error]         at scala.collection.immutable.List.map(List.scala:246)
[error]         at clue.gen.QueryGen.$anonfun$resolveData$19(QueryGen.scala:331)
[error]         at scala.collection.immutable.List.map(List.scala:246)
[error]         at clue.gen.QueryGen.clue$gen$QueryGen$$go$1(QueryGen.scala:323)
[error]         at clue.gen.QueryGen.$anonfun$resolveData$11(QueryGen.scala:274)
[error]         at scala.Option.map(Option.scala:242)
[error]         at clue.gen.QueryGen.$anonfun$resolveData$10(QueryGen.scala:274)
[error]         at scala.Option.fold(Option.scala:263)
[error]         at clue.gen.QueryGen.clue$gen$QueryGen$$go$1(QueryGen.scala:272)
[error]         at clue.gen.QueryGen.$anonfun$resolveData$20(QueryGen.scala:331)
[error]         at scala.collection.immutable.List.map(List.scala:250)
[error]         at clue.gen.QueryGen.$anonfun$resolveData$19(QueryGen.scala:331)
[error]         at scala.collection.immutable.List.map(List.scala:246)
[error]         at clue.gen.QueryGen.clue$gen$QueryGen$$go$1(QueryGen.scala:323)
[error]         at clue.gen.QueryGen$$anonfun$$nestedInanonfun$resolveData$19$1.applyOrElse(QueryGen.scala:327)
[error]         at clue.gen.QueryGen$$anonfun$$nestedInanonfun$resolveData$19$1.applyOrElse(QueryGen.scala:326)
[error]         at scala.collection.immutable.List.collect(List.scala:267)
[error]         at clue.gen.QueryGen.$anonfun$resolveData$19(QueryGen.scala:326)
[error]         at scala.collection.immutable.List.map(List.scala:250)
[error]         at clue.gen.QueryGen.clue$gen$QueryGen$$go$1(QueryGen.scala:323)
[error]         at clue.gen.QueryGen.$anonfun$resolveData$11(QueryGen.scala:274)
[error]         at scala.Option.map(Option.scala:242)
[error]         at clue.gen.QueryGen.$anonfun$resolveData$10(QueryGen.scala:274)
[error]         at scala.Option.fold(Option.scala:263)
[error]         at clue.gen.QueryGen.clue$gen$QueryGen$$go$1(QueryGen.scala:272)
[error]         at clue.gen.QueryGen.$anonfun$resolveData$11(QueryGen.scala:274)
[error]         at scala.Option.map(Option.scala:242)
[error]         at clue.gen.QueryGen.$anonfun$resolveData$10(QueryGen.scala:274)
[error]         at scala.Option.fold(Option.scala:263)
[error]         at clue.gen.QueryGen.clue$gen$QueryGen$$go$1(QueryGen.scala:272)
[error]         at clue.gen.QueryGen.$anonfun$resolveData$11(QueryGen.scala:274)
[error]         at scala.Option.map(Option.scala:242)
[error]         at clue.gen.QueryGen.$anonfun$resolveData$10(QueryGen.scala:274)
[error]         at scala.Option.fold(Option.scala:263)
[error]         at clue.gen.QueryGen.clue$gen$QueryGen$$go$1(QueryGen.scala:272)
[error]         at clue.gen.QueryGen.resolveData(QueryGen.scala:376)
[error]         at clue.gen.QueryGen.resolveData$(QueryGen.scala:220)
[error]         at clue.gen.GraphQLGen.resolveData(GraphQLGen.scala:15)
[error]         at clue.gen.QueryGen.$anonfun$addData$1(QueryGen.scala:416)
[error]         at scala.Function$.$anonfun$chain$2(Function.scala:23)
[error]         at scala.collection.LinearSeqOps.foldLeft(LinearSeq.scala:183)
[error]         at scala.collection.LinearSeqOps.foldLeft$(LinearSeq.scala:179)
[error]         at scala.collection.immutable.List.foldLeft(List.scala:79)
[error]         at scala.Function$.$anonfun$chain$1(Function.scala:23)
[error]         at clue.gen.GraphQLGen$$anonfun$2.$anonfun$applyOrElse$8(GraphQLGen.scala:138)
[error]         at apply @ clue.gen.GraphQLGen$$anonfun$2.$anonfun$applyOrElse$7(GraphQLGen.scala:115)
[error]         at >> @ clue.gen.GraphQLGenConfig.$anonfun$retrieveSchema$11(GraphQLGenConfig.scala:70)
[error]         at complete @ clue.gen.GraphQLGenConfig.$anonfun$getSchema$3(GraphQLGenConfig.scala:84)
[error]         at >> @ clue.gen.GraphQLGenConfig.$anonfun$retrieveSchema$11(GraphQLGenConfig.scala:70)
[error]         at apply @ clue.gen.GraphQLGenConfig.$anonfun$retrieveSchema$9(GraphQLGenConfig.scala:56)
[error]         at flatten @ clue.gen.GraphQLGenConfig.$anonfun$getSchema$1(GraphQLGenConfig.scala:87)
[error]         at flatTap @ clue.gen.GraphQLGenConfig.$anonfun$getSchema$2(GraphQLGenConfig.scala:84)
[error] (graphql / Compile / scalafix) scalafix.sbt.ScalafixFailed: UnexpectedError
armanbilge commented 1 year ago

Looks like for some reason this case is not matching, and it's proceeding to the next one, where it crashes. https://github.com/gemini-hlsw/clue/blob/1dc39739b30223dabeaf766b4a9d67775877531f/gen/rules/src/main/scala/clue/gen/QueryGen.scala#L241

armanbilge commented 1 year ago

So I've been staring at this one, and actually I am not sure if it's supposed to work—at least not in general. I think the issue is this bit:

... on Gcal $GcalStepConfigSubquery

I have a recollection that we discussed at one point, how fragments and subqueries actually do not mix. Because in the general case fragments are actually defining a new result type (that would not match the subquery's type).

e.g. in this example, fragments are creating a new result type that includes name. If on Droid or on Human was a subquery, it would not include name and thus those datatypes cannot be used.

query HeroForEpisode($ep: Episode!) {
  hero(episode: $ep) {
    name
    ... on Droid {
      primaryFunction
    }
    ... on Human {
      height
    }
  }
}

If my analysis is correct, then I think we should consider:

  1. arguably this is a special case, since you are only expanding a single fragment with no other fields. So should it get special treatment?
  2. otherwise this probably deserves a nicer error message :)

Thoughts?

rpiaggio commented 1 year ago

You are totally right. Interface queries don't necessarily materialize into an instance. We should extract a subquery on the actual instantiated type (hero in your example).

We could still define reusable blocks for these cases (ie: special case), and I think it would be just a string with the subquery (no output type or decoder), only that we would have to resolve the interpolation at compile time, like we do for actual subqueries. I wonder if this is worthwhile, thoughts? Do you think this it's straightforward or that I'm missing something?

armanbilge commented 1 year ago

We could still define reusable blocks for these cases (ie: special case), and I think it would be just a string with the subquery (no output type or decoder), only that we would have to resolve the interpolation at compile time, like we do for actual subqueries.

Oh, I see! So in this case, we just want to interpolate the string part of the query, and not share/reuse data classes? Did I understand correctly?

rpiaggio commented 1 year ago

Oh, I see! So in this case, we just want to interpolate the string part of the query, and not share/reuse data classes? Did I understand correctly?

Yes, that's what I mean. In other words, compile-time concatenation. It might be useful too when there are common fields but not a common interface. WDYT?

armanbilge commented 1 year ago

Yes, I do think that would be useful! I actually attempted that when I first started working on subqueries, because I assumed it would be helpful. It turned out to be difficult and not strictly needed 😂

I still have the branch lying around, I can give it another shot :) the strategy I was trying to use there, was that the Scala compiler will automatically concatenate final val strings at compile time. So I was hoping we could tap into that somehow. https://github.com/gemini-hlsw/clue/commits/pr/final-val-document