Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Reference & Query Functions

WCL provides six functions for inspecting the document and its environment: ref() for resolving a block by ID, query() for selecting sets of blocks using a pipeline, has() for checking whether a block has a named attribute or child, has_decorator() for checking whether a block carries a decorator, is_imported() for testing whether a file was imported, and has_schema() for testing whether a schema is declared. These are the bridge between the functional expression layer and the block-oriented document model.

The query engine is covered in depth in the Query Engine chapter. This page focuses on the function call syntax and common patterns.

Reference

FunctionSignatureDescription
refref(id: string) -> blockResolve a block by its ID; error if not found
queryquery(pipeline) -> listExecute a query pipeline; return matching blocks
hashas(block, name: string) -> boolTrue if block has an attribute or child block named name
has_decoratorhas_decorator(block, name: string) -> boolTrue if block carries the decorator @name
is_importedis_imported(path: string) -> boolTrue if the given file path was imported
has_schemahas_schema(name: string) -> boolTrue if a schema with the given name is declared

ref

ref(id) looks up a block by its inline ID and returns it as a value. Attribute access on the returned block uses dot notation.

service web {
  port: 8080
  image: "nginx:latest"
}

config proxy {
  upstream_port: ref("web").port     // 8080
  upstream_image: ref("web").image   // "nginx:latest"
}

If the ID does not exist, ref raises a compile-time error. ref can resolve any block type, not just service.

database primary {
  host: "db.internal"
  port: 5432
}

service api {
  db_host: ref("primary").host
  db_port: ref("primary").port
}

query

query(pipeline) runs a query pipeline against the document and returns a list of matching blocks. The pipeline syntax is described fully in the Query Engine chapter.

Basic form — select all blocks of a given type:

let all_services = query(service)
let all_workers  = query(worker)

With a filter:

let prod_services = query(service | where env == "prod")

With multiple pipeline stages:

let names = query(service | where env == "prod" | select name)

Query results are lists. Use collection and higher-order functions on them:

let ports = map(query(service), s => s.port)
let total_replicas = sum(map(query(service), s => s.replicas))

Use query results in for loops:

for svc in query(service | where has(@public)) {
  ingress ingress-${svc.name} {
    target: svc.name
    port: svc.port
  }
}

has

has(block, name) tests whether a block contains a named attribute or child block. It returns false rather than erroring when the name is absent, making it safe to use in conditionals and filters.

service api {
  port: 8080
  // no "tls" attribute
}

service secure {
  port: 443
  tls: true
}
let has_tls = has(ref("secure"), "tls")     // true
let api_tls = has(ref("api"), "tls")        // false

Use in a filter to find blocks that have a particular attribute:

let tls_services = filter(query(service), s => has(s, "tls"))

Use in a conditional to selectively generate configuration:

for svc in query(service) {
  if has(svc, "port") {
    health_check check-${svc.name} {
      url: "http://${svc.name}:${svc.port}/health"
    }
  }
}

has_decorator

has_decorator(block, name) tests whether a block was annotated with the decorator @name. The name is passed without the @ prefix.

@public
service web {
  port: 80
}

service internal-api {
  port: 8080
}
let is_public = has_decorator(ref("web"), "public")           // true
let api_public = has_decorator(ref("internal-api"), "public") // false

Filter to all publicly exposed services:

let public_services = filter(query(service), s => has_decorator(s, "public"))

Generate ingress rules only for @public blocks:

for svc in query(service) {
  if has_decorator(svc, "public") {
    ingress ingress-${svc.name} {
      host: "${svc.name}.example.com"
      port: svc.port
    }
  }
}

is_imported

is_imported(path) returns true if the given file path was imported into the current document. The path is resolved relative to the project root. This is useful for writing conditional configuration that activates only when an optional module is present.

let has_auth = is_imported("./auth.wcl")

if has_auth {
  config security {
    auth_enabled: true
  }
}

is_imported returns false for paths that were not imported — it never errors.

has_schema

has_schema(name) returns true if a schema with the given name is declared in the document, including schemas brought in via imports. This lets configuration react to what schema definitions are available at evaluation time.

let needs_schema = has_schema("service")

if needs_schema {
  config validation_enabled {
    strict: true
  }
}

Like is_imported, has_schema returns false rather than erroring when the name is not found.

// Guard generated blocks behind schema availability
let has_svc_schema = has_schema("service")
let has_db_schema  = has_schema("database")

Combining ref, query, has, has_decorator, is_imported, and has_schema

These six functions compose with the rest of WCL’s expression language:

// All services that have a port attribute and are marked @public
let exposed = filter(
  query(service | where has(@public)),
  s => has(s, "port")
)

// Generate a summary block
config fleet_summary {
  total_services: len(query(service))
  public_count: count(query(service), s => has_decorator(s, "public"))
  total_replicas: sum(map(query(service), s => s.replicas))
  primary_db_host: ref("primary").host
}

For advanced query pipeline syntax including multi-stage pipelines, recursive queries, and the select and order_by stages, see the Query Engine chapter.