# postgres.copyfmt

> A module of postgres's API.

`postgres.copyfmt` — encode and parse rows for PostgreSQL's COPY text and CSV sub-formats.

`COPY ... TO/FROM STDOUT/STDIN` streams raw bytes over
`PQputCopyData`/`PQgetCopyData` (wired up separately, in `copy.mojo`). This
module is the pure-Mojo codec for what flows through that pipe: `CopyEncoder`
builds row bytes a caller hands to `PQputCopyData` in chunks via `take()`;
`CopyDecoder` (and the free `decode_row`/`split_rows` functions) turn bytes
delivered by `PQgetCopyData` back into typed fields. Neither type touches
libpq — see
https://www.postgresql.org/docs/current/sql-copy.html ("Text Format" and
"CSV Format") for the wire formats implemented here.

A COPY row is a list of fields, each either NULL or a text value; this module
represents that as `List[Optional[String]]` — `None` is NULL, `Optional[String]("")`
is a real empty string, which text format and (quoted) CSV can both express
distinctly from NULL.

## Structs

### `CopyEncoder`

```mojo
struct CopyEncoder
```

Builds COPY row bytes in `format`, ready for `PQputCopyData` in chunks via `take()`.

Field values are escaped/quoted as they are appended, so the buffer is
always valid COPY data up to the last completed row — `size()`/`take()`
can be called mid-row, though a caller normally flushes only after
`end_row()`.

Implements: `Movable`

Fields:

- `format: Int`
- `delimiter: String`
- `null_string: String`

#### `__init__`

```mojo
def __init__(out self, format: Int = Int(0), delimiter: Optional[String] = None, null: Optional[String] = None)
```

Configure the encoder.

Args:

- `format`: `COPY_TEXT` or `COPY_CSV`.
- `delimiter`: One-byte field separator. Defaults to tab (text) or
    comma (CSV).
- `null`: The NULL marker. Defaults to `\N` (text) or the empty
    string (CSV) — PostgreSQL's own defaults for each sub-format.

Raises: Error: If `format` is neither sub-format, or `delimiter` is not
exactly one byte.

#### `field`

```mojo
def field(mut self, v: StringSpan)
```

Append a non-NULL field, escaping/quoting it for `self.format`.

#### `null`

```mojo
def null(mut self)
```

Append a NULL field (the raw, unescaped null marker).

#### `end_row`

```mojo
def end_row(mut self)
```

Terminate the current row with `\n` and reset for the next one.

#### `row`

```mojo
def row(mut self, values: List[Optional[String]])
```

Convenience: append a whole row (`None` entries as NULL) and terminate it.

#### `size`

```mojo
def size(self) -> Int
```

Bytes currently buffered.

#### `take`

```mojo
def take(mut self) -> List[UInt8]
```

Hand back the buffered bytes and reset the buffer, so a caller can flush in chunks without losing anything already appended.

#### `bytes`

```mojo
def bytes(ref self) -> Span[UInt8, origin_of(self._buf)]
```

A read-only view of the currently buffered bytes, without taking them.

### `CopyDecoder`

```mojo
struct CopyDecoder
```

`decode_row` bound to a fixed format/delimiter/null configuration, for callers decoding many rows of the same COPY stream.

Implements: `Movable`

Fields:

- `format: Int`
- `delimiter: String`
- `null_string: String`

#### `__init__`

```mojo
def __init__(out self, format: Int = Int(0), delimiter: Optional[String] = None, null: Optional[String] = None)
```

#### `decode_row`

```mojo
def decode_row(self, line: StringSpan) -> List[Optional[String]]
```

## Functions

### `decode_row`

```mojo
def decode_row(line: StringSpan, format: Int, delimiter: String, null: String) -> List[Optional[String]]
```

Parse one line of `COPY ... TO STDOUT` output into its fields.

`line` must not include the trailing newline (`split_rows` strips it).

### `split_rows`

```mojo
def split_rows(buffer: Span[UInt8]) -> Tuple[List[String], String]
```

Split a buffer of concatenated COPY row bytes on `\n`, returning the complete lines (newline stripped) and any trailing partial line still waiting for more bytes.

This is a byte-oriented batching helper for the common case where
`PQgetCopyData` results have been concatenated before parsing — it does
not track CSV quote state, so a row containing a *raw* embedded newline
(a CSV field with a real newline inside quotes) is indistinguishable here
from a row boundary. `PQgetCopyData` itself always delivers one complete
row per call regardless of embedded newlines; feed `decode_row` from
those calls directly if embedded raw newlines are possible.

## Aliases

### `COPY_TEXT`

```mojo
comptime COPY_TEXT = 0
```

PostgreSQL's default COPY sub-format: tab-delimited, `\N` for NULL, backslash escapes.

### `COPY_CSV`

```mojo
comptime COPY_CSV = 1
```

`COPY ... (FORMAT CSV)`: comma-delimited, double-quote quoting, empty unquoted string for NULL.
