Skip to content

add metadata order by to GET cypher queries#9382

Open
ajtmccarty wants to merge 10 commits into
infp-530-schema-order-by-metadatafrom
infp-530-schema-order-by-metadata-ifc-2650
Open

add metadata order by to GET cypher queries#9382
ajtmccarty wants to merge 10 commits into
infp-530-schema-order-by-metadatafrom
infp-530-schema-order-by-metadata-ifc-2650

Conversation

@ajtmccarty
Copy link
Copy Markdown
Contributor

@ajtmccarty ajtmccarty commented May 29, 2026

Why

Today, schema authors can declare order_by: ["name__value"] on a schema, but they cannot ask for descending order or for ordering by node-level metadata (created_at, updated_at). For example, a DocumentationNote relationship that should always render newest-first on the parent detail page requires per-query ordering arguments at every callsite.

Goal: make order_by: ["node_metadata__created_at__desc"] (and the rest of the new grammar) honored consistently across the three list paths — top-level node list, relationship-peer list, and hierarchy list — including direction suffixes on regular attributes (name__value__desc).

Non-goals: stricter rejection messages for malformed order_by entries (US3, deferred) and US2 verification beyond what falls out of US1's direction propagation.

Closes IFC-2650

What changed

Behavioral

  • New order_by grammar accepted by the schema validator: <attr>__<prop>[__<dir>], <rel>__<attr>__<prop>[__<dir>], node_metadata__<field>[__<dir>] where <field>{created_at, updated_at} and <dir>{asc, desc}. Direction defaults to ascending when no suffix is present. Existing schemas continue to behave as ascending without modification.
  • Direction on <attr> paths is now applied at query time. Affects NodeGetListQuery, RelationshipGetPeerQuery, and NodeGetHierarchyQuery.
  • Schema-level order_by: ["node_metadata__created_at__desc"] is honored across all three list paths and through the matching GraphQL fields (top-level + nested relationship-peer).
  • Precedence change: when a caller passes an explicit query-time order argument (REST/GraphQL/SDK), the schema's order_by is now ignored entirely. The UUID tiebreaker is appended once per query in all cases for deterministic ordering.

Implementation notes

  • validate_order_by routes metadata-prefixed entries (node_metadata__…) through the new parser and leaves attribute/relationship-attribute path entries to validate_schema_path so existing error messages ("X is not a valid attribute of Y", "cannot use X relationship") are preserved.
  • The parser is strict on its own responsibilities (empty entry, unknown metadata field, malformed direction token, direction token in property slot like account__name__desc) and lenient on path existence that requires knowledge of other schemas (remains delegated to validate_schema_path).
  • New shared helper build_subquery_order_metadata(node_alias, metadata_field, …) in core/query/subquery.py produces the inner Cypher for CALL (<alias>) { … } ordering by created_at / updated_at. Handles both default/global-branch shortcuts and the branch-aware traversal. Used by both RelationshipGetPeerQuery and NodeGetHierarchyQuery.
  • RelationshipGetPeerQuery and NodeGetHierarchyQuery each gained a _add_peer_order_by(...) private method; their query_init ordering sections collapse to one helper call plus an unconditional peer.uuid ASC tiebreaker.
  • NodeMetaOrder gained __bool__ so an empty input (order: {node_metadata: {}}) doesn't override the schema's order_by.
  • NodeInheritanceHandler._update_order_by_for_renamed_attributes skips entries that are metadata (via is_metadata_order_by_entry) so renames can't corrupt node_metadata__* references on the inheriting node.

What stayed the same

  • Wire shape: order_by is still list[str] | None.
  • An empty list or an absent order_by behave identically to today — UUID-only sort, unchanged.
  • validate_schema_path error messages for malformed paths are unchanged; the existing test_validate_order_by_error parametrized cases all still pass with their exact expected messages.

Impact & rollout

  • Backward compatibility: existing order_by declarations are unchanged in behavior (ascending by default, same paths accepted). The one observable change is the new replace-not-stack precedence when a caller passes a query-time order argument — previously the schema's entries would still apply as secondary sort, now they're dropped. Surfaces only for callers that set both schema order_by and a query-time order; the customary pattern (set one or the other) is unaffected.
  • Performance: each new direction suffix adds at most one Cypher keyword (ASC/DESC) to the outer ORDER BY. Each new metadata entry on the relationship-peer / hierarchy paths adds one subquery comparable in cost to the existing top-level metadata subquery. UUID tiebreaker is appended once per query on a projected property; no extra traversal.

Checklist

  • Tests added/updated (parser unit, schema-validator component, three query-layer component suites, GraphQL precedence, inheritance unit)
  • Changelog entry added (uv run towncrier create ...)
  • External docs updated (if user-facing or ops-facing change)
  • Internal .md docs updated (internal knowledge and AI code tools knowledge)
  • I have reviewed AI generated content

Summary by cubic

Adds schema-level order_by for node metadata (created_at, updated_at) with ASC/DESC and consistent, directional ordering across node lists, relationship peers, and hierarchies. Query-time order now replaces schema order for top-level lists (REST/GraphQL); when no query-time order is given, schema order is honored (INFP-530).

  • New Features

    • New syntax: node_metadata__created_at[__asc|__desc], node_metadata__updated_at[__asc|__desc].
    • Attribute/relationship order_by supports __asc/__desc; property segment required; non-value properties allowed.
    • Implemented in NodeGetListQuery, RelationshipGetPeerQuery, and NodeGetHierarchyQuery with per-entry direction and a UUID ASC tiebreaker; added build_subquery_order_metadata for branch-aware created_at/updated_at.
    • Parser-backed validation in schema; inheritance keeps metadata entries on rename; GraphQL top-level lists replace schema order when an order argument is provided, and both top-level and relationship-peer lists honor schema order when no query-time order is passed. Tests added across component, unit, and GraphQL suites.
  • Bug Fixes

    • Fixed NodeMetaOrder truthiness so empty query-time node_metadata no longer suppresses schema order_by.

Written for commit f418c00. Summary will update on new commits.

Review in cubic

@github-actions github-actions Bot added group/backend Issue related to the backend (API Server, Git Agent) type/spec A specification for an upcoming change to the project labels May 29, 2026
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 29, 2026

Merging this PR will not alter performance

✅ 12 untouched benchmarks


Comparing infp-530-schema-order-by-metadata-ifc-2650 (f418c00) with infp-530-schema-order-by-metadata (4ffb260)

Open in CodSpeed

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 14 files

Confidence score: 3/5

  • There is a concrete regression risk in backend/infrahub/core/schema/schema_branch.py: validate_order_by() currently validates only metadata entries, so invalid direction suffixes on attribute/relationship sorts may pass through and cause inconsistent query behavior.
  • The change in backend/infrahub/core/query/node.py has medium, somewhat uncertain impact: treating node_metadata as an override even when empty can bypass schema order_by and unexpectedly fall back to UUID sorting.
  • dev/specs/infp-530-order-by-metadata-direction/tasks.md has a duplicate task ID (T019), which is non-runtime but can create planning/traceability confusion; this is minor to fix.
  • Pay close attention to backend/infrahub/core/schema/schema_branch.py, backend/infrahub/core/query/node.py - sorting validation and override behavior could change user-visible ordering semantics.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="backend/infrahub/core/schema/schema_branch.py">

<violation number="1" location="backend/infrahub/core/schema/schema_branch.py:966">
P1: `validate_order_by()` parses only metadata entries, so invalid direction suffixes on attribute/relationship entries are not validated and can slip through.

(Based on your team's feedback about requiring explicit property paths and rejecting ambiguous direction-only forms in `order_by`.) [FEEDBACK_USED]</violation>
</file>

Shadow auto-approve: would not auto-approve because issues were found.

Re-trigger cubic

for order_by_path in node_schema.order_by:
element_name = "order_by"
for order_by_entry in node_schema.order_by:
if is_metadata_order_by_entry(order_by_entry):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: validate_order_by() parses only metadata entries, so invalid direction suffixes on attribute/relationship entries are not validated and can slip through.

(Based on your team's feedback about requiring explicit property paths and rejecting ambiguous direction-only forms in order_by.)

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/infrahub/core/schema/schema_branch.py, line 966:

<comment>`validate_order_by()` parses only metadata entries, so invalid direction suffixes on attribute/relationship entries are not validated and can slip through.

(Based on your team's feedback about requiring explicit property paths and rejecting ambiguous direction-only forms in `order_by`.) </comment>

<file context>
@@ -954,20 +955,26 @@ def validate_uniqueness_constraints(self) -> None:
-            for order_by_path in node_schema.order_by:
-                element_name = "order_by"
+            for order_by_entry in node_schema.order_by:
+                if is_metadata_order_by_entry(order_by_entry):
+                    try:
+                        parse_order_by_entry(entry=order_by_entry, node_schema=node_schema)
</file context>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not think this is right. the method attempts parsing metadata order_by entries within the conditional if is_metadata_order_by_entry(order_by_entry):, but non-metadata entries pass through the existing self.validate_schema_path() call

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback.

Comment thread dev/specs/infp-530-order-by-metadata-direction/tasks.md Outdated
Comment thread backend/infrahub/core/query/node.py Outdated
@ajtmccarty ajtmccarty changed the title Infp 530 schema order by metadata ifc 2650 add metadata order by to GET cypher queries May 29, 2026
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 3 files (changes from recent commits).

Shadow auto-approve: would require human review. This PR modifies core query logic for ordering in multiple critical paths (NodeGetListQuery, RelationshipGetPeerQuery, NodeGetHierarchyQuery), adds new subquery building functions, changes schema parsing and validation for order_by entries, and touches inheritance handling—all of which could affect

Re-trigger cubic

@ajtmccarty ajtmccarty marked this pull request as ready for review May 29, 2026 03:15
@ajtmccarty ajtmccarty requested a review from a team as a code owner May 29, 2026 03:15
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 1 file (changes from recent commits).

Shadow auto-approve: would require human review. This PR introduces significant new ordering functionality across multiple query paths (node list, relationship peer, hierarchy) with new grammar parsing, subquery logic, and a precedence change, all of which impact core business logic and carry a high risk of breakage if bugs exist.

Re-trigger cubic

# Grouped constants for validation/iteration
METADATA_CREATED_FIELDS = (METADATA_CREATED_AT, METADATA_CREATED_BY)
METADATA_UPDATED_FIELDS = (METADATA_UPDATED_AT, METADATA_UPDATED_BY)
# TODO: import from constants and append the "__"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that something missing in the PR, or just a note for later cleanup?

if is_metadata_order_by_entry(entry):
updated_entries.append(entry)
continue
updated_entries.extend(self._get_updated_renamed_attrs_data(attr_data=[entry], renamed_attrs=renamed_attrs))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_get_updated_renamed_attrs_data accepts a list, could we avoid multiple calls to it by going through the for loop then sending a list of entries to _get_updated_renamed_attrs_data?

Copy link
Copy Markdown
Contributor

@polmichel polmichel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review is not finished yet on my side, but still posting intermediate comments instead of keeping them locally.

# Grouped constants for validation/iteration
METADATA_CREATED_FIELDS = (METADATA_CREATED_AT, METADATA_CREATED_BY)
METADATA_UPDATED_FIELDS = (METADATA_UPDATED_AT, METADATA_UPDATED_BY)
# TODO: import from constants and append the "__"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this isn't explicitly stated in the Non-goals section of the PR description, I'm just bringing this line up in case it's not meant to be left in this PR.

],
"relationships": [
{"name": "node_metadata", "peer": "TestCriticality", "cardinality": "one"},
{"name": "node_metadata", "peer": "Criticality", "cardinality": "one"},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure why this has changed since the test seems to pass with or without the change.

Moreover, that is true that the other upper schemas using this relationship have the same peer but it seems that the classic convention is to concatenate the name and the namespace (e.g: TestCriticality).

But I guess this does not really matter.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

group/backend Issue related to the backend (API Server, Git Agent) type/spec A specification for an upcoming change to the project

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants