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
 }