Merge remote-tracking branch 'apache/master' into S2GRAPH-243
diff --git a/.travis.yml b/.travis.yml
index bc5e6cb..8664e15 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -16,7 +16,7 @@
 language: scala
 
 env:
-  - HBASE_VERSION=1.2.6.1
+  - HBASE_VERSION=1.4.8
 
 cache:
   directories:
diff --git a/.travis/install-hbase.sh b/.travis/install-hbase.sh
new file mode 100755
index 0000000..5744fd8
--- /dev/null
+++ b/.travis/install-hbase.sh
@@ -0,0 +1,21 @@
+#!/usr/bin/env sh
+# 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 -xe
+#
+#if [ ! -d "$HOME/hbase-$HBASE_VERSION/bin" ]; then
+#  cd $HOME && wget -q -O - http://mirror.apache-kr.org/hbase/$HBASE_VERSION/hbase-$HBASE_VERSION-bin.tar.gz | tar xz
+#fi
diff --git a/project/Common.scala b/project/Common.scala
index e4a1b61..42628f0 100644
--- a/project/Common.scala
+++ b/project/Common.scala
@@ -26,6 +26,7 @@
   val specs2Version = "3.8.5"
 
   val hbaseVersion = "1.2.6.1"
+  val asynchbaseVersion = "1.7.2"
   val hadoopVersion = "2.7.3"
   val tinkerpopVersion = "3.2.5"
 
diff --git a/s2core/build.sbt b/s2core/build.sbt
index 0368715..76ec221 100644
--- a/s2core/build.sbt
+++ b/s2core/build.sbt
@@ -41,7 +41,7 @@
   "com.h2database" % "h2" % "1.4.192",
   "com.stumbleupon" % "async" % "1.4.1",
   "io.netty" % "netty" % "3.9.4.Final" force(),
-  "org.hbase" % "asynchbase" % "1.7.2" excludeLogging(),
+  "org.hbase" % "asynchbase" % asynchbaseVersion excludeLogging(),
   "net.bytebuddy" % "byte-buddy" % "1.4.26",
   "org.apache.tinkerpop" % "gremlin-core" % tinkerpopVersion excludeLogging(),
   "org.apache.tinkerpop" % "gremlin-test" % tinkerpopVersion % "test",
diff --git a/s2core/src/main/scala/org/apache/s2graph/core/storage/hbase/AsynchbaseStorage.scala b/s2core/src/main/scala/org/apache/s2graph/core/storage/hbase/AsynchbaseStorage.scala
index f670e9c..0c49e71 100644
--- a/s2core/src/main/scala/org/apache/s2graph/core/storage/hbase/AsynchbaseStorage.scala
+++ b/s2core/src/main/scala/org/apache/s2graph/core/storage/hbase/AsynchbaseStorage.scala
@@ -297,7 +297,7 @@
         get.setMaxTimestamp(maxTs)
         get.setTimeout(queryParam.rpcTimeout)
 
-        val pagination = new ColumnPaginationFilter(queryParam.limit, queryParam.offset)
+        val pagination = new ColumnPaginationFilter(queryParam.innerLimit, queryParam.innerOffset)
         val columnRangeFilterOpt = queryParam.intervalOpt.map { interval =>
           new ColumnRangeFilter(intervalMaxBytes, true, intervalMinBytes, true)
         }
diff --git a/s2core/src/test/scala/org/apache/s2graph/core/Integrate/ExampleTest.scala b/s2core/src/test/scala/org/apache/s2graph/core/Integrate/ExampleTest.scala
new file mode 100644
index 0000000..76e30d9
--- /dev/null
+++ b/s2core/src/test/scala/org/apache/s2graph/core/Integrate/ExampleTest.scala
@@ -0,0 +1,130 @@
+package org.apache.s2graph.core.Integrate
+
+import org.apache.s2graph.core.schema._
+import play.api.libs.json.{JsObject, Json}
+
+import scala.concurrent.Await
+import scala.concurrent.duration.Duration
+
+class ExampleTest extends IntegrateCommon {
+
+  import TestUtil._
+
+  private def prepareTestData() = {
+    // create service KakaoFavorites
+    val createServicePayload = Json.parse(
+      """
+        |{"serviceName": "KakaoFavorites", "compressionAlgorithm" : "gz"}
+        |""".stripMargin)
+
+    val createFriendsPayload = Json.parse(
+      s"""{
+         |  "label": "friends",
+         |  "srcServiceName": "KakaoFavorites",
+         |  "srcColumnName": "userName",
+         |  "srcColumnType": "string",
+         |  "tgtServiceName": "KakaoFavorites",
+         |  "tgtColumnName": "userName",
+         |  "tgtColumnType": "string",
+         |  "isDirected": "false",
+         |  "indices": [],
+         |  "props": [],
+         |  "consistencyLevel": "strong"
+         |}""".stripMargin)
+
+    val createPostPayload = Json.parse(
+      """{
+        |  "label": "post",
+        |  "srcServiceName": "KakaoFavorites",
+        |  "srcColumnName": "userName",
+        |  "srcColumnType": "string",
+        |  "tgtServiceName": "KakaoFavorites",
+        |  "tgtColumnName": "url",
+        |  "tgtColumnType": "string",
+        |  "isDirected": "true",
+        |  "indices": [],
+        |  "props": [],
+        |  "consistencyLevel": "strong"
+        |}""".stripMargin)
+
+    val insertFriendsPayload = Json.parse(
+      """[
+        |  {"from":"Elmo","to":"Big Bird","label":"friends","props":{},"timestamp":1444360152477},
+        |  {"from":"Elmo","to":"Ernie","label":"friends","props":{},"timestamp":1444360152478},
+        |  {"from":"Elmo","to":"Bert","label":"friends","props":{},"timestamp":1444360152479},
+        |
+        |  {"from":"Cookie Monster","to":"Grover","label":"friends","props":{},"timestamp":1444360152480},
+        |  {"from":"Cookie Monster","to":"Kermit","label":"friends","props":{},"timestamp":1444360152481},
+        |  {"from":"Cookie Monster","to":"Oscar","label":"friends","props":{},"timestamp":1444360152482}
+        |]""".stripMargin)
+
+    val insertPostPayload = Json.parse(
+      """[
+        |  {"from":"Big Bird","to":"www.kakaocorp.com/en/main","label":"post","props":{},"timestamp":1444360152477},
+        |  {"from":"Big Bird","to":"github.com/kakao/s2graph","label":"post","props":{},"timestamp":1444360152478},
+        |  {"from":"Ernie","to":"groups.google.com/forum/#!forum/s2graph","label":"post","props":{},"timestamp":1444360152479},
+        |  {"from":"Grover","to":"hbase.apache.org/forum/#!forum/s2graph","label":"post","props":{},"timestamp":1444360152480},
+        |  {"from":"Kermit","to":"www.playframework.com","label":"post","props":{},"timestamp":1444360152481},
+        |  {"from":"Oscar","to":"www.scala-lang.org","label":"post","props":{},"timestamp":1444360152482}
+        |]""".stripMargin)
+
+
+    val (serviceName, cluster, tableName, preSplitSize, ttl, compressionAlgorithm) = parser.toServiceElements(createServicePayload)
+    management.createService(serviceName, cluster, tableName, preSplitSize, ttl, compressionAlgorithm)
+
+    Service.findByName("KakaoFavorites", useCache = false)
+
+    Label.findByName("friends", useCache = false).foreach { label =>
+      Label.delete(label.id.get)
+    }
+    parser.toLabelElements(createFriendsPayload)
+
+    Label.findByName("post", useCache = false).foreach { label =>
+      Label.delete(label.id.get)
+    }
+
+    parser.toLabelElements(createPostPayload)
+    Await.result(graph.mutateEdges(parser.parseJsonFormat(insertFriendsPayload, operation = "insert").map(_._1), withWait = true), Duration("10 seconds"))
+    Await.result(graph.mutateEdges(parser.parseJsonFormat(insertPostPayload, operation = "insert").map(_._1), withWait = true), Duration("10 seconds"))
+  }
+
+  override def initTestData(): Unit = {
+    prepareTestData()
+  }
+
+  test("[S2GRAPH-243]: Limit bug on 'graph/getEdges' offset 0, limit 3") {
+    val queryJson = Json.parse(
+      """{
+        |    "select": ["to"],
+        |    "srcVertices": [{"serviceName": "KakaoFavorites", "columnName": "userName", "id":"Elmo"}],
+        |    "steps": [
+        |      {"step": [{"label": "friends", "direction": "out", "offset": 0, "limit": 3}]}
+        |    ]
+        |}""".stripMargin)
+
+    val response = getEdgesSync(queryJson)
+
+    val expectedFriends = Seq("Bert", "Ernie", "Big Bird")
+    val friends = (response \ "results").as[Seq[JsObject]].map(obj => (obj \ "to").as[String])
+
+    friends shouldBe expectedFriends
+  }
+
+  test("[S2GRAPH-243]: Limit bug on 'graph/getEdges' offset 1, limit 2") {
+    val queryJson = Json.parse(
+      """{
+        |    "select": ["to"],
+        |    "srcVertices": [{"serviceName": "KakaoFavorites", "columnName": "userName", "id":"Elmo"}],
+        |    "steps": [
+        |      {"step": [{"label": "friends", "direction": "out", "offset": 1, "limit": 2}]}
+        |    ]
+        |}""".stripMargin)
+
+    val response = getEdgesSync(queryJson)
+
+    val expectedFriends = Seq("Ernie", "Big Bird")
+    val friends = (response \ "results").as[Seq[JsObject]].map(obj => (obj \ "to").as[String])
+
+    friends shouldBe expectedFriends
+  }
+}
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 d5212c8..460a817 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
@@ -193,14 +193,14 @@
 
   def getEdges(vertices: Seq[S2VertexLike], queryParam: QueryParam): Future[Seq[S2EdgeLike]] = {
     val step = Step(Seq(queryParam))
-    val q = Query(vertices, steps = Vector(step))
+    val q = Query(vertices, steps = Vector(step), QueryOption(returnDegree = false))
 
     graph.getEdges(q).map(_.edgeWithScores.map(_.edge))
   }
 
   def getEdges(vertex: S2VertexLike, queryParam: QueryParam): Future[Seq[S2EdgeLike]] = {
     val step = Step(Seq(queryParam))
-    val q = Query(Seq(vertex), steps = Vector(step))
+    val q = Query(Seq(vertex), steps = Vector(step), QueryOption(returnDegree = false))
 
     graph.getEdges(q).map(_.edgeWithScores.map(_.edge))
   }
diff --git a/s2graphql/src/main/scala/org/apache/s2graph/graphql/types/FieldResolver.scala b/s2graphql/src/main/scala/org/apache/s2graph/graphql/types/FieldResolver.scala
index 1994e5e..4de1b2e 100644
--- a/s2graphql/src/main/scala/org/apache/s2graph/graphql/types/FieldResolver.scala
+++ b/s2graphql/src/main/scala/org/apache/s2graph/graphql/types/FieldResolver.scala
@@ -53,7 +53,7 @@
     val vertex = c.value.asInstanceOf[S2VertexLike]
 
     val dir = c.arg[String]("direction")
-    val offset = c.arg[Int]("offset") + 1 // +1 for skip degree edge: currently not support
+    val offset = c.arg[Int]("offset") // +1 for skip degree edge: currently not support
     val limit = c.arg[Int]("limit")
     val whereClauseOpt = c.argOpt[String]("filter")
     val where = c.ctx.parser.extractWhere(label, whereClauseOpt)