# DB 작업 규칙 — 저장 프로시저 금지

## 규칙

**새로 만드는 모든 기능은 저장 프로시저(Stored Procedure) 없이 동작합니다.**

- 표에 직접 `INSERT` / `UPDATE` / `DELETE` 합니다 — **파라미터(바인딩) SQL** 로.
- ORM(Prisma, knex 등)을 쓰는 프로젝트는 ORM 을 그대로 씁니다.
- 이미 프로시저로 돌아가는 프로젝트도, **그 표를 고칠 일이 생기면 함께 옮깁니다.**

## 왜

인자 자리가 한 칸 밀려도 **타입 검사·린트·빌드가 전부 통과합니다.** 운영에서야 드러납니다.

```ts
// 프로시저 — 인자 19개. 가운데 컬럼을 끼워 넣으면 뒤가 전부 밀린다
await sp.call('photos_SP', [0, projectSeq, /* … */, filePath, thumbPath, fileSize, grade /* … */]);
//                                                      ↑ 하나 끼면 fileSize 가 grade 칸으로

// 직접 쓰기 — 이름으로 넣으므로 밀릴 수가 없다
await prisma.photos.create({ data: { projectSeq, filePath, thumbPath, grade } });
```

| 비교 | 저장 프로시저 | 직접 쓰기 |
|---|---|---|
| 컬럼 하나 추가 | 프로시저 재작성 + 모든 호출부 수정 | 한 줄 |
| 인자 대응 | 위치(순서)로 — 사람이 세어야 함 | 이름으로 |
| 오류 발견 시점 | 운영 | 개발(타입·테스트) |

인자가 15개를 넘으면 사람이 세어 가며 맞추게 되고, 그러면 반드시 틀립니다. **프로시저를 감싸는 래퍼를 따로 만들게 됐다면, 그것이 프로시저가 맞지 않는다는 신호입니다.**

## 여러 표를 한 번에 고쳐야 하면 — 트랜잭션

원자성(전부 성공 또는 전부 취소)이 필요하면 프로시저가 아니라 **트랜잭션**으로 묶습니다.

```ts
await prisma.$transaction([
  prisma.orders.update({ where: { id }, data: { status: 'DONE' } }),
  prisma.stock.update({ where: { itemId }, data: { qty: { decrement: n } } }),
]);
```

```js
// knex
await knex.transaction(async (trx) => {
  await trx('orders').where({ id }).update({ status: 'DONE' });
  await trx('stock').where({ itemId }).decrement('qty', n);
});
```

## 문자열을 이어 붙여 SQL 을 만들지 않는다

```js
// X — SQL 주입(injection) 위험
db.query(`SELECT * FROM cows WHERE name = '${name}'`);

// O — 파라미터 바인딩
db.query('SELECT * FROM cows WHERE name = ?', [name]);
```

## 기존 프로시저를 옮기는 순서

한꺼번에 걷어내지 않습니다.

1. **새로 만드는 것**은 처음부터 직접 쓰기로
2. **인자가 많은 것**부터 옮긴다
3. 나머지는 **그 표를 손댈 때** 같이 옮긴다

옮길 때는 **읽기 경로를 먼저** 바꾸고(안전합니다), 쓰기는 **한 표씩** 바꾸며 전후 결과를 비교합니다.

## 문자셋

DB·테이블·접속 문자셋은 `utf8mb4` 로 맞춥니다. 조회 도구도 같은 문자셋을 지정해야 한글이 깨져 보이지 않습니다('문자 인코딩 — UTF-8 필수' 문서 참조).
