change query: edge centric to vertec centric
Project: http://git-wip-us.apache.org/repos/asf/incubator-s2graph/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-s2graph/commit/a5afdf20 Tree: http://git-wip-us.apache.org/repos/asf/incubator-s2graph/tree/a5afdf20 Diff: http://git-wip-us.apache.org/repos/asf/incubator-s2graph/diff/a5afdf20 Branch: refs/heads/master Commit: a5afdf20dae62c272837c1d84a910d5e0fdc393f Parents: 07d816d Author: daewon <[email protected]> Authored: Fri Mar 23 18:35:21 2018 +0900 Committer: daewon <[email protected]> Committed: Fri Mar 23 18:35:21 2018 +0900 ---------------------------------------------------------------------- .../s2graph/graphql/marshaller/package.scala | 82 ++--- .../graphql/repository/GraphRepository.scala | 43 +-- .../s2graph/graphql/resolver/Resolver.scala | 8 +- .../apache/s2graph/graphql/types/S2Type.scala | 256 +++++++++------ .../apache/s2graph/graphql/types/package.scala | 15 +- .../apache/s2graph/graphql/ScenarioTest.scala | 328 ++++++++++++------- 6 files changed, 410 insertions(+), 322 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/a5afdf20/s2graphql/src/main/scala/org/apache/s2graph/graphql/marshaller/package.scala ---------------------------------------------------------------------- diff --git a/s2graphql/src/main/scala/org/apache/s2graph/graphql/marshaller/package.scala b/s2graphql/src/main/scala/org/apache/s2graph/graphql/marshaller/package.scala index 97a8097..ac73e14 100644 --- a/s2graphql/src/main/scala/org/apache/s2graph/graphql/marshaller/package.scala +++ b/s2graphql/src/main/scala/org/apache/s2graph/graphql/marshaller/package.scala @@ -3,35 +3,53 @@ package org.apache.s2graph.graphql import org.apache.s2graph.core.Management.JsonModel._ import org.apache.s2graph.graphql.types.S2Type._ import sangria.marshalling._ +import sangria.schema.Args package object marshaller { - def unwrapOption(map: Map[String, Any]): Map[String, Any] = map.map { - case (k, v: Some[_]) => v.get match { - case m: Map[_, _] => k -> unwrapOption(m.asInstanceOf[Map[String, Any]]) - case _ => k -> v.get - } - case (k, v: Map[_, _]) => k -> unwrapOption(v.asInstanceOf[Map[String, Any]]) - case org@_ => org + type RawNode = Map[String, Any] + + def unwrap(any: Any): Any = any match { + case s: Some[_] => unwrap(s.get) + case v: Seq[_] => v.map(unwrap) + case m: Map[_, _] => m.mapValues(unwrap) + case _ => any } - implicit object AddVertexParamFromInput extends FromInput[Vector[AddVertexParam]] { + implicit object AddVertexParamFromInput extends FromInput[List[AddVertexParam]] { val marshaller = CoercedScalaResultMarshaller.default def fromResult(node: marshaller.Node) = { - val currentTime = System.currentTimeMillis() - val inenrMap = unwrapOption(node.asInstanceOf[Map[String, Any]]) + val now = System.currentTimeMillis() + val map = unwrap(node).asInstanceOf[RawNode] - val params = inenrMap.flatMap { case (serviceName, serviceColumnMap) => - serviceColumnMap.asInstanceOf[Map[String, Any]].map { case (columnName, vertexParamMap) => - val propMap = vertexParamMap.asInstanceOf[Map[String, Any]] - val id = propMap("id") - val ts = propMap.getOrElse("timestamp", currentTime).asInstanceOf[Long] + val params = map.flatMap { case (columnName, vls: Vector[_]) => + vls.map { _m => + val m = _m.asInstanceOf[RawNode] + val id = m("id") + val ts = m.getOrElse("timestamp", now).asInstanceOf[Long] - AddVertexParam(ts, id, serviceName, columnName, propMap.filterKeys(k => k != "timestamp" || k != "id")) + AddVertexParam(ts, id, columnName, props = m) } } - params.toVector + params.toList + } + } + + implicit object AddEdgeParamFromInput extends FromInput[AddEdgeParam] { + val marshaller = CoercedScalaResultMarshaller.default + + def fromResult(node: marshaller.Node) = { + val inputMap = unwrap(node).asInstanceOf[RawNode] + val now = System.currentTimeMillis() + + val from = inputMap("from") + val to = inputMap("to") + val ts = inputMap.get("timestamp").map(_.asInstanceOf[Long]).getOrElse(now) + val dir = inputMap.get("direction").map(_.asInstanceOf[String]).getOrElse("out") + val props = inputMap + + AddEdgeParam(ts, from, to, dir, props) } } @@ -69,7 +87,7 @@ package object marshaller { val marshaller = CoercedScalaResultMarshaller.default def fromResult(node: marshaller.Node) = { - val input = unwrapOption(node.asInstanceOf[Map[String, Any]]) + val input = unwrap(node.asInstanceOf[Map[String, Any]]).asInstanceOf[Map[String, Any]] val partialServiceColumns = input.map { case (serviceName, serviceColumnMap) => val innerMap = serviceColumnMap.asInstanceOf[Map[String, Any]] @@ -85,32 +103,4 @@ package object marshaller { } } - implicit object AddEdgeParamFromInput extends FromInput[AddEdgeParam] { - val marshaller = CoercedScalaResultMarshaller.default - - def fromResult(node: marshaller.Node) = { - val inputMap = node.asInstanceOf[Map[String, Any]] - - val from = inputMap("from") - val to = inputMap("to") - - val ts = inputMap.get("timestamp") match { - case Some(Some(v)) => v.asInstanceOf[Long] - case _ => System.currentTimeMillis() - } - - val dir = inputMap.get("direction") match { - case Some(Some(v)) => v.asInstanceOf[String] - case _ => "out" - } - - val props = inputMap.get("props") match { - case Some(Some(v)) => v.asInstanceOf[Map[String, Option[Any]]].filter(_._2.isDefined).mapValues(_.get) - case _ => Map.empty[String, Any] - } - - AddEdgeParam(ts, from, to, dir, props) - } - } - } http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/a5afdf20/s2graphql/src/main/scala/org/apache/s2graph/graphql/repository/GraphRepository.scala ---------------------------------------------------------------------- diff --git a/s2graphql/src/main/scala/org/apache/s2graph/graphql/repository/GraphRepository.scala b/s2graphql/src/main/scala/org/apache/s2graph/graphql/repository/GraphRepository.scala index 4c802c3..ac6b167 100644 --- a/s2graphql/src/main/scala/org/apache/s2graph/graphql/repository/GraphRepository.scala +++ b/s2graphql/src/main/scala/org/apache/s2graph/graphql/repository/GraphRepository.scala @@ -25,7 +25,6 @@ import org.apache.s2graph.core.mysqls._ import org.apache.s2graph.core.rest.RequestParser import org.apache.s2graph.core.storage.MutateResponse import org.apache.s2graph.core.types._ -import org.apache.s2graph.graphql.types.S2ManagementType.PropWithColumn import org.apache.s2graph.graphql.types.S2Type._ import sangria.schema._ @@ -46,7 +45,7 @@ class GraphRepository(val graph: S2GraphLike) { implicit val ec = graph.ec - def AddEdgeParamToS2Edge(labelName: String, param: AddEdgeParam): S2EdgeLike = { + def toS2EdgeLike(labelName: String, param: AddEdgeParam): S2EdgeLike = { graph.toEdge( srcId = param.from, tgtId = param.to, @@ -56,47 +55,27 @@ class GraphRepository(val graph: S2GraphLike) { ) } - def toVertex(vid: Any, column: ServiceColumn): S2VertexLike = { + def toS2VertexLike(vid: Any, column: ServiceColumn): S2VertexLike = { graph.toVertex(column.service.serviceName, column.columnName, vid) } - def parseAddVertexParam(args: Args): Seq[S2VertexLike] = { - val vertexParams = args.arg[Vector[Vector[AddVertexParam]]]("vertex") - - vertexParams.flatMap { params => - params.map { param => - graph.toVertex( - serviceName = param.serviceName, - columnName = param.columnName, - id = param.id, - props = param.props, - ts = param.timestamp) - } - } + def toS2VertexLike(serviceName: String, param: AddVertexParam): S2VertexLike = { + graph.toVertex( + serviceName = serviceName, + columnName = param.columnName, + id = param.id, + props = param.props, + ts = param.timestamp) } - def addVertex(vertices: Seq[S2VertexLike]): Future[Seq[MutateResponse]] = { + def addVertices(vertices: Seq[S2VertexLike]): Future[Seq[MutateResponse]] = { graph.mutateVertices(vertices, withWait = true) } - def addEdges(args: Args): Future[Seq[MutateResponse]] = { - val edges: Seq[S2EdgeLike] = args.raw.keys.toList.flatMap { labelName => - val params = args.arg[Vector[AddEdgeParam]](labelName) - params.map(param => AddEdgeParamToS2Edge(labelName, param)) - } - + def addEdges(edges: Seq[S2EdgeLike]): Future[Seq[MutateResponse]] = { graph.mutateEdges(edges, withWait = true) } - def addEdge(args: Args): Future[Option[MutateResponse]] = { - val edges: Seq[S2EdgeLike] = args.raw.keys.toList.map { labelName => - val param = args.arg[AddEdgeParam](labelName) - AddEdgeParamToS2Edge(labelName, param) - } - - graph.mutateEdges(edges, withWait = true).map(_.headOption) - } - def getVertices(vertex: Seq[S2VertexLike]): Future[Seq[S2VertexLike]] = { graph.getVertices(vertex) } http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/a5afdf20/s2graphql/src/main/scala/org/apache/s2graph/graphql/resolver/Resolver.scala ---------------------------------------------------------------------- diff --git a/s2graphql/src/main/scala/org/apache/s2graph/graphql/resolver/Resolver.scala b/s2graphql/src/main/scala/org/apache/s2graph/graphql/resolver/Resolver.scala index 0715793..3310276 100644 --- a/s2graphql/src/main/scala/org/apache/s2graph/graphql/resolver/Resolver.scala +++ b/s2graphql/src/main/scala/org/apache/s2graph/graphql/resolver/Resolver.scala @@ -1,5 +1,9 @@ -package org.apache.s2graph.graphql.resolvers +package org.apache.s2graph.graphql.resolver -object resolver { +import org.apache.s2graph.core.S2VertexLike +import org.apache.s2graph.graphql.repository.GraphRepository +object Resolver { + def vertexResolver(v: S2VertexLike)(implicit repo: GraphRepository) { + } } http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/a5afdf20/s2graphql/src/main/scala/org/apache/s2graph/graphql/types/S2Type.scala ---------------------------------------------------------------------- diff --git a/s2graphql/src/main/scala/org/apache/s2graph/graphql/types/S2Type.scala b/s2graphql/src/main/scala/org/apache/s2graph/graphql/types/S2Type.scala index 0e7b9ba..59506d4 100644 --- a/s2graphql/src/main/scala/org/apache/s2graph/graphql/types/S2Type.scala +++ b/s2graphql/src/main/scala/org/apache/s2graph/graphql/types/S2Type.scala @@ -22,23 +22,16 @@ package org.apache.s2graph.graphql.types import org.apache.s2graph.core.Management.JsonModel.{Index, Prop} import org.apache.s2graph.core._ import org.apache.s2graph.core.mysqls._ -import org.apache.s2graph.core.storage.MutateResponse -import org.apache.s2graph.graphql._ import org.apache.s2graph.graphql.repository.GraphRepository -import org.apache.s2graph.graphql.types.S2ManagementType._ -import play.api.libs.json.JsValue -import sangria.marshalling.{CoercedScalaResultMarshaller, FromInput} import sangria.schema._ import scala.language.existentials -import scala.util.{Failure, Success, Try} import org.apache.s2graph.graphql.marshaller._ object S2Type { case class AddVertexParam(timestamp: Long, id: Any, - serviceName: String, columnName: String, props: Map[String, Any]) @@ -46,9 +39,10 @@ object S2Type { from: Any, to: Any, direction: String, - props: Map[String, Any]) + props: Map[String, Any]) { + } - // management params + // Management params case class ServiceColumnParam(serviceName: String, columnName: String, props: Seq[Prop] = Nil) @@ -56,66 +50,78 @@ object S2Type { val DirArg = Argument("direction", OptionInputType(DirectionType), "desc here", defaultValue = "out") - def makePropFields(fieldNameWithTypes: List[(String, String)]): List[Field[GraphRepository, Any]] = { - def makeField[A](name: String, cType: String, tpe: ScalarType[A]): Field[GraphRepository, Any] = - Field(name, - OptionType(tpe), - description = Option("desc here"), - resolve = _.value match { - case v: S2VertexLike => - name match { - case "timestamp" => v.ts.asInstanceOf[A] - case _ => - val innerVal = v.propertyValue(name).get - JSONParser.innerValToAny(innerVal, cType).asInstanceOf[A] - } - - case e: S2EdgeLike => - name match { - case "timestamp" => e.ts.asInstanceOf[A] - case _ => - val innerVal = e.propertyValue(name).get.innerVal - JSONParser.innerValToAny(innerVal, cType).asInstanceOf[A] - } - - case _ => throw new RuntimeException("Error !!!!") - }) - - fieldNameWithTypes.map { case (cName, cType) => - cType match { - case "boolean" | "bool" => makeField[Boolean](cName, cType, BooleanType) - case "string" | "str" | "s" => makeField[String](cName, cType, StringType) - case "int" | "integer" | "i" | "int32" | "integer32" => makeField[Int](cName, cType, IntType) - case "long" | "l" | "int64" | "integer64" => makeField[Long](cName, cType, LongType) - case "double" | "d" | "float64" | "float" | "f" | "float32" => makeField[Double](cName, cType, FloatType) - case _ => throw new RuntimeException(s"Cannot support data type: ${cType}") + def makeField[A](name: String, cType: String, tpe: ScalarType[A]): Field[GraphRepository, Any] = + Field(name, + OptionType(tpe), + description = Option("desc here"), + resolve = c => c.value match { + case v: S2VertexLike => name match { + case "timestamp" => v.ts.asInstanceOf[A] + case _ => + val innerVal = v.propertyValue(name).get + JSONParser.innerValToAny(innerVal, cType).asInstanceOf[A] + } + case e: S2EdgeLike => name match { + case "timestamp" => e.ts.asInstanceOf[A] + case "direction" => e.getDirection().asInstanceOf[A] + case _ => + val innerVal = e.propertyValue(name).get.innerVal + JSONParser.innerValToAny(innerVal, cType).asInstanceOf[A] + } + case _ => + throw new RuntimeException(s"Error on resolving field: ${name}, ${cType}, ${c.value.getClass}") } - } + ) + + def makePropField(cName: String, cType: String): Field[GraphRepository, Any] = cType match { + case "boolean" | "bool" => makeField[Boolean](cName, cType, BooleanType) + case "string" | "str" | "s" => makeField[String](cName, cType, StringType) + case "int" | "integer" | "i" | "int32" | "integer32" => makeField[Int](cName, cType, IntType) + case "long" | "l" | "int64" | "integer64" => makeField[Long](cName, cType, LongType) + case "double" | "d" => makeField[Double](cName, cType, FloatType) + case "float64" | "float" | "f" | "float32" => makeField[Double](cName, "double", FloatType) + case _ => throw new RuntimeException(s"Cannot support data type: ${cType}") } - def makeInputPartialEdgeParamType(label: Label): InputObjectType[AddEdgeParam] = { - lazy val InputPropsType = InputObjectType[Map[String, ScalarType[_]]]( - s"Input_${label.label}_edge_props", - description = "desc here", - () => label.labelMetaSet.toList.map { lm => + def makeInputFieldsOnService(service: Service): Seq[InputField[Any]] = { + val inputFields = service.serviceColumns(false).map { serviceColumn => + val idField = InputField("id", s2TypeToScalarType(serviceColumn.columnType)) + val propFields = serviceColumn.metasWithoutCache.filter(ColumnMeta.isValid).map { lm => InputField(lm.name, OptionInputType(s2TypeToScalarType(lm.dataType))) } - ) - lazy val labelFields = List( + val vertexMutateType = InputObjectType[Map[String, Any]]( + s"Input_${service.serviceName}_${serviceColumn.columnName}_vertex_mutate", + description = "desc here", + () => idField :: propFields + ) + + InputField[Any](serviceColumn.columnName, OptionInputType(ListInputType(vertexMutateType))) + } + + inputFields + } + + def makeInputFieldsOnLabel(label: Label): Seq[InputField[Any]] = { + val propFields = label.labelMetaSet.toList.map { lm => + InputField(lm.name, OptionInputType(s2TypeToScalarType(lm.dataType))) + } + + val labelFields = List( InputField("timestamp", OptionInputType(LongType)), InputField("from", s2TypeToScalarType(label.srcColumnType)), InputField("to", s2TypeToScalarType(label.srcColumnType)), InputField("direction", OptionInputType(DirectionType)) ) - InputObjectType[AddEdgeParam]( - s"Input_${label.label}_edge_mutate", - description = "desc here", - () => - if (label.labelMetaSet.isEmpty) labelFields - else labelFields ++ Seq(InputField("props", OptionInputType(InputPropsType))) - ) + labelFields.asInstanceOf[Seq[InputField[Any]]] ++ propFields.asInstanceOf[Seq[InputField[Any]]] + } + + def makeServiceColumnFields(column: ServiceColumn): List[Field[GraphRepository, Any]] = { + val reservedFields = List("id" -> column.columnType, "timestamp" -> "long") + val columnMetasKv = column.metasWithoutCache.filter(ColumnMeta.isValid).map { columnMeta => columnMeta.name -> columnMeta.dataType } + + (reservedFields ++ columnMetasKv).map { case (k, v) => makePropField(k, v) } } def makeServiceField(service: Service, allLabels: List[Label])(implicit repo: GraphRepository): List[Field[GraphRepository, Any]] = { @@ -124,19 +130,18 @@ object S2Type { column.id.get == lb.srcColumn.id.get || column.id.get == lb.tgtColumn.id.get }.distinct - lazy val connectedLabelFields: List[Field[GraphRepository, Any]] = connectedLabels.map(makeLabelField(_, connectedLabelFields)) - val columnMetasKv = column.metasWithoutCache.filter(ColumnMeta.isValid).map { columnMeta => columnMeta.name -> columnMeta.dataType } - val reservedFields = List("id" -> column.columnType, "timestamp" -> "long") + lazy val vertexPropFields = makeServiceColumnFields(column) - val vertexPropFields = makePropFields(reservedFields ++ columnMetasKv) + lazy val connectedLabelFields: List[Field[GraphRepository, Any]] = + connectedLabels.map(makeLabelField(_, connectedLabelFields)) - lazy val ConnectedLabelType = ObjectType( + lazy val connectedLabelType = ObjectType( s"Input_${service.serviceName}_${column.columnName}", () => fields[GraphRepository, Any](vertexPropFields ++ connectedLabelFields: _*) ) Field(column.columnName, - ListType(ConnectedLabelType), + ListType(connectedLabelType), arguments = List( Argument("id", OptionInputType(s2TypeToScalarType(column.columnType))), Argument("ids", OptionInputType(ListInputType(s2TypeToScalarType(column.columnType)))), @@ -145,13 +150,14 @@ object S2Type { description = Option("desc here"), resolve = c => { val ids = c.argOpt[Any]("id").toSeq ++ c.argOpt[List[Any]]("ids").toList.flatten - val vertices = ids.map(vid => c.ctx.toVertex(vid, column)) + val vertices = ids.map(vid => c.ctx.toS2VertexLike(vid, column)) val selectedFields = c.astFields.flatMap { f => f.selections.map(s => s.asInstanceOf[sangria.ast.Field].name) } - if (selectedFields.forall(_ == "id")) vertices else repo.getVertices(vertices) // fill props + if (selectedFields.forall(_ == "id")) scala.concurrent.Future.successful(vertices) + else repo.getVertices(vertices) // fill props } ): Field[GraphRepository, Any] } @@ -159,30 +165,61 @@ object S2Type { columnsOnService } + def fillPartialVertex(vertex: S2VertexLike, + column: ServiceColumn, + c: Context[GraphRepository, Any]): scala.concurrent.Future[S2VertexLike] = { + implicit val ec = c.ctx.ec + + val selectedFields = c.astFields.flatMap { f => + f.selections.map(s => s.asInstanceOf[sangria.ast.Field].name) + } + + lazy val newVertex = c.ctx.toS2VertexLike(vertex.innerId, column) + + if (selectedFields.forall(_ == "id")) scala.concurrent.Future.successful(vertex) + else c.ctx.getVertices(Seq(newVertex)).map(_.head) // fill props + } + def makeLabelField(label: Label, connectedLabelFields: => List[Field[GraphRepository, Any]]): Field[GraphRepository, Any] = { - val labelColumns = List("from" -> label.srcColumnType, "to" -> label.tgtColumnType, "timestamp" -> "long") + val labelColumns = List("direction" -> "string", "timestamp" -> "long") val labelProps = label.labelMetas.map { lm => lm.name -> lm.dataType } + lazy val edgeFields: List[Field[GraphRepository, Any]] = + (labelColumns ++ labelProps).map { case (k, v) => makePropField(k, v) } + + lazy val fromType = ObjectType(s"${label.label}_from", () => + makeServiceColumnFields(label.srcColumn) ++ connectedLabelFields + ) + + lazy val toType = ObjectType(s"${label.label}_to", () => + makeServiceColumnFields(label.tgtColumn) ++ connectedLabelFields + ) + lazy val fromField: Field[GraphRepository, Any] = Field("from", fromType, resolve = c => { + val vertex = c.value.asInstanceOf[S2EdgeLike].srcVertex + fillPartialVertex(vertex, label.srcColumn, c) + }) + + lazy val toField: Field[GraphRepository, Any] = Field("to", toType, resolve = c => { + val vertex = c.value.asInstanceOf[S2EdgeLike].tgtVertex + fillPartialVertex(vertex, label.tgtColumn, c) + }) + lazy val EdgeType = ObjectType( s"Label_${label.label}", - () => fields[GraphRepository, Any](edgeFields ++ connectedLabelFields: _*) + () => fields[GraphRepository, Any](List(fromField, toField) ++ edgeFields: _*) ) - lazy val edgeFields: List[Field[GraphRepository, Any]] = makePropFields(labelColumns ++ labelProps) lazy val edgeTypeField: Field[GraphRepository, Any] = Field( s"${label.label}", ListType(EdgeType), arguments = DirArg :: Nil, - description = Some("edges"), + description = Some("fetch edges"), resolve = { c => val dir = c.argOpt("direction").getOrElse("out") val vertex: S2VertexLike = c.value match { case v: S2VertexLike => v - case e: S2Edge => if (dir == "out") e.tgtVertex else e.srcVertex - // case vp: ServiceParam => - // if (dir == "out") c.ctx.toVertex(label.tgtColumn, vp) - // else c.ctx.toVertex(label.srcColumn, vp) + case _ => throw new IllegalArgumentException(s"ERROR: ${c.value.getClass}") } c.ctx.getEdges(vertex, label, dir) @@ -204,7 +241,7 @@ class S2Type(repo: GraphRepository) { * fields */ lazy val serviceFields: List[Field[GraphRepository, Any]] = repo.allServices.map { service => - lazy val serviceFields = paddingDummyField(makeServiceField(service, repo.allLabels())) + lazy val serviceFields = DummyObjectTypeField :: makeServiceField(service, repo.allLabels()) lazy val ServiceType = ObjectType( s"Service_${service.serviceName}", fields[GraphRepository, Any](serviceFields: _*) @@ -222,40 +259,30 @@ class S2Type(repo: GraphRepository) { * arguments */ lazy val addVertexArg = { - val fields = repo.allServices.map { service => - val inputFields = service.serviceColumns(false).map { serviceColumn => - val idField = InputField("id", s2TypeToScalarType(serviceColumn.columnType)) - val propFields = serviceColumn.metasWithoutCache.filter(ColumnMeta.isValid).map { lm => - InputField(lm.name, OptionInputType(s2TypeToScalarType(lm.dataType))) - } - - val vertexMutateType = InputObjectType[Map[String, Any]]( - s"Input_${service.serviceName}_${serviceColumn.columnName}_vertex_mutate", - description = "desc here", - () => idField :: propFields - ) - - InputField[Any](serviceColumn.columnName, vertexMutateType) - } - - val tpe = InputObjectType[Any]( - s"${service.serviceName}_param", fields = DummyInputField :: inputFields.toList + val serviceArguments = repo.allServices().map { service => + val serviceFields = DummyInputField +: makeInputFieldsOnService(service) + val serviceInputType = InputObjectType[List[AddVertexParam]]( + s"Input_vertex_${service.serviceName}_param", + () => serviceFields.toList ) - - InputField(service.serviceName, OptionInputType(tpe)) + Argument(service.serviceName, OptionInputType(serviceInputType)) } - InputObjectType[Vector[AddVertexParam]]("vertex_input", fields = DummyInputField :: fields) + serviceArguments } - lazy val addEdgeArg = repo.allLabels().map { label => - val inputPartialEdgeParamType = makeInputPartialEdgeParamType(label) - Argument(label.label, OptionInputType(inputPartialEdgeParamType)) - } + lazy val addEdgeArg = { + val labelArguments = repo.allLabels().map { label => + val labelFields = DummyInputField +: makeInputFieldsOnLabel(label) + val labelInputType = InputObjectType[AddEdgeParam]( + s"Input_label_${label.label}_param", + () => labelFields.toList + ) + + Argument(label.label, OptionInputType(ListInputType(labelInputType))) + } - lazy val addEdgesArg = repo.allLabels().map { label => - val inputPartialEdgeParamType = makeInputPartialEdgeParamType(label) - Argument(label.label, OptionInputType(ListInputType(inputPartialEdgeParamType))) + labelArguments } /** @@ -268,16 +295,31 @@ class S2Type(repo: GraphRepository) { lazy val mutationFields: List[Field[GraphRepository, Any]] = List( Field("addVertex", ListType(MutateResponseType), - arguments = Argument("vertex", ListInputType(addVertexArg)) :: Nil, + arguments = addVertexArg, resolve = c => { - val vertices = repo.parseAddVertexParam(c.args) - c.ctx.addVertex(vertices) + val vertices = c.args.raw.keys.flatMap { serviceName => + val addVertexParams = c.arg[List[AddVertexParam]](serviceName) + addVertexParams.map { param => + repo.toS2VertexLike(serviceName, param) + } + } + + c.ctx.addVertices(vertices.toSeq) } ), Field("addEdge", - OptionType(MutateResponseType), + ListType(MutateResponseType), arguments = addEdgeArg, - resolve = c => c.ctx.addEdge(c.args) + resolve = c => { + val edges = c.args.raw.keys.flatMap { labelName => + val addEdgeParams = c.arg[Vector[AddEdgeParam]](labelName) + addEdgeParams.map { param => + repo.toS2EdgeLike(labelName, param) + } + } + + c.ctx.addEdges(edges.toSeq) + } ) ) } http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/a5afdf20/s2graphql/src/main/scala/org/apache/s2graph/graphql/types/package.scala ---------------------------------------------------------------------- diff --git a/s2graphql/src/main/scala/org/apache/s2graph/graphql/types/package.scala b/s2graphql/src/main/scala/org/apache/s2graph/graphql/types/package.scala index ec633f7..a6077a8 100644 --- a/s2graphql/src/main/scala/org/apache/s2graph/graphql/types/package.scala +++ b/s2graphql/src/main/scala/org/apache/s2graph/graphql/types/package.scala @@ -63,7 +63,7 @@ package object types { EnumValue("string", value = "string"), EnumValue("int", value = "int"), EnumValue("long", value = "long"), - EnumValue("float", value = "float"), + EnumValue("double", value = "double"), EnumValue("boolean", value = "boolean") ) ) @@ -149,17 +149,4 @@ package object types { description = Some("dummy field"), resolve = _ => None ) - - def paddingDummyField(fields: List[Field[GraphRepository, Any]]): List[Field[GraphRepository, Any]] = { - if (fields.nonEmpty) fields else List(DummyObjectTypeField) - } - - def trySequence[A >: Throwable](tries: Seq[Try[A]]): Try[Seq[A]] = { - Try { - tries.collect { - case Success(v) => v - case Failure(e) => e - } - } - } } http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/a5afdf20/s2graphql/src/test/scala/org/apache/s2graph/graphql/ScenarioTest.scala ---------------------------------------------------------------------- diff --git a/s2graphql/src/test/scala/org/apache/s2graph/graphql/ScenarioTest.scala b/s2graphql/src/test/scala/org/apache/s2graph/graphql/ScenarioTest.scala index d5cbe3a..c5ea043 100644 --- a/s2graphql/src/test/scala/org/apache/s2graph/graphql/ScenarioTest.scala +++ b/s2graphql/src/test/scala/org/apache/s2graph/graphql/ScenarioTest.scala @@ -26,16 +26,16 @@ class ScenarioTest extends FunSpec with Matchers with BeforeAndAfterAll { val query = graphql""" - mutation { - Management { - createService( - name: "kakao" - compressionAlgorithm: gz - ) { - isSuccess + mutation { + Management { + createService( + name: "kakao" + compressionAlgorithm: gz + ) { + isSuccess + } } } - } """ @@ -51,7 +51,7 @@ class ScenarioTest extends FunSpec with Matchers with BeforeAndAfterAll { } } } - """.stripMargin + """ ) actual shouldBe expected @@ -61,31 +61,31 @@ class ScenarioTest extends FunSpec with Matchers with BeforeAndAfterAll { val query = graphql""" - mutation { - Management { - createServiceColumn ( - serviceName: kakao - columnName: "user" - columnType: string - props: { - name: "age" - dataType: int - defaultValue: "0" - storeInGlobalIndex: true - } - ) { - isSuccess - object { - name - props { + mutation { + Management { + createServiceColumn ( + serviceName: kakao + columnName: "user" + columnType: string + props: { + name: "age" + dataType: int + defaultValue: "0" + storeInGlobalIndex: true + } + ) { + isSuccess + object { name + props { + name + } } } } } - } - """ + """ val actual = testGraph.queryAsJs(query) val expected = Json.parse( @@ -114,26 +114,26 @@ class ScenarioTest extends FunSpec with Matchers with BeforeAndAfterAll { val query = graphql""" - mutation { - Management { - addPropsToServiceColumn( - service: { - kakao: { - columnName: user - props: { - name: "gender" - dataType: string - defaultValue: "" - storeInGlobalIndex: true + mutation { + Management { + addPropsToServiceColumn( + service: { + kakao: { + columnName: user + props: { + name: "gender" + dataType: string + defaultValue: "" + storeInGlobalIndex: true + } } } + ) { + isSuccess } - ) { - isSuccess } } - } - """ + """ val actual = testGraph.queryAsJs(query) val expected = Json.parse( @@ -157,22 +157,22 @@ class ScenarioTest extends FunSpec with Matchers with BeforeAndAfterAll { val query = graphql""" - query { - Management { - Service(name: kakao) { - name - serviceColumns { + query { + Management { + Service(name: kakao) { name - props { + serviceColumns { name - dataType + props { + name + dataType + } } } } } - } - """ + """ val actual = testGraph.queryAsJs(query) val expected = Json.parse( @@ -204,25 +204,25 @@ class ScenarioTest extends FunSpec with Matchers with BeforeAndAfterAll { val query = graphql""" - mutation { - Management { - createLabel( - name: "friends" - sourceService: { - kakao: { - columnName: user + mutation { + Management { + createLabel( + name: "friends" + sourceService: { + kakao: { + columnName: user + } } - } - targetService: { - kakao: { - columnName: user + targetService: { + kakao: { + columnName: user + } } + ) { + isSuccess } - ) { - isSuccess } } - } """ @@ -253,7 +253,7 @@ class ScenarioTest extends FunSpec with Matchers with BeforeAndAfterAll { labelName: friends props: { name: "score" - dataType: float + dataType: double defaultValue: "0" storeInGlobalIndex: true } @@ -285,37 +285,37 @@ class ScenarioTest extends FunSpec with Matchers with BeforeAndAfterAll { val query = graphql""" - query { - Management { - Label(name: friends) { - name - props { + query { + Management { + Label(name: friends) { name - dataType + props { + name + dataType + } } } } - } - """ + """ val actual = testGraph.queryAsJs(query) val expected = Json.parse( """ - { - "data": { - "Management": { - "Label": { - "name": "friends", - "props": [{ - "name": "score", - "dataType": "float" - }] - } - } - } - } - """) + { + "data": { + "Management": { + "Label": { + "name": "friends", + "props": [{ + "name": "score", + "dataType": "double" + }] + } + } + } + } + """) actual shouldBe expected } @@ -328,25 +328,18 @@ class ScenarioTest extends FunSpec with Matchers with BeforeAndAfterAll { mutation { addVertex( - vertex: [{ - kakao: { - user: { - id: "daewon" - age: 20 - gender: "M" - } - } - }, - { - kakao: { - user: { - id: "shon" - age: 19 - gender: "F" - } - } - }] - ) { + kakao: { + user: [{ + id: "daewon" + age: 20 + gender: "M" + }, + { + id: "shon" + age: 19 + gender: "F" + }] + }) { isSuccess } } @@ -387,25 +380,118 @@ class ScenarioTest extends FunSpec with Matchers with BeforeAndAfterAll { val actual = testGraph.queryAsJs(query) val expected = Json.parse( """ + { + "data": { + "kakao": { + "user": [{ + "id": "daewon", + "age": 20, + "gender": "M" + }, { + "id": "shon", + "age": 19, + "gender": "F" + }] + } + } + } + """) + + actual shouldBe expected + } + } + } + + describe("Add edge to label 'friends' and fetch ") { + it("should add edges: daewon -> shon(score: 2) to friends") { + val query = + graphql""" + + mutation { + addEdge( + friends: { + from: "daewon" + to: "shon" + score: 0.9 + } + ) { + isSuccess + } + } + """ + + val actual = testGraph.queryAsJs(query) + + val expected = Json.parse( + """ { "data": { - "kakao": { - "user": [{ - "id": "daewon", - "age": 20, - "gender": "M" - }, { - "id": "shon", - "age": 19, - "gender": "F" - }] - } + "addEdge": [{ + "isSuccess": true + }] } } """) - actual shouldBe expected - } + actual shouldBe expected + } + + it("should fetch edges: friends of kakao.user(daewon) ") { + val query = + graphql""" + + query { + kakao { + user(id: "daewon") { + id + friends { + score + to { + id + age + friends(direction: in) { + to { + id + age + } + direction + } + } + } + } + } + } + """ + + val actual = testGraph.queryAsJs(query) + val expected = Json.parse( + """ + { + "data" : { + "kakao" : { + "user" : [ { + "id" : "daewon", + "friends" : [ { + "score" : 0.9, + "to" : { + "id" : "shon", + "age" : 19, + "friends" : [ { + "to" : { + "id" : "daewon", + "age" : 20 + }, + "direction" : "in" + } ] + } + } ] + } ] + } + } + } + """) + + actual shouldBe expected } } }
