feat(named): add search_nodes named query
What does this MR do and why?
The Orbit UI's map filter bar (pick an entity type, search a property) had its query DSL hardcoded and filter search failed with a schema violation after schema changes. This adds two named queries so the engine owns that query text, the same way expand_neighbors already does for node expansion. The client now sends only the entity/property/search term.
Related Issues
Rails caller: gitlab-org/gitlab branch aalgutifan/orbit-explorer-traversal-nodes (MR to follow; depends on this shipping first).
Testing
cargo test -p named-queries,cargo test -p orbit-server named_quer,mise run named-queries:validatepass; the build script compiles both templates against the DSL schema with their example values.- End to end on GDK with a native GKG build from this branch, driving the real UI: filter searches for all 14 entity types in the filter bar, plus "Show in map" (no search term). The same requests were replayed through
glab orbit remote/ REST.
Performance Analysis
No new query shape: same single-node traversal the frontend was already sending, same limit: 50, now rendered server side.
- This merge request does not introduce any performance regression. If a performance regression is expected, explain why.
Agent context — long-form analysis, file-by-file walkthroughs, profiler output, alternatives considered
Templates
config/named_queries/search_nodes.yaml: parametersentity(string),field(string, identifier pattern),text(string, 3–1024 chars). Renders one nodenwithcolumns: "*"andfilters: { <field>: { contains: <text> } },limit: 50.config/named_queries/list_nodes.yaml: parameterentity. Renders the same node withfilters: { id: { gt: 0 } }(traversals need a selective filter;id > 0matches every row without assuming id numbering) andlimit: 50. Used by "Show in map".
Both declare parameters, so they are excluded from the list_named_queries catalog like expand_neighbors.
Template engine change (crates/named-queries/src/query.rs)
search_nodes needs the property name in a key position (filters.<field>), and $param only substituted values. An object key written as "$param:<name>" is now replaced by the parameter's value; the parameter must be declared, must render to a string, and may not collide with a sibling key. Key usage counts toward the "declared but unused" check. Documented in the crate header, config/schemas/named_query.schema.json, and docs/design-documents/querying/README.md.
Tests: one happy path (render_substitutes_parameter_used_as_object_key) and one combined rejection test (undeclared / non-string / colliding). load_embedded_contains_committed_queries lists both new names.
History that motivated this
- 7007a2b4 unified the DSL on a
nodesarray (singularnoderejected). - cf0cae1b replaced
{op, value}filters with operator-keyed objects. - The Rails caller (
graph_explorer.vueexecuteInstanceMapQuery) still emitted both old shapes and also encoded the operator choice and theid > 0selectivity workaround client side. That is all server side now.
Gotcha
crates/orbit-server/build.rs compiles every named query with its example: values against graph_query.schema.json; a bad example fails the orbit-server build, not the named-queries unit tests. Run cargo check -p orbit-server after editing a template.
Rails side (separate MR)
executeInstanceMapQuery({ entityType, field, text }) calls executeOrbitNamedQuery('search_nodes', { parameters: { entity, field, text } }) when there is a search term, otherwise executeOrbitNamedQuery('list_nodes', { parameters: { entity } }). No DSL, no limit, no filter object in the client. The query editor is no longer populated with raw DSL since none is built client side.