메인 콘텐츠로 건너뛰기

VillageSQL is a drop-in replacement for MySQL with extensions.

All examples on this page work on VillageSQL. Install Now →
sqlc는 SQL에서 직접 Go 구조체와 쿼리 메서드를 생성하므로, 애플리케이션 코드는 런타임에 쿼리를 조립하는 대신 평범한 Go 함수를 호출합니다. 코드와 작성한 SQL 사이에 ORM 계층이 없습니다. 이 가이드는 작은 posts 테이블을 만들고, MySQL용으로 sqlc로 코드를 생성한 뒤, 실행 중인 서버에서 실행합니다.

sqlc 설치

Homebrew로 sqlc를 설치하거나, Go 도구로 관리하고 싶다면 go install로 설치하세요:
버전을 확인합니다:

프로젝트 구성

Go 모듈과 세 개의 파일을 만듭니다: 스키마, 쿼리 파일, sqlc 구성 파일.
schema.sql은 sqlc가 타입을 생성할 테이블을 정의합니다. body에는 NOT NULL 제약이 없으며, 이 점은 나중에 중요해집니다:

어노테이션이 있는 쿼리 작성

sqlc는 평범한 SQL 파일을 읽고, 각 문장 위의 -- name: 주석을 생성된 Go 메서드로 바꿉니다. 쿼리 이름 뒤의 단어가 반환 형태를 정합니다: 단일 행은 :one, 슬라이스는 :many, 반환값 없음은 :exec, 원시 sql.Result는 :execresult, 영향받은 행 수는 :execrows. MySQL의 INSERT 문은 RETURNING을 지원하지 않으므로, 새 행의 ID를 돌려받으려면 전용 어노테이션이 필요합니다. :execlastid는 sql.Result.LastInsertId()를 대신 호출하여 그 값을 직접 반환합니다:

sqlc.yaml 구성

sqlc.yaml은 sqlc에 스키마와 쿼리 파일의 위치를 알려주고 MySQL 엔진을 설정합니다. 경로는 sqlc를 실행하는 디렉토리가 아니라 sqlc.yaml의 위치를 기준으로 해석됩니다:
package와 out은 생성되는 Go 패키지의 이름과 그것이 기록될 디렉토리를 지정합니다. sql_package: "database/sql"은 sqlc에 표준 라이브러리의 database/sql에 맞춰 코드를 생성하라고 지시하며, go-sql-driver/mysql이 구현하는 것이 바로 이것입니다.

코드 생성

이 명령은 posts/에 세 개의 파일을 생성합니다: db.go(Queries 구조체와 그 생성자), models.go(Post 구조체), query.sql.go(어노테이션이 있는 쿼리마다 하나의 메서드). 널 허용 열 body가 나타나는 곳은 models.go입니다:

널 허용 열은 포인터가 아니라 sql.NullString을 생성

body TEXT에 NOT NULL 제약이 없기 때문에, sqlc는 평범한 string이나 *string이 아니라 sql.NullString을 생성했습니다. 같은 규칙이 INSERT 문의 매개변수 목록에서 생성된 CreatePostParams에도 적용됩니다:
이것은 모든 삽입에서, 값을 전달할 때조차 sql.NullString을 명시적으로 구성해야 한다는 뜻입니다:
본문 없이 행을 삽입하려면 필드를 빈 문자열로 두는 대신 Valid: false로 설정하세요. 빈 sql.NullString{} 제로 값은 이미 Valid: false이므로, sql.NullString{Valid: false}라고 명시적으로 쓰는 것과 동일합니다:
널 허용 열을 다시 읽을 때는 .String을 신뢰하기 전에 .Valid를 확인해야 합니다:
열이 NULL을 허용해야 한다면, 그것을 의도적인 스키마 결정으로 만드세요. schema.sql의 body에 NOT NULL을 추가하고 sqlc generate를 다시 실행하면 생성된 필드가 나타나는 모든 곳에서 sql.NullString이 평범한 string으로 바뀌며, 모든 호출 지점에서 .Valid 확인이 사라집니다.

연결과 마이그레이션 실행

데이터베이스와, 그 데이터베이스 하나로 범위가 한정된 실제 비밀번호를 가진 애플리케이션 사용자를 만듭니다:
애플리케이션을 실행하기 전에 mysql 클라이언트로 schema.sql을 적용합니다:
드라이버를 추가하고 parseTime=true로 TCP를 통해 연결합니다. 이 옵션은 MySQL의 TIMESTAMP와 DATETIME 열을 []byte가 아니라 Go의 time.Time으로 스캔하는 데 필요합니다:

CreatePost는 int64를 반환하고 GetPost는 uint64를 받음

id BIGINT UNSIGNED는 sqlc가 열로 생성하는 모든 곳에서 uint64로 매핑되며, GetPost의 id 매개변수도 마찬가지입니다. 그러나 :execlastid로 생성된 CreatePost는 평범한 int64를 반환합니다. 이것은 sql.Result.LastInsertId()에서 오는데, 표준 라이브러리는 열의 실제 부호 여부와 관계없이 이것을 int64로 정의합니다. 방금 생성된 ID를 GetPost에 전달하려면 명시적 변환이 필요합니다:
실행 중인 서버에서 전체 프로그램을 처음부터 끝까지 실행하면:
프로그램 자체의 출력과 별개로 데이터베이스에서 직접 확인하면:

연결 풀 조정

database/sql은 sql.Open 아래에서 자체 연결 풀을 관리하며, sqlc가 생성한 Queries 구조체는 전달한 *sql.DB가 이미 가진 풀을 그대로 사용합니다. MySQL 백엔드에는 특히 두 가지 설정이 중요합니다:
SetConnMaxLifetime은 MySQL의 wait_timeout보다 낮게 설정해야 하며, 이 서버에서 wait_timeout의 기본값은 28800초(8시간)입니다. 이 설정이 없으면 MySQL이 서버 쪽에서 이미 닫은 연결이 Go 풀에 유휴 항목으로 남아 있다가, 그 연결에 대한 쿼리가 실패할 때까지 유지됩니다.

자주 묻는 질문

sqlc는 실행 중인 데이터베이스에 대해 동작하나요?

명시적으로 활성화할 때만 그렇습니다. sqlc generate는 schema.sql과 query.sql을 정적으로 파싱하며 실행 중인 서버가 필요하지 않습니다. sqlc vet과 sqlc push는 추가 검사를 위해 실제 데이터베이스에 연결할 수 있지만, 이 가이드의 기본 generate 워크플로는 연결을 열지 않습니다.

query.sql의 쿼리에 구문 오류가 있으면 어떻게 되나요?

sqlc generate는 아무것도 생성하기 전에 파일과 줄을 지목하는 파싱 오류로 실패합니다. 실패 시 출력 디렉토리에 아무것도 기록되지 않으므로, 깨진 쿼리가 오래되거나 부분적인 생성 코드를 만드는 일은 없습니다.

sqlc가 INSERT … ON DUPLICATE KEY UPDATE 코드를 생성할 수 있나요?

예. sqlc는 이것을 일반 INSERT 문으로 파싱합니다. 다른 삽입과 마찬가지로, 결과의 어느 부분이 필요한지에 따라 :exec, :execresult, :execrows 또는 :execlastid로 어노테이션을 지정하세요.

문제 해결

참고