# 命名策略与表结构

模型不写任何表名/列名也能生成 SQL——因为有一个**命名策略**在背后做规则推导。

## 默认策略：UPPER_SNAKE_CASE

`spi.UPPER_SNAKE_CASE_DATABASE_NAMING_STRATEGY`（构建 `SqlClient` 的默认值）：

| 模型侧 | 推导结果 | 例子 |
|---|---|---|
| 模型名 | 大写蛇形表名 | `BookStore` → `BOOK_STORE` |
| 属性名 | 大写蛇形列名 | `edition` → `EDITION` |
| m2o 外键列 | 属性名 + `_ID` | `store` → `STORE_ID` |
| 中间表 m2m | 两侧模型名拼接 | `Book + Author` → `BOOK_AUTHOR_MAPPING`（配 joinTable 名则显式） |
| 继承子表主键 | 模型缩写 + `_ID` | `PaperBook` → `PB_ID`（可用 `idMapping` 显式覆盖） |

## 自定义策略

需要贴合既有库（非驼峰转写的表/列名）时，实现 `DatabaseNamingStrategy`：

```ts
import { spi } from '@ts-grm/core'

const LOWER_SNAKE: spi.DatabaseNamingStrategy = {
    tableName(entity) { return toLowerSnake(entity.name) },
    columnName(prop) { return toLowerSnake(prop.name) },
    // sequenceName / middleTableName / middleTableThisRefColumnName / middleTableTargetRefColumnName
    // 未覆写的只影响对应场景，也可全部手写
}

const client = newSqlClient(driver, { strategy: { namingStrategy: LOWER_SNAKE, keywordStrategy: ... } })
```

接口还有 `keywordStrategy`（保留字处理）。**若不自定义，就用默认策略 + 下面两种显式覆盖**。

## 显式覆盖：表名与列名

- **模型级**：配置器 `ctx.table({ name: "实际表名" })` 覆盖表名（别名/旧库迁移场景）；
- **关联列**：`joinColumns({ columns: [{ columnName, referencedSubPath }] })`（复合外键列名，见[「复合值 embedded」](../core-concepts/embedded)）；
- **继承主键**：`name: { idMapping: "STORE_ID" }`（继承页已用）；
- **embedded**：成员列名 = 成员名本身（`name.firstName` → `FIRST_NAME`，无路径前缀）。

## 表结构由模型驱动

`createSchema()` / 实体管理器能从模型**一键生成建表 DDL**（含主键、外键、多态 check 约束）——声明即 schema（[「模式创建与实体管理器」](../sql-backend/schema)页）。手工建表时，按上面的命名规则对齐也一样。
