Skip to main content

User-defined constants and functions

Overview

To help in writing clear queries, NQE supports user-defined constants and functions, known as declarations. Declarations are defined before the main expression of the query. For example, here is a query that uses one user-defined constant and one user-defined function:

threshold = 10;
hasManySubInterfaces(iface) = length(iface.subinterfaces) > threshold;

foreach device in network.devices
foreach iface in device.interfaces
where hasManySubInterfaces(iface)
select {
device: device.name,
iface: iface.name
}
Note
  • Each declaration is terminated by a semicolon.
  • Each declaration can have zero or more parameters, enclosed in parentheses, and comma-separated. If there are zero parameters, the parentheses can be omitted.
  • Values of any type—not just primitive types—can be passed as arguments to user-defined functions.

Type Annotations

A declaration can be annotated with an NQE type. Type annotations serve to document your intentions about the parameter or return type and can be used by the NQE type checker to provide helpful messages about problems in logic that uses the declaration or in the declaration definition itself.

Return Type Annotations

A declaration can be annotated with the expected type it should return. For example, we can extend the definition of the user-defined declarations above to include the types:

threshold : Number = 100;
hasManySubInterfaces(iface) : Bool = length(iface.subinterfaces) > threshold;

This says that the value of threshold has type Number (i.e. is a number), and that the function hasManySubInterfaces returns a value of type Bool (true or false).

Parameter Type Annotations

Each parameter can also be annotated with the expected type it should receive. For example, we can again extend the definition of hasManySubInterfaces by annotating the iface parameter:

hasManySubInterfaces(iface : Iface) : Bool = length(iface.subinterfaces) > threshold;

This says that the function hasManySubInterfaces takes an iface of type Iface (i.e. is an interface) and returns a value of type Bool (true or false).

Note
  • You must either annotate all parameters with types or none of them.

Type References

The iface parameter above was referred to by its Iface type name. NQE also allows us to write the type annotation with a type that contains just the fields we need to use. For example, instead of using Iface, we can write:

hasManySubInterfaces(iface : { subinterfaces: List<SubInterface> }) : Bool =
length(iface.subinterfaces) > threshold;

This enables the function hasManySubInterfaces to not only accept a parameter of type Iface but also accept a parameter of any kind of record that at least has a field named subinterfaces of type List<SubInterface>. This is useful in scenarios where you want to write a function that is more flexible and can be reused in more than one call.

Possible Types in NQE

The above example shows a few different types, namely Number, Bool, named types (Iface), records, and lists, but many more types are available in NQE. To see all available types in NQE, see NQE Types.