Skip to content

Commit

Permalink
Merge pull request #152 from gemini-hlsw/topic/rework-sql-compiler
Browse files Browse the repository at this point in the history
Rework SQL compiler
  • Loading branch information
milessabin authored Jul 12, 2021
2 parents 4129433 + f03fbd6 commit 5d04a66
Show file tree
Hide file tree
Showing 77 changed files with 4,579 additions and 1,672 deletions.
21 changes: 8 additions & 13 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ val attoVersion = "0.9.5"
val catsVersion = "2.6.1"
val catsEffectVersion = "2.4.1"
val catsTestkitScalaTestVersion = "2.1.5"
val circeVersion = "0.14.1"
val doobieVersion = "0.13.4"
val fs2Version = "2.5.6"
val http4sVersion = "0.22.0-M8"
val kindProjectorVersion = "0.11.3"
val fs2Version = "2.5.8"
val http4sVersion = "0.22.0-RC1"
val kindProjectorVersion = "0.13.0"
val literallyVersion = "1.0.2"
val logbackVersion = "1.2.3"
val log4catsVersion = "1.3.1"
Expand All @@ -16,8 +17,8 @@ val sourcePosVersion = "1.0.0"
val testContainersVersion = "0.39.5"
val typenameVersion = "1.0.0"

val Scala2 = "2.13.5"
val Scala3 = "3.0.0"
val Scala2 = "2.13.6"
val Scala3 = "3.0.1"

inThisBuild(Seq(
homepage := Some(url("https://github.com/gemini-hlsw/gsp-graphql")),
Expand All @@ -39,7 +40,7 @@ lazy val commonSettings = Seq(
"org.typelevel" %% "cats-testkit" % catsVersion % "test",
"org.typelevel" %% "cats-testkit-scalatest" % catsTestkitScalaTestVersion % "test"
) ++ Seq(
compilerPlugin("org.typelevel" %% "kind-projector" % "0.11.3" cross CrossVersion.full),
compilerPlugin("org.typelevel" %% "kind-projector" % kindProjectorVersion cross CrossVersion.full),
).filterNot(_ => scalaVersion.value.startsWith("3.")),
headerMappings := headerMappings.value + (HeaderFileType.scala -> HeaderCommentStyle.cppStyleLineComment),
headerLicense := Some(HeaderLicense.Custom(
Expand Down Expand Up @@ -79,11 +80,7 @@ lazy val core = project
.settings(commonSettings)
.settings(
name := "gsp-graphql-core",
libraryDependencies ++= {
val circeVersion = scalaVersion.value match {
case Scala3 => "0.14.0-M7"
case Scala2 => "0.13.0"
}
libraryDependencies ++=
Seq(
"org.tpolecat" %% "atto-core" % attoVersion,
"org.typelevel" %% "cats-core" % catsVersion,
Expand All @@ -94,7 +91,6 @@ lazy val core = project
"org.tpolecat" %% "sourcepos" % sourcePosVersion,
"co.fs2" %% "fs2-core" % fs2Version,
)
}
)

lazy val circe = project
Expand Down Expand Up @@ -170,7 +166,6 @@ lazy val generic = project
})
)

// TODO: re-enable when http4s is available for 3.0.0-RC3
lazy val demo = project
.in(file("demo"))
.enablePlugins(AutomateHeaderPlugin)
Expand Down
4 changes: 2 additions & 2 deletions demo/src/main/scala/starwars/StarWarsData.scala
Original file line number Diff line number Diff line change
Expand Up @@ -210,14 +210,14 @@ object StarWarsMapping extends GenericMapping[Id] {
// Unique operator to pick out the target using the FieldEquals predicate.
case Select("hero", List(Binding("episode", TypedEnumValue(e))), child) =>
Episode.values.find(_.toString == e.name).map { episode =>
Select("hero", Nil, Unique(Eql(UniquePath(List("id")), Const(hero(episode).id)), child)).rightIor
Select("hero", Nil, Unique(Filter(Eql(UniquePath(List("id")), Const(hero(episode).id)), child))).rightIor
}.getOrElse(mkErrorResult(s"Unknown episode '${e.name}'"))

// The character, human and droid selectors all take a single ID argument and yield a
// single value (if any) or null. We use the Unique operator to pick out the target
// using the FieldEquals predicate.
case Select(f@("character" | "human" | "droid"), List(Binding("id", IDValue(id))), child) =>
Select(f, Nil, Unique(Eql(UniquePath(List("id")), Const(id)), child)).rightIor
Select(f, Nil, Unique(Filter(Eql(UniquePath(List("id")), Const(id)), child))).rightIor
}
))
// #elaborator
Expand Down
2 changes: 1 addition & 1 deletion docs/src/main/paradox/tutorial/in-memory-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ Extracting out the case for the `character` selector,

```scala
case Select("character", List(Binding("id", IDValue(id))), child) =>
Select("character", Nil, Unique(Eql(FieldPath(List("id")), Const(id)), child)).rightIor
Select("character", Nil, Unique(Filter(Eql(UniquePath(List("id")), Const(id)), child))).rightIor
```

we can see that this transforms the previous term as follows,
Expand Down
13 changes: 13 additions & 0 deletions local-docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
version: "3.7"

services:
postgres:
image: postgres:11.8
ports:
- "5432:5432"
environment:
- POSTGRES_DB=test
- POSTGRES_USER=test
- POSTGRES_PASSWORD=test
volumes:
- ./modules/sql/src/test/resources/db/:/docker-entrypoint-initdb.d/
31 changes: 15 additions & 16 deletions modules/circe/src/main/scala/circemapping.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@ package circe

import cats.Monad
import cats.implicits._
import io.circe.Json
import fs2.Stream
import io.circe.Json
import org.tpolecat.sourcepos.SourcePos

import Cursor.Env
import Cursor.{Context, Env}
import QueryInterpreter.{mkErrorResult, mkOneError}
import ScalarType._
import org.tpolecat.sourcepos.SourcePos

abstract class CirceMapping[F[_]: Monad] extends Mapping[F] {
case class CirceRoot(val otpe: Option[Type], val fieldName: String, root: Json, mutation: Mutation)(
implicit val pos: SourcePos
) extends RootMapping {
def cursor(query: Query, env: Env): Stream[F,Result[Cursor]] = {
def cursor(query: Query, env: Env, resultName: Option[String]): Stream[F,Result[(Query, Cursor)]] = {
(for {
tpe <- otpe
fieldTpe <- tpe.field(fieldName)
Expand All @@ -27,7 +27,7 @@ abstract class CirceMapping[F[_]: Monad] extends Mapping[F] {
case _: Query.Unique => fieldTpe.nonNull.list
case _ => fieldTpe
}
CirceCursor(Nil, cursorTpe, root, None, env).rightIor
(query, CirceCursor(Context(fieldName, resultName, cursorTpe), root, None, env)).rightIor
}).getOrElse(mkErrorResult(s"Type ${otpe.getOrElse("unspecified type")} has no field '$fieldName'")).pure[Stream[F,*]]
}

Expand All @@ -41,16 +41,15 @@ abstract class CirceMapping[F[_]: Monad] extends Mapping[F] {
}

case class CirceCursor(
path: List[String],
tpe: Type,
context: Context,
focus: Json,
parent: Option[Cursor],
env: Env
) extends Cursor {
def withEnv(env0: Env): Cursor = copy(env = env.add(env0))

def mkChild(path: List[String] = path, tpe: Type = tpe, focus: Json = focus): CirceCursor =
CirceCursor(path, tpe, focus, Some(this), Env.empty)
def mkChild(context: Context = context, focus: Json = focus): CirceCursor =
CirceCursor(context, focus, Some(this), Env.empty)

def isLeaf: Boolean =
tpe.dealias match {
Expand Down Expand Up @@ -80,7 +79,7 @@ abstract class CirceMapping[F[_]: Monad] extends Mapping[F] {

def asList: Result[List[Cursor]] = tpe match {
case ListType(elemTpe) if focus.isArray =>
focus.asArray.map(_.map(e => mkChild(tpe = elemTpe, focus = e)).toList)
focus.asArray.map(_.map(e => mkChild(context.asType(elemTpe), e)).toList)
.toRightIor(mkOneError(s"Expected List type, found $tpe for focus ${focus.noSpaces}"))
case _ =>
mkErrorResult(s"Expected List type, found $tpe for focus ${focus.noSpaces}")
Expand All @@ -91,7 +90,7 @@ abstract class CirceMapping[F[_]: Monad] extends Mapping[F] {
def asNullable: Result[Option[Cursor]] = tpe match {
case NullableType(tpe) =>
if (focus.isNull) None.rightIor
else Some(mkChild(tpe = tpe)).rightIor
else Some(mkChild(context.asType(tpe))).rightIor
case _ => mkErrorResult(s"Expected Nullable type, found $focus for $tpe")
}

Expand All @@ -108,18 +107,18 @@ abstract class CirceMapping[F[_]: Monad] extends Mapping[F] {

def narrow(subtpe: TypeRef): Result[Cursor] =
if (narrowsTo(subtpe))
mkChild(tpe = subtpe).rightIor
mkChild(context.asType(subtpe)).rightIor
else
mkErrorResult(s"Focus ${focus} of static type $tpe cannot be narrowed to $subtpe")

def hasField(fieldName: String): Boolean =
tpe.hasField(fieldName) && focus.asObject.map(_.contains(fieldName)).getOrElse(false)

def field(fieldName: String): Result[Cursor] = {
def field(fieldName: String, resultName: Option[String]): Result[Cursor] = {
val f = focus.asObject.flatMap(_(fieldName))
(tpe.field(fieldName), f) match {
case (Some(ftpe), None) if ftpe.isNullable => mkChild(path = fieldName :: path, tpe = ftpe, focus = Json.Null).rightIor
case (Some(ftpe), Some(json)) => mkChild(path = fieldName :: path, tpe = ftpe, focus = json).rightIor
(context.forField(fieldName, resultName), f) match {
case (Some(fieldContext), None) if fieldContext.tpe.isNullable => mkChild(fieldContext, Json.Null).rightIor
case (Some(fieldContext), Some(json)) => mkChild(fieldContext, json).rightIor
case _ =>
mkErrorResult(s"No field '$fieldName' for type $tpe")
}
Expand Down
16 changes: 6 additions & 10 deletions modules/core/src/main/scala/compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

package edu.gemini.grackle

import scala.annotation.tailrec

import atto.Atto._
import cats.data.Ior
import cats.implicits._
Expand All @@ -13,8 +15,6 @@ import QueryCompiler._
import QueryInterpreter.{ mkErrorResult, mkOneError }
import ScalarType._

import scala.annotation.tailrec

/**
* GraphQL query parser
*/
Expand Down Expand Up @@ -79,7 +79,7 @@ object QueryParser {

def parseSelections(sels: List[Selection], typeCondition: Option[String], fragments: Map[String, FragmentDefinition]): Result[Query] =
sels.traverse(parseSelection(_, typeCondition, fragments)).map { sels0 =>
if (sels0.size == 1) sels0.head else Group(sels0)
if (sels0.sizeCompare(1) == 0) sels0.head else Group(sels0)
}

def parseSelection(sel: Selection, typeCondition: Option[String], fragments: Map[String, FragmentDefinition]): Result[Query] = sel match {
Expand Down Expand Up @@ -406,15 +406,13 @@ object QueryCompiler {
case r@Rename(_, child) => transform(child, vars, schema, tpe).map(ec => r.copy(child = ec))
case g@Group(children) => children.traverse(q => transform(q, vars, schema, tpe)).map(eqs => g.copy(queries = eqs))
case g@GroupList(children) => children.traverse(q => transform(q, vars, schema, tpe)).map(eqs => g.copy(queries = eqs))
case u@Unique(_, child) => transform(child, vars, schema, tpe.nonNull).map(ec => u.copy(child = ec))
case u@Unique(child) => transform(child, vars, schema, tpe.nonNull.list).map(ec => u.copy(child = ec))
case f@Filter(_, child) => tpe.item.toRightIor(mkOneError(s"Filter of non-List type $tpe")).flatMap(item => transform(child, vars, schema, item).map(ec => f.copy(child = ec)))
case c@Component(_, _, child) => transform(child, vars, schema, tpe).map(ec => c.copy(child = ec))
case d@Defer(_, child, _) => transform(child, vars, schema, tpe).map(ec => d.copy(child = ec))
case s@Skip(_, _, child) => transform(child, vars, schema, tpe).map(ec => s.copy(child = ec))
case l@Limit(_, child) => transform(child, vars, schema, tpe).map(ec => l.copy(child = ec))
case o@OrderBy(_, child) => transform(child, vars, schema, tpe).map(ec => o.copy(child = ec))
case g@GroupBy(_, child) => transform(child, vars, schema, tpe).map(ec => g.copy(child = ec))
case c@Context(_, child) => transform(child, vars, schema, tpe).map(ec => c.copy(child = ec))
case e@Environment(_, child) => transform(child, vars, schema, tpe).map(ec => e.copy(child = ec))
case Skipped => Skipped.rightIor
case Empty => Empty.rightIor
Expand Down Expand Up @@ -600,7 +598,7 @@ object QueryCompiler {
val introspectionMapping: Map[TypeRef, PartialFunction[Select, Result[Query]]] = Map(
Introspection.schema.ref("Query") -> {
case sel@Select("__type", List(Binding("name", StringValue(name))), _) =>
sel.eliminateArgs(child => Unique(Eql(UniquePath(List("name")), Const(Option(name))), child)).rightIor
sel.eliminateArgs(child => Unique(Filter(Eql(UniquePath(List("name")), Const(Option(name))), child))).rightIor
},
Introspection.schema.ref("__Type") -> {
case sel@Select("fields", List(Binding("includeDeprecated", BooleanValue(include))), _) =>
Expand Down Expand Up @@ -695,20 +693,18 @@ object QueryCompiler {
case Group(queries) => handleGroupedQueries(queries, depth, width)
case GroupList(queries) => handleGroupedQueries(queries, depth, width)
case Component(_, _, child) => loop(child, depth, width, false)
case Context(_, child) => loop(child, depth, width, false)
case Environment(_, child) => loop(child, depth, width, false)
case Empty => (depth, width)
case Defer(_, child, _) => loop(child, depth, width, false)
case Filter(_, child) => loop(child, depth, width, false)
case GroupBy(_, child) => loop(child, depth, width, false)
case Introspect(_, _) => (depth, width)
case Limit(_, child) => loop(child, depth, width, false)
case Narrow(_, child) => loop(child, depth, width, true)
case OrderBy(_, child) => loop(child, depth, width, false)
case Rename(_, child) => loop(child, depth, width, false)
case Skip(_, _, child) => loop(child, depth, width, false)
case Skipped => (depth, width)
case Unique(_, child) => loop(child, depth, width, false)
case Unique(child) => loop(child, depth, width, false)
case UntypedNarrow(_, child) => loop(child, depth, width, false)
case Wrap(_, child) => loop(child, depth, width, false)
}
Expand Down
Loading

0 comments on commit 5d04a66

Please sign in to comment.