Hazelcast IMDG Standard Support has expired. Extended support for version 4.1 ends in April 2024. Extended support for version 4.2 ends in September 2024.

We recommend that you try Hazelcast Platform.

In Hazelcast Platform, we’ve combined the in-memory storage of IMDG with the stream processing power of Jet. Find out more in our Platform documentation.

The following topics are a good place to start:

Querying in Collections and Arrays

Hazelcast allows querying in collections and arrays. Querying in collections and arrays is compatible with all Hazelcast serialization methods, including the Portable serialization.

Let’s have a look at the following data structure expressed in pseudo-code:

class Motorbike {
    Wheel[] wheels;
}

class Wheel {
   String name;

}

In order to query a single element of a collection/array, you can execute the following query:

// it matches all motorbikes where the zero wheel's name is 'front-wheel'
Predicate p = Predicates.equal("wheels[0].name", "front-wheel");
Collection<Motorbike> result = map.values(p);

It is also possible to query a collection/array using the any semantic as shown below:

// it matches all motorbikes where any wheel's name is 'front-wheel'
Predicate p = Predicates.equal("wheels[any].name", "front-wheel");
Collection<Motorbike> result = map.values(p);

The exact same query may be executed using the SQL predicate as shown below:

Predicate p = Predicates.sql("wheels[any].name = 'front-wheel'");
Collection<Motorbike> result = map.values(p);

[] notation applies to both collections and arrays.

Hazelcast requires all elements of a collection to have the same type. Considering and expanding the above example:

  • If you have a wheels collection attribute, all of its elements must be of the Wheel type, subclasses of Wheel are not allowed.

  • Let’s say you have added a seats collection attribute, which is a Seat object. Then all of its elements must of this concrete Seat type.

So, you may have collections of different types in your map. However, each collection’s elements must be of the same concrete type within that collection attribute.

Consider custom attribute extractors if it is impossible or undesirable to reduce the variety of types to a single type. See the Custom Attributes section for information on them.

Indexing in Collections and Arrays

You can also create an index using a query in collections and arrays.

Please note that in order to leverage the index, the attribute name used in the query has to be the same as the one used in the index definition.

Let’s assume you have the following index definition:

  • XML

  • YAML

<hazelcast>
    ...
    <indexes>
        <index type="HASH">
            <attributes>
                <attribute>wheels[any].name</attribute>
            </attributes>
        </index>
    </indexes>
    ...
</hazelcast>
hazelcast:
  ...
  indexes:
    - type: HASH
      attributes:
        - wheels.[any].name

The following query uses the index:

Predicate p = Predicates.equal("wheels[any].name", "front-wheel");

The following query, however, does NOT leverage the index, since it does not use exactly the same attribute name that was used in the index:

Predicates.equal("wheels[0].name", "front-wheel")

In order to use the index in the case mentioned above, you have to create another index, as shown below:

  • XML

  • YAML

<hazelcast>
    ...
    <indexes>
        <index type="HASH">
            <attributes>
                <attribute>wheels[0].name</attribute>
            </attributes>
        </index>
    </indexes>
    ...
</hazelcast>
hazelcast:
  ...
  indexes:
    - type: HASH
      attributes:
        - wheels.[0].name

Corner cases

Handling of corner cases may be a bit different than in a programming language like Java.

Let’s have a look at the following examples in order to understand the differences. To make the analysis simpler, let’s assume that there is only one Motorbike object stored in a Hazelcast Map.

Id Query Data State Extraction Result Match

1

Predicates.equal("wheels[7].name", "front-wheel")

wheels.size() == 1

null

No

2

Predicates.equal("wheels[7].name", null)

wheels.size() == 1

null

Yes

3

Predicates.equal("wheels[0].name", "front-wheel")

wheels[0].name == null

null

No

4

Predicates.equal("wheels[0].name", null)

wheels[0].name == null

null

Yes

5

Predicates.equal("wheels[0].name", "front-wheel")

wheels[0] == null

null

No

6

Predicates.equal("wheels[0].name", null)

wheels[0] == null

null

Yes

7

Predicates.equal("wheels[0].name", "front-wheel")

wheels == null

null

No

8

Predicates.equal("wheels[0].name", null)

wheels == null

null

Yes

As you can see, no NullPointerExceptions or IndexOutOfBoundExceptions are thrown in the extraction process, even though parts of the expression are null.

Looking at examples 4, 6 and 8, we can also easily notice that it is impossible to distinguish which part of the expression was null. If we execute the following query wheels[1].name = null, it may be evaluated to true because:

  • wheels collection/array is null

  • index == 1 is out of bound

  • name attribute of the wheels[1] object is null.

In order to make the query unambiguous, extra conditions would have to be added, e.g., wheels != null AND wheels[1].name = null.