Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

index lookups by label and property value #172

Merged
merged 2 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion core/src/main/scala/flatgraph/Graph.scala
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,19 @@ class Graph(val schema: Schema, val storagePathMaybe: Option[Path] = None) exten
def allEdges: Iterator[Edge] =
allNodes.flatMap(Accessors.getEdgesOut)

/** Lookup nodes with a given property value (via index). N.b. currently only supported for String properties. Context: MultiDictIndex
/** Lookup nodes with a given label and property value via index. N.b. currently only supported for String properties. Context:
* MultiDictIndex requires the key to be a String and this is using reverse indices, i.e. the lookup is from String -> GNode.
*/
def nodesWithProperty(label: String, propertyName: String, value: String): Iterator[GNode] = {
val nodeKind = schema.getNodeKindByLabel(label)
val propertyKind = schema.getPropertyKindByName(propertyName)
if (nodeKind == Schema.UndefinedKind || propertyKind == Schema.UndefinedKind)
Iterator.empty
else
Accessors.getWithInverseIndex(this, nodeKind, propertyKind, value)
}

/** Lookup nodes with a given property value via index. N.b. currently only supported for String properties. Context: MultiDictIndex
* requires the key to be a String and this is using reverse indices, i.e. the lookup is from String -> GNode.
*/
def nodesWithProperty(propertyName: String, value: String): Iterator[GNode] = {
Expand Down
6 changes: 3 additions & 3 deletions core/src/main/scala/flatgraph/Schema.scala
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ class FreeSchema(
nodePropertyPrototypes: Array[AnyRef],
val edgeLabels: Array[String],
edgePropertyPrototypes: Array[AnyRef],
formalqtys: Array[FormalQtyType.FormalQuantity] = null
formalQuantities: Array[FormalQtyType.FormalQuantity] = null
) extends Schema {
val nodeMap = nodeLabels.zipWithIndex.toMap
val propMap = propertyLabels.zipWithIndex.toMap
Expand Down Expand Up @@ -188,10 +188,10 @@ class FreeSchema(
edgePropertyTypes(edgeKind).allocate(size)

override def getNodePropertyFormalQuantity(nodeKind: Int, propertyKind: Int): FormalQtyType.FormalQuantity =
if (formalqtys == null) getNodePropertyFormalType(nodeKind, propertyKind) match {
if (formalQuantities == null) getNodePropertyFormalType(nodeKind, propertyKind) match {
case FormalQtyType.NothingType => FormalQtyType.QtyNone
case _ => FormalQtyType.QtyMulti
}
else formalqtys(propertyOffsetArrayIndex(nodeKind, propertyKind))
else formalQuantities(propertyOffsetArrayIndex(nodeKind, propertyKind))

}
53 changes: 37 additions & 16 deletions core/src/test/scala/flatgraph/GraphTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -724,12 +724,20 @@ class GraphTests extends AnyWordSpec with Matchers {
}

"node property accessors" in {
val schema = TestSchema.make(
nodeKinds = 1,
edgeKinds = 0,
properties = 3,
val Node0Label = "V0"

// PropertyKeys - these are normally generated by the DomainClassesGenerator based on the schema
val propertySingle = new SinglePropertyKey[String](kind = 0, name = "propertySingle", default = "propertySingleDefault")
val propertyOptional = new OptionalPropertyKey[String](kind = 1, name = "propertyOptional")
val propertyMulti = new MultiPropertyKey[String](kind = 2, name = "propertyMulti")

val schema = new FreeSchema(
nodeLabels = Array(Node0Label),
edgeLabels = Array.empty,
propertyLabels = Array(propertySingle, propertyOptional, propertyMulti).map(_.name),
edgePropertyPrototypes = new Array[AnyRef](0),
nodePropertyPrototypes = Array(new Array[String](0), new Array[String](0), new Array[String](0)),
formalQtys = Array(FormalQtyType.QtyOne, null, FormalQtyType.QtyOption, null, FormalQtyType.QtyMulti)
formalQuantities = Array(FormalQtyType.QtyOne, null, FormalQtyType.QtyOption, null, FormalQtyType.QtyMulti)
)

// just to verify the free schema setup
Expand All @@ -746,11 +754,6 @@ class GraphTests extends AnyWordSpec with Matchers {
dnode.storedRef.get
}

// PropertyKey accessors - these are normally generated by the DomainClassesGenerator based on the schema
val propertySingle = new SinglePropertyKey[String](kind = 0, name = "propertySingle", default = "propertySingleDefault")
val propertyOptional = new OptionalPropertyKey[String](kind = 1, name = "propertyOptional")
val propertyMulti = new MultiPropertyKey[String](kind = 2, name = "propertyMulti")

// test cases where the property is not defined
v0.property(propertySingle) shouldBe "propertySingleDefault"

Expand Down Expand Up @@ -784,7 +787,7 @@ class GraphTests extends AnyWordSpec with Matchers {
debugDump(g) shouldBe
"""#Node numbers (kindId, nnodes) (0: 1), total 1
|Node kind 0. (eid, nEdgesOut, nEdgesIn):
| V0_0 : 0: [a], 1: [b], 2: [c, d]
| V0_0 : propertySingle: [a], propertyOptional: [b], propertyMulti: [c, d]
|""".stripMargin

// test cases where the property is defined
Expand All @@ -800,6 +803,29 @@ class GraphTests extends AnyWordSpec with Matchers {
Accessors.getNodePropertyOptionCompat(v0, 0) shouldBe Some("a")
Accessors.getNodePropertyOptionCompat(v0, 1) shouldBe Some("b")
Accessors.getNodePropertyOptionCompat(v0, 2) shouldBe Some(Seq("c", "d"))

// lookup nodes by property values from indices
g.nodesWithProperty(propertySingle.name, "a").l shouldBe List(v0)
g.nodesWithProperty(propertySingle.name, "A").l shouldBe Nil
g.nodesWithProperty(propertyOptional.name, "b").l shouldBe List(v0)
g.nodesWithProperty(propertyOptional.name, "B").l shouldBe Nil
g.nodesWithProperty(propertyMulti.name, "c").l shouldBe List(v0)
g.nodesWithProperty(propertyMulti.name, "C").l shouldBe Nil
g.nodesWithProperty(propertyMulti.name, "d").l shouldBe List(v0)
g.nodesWithProperty(propertyMulti.name, "D").l shouldBe Nil
g.nodesWithProperty("undefined", "foo").l shouldBe Nil

// lookup nodes by label and property values from indices
g.nodesWithProperty(Node0Label, propertySingle.name, "a").l shouldBe List(v0)
g.nodesWithProperty(Node0Label, propertySingle.name, "A").l shouldBe Nil
g.nodesWithProperty("undefined", propertySingle.name, "A").l shouldBe Nil
g.nodesWithProperty(Node0Label, propertyOptional.name, "b").l shouldBe List(v0)
g.nodesWithProperty(Node0Label, propertyOptional.name, "B").l shouldBe Nil
g.nodesWithProperty("undefined", propertyOptional.name, "B").l shouldBe Nil
g.nodesWithProperty(Node0Label, propertyMulti.name, "c").l shouldBe List(v0)
g.nodesWithProperty(Node0Label, propertyMulti.name, "C").l shouldBe Nil
g.nodesWithProperty("undefined", propertyMulti.name, "C").l shouldBe Nil
g.nodesWithProperty(Node0Label, "undefined", "foo").l shouldBe Nil
}

"report error when trying to use unsupported property types" in {
Expand Down Expand Up @@ -978,11 +1004,6 @@ class GraphTests extends AnyWordSpec with Matchers {
}
}

"trying to lookup unknown properties should be handled gracefully" in {
val g = Graph(schema)
g.nodesWithProperty("undefined", "foo").l shouldBe Nil
}

"opening a broken graph file should be handled gracefully" when {
"file is empty" in {
withTemporaryFile(s"flatgraph-${getClass.getSimpleName}-", "fg") { storagePath =>
Expand Down
Loading