# Database Guidelines

> Database patterns and conventions for this project.

---

## Overview

<!--
Document your project's database conventions here.

Questions to answer:
- What ORM/query library do you use?
- How are migrations managed?
- What are the naming conventions for tables/columns?
- How do you handle transactions?
-->

(To be filled by the team)

---

## Query Patterns

<!-- How should queries be written? Batch operations? -->

(To be filled by the team)

---

## Migrations

<!-- How to create and run migrations -->

(To be filled by the team)

---

## Naming Conventions

<!-- Table names, column names, index names -->

(To be filled by the team)

---

## Common Mistakes

<!-- Database-related mistakes your team has made -->

### Common Mistake: gorm 读 NULL 列到已有值的结构体字段不会清零

**Symptom**:UPDATE 把可空列(如 `*time.Time`)写成 NULL 后,用**同一个结构体变量**再次 `First()` 读回,该字段仍是旧值;而新变量读取正常。断言/返回值出现「幽灵旧值」。

**Cause**:gorm Scan 遇到 NULL 列时跳过赋值(保持字段现状),不会主动置 nil。复用变量(循环内重读、更新后回读同一 &ch)时旧值残留。

**Fix**:更新后回读一律用新变量(`var fresh model.X; db.First(&fresh, id)`)。

**Prevention**:凡「UPDATE 后回读返回」的服务方法,都声明新变量接收;测试中多次 `First` 同一行也各用独立变量。另:map Updates 写 NULL 用 `gorm.Expr("NULL")` 最稳(无类型 `nil` 在部分路径下不生效)。

### Common Mistake: 需要"条件更新多列含 NULL"时的写法

```go
// 正确:零写库条件 + 显式 NULL
db.Model(&model.AiChannel{}).
    Where("id = ? AND (fail_count > 0 OR disabled_until IS NOT NULL)", id).
    Updates(map[string]any{"fail_count": 0, "disabled_until": gorm.Expr("NULL")})
```
