Skip to content

Slice and IAdhocMap

ISlice — the domain view

An ISlice represents a single row in a query result: the set of column-value pairs that identify one cell in a pivot table. If a query groups by desk and ccy, a typical slice is {desk=Equity, ccy=USD}.

The interface is intentionally domain-friendly:

// Read a coordinate value
Object desk = slice.getRaw("desk");

// Iterate all coordinates
slice.forEachGroupBy((column, value) -> ...);

// Project to a subset of columns (produces a new, smaller slice)
ISlice projected = slice.retainAll(NavigableSet.of("desk"));

// Convert to a filter — useful when passing a slice as a constraint downstream
ISliceFilter filter = slice.asFilter();   // AND( desk=Equity, ccy=USD )

// The grand-total slice has no coordinates
boolean isGrandTotal = slice.isEmpty();

ISlice is also Comparable, so slices can be used as sorted-map keys directly.

ISliceWithStep

When a slice is passed to a measure's combination logic (e.g. ICombination.combine(...)), it arrives as ISliceWithStep — a thin extension that adds the CubeQueryStep context. This lets the combination function know which measure and which groupBy produced this slice, without the domain code needing to be aware of the query engine internals.


IAdhocMap — the technical contract

Every ISlice is backed by an IAdhocMap, accessible via slice.asAdhocMap(). The two views are interchangeable — IAdhocMap.asSlice() wraps it back — but they serve different audiences:

ISlice IAdhocMap
Audience Measure authors, filter logic Engine internals, optimisation code
Abstraction A named row in a pivot table A specialised SequencedMap<String, Object>
Key methods getRaw, asFilter, retainAll, isEmpty get, sequencedKeySet, retainAll, asSlice

IAdhocMap extends SequencedMap<String, Object> and adds three strong contracts:

1. Immutability

Every IAdhocMap is fully immutable. Mutation methods (put, remove, clear) throw UnsupportedAsImmutableException. Because the content can never change, the hash code is computed once and cached — exactly like String.hashCode(). The map is therefore safe to use as a HashMap key, to share across threads without synchronisation, and to cache without defensive copying.

See Immutability for the broader design rationale.

2. Stable iteration order (SequencedMap)

Keys are always iterated in insertion order. This makes result sets reproducible and allows the engine to compare two maps position-by-position rather than by key lookup, enabling several O(n)-instead-of-O(n log n) comparisons.

3. Perfect-hash key lookup

The key set is backed by SimplePerfectHash: for a fixed set of column names, a minimal perfect hash function maps every key to a unique array slot in one multiply-and-mask operation. There are no collision chains. A get(column) call is a single array access after one arithmetic step.

This matters inside the hot path of the query engine, where the same columns are looked up millions of times per query across thousands of slices.


retainAll and cached projections

Both ISlice and IAdhocMap expose retainAll(columns), which returns a new map/slice keeping only the specified columns. This is called constantly during groupBy projection (e.g. projecting a {desk, ccy, tenor} slice down to {desk} for the parent node).

The engine caches the mapping from (full keyset, retained columns) to the resulting RetainedKeySet in a static ConcurrentHashMap. The first projection for a given pair pays the construction cost; every subsequent call for the same pair is a direct array lookup. In practice, the same handful of projections recur across the entire query, so the cache is almost always warm.


Concrete implementations

Class Backing storage Use
MapOverLists List<?> of values Default row-oriented construction
MapOverIntFunction IntFunction<Object> Dictionary/columnar encoding; lazy value lookup
MaskedAdhocMap Another IAdhocMap + a mask Map Adds temporary columns (e.g. from IColumnGenerator) without copying the original

MaskedAdhocMap is particularly useful for composite tables and many-to-many scenarios where a handful of synthetic columns need to be injected on top of an existing slice without allocating a full new map.


Construction

Slices are normally built by the engine; measure authors receive them already constructed. When writing tests or custom table wrappers, use:

// From a plain Map
ISliceFactory factory = ...; // obtained from the cube/engine context
IAdhocMap map = AdhocMapHelpers.fromMap(factory, Map.of("desk", "Equity", "ccy", "USD"));
ISlice slice = map.asSlice();

// Grand-total slice (empty coordinates)
ISlice grandTotal = SliceHelpers.grandTotal();

Further reading

  • Immutability — why immutable data structures improve correctness beyond thread safety
  • Optimisations — perfect hashing, columnar encoding, and filter arithmetic
  • Concepts — where slices fit in the overall query flow