sqlc из Go Daily Library

Go
sqlc из Go Daily Library

Введение

Написание кода для работы с базой данных на 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😄

Ссылаться на

  1. Гитхаб sqlc:GitHub.com/Хорошо, Конрой/…
  2. Перейти на ежедневный репозиторий GitHub:GitHub.com/Darenjun/go-of…

я

мой блог:darjun.github.io

Добро пожаловать, чтобы обратить внимание на мою общедоступную учетную запись WeChat [GoUpUp], учитесь вместе и добивайтесь прогресса вместе ~