Repository: incubator-s2graph
Updated Branches:
  refs/heads/master 8f9214e82 -> 6a7e58a46


http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/1f1dee3d/s2graphql/README.md
----------------------------------------------------------------------
diff --git a/s2graphql/README.md b/s2graphql/README.md
new file mode 100644
index 0000000..4146e8e
--- /dev/null
+++ b/s2graphql/README.md
@@ -0,0 +1,530 @@
+# Suggest to implement GraphQL as standard web interface for S2Graph.
+
+  - To support GraphQL i used [Akka HTTP](https://github.com/akka/akka-http) 
and [Sangria](https://github.com/sangria-graphql). each is an HTTP Server and 
GraphQL Scala implementation.
+  - I also used [GraphiQL](https://github.com/graphql/graphiql) as a tool for 
GraphQL queries.
+
+## Wroking example
+
+![mutation](https://user-images.githubusercontent.com/1182522/35611013-f551f2b6-06a6-11e8-8f48-e39e667a8849.gif)
+
+![query](https://user-images.githubusercontent.com/1182522/35611725-599e1e5a-06a9-11e8-9a52-9e5fd3542c2e.gif)
+
+
+## Overview
+  
+  The reason I started this work is because the `Label` used by S2Graph has a 
strong type system, so I think it will work well with the `schema` provided by 
GraphQL.
+  
+  To do this, we converted S2Graph Model (Label, Service ...) into GraphLQL 
schema whenever added (changed).
+
+## Setup
+  Assume that hbase is running on localhost.  
+  If the hbase environment is not set up, you can run it with the following 
command
+
+```bash
+sbt package
+target/apache-s2graph-0.2.1-SNAPSHOT-incubating-bin/bin/hbase-standalone.sh 
start 
+```
+  
+If hbase is running well, run the following command after cloning the project 
locally.
+
+`GraphiQL` is not directly related to the `GraphQL` implementation, but is 
recommended for convenient queries.
+Because of the license problem, you should download the file through the 
following command.
+
+```bash
+cd s2graphql/src/main/resources
+wget 
https://raw.githubusercontent.com/sangria-graphql/sangria-akka-http-example/master/src/main/resources/graphiql.html
+```
+
+You can see that the `graphiql.html` file is added to the 
`s2graphql/src/main/resources` folder as shown below.
+
+```
+$ls
+application.conf  graphiql.html log4j.properties
+```
+
+And let's run http server.
+
+```bash
+sbt -DschemaCacheTTL=-1 -Dhttp.port=8000 'project s2graphql' '~re-start'
+```
+
+When the server is running, connect to `http://localhost:8000`. If it works 
normally, you can see the following screen.
+
+![2018-01-31 4 39 
25](https://user-images.githubusercontent.com/1182522/35610627-5ddd1cd6-06a5-11e8-8f02-446b28df54cb.png)
+
+## API List
+  - createService
+  - createLabel
+  - addEdges
+  - addEdge
+  - query (You can recursively browse the linked labels from the service and 
any other labels that are linked from that label)
+
+## Your First Grpah (GraphQL version)
+
+[S2Graph 
tutorial](https://github.com/apache/incubator-s2graph#your-first-graph)
+I have ported the contents of `Your first graph` provided by S2Graph based on 
GraphQL.
+
+### Start by connecting to `http://localhost:8000`.
+
+The environment for this example is Mac OS and Chrome.
+You can get help with schema-based `Autocompletion` using the `ctrl + space` 
key.
+
+If you add a `label` or `service`, you will need to `refresh` (`cmd + r`) your 
browser because the schema will change dynamically.
+
+1. First, we need a name for the new service.
+
+    The following POST query will create a service named "KakaoFavorites".
+
+Request 
+```graphql
+mutation {
+  createService(
+    name: "KakaoFavorites",
+    compressionAlgorithm: gz
+  ) {
+    isSuccess
+    message
+    created {
+      id
+    }
+  } 
+}
+```
+
+Response
+```json
+{
+  "data": {
+    "createService": {
+      "isSuccess": true,
+      "message": "Created successful",
+      "created": {
+        "id": 1
+      }
+    }
+  }
+}
+```
+
+To make sure the service is created correctly, check out the following.
+
+  > Since the schema has changed, GraphiQL must recognize the changed schema. 
To do this, refresh the browser several times.
+
+Request
+```graphql
+query {
+  Services(name: KakaoFavorites) {
+    id
+    name     
+  }
+}
+```
+
+Response
+```json
+{
+  "data": {
+    "Services": [
+      {
+        "id": 1,
+        "name": "KakaoFavorites"
+      }
+    ]
+  }
+}
+```
+
+2. Next, we will need some friends.
+
+    In S2Graph, relationships are organized as labels. Create a label called 
friends using the following createLabel API call:
+
+Request 
+
+```graphql
+mutation {
+  createLabel(
+    name: "friends",
+    sourceService: {
+      name: KakaoFavorites,
+      columnName: "userName",
+      dataType: string
+    },
+    targetService: {
+      name: KakaoFavorites,
+      columnName: "userName",
+      dataType: string
+    }
+    consistencyLevel: strong
+  ){
+    isSuccess
+    message
+    created {
+      id
+      name      
+    }
+  }
+}
+```
+
+Response 
+```json
+{
+  "data": {
+    "createLabel": {
+      "isSuccess": true,
+      "message": "Created successful",
+      "created": {
+        "id": 1,
+        "name": "friends"
+      }
+    }
+  }
+}
+```
+
+Check if the label has been created correctly
+> Since the schema has changed, GraphiQL must recognize the changed schema. To 
do this, refresh the browser several times.
+
+Request
+```graphql
+query { 
+  Labels(name: friends) {
+    id
+    name
+    srcColumnName
+    tgtColumnName    
+  }
+}
+```
+
+Response
+```json
+{
+  "data": {
+    "Labels": [
+      {
+        "id": 1,
+        "name": "friends",
+        "srcColumnName": "userName",
+        "tgtColumnName": "userName"
+      }
+    ]
+  }
+}
+```
+
+Now that the label friends is ready, we can store the friendship data. 
+Entries of a label are called edges, and you can add edges with edges/insert 
API:
+
+> Since the schema has changed, GraphiQL must recognize the changed schema. To 
do this, refresh the browser several times.
+
+Request
+```graphql
+mutation {
+  addEdges(
+    friends: [
+      {from: "Elmo", to: "Big Bird"},
+      {from: "Elmo", to: "Ernie"},    
+      {from: "Elmo", to: "Bert"},    
+      {from: "Cookie Monster", to: "Grover"},    
+      {from: "Cookie Monster", to: "Kermit"},    
+      {from: "Cookie Monster", to: "Oscar"},    
+    ]
+  ) {
+    isSuccess    
+  }
+}
+```
+
+Response
+```json
+{
+  "data": {
+    "addEdges": [
+      {
+        "isSuccess": true
+      },
+      {
+        "isSuccess": true
+      },
+      {
+        "isSuccess": true
+      },
+      {
+        "isSuccess": true
+      },
+      {
+        "isSuccess": true
+      },
+      {
+        "isSuccess": true
+      }
+    ]
+  }
+}
+```
+
+Query friends of Elmo with getEdges API:
+
+Request
+
+```graphql
+query {
+  KakaoFavorites(id: "Elmo") {    
+    friends {
+      to
+    }
+  }
+}
+```
+
+Response
+
+```json
+{
+  "data": {
+    "KakaoFavorites": [
+      {
+        "friends": [
+          {
+            "to": "Bert"
+          },
+          {
+            "to": "Ernie"
+          },
+          {
+            "to": "Big Bird"
+          }
+        ]
+      }
+    ]
+  }
+}
+```
+
+Now query friends of Cookie Monster:
+
+```graphql
+query {
+  KakaoFavorites(id: "Cookie Monster") {
+    friends {
+      to
+    }
+  }
+}
+```
+
+3. Users of Kakao Favorites will be able to post URLs of their favorite 
websites.
+
+Request
+
+```graphql
+mutation {
+  createLabel(
+    name: "post",
+    sourceService: {
+      name: KakaoFavorites,
+      columnName: "userName",
+      dataType: string
+    },
+    targetService: {
+      name: KakaoFavorites,
+      columnName: "url",
+      dataType: string,      
+    }
+    consistencyLevel: strong
+  ) {
+    isSuccess
+    message
+    created {
+      id
+      name      
+    }
+  }
+}
+```
+
+Response
+
+```json
+{
+  "data": {
+    "createLabel": {
+      "isSuccess": true,
+      "message": "Created successful",
+      "created": {
+        "id": 2,
+        "name": "post"
+      }
+    }
+  }
+}
+```
+
+Now, insert some posts of the users:
+
+> Since the schema has changed, GraphiQL must recognize the changed schema. To 
do this, refresh the browser several times.
+
+
+Request
+
+```graphql
+mutation {
+  addEdges(
+    post: [
+      { from: "Big Bird", to: "www.kakaocorp.com/en/main" },
+      { from: "Big Bird", to: "github.com/kakao/s2graph" },
+      { from: "Ernie", to: "groups.google.com/forum/#!forum/s2graph" },
+      { from: "Grover", to: "hbase.apache.org/forum/#!forum/s2graph" },
+      { from: "Kermit", to: "www.playframework.com"},
+      { from: "Oscar", to: "www.scala-lang.org"}
+    ]
+  ) {
+    isSuccess
+  }
+}
+```
+
+Response
+```json
+{
+  "data": {
+    "addEdges": [
+      {
+        "isSuccess": true
+      },
+      {
+        "isSuccess": true
+      },
+      {
+        "isSuccess": true
+      },
+      {
+        "isSuccess": true
+      },
+      {
+        "isSuccess": true
+      },
+      {
+        "isSuccess": true
+      }
+    ]
+  }
+}
+```
+
+4. So far, we have designed a label schema for the labels friends and post, 
and stored some edges to them.+
+
+    This should be enough for creating the timeline feature! The following 
two-step query will return the URLs for Elmo's timeline, which are the posts of 
Elmo's friends:
+
+Request
+
+```graphql
+query {
+  KakaoFavorites(id: "Elmo") {
+    friends {
+      post {
+        from
+        to
+      }
+    }
+  }
+}
+```
+
+Response
+```json
+{
+  "data": {
+    "KakaoFavorites": [
+      {
+        "friends": [
+          {
+            "post": []
+          },
+          {
+            "post": [
+              {
+                "from": "Ernie",
+                "to": "groups.google.com/forum/#!forum/s2graph"
+              }
+            ]
+          },
+          {
+            "post": [
+              {
+                "from": "Big Bird",
+                "to": "www.kakaocorp.com/en/main"
+              },
+              {
+                "from": "Big Bird",
+                "to": "github.com/kakao/s2graph"
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+}
+```
+
+Also try Cookie Monster's timeline:
+
+Request
+```graphql
+query {
+  KakaoFavorites(id: "Cookie Monster") {
+    friends {
+      post {
+        from
+        to
+      }
+    }
+  }
+}
+```
+
+Response
+```json
+{
+  "data": {
+    "KakaoFavorites": [
+      {
+        "friends": [
+          {
+            "post": [
+              {
+                "from": "Oscar",
+                "to": "www.scala-lang.org"
+              }
+            ]
+          },
+          {
+            "post": [
+              {
+                "from": "Kermit",
+                "to": "www.playframework.com"
+              }
+            ]
+          },
+          {
+            "post": [
+              {
+                "from": "Grover",
+                "to": "hbase.apache.org/forum/#!forum/s2graph"
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+}
+```
+
+
+![2018-01-31 5 18 
46](https://user-images.githubusercontent.com/1182522/35612101-db97e160-06aa-11e8-9286-0dd1ffa15c82.png)
+
+The example above is by no means a full blown social network timeline, but it 
gives you an idea of how to represent, store and query graph data with S2Graph.
+

http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/1f1dee3d/s2graphql/build.sbt
----------------------------------------------------------------------
diff --git a/s2graphql/build.sbt b/s2graphql/build.sbt
new file mode 100644
index 0000000..d344892
--- /dev/null
+++ b/s2graphql/build.sbt
@@ -0,0 +1,21 @@
+name := "s2graphql"
+
+version := "0.1"
+
+description := "GraphQL server with akka-http and sangria and s2graph"
+
+scalacOptions ++= Seq("-deprecation", "-feature")
+
+libraryDependencies ++= Seq(
+  "org.sangria-graphql" %% "sangria" % "1.3.3",
+  "org.sangria-graphql" %% "sangria-spray-json" % "1.0.0",
+
+  "com.typesafe.akka" %% "akka-http" % "10.0.10",
+  "com.typesafe.akka" %% "akka-http-spray-json" % "10.0.10",
+
+  "com.typesafe.akka" %% "akka-slf4j" % "2.4.6",
+
+  "org.scalatest" %% "scalatest" % "3.0.4" % Test
+)
+
+Revolver.settings

http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/1f1dee3d/s2graphql/project/build.properties
----------------------------------------------------------------------
diff --git a/s2graphql/project/build.properties 
b/s2graphql/project/build.properties
new file mode 100644
index 0000000..af73dc4
--- /dev/null
+++ b/s2graphql/project/build.properties
@@ -0,0 +1,18 @@
+# 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.
+
+sbt.version = 0.13.15

http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/1f1dee3d/s2graphql/project/plugins.sbt
----------------------------------------------------------------------
diff --git a/s2graphql/project/plugins.sbt b/s2graphql/project/plugins.sbt
new file mode 100644
index 0000000..4f78e51
--- /dev/null
+++ b/s2graphql/project/plugins.sbt
@@ -0,0 +1 @@
+addSbtPlugin("io.spray" % "sbt-revolver" % "0.9.1")
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/1f1dee3d/s2graphql/src/main/resources/application.conf
----------------------------------------------------------------------
diff --git a/s2graphql/src/main/resources/application.conf 
b/s2graphql/src/main/resources/application.conf
new file mode 100644
index 0000000..71e8a99
--- /dev/null
+++ b/s2graphql/src/main/resources/application.conf
@@ -0,0 +1,7 @@
+akka {
+  loggers = ["akka.event.slf4j.Slf4jLogger"]
+  event-handlers = ["akka.event.slf4j.Slf4jEventHandler"]
+  loglevel = "INFO"
+}
+
+

http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/1f1dee3d/s2graphql/src/main/resources/log4j.properties
----------------------------------------------------------------------
diff --git a/s2graphql/src/main/resources/log4j.properties 
b/s2graphql/src/main/resources/log4j.properties
new file mode 100644
index 0000000..2070d82
--- /dev/null
+++ b/s2graphql/src/main/resources/log4j.properties
@@ -0,0 +1,26 @@
+# 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.
+
+# Set root logger level to DEBUG and its only appender to A1.
+log4j.rootLogger=INFO, A1
+
+# A1 is set to be a ConsoleAppender.
+log4j.appender.A1=org.apache.log4j.ConsoleAppender
+
+# A1 uses PatternLayout.
+log4j.appender.A1.layout=org.apache.log4j.PatternLayout
+log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n

http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/1f1dee3d/s2graphql/src/main/scala/GraphQLServer.scala
----------------------------------------------------------------------
diff --git a/s2graphql/src/main/scala/GraphQLServer.scala 
b/s2graphql/src/main/scala/GraphQLServer.scala
new file mode 100644
index 0000000..c2e9ca1
--- /dev/null
+++ b/s2graphql/src/main/scala/GraphQLServer.scala
@@ -0,0 +1,110 @@
+/*
+ * 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
+
+import java.util.concurrent.Executors
+
+import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
+import akka.http.scaladsl.model.StatusCodes._
+import akka.http.scaladsl.server.Directives._
+import akka.http.scaladsl.server._
+import com.typesafe.config.ConfigFactory
+import org.apache.s2graph.core.S2Graph
+import org.apache.s2graph.core.utils.SafeUpdateCache
+import sangria.ast.Document
+import sangria.execution._
+import sangria.marshalling.sprayJson._
+import sangria.parser.QueryParser
+import sangria.renderer.SchemaRenderer
+import sangria.schema.Schema
+import spray.json.{JsObject, JsString, JsValue}
+
+import scala.concurrent.ExecutionContext
+import scala.util.{Failure, Success, Try}
+
+object GraphQLServer {
+
+  // Init s2graph
+  val numOfThread = Runtime.getRuntime.availableProcessors()
+  val threadPool = Executors.newFixedThreadPool(numOfThread * 2)
+
+  implicit val ec = ExecutionContext.fromExecutor(threadPool)
+
+  val config = ConfigFactory.load()
+  val s2graph = new S2Graph(config)
+  val schemaCacheTTL = Try(config.getInt("schemaCacheTTL")).getOrElse(-1)
+  val s2Repository = new GraphRepository(s2graph)
+  val schemaCache = new SafeUpdateCache[Schema[GraphRepository, 
Any]]("schema", maxSize = 1, ttl = schemaCacheTTL)
+
+  def endpoint(requestJSON: spray.json.JsValue)(implicit e: ExecutionContext): 
Route = {
+
+    val spray.json.JsObject(fields) = requestJSON
+    val spray.json.JsString(query) = fields("query")
+
+    val operation = fields.get("operationName") collect {
+      case spray.json.JsString(op) => op
+    }
+
+    val vars = fields.get("variables") match {
+      case Some(obj: spray.json.JsObject) => obj
+      case _ => spray.json.JsObject.empty
+    }
+
+    QueryParser.parse(query) match {
+      case Success(queryAst) => complete(executeGraphQLQuery(queryAst, 
operation, vars))
+      case Failure(error) => complete(BadRequest -> 
spray.json.JsObject("error" -> JsString(error.getMessage)))
+    }
+  }
+
+  /**
+    * In development mode(schemaCacheTTL = -1),
+    * a new schema is created for each request.
+    */
+  println(s"schemaCacheTTL: ${schemaCacheTTL}")
+
+  private def createNewSchema(): Schema[GraphRepository, Any] = {
+    println(s"Schema updated: ${System.currentTimeMillis()}")
+
+    val s2Type = new S2Type(s2Repository)
+    val newSchema = new SchemaDef(s2Type).S2GraphSchema
+
+    println(SchemaRenderer.renderSchema(newSchema))
+    println("-" * 80)
+
+    newSchema
+  }
+
+  private def executeGraphQLQuery(query: Document, op: Option[String], vars: 
JsObject)(implicit e: ExecutionContext) = {
+    val s2schema = schemaCache.withCache("s2Schema")(createNewSchema())
+
+    Executor.execute(
+      s2schema,
+      query,
+      s2Repository,
+      variables = vars,
+      operationName = op
+    )
+      .map((res: spray.json.JsValue) => OK -> res)
+      .recover {
+        case error: QueryAnalysisError => BadRequest -> error.resolveError
+        case error: ErrorWithResolver => InternalServerError -> 
error.resolveError
+      }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/1f1dee3d/s2graphql/src/main/scala/GraphRepository.scala
----------------------------------------------------------------------
diff --git a/s2graphql/src/main/scala/GraphRepository.scala 
b/s2graphql/src/main/scala/GraphRepository.scala
new file mode 100644
index 0000000..d4c910a
--- /dev/null
+++ b/s2graphql/src/main/scala/GraphRepository.scala
@@ -0,0 +1,164 @@
+/*
+ * 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
+
+import org.apache.s2graph.S2Type._
+import org.apache.s2graph.core.Management.JsonModel.{Index, Prop}
+import org.apache.s2graph.core._
+import org.apache.s2graph.core.mysqls.{Label, LabelIndex, Service, 
ServiceColumn}
+import org.apache.s2graph.core.rest.RequestParser
+import org.apache.s2graph.core.storage.MutateResponse
+import org.apache.s2graph.core.types.{HBaseType, LabelWithDirection}
+import play.api.libs.json._
+import sangria.schema.{Action, Args}
+
+import scala.concurrent._
+import scala.util.{Failure, Success, Try}
+
+
+/**
+  *
+  * @param graph
+  */
+class GraphRepository(graph: S2GraphLike) {
+
+  val management = graph.management
+  val parser = new RequestParser(graph)
+
+  implicit val ec = graph.ec
+
+  def partialVertexParamToVertex(column: ServiceColumn, param: 
PartialVertexParam): S2VertexLike = {
+    val vid = JSONParser.jsValueToInnerVal(param.vid, column.columnType, 
column.schemaVersion).get
+    graph.toVertex(param.service.serviceName, column.columnName, vid)
+  }
+
+  def partialEdgeParamToS2Edge(labelName: String, param: PartialEdgeParam): 
S2EdgeLike = {
+    graph.toEdge(
+      srcId = param.from,
+      tgtId = param.to,
+      labelName = labelName,
+      props = param.props,
+      direction = param.direction
+    )
+  }
+
+  def addEdges(args: Args): Future[Seq[MutateResponse]] = {
+    val edges: Seq[S2EdgeLike] = args.raw.keys.toList.flatMap { labelName =>
+      val params = args.arg[Vector[PartialEdgeParam]](labelName)
+      params.map(param => partialEdgeParamToS2Edge(labelName, param))
+    }
+
+    graph.mutateEdges(edges)
+  }
+
+  def addEdge(args: Args): Future[Option[MutateResponse]] = {
+    val edges: Seq[S2EdgeLike] = args.raw.keys.toList.map { labelName =>
+      val param = args.arg[PartialEdgeParam](labelName)
+      partialEdgeParamToS2Edge(labelName, param)
+    }
+
+    graph.mutateEdges(edges).map(_.headOption)
+  }
+
+  def getEdges(vertex: S2VertexLike, label: Label, _dir: String): 
Future[Seq[S2EdgeLike]] = {
+    val dir = GraphUtil.directions(_dir)
+    val labelWithDir = LabelWithDirection(label.id.get, dir)
+    val step = Step(Seq(QueryParam(labelWithDir)))
+    val q = Query(Seq(vertex), steps = Vector(step))
+
+    graph.getEdges(q).map(_.edgeWithScores.map(_.edge))
+  }
+
+  def createService(args: Args): Try[Service] = {
+    val serviceName = args.arg[String]("name")
+
+    Service.findByName(serviceName) match {
+      case Some(_) => Failure(new RuntimeException(s"Service (${serviceName}) 
already exists"))
+      case None =>
+        val cluster = 
args.argOpt[String]("cluster").getOrElse(parser.DefaultCluster)
+        val hTableName = 
args.argOpt[String]("hTableName").getOrElse(s"${serviceName}-${parser.DefaultPhase}")
+        val preSplitSize = args.argOpt[Int]("preSplitSize").getOrElse(1)
+        val hTableTTL = args.argOpt[Int]("hTableTTL")
+        val compressionAlgorithm = 
args.argOpt[String]("compressionAlgorithm").getOrElse(parser.DefaultCompressionAlgorithm)
+
+        val serviceTry = management
+          .createService(serviceName,
+            cluster,
+            hTableName,
+            preSplitSize,
+            hTableTTL,
+            compressionAlgorithm)
+
+        serviceTry
+    }
+  }
+
+  def createLabel(args: Args): Try[Label] = {
+    val labelName = args.arg[String]("name")
+
+    val srcServiceProp = args.arg[LabelServiceProp]("sourceService")
+    val tgtServiceProp = args.arg[LabelServiceProp]("targetService")
+
+    val allProps = args.argOpt[Vector[Prop]]("props").getOrElse(Vector.empty)
+    val indices = args.argOpt[Vector[Index]]("indices").getOrElse(Vector.empty)
+
+    val serviceName = 
args.argOpt[String]("serviceName").getOrElse(tgtServiceProp.name)
+    val consistencyLevel = 
args.argOpt[String]("consistencyLevel").getOrElse("weak")
+    val hTableName = args.argOpt[String]("hTableName")
+    val hTableTTL = args.argOpt[Int]("hTableTTL")
+    val schemaVersion = 
args.argOpt[String]("schemaVersion").getOrElse(HBaseType.DEFAULT_VERSION)
+    val isAsync = args.argOpt("isAsync").getOrElse(false)
+    val compressionAlgorithm = 
args.argOpt[String]("compressionAlgorithm").getOrElse(parser.DefaultCompressionAlgorithm)
+    val isDirected = args.argOpt[Boolean]("isDirected").getOrElse(true)
+    val options = args.argOpt[String]("options") // TODO: support option type
+
+    val labelTry: scala.util.Try[Label] = management.createLabel(
+      labelName,
+      srcServiceProp.name,
+      srcServiceProp.columnName,
+      srcServiceProp.dataType,
+      tgtServiceProp.name,
+      tgtServiceProp.columnName,
+      tgtServiceProp.dataType,
+      isDirected,
+      serviceName,
+      indices,
+      allProps,
+      consistencyLevel,
+      hTableName,
+      hTableTTL,
+      schemaVersion,
+      isAsync,
+      compressionAlgorithm,
+      options
+    )
+
+    labelTry
+  }
+
+  def allServices: List[Service] = Service.findAll()
+
+  def findServiceByName(name: String): Option[Service] = 
Service.findByName(name)
+
+  def allLabels: List[Label] = Label.findAll()
+
+  def findLabelByName(name: String): Option[Label] = Label.findByName(name)
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/1f1dee3d/s2graphql/src/main/scala/HttpServer.scala
----------------------------------------------------------------------
diff --git a/s2graphql/src/main/scala/HttpServer.scala 
b/s2graphql/src/main/scala/HttpServer.scala
new file mode 100644
index 0000000..cf1cc0c
--- /dev/null
+++ b/s2graphql/src/main/scala/HttpServer.scala
@@ -0,0 +1,61 @@
+/*
+ * 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
+
+import akka.actor.ActorSystem
+import akka.http.scaladsl.Http
+import akka.stream.ActorMaterializer
+import akka.http.scaladsl.server._
+import akka.http.scaladsl.server.Directives._
+import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
+
+import Console._
+import scala.concurrent.Await
+import scala.language.postfixOps
+
+object Server extends App {
+
+  implicit val actorSystem = ActorSystem("s2graphql-server")
+  implicit val materializer = ActorMaterializer()
+
+  import actorSystem.dispatcher
+  import scala.concurrent.duration._
+
+  println("Starting GRAPHQL server...")
+
+  val route: Route =
+    (post & path("graphql")) {
+      entity(as[spray.json.JsValue])(GraphQLServer.endpoint)
+    } ~ {
+      getFromResource("graphiql.html")
+    }
+
+  val port = sys.props.get("http.port").fold(8000)(_.toInt)
+  Http().bindAndHandle(route, "0.0.0.0", port)
+
+
+  def shutdown(): Unit = {
+    println("Terminating...")
+    actorSystem.terminate()
+    Await.result(actorSystem.whenTerminated, 10 seconds)
+
+    println("Terminated.")
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/1f1dee3d/s2graphql/src/main/scala/S2Type.scala
----------------------------------------------------------------------
diff --git a/s2graphql/src/main/scala/S2Type.scala 
b/s2graphql/src/main/scala/S2Type.scala
new file mode 100644
index 0000000..59eae4c
--- /dev/null
+++ b/s2graphql/src/main/scala/S2Type.scala
@@ -0,0 +1,519 @@
+/*
+ * 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
+
+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 play.api.libs.json.JsValue
+import sangria.marshalling.{CoercedScalaResultMarshaller, FromInput}
+import sangria.schema._
+
+import scala.language.existentials
+import scala.util.{Failure, Success, Try}
+
+object S2Type {
+
+  import sangria.schema._
+
+  case class LabelServiceProp(name: String, columnName: String, dataType: 
String)
+
+  case class MutationResponse[T](result: Try[T])
+
+  case class PartialVertexParam(service: Service, vid: JsValue)
+
+  case class PartialEdgeParam(ts: Long,
+                              from: Any,
+                              to: Any,
+                              direction: String,
+                              props: Map[String, Any])
+
+  implicit object PartialEdgeFromInput extends FromInput[PartialEdgeParam] {
+    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]
+      }
+
+      PartialEdgeParam(ts, from, to, dir, props)
+    }
+  }
+
+  implicit object IndexFromInput extends FromInput[Index] {
+    val marshaller = CoercedScalaResultMarshaller.default
+
+    def fromResult(node: marshaller.Node) = {
+      val input = node.asInstanceOf[Map[String, Any]]
+      Index(input("name").asInstanceOf[String], 
input("propNames").asInstanceOf[Seq[String]])
+    }
+  }
+
+  implicit object PropFromInput extends FromInput[Prop] {
+    val marshaller = CoercedScalaResultMarshaller.default
+
+    def fromResult(node: marshaller.Node) = {
+      val input = node.asInstanceOf[Map[String, String]]
+      Prop(input("name"), input("defaultValue"), input("dataType"))
+    }
+  }
+
+  implicit object LabelServiceFromInput extends FromInput[LabelServiceProp] {
+    val marshaller = CoercedScalaResultMarshaller.default
+
+    def fromResult(node: marshaller.Node) = {
+      val input = node.asInstanceOf[Map[String, String]]
+      LabelServiceProp(input("name"), input("columnName"), input("dataType"))
+    }
+  }
+
+  def s2TypeToScalarType(from: String): ScalarType[_] = from match {
+    case "string" => StringType
+    case "int" => IntType
+    case "integer" => IntType
+    case "long" => LongType
+    case "float" => FloatType
+    case "double" => FloatType
+    case "boolean" => BooleanType
+    case "bool" => BooleanType
+  }
+}
+
+class S2Type(repo: GraphRepository) {
+
+  import sangria.macros.derive._
+  import S2Type._
+
+  lazy val DirArg = Argument("direction", OptionInputType(DirectionType), 
"desc here", defaultValue = "out")
+
+  lazy val NameArg = Argument("name", StringType, description = "desc here")
+
+  lazy val ServiceNameArg = Argument("name", OptionInputType(ServiceListType), 
description = "desc here")
+
+  lazy val LabelNameArg = Argument("name", OptionInputType(LabelListType), 
description = "desc here")
+
+  lazy val PropArg = Argument("props", 
OptionInputType(ListInputType(InputPropType)), description = "desc here")
+
+  lazy val IndicesArg = Argument("indices", 
OptionInputType(ListInputType(InputIndexType)), description = "desc here")
+
+  lazy val ServiceType = deriveObjectType[GraphRepository, Service](
+    ObjectTypeName("Service"),
+    ObjectTypeDescription("desc here"),
+    RenameField("serviceName", "name")
+  )
+
+  lazy val LabelMetaType = deriveObjectType[GraphRepository, LabelMeta](
+    ObjectTypeName("LabelMeta"),
+    ExcludeFields("seq", "labelId")
+  )
+
+  lazy val DataTypeType = EnumType(
+    "DataType",
+    description = Option("desc here"),
+    values = List(
+      EnumValue("string", value = "string"),
+      EnumValue("int", value = "int"),
+      EnumValue("long", value = "long"),
+      EnumValue("float", value = "float"),
+      EnumValue("boolean", value = "boolean")
+    )
+  )
+
+  lazy val DirectionType = EnumType(
+    "Direction",
+    description = Option("desc here"),
+    values = List(
+      EnumValue("out", value = "out"),
+      EnumValue("in", value = "in")
+    )
+  )
+
+  lazy val InputIndexType = InputObjectType[Index](
+    "Index",
+    description = "desc here",
+    fields = List(
+      InputField("name", StringType),
+      InputField("propNames", ListInputType(StringType))
+    )
+  )
+
+  lazy val InputPropType = InputObjectType[Prop](
+    "Prop",
+    description = "desc here",
+    fields = List(
+      InputField("name", StringType),
+      InputField("dataType", DataTypeType),
+      InputField("defaultValue", StringType)
+    )
+  )
+
+  lazy val dummyEnum = EnumValue("_", value = "_")
+
+  lazy val ServiceListType = EnumType(
+    s"ServiceList",
+    description = Option("desc here"),
+    values =
+      dummyEnum +: repo.allServices.map { service =>
+        EnumValue(service.serviceName, value = service.serviceName)
+      }
+  )
+
+  lazy val LabelListType = EnumType(
+    s"LabelList",
+    description = Option("desc here"),
+    values =
+      dummyEnum +: repo.allLabels.map { label =>
+        EnumValue(label.label, value = label.label)
+      }
+  )
+
+  lazy val CompressionAlgorithmType = EnumType(
+    "CompressionAlgorithm",
+    description = Option("desc here"),
+    values = List(
+      EnumValue("gz", description = Option("desc here"), value = "gz"),
+      EnumValue("lz4", description = Option("desc here"), value = "lz4")
+    )
+  )
+
+  lazy val ConsistencyLevelType = EnumType(
+    "ConsistencyList",
+    description = Option("desc here"),
+    values = List(
+      EnumValue("weak", description = Option("desc here"), value = "weak"),
+      EnumValue("strong", description = Option("desc here"), value = "strong")
+    )
+  )
+
+  lazy val InputLabelServiceType = InputObjectType[LabelServiceProp](
+    "LabelServiceProp",
+    description = "desc here",
+    fields = List(
+      InputField("name", ServiceListType),
+      InputField("columnName", StringType),
+      InputField("dataType", DataTypeType)
+    )
+  )
+
+  lazy val LabelIndexType = deriveObjectType[GraphRepository, LabelIndex](
+    ObjectTypeName("LabelIndex"),
+    ObjectTypeDescription("desc here"),
+    ExcludeFields("seq", "metaSeqs", "formulars", "labelId")
+  )
+
+  lazy val LabelType = deriveObjectType[GraphRepository, Label](
+    ObjectTypeName("Label"),
+    ObjectTypeDescription("desc here"),
+    AddFields(
+      Field("indexes", ListType(LabelIndexType), resolve = c => Nil),
+      Field("props", ListType(LabelMetaType), resolve = c => Nil)
+    ),
+    RenameField("label", "name")
+  )
+
+  def makeInputPartialEdgeParamType(label: Label): 
InputObjectType[PartialEdgeParam] = {
+    lazy val InputPropsType = InputObjectType[Map[String, ScalarType[_]]](
+      s"${label.label}_props",
+      description = "desc here",
+      () => label.labelMetaSet.toList.map { lm =>
+        InputField(lm.name, OptionInputType(s2TypeToScalarType(lm.dataType)))
+      }
+    )
+
+    lazy val labelFields = List(
+      InputField("timestamp", OptionInputType(LongType)),
+      InputField("from", s2TypeToScalarType(label.srcColumnType)),
+      InputField("to", s2TypeToScalarType(label.srcColumnType)),
+      InputField("direction", OptionInputType(DirectionType))
+    )
+
+    InputObjectType[PartialEdgeParam](
+      s"${label.label}_mutate",
+      description = "desc here",
+      () =>
+        if (label.labelMetaSet.isEmpty) labelFields
+        else labelFields ++ Seq(InputField("props", 
OptionInputType(InputPropsType)))
+    )
+  }
+
+  lazy val EdgeArg = repo.allLabels.map { label =>
+    val inputPartialEdgeParamType = makeInputPartialEdgeParamType(label)
+    Argument(label.label, OptionInputType(inputPartialEdgeParamType))
+  }
+
+  lazy val EdgesArg = repo.allLabels.map { label =>
+    val inputPartialEdgeParamType = makeInputPartialEdgeParamType(label)
+    Argument(label.label, 
OptionInputType(ListInputType(inputPartialEdgeParamType)))
+  }
+
+  lazy val serviceOptArgs = List(
+    "compressionAlgorithm" -> CompressionAlgorithmType,
+    "cluster" -> StringType,
+    "hTableName" -> StringType,
+    "preSplitSize" -> IntType,
+    "hTableTTL" -> IntType
+  ).map { case (name, _type) => Argument(name, OptionInputType(_type)) }
+
+  lazy val labelRequiredArg = List(
+    "sourceService" -> InputLabelServiceType,
+    "targetService" -> InputLabelServiceType
+  ).map { case (name, _type) => Argument(name, _type) }
+
+  lazy val labelOptsArgs = List(
+    "serviceName" -> ServiceListType,
+    "consistencyLevel" -> ConsistencyLevelType,
+    "isDirected" -> BooleanType,
+    "isAsync" -> BooleanType,
+    "schemaVersion" -> StringType
+  ).map { case (name, _type) => Argument(name, OptionInputType(_type)) }
+
+  lazy val ServiceMutationResponseType = makeMutationResponseType[Service](
+    "CreateService",
+    "desc here",
+    ServiceType
+  )
+
+  lazy val LabelMutationResponseType = makeMutationResponseType[Label](
+    "CreateLabel",
+    "desc here",
+    LabelType
+  )
+
+  lazy val EdgeMutateResponseType = deriveObjectType[GraphRepository, 
MutateResponse](
+    ObjectTypeName("EdgeMutateResponse"),
+    ObjectTypeDescription("desc here"),
+    AddFields(
+      Field("isSuccess", BooleanType, resolve = c => c.value.isSuccess)
+    )
+  )
+
+  def makeMutationResponseType[T](name: String, desc: String, tpe: 
ObjectType[_, T]) = {
+    ObjectType(
+      name,
+      desc,
+      () => fields[Unit, MutationResponse[T]](
+        Field("isSuccess",
+          BooleanType,
+          resolve = _.value.result.isSuccess
+        ),
+        Field("message",
+          StringType,
+          resolve = _.value.result match {
+            case Success(_) => s"Created successful"
+            case Failure(ex) => ex.getMessage
+          }
+        ),
+        Field("created",
+          OptionType(tpe),
+          resolve = _.value.result.toOption
+        )
+      )
+    )
+  }
+
+  lazy val vertexIdField: Field[GraphRepository, Any] = Field(
+    "id",
+    PlayJsonPolyType.PolyType,
+    description = Some("desc here"),
+    resolve = _.value match {
+      case v: PartialVertexParam => v.vid
+      case _ => throw new RuntimeException("dead code")
+    }
+  )
+
+  lazy val tsField: Field[GraphRepository, Any] =
+    Field("timestamp",
+      LongType,
+      description = Option("desc here"),
+      resolve = _.value match {
+        case e: S2EdgeLike => e.ts
+        case _ => throw new RuntimeException("dead code")
+      })
+
+  def makeEdgePropFields(edgeFieldNameWithTypes: 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 e: S2EdgeLike =>
+          val innerVal = name match {
+            case "from" => e.srcForVertex.innerId
+            case "to" => e.tgtForVertex.innerId
+            case _ => e.propertyValue(name).get.innerVal
+          }
+
+          JSONParser.innerValToAny(innerVal, cType).asInstanceOf[A]
+
+        case _ => throw new RuntimeException("dead code")
+      })
+
+    edgeFieldNameWithTypes.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}")
+      }
+    }
+  }
+
+  // ex: KakaoFavorites
+  lazy val serviceVertexFields: List[Field[GraphRepository, Any]] = 
repo.allServices.map { service =>
+    val serviceId = service.id.get
+    val connectedLabels = repo.allLabels.filter { lb =>
+      lb.srcServiceId == serviceId || lb.tgtServiceId == serviceId
+    }.distinct
+
+    // label connected on services, friends, post
+    lazy val connectedLabelFields: List[Field[GraphRepository, Any]] = 
connectedLabels.map { label =>
+      val labelColumns = List("from" -> label.srcColumnType, "to" -> 
label.tgtColumnType)
+      val labelProps = label.labelMetas.map { lm => lm.name -> lm.dataType }
+
+      lazy val EdgeType = ObjectType(label.label, () => 
fields[GraphRepository, Any](edgeFields ++ connectedLabelFields: _*))
+      lazy val edgeFields: List[Field[GraphRepository, Any]] = tsField :: 
makeEdgePropFields(labelColumns ++ labelProps)
+      lazy val edgeTypeField: Field[GraphRepository, Any] = Field(
+        label.label,
+        ListType(EdgeType),
+        arguments = DirArg :: Nil,
+        description = Some("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: PartialVertexParam =>
+              if (dir == "out") 
c.ctx.partialVertexParamToVertex(label.tgtColumn, vp)
+              else c.ctx.partialVertexParamToVertex(label.srcColumn, vp)
+          }
+
+          c.ctx.getEdges(vertex, label, dir)
+        }
+      )
+
+      edgeTypeField
+    }
+
+    lazy val VertexType = ObjectType(
+      s"${service.serviceName}",
+      fields[GraphRepository, Any](vertexIdField +: connectedLabelFields: _*)
+    )
+
+    Field(
+      service.serviceName,
+      ListType(VertexType),
+      arguments = List(
+        Argument("id", OptionInputType(PlayJsonPolyType.PolyType)),
+        Argument("ids", 
OptionInputType(ListInputType(PlayJsonPolyType.PolyType)))
+      ),
+      description = Some(s"serviceName: ${service.serviceName}"),
+      resolve = { c =>
+        val id = c.argOpt[JsValue]("id").toSeq
+        val ids = c.argOpt[List[JsValue]]("ids").toList.flatten
+        val svc = c.ctx.findServiceByName(service.serviceName).get
+
+        (id ++ ids).map { vid => PartialVertexParam(svc, vid) }
+      }
+    ): Field[GraphRepository, Any]
+  }
+
+  lazy val serviceField: Field[GraphRepository, Any] = Field(
+    "Services",
+    ListType(ServiceType),
+    description = Option("desc here"),
+    arguments = List(ServiceNameArg),
+    resolve = { c =>
+      c.argOpt[String]("name") match {
+        case Some(name) => c.ctx.allServices.filter(_.serviceName == name)
+        case None => c.ctx.allServices
+      }
+    }
+  )
+
+  lazy val labelField: Field[GraphRepository, Any] = Field(
+    "Labels",
+    ListType(LabelType),
+    description = Option("desc here"),
+    arguments = List(LabelNameArg),
+    resolve = { c =>
+      c.argOpt[String]("name") match {
+        case Some(name) => c.ctx.allLabels.filter(_.label == name)
+        case None => c.ctx.allLabels
+      }
+    }
+  )
+
+  /**
+    * Query fields
+    * Provide s2graph query API
+    *
+    * - Fields is created(or changed) for metadata is changed.
+    */
+  lazy val queryFields = Seq(serviceField, labelField) ++ serviceVertexFields
+
+  /**
+    * Mutation fields
+    * Provide s2graph management API
+    *
+    * - createService
+    * - createLabel
+    * - addEdge
+    * - addEdges
+    */
+  lazy val mutationFields: List[Field[GraphRepository, Any]] = List(
+    Field("createService",
+      ServiceMutationResponseType,
+      arguments = NameArg :: serviceOptArgs,
+      resolve = c => MutationResponse(c.ctx.createService(c.args))
+    ),
+    Field("createLabel",
+      LabelMutationResponseType,
+      arguments = NameArg :: PropArg :: IndicesArg :: labelRequiredArg ::: 
labelOptsArgs,
+      resolve = c => MutationResponse(c.ctx.createLabel(c.args))
+    ),
+    Field("addEdge",
+      OptionType(EdgeMutateResponseType),
+      arguments = EdgeArg,
+      resolve = c => c.ctx.addEdge(c.args)
+    ),
+    Field("addEdges",
+      ListType(EdgeMutateResponseType),
+      arguments = EdgesArg,
+      resolve = c => c.ctx.addEdges(c.args)
+    )
+  )
+}

http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/1f1dee3d/s2graphql/src/main/scala/SangriaPlayJsonScalarType.scala
----------------------------------------------------------------------
diff --git a/s2graphql/src/main/scala/SangriaPlayJsonScalarType.scala 
b/s2graphql/src/main/scala/SangriaPlayJsonScalarType.scala
new file mode 100644
index 0000000..8421b8a
--- /dev/null
+++ b/s2graphql/src/main/scala/SangriaPlayJsonScalarType.scala
@@ -0,0 +1,76 @@
+/*
+ * 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
+
+import sangria.ast._
+import sangria.schema._
+import sangria.validation.ValueCoercionViolation
+
+// https://gist.github.com/OlegIlyenko/5b96f4b54f656aac226d3c4bc33fd2a6
+
+object PlayJsonPolyType {
+
+  import sangria.ast
+  import sangria.schema._
+  import play.api.libs.json._
+
+  case object JsonCoercionViolation extends ValueCoercionViolation("Not valid 
JSON")
+
+  def scalarTypeToJsValue(v: sangria.ast.Value): JsValue = v match {
+    case v: IntValue => JsNumber(v.value)
+    case v: BigIntValue => JsNumber(BigDecimal(v.value.bigInteger))
+    case v: FloatValue => JsNumber(v.value)
+    case v: BigDecimalValue => JsNumber(v.value)
+    case v: StringValue => JsString(v.value)
+    case v: BooleanValue => JsBoolean(v.value)
+    case v: ListValue => JsNull
+    case v: VariableValue => JsNull
+    case v: NullValue => JsNull
+    case v: ObjectValue => JsNull
+  }
+
+  implicit val PolyType = ScalarType[JsValue]("Poly",
+    description = Some("Type Poly = String | Number | Boolean"),
+    coerceOutput = (value, _) ⇒ value match {
+      case JsString(s) => s
+      case JsNumber(n) => n
+      case JsBoolean(b) => b
+      case JsNull => null
+      case _ => value
+    },
+    coerceUserInput = {
+      case v: String => Right(JsString(v))
+      case v: Boolean => Right(JsBoolean(v))
+      case v: Int => Right(JsNumber(v))
+      case v: Long => Right(JsNumber(v))
+      case v: Float => Right(JsNumber(v.toDouble))
+      case v: Double => Right(JsNumber(v))
+      case v: BigInt => Right(JsNumber(BigDecimal(v)))
+      case v: BigDecimal => Right(JsNumber(v))
+      case _ => Left(JsonCoercionViolation)
+    },
+    coerceInput = {
+      case value: ast.StringValue => Right(JsString(value.value))
+      case value: ast.IntValue => Right(JsNumber(value.value))
+      case value: ast.FloatValue => Right(JsNumber(value.value))
+      case value: ast.BigIntValue => 
Right(JsNumber(BigDecimal(value.value.bigInteger)))
+      case _ => Left(JsonCoercionViolation)
+    })
+}

http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/1f1dee3d/s2graphql/src/main/scala/SchemaDef.scala
----------------------------------------------------------------------
diff --git a/s2graphql/src/main/scala/SchemaDef.scala 
b/s2graphql/src/main/scala/SchemaDef.scala
new file mode 100644
index 0000000..6829040
--- /dev/null
+++ b/s2graphql/src/main/scala/SchemaDef.scala
@@ -0,0 +1,36 @@
+/*
+ * 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
+
+/**
+  * S2Graph GraphQL schema.
+  *
+  * When a Label or Service is created, the GraphQL schema is created 
dynamically.
+  */
+class SchemaDef(s2Type: S2Type) {
+
+  import sangria.schema._
+
+  val S2QueryType = ObjectType[GraphRepository, Any]("Query", 
fields(s2Type.queryFields: _*))
+
+  val S2MutationType = ObjectType[GraphRepository, Any]("Mutation", 
fields(s2Type.mutationFields: _*))
+
+  val S2GraphSchema = Schema(S2QueryType, Option(S2MutationType))
+}

http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/1f1dee3d/s2graphql/src/test/resources/application.conf
----------------------------------------------------------------------
diff --git a/s2graphql/src/test/resources/application.conf 
b/s2graphql/src/test/resources/application.conf
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/1f1dee3d/s2rest_play/app/org/apache/s2graph/rest/play/controllers/AdminController.scala
----------------------------------------------------------------------
diff --git 
a/s2rest_play/app/org/apache/s2graph/rest/play/controllers/AdminController.scala
 
b/s2rest_play/app/org/apache/s2graph/rest/play/controllers/AdminController.scala
index 2d3530b..2ee6395 100644
--- 
a/s2rest_play/app/org/apache/s2graph/rest/play/controllers/AdminController.scala
+++ 
b/s2rest_play/app/org/apache/s2graph/rest/play/controllers/AdminController.scala
@@ -304,20 +304,20 @@ object AdminController extends Controller {
    * @param columnName
    * @return
    */
-  def addServiceColumnProp(serviceName: String, columnName: String) = 
Action(parse.json) { request =>
-    addServiceColumnPropInner(serviceName, columnName)(request.body) match {
+  def addServiceColumnProp(serviceName: String, columnName: String, 
storeInGlobalIndex: Boolean = false) = Action(parse.json) { request =>
+    addServiceColumnPropInner(serviceName, columnName, 
storeInGlobalIndex)(request.body) match {
       case None => bad(s"can`t find service with $serviceName or can`t find 
serviceColumn with $columnName")
       case Some(m) => Ok(m.toJson).as(applicationJsonHeader)
     }
   }
 
-  def addServiceColumnPropInner(serviceName: String, columnName: String)(js: 
JsValue) = {
+  def addServiceColumnPropInner(serviceName: String, columnName: String, 
storeInGlobalIndex: Boolean = false)(js: JsValue) = {
     for {
       service <- Service.findByName(serviceName)
       serviceColumn <- ServiceColumn.find(service.id.get, columnName)
       prop <- requestParser.toPropElements(js).toOption
     } yield {
-      ColumnMeta.findOrInsert(serviceColumn.id.get, prop.name, prop.datatType)
+      ColumnMeta.findOrInsert(serviceColumn.id.get, prop.name, prop.dataType, 
storeInGlobalIndex)
     }
   }
 

Reply via email to