
在 Go 语言中编写数据库操作代码真的非常痛苦！`database/sql`标准库提供的都是比较底层的接口。我们需要编写大量重复的代码。大量的模板代码不仅写起来烦，而且还容易出错。有时候字段类型修改了一下，可能就需要改动很多地方；添加了一个新字段，之前使用`select *`查询语句的地方都要修改。如果有些地方有遗漏，可能就会造成运行时`panic`。即使使用 ORM 库，这些问题也不能完全解决！这时候，`sqlc`来了！`sqlc`可以根据我们编写的 SQL 语句生成类型安全的、地道的 Go 接口代码，我们要做的只是调用这些方法。

<!--more-->

sqlc generates **fully type-safe idiomatic Go code** from SQL. Here’s how it works:

1. 编写 SQL 语句
2. 使用 `sqlc`生成我们所需要的 go 查询接口
3. 使用这些接口与数据库交互

### 快速比较

- database/sql
  - 快
  - 易出错，且 runtime 才能捕获问题
- gorm（Golang 的库）
  - CRUD 已被实现，所需要的代码少
  - 需要用 gorm 的函数来写查询代码（新的学习成本）
  - 高负载下运行慢
- sqlx
  - 速度快，易用
  - 同样易出错，且在 runtime 才能捕获问题
- sqlc（choose)
  - 快，易用，自动生成代码
  - 编写代码时即可发现 SQL 的错误
  - 能支持 PG

### 安装 sqlc

拉取镜像

```shell
docker pull kjconroy/sqlc
```

初始化配置文件

```shell
docker run -rm -v 你的项目路径:/src -w /src kjconroy/sqlc init
```

执行命令

```shell
docker run -rm -v D:\repository\simplebank:/src -w /src kjconroy/sqlc init
```

运行之后会在`D:\repository\simplebank`生成`sqlc.yaml`文件

编辑配置文件。

写入以下内容，配置文件中的目录都要存在

```
version: 1
packages:
  - path: "./db/sqlc" # 生成go 代码的位置
    name: "db"  # 生成 go package 的名字
    engine: "postgresql" # 使用的数据库引擎
    schema: "./db/migration/" # 迁移表的sql语句 我们使用migrate中的up文件
    queries: "./db/query" # CRUD的sql
    emit_json_tags: true  # 添加json在生成的struct中
    emit_prepared_queries: false 
    emit_interface: false
    emit_exact_table_names: false
```

在`./db/query/account.sql`中写入 crud 语句

```sql
-- name: CreateAccount :one
INSERT INTO accounts(owner,
                     balance,
                     currency)
VALUES ($1, $2, $3)
returning *;

-- name: GetAccount :one
SELECT *
FROM accounts
WHERE id = $1
LIMIT 1;

-- name: ListAccounts :many
select *
from accounts
order by id
limit $1 offset $2;

-- name: UpdateAccount :one
update accounts
set balance =$2
where id = $1
returning *;

-- name: DeleteAccount :exec
delete
from accounts
where id = $1;

-- 修改 sql 语句中的展位符号名称
-- 生成的参数结构体字段名称就是我们自定义的名称了。

-- name: AddAccountBalance :one
update accounts
set balance = balance+ $2
where id = $1
returning *;
-- name: AddAccountBalance :one
update accounts
set balance = balance+ sqlc.arg(amount)
where id = sqlc.arg(id)
returning *;
```

我们可以看到这和我们自己写 sql 并无不同，最大的区别就是每一句 sql 上面都会有一个注释

name: 后面的是我们要生成的那个 go 查询接口的方法名，再后后面的 one、many、exec 都有不同的含义：

- one：只有一个返回值
- many：多个返回值
- exec：没有返回值

生成 CRUD 代码，执行

```shell
docker run --rm -v D:\repository\simplebank:/src -w /src kjconroy/sqlc generate
```

在`./db/sqlc`中会生成 CRUD 代码

现在可以在 db/sqlc 文件夹下查看生成的 go code

其实除了我们需要的`account.sql.go`,`entry.sql.go`,`transfer.sql.go`，还会生成三个.go 文件，可以简单看一下里面都是些什么内容：

`db.go`初始化了一个 Queries 结构，我们需要传入一个自己的`db`连接对象

```go
// Code generated by sqlc. DO NOT EDIT.

package db

import (
	"context"
	"database/sql"
)

type DBTX interface {
	ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
	PrepareContext(context.Context, string) (*sql.Stmt, error)
	QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
	QueryRowContext(context.Context, string, ...interface{}) *sql.Row
}

func New(db DBTX) *Queries {
	return &Queries{db: db}
}

type Queries struct {
	db DBTX
}

func (q *Queries) WithTx(tx *sql.Tx) *Queries {
	return &Queries{
		db: tx,
	}
}
```

models.go：就是将我们每个表的字段都做了一次结构体的封装

```go
// Code generated by sqlc. DO NOT EDIT.

package db

import (
	"time"
)

type Account struct {
	ID        int64     `json:"id"`
	Owner     string    `json:"owner"`
	Balance   int64     `json:"balance"`
	Currency  string    `json:"currency"`
	CreatedAt time.Time `json:"created_at"`
}

```

`querier.go`：定义一个接口，封装所有的`sql`查询接口

```go
// Code generated by sqlc. DO NOT EDIT.

package db

import (
	"context"
)

type Querier interface {
	CreateAccount(ctx context.Context, arg CreateAccountParams) (Account, error)
	DeleteAccount(ctx context.Context, id int64) error
	GetAccount(ctx context.Context, id int64) (Account, error)
	ListAccounts(ctx context.Context, arg ListAccountsParams) ([]Account, error)
	UpdateAccount(ctx context.Context, arg UpdateAccountParams) (Account, error)
}

var _ Querier = (*Queries)(nil)

```

`account.sql.go`：用 go 实现了我们刚才写的那些`sql`语句，一些输入和输出结构都用了`struct`来定义

```go
// Code generated by sqlc. DO NOT EDIT.
// source: account.sql

package db

import (
	"context"
)

const addAccountBalance = `-- name: AddAccountBalance :one
update accounts
set balance = balance+ $1
where id = $2
returning id, owner, balance, currency, created_at
`

type AddAccountBalanceParams struct {
	Amount int64 `json:"amount"`
	ID     int64 `json:"id"`
}

func (q *Queries) AddAccountBalance(ctx context.Context, arg AddAccountBalanceParams) (Account, error) {
	row := q.db.QueryRowContext(ctx, addAccountBalance, arg.Amount, arg.ID)
	var i Account
	err := row.Scan(
		&i.ID,
		&i.Owner,
		&i.Balance,
		&i.Currency,
		&i.CreatedAt,
	)
	return i, err
}
... 剩下的省略
```

至此我们就完全可以用 go 来与数据库实现交互了，sqlc 的优势也很明显了，我们只需要写 sql，而不需要关心 go 如何与 sql 进行交互的

同时 sqlc 还支持了语法错误的判断，而不存在我们在运行程序是因为 sql 出错而 panic 的情况。

### 使用

```go
package main

import (
	"context"
	"database/sql"
	"log"
	"testing"

	_ "github.com/lib/pq"
)

const (
	DB_DRIVER = "postgres"
	DB_SOURCE = "postgresql://postgres:postgres@localhost:25432/simple_bank?sslmode=disable"
)

var (
	testQueries *Queries // 全局的 queries 数据库测试都要用到
	testDB      *sql.DB
)

func main() {

	// 连接数据库
	var err error
	testDB, err = sql.Open(DB_DRIVER, DB_SOURCE)
	if err != nil {
		log.Fatalln("cannot connect to db :", err)
	}
	testQueries = New(testDB) // 生成测试的 queries

	account, err := testQueries.CreateAccount(context.Background(),
		CreateAccountParams{
			Owner:    "jimyag",
			Balance:  1111,
			Currency: "USD",
		})
	if err != nil {
		log.Fatalln("cannot create account")
	}
	log.Print(account)
}

```

### 参考

[Installing sqlc — sqlc 1.12.0 documentation](https://docs.sqlc.dev/en/stable/overview/install.html)

[sqlc 使用说明 - 码农教程 (manongjc.com)](http://www.manongjc.com/detail/29-zfddgqpfjmnlvze.html)

[sqlc-地鼠文档 (topgoer.cn)](https://www.topgoer.cn/docs/goday/goday-1crfurcbv0dl5)


