Skip to main content

Single-target queries

When writing GraphQL queries for transformations, generators, artifacts, and computed attributes, it's critical to use a single-target query pattern to ensure proper tracking by the system.

What is a single-target query?​

A single-target query is a GraphQL query that targets a unique node using a unique attribute or ID. This pattern enables Infrahub to identify exactly which objects are affected by a change, allowing it to selectively trigger the necessary action instead of everything.

Why is this important?​

Without single-target queries, Infrahub cannot determine which specific actions need to be triggered when data changes. This can lead to excessive processing that significantly impacts performance.

Real-world impact: In one production scenario, a proposed change pipeline generated 600 artifact regenerations when only 5 actually required execution. Properly using single-target queries resolved this issue.

Requirements for a valid single-target query​

For a query to be recognized as single-target, it must meet all of these criteria:

  1. Filter on a unique identifier: Use either id or a unique attribute like name__value
  2. Use a required variable: The filter must use a required variable, for example, $name: String!. A literal value is also valid but limits the query to a single fixed object
  3. Use exact match filters: Use singular filters, for example, name__value: $name, not list filters, for example, name__values: $name

Valid single-target query examples​

Using a unique attribute with required variable:

query DeviceConfig($device_name: String!) {
InfraDevice(name__value: $device_name) {
edges {
node {
id
name {
value
}
interfaces {
edges {
node {
name {
value
}
}
}
}
}
}
}
}

Using ID with required variable:

query DeviceById($device_id: String!) {
InfraDevice(ids: [$device_id]) {
edges {
node {
id
name {
value
}
}
}
}
}

Invalid query examples (will cause excessive artifact generation)​

Missing filter (queries all objects):

query AllDevices {
InfraDevice {
edges {
node {
id
name {
value
}
}
}
}
}

Using optional variable:

query DeviceConfig($device_name: String) { # NOT required (no !)
InfraDevice(name__value: $device_name) {
edges {
node {
id
}
}
}
}

Using list filter instead of exact match:

query DeviceConfig($device_name: String!) {
InfraDevice(name__values: $device_name) { # name__values instead of name__value
edges {
node {
id
}
}
}
}

Filtering on non-unique attribute:

query DevicesByRole($role: String!) {
InfraDevice(role__value: $role) { # role is not unique
edges {
node {
id
}
}
}
}

Part of the query is not unique:

query DeviceConfig($device_name: String!) {
InfraDevice(name__value: $device_name) {
edges {
node {
id
name {
value
}
}
}
}
BuiltinTag { # This part is not unique
edges {
node {
name {
value
}
}
}
}
}

Ensuring your query is single-target​

Check a query before you use it in an artifact definition or a Generator definition with infrahubctl graphql query-report:

infrahubctl graphql query-report device_config

The command reads the query named device_config from the queries section of your .infrahub.yml file. Add --online to check the query stored on the Infrahub server instead, and --branch <name> to check it against the schema of a branch. Uniqueness constraints come from the schema, so the result can differ between branches.

The report shows Targets unique nodes: true when every query in the document returns a single object, and Targets unique nodes: false otherwise.

Why every target is regenerated​

When a change in a proposed change or a merge affects an artifact definition or a Generator definition, Infrahub regenerates only the targets whose data changed. When it cannot trace the change back to specific targets, it regenerates every target of the definition, so that no artifact or Generator output is left out of date.

The task log states the reason with one of the following messages, followed by All targets will be processed. After a merge, the same reason is logged, followed by All artifacts will be processed. or All instances will be processed.

Message in the task logWhen it applies
the query does not guarantee unique targetsThe query is not single-target. Fix it with the requirements on this page and check it with infrahubctl graphql query-report.
the query reads <kinds> through a relationship, and a change there cannot be traced back to specific targetsThe change is on one of the listed kinds, and the query reads that kind through a relationship in a way Infrahub cannot follow back to the target. This happens when the path to the kind goes through a fragment (... on <Kind>), or when the query also reads the same kind at its root.
the query reads a human_friendly_id or display_label whose value cannot be traced back to specific targetsThe query reads the human_friendly_id or display_label of an object, or filters on it with hfid or display_label__value, and Infrahub cannot determine from the schema which attributes of which related objects that value is built from, or the query also reads that related kind at its root.

A field the query filters on counts as a field it reads, even when the query does not return it. For example, InfraDevice(site__name__value: $site) reads the site relationship of the device, so moving a device to another site regenerates its artifact.

A change reached through a relationship regenerates only the affected targets unless one of the conditions above applies. For example, when an artifact for each device reads the description of its interfaces, changing the description of one interface regenerates only the artifact of the device that has that interface.

When single-target queries are required​

Single-target queries are required for:

When single-target queries are NOT required​

You do not need single-target queries for:

  • Ad-hoc queries via the GraphQL interface or API
  • Reporting queries that intentionally fetch multiple objects
  • Dashboard queries for UI components
  • Bulk data exports

In these cases, you can freely query multiple objects without unique filters.