DEV Community

Cover image for Se conectando ao PostgreSQL usando Golang
Wagner Abrantes
Wagner Abrantes

Posted on • Updated on

Se conectando ao PostgreSQL usando Golang

Nem tudo precisa ser na CLI - Instalando o PostgreSQL e o pgAdmin4

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

);
Enter fullscreen mode Exit fullscreen mode

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');
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

Depois que conseguir damos um refresh no DB e vamos agora fazer um select para verificar nossos dados.

SELECT * FROM article
Enter fullscreen mode Exit fullscreen mode

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.

Database Driver Config

A estrutura das pastas é assim:

├── gopostgres
|    └── dbconfig
|        └── driverConfig.go
└── main.go
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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.

main.go

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Uma função bem simples para tratar erros durante o uso das queries.

func checkErr(err error) {
    if err != nil {
        panic(err.Error())
    }
}
Enter fullscreen mode Exit fullscreen mode

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()
}
Enter fullscreen mode Exit fullscreen mode

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!
Enter fullscreen mode Exit fullscreen mode

SELECT

A estrutura original do SELECT que a maioria deve conhecer:

SELECT id, title, body FROM article
Enter fullscreen mode Exit fullscreen mode

Ou poderia ser:

SELECT * FROM article
Enter fullscreen mode Exit fullscreen mode

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

SELECT BY ID

Fazemos um SELECT para buscar apenas uma linha da tabela pelo seu id.

SELECT id, title, body FROM article WHERE id 1
Enter fullscreen mode Exit fullscreen mode

"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)
}
Enter fullscreen mode Exit fullscreen mode
Accessing MobyDick ... Connected!

1   Golang + PostgreSQL But I must explain to you how all this mistaken idea
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

INSERT

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")
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

UPDATE

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
Enter fullscreen mode Exit fullscreen mode

"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)
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

DELETE

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
Enter fullscreen mode Exit fullscreen mode

"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)
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (1)

Collapse
 
nicmorais profile image
Nicolas Morais • Edited

O uso do (pseudo) tipo serial é desaconselhado pela Wiki oficial do Postgres, além disso, eu não sei como o pgAdmin cria o banco, mas se for com aspas duplas, não será uma boa ideia essas letras maiúsculas no futuro.

Some comments may only be visible to logged-in visitors. Sign in to view all comments.