# Naming

Names are the SQL spelling PostgreSQL stores, and casing rewrites a name you did not set. The names section sets a SQL name, a previous name, or a comment. The casing section chooses snake case or camel case.

## Names

<Callout type="info" title="Where this fits">
TypeScript sees the field name. Names are the SQL name, a rename that keeps the old column, the comment, and casing. [Visibility](/docs/schema/visibility) decides who can read or write it.
</Callout>

Insert, update, and `where` use the field name. The SQL name is what the statement quotes.

### SQL name

The SQL name is the field name, unless you replace it. [Casing](/docs/schema/names-and-casing#casing) `"snake"` and `"camel"` rewrite a name you did not set. `publishedAt` is `published_at`. `userID` is `user_id`. Casing does not rewrite a name you set.

| Call | Result |
| --- | --- |
| `.sqlName("headline")` | That column is stored as `headline`. |
| `sqlName: "note"` | The table is stored as `note`. |

#### column

`.sqlName()` replaces one column. Insert still uses the field name.

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

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

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

#### table

`sqlName` on the table replaces the table name. The client property stays `notes`.

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

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

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

<Callout type="error" title="OKM1060">
An empty column or table `sqlName` is refused.
</Callout>

<Callout type="error" title="OKM1122">
A reserved word, a control character, or a name longer than 63 bytes is refused.
</Callout>

<Callout type="error" title="OKM1023">
Two tables that store the same SQL name are refused. The message lists the table names. Two names longer than 63 bytes that shorten to the same stored name and hash suffix are refused the same way.
</Callout>

### Previous name

A migration plans `ALTER TABLE … RENAME TO` or `RENAME COLUMN`. Renaming an identity column also renames the sequence `{table}_{column}_seq`. A table rename does the same for each identity column. Dropping one name and adding another, with no `renamedFrom`, is refused and the fix names the line to add. A previous name that the last snapshot does not contain is refused. Remove `renamedFrom` once the migration that used it has been applied. With no snapshot, that check does not run.

| Call | Result |
| --- | --- |
| `.renamedFrom("headline")` | The previous column SQL name. |
| `renamedFrom: "posts"` | The previous table SQL name. |

#### column

`.renamedFrom()` is the previous column.

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

export const notes = table("notes", {
  id: t.identity(),
  title: t.text().renamedFrom("headline"), // [!code focus]
});
```

#### table

`renamedFrom` on the table is the previous table name.

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

export const notes = table(
  "notes",
  { id: t.identity(), title: t.text() },
  { renamedFrom: "posts" }, // [!code focus]
);
```

<Callout type="error" title="OKM1060">
An empty column or table `renamedFrom` is refused.
</Callout>

<Callout type="error" title="OKM1020">
`Table renamedFrom … is not in the previous snapshot.` A column message names the table and the previous column.
</Callout>

<Callout type="error" title="OKM1530">
`Drop headline and add title could be a rename.` The fix is `.renamedFrom("headline")` on the new column, or `renamedFrom: "posts"` on the new table.
</Callout>

### Comment

`.comment()` and a table `comment` are checked. The catalog has no comment field, so neither is emitted. Omit the call to leave it off. An empty table comment is refused. An empty column comment is accepted.

#### column

`.comment()` is checked on that column and not stored.

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

export const notes = table("notes", {
  id: t.identity(),
  title: t.text().comment("Shown in the list"), // [!code focus]
});
```

#### table

A table `comment` is checked and not stored.

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

export const notes = table(
  "notes",
  { id: t.identity(), title: t.text() },
  { comment: "One row per note" }, // [!code focus]
);
```

<Callout type="error" title="OKM1060">
An empty table `comment` is refused.
</Callout>

<Callout type="error" title="OKM1122">
A comment that contains a control character is refused.
</Callout>

## Casing

`casing` is the SQL spelling of a name you did not set. Insert, update, and `where` still use the field name. The client name stays the name you passed to [`table()`](/docs/schema/tables).

| Value | Result |
| --- | --- |
| omitted | The SQL name is the field name, and the table name. |
| `"snake"` | Words are lowercased and joined with `_`. A run of two or more capitals is one word. A single capital is its own word. |
| `"camel"` | The first word is lowercase and the others start with a capital. |
| any other value | Refused. |

### omitted

Leave `casing` out and Postgres stores the name you wrote.

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

const notes = table("notes", {
  id: t.identity(),
  publishedAt: t.timestamptz().nullable(), // [!code focus]
});

export default schema({ tables: [notes] });
```

### snake

`"snake"` rewrites a table name and a field. A run of capitals is one word only when it has two or more letters, and a single capital is its own word. `userID` is `user_id`, `getURLs` is `get_urls`, and `HTMLParser` is `html_parser`. `iOS` is `i_os`, `IPv6Address` is `i_pv6_address`, and `userId2FA` is `user_id2_fa`. A digit stays with the word before it: `address2` is `address2` and `line1Text` is `line1_text`. An underscore at either end is kept, and a letter outside ASCII passes through unchanged.

[sqlName](/docs/schema/names-and-casing#sql-name) and `renamedFrom` are stored as you wrote them. Indexes, checks, and foreign keys then use that SQL name.

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

const notes = table("publishedNotes", {
  id: t.identity(),
  publishedAt: t.timestamptz().nullable(), // [!code focus]
  headline: t.text().sqlName("title"), // [!code focus]
});

export default schema({
  tables: [notes],
  casing: "snake", // [!code focus]
});
```

<Callout type="info" title="Stored names">
The table is `published_notes`. `publishedAt` is `published_at`. `headline` is `title`, because `sqlName` wins.
</Callout>

### camel

`"camel"` splits a name into the same words and joins them as `camelCase`. `user_id` is `userId`, and `HTMLParser` is `htmlParser`. `iOS` is `iOs`, `IPv6Address` is `iPv6Address`, and `userId2FA` is `userId2Fa`. `publishedNotes` and `publishedAt` stay as written. Use it when the database already spells names that way.

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

const notes = table("publishedNotes", {
  id: t.identity(),
  publishedAt: t.timestamptz().nullable(),
});

export default schema({
  tables: [notes],
  casing: "camel", // [!code focus]
});
```

### pull

`okm pull` keeps each database name as the TypeScript key. `--casing snake` or `--casing camel` is opt-in. The key is the other spelling, so `schema({ casing })` can write the database name back. `user_id` with `--casing snake` is `userId`. `userId` with `--casing camel` is `user_id`.

`.sqlName()` is added when that key does not spell the database name exactly. `a_b_c` with `--casing snake` is `aBC`, and `aBC` spells `a_bc`, so the column is `aBC: t.text().sqlName("a_b_c")`. Each of those names is a line in the pull report.

### What casing covers

Casing spells every name you did not set: tables, columns, and the names derived from them (constraints, indexes, identity sequences, policies, and the `okm_touch` function). Views, enums, domains, functions, and triggers use the name you wrote.

### Changing casing

The stored name changes, so a database that already holds the old spelling gets a rename plan, not a silent rewrite. An unexplained change is OKM1530, and the message names casing as the cause. Keep the stored name with [sqlName](/docs/schema/names-and-casing#sql-name) (`.sqlName()` on a column, `sqlName` on a table), or declare the rename with `.renamedFrom()` or `renamedFrom`.

### Refused

<Callout type="error" title="OKM1023">
Two fields of one table that spell the same SQL name, such as `userID` and `user_id` under `"snake"`, are refused with a message that names both. Name one with `sqlName`.
</Callout>

<Callout type="error" title="OKM1122">
An empty name, or a name over 63 bytes after casing, is refused at `schema()`.
</Callout>

Any value other than `"snake"` or `"camel"` is OKM1060: `schema() casing <value> must be snake or camel.`

| Next | You use it for |
| --- | --- |
| [Traits](/docs/schema/traits#on-one-table) | Columns added to one table or to every table. |

Index: [/llms.txt](/llms.txt)
