Merge branch 'master' into S2GRAPH-232
* master:
[S2GRAPH-230] ResourceManager onEvict cause segmentation fault with AnnoyModelFetcher
[S2GRAPH-231] Change the GraphQL type name to a valid string.
reTest ci
add background task on ResourceManager onEvict.
diff --git a/s2core/src/main/scala/org/apache/s2graph/core/schema/ColumnMeta.scala b/s2core/src/main/scala/org/apache/s2graph/core/schema/ColumnMeta.scala
index e850541..b1aba3e 100644
--- a/s2core/src/main/scala/org/apache/s2graph/core/schema/ColumnMeta.scala
+++ b/s2core/src/main/scala/org/apache/s2graph/core/schema/ColumnMeta.scala
@@ -136,6 +136,8 @@
val cacheKey = className + s"columnId=${columnId}"
(cacheKey -> ls)
}.toList)
+
+ ls
}
def updateStoreInGlobalIndex(id: Int, storeInGlobalIndex: Boolean)(implicit session: DBSession = AutoSession): Try[Long] = Try {
diff --git a/s2core/src/main/scala/org/apache/s2graph/core/schema/LabelIndex.scala b/s2core/src/main/scala/org/apache/s2graph/core/schema/LabelIndex.scala
index bb8425f..4209ca5 100644
--- a/s2core/src/main/scala/org/apache/s2graph/core/schema/LabelIndex.scala
+++ b/s2core/src/main/scala/org/apache/s2graph/core/schema/LabelIndex.scala
@@ -166,6 +166,8 @@
val cacheKey = s"labelId=${labelId}"
(className + cacheKey -> ls)
}.toList)
+
+ ls
}
}
diff --git a/s2graphql/src/main/scala/org/apache/s2graph/graphql/GraphQLServer.scala b/s2graphql/src/main/scala/org/apache/s2graph/graphql/GraphQLServer.scala
index 5f1c225..3b61085 100644
--- a/s2graphql/src/main/scala/org/apache/s2graph/graphql/GraphQLServer.scala
+++ b/s2graphql/src/main/scala/org/apache/s2graph/graphql/GraphQLServer.scala
@@ -45,7 +45,7 @@
import scala.util.control.NonFatal
import scala.util.{Failure, Success, Try}
-object GraphQLServer {
+class GraphQLServer() {
val className = Schema.getClass.getName
val logger = LoggerFactory.getLogger(this.getClass)
@@ -57,12 +57,14 @@
val config = ConfigFactory.load()
val s2graph = new S2Graph(config)
- val schemaCacheTTL = Try(config.getInt("schemaCacheTTL")).getOrElse(-1)
- val s2Repository = new GraphRepository(s2graph)
+ val schemaCacheTTL = Try(config.getInt("schemaCacheTTL")).getOrElse(3000)
+ val withAdmin = Try(config.getBoolean("schemaCacheTTL")).getOrElse(false)
+
val schemaConfig = ConfigFactory.parseMap(Map(
SafeUpdateCache.MaxSizeKey -> 1, SafeUpdateCache.TtlKey -> schemaCacheTTL
).asJava)
+ // Manage schema instance lifecycle
val schemaCache = new SafeUpdateCache(schemaConfig)
def updateEdgeFetcher(requestJSON: spray.json.JsValue)(implicit e: ExecutionContext): Try[Unit] = {
@@ -77,17 +79,26 @@
ret
}
+ val schemaCacheKey = className + "s2Schema"
+
+ schemaCache.put(schemaCacheKey, createNewSchema(withAdmin))
+
/**
- * In development mode(schemaCacheTTL = -1),
+ * In development mode(schemaCacheTTL = 1),
* a new schema is created for each request.
*/
- logger.info(s"schemaCacheTTL: ${schemaCacheTTL}")
- private def createNewSchema(): Schema[GraphRepository, Any] = {
- val newSchema = new SchemaDef(s2Repository).S2GraphSchema
- logger.info(s"Schema updated: ${System.currentTimeMillis()}")
+ private def createNewSchema(withAdmin: Boolean): (SchemaDef, GraphRepository) = {
+ logger.info(s"Schema update start")
- newSchema
+ val ts = System.currentTimeMillis()
+
+ val s2Repository = new GraphRepository(s2graph)
+ val newSchema = new SchemaDef(s2Repository, withAdmin)
+
+ logger.info(s"Schema updated: ${(System.currentTimeMillis() - ts) / 1000} sec")
+
+ newSchema -> s2Repository
}
def formatError(error: Throwable): JsValue = error match {
@@ -115,15 +126,16 @@
def executeGraphQLQuery(query: Document, op: Option[String], vars: JsObject)(implicit e: ExecutionContext) = {
import GraphRepository._
- val cacheKey = className + "s2Schema"
- val s2schema = schemaCache.withCache(cacheKey, broadcast = false, onEvict = onEvictSchema)(createNewSchema())
+ val (schemaDef, s2Repository) =
+ schemaCache.withCache(schemaCacheKey, broadcast = false, onEvict = onEvictSchema)(createNewSchema(withAdmin))
+
val resolver: DeferredResolver[GraphRepository] = DeferredResolver.fetchers(vertexFetcher, edgeFetcher)
val includeGrpaph = vars.fields.get("includeGraph").contains(spray.json.JsBoolean(true))
val middleWares = if (includeGrpaph) GraphFormatted :: TransformMiddleWare else TransformMiddleWare
Executor.execute(
- s2schema,
+ schemaDef.schema,
query,
s2Repository,
variables = vars,
diff --git a/s2graphql/src/main/scala/org/apache/s2graph/graphql/HttpServer.scala b/s2graphql/src/main/scala/org/apache/s2graph/graphql/HttpServer.scala
index 65ff348..8b89c73 100644
--- a/s2graphql/src/main/scala/org/apache/s2graph/graphql/HttpServer.scala
+++ b/s2graphql/src/main/scala/org/apache/s2graph/graphql/HttpServer.scala
@@ -54,12 +54,14 @@
import spray.json.DefaultJsonProtocol._
+ val graphQLServer = new GraphQLServer()
+
val route: Route =
get {
getFromResource("assets/graphiql.html")
} ~ (post & path("updateEdgeFetcher")) {
entity(as[JsValue]) { body =>
- GraphQLServer.updateEdgeFetcher(body) match {
+ graphQLServer.updateEdgeFetcher(body) match {
case Success(_) => complete(StatusCodes.OK -> JsString("Update fetcher finished"))
case Failure(e) =>
logger.error("Error on execute", e)
@@ -70,11 +72,11 @@
parameters('operationName.?, 'variables.?) { (operationNameParam, variablesParam) =>
entity(as[Document]) { document ⇒
variablesParam.map(parseJson) match {
- case None ⇒ complete(GraphQLServer.executeGraphQLQuery(document, operationNameParam, JsObject()))
- case Some(Right(js)) ⇒ complete(GraphQLServer.executeGraphQLQuery(document, operationNameParam, js.asJsObject))
+ case None ⇒ complete(graphQLServer.executeGraphQLQuery(document, operationNameParam, JsObject()))
+ case Some(Right(js)) ⇒ complete(graphQLServer.executeGraphQLQuery(document, operationNameParam, js.asJsObject))
case Some(Left(e)) ⇒
logger.error("Error on execute", e)
- complete(StatusCodes.BadRequest -> GraphQLServer.formatError(e))
+ complete(StatusCodes.BadRequest -> graphQLServer.formatError(e))
}
} ~ entity(as[JsValue]) { body ⇒
val fields = body.asJsObject.fields
@@ -84,13 +86,13 @@
val variables = fields.get("variables").filterNot(_ == JsNull)
query.map(QueryParser.parse(_)) match {
- case None ⇒ complete(StatusCodes.BadRequest -> GraphQLServer.formatError("No query to execute"))
+ case None ⇒ complete(StatusCodes.BadRequest -> graphQLServer.formatError("No query to execute"))
case Some(Failure(error)) ⇒
logger.error("Error on execute", error)
- complete(StatusCodes.BadRequest -> GraphQLServer.formatError(error))
+ complete(StatusCodes.BadRequest -> graphQLServer.formatError(error))
case Some(Success(document)) => variables match {
- case Some(js) ⇒ complete(GraphQLServer.executeGraphQLQuery(document, operationName, js.asJsObject))
- case None ⇒ complete(GraphQLServer.executeGraphQLQuery(document, operationName, JsObject()))
+ case Some(js) ⇒ complete(graphQLServer.executeGraphQLQuery(document, operationName, js.asJsObject))
+ case None ⇒ complete(graphQLServer.executeGraphQLQuery(document, operationName, JsObject()))
}
}
}
@@ -101,7 +103,9 @@
logger.info(s"Starting GraphQL server... $port")
- Http().bindAndHandle(route, "0.0.0.0", port)
+ Http().bindAndHandle(route, "0.0.0.0", port).foreach { binding =>
+ logger.info(s"GraphQL server ready for connect")
+ }
def shutdown(): Unit = {
logger.info("Terminating...")
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 b5e65dc..d5212c8 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
@@ -79,17 +79,80 @@
tryObj
}
+
+ def services(): List[Service] = {
+ Service.findAll().distinct
+ }
+
+ def serviceColumns(): List[ServiceColumn] = {
+ val allServices = services().toSet
+
+ ServiceColumn
+ .findAll()
+ .filter(sc => allServices(sc.service))
+ .distinct
+ }
+
+ def labels() = {
+ val allServiceColumns = serviceColumns().toSet
+
+ Label
+ .findAll()
+ .filter(l => allServiceColumns(l.srcColumn) || allServiceColumns(l.tgtColumn))
+ .distinct
+ }
+
+ def labelIndices() = {
+ LabelIndex.findAll()
+ }
+
+ def labelMetas() = {
+ LabelMeta.findAll()
+ }
+
+ def columnMetas() = {
+ ColumnMeta.findAll()
+ }
}
class GraphRepository(val graph: S2GraphLike) {
+
implicit val logger = LoggerFactory.getLogger(this.getClass)
import GraphRepository._
+ implicit val ec = graph.ec
+
val management = graph.management
val parser = new RequestParser(graph)
- implicit val ec = graph.ec
+ val services = GraphRepository.services()
+ val serviceColumns = GraphRepository.serviceColumns()
+ val columnMetas = GraphRepository.columnMetas()
+
+ val labels = GraphRepository.labels
+ val labelMetas = GraphRepository.labelMetas()
+ val labelIndices = GraphRepository.labelIndices()
+
+ val serviceColumnMap = services.map { s =>
+ s -> serviceColumns.filter(s.id.get == _.serviceId)
+ }.toMap
+
+ val labelMetaMap = labels.map { l =>
+ l -> labelMetas.filter(l.id.get == _.labelId)
+ }.toMap
+
+ val labelIndiceMap = labels.map { l =>
+ l -> labelIndices.filter(l.id.get == _.labelId)
+ }.toMap
+
+ val columnMetaMap = serviceColumns.map { sc =>
+ sc -> columnMetas.filter(sc.id.get == _.columnId)
+ }.toMap
+
+ val columnLabelMap = serviceColumns.map { sc =>
+ sc -> labels.filter(l => l.srcColumn == sc || l.tgtColumn == sc)
+ }.toMap
def toS2EdgeLike(labelName: String, param: AddEdgeParam): S2EdgeLike = {
graph.toEdge(
@@ -273,14 +336,8 @@
def deleteLabel(args: Args): Try[Label] = {
val labelName = args.arg[String]("name")
-
val deleteLabelTry = Management.deleteLabel(labelName)
+
withLogTryResponse("deleteLabel", deleteLabelTry)
}
-
- def services(): List[Service] = Service.findAll()
-
- def serviceColumns(): List[ServiceColumn] = ServiceColumn.findAll()
-
- def labels() = Label.findAll()
}
diff --git a/s2graphql/src/main/scala/org/apache/s2graph/graphql/types/ManagementType.scala b/s2graphql/src/main/scala/org/apache/s2graph/graphql/types/ManagementType.scala
index 9baec93..deb7664 100644
--- a/s2graphql/src/main/scala/org/apache/s2graph/graphql/types/ManagementType.scala
+++ b/s2graphql/src/main/scala/org/apache/s2graph/graphql/types/ManagementType.scala
@@ -68,7 +68,7 @@
import org.apache.s2graph.graphql.bind.Unmarshaller._
import org.apache.s2graph.graphql.types.StaticTypes._
- lazy val serviceColumnOnServiceWithPropInputObjectFields = repo.services().map { service =>
+ lazy val serviceColumnOnServiceWithPropInputObjectFields = repo.services.map { service =>
InputField(service.serviceName.toValidName, OptionInputType(InputObjectType(
s"Input_${service.serviceName.toValidName}_ServiceColumn_Props",
description = "desc here",
@@ -79,7 +79,7 @@
)))
}
- lazy val serviceColumnOnServiceInputObjectFields = repo.services().map { service =>
+ lazy val serviceColumnOnServiceInputObjectFields = repo.services.map { service =>
InputField(service.serviceName.toValidName, OptionInputType(InputObjectType(
s"Input_${service.serviceName.toValidName}_ServiceColumn",
description = "desc here",
@@ -90,17 +90,20 @@
}
def makeServiceColumnEnumTypeOnService(service: Service): EnumType[String] = {
- val columns = service.serviceColumns(false).toList
+ val columns = repo.serviceColumnMap(service)
+
EnumType(
- s"Enum_${service.serviceName}_ServiceColumn",
+ s"Enum_${service.serviceName.toValidName}_ServiceColumn",
description = Option("desc here"),
- values = dummyEnum +: columns.map { column =>
- EnumValue(column.columnName.toValidName, value = column.columnName.toValidName)
- }
+ values =
+ if (columns.isEmpty) dummyEnum :: Nil
+ else columns.map { column =>
+ EnumValue(column.columnName.toValidName, value = column.columnName)
+ }
)
}
- lazy val labelPropsInputFields = repo.labels().map { label =>
+ lazy val labelPropsInputFields = repo.labels.map { label =>
InputField(label.label.toValidName, OptionInputType(InputObjectType(
s"Input_${label.label.toValidName}_props",
description = "desc here",
@@ -115,7 +118,7 @@
ObjectTypeDescription("desc here"),
RenameField("serviceName", "name"),
AddFields(
- Field("serviceColumns", ListType(ServiceColumnType), resolve = c => c.value.serviceColumns(false).toList)
+ Field("serviceColumns", ListType(ServiceColumnType), resolve = c => c.value.serviceColumns(true).toList)
)
)
@@ -135,28 +138,34 @@
lazy val ServiceListType = EnumType(
s"Enum_Service",
description = Option("desc here"),
- values =
- dummyEnum +: repo.services().map { service =>
+ values = {
+ if (repo.services.isEmpty) dummyEnum :: Nil
+ else repo.services.map { service =>
EnumValue(service.serviceName.toValidName, value = service.serviceName)
}
+ }
)
lazy val ServiceColumnListType = EnumType(
s"Enum_ServiceColumn",
description = Option("desc here"),
- values =
- dummyEnum +: repo.serviceColumns().map { serviceColumn =>
+ values = {
+ if (repo.serviceColumns.isEmpty) dummyEnum :: Nil
+ else repo.serviceColumns.map { serviceColumn =>
EnumValue(serviceColumn.columnName.toValidName, value = serviceColumn.columnName)
}
+ }
)
lazy val EnumLabelsType = EnumType(
s"Enum_Label",
description = Option("desc here"),
- values =
- dummyEnum +: repo.labels().map { label =>
+ values = {
+ if (repo.labels.isEmpty) dummyEnum :: Nil
+ else repo.labels.map { label =>
EnumValue(label.label.toValidName, value = label.label)
}
+ }
)
lazy val ServiceMutationResponseType = makeMutationResponseType[Service](
@@ -184,8 +193,8 @@
arguments = List(LabelNameArg),
resolve = { c =>
c.argOpt[String]("name") match {
- case Some(name) => c.ctx.labels().filter(_.label == name)
- case None => c.ctx.labels()
+ case Some(name) => repo.labels.filter(_.label == name)
+ case None => repo.labels
}
}
)
@@ -201,19 +210,25 @@
val AddPropServiceType = InputObjectType[ServiceColumnParam](
"Input_Service_ServiceColumn_Props",
description = "desc",
- fields = DummyInputField +: serviceColumnOnServiceWithPropInputObjectFields
+ fields =
+ if (serviceColumnOnServiceWithPropInputObjectFields.isEmpty) DummyInputField :: Nil
+ else serviceColumnOnServiceWithPropInputObjectFields
)
val ServiceColumnSelectType = InputObjectType[ServiceColumnParam](
"Input_Service_ServiceColumn",
description = "desc",
- fields = DummyInputField +: serviceColumnOnServiceInputObjectFields
+ fields =
+ if (serviceColumnOnServiceInputObjectFields.isEmpty) DummyInputField :: Nil
+ else serviceColumnOnServiceInputObjectFields
)
val InputServiceType = InputObjectType[ServiceColumnParam](
"Input_Service",
description = "desc",
- fields = DummyInputField +: serviceColumnOnServiceInputObjectFields
+ fields =
+ if (serviceColumnOnServiceInputObjectFields.isEmpty) DummyInputField :: Nil
+ else serviceColumnOnServiceInputObjectFields
)
lazy val servicesField: Field[GraphRepository, Any] = Field(
@@ -223,8 +238,8 @@
arguments = List(ServiceNameArg),
resolve = { c =>
c.argOpt[String]("name") match {
- case Some(name) => c.ctx.services().filter(_.serviceName.toValidName == name)
- case None => c.ctx.services()
+ case Some(name) => repo.services.filter(_.serviceName.toValidName == name)
+ case None => repo.services
}
}
)
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 a18fc4e..3632525 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
@@ -68,10 +68,13 @@
}
}
- def makeInputFieldsOnService(service: Service): Seq[InputField[Any]] = {
- val inputFields = service.serviceColumns(false).map { serviceColumn =>
+ def makeInputFieldsOnService(service: Service)(implicit repo: GraphRepository): Seq[InputField[Any]] = {
+ val serviceColumns = repo.serviceColumnMap(service)
+
+ val inputFields = serviceColumns.map { serviceColumn =>
+ val columnMetas = repo.columnMetaMap(serviceColumn)
val idField = InputField("id", toScalarType(serviceColumn.columnType))
- val propFields = serviceColumn.metasWithoutCache.filter(ColumnMeta.isValid).map { lm =>
+ val propFields = columnMetas.filter(ColumnMeta.isValid).map { lm =>
InputField(lm.name.toValidName, OptionInputType(toScalarType(lm.dataType)))
}
@@ -87,8 +90,10 @@
inputFields
}
- def makeInputFieldsOnLabel(label: Label): Seq[InputField[Any]] = {
- val propFields = label.labelMetaSet.toList.filterNot(_.name == "timestamp").map { lm =>
+ def makeInputFieldsOnLabel(label: Label)(implicit repo: GraphRepository): Seq[InputField[Any]] = {
+ val labelMetaSet = repo.labelMetaMap(label)
+
+ val propFields = labelMetaSet.filterNot(_.name == "timestamp").map { lm =>
InputField(lm.name.toValidName, OptionInputType(toScalarType(lm.dataType)))
}
@@ -102,35 +107,119 @@
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 }
+ def makeServiceColumnFields(column: ServiceColumn)
+ (implicit repo: GraphRepository): List[Field[GraphRepository, Any]] = {
- val (sameLabel, diffLabel) = allLabels.toList.partition(l => l.srcColumn == l.tgtColumn)
+ val columnMetas = repo.columnMetaMap(column)
+ val relatedLabels = repo.columnLabelMap(column)
- val outLabels = diffLabel.filter(l => column == l.srcColumn).distinct.toList
- val inLabels = diffLabel.filter(l => column == l.tgtColumn).distinct.toList
+ val reservedFields = Vector("id" -> column.columnType, "timestamp" -> "long")
+ val columnMetasKv = columnMetas.filter(ColumnMeta.isValid).map { columnMeta => columnMeta.name -> columnMeta.dataType }
+
+ val (sameLabel, diffLabel) = relatedLabels.toList.partition(l => l.srcColumn == l.tgtColumn)
+
+ val outLabels = diffLabel.filter(l => column == l.srcColumn).distinct
+ val inLabels = diffLabel.filter(l => column == l.tgtColumn).distinct
val inOutLabels = sameLabel.filter(l => l.srcColumn == column && l.tgtColumn == column)
- lazy val columnFields = (reservedFields ++ columnMetasKv).map { case (k, v) => makeGraphElementField(k.toValidName, v) }
+ val columnFields = reservedFields.map { case (k, v) => makeGraphElementField(k.toValidName, v) }
+ val propFields = 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)
+ val outLabelFields: List[Field[GraphRepository, Any]] = outLabels.map(l => toLabelFieldOnColumn("out", l))
+ val inLabelFields: List[Field[GraphRepository, Any]] = inLabels.map(l => toLabelFieldOnColumn("in", l))
+ val inOutLabelFields: List[Field[GraphRepository, Any]] = inOutLabels.map(l => toLabelFieldOnColumn("both", l))
- lazy val labelFieldNameSet = (outLabels ++ inLabels ++ inOutLabels).map(_.label).toSet
+ val propsType =
+ if (propFields.isEmpty) Nil
+ else List(wrapField(
+ s"ServiceColumn_${column.service.serviceName.toValidName}_${column.columnName.toValidName}_props", "props", propFields))
- propsType :: inLabelFields ++ outLabelFields ++ inOutLabelFields ++ columnFields.filterNot(cf => labelFieldNameSet(cf.name))
+ lazy val labelFieldNameSet = (outLabels ++ inLabels ++ inOutLabels).map(_.label.toValidName).toSet
+
+ propsType ++ inLabelFields ++ outLabelFields ++ inOutLabelFields ++ columnFields
}
- 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: _*)
- )
+ def toLabelFieldOnColumn(dir: String, label: Label)
+ (implicit repo: GraphRepository): Field[GraphRepository, Any] = {
+
+ val LabelType = makeLabelType(dir, label)
+
+ 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 indices = repo.labelIndiceMap(label)
+
+ val indexEnumType = EnumType(
+ s"Label_Index_${label.label.toValidName}",
+ description = Option("desc here"),
+ values =
+ if (indices.isEmpty) EnumValue("_", value = "_") :: Nil
+ else indices.map(idx => EnumValue(idx.name.toValidName, value = idx.name))
+ )
+
+ 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")
+ )
+
+ val edgeTypeField: Field[GraphRepository, Any] = Field(
+ s"${label.label.toValidName}",
+ ListType(LabelType),
+ 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)(m => m._2))
+ }
+ )
+
+ edgeTypeField
+ }
+
+
+ def makeColumnType(column: ServiceColumn)
+ (implicit repo: GraphRepository): ObjectType[GraphRepository, Any] = {
+
+ val objectName = s"ServiceColumn_${column.service.serviceName.toValidName}_${column.columnName.toValidName}"
+
+ lazy val serviceColumnFields = makeServiceColumnFields(column)
+
+ val ColumnType = ObjectType(
+ objectName,
+ () => fields[GraphRepository, Any](serviceColumnFields: _*)
+ )
+
+ ColumnType
+ }
+
+ def makeServiceType(service: Service)(implicit repo: GraphRepository): ObjectType[GraphRepository, Any] = {
+
+ val _serviceFields = makeServiceFields(service)
+ val serviceFields = if (_serviceFields.isEmpty) DummyObjectTypeField :: _serviceFields else _serviceFields
+
+ ObjectType(
+ s"Service_${service.serviceName.toValidName}",
+ fields[GraphRepository, Any](serviceFields: _*)
+ )
+ }
+
+ def makeServiceFields(service: Service)(implicit repo: GraphRepository): List[Field[GraphRepository, Any]] = {
+
+ val serviceColumns = repo.serviceColumnMap(service)
+ val columnsOnService = serviceColumns.map { column =>
+
+ val ColumnType = makeColumnType(column)
Field(column.columnName.toValidName,
ListType(ColumnType),
@@ -152,110 +241,121 @@
): Field[GraphRepository, Any]
}
- columnsOnService
+ columnsOnService.toList
}
- def makeLabelField(dir: String, label: Label, allLabels: Seq[Label]): Field[GraphRepository, Any] = {
+ def makeLabelType(dir: String, label: Label)
+ (implicit repo: GraphRepository): ObjectType[GraphRepository, Any] = {
+
+ val objectName = s"Label_${label.label.toValidName}_${dir}"
+
+ lazy val labelFields = makeLabelFields(dir, label)
+
+ val LabelType = ObjectType(
+ objectName,
+ () => fields[GraphRepository, Any](labelFields: _*)
+ )
+
+ LabelType
+ }
+
+ def makeLabelFields(dir: String, label: Label)
+ (implicit repo: GraphRepository): List[Field[GraphRepository, Any]] = {
+
+ val relatedMetas = repo.labelMetaMap(label)
val labelReserved = List("direction" -> "string", "timestamp" -> "long")
- val labelProps = label.labelMetas.map { lm => lm.name -> lm.dataType }
+
+ val labelProps = relatedMetas
+ .filterNot(l => labelReserved.exists(kv => kv._1 == l.name))
+ .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) }
+ val labelFields = labelReserved.map { case (k, v) => makeGraphElementField(k.toValidName, v) }
+ val propFields = labelProps.map { case (k, v) => makeGraphElementField(k.toValidName, v) }
- lazy val labelPropField = wrapField(s"Label_${label.label.toValidName}_props", "props", labelFields)
+ val labelPropField =
+ if (propFields.isEmpty) Nil
+ else List(wrapField(s"Label_${label.label.toValidName}_props", "props", propFields))
- lazy val labelColumnType = ObjectType(s"Label_${label.label.toValidName}_${column.columnName.toValidName}",
- () => makeServiceColumnFields(column, allLabels)
- )
+ val labelColumnType = makeColumnType(column)
- 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 =>
+ val serviceColumnField: Field[GraphRepository, Any] =
+ Field(column.columnName.toValidName, labelColumnType, resolve = c => {
implicit val ec = c.ctx.ec
+ val vertexQueryParam = FieldResolver.serviceColumnOnLabel(c)
- val edgeQueryParam = graphql.types.FieldResolver.label(label, c)
- val empty = Seq.empty[S2EdgeLike]
+ DeferredValue(GraphRepository.vertexFetcher.defer(vertexQueryParam)).map(m => m._2.head)
+ })
- DeferredValue(GraphRepository.edgeFetcher.deferOpt(edgeQueryParam)).map(m => m.fold(empty)(_._2))
- }
- )
+ List(serviceColumnField) ++ labelPropField ++ labelFields.filterNot(_.name.toValidName == column.columnName.toValidName)
+ }
- edgeTypeField
+ def services(): List[Service] = {
+ Service.findAll().distinct
+ }
+
+ def serviceColumns(): List[ServiceColumn] = {
+ val allServices = services().toSet
+
+ ServiceColumn
+ .findAll()
+ .filter(sc => allServices(sc.service))
+ .distinct
+ }
+
+ def labels() = {
+ val allServiceColumns = serviceColumns().toSet
+
+ Label
+ .findAll()
+ .filter(l => allServiceColumns(l.srcColumn) || allServiceColumns(l.tgtColumn))
+ .distinct
+ }
+
+ def labelIndices() = {
+ LabelIndex.findAll()
+ }
+
+ def labelMetas() = {
+ LabelMeta.findAll()
+ }
+
+ def columnMetas() = {
+ ColumnMeta.findAll()
}
}
-class S2Type(repo: GraphRepository) {
+class S2Type(_repo: GraphRepository) {
+ implicit val repo = _repo
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())
+ val serviceFields: List[Field[GraphRepository, Any]] = {
- lazy val ServiceType = ObjectType(
- s"Service_${service.serviceName.toValidName}",
- fields[GraphRepository, Any](serviceFields: _*)
- )
+ repo.services.flatMap { service =>
+ val ServiceType = makeServiceType(service)
- Field(
- service.serviceName.toValidName,
- ServiceType,
- description = Some(s"serviceName: ${service.serviceName}"),
- resolve = _ => service
- ): Field[GraphRepository, Any]
+ val f = Field(
+ service.serviceName.toValidName,
+ ServiceType,
+ description = Some(s"serviceName: ${service.serviceName}"),
+ resolve = _ => service
+ ): Field[GraphRepository, Any]
+
+ List(f)
+ }
}
/**
* arguments
*/
lazy val addVertexArg = {
- val serviceArguments = repo.services().map { service =>
+ val serviceArguments = repo.services.map { service =>
val serviceFields = DummyInputField +: makeInputFieldsOnService(service)
val ServiceInputType = InputObjectType[List[AddVertexParam]](
@@ -269,7 +369,7 @@
}
lazy val addEdgeArg = {
- val labelArguments = repo.labels().map { label =>
+ val labelArguments = repo.labels.map { label =>
val labelFields = DummyInputField +: makeInputFieldsOnLabel(label)
val labelInputType = InputObjectType[AddEdgeParam](
s"Input_label_${label.label.toValidName}_param",
@@ -287,7 +387,7 @@
* Provide s2graph query / mutate API
* - Fields is created(or changed) for metadata is changed.
*/
- lazy val queryFields = serviceFields
+ val queryFields = serviceFields
lazy val mutationFields: List[Field[GraphRepository, Any]] = List(
Field("addVertex",
diff --git a/s2graphql/src/main/scala/org/apache/s2graph/graphql/types/SchemaDef.scala b/s2graphql/src/main/scala/org/apache/s2graph/graphql/types/SchemaDef.scala
index 7451023..835cd0b 100644
--- a/s2graphql/src/main/scala/org/apache/s2graph/graphql/types/SchemaDef.scala
+++ b/s2graphql/src/main/scala/org/apache/s2graph/graphql/types/SchemaDef.scala
@@ -20,13 +20,16 @@
package org.apache.s2graph.graphql.types
import org.apache.s2graph.graphql.repository.GraphRepository
+import sangria.schema.ObjectType
+
+import scala.collection.mutable
/**
* S2Graph GraphQL schema.
*
* When a Label or Service is created, the GraphQL schema is created dynamically.
*/
-class SchemaDef(g: GraphRepository) {
+class SchemaDef(g: GraphRepository, withAdmin: Boolean = false) {
import sangria.schema._
@@ -39,19 +42,26 @@
fields(s2Type.queryFields ++ queryManagementFields: _*)
)
- val mutateManagementFields = List(wrapField("MutationManagement", "Management", s2ManagementType.mutationFields))
- val S2MutationType = ObjectType[GraphRepository, Any](
- "Mutation",
- fields(s2Type.mutationFields ++ mutateManagementFields: _*)
- )
+ lazy val mutateManagementFields = List(wrapField("MutationManagement", "Management", s2ManagementType.mutationFields))
+
+ val S2MutationType =
+ if (!withAdmin) None
+ else {
+ val mutationTpe = ObjectType[GraphRepository, Any](
+ "Mutation",
+ fields(s2Type.mutationFields ++ mutateManagementFields: _*)
+ )
+
+ Option(mutationTpe)
+ }
val directives = S2Directive.Transform :: BuiltinDirectives
private val s2Schema = Schema(
S2QueryType,
- Option(S2MutationType),
+ S2MutationType,
directives = directives
)
- val S2GraphSchema = s2Schema
+ val schema = s2Schema
}