Миграция базы данных в Go при помощи пакета Migrate

В этом уроке вы узнаете, как использовать инструмент миграции (написанный на Go и довольно популярный в сообществе Go) для выполнения миграции базы данных. Во второй части вы напишете код Go для чтения данных из базы данных.

В этой статье вы собираетесь использовать PostgreSQL в качестве базы данных, но миграция совместима со многими другими базами данных.

Настройка

Установка пакета

migrate — это инструмент командной строки, который используется для запуска миграции. Его также можно использовать программно, но в этом уроке мы собираемся использовать его через интерфейс командной строки. Существует несколько способов установки migrate командной строки, например Brew, Scoop и т. д. Ознакомьтесь с документацией по установке, чтобы увидеть все доступные варианты.

Мы собираемся установить его с помощью go install.

go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest

База данных

Для запуска примера базы данных Postgres мы предполагаем что у вас уже установлена база данных, локально или в docker контейнере.

Создание миграций

Вы собираетесь создать posts таблицу, в которой будет два столбца: заголовок и тело.

Чтобы создать новую миграцию, выполните migrate create команду с соответствующими параметрами.

migrate create -ext sql -dir db/migrations create_posts_table
  • ext указывает расширение файла, которое будет использоваться при создании файла миграции.
  • dir указывает, в каком каталоге создавать миграции.

Это создаст две миграции в db/migrations папке, соответствующей шаблону: <timestamp>_create_posts_table.down.sql и <timestamp>_create_posts_table.up.sql.

  • Миграция UP будет содержать sql для создания таблицы сообщений.
  • Миграция ВНИЗ будет содержать sql для возврата к тому, что было сделано при миграции вверх .

SQL

В <timestamp>_create_posts_table.up.sql файле миграции напишите sql для создания таблицы сообщений .

CREATE TABLE IF NOT EXISTS posts (title varchar, body varchar);

В <timestamp>_create_posts_table.down.sql файле миграции напишите sql для удаления таблицы сообщений .

DROP TABLE IF EXISTS posts;

Запуск миграции

Для миграции нужен способ подключения базы данных для выполнения операторов sql. Для этого вам понадобится действительная строка подключения Postgres следующего формата:

postgres://<username>:<password>@localhost:<port>/<db_name>?sslmode=disable

Если вы используете базу данных PostgreSQL, строка подключения будет выглядеть следующим образом:

postgres://postgres:postgres@localhost:5454/postgres?sslmode=disable

Для запуска миграции используйте migrate up команду с соответствующими параметрами.

export DB_URL='postgres://postgres:postgres@localhost:5454/postgres?sslmode=disable'

# Run migrations
migrate -database ${DB_URL} -path db/migrations up

Откат миграций

Чтобы откатить все миграции, т.е. выполнить все *.down.sql файлы, используйте migration down команду.

export DB_URL='postgres://postgres:postgres@localhost:5454/postgres?sslmode=disable'

migrate -database ${DB_URL} -path db/migrations down

Написание кода

Теперь, когда у вас есть posts таблица в базе данных, вы собираетесь написать код Go для доступа к данным. Это необязательный раздел, можете его пропустить.

Для доступа к базе данных postgres нам понадобится драйвер базы данных, давайте воспользуемся популярным пакетом pq .

go get github.com/lib/pq

Устанавливаем соединение с базой данных.

package main

import (
	"database/sql"
    "log"
    _ "github.com/lib/pq"
)

func main() {
	// credentials used from the compose setup previously
	db, err := sql.Open("postgres", "postgres://postgres:postgres@localhost:5454/postgres?sslmode=disable")
	if err != nil {
		log.Fatal(err)
	}

	defer db.Close()
    
    if err := db.Ping(); err != nil {
		log.Fatal("Failed to ping db", err)
	}
}

Создайте структуру для представления строки post таблицы.

type Post struct {
	Title string
	Body  string
}

Выбор всех сообщений из базы данных и их отображение.

package main

import (
	"database/sql"
    "log"
    "fmt"
    _ "github.com/lib/pq"
)

type Post struct {
	Title string
	Body  string
}

func main() {
	...
    // previous code of db connection setup
    ...
    
    // query all the posts
	result, err := db.Query("SELECT * FROM posts;")
	if err != nil {
		log.Fatal(err)
	}

	defer result.Close()

	// iterate over all the posts and print them
	for result.Next() {
		var p Post

		err := result.Scan(&p.Title, &p.Body)
		if err != nil {
			log.Fatal(err)
		}

		fmt.Printf("%+v\n", p)
	}
}