Property Index Rule
Rule name: property_index (on by default, sixth in the pipeline, right after
Source Filter Pushdown).
What it rewrites
Nothing in the plan's shape: it records on a v()/e() start step which
property index the step reads instead of its label buckets.
.profile() shows the choice on the start step:
v().has_label("person").has("title", "CEO") → v(labels: ["person"]).has("title", "CEO") [index: vertex person.title]
v().has_label("person").has("age", P.gt(30)) → v(labels: ["person"]) [index: vertex person.age]
has("age", P.gt(30))
e().has_label("knows").has("weight", P.lt(0.5)) → e(labels: ["knows"]) [index: edge knows.weight]
has("weight", P.lt(0.5))
It looks at
- the
has(key, value)conditions Source Filter Pushdown folded into the start step (scalar values), and - the
has(key, P)steps in the run of pure filters (has,has_label,has_id,has_key,has_not) right after it, withP.eq,P.within,P.gt/gte/lt/lte,P.between,P.insideorP.outside.
A condition is covered when every label folded into the start step has an index on its key. Of the
covered conditions the rule picks the most selective by the indexes' statistics (an equality: about
entries / distinct_values elements per label and value; a range: half the entries).
Why
The start step then reads only the elements whose value lies in the slices of the index, instead
of every element of its labels. The conditions are not removed: the start step still checks its
folded conditions on each candidate and the has(key, P) step stays in the plan, so the result
equals the scan's (only the order of the elements may differ).
When it does not fire
- No index is declared (the rule then costs nothing), or one of the folded labels has none on the key. Only indexes the storage serves count: an index of a type this version does not know (written by a newer version) is kept in the catalog but never read.
- The start step has no folded label (
v().has("title", "CEO")), has ids folded (v("1").has_label(..): the ids are cheaper), or a fusedcount()/group_count(). - The condition is not served:
P.neq,P.without, text predicates, composites, a list or map value, a jpath key,NaN; aP.eqwith a string operand inside a child traversal (a string first names a path label there).
g.with("optimizer.disabled", ["property_index"]) turns it off for one query, for example to
compare the plan with the label scan.