# `Tempo.SQL.SQLite.Document`
[🔗](https://github.com/elixir-tempo/tempo_sql/blob/v0.4.0/lib/tempo/sql/sqlite/document.ex#L1)

The JSON document a SQLite column holds for a Tempo expression.

SQLite has no range type, so `Tempo.Ecto.SQLite.TempoRange` stores an
expression as one `TEXT` value holding a JSON object: the expression
whole, as the PostgreSQL composite types keep it, beside the integer
bounds of the time it covers.

    {"v": 1, "frame": "utc", "lo": 1781514000, "hi": 1781517600,
     "spans": [[1781514000, 1781517600]],
     "meta": {"expression": "2026Y6M15DT9H0MZ/T10H0MZ", ...}}

* `lo` and `hi` are the half-open bounds of the stretch the expression
  covers, from the start of its first span to the end of its last, in
  whole seconds: since the Unix epoch at UTC for a column on the UTC
  frame, and since 1970-01-01T00:00 on the wall clock for a floating
  one. A bound with a fraction of a second is rounded outward, so the
  bounds always cover the expression. An open end is the least or the
  greatest 64-bit integer.

* `spans` are the bounds of each span the expression has, in order. A
  recurrence with no end has its stretch alone.

* `meta` is the document `Tempo.SQL.Meta` writes, from which the
  expression is read back as it was written.

`encode/2` writes the document, `decode/1` reads the expression back,
and `bounds/2` gives the bounds alone, for a query's probe.

# `bound`

```elixir
@type bound() :: integer()
```

A bound in whole seconds on a column's frame.

# `bounds`

```elixir
@spec bounds(Tempo.t(), Tempo.SQL.Conversion.frame()) ::
  {:ok, {bound(), bound()}} | {:error, Exception.t()}
```

The half-open bounds of the stretch an expression covers, in whole
seconds on a frame.

### Arguments

* `expression` is a `t:Tempo.t/0` with a place on the time line.

* `frame` is `:utc` (the default) or `:floating`.

### Returns

* `{:ok, {lower, upper}}`, each an integer, an open end the least or
  the greatest 64-bit integer.

* `{:error, exception}` as `Tempo.SQL.Conversion.interval_to_queryable_range/2`
  returns it.

### Examples

    iex> Tempo.SQL.SQLite.Document.bounds(~o"2026-06-15/2026-06-16", :floating)
    {:ok, {1781481600, 1781654400}}

    iex> Tempo.SQL.SQLite.Document.bounds(~o"2026-06-15T09:00Z/..")
    {:ok, {1781514000, 9223372036854775807}}

# `decode`

```elixir
@spec decode(binary()) :: {:ok, Tempo.t()} | {:error, term()}
```

Reads back the expression a SQLite column's document holds.

### Arguments

* `json` is the document, as `encode/2` writes it.

### Returns

* `{:ok, expression}`, a `t:Tempo.t/0`, as it was written.

* `{:error, reason}` for a document that is not JSON, not of this
  shape, or whose expression cannot be read.

### Examples

    iex> {:ok, json} = Tempo.SQL.SQLite.Document.encode(~o"2026-06", :floating)
    iex> Tempo.SQL.SQLite.Document.decode(json)
    {:ok, ~o"2026-06"}

    iex> {:error, {:invalid_document, _}} = Tempo.SQL.SQLite.Document.decode(~s({"v": 9}))

# `encode`

```elixir
@spec encode(Tempo.t(), Tempo.SQL.Conversion.frame()) ::
  {:ok, binary()} | {:error, Exception.t()}
```

Writes the document a SQLite column holds for an expression.

### Arguments

* `expression` is a `t:Tempo.t/0` with ISO 8601 text and a place on
  the time line.

* `frame` is `:utc` (the default) or `:floating`, the column's frame.

### Returns

* `{:ok, json}`, the document as a binary.

* `{:error, exception}` where `exception` is a
  `t:Tempo.SQL.UnsupportedValueError.t/0` for an expression with no
  place on the time line or of the other frame (the reason `:frame`),
  or the error `Tempo.to_iso8601/1` returns.

### Examples

    iex> {:ok, json} = Tempo.SQL.SQLite.Document.encode(~o"2026-06-15T09:00Z/2026-06-15T10:00Z")
    iex> document = Tempo.SQL.JSON.decode!(json)
    iex> {document["lo"], document["hi"]}
    {1781514000, 1781517600}

    iex> {:error, error} = Tempo.SQL.SQLite.Document.encode(~o"2026-06-15T09:00/2026-06-15T10:00")
    iex> error.reason
    :frame

---

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