sqlite-expertlisted
Install: claude install-skill LeeYudok/doksam-skills
# sqlite-expert
SQLite **엔진 고유의 문제**가 대상이다. 스키마 설계 이론·PostgreSQL 운영은 `db-expert`,
Go 코드 관용구는 `go-expert` 가 맡는다.
SQLite 는 "작은 RDB"가 아니라 **파일 하나가 데이터베이스인 라이브러리**다. 서버가 없다는
사실에서 이 문서의 거의 모든 항목이 파생된다.
## 1. 남의 파일을 읽을 때 — 원본을 바꾸지 않는다
앱이 쓰고 있는 캐시·데이터 파일을 조회하는 작업이 흔하다. **원본을 건드리면 그 앱의 데이터가
깨진다.** 기본은 읽기 전용이다.
```go
dsn := "file:" + path + "?mode=ro&_pragma=busy_timeout(5000)"
```
- **`mode=ro`** — 쓰기를 엔진 수준에서 막는다. 애플리케이션 규율에 기대지 않는다.
- **`busy_timeout`** — 다른 프로세스가 쓰는 중이면 즉시 실패하지 않고 기다린다.
없으면 산발적인 `database is locked` 로 나타난다.
- **URI 파일명에서 `?`·`#` 은 구분자다.** 경로에 들어 있으면 퍼센트 인코딩한다.
경로 문자열을 그냥 이어붙이면 파일을 못 찾는다.
### 곁 파일까지 확인한다
읽기만 해도 `-wal`·`-shm`·`-journal` 이 생기면 **원본 폴더를 오염시킨 것**이다.
WAL 모드 DB 를 열면 실제로 발생할 수 있다. 정말 건드리면 안 되는 파일은
`immutable=1` 을 고려하되, 이건 "파일이 변하지 않는다"는 약속이므로 앱이 쓰는 중이면 쓰지 않는다.
**가장 안전한 순서**: 사본을 떠서 사본을 연다. 그럴 수 없으면 `mode=ro` + 곁 파일 검사.
### 이건 테스트로 고정한다
문서에만 적힌 "읽기 전용"은 다음 리팩터링에서 사라진다. 회귀 테스트로 못 박는다.
```go
// 조회란 조회를 다 돌린 뒤 파일 해시가 같은지, 곁 파일이 안 생겼는지
before := sha256sum(path)
// ... Rooms / Messages / Count / Search ...
if after := sha256sum(path); after != before { t.Error("원본이 바뀌었다") }
for _, s := range []string{"-wal", "-shm", "-journal"} {
if _, err := os.Stat(path + s); !os.IsNotExist(err) { t.Error("곁 파일이 생겼다") }
}
```
쓰기가 실제로 막히는지도 확인한다 — `mode=ro` 로 연 뒤 `DELETE` 가 실패해야 한다.
## 2. 동적 테이블·컬럼명 — 유일한 방어선
테이블명은 **플레이스홀더로 넘길 수 없다.** 스키마가 `Chat_<방ID>` 처럼 데이터에 따라
갈리는 구조면 문자열 조립이 불가피하다. 그러면 검증이 유일한 방어선이 된다.
```go
var roomIDRe = regexp.MustCompile(`^[0-9