SQLC – migração e transação
Vamos falar hoje sobre o SQLC ele elimina a utilização de ORM, é uma ferramenta de código aberto que compila códigos SQL em Go seguro (type-safe) e idiomático. Você cria seu select, update e insert e ele gera o código GO com tudo mapeado. Para instalar você pode utilizar a própria página deles aqui.
Eu optei por instalar dessa forma:
go install github.com/sqlc-dev/sqlc/cmd/sqlc@latestInstale também o golang-migrate, link aqui. Eu usei o comando do MAC:
brew install golang-migratePara o MAC precisei colocar a pasta BIN no PATH:
echo ‘export PATH="$PATH:$(go env GOPATH)/bin"‘ >> ~/.zshrc
source ~/.zshrcNosso estrutura de pasta:

O arquivo sqlc.yaml é o arquivo de configuração central do SQLC:
version: "2"
sql:
– schema: "sql/migrations"
queries: "sql/queries"
engine: "mysql"
gen:
go:
package: "db"
out: "internal/db"
overrides:
– db_type: "decimal"
go_type: "float64"Ele aponta para um schema de migrations. A linha queries é onde vai ficar nosso SQL para ele gerar os código em GO e, o engine é o banco de dados, o parâmetro out é para onde vai os arquivos e por fim overrides é para todos decimals ficar em float64 para ele não gerar como string como no padrão dele.
Agora vamos ver o nossos dois arquivos da pasta migrations, 000001_init.up.sql:
CREATE TABLE authors (
id varchar(36) NOT NULL PRIMARY KEY,
name text NOT NULL,
bio text
);
CREATE TABLE books (
id varchar(36) NOT NULL PRIMARY KEY,
authors_id varchar(36) NOT NULL,
name text NOT NULL,
description text,
price decimal(10,2) NOT NULL,
FOREIGN KEY (authors_id) REFERENCES authors(id)
);000001_init.down.sql:
DROP TABLE IF EXISTS books;
DROP TABLE IF EXISTS authors;Nosso query.sql dentro da pasta queries. Os parâmetros com exec é só para executar com many ou one é para retornar muitos ou só um:
— name: ListAuthors :many
SELECT * FROM authors;
— name: GetAuthor :one
SELECT * FROM authors
WHERE id = ?;
— name: CreateAuthor :exec
INSERT INTO authors (id, name, bio)
VALUES (?,?,?);
— name: UpdateAuthor :exec
UPDATE authors SET name = ?, bio = ?
WHERE id = ?;
— name: DeleteAuthor :exec
DELETE FROM authors WHERE id = ?;
— name: CreateBook :exec
INSERT INTO books (id, name, description, authors_id, price)
VALUES (?,?,?,?,?);
— name: ListBooks :many
SELECT b.*, a.name as author_name
FROM books b JOIN authors a ON b.authors_id = a.id;Nosso Makefile:
DB_HOST=localhost
DB_PORT=3306
DB_USER=root
DB_PASS=root
DB_NAME=library
createmigration:
migrate create -ext=sql -dir=sql/migrations -seq init
migrate:
migrate -path=sql/migrations \
-database "mysql://$(DB_USER):$(DB_PASS)@tcp($(DB_HOST):$(DB_PORT))/$(DB_NAME)" \
-verbose up
migratedown:
migrate -path=sql/migrations \
-database "mysql://$(DB_USER):$(DB_PASS)@tcp($(DB_HOST):$(DB_PORT))/$(DB_NAME)" \
-verbose down
.PHONY: migrate migratedown createmigrationAgora que passamos por todo conteúdo precisamos gerar os arquivos em GO que mapeia toda nossa estrutura para pasta internal/db. Para isso vamos executar na raiz o comando:
sqlc generateAgora vamos ver os arquvios que ele gerou na pasta internal/db. db.go:
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
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:
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
package db
import (
"database/sql"
)
type Author struct {
ID string
Name string
Bio sql.NullString
}
type Book struct {
ID string
AuthorsID string
Name string
Description sql.NullString
Price float64
}query.sql.go:
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
// source: query.sql
package db
import (
"context"
"database/sql"
)
const createAuthor = `– name: CreateAuthor :exec
INSERT INTO authors (id, name, bio)
VALUES (?,?,?)
`
type CreateAuthorParams struct {
ID string
Name string
Bio sql.NullString
}
func (q *Queries) CreateAuthor(ctx context.Context, arg CreateAuthorParams) error {
_, err := q.db.ExecContext(ctx, createAuthor, arg.ID, arg.Name, arg.Bio)
return err
}
const createBook = `– name: CreateBook :exec
INSERT INTO books (id, name, description, authors_id, price)
VALUES (?,?,?,?,?)
`
type CreateBookParams struct {
ID string
Name string
Description sql.NullString
AuthorsID string
Price float64
}
func (q *Queries) CreateBook(ctx context.Context, arg CreateBookParams) error {
_, err := q.db.ExecContext(ctx, createBook,
arg.ID,
arg.Name,
arg.Description,
arg.AuthorsID,
arg.Price,
)
return err
}
const deleteAuthor = `– name: DeleteAuthor :exec
DELETE FROM authors WHERE id = ?
`
func (q *Queries) DeleteAuthor(ctx context.Context, id string) error {
_, err := q.db.ExecContext(ctx, deleteAuthor, id)
return err
}
const getAuthor = `– name: GetAuthor :one
SELECT id, name, bio FROM authors
WHERE id = ?
`
func (q *Queries) GetAuthor(ctx context.Context, id string) (Author, error) {
row := q.db.QueryRowContext(ctx, getAuthor, id)
var i Author
err := row.Scan(&i.ID, &i.Name, &i.Bio)
return i, err
}
const listAuthors = `– name: ListAuthors :many
SELECT id, name, bio FROM authors
`
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
}
const listBooks = `– name: ListBooks :many
SELECT b.id, b.authors_id, b.name, b.description, b.price, a.name as author_name
FROM books b JOIN authors a ON b.authors_id = a.id
`
type ListBooksRow struct {
ID string
AuthorsID string
Name string
Description sql.NullString
Price float64
AuthorName string
}
func (q *Queries) ListBooks(ctx context.Context) ([]ListBooksRow, error) {
rows, err := q.db.QueryContext(ctx, listBooks)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ListBooksRow
for rows.Next() {
var i ListBooksRow
if err := rows.Scan(
&i.ID,
&i.AuthorsID,
&i.Name,
&i.Description,
&i.Price,
&i.AuthorName,
); 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
}
const updateAuthor = `– name: UpdateAuthor :exec
UPDATE authors SET name = ?, bio = ?
WHERE id = ?
`
type UpdateAuthorParams struct {
Name string
Bio sql.NullString
ID string
}
func (q *Queries) UpdateAuthor(ctx context.Context, arg UpdateAuthorParams) error {
_, err := q.db.ExecContext(ctx, updateAuthor, arg.Name, arg.Bio, arg.ID)
return err
}Agora enfim temos todos os arquivos gerados! É só chamar as funções. Vamos ver um exemplo, crie na pasta cmd uma pasta chamada execSQLC e adicione um arquivo main.go nela:
package main
import (
"context"
"database/sql"
_ "github.com/go-sql-driver/mysql"
"github.com/google/uuid"
"github.com/rafaelsouzaribeiro/sqlc/internal/db"
)
func main() {
ctx := context.Background()
dbConn, err := sql.Open("mysql", "root:root@tcp(localhost:3306)/library")
if err != nil {
panic(err)
}
defer dbConn.Close()
queries := db.New(dbConn)
// Criar um novo autor
err = queries.CreateAuthor(ctx, db.CreateAuthorParams{
ID: uuid.New().String(),
Name: "J.K. Rowling",
Bio: sql.NullString{String: "British author, best known for the Harry Potter series.", Valid: true},
})
if err != nil {
panic(err)
}
// Listar autores
authors, err := queries.ListAuthors(ctx)
if err != nil {
panic(err)
}
for _, author := range authors {
println("Author:", author.Name, "Bio:", author.Bio.String)
}
// Criar um novo livro para o autor
err = queries.CreateBook(ctx, db.CreateBookParams{
ID: uuid.New().String(),
Name: "Harry Potter and the Sorcerer’s Stone",
Description: sql.NullString{String: "The first book in the Harry Potter series.", Valid: true},
Price: 19.99,
AuthorsID: authors[0].ID, // Associar o livro ao primeiro autor
})
if err != nil {
panic(err)
}
// Listar Author com seus livros
books, err := queries.ListBooks(ctx)
if err != nil {
panic(err)
}
for _, book := range books {
println("Book:", book.Name, "Author:", book.AuthorName, "Price:", book.Price)
}
}Pronto! Está feito. Vamos falar sobre transação com SQLC para acessar clique aqui.
0 comentários