# `Capstan.Protocol.Command`
[🔗](https://github.com/baselabs/capstan/blob/v1.3.1/lib/capstan/protocol/command.ex#L1)

MySQL replication commands and text-resultset decoding.

Builds the request payloads capstan sends over an authenticated
`Capstan.Protocol.Packet` socket — `COM_QUERY`, `COM_REGISTER_SLAVE`, and the
resume-carrying `COM_BINLOG_DUMP_GTID` — and decodes the text resultset a
`COM_QUERY` returns. Framing, the >16 MiB split, and length-encoded primitives all
live in `Capstan.Protocol.Packet`; GTID-set algebra lives in `Capstan.Gtid`.

## The resume encoding (F2 — silent-loss class)

`COM_BINLOG_DUMP_GTID` carries the checkpoint the client has already applied, and
the server resumes streaming from the first transaction *not* in it. The wire
interval end is **EXCLUSIVE** while `Capstan.Gtid` is **INCLUSIVE**: a checkpoint of
`uuid:1-11` (GTIDs 1..11 applied) encodes as `start = 1, end = 12`. The `high + 1`
conversion lives here, in `com_binlog_dump_gtid/2`, and nowhere else. An off-by-one
skips or replays exactly one transaction per interval on every restart — silently,
with no server error — so it is pinned by round-trip byte tests and a live tripwire.

## NULL in a text resultset

A NULL column value is the single byte `0xFB`, which is *not* a length. `Packet`'s
`lenenc_int/1` has no `0xFB` clause by design, so the row decoder must detect NULL
and yield `nil` before delegating to `lenenc_str/1` — otherwise a NULL column would
raise `FunctionClauseError`.

# `row`

```elixir
@type row() :: [binary() | nil]
```

A row of a text resultset; a NULL column is `nil`.

# `com_binlog_dump_gtid`

```elixir
@spec com_binlog_dump_gtid(non_neg_integer(), Capstan.Gtid.t()) :: binary()
```

Builds a `COM_BINLOG_DUMP_GTID` request that resumes from `gtid_set`.

The payload is the command byte, `flags` (BINLOG_THROUGH_GTID), `server_id`, an
empty binlog name (length 0), the start position (4), then the `data_size`-prefixed
GTID-set block: `n_sids`, and per source the 16 raw UUID bytes, its interval count,
and each `{start, end}` pair. **Each INCLUSIVE `high` is encoded as the EXCLUSIVE
wire `high + 1`**. An empty set encodes as `n_sids = 0` and streams everything
the server still retains.

# `com_query`

```elixir
@spec com_query(binary()) :: binary()
```

Builds a `COM_QUERY` request: the command byte `0x03` followed by the SQL text.

# `com_register_slave`

```elixir
@spec com_register_slave(non_neg_integer()) :: binary()
```

Builds a `COM_REGISTER_SLAVE` request identifying this client to the source.

Hostname, user and password are advertised empty and the replication rank and
master-id are zero — capstan positions with `COM_BINLOG_DUMP_GTID`, so only the
`server_id` is meaningful.

# `query`

```elixir
@spec query(Capstan.Protocol.Packet.socket(), binary(), timeout()) ::
  :ok
  | {:ok, [row()]}
  | {:error, {:query_error, non_neg_integer()} | {:transport, term()}}
```

Sends `sql` as a `COM_QUERY` on `socket` and reads the response.

Returns `:ok` for a statement with no resultset (an OK packet — e.g. `SET`),
`{:ok, rows}` for a text resultset (each row a list of column values, `nil` for a
NULL column), and `{:error, {:query_error, code}}` for a server error. A transport
failure on send is `{:error, {:transport, reason}}`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
