Graph edge field type
A graph_edge field stores one edge of a directed graph — a vocabulary, a taxonomy, an org chart, a road network. Edges indexed this way are traversed rather than searched, by the graph_traversal and concept_similarity queries.
Mapping
PUT /concepts
{
"settings": { "number_of_shards": 1 },
"mappings": {
"properties": {
"subclass_of": { "type": "graph_edge" }
}
}
}
The index must have exactly one shard
That setting is not stylistic. A graph_edge field cannot be created on a multi-shard index, and the mapping is refused outright:
[graph_edge] requires a single-shard index, but this index has 5
Adjacency is derived per segment, and a traversal unions the segments one shard can see. A graph spread over five shards would answer from one fifth of itself and report that as complete — a four-hop route coming back as a single edge. Refusing the mapping is preferred to returning a confidently partial answer.
Graphs are small — roughly 5.5 bytes per edge, so even a continental road network is about 0.7 GB — and one shard is rarely a real constraint. The index you search has no such restriction: a query names the graph with graph_index, and a corpus of any shard count is filtered against the resolved nodes.
Indexing an edge
Each document holds a single edge, as an object with source and target:
POST /concepts/_doc/e1
{
"subclass_of": { "source": 319, "target": 959 }
}
| Parameter | Type | Required/Optional | Description |
|---|---|---|---|
source | Long | Required | The concept the edge leads from. |
target | Long | Required | The concept the edge leads to. |
Both are required. Supplying only one, or any other key inside the object, raises an error.
Node identifiers are longs. Use whatever identifier your vocabulary already publishes rather than minting new ones — a MeSH descriptor number, a Gene Ontology term, an internal category id — so results stay traceable to the source vocabulary.
The edge above is real: 319 is the MeSH descriptor D000319 (Adrenergic beta-Antagonists) and 959 is D000959 (Antihypertensive Agents), so it reads beta blockers are a kind of antihypertensive agent. Taking the numeric part of the published code as the node id keeps every result traceable back to the vocabulary it came from.
Direction matters
For a subsumption hierarchy, index child to parent, so a traversal from a concept walks up toward its ancestors and its closure is the set of things beneath it:
{ "subclass_of": { "source": 8790, "target": 319 } }
Metoprolol (D008790) is a kind of beta blocker, so it is the source and the class is the target. Traversing from 319 then reaches 8790 — and every other drug in the class — which is what makes a query for the class return papers that only ever name the individual drug.
Reversing this silently inverts every query, so it is worth confirming against a known concept before loading a full vocabulary.
Multiple relations in one index
Map several graph_edge fields to hold independent relations over the same nodes:
PUT /ontology
{
"mappings": {
"properties": {
"subclass_of": { "type": "graph_edge" },
"part_of": { "type": "graph_edge" }
}
}
}
A traversal over one relation never follows edges of the other.
What you cannot do with it
A graph_edge field is not searchable in the ordinary sense. Running a term query against one returns an error directing you to a traversal query — the data exists to be walked, not matched.
For how to query these fields, record information content for the corpus-aware similarity measures, and compose traversals with keyword and vector search, see Concept expansion and vocabularies.