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.

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:validate pass; 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: parameters entity (string), field (string, identifier pattern), text (string, 3–1024 chars). Renders one node n with columns: "*" and filters: { <field>: { contains: <text> } }, limit: 50.
  • config/named_queries/list_nodes.yaml: parameter entity. Renders the same node with filters: { id: { gt: 0 } } (traversals need a selective filter; id > 0 matches every row without assuming id numbering) and limit: 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 nodes array (singular node rejected).
  • cf0cae1b replaced {op, value} filters with operator-keyed objects.
  • The Rails caller (graph_explorer.vue executeInstanceMapQuery) still emitted both old shapes and also encoded the operator choice and the id > 0 selectivity 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.

Edited by Aaron Algutifan

Merge request reports

Loading
Loading