# Shape

<Callout type="info" title="Where this fits">
A column is one value until you wrap it. An array repeats a scalar, a picklist narrows text, and an enum is a Postgres type. [Names](/docs/define/table/names) are what the database stores.
</Callout>

An array repeats one scalar. A picklist keeps the column as text and checks the value. An enum is its own Postgres type.

### Array

`.array()` wraps a scalar. The SQL type gains one `[]` per rank. There is no second `.array()` call to add a dimension.

An empty array is a value. An element cannot be null. `.nullable()` makes the whole column null, not an element. A picklist or an enum is wrapped after it is declared: `.picklist(values).array()` or `t.enum(name, labels).array()`.

| `dims` | Result |
| --- | --- |
| omitted | Rank 1. `text[]`. |
| `2` to `6` | That many `[]`. `2` is `integer[][]`. |
| outside 1 to 6 | Refused. |

#### one

Omit `dims` for a list.

```ts title="array.ts"
import { connect } from "okmodel/pg/postgresjs";
import { schema, table, t } from "okmodel/pg";

const notes = table("notes", {
  id: t.identity(),
  tags: t.text().array(), // [!code focus]
});
const app = schema({ tables: [notes] });

export async function add(url: string) {
  await using db = connect(url, { schema: app });
  return db.notes.insert({ tags: ["ship"] });
}
```

#### rank

`dims` is the rank. `2` is a matrix.

```ts title="matrix.ts"
import { connect } from "okmodel/pg/postgresjs";
import { schema, table, t } from "okmodel/pg";

const notes = table("notes", {
  id: t.identity(),
  grid: t.integer().array({ dims: 2 }), // [!code focus]
});
const app = schema({ tables: [notes] });

export async function add(url: string) {
  await using db = connect(url, { schema: app });
  return db.notes.insert({ grid: [[1, 2]] });
}
```

<Callout type="error" title="OKM1060">
A rank outside 1 to 6 is refused. Calling `.array()` on a column that is already an array is refused.
</Callout>

<Callout type="error" title="OKM1210">
A value that is not an array is rejected, and so is a null element. A rank that does not match `dims` is rejected when the value is read back.
</Callout>

### Picklist

`.picklist(values)` narrows a string column to that union. A default has to be one of the values. `.picklist(values).default("open")` and `.default("open").picklist(values)` both check it.

| `check` | Result |
| --- | --- |
| omitted | The catalog CHECK is `column IN (...)`, in the order you listed. |
| `false` | The list stays in the type. The catalog has no CHECK. Encode still rejects a value outside the list. |

#### check

The default writes the CHECK.

```ts title="picklist.ts"
import { connect } from "okmodel/pg/postgresjs";
import { schema, table, t } from "okmodel/pg";

const notes = table("notes", {
  id: t.identity(),
  status: t.text().picklist(["open", "done"]).default("open"), // [!code focus]
});
const app = schema({ tables: [notes] });

export async function close(url: string, id: string) {
  await using db = connect(url, { schema: app });
  return db.notes.update({ where: { id }, set: { status: "done" } });
}
```

#### type

`{ check: false }` keeps the union and skips the CHECK.

```ts title="picklist-type.ts"
import { table, t } from "okmodel/pg";

export const notes = table("notes", {
  id: t.identity(),
  status: t.text().picklist(["open", "done"], { check: false }), // [!code focus]
});
```

<Callout type="error" title="OKM1060">
An empty list, an empty value, or a repeated value is refused. A default outside the list is refused. The message names the value.
</Callout>

<Callout type="error" title="OKM1210">
A value outside the list is rejected when it is encoded. The message names the accepted values.
</Callout>

### Enum

`t.enum(name, labels)` creates a Postgres enum. The column type is that name, not `text`. Labels are stored in that order. The TypeScript type is the union of the labels. Two columns may share the name when the label lists match.

A [picklist](#picklist) stays a string column and adds a CHECK. An enum is its own type. Adding a label, with the labels you already have still in that order, plans `ALTER TYPE … ADD VALUE`, and that step runs on its own. Removing a label, or changing their order, replaces the type and rewrites each column that uses it.

```ts title="enum.ts"
import { connect } from "okmodel/pg/postgresjs";
import { schema, table, t } from "okmodel/pg";

const notes = table("notes", {
  id: t.identity(),
  status: t.enum("note_status", ["open", "done"]), // [!code focus]
});
const app = schema({ tables: [notes] });

export async function open(url: string) {
  await using db = connect(url, { schema: app });
  return db.notes.insert({ status: "open" });
}
```

<Callout type="error" title="OKM1060">
An empty name, an empty list, an empty label, or a repeated label is refused.
</Callout>

<Callout type="error" title="OKM1210">
A value outside the labels is rejected when it is encoded. The message names the accepted labels.
</Callout>

<Callout type="error" title="OKM1020">
The same enum name with a different label list is refused. The message names both tables.
</Callout>
