O inicio é bem simples e vai depender mais do SO, estou usando o postgreSQL pois é um dos databases mais utilizados, mas você pode utilizar outro banco relacional com pequenas modificações.
Não se trata de um tutorial sobre SQL em si e sim de fazer a conexão do seu banco de dados com o Golang.
Por esse motivo eu vou instalar e administrar o Postgres da forma mais simples possível, que é através de um client, pois não sou de ferro. Estou usando o Manjaro no momento e vou instalar o Postgres através do gerenciador de aplicações e também vou instalar o pgAdmin para gerenciamento de servidores e bancos de dados visual no PostgreSQL.
Acredito que essa seja uma abordagem interessante para iniciantes que não tem experiência com banco de dados, existe um folclore muito grande a respeito de UPDATE sem WHERE por aí e apesar das histórias parecerem engraçadas depois de um tempo o medo é real hahaha.
Na loja de apps do seu SO é muito provável que você encontre os dois mas de qualquer forma vou deixar alguns links.
Aqui vou deixar um link pra quem quiser baixar o PostgreSQL no ubuntu.
Aqui o link de download do pgAdmin4
Terminando de instalar os dois, quando abrir o pgAdmin ele vai subir o client no seu browser e em seguida pedir pra você definir uma senha, vou definir a minha como “vapordev123”.
Clicando com o botão direito em cima de servers podemos criar um, vou chamar o meu de “AHAB”.
Abaixo uma mensagem de erro nos avisa de que precisamos definir mais informações.
Na aba de connection vamos definir o nosso host que será o “localhost” e a porta que será “5432” o restante vamos manter, clicamos em save e temos nosso servidor.
Vou criar agora um banco de dados, clico com o botão direito em cima do server AHAB, create/database.
Aqui só precisamos definir o nome do banco, que será “MobyDick”, e agora save!. O Banco de dados tá pronto, agora vamos criar a tabela que vamos manipular usando Go.
Acompanhe na imagem:
- Azul: Clique em cima para selecionar MobyDick.
- Vermelho: Clique no Query Tool e abrira um console para escrevermos.
- Verde: Nossa tabela se chama “article”, terá um “id”, “title” e “body”.
- Rosa: Clique no botão de execução para criar a tabela.
CREATE TABLE article (
id SERIAL PRIMARY KEY,
title VARCHAR(255),
body TEXT
);
Clicando com o botão direito sobre MobyDick clique em refresh e agora você pode verificar a tabela em:
MobyDick/Schemas/public/Tables, e article estará lá.
Os bancos de dados tem seus próprios tipos, para ter uma ideia simples da tabela que criamos, id é apenas um identificador, ele serve também para quando tivermos uma nova tabela criar uma relação entre elas passando o id como uma chave estrangeira.
Nosso title recebeu um VARCHAR(255) que é um dado de caractere como string e tem uma limitação de 255 caracteres.
Body recebeu o tipo TEXT pois esse é um dado de caractere que não tem limite, que o torna o candidato mais adequado para ser o corpo de um artigo.
Aqui mesmo no pgAdmin vamos inserir o primeiro artigo na nossa tabela antes de ir ao código Golang.
Clique no Query Tool e use o seguinte código e execute.
INSERT INTO article (id, title, body) VALUES (1, 'Golang + PostgreSQL', 'But I must explain to you how all this mistaken idea');
Esse é um artigo em que no body eu coloquei um “lorem ipsum”. Mas a estrutura desse insert é essa:
INSERT INTO article (id,title,body) VALUES (O que você quer inserir separado por vírgulas);
Depois que conseguir damos um refresh no DB e vamos agora fazer um select para verificar nossos dados.
SELECT * FROM article
Isso deve retornar na parte de baixo a tabela e dados inseridos. Estou usando cada vez menos imagens pois imagino que você deva estar se habituando a página de administração do DB e também para evitar repetição.
A estrutura das pastas é assim:
├── gopostgres
| └── dbconfig
| └── driverConfig.go
└── main.go
Você pode escolher o nome que for melhor, mas eu fiz uma pasta diferente para o arquivo driverConfig.go pois vou usá-lo como pacote e importar no main.go mais a frente.
package dbconfig
import "fmt"
type Article struct {
ID int
Title string
Body []byte
}
A struct que será a nossa representação da tabela Article aqui no código, caso você tenha visto o tutorial o WEBtrhough a estrutura é parecida, foi proposital pra que esse artigo seja um complemento.
const PostgresDriver = "postgres"
const User = "postgres"
const Host = "localhost"
const Port = "5432"
const Password = "vapordev123"
const DbName = "MobyDick"
const TableName = "article"
Uma série de constantes, com strings que já são familiares, todas as informações que definimos no banco de dados usaremos aqui para que a conexão possa ser feita.
A única constante definida aqui em função do código é a PostgresDriver, vamos usar um driver de terceiros pois não existe opção na biblioteca padrão.
O driver é especifico de acordo com o banco de dados que você pretende utilizar, e com as suas ambições, alguns drivers vem com um toolkit para aplicações, aqui eu quero apenas fazer a conexão e usar a biblioteca padrão de SQL do Go para fazer as operações necessárias.
var DataSourceName = fmt.Sprintf("host=%s port=%s user=%s "+
"password=%s dbname=%s sslmode=disable", Host, Port, User, Password, DbName)
Essa é a criação da string completa com todas as informações necessárias para conectar e fazer operações.
A única coisa que não cheguei a mencionar for essa ultima parte da string "sslmode" quando criamos nosso banco de dados não fizemos modificação no SSL, então aqui o valor na string é "disable" pois esse é o valor padrão.
A função Sprintf envia strings formatadas, então iremos usar DataSourceName como argumento mais adiante.
O arquivo main.go fica fora da pasta dbconfig por isso precisamos importar aqui ./dbconfig e usamos uma variável para chamar os itens presentes no arquivo driverConfig.go.
Importamos também "fmt", o pacote SQL, e o github.com/lib/pq que é o driver do postgres temos um underscore na importação do driver pois não usaremos funções dele aqui, só iremos enviar a string de conexão.
Como já disse as libs de drivers são de terceiros então você precisará usar go get para fazer download do pacote.
go get github.com/lib/pq
Duas variáveis com package level scope db que aponta para o struct DB presente no pacote SQL e a err do tipo error que será usada em conjunto com a função checkErr para checagem de erros adiante.
package main
import (
"database/sql"
"fmt"
dbConfig "./dbconfig"
_ "github.com/lib/pq"
)
var db *sql.DB
var err error
Uma função bem simples para tratar erros durante o uso das queries.
func checkErr(err error) {
if err != nil {
panic(err.Error())
}
}
Na main func sinalizamos o acesso e você já pode ver como usamos dados importados do driverConfig.go usando o nome definido aqui na importação e chamando o nome original presente na constante.
Atribuímos as variáveis o valor da função de Open do sql que recebe como parâmetro, o driver e a dataSourceName com todas as constantes definidas como senha, host e etc.
Fazemos error handling para checar tudo ocorreu como esperado, pode ser usada também a função Ping() que bate no host do banco pra verificar a conexão.
O defer é um statement do Golang, ele serve pra fazer o adiamento de algo, abaixo de db.Close tem as funções que para manipular a nossa tabela, usar um defer aqui quer dizer: "Fecha a conexão com o banco de dados, mas só depois que terminar de executar essas chamadas de função".
func main() {
fmt.Printf("Accessing %s ... ", dbConfig.DbName)
db, err = sql.Open(dbConfig.PostgresDriver, dbConfig.DataSourceName)
if err != nil {
panic(err.Error())
} else {
fmt.Println("Connected!")
}
defer db.Close()
sqlSelect()
sqlSelectID()
sqlInsert()
sqlUpdate()
sqlDelete()
}
Para testar a conexão só precisamos comentar as chamadas de funções com "//" e também não podemos esquecer de deixar aberto o client do PostgreSQL pois precisamos bater no localhost para obter as respostas.
Output:
Accessing MobyDick ... Connected!
A estrutura original do SELECT que a maioria deve conhecer:
SELECT id, title, body FROM article
Ou poderia ser:
SELECT * FROM article
Significa, "Postgre, me da todas as informações dessa tabela aqui", isso é chamado de statement no SQL que você pode traduzir e chamar de "declaração", o SELECT seria apenas uma dessas declarações.
func sqlSelect() {
sqlStatement, err := db.Query("SELECT id, title, body FROM " + dbConfig.TableName)
checkErr(err)
for sqlStatement.Next() {
var article dbConfig.Article
err = sqlStatement.Scan(&article.ID, &article.Title, &article.Body)
checkErr(err)
fmt.Printf("%d\t%s\t%s \n", article.ID, article.Title, article.Body)
}
}
Temos um error handling.
A função db.Query retorna "rows" que são as linhas do sql, essas linhas precisam ser scaneadas uma a uma com auxilio de outras duas funções.
Usamos o for com sqlStatement.Next o Next prepara as linhas do DB uma a uma para serem lidas pelo sqlStatement.Scan.
Instanciámos article para passar os valores do DB para a struct, o método Scan recebe os valores da struct e transforma os tipos do PostgreSQL como VARCHAR, ID, TEXT em tipos do Go.
Depois de recuperados os dados printamos eles no console. Essa é a primeira linha que adicionei na tabela usando o pgAdmin.
Output:
Accessing MobyDick ... Connected!
1 Golang + PostgreSQL But I must explain to you how all this mistaken idea
Fazemos um SELECT para buscar apenas uma linha da tabela pelo seu id.
SELECT id, title, body FROM article WHERE id 1
"Postgre, me retorna id, title e body da tabela article onde o id é 1"
O Sprintf para enviar o sqlStatement para db.QueryRow que é usado quando se precisa retornar apenas uma linha da tabela, podendo receber mais argumentos além do statement, nesse caso "1" que é o id da linha que devemos retornar.
Em seguida a mesma estrutura de Scan e Print que usamos para resgatar os valores e imprimir o output.
func sqlSelectID() {
var article dbConfig.Article
sqlStatement := fmt.Sprintf("SELECT id, title, body FROM %s where id = $1", dbConfig.TableName)
err = db.QueryRow(sqlStatement, 1).Scan(&article.ID, &article.Title, &article.Body)
checkErr(err)
fmt.Printf("%d\t%s\t%s \n", article.ID, article.Title, article.Body)
}
Accessing MobyDick ... Connected!
1 Golang + PostgreSQL But I must explain to you how all this mistaken idea
Temos o mesmo resultado pois não fizemos alterações com a tabela e esse select usa o id "1" como parâmetro, vou mostrar mais um output usando "2" como id pra mostrar como fica a saída de erro.
Accessing MobyDick ... Connected!
panic: sql: no rows in result set
goroutine 1 [running]:
main.checkErr(...)
/home/vapordev/workspace/GoPostgres/main.go:114
main.sqlSelectID()
/home/vapordev/workspace/GoPostgres/main.go:58 +0x34b
main.main()
/home/vapordev/workspace/GoPostgres/main.go:29 +0x1aa
exit status 2
Ele conecta com o DB mas não retorna a linha pois ela não existe. Podemos ver tbm as linhas com o rastro do erro, linha 114 é onde está a nossa função checkErr() então toda fonte de erro começa nela, em seguida temos linha 58 que é exatamente onde o erro ocorreu e linha 29 que é de onde foi feita a chamada da função.
Um statement que insere informações na tabela, esse e os demais tem uma estrutura muito parecida pois nenhum precisará retornar uma tabela só precisaremos verificar se a ação foi executada com sucesso.
Criamos o statement:
INSERT INTO article VALUES ("o que você quer inserir separado por virgulas")
O db.Prepare cria uma instrução para queries ou execuções posteriores, várias queries ou execuções podem ser executadas de forma concorrente.
O db.Prepare atribuído na variável insert é usado com o método Exec que executa uma instrução preparada com os argumentos e retorna um Resultado. Nessa etapa é onde inserimos os dados que foram interpolados no statement ($1=id, $2=title, $3=body).
RowsAffected retorna o número de linhas afetadas pela operação. Nem todos os bancos suportam essa função, no caso o RowsAffected faz parte de uma interface result que tem outra função chamada LastInsertId() que nesse caso não funciona com o driver do postgres então vamos usar RowsAffected mesmo.
func sqlInsert() {
sqlStatement := fmt.Sprintf("INSERT INTO %s VALUES ($1,$2, $3)", dbConfig.TableName)
insert, err := db.Prepare(sqlStatement)
checkErr(err)
result, err := insert.Exec(5, "Maps in Golang", "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium")
checkErr(err)
affect, err := result.RowsAffected()
checkErr(err)
fmt.Println(affect)
}
A saida mostrando que a conecxão foi bem sucedida e que uma linha foi afetada, a partir daqui não temos retorno de linhas da tabela, mas se quisermos verificar o conteúdo do banco basta dar um novo select.
Output:
Accessing MobyDick ... Connected!
1
A saida após feito o insert e utilizado select novamente.
Output:
Accessing MobyDick ... Connected!
1 Golang + PostgreSQL But I must explain to you how all this mistaken idea
5 Maps in Golang Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium
A partir daqui as coisas ficam entediantes pois o que muda são os padrões de statement, mas o código tem pouca diferença.
Novamente preparamos uma string com o Statement da vez que é um UPDATE.
UPDATE article SET body WHERE id 5
"Vamos fazer uma modificação em article no campo body do elemento com o id numero 5"
func sqlUpdate() {
sqlStatement := fmt.Sprintf("update %s set body=$1 where id=$2", dbConfig.TableName)
update, err := db.Prepare(sqlStatement)
checkErr(err)
result, err := update.Exec("But I must explain to you how all this mistaken idea", 5)
checkErr(err)
affect, err := result.RowsAffected()
checkErr(err)
fmt.Println(affect)
}
Temos a mesma estrutura agora porém com apenas dois argumentos no updade.Exec o body e o id da linha que queremos modificar na tabela.
O identificador no final das contas ajuda muito nas buscas quando precisamos editar algo pois é mais fácil de usar como referência.
A saida após o UPDATE na linha que acabamos de inserir na tabela, nesse caso mudamos o body.
Output:
Accessing MobyDick ... Connected!
1
Aqui vou dar mais um SELECT pra mostrar o resultado da tabela após as alterações, você pode chamar a função de sqlSelect() que foi criada, dentro das outras funções pra verificar a tabela após a modificação se quiser.
Output:
Accessing MobyDick ... Connected!
1 Golang + PostgreSQL But I must explain to you how all this mistaken idea
5 Maps in Golang But I must explain to you how all this mistaken idea
O DELETE é ainda mais simples pois só precisamos do identificador para passar como referência do que queremos deletar.
DELETE FROM article WHERE id=1
"Deleta da tabela article o item de id 1"
func sqlDelete() {
sqlStatement := fmt.Sprintf("delete from %s where id=$1", dbConfig.TableName)
delete, err := db.Prepare(sqlStatement)
checkErr(err)
result, err := delete.Exec(5)
checkErr(err)
affect, err := result.RowsAffected()
checkErr(err)
fmt.Println(affect)
}
Aqui temos também o retorno de linhas afetadas e vou chamar o SELECT novamente para mostrar o estado atual da tabela.
Output:
Accessing MobyDick ... Connected!
1
Chamando o SELECT após as alterações.
Output:
Accessing MobyDick ... Connected!
1 Golang + PostgreSQL But I must explain to you how all this mistaken idea
Sobrou apenas a linha original.
Existem outras alterações com SQL que podem ser feitas com duas ou mais tabelas como JOINS, esse não é o caso então podemos deixar essa para uma proxima.