Skip to content

Multi-properties

Aerospike Graph Service (AGS) supports Apache TinkerPop’s model for vertex multi-properties (a vertex property with multiple values).

This page explains how to define, query, and update multi-properties using Cardinality.list and Cardinality.set.

Cardinality options

  • list: Stores multiple values per property key. New values are appended without checking for duplicates.
  • set: Stores multiple unique values per property key. Duplicate key-value pairs are not added.

Defining multi-properties

To associate multiple values with a property key on a vertex, use VertexProperty.Cardinality.list or VertexProperty.Cardinality.set.

Using list cardinality

With list cardinality, each call to .property() appends a new value without checking for duplicates:

g.addV("person")
.property(VertexProperty.Cardinality.list, "alias", "Sam")
.property(VertexProperty.Cardinality.list, "alias", "Sammy")
.iterate();

This results in a vertex with two alias values: Sam and Sammy. Each property value is stored independently under the same key.

Using set cardinality

With set cardinality, duplicate key-value pairs are not added:

final Object vertexId = g.addV().id().next();
g.V(vertexId).property("foo", 1).next();
g.V(vertexId).properties("foo").count().next(); // count == 1
g.V(vertexId).property(VertexProperty.Cardinality.set, "foo", 1L).next();
g.V(vertexId).properties("foo").count().next(); // count == 1 (not added, same value)
g.V(vertexId).property(VertexProperty.Cardinality.set, "foo", 1.0).next();
g.V(vertexId).properties("foo").count().next(); // count == 2 (added, different type)

When you add a vertex property with set cardinality:

  • If a property with the same key and value already exists, the existing property is returned without adding a duplicate.
  • If the key-value combination is unique, a new vertex property is added.

For details on type comparison and selection behavior, see Set cardinality behavior.

Querying multi-properties

The .has() step automatically matches any value stored under a multi-property:

g.V().has("alias", "Sam").toList(); // Matches
g.V().has("alias", "Sammy").toList(); // Matches
g.V().has("alias", "Sam").has("alias", "Sammy").toList(); // Still matches

These semantics allow for concise multi-value filtering.

Updating vertex property cardinality

By default, property cardinality is single, meaning any new value overwrites the previous one:

g.addV("person").property("name", "Alice").iterate();
g.V().has("name", "Alice").property("name", "Ally").iterate();
// Result: "name" is now "Ally"

To preserve multiple values, you must explicitly use Cardinality.list or Cardinality.set:

g.addV("person")
.property(VertexProperty.Cardinality.list, "alias", "Alice")
.property(VertexProperty.Cardinality.list, "alias", "Ally")
.iterate();
// Result: both "Alice" and "Ally" stored under "alias"

Each invocation of .property() adds a new value without replacing the existing ones.

Meta-properties

Each vertex property value is stored as a separate object and can include its own meta-properties. These meta-properties are attached to individual property values, not to the vertex itself.

Defining and querying meta-properties

g.addV("person")
// livedIn = "San Francisco", with meta-properties: year = 2023 and season = Spring
.property(VertexProperty.Cardinality.list, "livedIn", "San Francisco", "year", "2023", "season", "Spring")
// livedIn = "Seattle", with meta-properties: year = 2024 and season = Summer
.property(VertexProperty.Cardinality.list, "livedIn", "Seattle", "year", "2024", "season", "Summer")
// livedIn = "San Francisco", with meta-properties: year = 2025 and season = Winter
.property(VertexProperty.Cardinality.list, "livedIn", "San Francisco", "year", "2025", "season", "Winter")
.iterate();

Meta-properties like year or season can be queried using .properties():

g.V().properties("livedIn").has("year", "2024").value().toList(); // Returns "Seattle"
g.V().properties("livedIn").has("season", "Winter").value().toList(); // Returns "San Francisco"

This pattern is useful when you need to differentiate between multiple values of the same property using additional contextual data.

Meta-properties with set cardinality

When you add a vertex property with meta-properties using set cardinality and a matching property already exists:

  • If the meta-property key does not exist on the existing property, the meta-property is added.
  • If the meta-property key already exists, its value is updated.
  • Meta-property updates apply only to the single property that is selected and returned, not to all properties with matching values.
final Object vertexId = g.addV().id().next();
// Add two vertex properties with the same value (1) and meta-properties metafoo and metabar
g.V(vertexId).property(VertexProperty.Cardinality.list, "foo", 1, "metafoo", "foo", "metabar", "bar").next();
g.V(vertexId).property(VertexProperty.Cardinality.list, "foo", 1, "metafoo", "foo", "metabar", "bar").next();
g.V(vertexId).property(VertexProperty.Cardinality.set, "foo", 1, "metafoo", "foo2", "metabaz", "baz").next();
// Result:
// Two vertex properties with {"foo":1}
// One vertex property has meta-properties {"metafoo":"foo", "metabar":"bar"}
// The other vertex property has meta-properties {"metafoo":"foo2", "metabar":"bar", "metabaz":"baz"}

Set cardinality behavior

This section describes advanced behavior specific to set cardinality.

Type compatibility in sets

Integer (int) and Long (long) values are considered comparable when determining uniqueness in a set. This means:

  • Adding a property with key foo and Integer value 123 does not add a new property if a property with key foo and Long value 123 already exists.
  • All other data types are compared strictly by type. For example, Double 1.0 is considered different from Integer 1.
final Object vertexId = g.addV().id().next();
g.V(vertexId).property("foo", 1).next();
g.V(vertexId).properties("foo").count().next(); // count == 1
g.V(vertexId).property(VertexProperty.Cardinality.set, "foo", 1L).next();
g.V(vertexId).properties("foo").count().next(); // count == 1 (int and long are comparable)
g.V(vertexId).property(VertexProperty.Cardinality.set, "foo", 1.0).next();
g.V(vertexId).properties("foo").count().next(); // count == 2 (double is not comparable to int)

Selection hierarchy for set operations

When you add a vertex property using set cardinality, two independent decisions occur:

  1. Uniqueness check: The database determines whether to add a new property based on type-comparable equality (int/long are comparable; other types are not).
  2. Property selection: AGS determines which existing property to return for subsequent traversal steps (such as adding meta-properties).

These decisions use different matching rules. For example, Double 123.00 does not trigger set uniqueness against Integer 123 (different types), but it does satisfy predicate matching for property selection.

AGS uses the following selection hierarchy to determine which property to return:

  1. Type equality: Properties with exact type and value match are selected first.
  2. Predicate matching: Properties that satisfy TinkerPop predicate logic (such as hasValue() or has()) are selected.
  3. Random selection: If multiple properties match at the same priority level, one is selected randomly.
Feedback

Was this page helpful?

What type of feedback are you giving?

What would you like us to know?

+Capture screenshot

Can we reach out to you?