日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

go get 的不再src目录中_Go 每日一库之 sqlc:根据 sql 生成代码

發布時間:2023/12/2 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 go get 的不再src目录中_Go 每日一库之 sqlc:根据 sql 生成代码 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

簡介

在 Go 語言中編寫數據庫操作代碼真的非常痛苦!database/sql標準庫提供的都是比較底層的接口。我們需要編寫大量重復的代碼。大量的模板代碼不僅寫起來煩,而且還容易出錯。有時候字段類型修改了一下,可能就需要改動很多地方;添加了一個新字段,之前使用select *查詢語句的地方都要修改。如果有些地方有遺漏,可能就會造成運行時panic。即使使用 ORM 庫,這些問題也不能完全解決!這時候,sqlc來了!sqlc可以根據我們編寫的 SQL 語句生成類型安全的、地道的 Go 接口代碼,我們要做的只是調用這些方法。

快速使用

先安裝:

$?go?get?github.com/kyleconroy/sqlc/cmd/sqlc

當然還有對應的數據庫驅動:

$?go?get?github.com/lib/pq
$?go?get?github.com/go-sql-driver/mysql

sqlc是一個命令行工具,上面代碼會將可執行程序sqlc放到$GOPATH/bin目錄下。我習慣把$GOPATH/bin目錄加入到系統PATH中。所以可以執行使用這個命令。

因為sqlc用到了一個 linux 下的庫,在 windows 上無法正常編譯。在 windows 上我們可以使用 docker 鏡像kjconroy/sqlc。docker 的安裝就不介紹了,網上有很多教程。拉取kjconroy/sqlc鏡像:

$?docker?pull?kjconroy/sqlc

然后,編寫 SQL 語句。在schema.sql文件中編寫建表語句:

CREATE?TABLE?authors?(
??id???BIGSERIAL?PRIMARY?KEY,
??name?TEXT?NOT?NULL,
??bio??TEXT
);

在query.sql文件中編寫查詢語句:

--?name:?GetAuthor?:one
SELECT?*?FROM?authors
WHERE?id?=?$1?LIMIT?1;

--?name:?ListAuthors?:many
SELECT?*?FROM?authors
ORDER?BY?name;

--?name:?CreateAuthor?:exec
INSERT?INTO?authors?(
??name,?bio
)?VALUES?(
??$1,?$2
)
RETURNING?*;

--?name:?DeleteAuthor?:exec
DELETE?FROM?authors
WHERE?id?=?$1;

sqlc支持 PostgreSQL 和 MySQL,不過對 MySQL 的支持是實驗性的。期待后續完善對 MySQL 的支持,增加對其它數據庫的支持。本文我們使用的是 PostgreSQL。編寫數據庫程序時,上面兩個 sql 文件是少不了的。sqlc額外只需要一個小小的配置文件sqlc.yaml:

version:?"1"
packages:
??-?name:?"db"
????path:?"./db"
????queries:?"./query.sql"
????schema:?"./schema.sql"
  • version:版本;
  • packages:
    • name:生成的包名;
    • path:生成文件的路徑;
    • queries:查詢 SQL 文件;
    • schema:建表 SQL 文件。

在 windows 上執行下面的命令生成對應的 Go 代碼:

docker?run?--rm?-v?CONFIG_PATH:/src?-w?/src?kjconroy/sqlc?generate

上面的CONFIG_PATH替換成配置所在目錄,我的是D:\code\golang\src\github.com\darjun\go-daily-lib\sqlc\get-started。sqlc為我們在同級目錄下生成了數據庫操作代碼,目錄結構如下:

db
├──?db.go
├──?models.go
└──?query.sql.go

sqlc根據我們schema.sql和query.sql生成了模型對象結構:

//?models.go
type?Author?struct?{
??ID???int64
??Name?string
??Bio??sql.NullString
}

和操作接口:

//?query.sql.go
func?(q?*Queries)?CreateAuthor(ctx?context.Context,?arg?CreateAuthorParams)?(Author,?error)func?(q?*Queries)?DeleteAuthor(ctx?context.Context,?id?int64)?errorfunc?(q?*Queries)?GetAuthor(ctx?context.Context,?id?int64)?(Author,?error)func?(q?*Queries)?ListAuthors(ctx?context.Context)?([]Author,?error)

其中Queries是sqlc封裝的一個結構。

說了這么多,來看看如何使用:

package?main

import?(
??"database/sql"
??"fmt"
??"log"

??_?"github.com/lib/pq"
??"golang.org/x/net/context"

??"github.com/darjun/go-daily-lib/sqlc/get-started/db"
)

func?main()?{
??pq,?err?:=?sql.Open("postgres",?"dbname=sqlc?sslmode=disable")
??if?err?!=?nil?{
????log.Fatal(err)
??}

??queries?:=?db.New(pq)

??authors,?err?:=?queries.ListAuthors(context.Background())
??if?err?!=?nil?{
????log.Fatal("ListAuthors?error:",?err)
??}
??fmt.Println(authors)

??insertedAuthor,?err?:=?queries.CreateAuthor(context.Background(),?db.CreateAuthorParams{
????Name:?"Brian?Kernighan",
????Bio:??sql.NullString{String:?"Co-author?of?The?C?Programming?Language?and?The?Go?Programming?Language",?Valid:?true},
??})
??if?err?!=?nil?{
????log.Fatal("CreateAuthor?error:",?err)
??}
??fmt.Println(insertedAuthor)

??fetchedAuthor,?err?:=?queries.GetAuthor(context.Background(),?insertedAuthor.ID)
??if?err?!=?nil?{
????log.Fatal("GetAuthor?error:",?err)
??}
??fmt.Println(fetchedAuthor)

??err?=?queries.DeleteAuthor(context.Background(),?insertedAuthor.ID)
??if?err?!=?nil?{
????log.Fatal("DeleteAuthor?error:",?err)
??}
}

生成的代碼在包db下(由packages.name選項指定),首先調用db.New()將sql.Open()的返回值sql.DB作為參數傳入,得到Queries對象。我們對authors表的操作都需要通過該對象的方法。

上面程序要運行,還需要啟動 PostgreSQL,創建數據庫和表:

$ createdb sqlc
$ psql -f schema.sql -d sqlc

上面第一條命令創建一個名為sqlc的數據庫,第二條命令在數據庫sqlc中執行schema.sql文件中的語句,即創建表。

最后運行程序(多文件程序不能用go run main.go):

$ go run .
[]
{1 Brian Kernighan {Co-author of The C Programming Language and The Go Programming Language true}}
{1 Brian Kernighan {Co-author of The C Programming Language and The Go Programming Language true}}

代碼生成

除了 SQL 語句本身,sqlc需要我們在編寫 SQL 語句的時候通過注釋的方式為生成的程序提供一些基本信息。語法為:

--?name:??

name為生成的方法名,如上面的CreateAuthor/ListAuthors/GetAuthor/DeleteAuthor等,cmd可以有以下取值:

  • :one:表示 SQL 語句返回一個對象,生成的方法的返回值為(對象類型, error),對象類型可以從表名得出;
  • :many:表示 SQL 語句會返回多個對象,生成的方法的返回值為([]對象類型, error);
  • :exec:表示 SQL 語句不返回對象,只返回一個error;
  • :execrows:表示 SQL 語句需要返回受影響的行數。

:one

--?name:?GetAuthor?:one
SELECT?id,?name,?bio?FROM?authors
WHERE?id?=?$1?LIMIT?1

注釋中--name指示生成方法GetAuthor,從表名得出返回的基礎類型為Author。:one又表示只返回一個對象。故最終的返回值為(Author, error):

//?db/query.sql.go
const?getAuthor?=?`--?name:?GetAuthor?:one
SELECT?id,?name,?bio?FROM?authors
WHERE?id?=?$1?LIMIT?1
`

func?(q?*Queries)?GetAuthor(ctx?context.Context,?id?int64)?(Author,?error)?{
??row?:=?q.db.QueryRowContext(ctx,?getAuthor,?id)
??var?i?Author
??err?:=?row.Scan(&i.ID,?&i.Name,?&i.Bio)
??return?i,?err
}

:many

--?name:?ListAuthors?:many
SELECT?*?FROM?authors
ORDER?BY?name;

注釋中--name指示生成方法ListAuthors,從表名authors得到返回的基礎類型為Author。:many表示返回一個對象的切片。故最終的返回值為([]Author, error):

//?db/query.sql.go
const?listAuthors?=?`--?name:?ListAuthors?:many
SELECT?id,?name,?bio?FROM?authors
ORDER?BY?name
`

func?(q?*Queries)?ListAuthors(ctx?context.Context)?([]Author,?error)?{
??rows,?err?:=?q.db.QueryContext(ctx,?listAuthors)
??if?err?!=?nil?{
????return?nil,?err
??}
??defer?rows.Close()
??var?items?[]Author
??for?rows.Next()?{
????var?i?Author
????if?err?:=?rows.Scan(&i.ID,?&i.Name,?&i.Bio);?err?!=?nil?{
??????return?nil,?err
????}
????items?=?append(items,?i)
??}
??if?err?:=?rows.Close();?err?!=?nil?{
????return?nil,?err
??}
??if?err?:=?rows.Err();?err?!=?nil?{
????return?nil,?err
??}
??return?items,?nil
}

這里注意一個細節,即使我們使用了select *,生成的代碼中 SQL 語句被也改寫成了具體的字段:

SELECT?id,?name,?bio?FROM?authors
ORDER?BY?name

這樣后續如果我們需要添加或刪除字段,只要執行了sqlc命令,這個 SQL 語句和ListAuthors()方法就能保持一致!是不是很方便?

:exec

--?name:?DeleteAuthor?:exec
DELETE?FROM?authors
WHERE?id?=?$1

注釋中--name指示生成方法DeleteAuthor,從表名authors得到返回的基礎類型為Author。:exec表示不返回對象。故最終的返回值為error:

//?db/query.sql.go
const?deleteAuthor?=?`--?name:?DeleteAuthor?:exec
DELETE?FROM?authors
WHERE?id?=?$1
`

func?(q?*Queries)?DeleteAuthor(ctx?context.Context,?id?int64)?error?{
??_,?err?:=?q.db.ExecContext(ctx,?deleteAuthor,?id)
??return?err
}

:execrows

--?name:?DeleteAuthorN?:execrows
DELETE?FROM?authors
WHERE?id?=?$1

注釋中--name指示生成方法DeleteAuthorN,從表名authors得到返回的基礎類型為Author。:exec表示返回受影響的行數(即刪除了多少行)。故最終的返回值為(int64, error):

//?db/query.sql.go
const?deleteAuthorN?=?`--?name:?DeleteAuthorN?:execrows
DELETE?FROM?authors
WHERE?id?=?$1
`

func?(q?*Queries)?DeleteAuthorN(ctx?context.Context,?id?int64)?(int64,?error)?{
??result,?err?:=?q.db.ExecContext(ctx,?deleteAuthorN,?id)
??if?err?!=?nil?{
????return?0,?err
??}
??return?result.RowsAffected()
}

不管編寫的 SQL 多復雜,總是逃不過上面的規則。我們只需要在編寫 SQL 語句時額外添加一行注釋,sqlc就能為我們生成地道的 SQL 操作方法。生成的代碼與我們自己手寫的沒什么不同,錯誤處理都很完善,而且了避免手寫的麻煩與錯誤。

模型對象

sqlc為所有的建表語句生成對應的模型結構。結構名為表名的單數形式,且首字母大寫。例如:

CREATE?TABLE?authors?(
??id???SERIAL?PRIMARY?KEY,
??name?text???NOT?NULL
);

生成對應的結構:

type?Author?struct?{
??ID???int
??Name?string
}

而且sqlc可以解析ALTER TABLE語句,它會根據最終的表結構來生成模型對象的結構。例如:

CREATE?TABLE?authors?(
??id??????????SERIAL?PRIMARY?KEY,
??birth_year??int????NOT?NULL
);

ALTER?TABLE?authors?ADD?COLUMN?bio?text?NOT?NULL;
ALTER?TABLE?authors?DROP?COLUMN?birth_year;
ALTER?TABLE?authors?RENAME?TO?writers;

上面的 SQL 語句中,建表時有兩列id和birth_year。第一條ALTER TABLE語句添加了一列bio,第二條刪除了birth_year列,第三條將表名authors改為writers。sqlc依據最終的表名writers和表中的列id、bio生成代碼:

package?db

type?Writer?struct?{
??ID??int
??Bio?string
}

配置字段

sqlc.yaml文件中還可以設置其他的配置字段。

emit_json_tags

默認為false,設置該字段為true可以為生成的模型對象結構添加 JSON 標簽。例如:

CREATE?TABLE?authors?(
??id?????????SERIAL????PRIMARY?KEY,
??created_at?timestamp?NOT?NULL
);

生成:

package?db

import?(
??"time"
)

type?Author?struct?{
??ID????????int???????`json:"id"`
??CreatedAt?time.Time?`json:"created_at"`
}

emit_prepared_queries

默認為false,設置該字段為true,會為 SQL 生成對應的prepared statement。例如,在快速開始的示例中設置這個選項,最終生成的結構Queries中會添加所有 SQL 對應的prepared statement對象:

type?Queries?struct?{
??db????????????????DBTX
??tx????????????????*sql.Tx
??createAuthorStmt??*sql.Stmt
??deleteAuthorStmt??*sql.Stmt
??getAuthorStmt?????*sql.Stmt
??listAuthorsStmt???*sql.Stmt
}

和一個Prepare()方法:

func?Prepare(ctx?context.Context,?db?DBTX)?(*Queries,?error)?{
??q?:=?Queries{db:?db}
??var?err?error
??if?q.createAuthorStmt,?err?=?db.PrepareContext(ctx,?createAuthor);?err?!=?nil?{
????return?nil,?fmt.Errorf("error?preparing?query?CreateAuthor:?%w",?err)
??}
??if?q.deleteAuthorStmt,?err?=?db.PrepareContext(ctx,?deleteAuthor);?err?!=?nil?{
????return?nil,?fmt.Errorf("error?preparing?query?DeleteAuthor:?%w",?err)
??}
??if?q.getAuthorStmt,?err?=?db.PrepareContext(ctx,?getAuthor);?err?!=?nil?{
????return?nil,?fmt.Errorf("error?preparing?query?GetAuthor:?%w",?err)
??}
??if?q.listAuthorsStmt,?err?=?db.PrepareContext(ctx,?listAuthors);?err?!=?nil?{
????return?nil,?fmt.Errorf("error?preparing?query?ListAuthors:?%w",?err)
??}
??return?&q,?nil
}

生成的其它方法都使用了這些對象,而非直接使用 SQL 語句:

func?(q?*Queries)?CreateAuthor(ctx?context.Context,?arg?CreateAuthorParams)?(Author,?error)?{
??row?:=?q.queryRow(ctx,?q.createAuthorStmt,?createAuthor,?arg.Name,?arg.Bio)
??var?i?Author
??err?:=?row.Scan(&i.ID,?&i.Name,?&i.Bio)
??return?i,?err
}

我們需要在程序初始化時調用這個Prepare()方法。

emit_interface

默認為false,設置該字段為true,會為查詢結構生成一個接口。例如,在快速開始的示例中設置這個選項,最終生成的代碼會多出一個文件querier.go:

//?db/querier.go
type?Querier?interface?{
??CreateAuthor(ctx?context.Context,?arg?CreateAuthorParams)?(Author,?error)
??DeleteAuthor(ctx?context.Context,?id?int64)?error
??DeleteAuthorN(ctx?context.Context,?id?int64)?(int64,?error)
??GetAuthor(ctx?context.Context,?id?int64)?(Author,?error)
??ListAuthors(ctx?context.Context)?([]Author,?error)
}

覆寫類型

sqlc在生成模型對象結構時會根據數據庫表的字段類型推算出一個 Go 語言類型,例如text對應string。我們也可以在配置文件中指定這種類型映射。

version:?"1"
packages:
??-?name:?"db"
????path:?"./db"
????queries:?"./query.sql"
????schema:?"./schema.sql"
overrides:
??-?go_type:?"github.com/uniplaces/carbon.Time"
????db_type:?"pg_catalog.timestamp"

在overrides下go_type表示使用的 Go 類型。如果是非標準類型,必須指定全限定類型(即包路徑 + 類型名)。db_type設置為要映射的數據庫類型。sqlc會自動導入對應的標準包或第三方包。生成代碼如下:

package?db

import?(
??"github.com/uniplaces/carbon"
)

type?Author?struct?{
??ID???????int32
??Name?????string
??CreateAt?carbon.Time
}

**需要注意的是db_type的表示,文檔這里一筆帶過,使用上還是有些晦澀。**我也是看源碼才找到如何覆寫timestamp類型的,需要將db_type設置為pg_catalog.timestamp。同理timestamptz、timetz等類型也需要加上這個前綴。**一般復雜類型都需要加上前綴,一般的基礎類型可以加也可以不加。**遇到不確定的情況,可以去看看源碼gen.go#L634。

也可以設定某個字段的類型,例如我們要將創建時間字段created_at設置為使用carbon.Time:

version:?"1"
packages:
??-?name:?"db"
????path:?"./db"
????queries:?"./query.sql"
????schema:?"./schema.sql"
overrides:
??-?column:?"authors.create_at"
????go_type:?"github.com/uniplaces/carbon.Time"

生成代碼如下:

//?db/models.go
package?db

import?(
??"github.com/uniplaces/carbon"
)

type?Author?struct?{
??ID???????int32
??Name?????string
??CreateAt?carbon.Time
}

最后我們還可以給生成的結構字段命名:

version:?"1"
packages:
??-?name:?"db"
????path:?"./db"
????queries:?"./query.sql"
????schema:?"./schema.sql"
rename:
????id:?"Id"
????name:?"UserName"
????create_at:?"CreateTime"

上面配置為生成的結構設置字段名,生成代碼:

package?db

import?(
??"time"
)

type?Author?struct?{
??Id?????????int32
??UserName???string
??CreateTime?time.Time
}

安裝 PostgreSQL

我之前使用 MySQL 較多。由于sqlc對 MySQL 的支持不太好,在體驗這個庫的時候還是選擇支持較好的 ?PostgreSQL。不得不說,在 win10 上,PostgreSQL 的安裝門檻實在是太高了!我摸索了很久最后只能在https://www.enterprisedb.com/download-postgresql-binaries下載可執行文件。我選擇了 10.12 版本,下載、解壓、將文件夾中的bin加入系統PATH。創建一個data目錄,然后執行下面的命令初始化數據:

$ initdb data

注冊 PostgreSQL 服務,這樣每次系統重啟后會自動啟動:

$ pg_ctl register -N "pgsql" -D D:\data

這里的data目錄就是上面創建的,并且一定要使用絕對路徑!

啟動服務:

$ sc start pgsql

最后使用我們前面介紹的命令創建數據庫和表即可。

如果有使用installer成功安裝的小伙伴,還請不吝賜教!

總結

雖然目前還有一些不完善的地方,例如對 MySQL 的支持是實驗性的,sqlc工具的確能大大簡化我們使用 Go 編寫數據庫代碼的復雜度,提升我們的編碼效率,減少我們出錯的概率。使用 PostgreSQL 的小伙伴非常建議嘗試一番!

大家如果發現好玩、好用的 Go 語言庫,歡迎到 Go 每日一庫 GitHub 上提交 issue?

參考

  • sqlc GitHub:https://github.com/kyleconroy/sqlc
  • Go 每日一庫 GitHub:https://github.com/darjun/go-daily-lib
  • 我的博客:https://darjun.github.io

    歡迎關注我的微信公眾號【GoUpUp】,共同學習,一起進步~

    總結

    以上是生活随笔為你收集整理的go get 的不再src目录中_Go 每日一库之 sqlc:根据 sql 生成代码的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。