Scalar and String Methods
This guide explains common scalar and string query patterns. The method reference indexes exact signatures and contracts. Backend support is listed separately in the local backend guide; the local backend supports the complete built-in catalog. See the JSON guide, temporal guide, collections guide, and callback guide for other method families.
Use the methods in a filter
name:lower:startswith("a")
name:contains("Ali", "lic")
name:matches("^A.*e$")
active:and(age:round>=18)
contains() requires at least one argument and checks that every supplied
substring occurs in the receiver. matches() searches for a matching substring;
use ^ and $ when the whole string must match. HFF regex syntax follows RE2
except for the excluded \C byte escape, which is invalid and yields null.
lower() and upper() use locale-independent Unicode simple mappings and do
not normalize strings. len() counts Unicode code points for strings and bytes
for byte values.
For Int, round(), ceil(), and floor() leave the value unchanged. Float
round(), ceil(), and floor() follow the backend's float.rounding
Profile detail. Rounding methods preserve the input numeric subtype.
string:bool() accepts exactly true or false, and
string:number() accepts JSON number spelling; neither trims whitespace.
Invalid patterns or conversions yield null.
Float operations are approximate and follow the backend's float.* Profile
details. For example, the local backend uses Go math.Round, so a stored
Float value of 2.5 rounds to 3; PostgreSQL uses its native Float rounding,
which rounds that midpoint to 2 on the supported platform. Parsing can also
differ: local string:number() accepts underflow such as 1e-400 as zero,
while PostgreSQL's Float profile rejects underflow as null. Query Float
literals are parsed by the core compiler before backend translation. Inspect
the built filter/query profile when these differences affect results.
Regular-expression support
The local backend follows HFF's RE2 contract. PostgreSQL 15 and later supports
a smaller, literal-pattern subset for ~, !~, and string:matches():
name~"^A"
name:matches("^A")
name!~"^A"
The PostgreSQL subset accepts non-NUL ASCII literals and character classes
whose parsed ranges contain only non-NUL ASCII (including \d, \s, and
\w), alternation, concatenation, captures, *, +, ?, repeats whose
explicit bounds are at most 255, and absolute text anchors. Dot, case folding,
multiline anchors, word boundaries, quantified anchors, and non-literal
patterns are rejected by Build when the regex must be translated. Ordinary
negated or Unicode classes usually introduce unsupported ranges. A valid
RE2 pattern outside that subset is a backend error. A malformed pattern yields
null under both ~ and !~; the operator does not turn an invalid pattern
into true. Null or no-value operator operands produce false, while
matches() propagates null or no-value. Prefix ! follows ordinary HFF
boolean semantics on the method result. See the
PostgreSQL regex subset.
The PostgreSQL backend lowers supported literal regex filters on native String columns directly. Callback bodies and nullable value contexts retain their usual semantics. See the PostgreSQL SQL-shape notes for exact examples and limits.
Base64 and byte values
string:frombase64() decodes standard padded base64 into an immutable bytes
value. bytes:base64() encodes bytes with the standard alphabet and required
padding. string:frombase64r() and bytes:base64r() use the same standard
alphabet without padding; empty input represents empty bytes. Each decoder
accepts only its matching form and rejects URL-safe characters, embedded
CR/LF, and non-canonical pad bits. Invalid decoding returns null, and null
inputs propagate as null.
The PostgreSQL backend supports bytes:len(), bytes:base64(), and
bytes:base64r() for native bytea and computed Bytes values, plus both
decoders for supported string receivers. Its SQL profile also supports
schema-known Bytes in JSON/JSONB collections as canonical padded base64 strings.
A base64-looking Any string is not inferred to be Bytes, and a String|Bytes
union remains ambiguous. See the
PostgreSQL bytea guide and
JSON Bytes guide.
Use the r methods when a protocol requires standard base64 without =
padding. For example, the bytes 01 02 03 04 encode as AQIDBA with
base64r() and AQIDBA== with base64(). The two decoders accept only their
matching form; neither method guesses whether padding was omitted.
The PostgreSQL JSON backend also supports the schema-known
Bytes|Int|Float|Null and Bytes|Bool|Int|Float|Null subsets in native JSON
and JSONB with numeric metadata when Int/Float identity is needed. The
category-disjoint Bytes|Bool and Bytes|Bool|Null subsets need no numeric
metadata. This allows Bytes guards, decoded-byte len(), membership,
serialization, and numeric operations on numeric values. Lists containing only
numbers can be sorted with stable Int/Float ties; a list containing Bytes or
another nonnumeric value yields ordinary null. The listed Bool variants are
also recoverable by JSON category. Bytes itself cannot be ordered. See the
JSON union guide and
runnable union example.
Assuming encoded is a string field, these predicates check the decoded byte
length and a decode/encode round trip:
encoded:frombase64:len=4
encoded:frombase64:base64=encoded
The methods charge their input byte length to the local per-match MaxSteps
budget. Run the complete example from the repository root:
go run ./examples/base64
It prints:
"AQIDBA==" -> true
"AQIDBA" -> false
"AQIDBA==\n" -> false
The raw-encoding example checks local behavior and builds the corresponding PostgreSQL predicate without connecting to a database:
go run ./examples/base64-raw
It prints:
"AQIDBA" -> true
"AQIDBA==" -> false
"AQIDBB" -> false
PostgreSQL predicate built: true
Parameter count: 1
See the local backend configuration for limit behavior.
Generic value methods
payload:exists
payload:type="string"
payload:in("open", "pending")
exists() checks that a value is present and non-null, so false, zero, and empty
strings are still present. type() returns the runtime type name, including
"null" for a null value. Comparing that name does not narrow another
expression; use an explicit type guard when a method call needs a narrower type.
in() requires at least one candidate and uses HFF value equality.
Keep capabilities narrow with a type guard
features := capabilities.Standard().Without(
capabilities.Method("list.contains"),
)
e, err := engine.New(
engine.WithSchema(schema.Struct(schema.Any("value"))),
engine.WithCapabilities(features),
)
if err != nil {
return err
}
program, err := e.Compile(ctx, `value:[string]:contains("hff")`)
if err != nil {
return err
}
filter, err := local.Build(ctx, program)
The explicit guard narrows the call to the string signature, so the query does
not require the disabled list.contains capability. A matching string is
preserved; a different runtime type yields internal no-value and therefore a
completed false predicate. Since local list.contains is now implemented, this
example demonstrates type-specific capability selection; the disabled method
still cannot be used by an unguarded list call. Without the guard, a call
through an Any receiver requires every statically applicable method
capability. Capability checks are compile-time checks, while backend support
is checked by Build.
Run the complete example from the repository root:
go run ./examples/scalar-methods
It prints:
Scalar predicate: true
Guarded string: true
Guarded list: false