Spec-Zone.ru › DuckDB

API ADBC

Arrow Database Connectivity (ADBC), аналогично ODBC и JDBC, представляет собой API в стиле C, что обеспечивает переносимость кода между различными системами баз данных. Это позволяет разработчикам без труда создавать приложения, взаимодействующие с системами баз данных, не используя код, специфичный для конкретной системы. Основное различие между ADBC и ODBC/JDBC заключается в том, что ADBC использует Arrow для передачи данных между системой базы данных и приложением. DuckDB имеет драйвер ADBC, который использует преимущества безопасной интеграции DuckDB и Arrow для эффективной передачи данных.

Драйвер ADBC DuckDB в настоящее время поддерживает версию 0.7 ADBC.

Для более подробного обсуждения ADBC и подробного объяснения API, обратитесь к странице документации ADBC.

Реализованные Функциональности

Драйвер DuckDB-ADBC реализует полную спецификацию ADBC, за исключением функций ConnectionReadPartition и StatementExecutePartitions. Обе эти функции предназначены для поддержки систем, которые внутренне разбивают результаты запроса, что не применяется к DuckDB. В этом разделе мы опишем основные функции, которые существуют в ADBC, вместе с аргументами, которые они принимают, и предоставим примеры для каждой функции.

База данных

Набор функций, которые работают с базой данных.

Имя функции Описание Аргументы Пример
DatabaseNew Выделить новую (но не инициализированную) базу данных. (AdbcDatabase *database, AdbcError *error) AdbcDatabaseNew(&adbc_database, &adbc_error)
DatabaseSetOption Установить параметр char*. (AdbcDatabase *database, const char *key, const char *value, AdbcError *error) AdbcDatabaseSetOption(&adbc_database, "path", "test.db", &adbc_error)
DatabaseInit Завершить настройку параметров и инициализировать базу данных. (AdbcDatabase *database, AdbcError *error) AdbcDatabaseInit(&adbc_database, &adbc_error)
DatabaseRelease Уничтожить базу данных. (AdbcDatabase *database, AdbcError *error) AdbcDatabaseRelease(&adbc_database, &adbc_error)

Подключение

Набор функций, которые создают и уничтожают подключение для взаимодействия с базой данных.

Имя функции Описание Аргументы Пример
ConnectionNew Выделить новое (но не инициализированное) подключение. (AdbcConnection*, AdbcError*) AdbcConnectionNew(&adbc_connection, &adbc_error)
ConnectionSetOption Параметры могут быть установлены до ConnectionInit. (AdbcConnection*, const char*, const char*, AdbcError*) AdbcConnectionSetOption(&adbc_connection, ADBC_CONNECTION_OPTION_AUTOCOMMIT, ADBC_OPTION_VALUE_DISABLED, &adbc_error)
ConnectionInit Завершить настройку параметров и инициализировать подключение. (AdbcConnection*, AdbcDatabase*, AdbcError*) AdbcConnectionInit(&adbc_connection, &adbc_database, &adbc_error)
ConnectionRelease Уничтожить это подключение. (AdbcConnection*, AdbcError*) AdbcConnectionRelease(&adbc_connection, &adbc_error)

Набор функций, которые извлекают метаданные о базе данных. Как правило, эти функции возвращают объекты Arrow, а именно ArrowArrayStream.

Имя функции Описание Аргументы Пример
ConnectionGetObjects Получить иерархический вид всех каталогов, схем баз данных, таблиц и столбцов. (AdbcConnection*, int, const char*, const char*, const char*, const char**, const char*, ArrowArrayStream*, AdbcError*) AdbcDatabaseInit(&adbc_database, &adbc_error)
ConnectionGetTableSchema Получить схему Arrow таблицы. (AdbcConnection*, const char*, const char*, const char*, ArrowSchema*, AdbcError*) AdbcDatabaseRelease(&adbc_database, &adbc_error)
ConnectionGetTableTypes Получить список типов таблиц в базе данных. (AdbcConnection*, ArrowArrayStream*, AdbcError*) AdbcDatabaseNew(&adbc_database, &adbc_error)

Набор функций с семантикой транзакций для подключения. По умолчанию все подключения начинаются в режиме автоматического подтверждения, но это можно отключить с помощью функции ConnectionSetOption.

Имя функции Описание Аргументы Пример
ConnectionCommit Подтвердить все ожидающие транзакции. (AdbcConnection*, AdbcError*) AdbcConnectionCommit(&adbc_connection, &adbc_error)
ConnectionRollback Отменить все ожидающие транзакции. (AdbcConnection*, AdbcError*) AdbcConnectionRollback(&adbc_connection, &adbc_error)

Запрос

Запросы хранят состояние, связанное с выполнением запроса. Они представляют как одноразовые запросы, так и подготовленные запросы. Они могут быть повторно использованы; однако, это сделает устаревшими предыдущие наборы результатов от данного запроса.

Функции, используемые для создания, уничтожения и настройки параметров запроса:

Имя функции Описание Аргументы Пример
StatementNew Создать новый запрос для данного подключения. (AdbcConnection*, AdbcStatement*, AdbcError*) AdbcStatementNew(&adbc_connection, &adbc_statement, &adbc_error)
StatementRelease Уничтожить запрос. (AdbcStatement*, AdbcError*) AdbcStatementRelease(&adbc_statement, &adbc_error)
StatementSetOption Установить строковый параметр запроса. (AdbcStatement*, const char*, const char*, AdbcError*) StatementSetOption(&adbc_statement, ADBC_INGEST_OPTION_TARGET_TABLE, "TABLE_NAME", &adbc_error)

Функции, связанные с выполнением запроса:

Имя функции Описание Аргументы Пример
StatementSetSqlQuery Установить SQL-запрос для выполнения. Запрос можно выполнить с помощью StatementExecuteQuery. (AdbcStatement*, const char*, AdbcError*) AdbcStatementSetSqlQuery(&adbc_statement, "SELECT * FROM TABLE", &adbc_error)
StatementSetSubstraitPlan Установить план субстрата для выполнения. Запрос можно выполнить с помощью StatementExecuteQuery. (AdbcStatement*, const uint8_t*, size_t, AdbcError*) AdbcStatementSetSubstraitPlan(&adbc_statement, substrait_plan, length, &adbc_error)
StatementExecuteQuery Выполнить запрос и получить результаты. (AdbcStatement*, ArrowArrayStream*, int64_t*, AdbcError*) AdbcStatementExecuteQuery(&adbc_statement, &arrow_stream, &rows_affected, &adbc_error)
StatementPrepare Преобразовать этот запрос в подготовленный запрос для многократного выполнения. (AdbcStatement*, AdbcError*) AdbcStatementPrepare(&adbc_statement, &adbc_error)

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

Имя функции Описание Аргументы Пример
StatementBindStream Привязать поток Arrow. Это может быть использовано для массовой вставки или подготовленных запросов. (AdbcStatement*, ArrowArrayStream*, AdbcError*) StatementBindStream(&adbc_statement, &input_data, &adbc_error)

Примеры

Независимо от используемого языка программирования, для использования ADBC с DuckDB потребуются два параметра базы данных. Первый — driver, который принимает путь к библиотеке DuckDB. Второй параметр — entrypoint, который представляет собой экспортированную функцию из драйвера DuckDB-ADBC, инициализирующую все функции ADBC. После настройки этих двух параметров можно (необязательно) установить параметр path, указав путь на диске для хранения базы данных DuckDB. Если не указано, создается база данных в оперативной памяти. После настройки всех необходимых параметров можно приступить к инициализации базы данных. Ниже приведен способ сделать это в различных средах программирования.

C++

Начнём наш пример на C++ объявлением необходимых переменных для запроса данных через ADBC. Эти переменные включают Error, Database, Connection, обработку Statement и поток Arrow для передачи данных между DuckDB и приложением.

AdbcError adbc_error;
AdbcDatabase adbc_database;
AdbcConnection adbc_connection;
AdbcStatement adbc_statement;
ArrowArrayStream arrow_stream;

Затем мы можем инициализировать нашу переменную базы данных. Перед инициализацией базы данных необходимо установить параметры driver и entrypoint, как указано выше. Затем мы устанавливаем параметр path и инициализируем базу данных. В примере ниже строка "path/to/libduckdb.dylib" должна быть путем к динамической библиотеке DuckDB. Она будет .dylib на macOS и .so на Linux.

AdbcDatabaseNew(&adbc_database, &adbc_error);
AdbcDatabaseSetOption(&adbc_database, "driver", "path/to/libduckdb.dylib", &adbc_error);
AdbcDatabaseSetOption(&adbc_database, "entrypoint", "duckdb_adbc_init", &adbc_error);
// By default, we start an in-memory database, but you can optionally define a path to store it on disk.
AdbcDatabaseSetOption(&adbc_database, "path", "test.db", &adbc_error);
AdbcDatabaseInit(&adbc_database, &adbc_error);

После инициализации базы данных необходимо создать и инициализировать подключение к ней.

AdbcConnectionNew(&adbc_connection, &adbc_error);
AdbcConnectionInit(&adbc_connection, &adbc_database, &adbc_error);
END_OF_DOCUMENT_MARKER

Теперь мы можем инициализировать наше выражение и выполнить запросы через наше соединение. После AdbcStatementExecuteQuery поле arrow_stream заполняется результатом.

AdbcStatementNew(&adbc_connection, &adbc_statement, &adbc_error);
AdbcStatementSetSqlQuery(&adbc_statement, "SELECT 42", &adbc_error);
int64_t rows_affected;
AdbcStatementExecuteQuery(&adbc_statement, &arrow_stream, &rows_affected, &adbc_error);
arrow_stream.release(arrow_stream)

Помимо выполнения запросов, мы также можем вводить данные через arrow_streams. Для этого необходимо установить параметр с именем таблицы, в которую нужно вставить данные, связать поток и затем выполнить запрос.

StatementSetOption(&adbc_statement, ADBC_INGEST_OPTION_TARGET_TABLE, "AnswerToEverything", &adbc_error);
StatementBindStream(&adbc_statement, &arrow_stream, &adbc_error);
StatementExecuteQuery(&adbc_statement, nullptr, nullptr, &adbc_error);

Python

В первую очередь необходимо использовать pip и установить менеджер драйверов ADBC. Вам также потребуется установить pyarrow для прямого доступа к наборам результатов в формате Apache Arrow (например, используя fetch_arrow_table).

pip install adbc_driver_manager pyarrow

Дополнительную информацию о пакете adbc_driver_manager см. в adbc_driver_manager документации по пакету.

Как и в C++, нам необходимо предоставить параметры инициализации, содержащие расположение разделяемой библиотеки libduckdb и функцию входа. Обратите внимание, что аргумент path для DuckDB передаётся через словарь db_kwargs.

import adbc_driver_duckdb.dbapi

with adbc_driver_duckdb.dbapi.connect("test.db") as conn, conn.cursor() as cur:
    cur.execute("SELECT 42")
    # fetch a pyarrow table
    tbl = cur.fetch_arrow_table()
    print(tbl)

Наряду с fetch_arrow_table, на курсоре также реализованы другие методы из DBApi, такие как fetchone и fetchall. Данные также можно вводить через arrow_streams. Нам просто нужно установить параметры в выражении, чтобы связать поток данных и выполнить запрос.

import adbc_driver_duckdb.dbapi
import pyarrow

data = pyarrow.record_batch(
    [[1, 2, 3, 4], ["a", "b", "c", "d"]],
    names = ["ints", "strs"],
)

with adbc_driver_duckdb.dbapi.connect("test.db") as conn, conn.cursor() as cur:
    cur.adbc_ingest("AnswerToEverything", data)

Go

Сначала убедитесь, что загружена библиотека libduckdb (например, .so в Linux, .dylib в Mac или .dll в Windows) со страницы релизов и поместите её в свою LD_LIBRARY_PATH перед запуском кода (в противном случае ошибка объяснит вам варианты относительно расположения этого файла).

Следующий пример использует базу данных DuckDB в оперативной памяти для изменения наборов записей Arrow в оперативной памяти с помощью SQL-запросов:

package main

import (
    "bytes"
    "context"
    "fmt"
    "io"

    "github.com/apache/arrow-adbc/go/adbc"
    "github.com/apache/arrow-adbc/go/adbc/drivermgr"
    "github.com/apache/arrow/go/v17/arrow"
    "github.com/apache/arrow/go/v17/arrow/array"
    "github.com/apache/arrow/go/v17/arrow/ipc"
    "github.com/apache/arrow/go/v17/arrow/memory"
)

func _makeSampleArrowRecord() arrow.Record {
    b := array.NewFloat64Builder(memory.DefaultAllocator)
    b.AppendValues([]float64{1, 2, 3}, nil)
    col := b.NewArray()

    defer col.Release()
    defer b.Release()

    schema := arrow.NewSchema([]arrow.Field{{Name: "column1", Type: arrow.PrimitiveTypes.Float64}}, nil)
    return array.NewRecord(schema, []arrow.Array{col}, int64(col.Len()))
}

type DuckDBSQLRunner struct {
    ctx  context.Context
    conn adbc.Connection
    db   adbc.Database
}

func NewDuckDBSQLRunner(ctx context.Context) (*DuckDBSQLRunner, error) {
    var drv drivermgr.Driver
    db, err := drv.NewDatabase(map[string]string{
        "driver":     "duckdb",
        "entrypoint": "duckdb_adbc_init",
        "path":       ":memory:",
    })
    if err != nil {
        return nil, fmt.Errorf("failed to create new in-memory DuckDB database: %w", err)
    }
    conn, err := db.Open(ctx)
    if err != nil {
        return nil, fmt.Errorf("failed to open connection to new in-memory DuckDB database: %w", err)
    }
    return &DuckDBSQLRunner{ctx: ctx, conn: conn, db: db}, nil
}

func serializeRecord(record arrow.Record) (io.Reader, error) {
    buf := new(bytes.Buffer)
    wr := ipc.NewWriter(buf, ipc.WithSchema(record.Schema()))
    if err := wr.Write(record); err != nil {
        return nil, fmt.Errorf("failed to write record: %w", err)
    }
    if err := wr.Close(); err != nil {
        return nil, fmt.Errorf("failed to close writer: %w", err)
    }
    return buf, nil
}

func (r *DuckDBSQLRunner) importRecord(sr io.Reader) error {
    rdr, err := ipc.NewReader(sr)
    if err != nil {
        return fmt.Errorf("failed to create IPC reader: %w", err)
    }
    defer rdr.Release()
    stmt, err := r.conn.NewStatement()
    if err != nil {
        return fmt.Errorf("failed to create new statement: %w", err)
    }
    if err := stmt.SetOption(adbc.OptionKeyIngestMode, adbc.OptionValueIngestModeCreate); err != nil {
        return fmt.Errorf("failed to set ingest mode: %w", err)
    }
    if err := stmt.SetOption(adbc.OptionKeyIngestTargetTable, "temp_table"); err != nil {
        return fmt.Errorf("failed to set ingest target table: %w", err)
    }
    if err := stmt.BindStream(r.ctx, rdr); err != nil {
        return fmt.Errorf("failed to bind stream: %w", err)
    }
    if _, err := stmt.ExecuteUpdate(r.ctx); err != nil {
        return fmt.Errorf("failed to execute update: %w", err)
    }
    return stmt.Close()
}

func (r *DuckDBSQLRunner) runSQL(sql string) ([]arrow.Record, error) {
    stmt, err := r.conn.NewStatement()
    if err != nil {
        return nil, fmt.Errorf("failed to create new statement: %w", err)
    }
    defer stmt.Close()

    if err := stmt.SetSqlQuery(sql); err != nil {
        return nil, fmt.Errorf("failed to set SQL query: %w", err)
    }
    out, n, err := stmt.ExecuteQuery(r.ctx)
    if err != nil {
        return nil, fmt.Errorf("failed to execute query: %w", err)
    }
    defer out.Release()

    result := make([]arrow.Record, 0, n)
    for out.Next() {
        rec := out.Record()
        rec.Retain() // .Next() will release the record, so we need to retain it
        result = append(result, rec)
    }
    if out.Err() != nil {
        return nil, out.Err()
    }
    return result, nil
}

func (r *DuckDBSQLRunner) RunSQLOnRecord(record arrow.Record, sql string) ([]arrow.Record, error) {
    serializedRecord, err := serializeRecord(record)
    if err != nil {
        return nil, fmt.Errorf("failed to serialize record: %w", err)
    }
    if err := r.importRecord(serializedRecord); err != nil {
        return nil, fmt.Errorf("failed to import record: %w", err)
    }
    result, err := r.runSQL(sql)
    if err != nil {
        return nil, fmt.Errorf("failed to run SQL: %w", err)
    }

    if _, err := r.runSQL("DROP TABLE temp_table"); err != nil {
        return nil, fmt.Errorf("failed to drop temp table after running query: %w", err)
    }
    return result, nil
}

func (r *DuckDBSQLRunner) Close() {
    r.conn.Close()
    r.db.Close()
}

func main() {
    rec := _makeSampleArrowRecord()
    fmt.Println(rec)

    runner, err := NewDuckDBSQLRunner(context.Background())
    if err != nil {
        panic(err)
    }
    defer runner.Close()

    resultRecords, err := runner.RunSQLOnRecord(rec, "SELECT column1+1 FROM temp_table")
    if err != nil {
        panic(err)
    }

    for _, resultRecord := range resultRecords {
        fmt.Println(resultRecord)
        resultRecord.Release()
    }
}

Запуск этого примера даёт следующий результат:

record:
  schema:
  fields: 1
    - column1: type=float64
  rows: 3
  col[0][column1]: [1 2 3]

record:
  schema:
  fields: 1
    - (column1 + 1): type=float64, nullable
  rows: 3
  col[0][(column1 + 1)]: [2 3 4]

© Copyright 2018–2024 Stichting DuckDB Foundation
Licensed under the MIT License.
https://duckdb.org/docs/api/adbc.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API