Skip to content

support ordering in GraphQL many relationships, respect order of ordering clauses#9397

Draft
ajtmccarty wants to merge 3 commits into
infp-530-schema-order-by-metadata-ifc-2650from
infp-530-schema-order-by-metadata-ifc-2651
Draft

support ordering in GraphQL many relationships, respect order of ordering clauses#9397
ajtmccarty wants to merge 3 commits into
infp-530-schema-order-by-metadata-ifc-2650from
infp-530-schema-order-by-metadata-ifc-2651

Conversation

@ajtmccarty
Copy link
Copy Markdown
Contributor

@ajtmccarty ajtmccarty commented May 30, 2026

Why

Adds tests for various ordering combinations for NodeGetListQuery, RelationshipGetPeerQuery, and NodeGetHierarchyQuery.

Adds support for ordering to many relationships within the graphql resolver. See the new test_graphql_relationship_peer_query_order_replaces_schema_order for an example.

Fixes a bug in NodeGetListQuery that caused metadata ordering to always be applied first regardless of the ordering defined on the schema or in the graphql request.

Non-goals: no changes to the wire shape of order_by, no changes to the parser grammar, no new metadata fields beyond created_at / updated_at.

Closes IFC-2561

What changed

Behavioral:

  • NodeGetListQuery now emits its outer ORDER BY clauses in the schema-declared order, even when entries mix metadata and attributes. Pure-attribute and pure-metadata schemas are unchanged.
  • The GraphQL many-relationship order argument is now honored. When provided, it fully replaces the peer schema's order_by (no stacking).

Implementation:

  • Added a schema_order_position field to FieldAttributeRequirement so ORDER FARs can be sorted back into declared order after both metadata and attribute subqueries have been emitted. The subquery helpers (_add_metadata_subqueries, _add_node_order_attributes) no longer touch self.order_by; a new _emit_schema_order_by sorts ORDER FARs by schema_order_position and emits the outer clauses.
  • Refactored _get_order_requirements into two small helpers (_upsert_metadata_order_requirement, _upsert_attribute_order_requirement) that either promote an existing filter FAR to also act as an ORDER or return a fresh ORDER-only FAR. The nested requirements_map dict is gone; lookup is now a direct iteration over filter_requirements.
  • New FAR.index assignment is len(filter_requirements) + 1 + position. Unique by construction, no threaded counter.
  • Dropped the dead _get_metadata_order_fields helper. The has_any_order check now leans on the new OrderModel.__bool__ (true when disable or node_metadata carries non-default content) plus the existing NodeMetaOrder.__bool__.
  • _query_time_order_overrides_schema in both NodeGetListQuery and RelationshipGetPeerQuery collapses to return bool(self.requested_order).
  • Added a requested_order: OrderModel | None parameter to RelationshipGetPeerQuery, threaded through NodeManager.query_peers, the PeerRelationshipsDataLoader cache key, and the many-relationship resolver. _add_peer_order_by drops the peer schema's order_by when requested_order carries non-default content.

What stayed the same:

  • Wire shape of order_by is unchanged.
  • Parser grammar is unchanged.
  • Existing pure-attribute and pure-metadata schemas produce byte-identical ORDER BY clauses.
  • Relationship and hierarchy query paths already iterated the schema's order_by in declared order, so they needed no fix.

Checklist

  • Tests added/updated
  • 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 ordering to GraphQL many relationships and fixes ORDER BY precedence so mixed metadata/attribute entries follow the schema’s order. Query-time order fully replaces schema order_by, cache keys vary by order, and the order_by wire shape and grammar are unchanged.

  • New Features

    • Many-relationship order is honored end-to-end (threaded through NodeManager.query_peers, PeerRelationshipsDataLoader, and RelationshipGetPeerQuery).
    • Supports created_at/updated_at ASC/DESC; query-time overrides replace schema defaults. Aligns with INFP-530.
  • Bug Fixes

    • Node list and hierarchy queries emit ORDER BY clauses in schema-declared order when mixing metadata and attributes.
    • OrderModel is now frozen and hashable; peer loader cache keys include order to prevent stale results.

Written for commit f0c3523. 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 30, 2026
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 30, 2026

Merging this PR will not alter performance

✅ 12 untouched benchmarks


Comparing infp-530-schema-order-by-metadata-ifc-2651 (f0c3523) with infp-530-schema-order-by-metadata-ifc-2650 (e0da4e0)

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.

2 issues found across 11 files

Confidence score: 3/5

  • There is some merge risk due to a concrete behavior issue in backend/infrahub/core/query/node.py: the metadata ORDER upsert can match non-metadata filters by name, which may misroute node_metadata__created_at ordering to an attribute requirement.
  • The issue above is medium severity (6/10) with fairly strong confidence (8/10), so it could cause incorrect query ordering behavior for users even though it is likely localized.
  • The backend/infrahub/graphql/loaders/peers.py hash truthiness check is low severity (2/10) and mostly a cache-key correctness edge case, so it should not heavily block merge on its own.
  • Pay close attention to backend/infrahub/core/query/node.py, backend/infrahub/graphql/loaders/peers.py - ordering resolution and cache-key hashing semantics may behave unexpectedly in edge cases.
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/graphql/loaders/peers.py">

<violation number="1" location="backend/infrahub/graphql/loaders/peers.py:48">
P3: Hash uses `__bool__` truthiness (`if self.order`) instead of an explicit `is not None` check. `OrderModel(disable=False)` is falsy and would hash identically to `None`, coupling cache-key behavior to `OrderModel.__bool__` semantics. Prefer `if self.order is not None` for robustness.</violation>
</file>

<file name="backend/infrahub/core/query/node.py">

<violation number="1" location="backend/infrahub/core/query/node.py:2220">
P2: Metadata ORDER upsert matches non-metadata filters by name, which can route `node_metadata__created_at` ordering to an attribute requirement instead of metadata.</violation>
</file>

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

Re-trigger cubic

field is fine because the outer ORDER BY references the field name directly.
"""
for req in filter_requirements:
if req.field_name == metadata_field:
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.

P2: Metadata ORDER upsert matches non-metadata filters by name, which can route node_metadata__created_at ordering to an attribute requirement instead of metadata.

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

<comment>Metadata ORDER upsert matches non-metadata filters by name, which can route `node_metadata__created_at` ordering to an attribute requirement instead of metadata.</comment>

<file context>
@@ -2221,105 +2202,127 @@ def _get_filter_requirements(self, start_index: int) -> list[FieldAttributeRequi
+        field is fine because the outer ORDER BY references the field name directly.
+        """
+        for req in filter_requirements:
+            if req.field_name == metadata_field:
+                req.types.append(FieldAttributeRequirementType.ORDER)
+                req.order_direction = direction
</file context>
Suggested change
if req.field_name == metadata_field:
if req.is_metadata and req.field_name == metadata_field:

str(self.branch_agnostic),
str(hash(self.include_metadata)),
# TODO: would it be safer to add a __hash__ method to OrderModel or is order guaranteed in model_dump_json?
self.order.model_dump_json() if self.order else "",
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.

P3: Hash uses __bool__ truthiness (if self.order) instead of an explicit is not None check. OrderModel(disable=False) is falsy and would hash identically to None, coupling cache-key behavior to OrderModel.__bool__ semantics. Prefer if self.order is not None for robustness.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/infrahub/graphql/loaders/peers.py, line 48:

<comment>Hash uses `__bool__` truthiness (`if self.order`) instead of an explicit `is not None` check. `OrderModel(disable=False)` is falsy and would hash identically to `None`, coupling cache-key behavior to `OrderModel.__bool__` semantics. Prefer `if self.order is not None` for robustness.</comment>

<file context>
@@ -42,6 +44,8 @@ def __hash__(self) -> int:
                 str(self.branch_agnostic),
                 str(hash(self.include_metadata)),
+                # TODO: would it be safer to add a __hash__ method to OrderModel or is order guaranteed in model_dump_json?
+                self.order.model_dump_json() if self.order else "",
             ]
         )
</file context>
Suggested change
self.order.model_dump_json() if self.order else "",
self.order.model_dump_json() if self.order is not None else "",

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 not auto-approve. Auto-approval blocked by 2 unresolved issues from previous reviews.

Re-trigger cubic

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.

1 participant