forEachNodeIn

fun CatalogPath.forEachNodeIn(document: Variant, sink: (VariantNode) -> Unit)

Every node this path stands for in document, in document order.

{"items":[{"sku":"a"},{"sku":"b"}]}

CatalogPath.parse("$.items[*].sku").forEachNodeIn(document) { println(it.toJsonSummaryString()) }
// $['items'][0]['sku'] "a"
// $['items'][1]['sku'] "b"

This is the half the engine used to leave to the caller. An index over $.items[*].sku narrows to the documents holding a value; which $.items[N] carried it is a walk of one document, and everybody who indexed an array path was writing that walk by hand — differently each time, and with elementCount throwing on a non-array and stringValue() throwing on a number as the two ways it goes wrong quietly.

A sink, not a Sequence. VariantNode.value is a view over mapped bytes, so a lazy sequence would let one escape the snapshot that maps it — a read of freed memory, or on Windows a mapping that then cannot be unmapped. Views here are valid for the duration of the call, in the words TermExtractor.extract already uses; anything kept must be copied. nodesIn is the form that accepts the copy of the list, and it copies no bytes either.

The set is a superset of the locations an index over this path recorded — never a subset, and that direction is chosen rather than incidental. TermExtractor bounds its walk by IndexOptions.maxDepth and maxChildren, so an index over $.items[*].sku on a document with more elements than maxChildren recorded a term for only the first of them. An expander applying the same bound would return fewer nodes than the index matched, and a caller who narrowed by the index and then expanded would find nothing — a silent wrong answer. Returning more is harmless: a caller re-checks the value it was looking for anyway. So this walk has no depth, breadth or path budget, and it must not acquire one.

A container is a node. $.items stands for the array itself, $ for the whole document. That is SegmentSketchBuilder's reading of what a path means — it records an observation for every container, not only for scalars — and it is RFC 9535's, whose nodelist holds values of any type. It is also what keeps the paragraph above true, since TermExtractor reaches its sink for scalars alone.

A step that does not apply yields nothing and is not an error: an absent field, a field step into an array, an element step into an object. Same rule as Variant.select, and for the same reason — "this document has nothing there" is an answer, not a failure.

Throws

if the document's bytes do not decode.