Перейти к содержанию

picodata-go

В данном разделе приведено описание picodata-go — Golang-драйвера для работы с СУБД Picodata.

Общие сведения

Драйвер использует библиотеку pgxpool и предоставляет публичный API для удобной работы с кластерной СУБД.

Подключение

Пример подключения драйвера использования его в коде Golang-приложения:

package main

import (
    "context"
    "fmt"
    "os"

    picogo "github.com/picodata/picodata-go"
    logger "github.com/picodata/picodata-go/logger"
    strats "github.com/picodata/picodata-go/strategies"
)

func main() {
    // export PICODATA_CONNECTION_URL=postgres://username:password@host:port
    pool, err := picogo.New(context.Background(), os.Getenv("PICODATA_CONNECTION_URL"), picogo.WithBalanceStrategy(strats.NewRoundRobinStrategy()), picogo.WithLogLevel(logger.LevelError))
    if err != nil {
        fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err)
        os.Exit(1)
    }
    defer pool.Close()
    /*
     CREATE TABLE items (id INTEGER NOT NULL,name TEXT NOT NULL,stock INTEGER,PRIMARY KEY (id)) USING memtx DISTRIBUTED BY (id) OPTION (TIMEOUT = 3.0);
     INSERT INTO items VALUES
     (1, 'bricks', 1123),
     (2, 'panels', 998),
     (3, 'piles', 177);
    */
    var (
        id    int
        name  string
        stock int
    )
    err = pool.QueryRow(context.Background(), "select * from items where id=$1", 2).Scan(&id, &name, &stock)
    if err != nil {
        fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
        os.Exit(1)
    }

    fmt.Println(id, name, stock)
}

Поддерживаемые возможности

Golang-драйвер для Picodata поддерживает следующие возможности:

  • автоматическое обнаружение инстансов кластера — достаточно указать адрес одного узла кластера, и драйвер сам наполнит пул соединений на основе всей топологии.
  • открытый API, с помощью которого можно балансировать нагрузку на кластер — несколько стандартных стратегий балансировки уже доступны "из коробки", также реализован API для реализации и использования своих собственных алгоритмов.
  • автоматическое управление топологией кластера — драйвер самостоятельно следит за состоянием кластера и его топологией.

Проверка работы

Мы предоставляем тестовое Go-приложение, которое позволяет проверить функциональность Golang-драйвер при работе с Picodata. Тестовое приложение:

  • поднимает тестовый кластер посредством docker-compose
  • подключается к кластеру с помощью Golang-драйвера
  • позволяет выполнять CRUD-операции для тестовой таблицы в Picodata

Для проверки работы тестового приложения потребуются компилятор языка Go версии 1.24 или новее, и Docker.

Порядок действий:

  1. Склонируйте репозиторий библиотеки и перейдите в директорию с тестовым приложением:

    git clone https://github.com/picodata/picodata-go && cd picodata-go/examples/crud
    

  2. Запустите контейнеры с тестовым кластером Picodata:

    docker-compose up -d
    

  3. Запустите приложение:

    go run main.go
    

Примечание

Попробуйте изменить LogLevel в коде тестового приложения и посмотрите как изменится вывод:

picogo.WithLogLevel(logger.LevelError)
заменить на
picogo.WithLogLevel(logger.LevelDebug)

Результатом успешной работы будет вставка строки в тестовую таблицу и вывод содержимого таблицы. Введите слово help и нажмите Enter. На экране будет показан список всех доступных команд и пример использования.

(db) > help
...
(db) > list
(db) > add 'PicoData'
(db) > list
1581935770 -> 'PicoData'
(db) > update 1581935770 'Learn Go'
(db) > list
1581935770 -> 'Learn Go'

Структура приложения

Ниже показано дерево файлов минимального тестового приложения:

├── docker-compose.yaml
├── go.mod
├── go.sum
└── main.go

Содержимое файлов тестового приложения:

docker-compose.yaml
---
version: '3'

services:
picodata-1:
    image: docker-public.binary.picodata.io/picodata:25.2.3
    container_name: picodata-1
    hostname: picodata-1
    environment:
        PICODATA_INSTANCE_NAME: picodata-1
        PICODATA_INSTANCE_DIR: picodata-1
        PICODATA_IPROTO_LISTEN: picodata-1:3301
        PICODATA_IPROTO_ADVERTISE: picodata-1:3301
        PICODATA_PEER: picodata-1:3301
        PICODATA_PG_LISTEN: picodata-1:4327
        PICODATA_PG_SSL: "false"
        PICODATA_ADMIN_PASSWORD: "T0psecret"
    ports:
    - "3301:3301"
    - "4327:4327"

picodata-2:
    image: docker-public.binary.picodata.io/picodata:25.2.3
    container_name: picodata-2
    hostname: picodata-2
    depends_on:
    - picodata-1
    environment:
        PICODATA_INSTANCE_NAME: picodata-2
        PICODATA_INSTANCE_DIR: picodata-2
        PICODATA_IPROTO_LISTEN: picodata-2:3302
        PICODATA_IPROTO_ADVERTISE: picodata-2:3302
        PICODATA_PG_LISTEN: picodata-2:4328
        PICODATA_PG_SSL: "false"
        PICODATA_PEER: picodata-1:3301
    ports:
    - "3302:3302"
    - "4328:4328"


picodata-3:
    image: docker-public.binary.picodata.io/picodata:25.2.3
    container_name: picodata-3
    hostname: picodata-3
    depends_on:
    - picodata-1
    environment:
        PICODATA_INSTANCE_NAME: picodata-3
        PICODATA_INSTANCE_DIR: picodata-3
        PICODATA_IPROTO_LISTEN: picodata-3:3303
        PICODATA_IPROTO_ADVERTISE: picodata-3:3303
        PICODATA_PG_LISTEN: picodata-3:4329
        PICODATA_PG_SSL: "false"
        PICODATA_PEER: picodata-1:3301
    ports:
    - "3303:3303"
    - "4329:4329"
main.go
package main

import (
    "bufio"
    "context"
    "fmt"
    "math/rand/v2"
    "os"
    "strconv"
    "strings"

    picogo "github.com/picodata/picodata-go"
    "github.com/picodata/picodata-go/logger"
    strats "github.com/picodata/picodata-go/strategies"
)

const (
    // In production, you may use environment variable.
    // We expose connection url here to keep example simple.
    CONNECTION_URL = "postgres://admin:T0psecret@localhost:4327"

    // Query to create table to store and get data from.
    QUERY_CREATE_TASKS_TABLE = "create table if not exists tasks (id integer primary key,description text not null)"
)

// Pool is a global variable to keep example simple.
var pool *picogo.Pool

func main() {
    var err error

    // Connect to the Picodata database.
    pool, err = picogo.New(context.Background(), CONNECTION_URL, picogo.WithBalanceStrategy(strats.NewRoundRobinStrategy()), picogo.WithLogLevel(logger.LevelError))
    if err != nil {
        fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err)
        os.Exit(1)
    }
    defer pool.Close()

    // Create test table "tasks" for storing and reading data.
    _, err = pool.Exec(context.Background(), QUERY_CREATE_TASKS_TABLE)
    if err != nil {
        fmt.Fprintf(os.Stderr, "Unable to create table: %v\n", err)
        os.Exit(1)
    }

    reader := bufio.NewReader(os.Stdin)

    // Read user's commands from terminal and execute them.
    for {
        fmt.Print("(db) > ")
        line, err := reader.ReadString('\n')
        if err != nil {
            fmt.Fprintf(os.Stderr, "Error while reading command: %v\n", err)
            continue
        }

        line = strings.TrimSpace(line)

        args := strings.Split(line, " ")

        // Perform CRUD operations.
        switch args[0] {
        case "help":
            printHelp()
            continue
        case "list":
            err = listTasks()
            if err != nil {
                fmt.Fprintf(os.Stderr, "Unable to list tasks: %v\n", err)
                continue
            }
        case "add":
            err = addTask(strings.Join(args[1:], " "))
            if err != nil {
                fmt.Fprintf(os.Stderr, "Unable to add task: %v\n", err)
                continue
            }
        case "update":
            n, err := strconv.ParseInt(args[1], 10, 32)
            if err != nil {
                fmt.Fprintf(os.Stderr, "Unable convert task_num into int32: %v\n", err)
                continue
            }
            err = updateTask(int32(n), strings.Join(args[2:], " "))
            if err != nil {
                fmt.Fprintf(os.Stderr, "Unable to update task: %v\n", err)
                continue
            }
        case "remove":
            n, err := strconv.ParseInt(args[1], 10, 32)
            if err != nil {
                fmt.Fprintf(os.Stderr, "Unable convert task_num into int32: %v\n", err)
                continue
            }
            err = removeTask(int32(n))
            if err != nil {
                fmt.Fprintf(os.Stderr, "Unable to remove task: %v\n", err)
                continue
            }
        case "exit":
            return
        default:
            fmt.Fprintln(os.Stderr, "Invalid command")
            printHelp()
            continue
        }
    }

}

func listTasks() error {
    rows, _ := pool.Query(context.Background(), "select * from tasks")

    for rows.Next() {
        var id int32
        var description string
        err := rows.Scan(&id, &description)
        if err != nil {
            return err
        }
        fmt.Printf("%d -> %s\n", id, description)
    }

    return rows.Err()
}

func addTask(description string) error {
    id := rand.Int32()
    _, err := pool.Exec(context.Background(), "insert into tasks values($1, $2)", id, description)
    return err
}

func updateTask(itemNum int32, description string) error {
    _, err := pool.Exec(context.Background(), "update tasks set description=$1 where id=$2", description, itemNum)
    return err
}

func removeTask(itemNum int32) error {
    _, err := pool.Exec(context.Background(), "delete from tasks where id=$1", itemNum)
    return err
}

func printHelp() {
    fmt.Print(`

Commands:

    help
    list
    add string
    update id string
    remove id
    exit

Example:

    add 'pico data'
    list

`)
}
go.mod
module picotestapp

go 1.24.5

require github.com/picodata/picodata-go v1.0.0

require (
    github.com/jackc/pgpassfile v1.0.0 // indirect
    github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
    github.com/jackc/pgx/v5 v5.7.2 // indirect
    github.com/jackc/puddle/v2 v2.2.2 // indirect
    golang.org/x/crypto v0.31.0 // indirect
    golang.org/x/sync v0.10.0 // indirect
    golang.org/x/text v0.21.0 // indirect
)
go.sum

Генерируется автоматически.