Repository: incubator-s2graph Updated Branches: refs/heads/master 7951f5520 -> 146094b50
[S2GRAPH-135] Change the way LabelIndexOption is implemented and improve it Project: http://git-wip-us.apache.org/repos/asf/incubator-s2graph/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-s2graph/commit/20bdf929 Tree: http://git-wip-us.apache.org/repos/asf/incubator-s2graph/tree/20bdf929 Diff: http://git-wip-us.apache.org/repos/asf/incubator-s2graph/diff/20bdf929 Branch: refs/heads/master Commit: 20bdf9291acfbb830db441490e1d5351fa8a0ca3 Parents: 247b2cb Author: daewon <[email protected]> Authored: Fri Dec 2 00:11:04 2016 +0900 Committer: daewon <[email protected]> Committed: Fri Dec 2 00:24:26 2016 +0900 ---------------------------------------------------------------------- .../scala/org/apache/s2graph/core/S2Edge.scala | 52 ++++--- .../apache/s2graph/core/mysqls/LabelIndex.scala | 52 +++---- .../apache/s2graph/core/storage/Storage.scala | 44 ++---- .../core/Integrate/IntegrateCommon.scala | 63 +++++++- .../core/Integrate/LabelIndexOptionTest.scala | 144 +++++++++++++++++++ 5 files changed, 272 insertions(+), 83 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/20bdf929/s2core/src/main/scala/org/apache/s2graph/core/S2Edge.scala ---------------------------------------------------------------------- diff --git a/s2core/src/main/scala/org/apache/s2graph/core/S2Edge.scala b/s2core/src/main/scala/org/apache/s2graph/core/S2Edge.scala index 2960265..5c2a5dc 100644 --- a/s2core/src/main/scala/org/apache/s2graph/core/S2Edge.scala +++ b/s2core/src/main/scala/org/apache/s2graph/core/S2Edge.scala @@ -136,6 +136,8 @@ case class IndexEdge(graph: S2Graph, lazy val labelIndexMetaSeqs = labelIndex.sortKeyTypes + def indexOption = if (isInEdge) labelIndex.inDirOption else labelIndex.outDirOption + /** TODO: make sure call of this class fill props as this assumes */ lazy val orders = for (meta <- labelIndexMetaSeqs) yield { propsWithTs.get(meta.name) match { @@ -149,8 +151,10 @@ case class IndexEdge(graph: S2Graph, case LabelMeta.timestamp=> InnerVal.withLong(version, schemaVer) case LabelMeta.to => toEdge.tgtVertex.innerId case LabelMeta.from => toEdge.srcVertex.innerId + case LabelMeta.fromHash => indexOption.map { option => + InnerVal.withLong(MurmurHash3.stringHash(toEdge.srcVertex.innerId.toString()).abs % option.totalModular, schemaVer) + }.getOrElse(throw new RuntimeException("from_hash must be used with sampling")) // for now, it does not make sense to build index on srcVertex.innerId since all edges have same data. - // throw new RuntimeException("_from on indexProps is not supported") case _ => toInnerVal(meta.defaultValue, meta.dataType, schemaVer) } @@ -574,10 +578,34 @@ case class S2Edge(innerGraph: S2Graph, } +object EdgeMutate { + def filterIndexOptionForDegree(edges: Seq[IndexEdge]): Seq[IndexEdge] = edges.filter { ie => + ie.indexOption.fold(true)(_.storeDegree) + } + + def filterIndexOption(edges: Seq[IndexEdge]): Seq[IndexEdge] = edges.filter { ie => + ie.indexOption.fold(true) { option => + val hashValueOpt = ie.orders.find { case (k, v) => k == LabelMeta.fromHash }.map { case (k, v) => + v.value.toString.toLong + } + + option.sample(ie, hashValueOpt) + } + } +} + case class EdgeMutate(edgesToDelete: List[IndexEdge] = List.empty[IndexEdge], edgesToInsert: List[IndexEdge] = List.empty[IndexEdge], newSnapshotEdge: Option[SnapshotEdge] = None) { + val edgesToInsertWithIndexOpt: Seq[IndexEdge] = EdgeMutate.filterIndexOption(edgesToInsert) + + val edgesToDeleteWithIndexOpt: Seq[IndexEdge] = EdgeMutate.filterIndexOption(edgesToDelete) + + val edgesToInsertWithIndexOptForDegree: Seq[IndexEdge] = EdgeMutate.filterIndexOptionForDegree(edgesToInsert) + + val edgesToDeleteWithIndexOptForDegree: Seq[IndexEdge] = EdgeMutate.filterIndexOptionForDegree(edgesToDelete) + def toLogString: String = { val l = (0 until 50).map(_ => "-").mkString("") val deletes = s"deletes: ${edgesToDelete.map(e => e.toLogString).mkString("\n")}" @@ -745,24 +773,6 @@ object S2Edge { } } - def filterOutWithLabelOption(ls: Seq[IndexEdge]): Seq[IndexEdge] = ls.filter { ie => - ie.labelIndex.dir match { - case None => - // both direction use same indices that is defined when label creation. - true - case Some(dir) => - if (dir != ie.dir) { - // current labelIndex's direction is different with indexEdge's direction so don't touch - false - } else { - ie.labelIndex.writeOption.map { option => - val hashValueOpt = ie.orders.find { case (k, v) => k == LabelMeta.fromHash }.map{ case (k, v) => v.value.toString.toLong } - option.sample(ie, hashValueOpt) - }.getOrElse(true) - } - } - } - def buildMutation(snapshotEdgeOpt: Option[S2Edge], requestEdge: S2Edge, newVersion: Long, @@ -793,7 +803,7 @@ object S2Edge { val edgesToDelete = snapshotEdgeOpt match { case Some(snapshotEdge) if snapshotEdge.op != GraphUtil.operations("delete") => snapshotEdge.copy(op = GraphUtil.defaultOpByte) - .relatedEdges.flatMap { relEdge => filterOutWithLabelOption(relEdge.edgesWithIndexValid) } + .relatedEdges.flatMap { relEdge => relEdge.edgesWithIndexValid } case _ => Nil } @@ -807,7 +817,7 @@ object S2Edge { ) newPropsWithTs.foreach { case (k, v) => newEdge.property(k.name, v.innerVal.value, v.ts) } - newEdge.relatedEdges.flatMap { relEdge => filterOutWithLabelOption(relEdge.edgesWithIndexValid) } + newEdge.relatedEdges.flatMap { relEdge => relEdge.edgesWithIndexValid } } http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/20bdf929/s2core/src/main/scala/org/apache/s2graph/core/mysqls/LabelIndex.scala ---------------------------------------------------------------------- diff --git a/s2core/src/main/scala/org/apache/s2graph/core/mysqls/LabelIndex.scala b/s2core/src/main/scala/org/apache/s2graph/core/mysqls/LabelIndex.scala index 7b1cd07..fa61149 100644 --- a/s2core/src/main/scala/org/apache/s2graph/core/mysqls/LabelIndex.scala +++ b/s2core/src/main/scala/org/apache/s2graph/core/mysqls/LabelIndex.scala @@ -19,10 +19,6 @@ package org.apache.s2graph.core.mysqls -/** - * Created by shon on 6/3/15. - */ - import org.apache.s2graph.core.GraphUtil import org.apache.s2graph.core.mysqls.LabelIndex.WriteOption import org.apache.s2graph.core.utils.logger @@ -45,13 +41,14 @@ object LabelIndex extends Model[LabelIndex] { rs.stringOpt("options") ) } - object WriteOption { - val Default = WriteOption() - } - case class WriteOption(method: String = "default", - rate: Double = 1.0, - totalModular: Long = 100, - storeDegree: Boolean = true) { + + case class WriteOption(dir: Byte, + method: String, + rate: Double, + totalModular: Long, + storeDegree: Boolean) { + + val isBufferIncrement = method == "drop" || method == "sample" || method == "hash_sample" def sample[T](a: T, hashOpt: Option[Long]): Boolean = { if (method == "drop") false @@ -182,29 +179,36 @@ case class LabelIndex(id: Option[Int], labelId: Int, name: String, seq: Byte, me lazy val sortKeyTypesArray = sortKeyTypes.toArray lazy val propNames = sortKeyTypes.map { labelMeta => labelMeta.name } - val dirJs = dir.map(GraphUtil.fromDirection).getOrElse("both") - val optionsJs = try { options.map(Json.parse).getOrElse(Json.obj()) } catch { case e: Exception => Json.obj() } - lazy val toJson = Json.obj( - "name" -> name, - "propNames" -> sortKeyTypes.map(x => x.name), - "dir" -> dirJs, - "options" -> optionsJs - ) + lazy val toJson = { + val dirJs = dir.map(GraphUtil.fromDirection).getOrElse("both") + val optionsJs = try { options.map(Json.parse).getOrElse(Json.obj()) } catch { case e: Exception => Json.obj() } - lazy val writeOption: Option[WriteOption] = try { + Json.obj( + "name" -> name, + "propNames" -> sortKeyTypes.map(x => x.name), + "dir" -> dirJs, + "options" -> optionsJs + ) + } + + def parseOption(dir: String): Option[WriteOption] = try { options.map { string => - val jsObj = Json.parse(string) - val method = (jsObj \ "method").as[String] + val jsObj = Json.parse(string) \ dir + val method = (jsObj \ "method").asOpt[String].getOrElse("default") val rate = (jsObj \ "rate").asOpt[Double].getOrElse(1.0) val totalModular = (jsObj \ "totalModular").asOpt[Long].getOrElse(100L) - val storeDegree = (jsObj \ "degree").asOpt[Boolean].getOrElse(true) + val storeDegree = (jsObj \ "storeDegree").asOpt[Boolean].getOrElse(true) - WriteOption(method, rate, totalModular, storeDegree) + WriteOption(GraphUtil.directions(dir).toByte, method, rate, totalModular, storeDegree) } } catch { case e: Exception => logger.error(s"Parse failed labelOption: ${this.label}", e) None } + + lazy val inDirOption = parseOption("in") + + lazy val outDirOption = parseOption("out") } http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/20bdf929/s2core/src/main/scala/org/apache/s2graph/core/storage/Storage.scala ---------------------------------------------------------------------- diff --git a/s2core/src/main/scala/org/apache/s2graph/core/storage/Storage.scala b/s2core/src/main/scala/org/apache/s2graph/core/storage/Storage.scala index 59b7518..421bec3 100644 --- a/s2core/src/main/scala/org/apache/s2graph/core/storage/Storage.scala +++ b/s2core/src/main/scala/org/apache/s2graph/core/storage/Storage.scala @@ -1023,16 +1023,12 @@ abstract class Storage[Q, R](val graph: S2Graph, /** EdgeMutate */ def indexedEdgeMutations(edgeMutate: EdgeMutate): Seq[SKeyValue] = { // skip sampling for delete operation - val deleteMutations = edgeMutate.edgesToDelete.flatMap { indexEdge => + val deleteMutations = edgeMutate.edgesToDeleteWithIndexOpt.flatMap { indexEdge => indexEdgeSerializer(indexEdge).toKeyValues.map(_.copy(operation = SKeyValue.Delete, durability = indexEdge.label.durability)) } - val insertMutations = edgeMutate.edgesToInsert.flatMap { indexEdge => - if (indexEdge.isOutEdge) indexEdgeSerializer(indexEdge).toKeyValues.map(_.copy(operation = SKeyValue.Put, durability = indexEdge.label.durability)) - else { - // For InEdge - indexEdgeSerializer(indexEdge).toKeyValues.map(_.copy(operation = SKeyValue.Put, durability = indexEdge.label.durability)) - } + val insertMutations = edgeMutate.edgesToInsertWithIndexOpt.flatMap { indexEdge => + indexEdgeSerializer(indexEdge).toKeyValues.map(_.copy(operation = SKeyValue.Put, durability = indexEdge.label.durability)) } deleteMutations ++ insertMutations @@ -1041,46 +1037,26 @@ abstract class Storage[Q, R](val graph: S2Graph, def snapshotEdgeMutations(edgeMutate: EdgeMutate): Seq[SKeyValue] = edgeMutate.newSnapshotEdge.map(e => snapshotEdgeSerializer(e).toKeyValues.map(_.copy(durability = e.label.durability))).getOrElse(Nil) - def incrementsInOut(edgeMutate: EdgeMutate): (Seq[SKeyValue], Seq[SKeyValue]) = { - - def filterOutDegree(e: IndexEdge): Boolean = - e.labelIndex.writeOption.fold(true)(_.storeDegree) - - (edgeMutate.edgesToDelete.isEmpty, edgeMutate.edgesToInsert.isEmpty) match { + def increments(edgeMutate: EdgeMutate): Seq[SKeyValue] = { + (edgeMutate.edgesToDeleteWithIndexOptForDegree.isEmpty, edgeMutate.edgesToInsertWithIndexOptForDegree.isEmpty) match { case (true, true) => - /** when there is no need to update. shouldUpdate == false */ - (Nil, Nil) - case (true, false) => + Nil + case (true, false) => /** no edges to delete but there is new edges to insert so increase degree by 1 */ - val (inEdges, outEdges) = edgeMutate.edgesToInsert.partition(_.isInEdge) + edgeMutate.edgesToInsertWithIndexOptForDegree.flatMap(buildIncrementsAsync(_)) - val in = inEdges.filter(filterOutDegree).flatMap(buildIncrementsAsync(_)) - val out = outEdges.filter(filterOutDegree).flatMap(buildIncrementsAsync(_)) - - in -> out case (false, true) => - /** no edges to insert but there is old edges to delete so decrease degree by 1 */ - val (inEdges, outEdges) = edgeMutate.edgesToDelete.partition(_.isInEdge) + edgeMutate.edgesToDeleteWithIndexOptForDegree.flatMap(buildIncrementsAsync(_, -1)) - val in = inEdges.filter(filterOutDegree).flatMap(buildIncrementsAsync(_, -1)) - val out = outEdges.filter(filterOutDegree).flatMap(buildIncrementsAsync(_, -1)) - - in -> out case (false, false) => - /** update on existing edges so no change on degree */ - (Nil, Nil) + Nil } } - def increments(edgeMutate: EdgeMutate): Seq[SKeyValue] = { - val (in, out) = incrementsInOut(edgeMutate) - in ++ out - } - /** IndexEdge */ def buildIncrementsAsync(indexedEdge: IndexEdge, amount: Long = 1L): Seq[SKeyValue] = { val newProps = indexedEdge.updatePropsWithTs() http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/20bdf929/s2core/src/test/scala/org/apache/s2graph/core/Integrate/IntegrateCommon.scala ---------------------------------------------------------------------- diff --git a/s2core/src/test/scala/org/apache/s2graph/core/Integrate/IntegrateCommon.scala b/s2core/src/test/scala/org/apache/s2graph/core/Integrate/IntegrateCommon.scala index d280570..b4d6af6 100644 --- a/s2core/src/test/scala/org/apache/s2graph/core/Integrate/IntegrateCommon.scala +++ b/s2core/src/test/scala/org/apache/s2graph/core/Integrate/IntegrateCommon.scala @@ -6,9 +6,9 @@ * 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 @@ -70,7 +70,8 @@ trait IntegrateCommon extends FunSuite with Matchers with BeforeAndAfterAll { val labelNames = Map(testLabelName -> testLabelNameCreate, testLabelName2 -> testLabelName2Create, testLabelNameV1 -> testLabelNameV1Create, - testLabelNameWeak -> testLabelNameWeakCreate) + testLabelNameWeak -> testLabelNameWeakCreate, + testLabelNameLabelIndex -> testLabelNameLabelIndexCreate) for { (labelName, create) <- labelNames @@ -135,7 +136,7 @@ trait IntegrateCommon extends FunSuite with Matchers with BeforeAndAfterAll { logger.info(s2Query.toString) val stepResult = Await.result(graph.getEdges(s2Query), HttpRequestWaitingTime) val result = PostProcess.toJson(Option(s2Query.jsonQuery))(graph, s2Query.queryOption, stepResult) -// val result = Await.result(graph.getEdges(s2Query).(PostProcess.toJson), HttpRequestWaitingTime) + // val result = Await.result(graph.getEdges(s2Query).(PostProcess.toJson), HttpRequestWaitingTime) logger.debug(s"${Json.prettyPrint(result)}") result } @@ -169,6 +170,7 @@ trait IntegrateCommon extends FunSuite with Matchers with BeforeAndAfterAll { val testLabelName2 = "s2graph_label_test_2" val testLabelNameV1 = "s2graph_label_test_v1" val testLabelNameWeak = "s2graph_label_test_weak" + val testLabelNameLabelIndex = "s2graph_label_test_index" val testColumnName = "user_id_test" val testColumnType = "long" val testTgtColumnName = "item_id_test" @@ -176,6 +178,12 @@ trait IntegrateCommon extends FunSuite with Matchers with BeforeAndAfterAll { val newHTableName = "new-htable" val index1 = "idx_1" val index2 = "idx_2" + val idxStoreInDropDegree = "idx_drop_In" + val idxStoreOutDropDegree = "idx_drop_out" + val idxStoreIn = "idx_store_In" + val idxStoreOut = "idx_store_out" + val idxDropInStoreDegree = "idx_drop_in_store_degree" + val idxDropOutStoreDegree = "idx_drop_out_store_degree" val NumOfEachTest = 30 val HttpRequestWaitingTime = Duration("60 seconds") @@ -342,5 +350,52 @@ trait IntegrateCommon extends FunSuite with Matchers with BeforeAndAfterAll { "isDirected": true, "compressionAlgorithm": "gz" }""" + + val testLabelNameLabelIndexCreate = + s""" + { + "label": "$testLabelNameLabelIndex", + "srcServiceName": "$testServiceName", + "srcColumnName": "$testColumnName", + "srcColumnType": "long", + "tgtServiceName": "$testServiceName", + "tgtColumnName": "$testColumnName", + "tgtColumnType": "long", + "indices": [ + {"name": "$index1", "propNames": ["weight", "time", "is_hidden", "is_blocked"]}, + {"name": "$idxStoreInDropDegree", "propNames": ["time"], "options": { "in": {"storeDegree": false }, "out": {"method": "drop", "storeDegree": false }}}, + {"name": "$idxStoreOutDropDegree", "propNames": ["weight"], "options": { "out": {"storeDegree": false}, "in": { "method": "drop", "storeDegree": false }}}, + {"name": "$idxStoreIn", "propNames": ["is_hidden"], "options": { "out": {"method": "drop", "storeDegree": false }}}, + {"name": "$idxStoreOut", "propNames": ["weight", "is_blocked"], "options": { "in": {"method": "drop", "storeDegree": false }, "out": {"method": "normal" }}}, + {"name": "$idxDropInStoreDegree", "propNames": ["is_blocked"], "options": { "in": {"method": "drop" }, "out": {"method": "drop", "storeDegree": false }}}, + {"name": "$idxDropOutStoreDegree", "propNames": ["weight", "is_blocked", "_timestamp"], "options": { "in": {"method": "drop", "storeDegree": false }, "out": {"method": "drop"}}} + ], + "props": [ + { + "name": "time", + "dataType": "long", + "defaultValue": 0 + }, + { + "name": "weight", + "dataType": "long", + "defaultValue": 0 + }, + { + "name": "is_hidden", + "dataType": "boolean", + "defaultValue": false + }, + { + "name": "is_blocked", + "dataType": "boolean", + "defaultValue": false + } + ], + "consistencyLevel": "strong", + "schemaVersion": "v4", + "compressionAlgorithm": "gz", + "hTableName": "$testHTableName" + }""" } } http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/20bdf929/s2core/src/test/scala/org/apache/s2graph/core/Integrate/LabelIndexOptionTest.scala ---------------------------------------------------------------------- diff --git a/s2core/src/test/scala/org/apache/s2graph/core/Integrate/LabelIndexOptionTest.scala b/s2core/src/test/scala/org/apache/s2graph/core/Integrate/LabelIndexOptionTest.scala new file mode 100644 index 0000000..6e213b0 --- /dev/null +++ b/s2core/src/test/scala/org/apache/s2graph/core/Integrate/LabelIndexOptionTest.scala @@ -0,0 +1,144 @@ +/* + * 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.core.Integrate + +import org.apache.s2graph.core._ +import org.scalatest.BeforeAndAfterEach +import play.api.libs.json._ + +class LabelIndexOptionTest extends IntegrateCommon with BeforeAndAfterEach { + + import TestUtil._ + + // called by start test, once + override def initTestData(): Unit = { + super.initTestData() + + val insert = "insert" + val e = "e" + val weight = "weight" + val is_hidden = "is_hidden" + + insertEdgesSync( + toEdge(1, insert, e, 0, 1, testLabelNameLabelIndex), + toEdge(1, insert, e, 0, 2, testLabelNameLabelIndex), + toEdge(1, insert, e, 0, 3, testLabelNameLabelIndex) + ) + } + + def getQuery(ids: Seq[Int], direction: String, indexName: String): Query = + Query( + vertices = ids.map(graph.toVertex(testServiceName, testColumnName, _)), + steps = Vector( + Step(Seq(QueryParam(testLabelNameLabelIndex, direction = direction, indexName = indexName))) + ) + ) + + /** + * "indices": [ + * {"name": "$index1", "propNames": ["weight", "time", "is_hidden", "is_blocked"]}, + * {"name": "$idxStoreInDropDegree", "propNames": ["time"], "options": { "in": {"storeDegree": false }, "out": {"method": "drop", "storeDegree": false }}}, + * {"name": "$idxStoreOutDropDegree", "propNames": ["weight"], "options": { "out": {"storeDegree": false}, "in": { "method": "drop", "storeDegree": false }}}, + * {"name": "$idxStoreIn", "propNames": ["is_hidden"], "options": { "out": {"method": "drop", "storeDegree": false }}}, + * {"name": "$idxStoreOut", "propNames": ["weight", "is_blocked"], "options": { "in": {"method": "drop", "storeDegree": false }, "out": {"method": "normal" }}}, + * {"name": "$idxDropInStoreDegree", "propNames": ["is_blocked"], "options": { "in": {"method": "drop" }, "out": {"method": "drop", "storeDegree": false }}}, + * {"name": "$idxDropOutStoreDegree", "propNames": ["weight", "is_blocked", "_timestamp"], "options": { "in": {"method": "drop", "storeDegree": false }, "out": {"method": "drop"}}} + * ], + **/ + + /** + * index without no options + */ + test("normal index should store in/out direction Edges with Degrees") { + var edges = getEdgesSync(getQuery(Seq(0), "out", index1)) + (edges \ "results").as[Seq[JsValue]].size should be(3) + (edges \\ "_degree").map(_.as[Long]).sum should be(3) + + edges = getEdgesSync(getQuery(Seq(1, 2, 3), "in", index1)) + (edges \ "results").as[Seq[JsValue]].size should be(3) + (edges \\ "_degree").map(_.as[Long]).sum should be(3) + } + + /** + * { "out": {"method": "drop", "storeDegree": false } } + */ + test("storeDegree: store out direction Edge and drop Degree") { + val edges = getEdgesSync(getQuery(Seq(0), "out", idxStoreOutDropDegree)) + (edges \ "results").as[Seq[JsValue]].size should be(3) + (edges \\ "_degree").map(_.as[Long]).sum should be(0) + } + + /** + * { "in": { "method": "drop", "storeDegree": false } } + */ + test("storeDegree: store in direction Edge and drop Degree") { + val edges = getEdgesSync(getQuery(Seq(1, 2, 3), "in", idxStoreInDropDegree)) + (edges \ "results").as[Seq[JsValue]].size should be(3) + (edges \\ "_degree").map(_.as[Long]).sum should be(0) + } + + /** + * { "out": {"method": "drop", "storeDegree": false } } + */ + test("index for in direction should drop out direction edge") { + val edges = getEdgesSync(getQuery(Seq(0), "out", idxStoreIn)) + (edges \ "results").as[Seq[JsValue]].size should be(0) + (edges \\ "_degree").map(_.as[Long]).sum should be(0) + } + + test("index for in direction should store in direction edge") { + val edges = getEdgesSync(getQuery(Seq(1, 2, 3), "in", idxStoreIn)) + (edges \ "results").as[Seq[JsValue]].size should be(3) + (edges \\ "_degree").map(_.as[Long]).sum should be(3) + } + + /** + * { "in": {"method": "drop", "storeDegree": false } } + */ + test("index for out direction should store out direction edge") { + val edges = getEdgesSync(getQuery(Seq(0), "out", idxStoreOut)) + (edges \ "results").as[Seq[JsValue]].size should be(3) + (edges \\ "_degree").map(_.as[Long]).sum should be(3) + } + + test("index for out direction should drop in direction edge") { + val edges = getEdgesSync(getQuery(Seq(1, 2, 3), "in", idxStoreOut)) + (edges \ "results").as[Seq[JsValue]].size should be(0) + (edges \\ "_degree").map(_.as[Long]).sum should be(0) + } + + /** + * { "out": {"method": "drop", "storeDegree": false} } + */ + test("index for in direction should drop in direction edge and store degree") { + val edges = getEdgesSync(getQuery(Seq(1, 2, 3), "in", idxDropInStoreDegree)) + (edges \ "results").as[Seq[JsValue]].size should be(0) + (edges \\ "_degree").map(_.as[Long]).sum should be(3) + } + + /** + * { "in": {"method": "drop", "storeDegree": false }, "out": {"method": "drop"} } + */ + test("index for out direction should drop out direction edge and store degree") { + val edges = getEdgesSync(getQuery(Seq(0), "out", idxDropOutStoreDegree)) + (edges \ "results").as[Seq[JsValue]].size should be(0) + (edges \\ "_degree").map(_.as[Long]).sum should be(3) + } +}
