blob: a18fc4e396b0778792def1dd97590a3b32b6a2c2 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.s2graph.graphql.types
import org.apache.s2graph.core.Management.JsonModel._
import org.apache.s2graph.core._
import org.apache.s2graph.core.schema._
import org.apache.s2graph.graphql
import org.apache.s2graph.graphql.repository.GraphRepository
import sangria.schema._
import org.apache.s2graph.graphql.types.StaticTypes._
import scala.language.existentials
object S2Type {
case class EdgeQueryParam(v: S2VertexLike, qp: QueryParam)
case class AddVertexParam(timestamp: Long,
id: Any,
columnName: String,
props: Map[String, Any])
case class AddEdgeParam(ts: Long,
from: Any,
to: Any,
direction: String,
props: Map[String, Any])
// Management params
case class ServiceColumnParam(serviceName: String,
columnName: String,
props: Seq[Prop] = Nil)
def makeGraphElementField(cName: String, cType: String): 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 = c => FieldResolver.graphElement[A](name, cType, c)
)
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 makeInputFieldsOnService(service: Service): Seq[InputField[Any]] = {
val inputFields = service.serviceColumns(false).map { serviceColumn =>
val idField = InputField("id", toScalarType(serviceColumn.columnType))
val propFields = serviceColumn.metasWithoutCache.filter(ColumnMeta.isValid).map { lm =>
InputField(lm.name.toValidName, OptionInputType(toScalarType(lm.dataType)))
}
val vertexMutateType = InputObjectType[Map[String, Any]](
s"Input_${service.serviceName.toValidName}_${serviceColumn.columnName.toValidName}_vertex_mutate",
description = "desc here",
() => idField :: propFields
)
InputField[Any](serviceColumn.columnName.toValidName, OptionInputType(ListInputType(vertexMutateType)))
}
inputFields
}
def makeInputFieldsOnLabel(label: Label): Seq[InputField[Any]] = {
val propFields = label.labelMetaSet.toList.filterNot(_.name == "timestamp").map { lm =>
InputField(lm.name.toValidName, OptionInputType(toScalarType(lm.dataType)))
}
val labelFields = List(
InputField("timestamp", OptionInputType(LongType)),
InputField("from", toScalarType(label.srcColumnType)),
InputField("to", toScalarType(label.srcColumnType)),
InputField("direction", OptionInputType(BothDirectionType))
)
labelFields.asInstanceOf[Seq[InputField[Any]]] ++ propFields.asInstanceOf[Seq[InputField[Any]]]
}
def makeServiceColumnFields(column: ServiceColumn, allLabels: Seq[Label]): 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 }
val (sameLabel, diffLabel) = allLabels.toList.partition(l => l.srcColumn == l.tgtColumn)
val outLabels = diffLabel.filter(l => column == l.srcColumn).distinct.toList
val inLabels = diffLabel.filter(l => column == l.tgtColumn).distinct.toList
val inOutLabels = sameLabel.filter(l => l.srcColumn == column && l.tgtColumn == column)
lazy val columnFields = (reservedFields ++ columnMetasKv).map { case (k, v) => makeGraphElementField(k.toValidName, v) }
lazy val outLabelFields: List[Field[GraphRepository, Any]] = outLabels.map(l => makeLabelField("out", l, allLabels))
lazy val inLabelFields: List[Field[GraphRepository, Any]] = inLabels.map(l => makeLabelField("in", l, allLabels))
lazy val inOutLabelFields: List[Field[GraphRepository, Any]] = inOutLabels.map(l => makeLabelField("both", l, allLabels))
lazy val propsType = wrapField(s"ServiceColumn_${column.service.serviceName}_${column.columnName}_props", "props", columnFields)
lazy val labelFieldNameSet = (outLabels ++ inLabels ++ inOutLabels).map(_.label).toSet
propsType :: inLabelFields ++ outLabelFields ++ inOutLabelFields ++ columnFields.filterNot(cf => labelFieldNameSet(cf.name))
}
def makeServiceField(service: Service, allLabels: List[Label])(implicit repo: GraphRepository): List[Field[GraphRepository, Any]] = {
val columnsOnService = service.serviceColumns(false).toList.map { column =>
lazy val serviceColumnFields = makeServiceColumnFields(column, allLabels)
lazy val ColumnType = ObjectType(
s"ServiceColumn_${service.serviceName}_${column.columnName}",
() => fields[GraphRepository, Any](serviceColumnFields: _*)
)
Field(column.columnName.toValidName,
ListType(ColumnType),
arguments = List(
Argument("id", OptionInputType(toScalarType(column.columnType))),
Argument("ids", OptionInputType(ListInputType(toScalarType(column.columnType)))),
Argument("search", OptionInputType(StringType)),
Argument("offset", OptionInputType(IntType), defaultValue = 0),
Argument("limit", OptionInputType(IntType), defaultValue = 100),
Argument("filter", OptionInputType(StringType), "desc here")
),
description = Option("desc here"),
resolve = c => {
implicit val ec = c.ctx.ec
val vertexQueryParam = FieldResolver.serviceColumnOnService(column, c)
DeferredValue(GraphRepository.vertexFetcher.defer(vertexQueryParam)).map(m => m._2)
}
): Field[GraphRepository, Any]
}
columnsOnService
}
def makeLabelField(dir: String, label: Label, allLabels: Seq[Label]): Field[GraphRepository, Any] = {
val labelReserved = List("direction" -> "string", "timestamp" -> "long")
val labelProps = label.labelMetas.map { lm => lm.name -> lm.dataType }
val column = if (dir == "out") label.tgtColumn else label.srcColumn
lazy val labelFields: List[Field[GraphRepository, Any]] =
(labelReserved ++ labelProps).map { case (k, v) => makeGraphElementField(k.toValidName, v) }
lazy val labelPropField = wrapField(s"Label_${label.label.toValidName}_props", "props", labelFields)
lazy val labelColumnType = ObjectType(s"Label_${label.label.toValidName}_${column.columnName.toValidName}",
() => makeServiceColumnFields(column, allLabels)
)
lazy val serviceColumnField: Field[GraphRepository, Any] = Field(column.columnName, labelColumnType, resolve = c => {
implicit val ec = c.ctx.ec
val vertexQueryParam = FieldResolver.serviceColumnOnLabel(c)
DeferredValue(GraphRepository.vertexFetcher.defer(vertexQueryParam)).map(m => m._2.head)
})
lazy val EdgeType = ObjectType(
s"Label_${label.label.toValidName}_${column.columnName.toValidName}_${dir}",
() => fields[GraphRepository, Any](
List(serviceColumnField, labelPropField) ++ labelFields.filterNot(_.name == column.columnName): _*)
)
val dirArgs = dir match {
case "in" => Argument("direction", OptionInputType(InDirectionType), "desc here", defaultValue = "in") :: Nil
case "out" => Argument("direction", OptionInputType(OutDirectionType), "desc here", defaultValue = "out") :: Nil
case "both" => Argument("direction", OptionInputType(BothDirectionType), "desc here", defaultValue = "out") :: Nil
}
val idxNames = label.indices.map { idx =>
EnumValue(idx.name.toValidName, value = idx.name.toValidName)
}
val indexEnumType = EnumType(
s"Label_Index_${label.label.toValidName}",
description = Option("desc here"),
values = idxNames
)
val paramArgs = List(
Argument("offset", OptionInputType(IntType), "desc here", defaultValue = 0),
Argument("limit", OptionInputType(IntType), "desc here", defaultValue = 100),
Argument("index", OptionInputType(indexEnumType), "desc here"),
Argument("filter", OptionInputType(StringType), "desc here")
)
lazy val edgeTypeField: Field[GraphRepository, Any] = Field(
s"${label.label.toValidName}",
ListType(EdgeType),
arguments = dirArgs ++ paramArgs,
description = Some("fetch edges"),
resolve = { c =>
implicit val ec = c.ctx.ec
val edgeQueryParam = graphql.types.FieldResolver.label(label, c)
val empty = Seq.empty[S2EdgeLike]
DeferredValue(GraphRepository.edgeFetcher.deferOpt(edgeQueryParam)).map(m => m.fold(empty)(_._2))
}
)
edgeTypeField
}
}
class S2Type(repo: GraphRepository) {
import S2Type._
import org.apache.s2graph.graphql.bind.Unmarshaller._
implicit val graphRepository = repo
/**
* fields
*/
lazy val serviceFields: List[Field[GraphRepository, Any]] = repo.services().map { service =>
lazy val serviceFields = DummyObjectTypeField :: makeServiceField(service, repo.labels())
lazy val ServiceType = ObjectType(
s"Service_${service.serviceName.toValidName}",
fields[GraphRepository, Any](serviceFields: _*)
)
Field(
service.serviceName.toValidName,
ServiceType,
description = Some(s"serviceName: ${service.serviceName}"),
resolve = _ => service
): Field[GraphRepository, Any]
}
/**
* arguments
*/
lazy val addVertexArg = {
val serviceArguments = repo.services().map { service =>
val serviceFields = DummyInputField +: makeInputFieldsOnService(service)
val ServiceInputType = InputObjectType[List[AddVertexParam]](
s"Input_vertex_${service.serviceName.toValidName}_param",
() => serviceFields.toList
)
Argument(service.serviceName.toValidName, OptionInputType(ServiceInputType))
}
serviceArguments
}
lazy val addEdgeArg = {
val labelArguments = repo.labels().map { label =>
val labelFields = DummyInputField +: makeInputFieldsOnLabel(label)
val labelInputType = InputObjectType[AddEdgeParam](
s"Input_label_${label.label.toValidName}_param",
() => labelFields.toList
)
Argument(label.label.toValidName, OptionInputType(ListInputType(labelInputType)))
}
labelArguments
}
/**
* Query fields
* Provide s2graph query / mutate API
* - Fields is created(or changed) for metadata is changed.
*/
lazy val queryFields = serviceFields
lazy val mutationFields: List[Field[GraphRepository, Any]] = List(
Field("addVertex",
ListType(MutateResponseType),
arguments = addVertexArg,
resolve = c => {
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",
ListType(MutateResponseType),
arguments = addEdgeArg,
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)
}
)
)
}