Filters

Overview

Filters are a simple declarative language used to query Haystack data. A filter is a predicate function that takes a dict and returns true/false (match/no match). Some filter constructs require the use of specs in which case the filter must be evaluated in the context of a namespace.

Usage

The simplest filter is just a tag name that matches any record that contains that tag (regardless of its value):

site  // query any record with the "site" tag

To match a tag value you can use any of the equality or comparison operators:

geoPostalCode == "23220"   // equal to
geoPostalCode != "23220"   // not equal to
curVal < 75                // less than
curVal <= 75               // less than or equal to
curVal > 75                // greater than
curVal >= 75               // greater than or equal to

The scalars to compare against are encoded using Zinc encoding (with a couple of exceptions noted below).

You can combine filters using and, or, or not:

not point                 // missing the point tag
site or equip             // has site or equip tag
equip and hvac            // has equip and hvac tag
equip and not ahu         // has equip tag, but not the ahu tag

You can also use a Xeto spec name to match records of that spec; see Specs below:

Meter
ph::Meter
ph.points::OutsideAirTempSensor

Specs

A spec name matches a record that fits the spec:

  • the record's spec tag references the spec or one of its subtypes
  • or the spec is a sugar spec and the record's spec tag references the sugar's nominal anchor or one of its subtypes, and the record has every constraint tag: markers must be present and invariant values must be equal

For example given the sugar spec:

DischargeFanRunCmd: DuctFanRunCmd <sugar> { discharge }

The filter DischargeFanRunCmd matches the same records as:

DuctFanRunCmd and discharge

plus any record whose spec tag asserts DischargeFanRunCmd directly. Combinations without a sugar name are queried the same way, as the anchor and its tags:

DuctFanRunCmd and discharge and coldDeck

Matching uses only the spec tag and constraint tags. It never checks required slots or queries; use validation for that. So a record which asserts a sugar spec but lacks a constraint tag still matches, and DischargeFanRunCmd and not discharge finds such malformed records.

A record without a spec tag matches only Dict: tags alone never make a record match a spec. Constraints added to a sugar spec by mixins do not apply to filters.

Spec names may be qualified such as ph::Meter or unqualified such as Meter. An unqualified name must resolve to exactly one type in the namespace.

Number Comparisons

Equality and comparison operators for Numbers require an exact unit match, otherwise the operator evaluates to false. Even if the units measure the same quantity, no conversion should be performed. Here are examples:

75 == 75        // true
75°F == 75°F    // true
75°F == 75      // false
75°F == 75°C    // false
1min == 60sec   // false

123 != 123      // false
123 != 123°F    // true
123°C != 123°F  // true
1min != 60sec   // true

5 > 4           // true
5 > 4°F         // false
5°C > 4°F       // false

Arrow Operator

You use the -> operator to dereference a tag that has a Ref value. For example, if your equip rec has a siteRef tag that references the site, you can query for equip in a given city such as:

equip and siteRef->geoCity == "Chicago"

The filter above will match if the following hold true:

  • it has equip tag
  • and it has a siteRef tag that is a Ref
  • and what the siteRef tag points to has the geoCity tag
  • and that the site's geoCity tag is equal to "Chicago"

The arrow operator may also be used if the tag value is a nested Dict.

Ref Lists

Haystack allows a list of refs to be used for relationships where a single ref might be used. When a tag value is configured as a list of refs, then a filter will match if any one of the refs in the list match.

Consider an example with a VAV that has an airRef configured as a list of two refs:

// records
id:@ahu1, dis:"AHU-1", ahu, equip
id:@ahu2, dis:"AHU-2", ahu, equip
id:@vav, airRef:[@ahu1, @ahu2], vav, equip

// all of these filters will match the VAV record
airRef == @ahu1
airRef == @ahu2
airRef->dis == "AHU-1"
airRef->dis == "AHU-2"

Note that pathing through a list of refs using "->" will check the path on each ref iteratively.

This technique should never be used for the fundamental containment tags siteRef, equipRef, or spaceRef.

Grammar

The formal grammar of the filter langauge:

 <filter>     :=  <condOr>
 <condOr>     :=  <condAnd> ("or" <condAnd>)*
 <condAnd>    :=  <term> ("and" <term>)*
 <term>       :=  <parens> | <has> | <missing> | <cmp> | <isSpec>
 <parens>     :=  "(" <filter> ")"
 <has>        :=  <path>
 <missing>    :=  "not" <path>
 <cmp>        :=  <path> <cmpOp> <val>
 <cmpOp>      :=  "==" | "!=" | "<" | "<=" | ">" | ">="
 <isSpec>     :=  [<specLib> "::"] <specName>
 <specLib>    :=  <name> ("." <name>)*
 <path>       :=  <name> ("->" <name>)*

 <val>        :=  <bool> | <ref> | <str> | <uri> |
                  <number> | <date> | <time>
 <bool>       := "true" or "false"
 <number>     := same as Zinc (keywords not supported INF, -INF, NaN)
 <ref>        := same as Zinc
 <symbol>     := same as Zinc
 <str>        := same as Zinc
 <uri>        := same as Zinc
 <date>       := same as Zinc
 <time>       := same as Zinc
 <name>       := same as Zinc <id>
 <specName>   := <alphaHi> (<alphaLo> | <alphaHi> | <digit> | '_')*

See Zinc grammar for productions reused from Zinc. Bools are encoded as "true" or "false" (Zinc encodes as "T" or "F").

Haxall 4.0.7 ∙ 27-Oct-2026 09:10 EDT