Использование PostgreSQL COPY FROM STDIN

Можно ли использовать оператор PostgreSQL COPY FROM STDIN для загрузки данных из файла CSV путем передачи какого-либо объекта Reader или Writer так же, как это делается в Java? Какую библиотеку мне следует использовать? Пример Котлина для справки:

val cm = CopyManager(conn as BaseConnection)
val total = cm.copyIn("COPY my_table FROM STDIN FORMAT csv", inputStream)
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
115
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Глядя на две самые популярные библиотеки postgres Golang:

lib/pq

Драйвер поддерживает это с помощью методов стандартной библиотеки, но вам нужно будет использовать переменную io.Reader для передачи каждой строки CSV.

package main

import (
    "bufio"
    "context"
    "database/sql"
    "fmt"
    "io"
    "log"
    "os"
    "os/signal"

    "github.com/lib/pq"
)

func main() {
    ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
    defer cancel()

    if err := run(ctx); err != nil {
        log.Fatal(err)
    }
}

func run(ctx context.Context) error {
    // Open CSV file (this could also be os.Stdin, etc)
    f, err := os.Open("/tmp/csv")
    if err != nil {
        return err
    }
    defer f.Close()

    // Open database connection using pq driver
    db, err := sql.Open("postgres", "postgres://postgres@localhost:5432/postgres?sslmode=disable")
    if err != nil {
        return err
    }
    defer db.Close()

    // Execute copy
    if err = copyFrom(ctx, db, "my_table", f); err != nil {
        return err
    }

    return nil
}

func copyFrom(ctx context.Context, db *sql.DB, table string, r io.Reader) error {
    query := fmt.Sprintf("COPY %s FROM STDIN WITH (FORMAT csv)", pq.QuoteIdentifier(table))

    tx, err := db.BeginTx(ctx, nil)
    if err != nil {
        return err
    }
    defer tx.Rollback()

    stmt, err := tx.PrepareContext(ctx, query)
    if err != nil {
        return err
    }

    sc := bufio.NewScanner(r)
    for sc.Scan() {
        if _, err = stmt.ExecContext(ctx, sc.Text()); err != nil {
            return err
        }
    }
    if err = sc.Err(); err != nil {
        return err
    }

    if _, err = stmt.ExecContext(ctx); err != nil {
        return err
    }

    return tx.Commit()
}

Примечание

  1. lib/pq находится в режиме обслуживания
  2. Сообщалось о многочисленных гонках данных в базовом коде, обрабатывающем COPY операторы, по крайней мере один из которых все еще открыт

jackc/pgx

Тип нижнего уровня pgconn.PgConn имеет метод CopyFrom, который позволяет передать произвольный оператор и io.Reader. Если вы подключаетесь через пакет stdlib db, вы все равно можете получить доступ к базовому pgconn.PgConn, как показано ниже, хотя существуют и другие способы обработки соединений/пулов и т. д. при использовании pgx, так что стоит взглянуть и на них тоже. .

package main

import (
    "context"
    "database/sql"
    "fmt"
    "io"
    "log"
    "os"
    "os/signal"

    "github.com/jackc/pgx/v5"
    "github.com/jackc/pgx/v5/stdlib"
)

func main() {
    ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
    defer cancel()

    if err := run(ctx); err != nil {
        log.Fatal(err)
    }
}

func run(ctx context.Context) error {
    // Open CSV file (this could also be os.Stdin, etc)
    f, err := os.Open("/tmp/csv")
    if err != nil {
        return err
    }
    defer f.Close()

    // Open database connection using pgx driver
    db, err := sql.Open("pgx", "postgres://postgres@localhost:5432/postgres?sslmode=disable")
    if err != nil {
        return err
    }
    defer db.Close()

    // Execute copy
    if err = copyFrom(ctx, db, "my_table", f); err != nil {
        return err
    }

    return nil
}

func copyFrom(ctx context.Context, db *sql.DB, table string, r io.Reader) error {
    query := fmt.Sprintf("COPY %s FROM STDIN WITH (FORMAT csv)", pgx.Identifier{table}.Sanitize())

    conn, err := db.Conn(ctx)
    if err != nil {
        return err
    }
    defer conn.Close()

    return conn.Raw(func(driverConn any) error {
        pgConn := driverConn.(*stdlib.Conn).Conn().PgConn()
        _, err := pgConn.CopyFrom(ctx, r, query)
        return err
    })
}

Другие вопросы по теме