# Typed queries

Source: https://libtmux.org/en/haskell/latest/guides/query/

> Use runQuery when a predicate needs inspection, typed fields or relationship composition.

Use [`runQuery`](https://libtmux.org/en/haskell/latest/reference/libtmux-query-v-runquery/) when a predicate needs inspection,
typed fields or relationship composition. It reads an existing
[`Snapshot`](https://libtmux.org/en/haskell/latest/reference/libtmux-snapshot-t-snapshot/) and returns ordinary lists inside
`Either QueryError`. Native comprehensions and `filter` remain the unrestricted
path for application logic; see the [capture program](https://libtmux.org/en/haskell/latest/guides/capture/).

This program finds active panes at least 120 cells wide. It compares the
typed selection with a native predicate over the same snapshot, checks
uniqueness, follows session-to-link-to-pane relationships, and inspects the
required fields. The fixture links one window at indices 0 and 4: one
physical pane therefore appears twice in the contextual source.

Create the [private demonstration server](https://github.com/libtmux/libtmux-haskell/blob/663e212622a02aa74a9bdc8ff71e2b45fcb1d4d5/README.md) from the README. From a
[provisioned checkout](https://github.com/libtmux/libtmux-haskell/blob/663e212622a02aa74a9bdc8ff71e2b45fcb1d4d5/.github/CONTRIBUTING.md#toolchain), run:

```console
$ cabal run --verbose=0 query-graph -- \
    "$LIBTMUX_DEMO_DIR/s" \
    "$(command -v tmux)"
```

CI runs the installed program against an owned fixture, asserts output and
empty stderr, and observes the server and initial pane exiting afterward.
The program accepts an existing socket and tmux executable. Every
query construction and selection below is pure except the explicitly named
[`capture`](https://libtmux.org/en/haskell/latest/reference/libtmux-server-v-capture/) and [`fetch`](https://libtmux.org/en/haskell/latest/reference/libtmux-query-v-fetch/).

<!-- example: query-graph -->
```haskell
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}

-- | Query one detached graph, then distinguish physical and contextual matches.
module Main (main) where

import Control.Exception (ExceptionWithContext, rethrowIO)
import Data.Bifunctor (first)
import Data.Function ((&))
import Data.Text qualified as Text
import LibTmux qualified as Tmux
import LibTmux.Query qualified as Q
import LibTmux.Query.Link qualified as Link
import LibTmux.Query.Occurrence qualified as Occurrence
import LibTmux.Query.Pane qualified as Pane
import LibTmux.Query.Session qualified as Session
import System.Environment (getArgs)
import System.Exit (die)
import System.OsPath.Posix qualified as Path

data ApplicationError
  = OperationalFailure (ExceptionWithContext Tmux.TmuxException)
  | InvalidQuery Q.QueryError
  | InvalidSelection Q.CardinalityError
  | UnexpectedResult String

activeWide :: Q.Expr Tmux.Pane
activeWide = Q.allOf [Q.eq Pane.active True, Q.dimensionAtLeast Pane.width 120]

selectedPanes :: Q.Query Tmux.Pane
selectedPanes = Q.panes & Q.matching activeWide

main :: IO ()
main = getArgs >>= \case
  [socketArgument, executableArgument] -> do
    socket <- Path.encodeFS socketArgument
    executable <- Path.encodeFS executableArgument
    let config = (Tmux.serverConfig socket) {Tmux.executable = executable}
    acquired <- Tmux.tryTmux $ Tmux.withServer config $ \server -> do
      snapshot <- Tmux.capture server
      fresh <- Q.fetch server selectedPanes
      pure (snapshot, fresh)
    case first OperationalFailure acquired >>= uncurry report of
      Left (OperationalFailure problem) -> rethrowIO problem
      Left (InvalidQuery problem) -> die ("Query validation failed: " <> show problem)
      Left (InvalidSelection problem) -> die ("Selection failed: " <> show problem)
      Left (UnexpectedResult problem) -> die problem
      Right linesToPrint -> mapM_ putStrLn linesToPrint
  _ -> die "Usage: query-graph SOCKET TMUX_EXECUTABLE"

report :: Tmux.Snapshot -> Either Q.QueryError [Tmux.Pane] -> Either ApplicationError [String]
report snapshot acquired = do
  selected <- first InvalidQuery (Q.runQuery snapshot selectedPanes)
  let native = [pane | pane <- Tmux.panes snapshot, pane.active, Tmux.dimensionValue pane.width >= 120]
  if selected == native then Right () else Left (UnexpectedResult "query and native selection disagree")
  unique <- first InvalidSelection (Q.exactlyOne selected)
  occurrences <- first InvalidQuery $ Q.runQuery snapshot $
    Q.paneOccurrences & Q.matching (Q.related Occurrence.pane activeWide)
  highest <- first InvalidQuery $ Q.runQuery snapshot $
    Q.windowLinks & Q.orderBy [Q.descending Link.index] & Q.limit 1
  link <- first InvalidSelection (Q.exactlyOne highest)
  let containingEditors = Q.sessions & Q.matching (Q.some Session.windowLinks (Q.some Link.panes activeWide))
  sessions <- first InvalidQuery (Q.runQuery snapshot containingEditors)
  fresh <- first InvalidQuery acquired
  Right
    [ "query agrees with native predicate: True"
    , "unique physical pane: " <> Text.unpack (Tmux.renderPaneId (Tmux.paneId unique.ref))
    , "contextual matches: " <> show (length occurrences)
    , "contextual uniqueness: " <> either show (const "unique") (Q.exactlyOne occurrences)
    , "highest link index: " <> show (Tmux.windowIndexValue link.index)
    , "sessions containing a match: " <> show (length sessions)
    , "required fields: " <> show (Q.requirements selectedPanes).fieldNames
    , "required relationships: " <> show (Q.requirements containingEditors).relationshipNames
    , "fresh capture matches: " <> show (length fresh)
    ]
```

Expected output:

<!-- example-output: query-graph -->
```text
query agrees with native predicate: True
unique physical pane: %0
contextual matches: 2
contextual uniqueness: AtLeastTwoMatches
highest link index: 4
sessions containing a match: 1
required fields: ["pane.active","pane.width"]
required relationships: ["session.windowLinks","windowLink.panes"]
fresh capture matches: 1
```

## Choose the source before checking cardinality

[`panes`](https://libtmux.org/en/haskell/latest/reference/libtmux-query-v-panes/) selects unique physical panes.
[`paneOccurrences`](https://libtmux.org/en/haskell/latest/reference/libtmux-query-v-paneoccurrences/) selects their appearances through
window links. Filtering preserves the chosen source's multiplicity.
[`exactlyOne`](<https://libtmux.org/en/haskell/latest/reference/libtmux-query-v-exactlyone/>) rejects no matches and reports [`AtLeastTwoMatches`](<https://libtmux.org/en/haskell/latest/reference/libtmux-query-v-atleasttwomatches/>) at the second
match; it does not count the remaining list. [`atMostOne`](<https://libtmux.org/en/haskell/latest/reference/libtmux-query-v-atmostone/>) also permits absence.
Proving uniqueness may still require traversing every input element.

[`some`](https://libtmux.org/en/haskell/latest/reference/libtmux-query-v-some/) tests one related child against its entire
predicate. Put all required conditions inside that child predicate when one
pane must satisfy them together. Two separate existential predicates may
match different panes. Over a known empty relation, [`some`](<https://libtmux.org/en/haskell/latest/reference/libtmux-query-v-some/>) is false, [`every`](<https://libtmux.org/en/haskell/latest/reference/libtmux-query-v-every/>)
is true and [`none`](<https://libtmux.org/en/haskell/latest/reference/libtmux-query-v-none/>) is true. Missing required lookups remain errors.
[`optionalRelated`](<https://libtmux.org/en/haskell/latest/reference/libtmux-query-v-optionalrelated/>) is false for a known absent target; its negation includes
absence. An unavailable relationship is a validation error, even on an empty
source or in a branch that would otherwise short-circuit.

## Inspect, order and limit

The qualified [field modules](https://libtmux.org/en/haskell/latest/guides/api-documentation/) expose typed tokens from the capture
catalogue. Boolean fields cannot take numeric comparisons, and fields from
different row types cannot be mixed. [`dimensionAtLeast`](<https://libtmux.org/en/haskell/latest/reference/libtmux-query-v-dimensionatleast/>) accepts an unbounded
[`Integer`](<https://hackage-content.haskell.org/package/base-4.22.0.0/docs/Prelude.html#t:Integer>) threshold while observed dimensions retain their checked type.
Text equality and containment are exact, without case folding or Unicode
normalization. Command names, paths and window labels retain their declared
byte-preserving types; there is no implicit text conversion.

[`requirements`](https://libtmux.org/en/haskell/latest/reference/libtmux-query-v-requirements/) reports stable source, field and
relationship names, ordering keys and the final limit. This is dependency
inspection, not serialization or a backend execution plan. Validation checks
all declared requirements before examining rows, including ordering keys,
empty membership predicates and zero limits.

[`matching`](<https://libtmux.org/en/haskell/latest/reference/libtmux-query-v-matching/>) accumulates predicates. [`orderBy`](<https://libtmux.org/en/haskell/latest/reference/libtmux-query-v-orderby/>) replaces the ordering keys and
uses stable sorting; tied rows keep source order. [`limit`](<https://libtmux.org/en/haskell/latest/reference/libtmux-query-v-limit/>) replaces the final
nonnegative [`Natural`](<https://hackage-content.haskell.org/package/base-4.22.0.0/docs/Numeric-Natural.html#t:Natural>) bound. Evaluation always filters, orders and then
limits, regardless of builder order. Every authored description and operand
list must be finite.

## Keep acquisition and failures explicit

[`fetch`](https://libtmux.org/en/haskell/latest/reference/libtmux-query-v-fetch/) captures a new complete snapshot on every
call. It checks compiled evaluator support before acquisition, then validates
version requirements against captured metadata. This implementation performs
no backend filtering. Reuse [`runQuery`](<https://libtmux.org/en/haskell/latest/reference/libtmux-query-v-runquery/>) over one snapshot when several
selections should share observations; see [capture costs](https://libtmux.org/en/haskell/latest/guides/performance/).

The example combines operational exceptions, pure query errors and
cardinality errors in an application error type. [`tryTmux`](<https://libtmux.org/en/haskell/latest/reference/libtmux-server-v-trytmux/>) catches library
exceptions only. Its handler preserves the exception context with `rethrowIO`,
keeping cleanup and capture recovery owners reachable; asynchronous
cancellation propagates. See [recovery](https://libtmux.org/en/haskell/latest/guides/capture/) when an application needs
to inspect and resume those owners.

The report reads detached values after the Server closes. Its equality check
uses one snapshot. The final fresh-capture count demonstrates another
acquisition; two independently changing captures do not prove evaluator
equivalence. Cardinality and reference equality establish observed selection,
not current liveness. Public mutation, query interchange, backend optimization
and event subscriptions remain [unfinished](https://libtmux.org/en/haskell/latest/guides/status/).

For a separate application, copy the [program](https://github.com/libtmux/libtmux-haskell/blob/663e212622a02aa74a9bdc8ff71e2b45fcb1d4d5/examples/query-graph/Main.hs)
and [package file](https://github.com/libtmux/libtmux-haskell/blob/663e212622a02aa74a9bdc8ff71e2b45fcb1d4d5/examples/query-graph/libtmux-query-graph.cabal), then use
its [immutable source dependency](https://github.com/libtmux/libtmux-haskell/blob/663e212622a02aa74a9bdc8ff71e2b45fcb1d4d5/examples/query-graph/cabal.project.vcs)
with the [installation instructions](https://libtmux.org/en/haskell/latest/guides/installation/).
