Введение
Написание кода для работы с базой данных на Go — настоящая боль!database/sql
Стандартная библиотека предоставляет относительно низкоуровневые интерфейсы. Нам нужно написать много повторяющегося кода. Много шаблонного кода не только громоздко писать, но и подвержено ошибкам. Иногда изменяется тип поля, и может потребоваться изменить многие места; новое поле добавляется и используется доselect *
Оператор запроса должен быть изменен. Если в некоторых местах есть упущения, это может привести к тому, что среда выполненияpanic
. Даже с библиотекой ORM эти проблемы не могут быть полностью решены! В этот момент,sqlc
приходящий!sqlc
Типобезопасный, идиоматический код интерфейса Go может быть сгенерирован из написанных нами операторов SQL, все, что нам нужно сделать, это вызвать эти методы.
быстрый в использовании
Сначала установите:
$ 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 мы можем использовать образ докераkjconroy/sqlc
. Установку докера вводить не буду, в интернете много туториалов. Вытащить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) error
func (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> <cmd>
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
. Статья 1ALTER 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Установить порогЭто слишком высоко! Я долго искал и, наконец, я могу найти толькоwoohoo.enterprise dB.com/download-broken…Загрузите исполняемый файл. Выбрал версию 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😄
Ссылаться на
- Гитхаб sqlc:GitHub.com/Хорошо, Конрой/…
- Перейти на ежедневный репозиторий GitHub:GitHub.com/Darenjun/go-of…
я
мой блог:darjun.github.io
Добро пожаловать, чтобы обратить внимание на мою общедоступную учетную запись WeChat [GoUpUp], учитесь вместе и добивайтесь прогресса вместе ~