DuckDB 不僅提供 CLI 和 Python 整合,更支援多種程式語言的客戶端 API,涵蓋 C/C++、Java、Julia、R、Node.js、Rust 和 Swift,以及 ODBC 標準。這些 API 各有其適用場景,例如 C++ API 適合開發向量化使用者自定義函式,R API 則與 R 的 DBI 介面和 dplyr 生態系統無縫整合。針對大量資料載入,使用準備好的 SQL 陳述式能有效提升效能和安全性,避免 SQL 注入風險。

附錄:DuckDB 的客戶端 API

到目前為止,我們主要關注了 DuckDB CLI 和 Python 整合。CLI 是最簡單、最快速啟動 DuckDB 資料函式庫的方式,同時也是教學的最佳選擇。Python 整合則自然而然地被廣泛使用,因為許多分析應用場景都源自於 Python 生態系統。

然而,一本文的篇幅有限,因此在本附錄中,我們將更簡潔地介紹其他整合方式。如果您對其他語言的整合感興趣,我們假設您已經熟悉 Java 的類別路徑和依賴管理。

本附錄涵蓋以下內容:

  • DuckDB 的替代客戶端 API
  • 這些 API 的適用場景
  • 關於平行性的簡要說明
  • 如何透過客戶端 API 載入大量資料
  • JDBC 整合的展示

官方支援的語言

DuckDB 官方支援以下語言:

C 語言

DuckDB 實作了一個自定義的 C API,在很大程度上遵循了 SQLite C API 的設計。該 API 包含在 duckdb.h 標頭檔中。此外,還提供了一個完整的 SQLite API 包裝器,可以用來將現有的 SQLite 程式重新連結到 DuckDB。C API 被釋出為 libduckdb

C++ 語言

由於 DuckDB 是用 C++ 編寫的,因此 C++ 整合是很自然的。它同樣被釋出為 libduckdb。值得注意的是,如果您需要提供向量化的使用者自定義函式(UDF),應該使用這個 API。

Java 語言

如本附錄所述,DuckDB 提供了一個 JDBC 驅動程式(org.duckdb:duckdb_jdbc),可以透過 Maven 中央倉函式庫取得。該驅動程式包含了主要作業系統上的 DuckDB 二進位制檔案,可以在您的 Java 程式中使用。

Julia 語言

這個整合將在與 Julia 客戶端相同的程式中執行,並完全支援 DBInterface 介面。DuckDB 提供了原生的 Julia DataFrames,可以無縫地繼續在科學計算語言中進行分析工作。該整合以 DuckDB 軟體包的形式提供。

Python 語言

正如本文中多次展示的那樣,DuckDB 與 Python 生態系統整合良好,允許您直接在 Python 程式和筆記本中執行分析,甚至可以與 Pandas DataFrames 無縫互動。該整合以 duckdb 軟體包的形式提供。

Node.js 語言

提供了一個類別似於 SQLite API 的介面。值得注意的是,該 API 暴露了 Apache Arrow 整合,用於零複製資料攝取。

R 語言

官方的 duckdb 軟體包提供了 R 的 DBI 介面實作及其所有方法。與 Julia 類別似,該軟體包針對高效的資料傳輸進行了最佳化。任何 DuckDB 表都可以直接對映到 R DataFrame,反之亦然。DuckDB R 整合也與 dbplyrdplyr 這兩個軟體包配合良好,它們與 Python 中提供的關聯式 API 有些相似,用於安全的程式化查詢構建。

Rust 語言

Rust API 是圍繞 C-API 的慣用且符合人體工程學的包裝器,可以透過 crates.io 安裝。

Swift 語言

Swift API 使 Swift 平台上的開發者能夠使用原生的 Swift 介面來充分發揮 DuckDB 的強大功能。該 API 不僅適用於 Apple 平台,也適用於 Linux,為日益增長的伺服器端 Swift 生態系統開啟了新的機會。

ODBC

開放式資料函式庫連線(ODBC)是一種 C 風格的 API,提供對不同型別的資料函式倉管理系統(DBMS)的存取。ODBC API 由驅動程式管理器(DM)和 ODBC 驅動程式組成。DuckDB 支援 ODBC 版本 3.0。

此外,還有兩個值得特別提及的整合:WebAssembly(WASM)和 Arrow 資料函式庫連線(ADBC)。

A.2 關於平行處理的探討

本文主要聚焦於資料處理,並在第9章與 Streamlit 相關的內容中簡要探討了應用程式開發。大部分互動式應用程式會提供平行存取功能,這可能透過多執行緒環境(如 Java)中的多個執行緒實作,也可能在單一執行緒上的事件迴圈中實作。

DuckDB 為平行處理提供了兩種可組態的選項:

  • 單一程式可同時讀取和寫入資料函式庫。
  • 多個程式可以讀取資料函式庫,但所有程式都必須以唯讀模式運作,因此無法進行寫入。

這並不意味著 DuckDB 無法用於平行互動式應用程式;您只需瞭解其限制。當使用第一種選項時,DuckDB 支援多個寫入執行緒,這得益於多版本平行控制(MVCC)和樂觀平行控制的結合,但所有操作均在該單一寫入程式內進行。

如果您的應用程式不跨越多個程式,則可以安全地存取一個分享的記憶體內或根據檔案的資料函式庫,條件如下:

  • 只要沒有寫入衝突,多個平行寫入操作將成功完成。
  • 即使在同一張表上,追加操作也不會發生衝突。
  • 多個執行緒也可以同時更新不同的表或同一張表的不同子集。
  • 當兩個執行緒嘗試同時編輯(更新或刪除)同一行時,樂觀平行控制將發揮作用。在這種情況下,其中一個執行緒的更新嘗試將因衝突錯誤而失敗。

典型的微服務架構無法無限制地運作。在這種架構中,您的協調器會透過啟動新程式或停止現有程式來擴充套件或縮減應用程式,同時存取相同的底層檔案。這種場景僅限於對相同的 DuckDB 檔案進行唯讀存取,或對不同的 DuckDB 檔案進行讀寫存取。

A.3 使用案例

許多我們日常使用的手機應用程式都嵌入了關聯式資料函式庫:SQLite。它存在於:

  • 每台 Android 裝置
  • 每部 iPhone 和 iOS 裝置
  • 每台 Mac
  • 許多電視機和有線電視機頂盒
  • 許多汽車多媒體系統

SQLite 本身聲稱是“佈署最廣泛和使用最多的資料函式庫引擎”,據估計有 1 兆個 SQLite 資料函式庫正在使用中(參見 https://www.sqlite.org/mostdeployed.html)。DB-Engines 是一個定期對資料函式庫進行人氣排名的網站,通常將其列入前 10 名。

由於 DuckDB 的 API 與 SQLite 大部分相容,您可以在下一個 iOS 應用程式中使用 DuckDB 取代 SQLite。透過將 DuckDB 整合到您的應用程式中,即使在唯讀模式下,您也可以受益於其資料匯入功能。您可以使用 DuckDB 一致地查詢所有支援的格式,並透過 SQL 直接在應用程式中使用查詢結果,如第 5 章所述。對於唯讀的 DuckDB 資料函式庫,您也可以執行硬編碼或動態生成的查詢。

使用 DuckDB 的 OLTP 應用程式

您的典型線上交易處理(OLTP)應用程式是否應使用 DuckDB 作為主要儲存,這一點值得商榷。雖然 DuckDB 支援事務語義和每秒數千次的寫入操作,但它並非針對具有許多小型操作的典型事務工作負載而設計。在保證單一程式佈署的情況下,這將與使用 SQLite 或 H2(一種根據 Java 的嵌入式記憶體資料函式庫)的其他應用程式類別似。DuckDB 明確針對線上分析處理(OLAP)進行了最佳化,並且在這些使用案例中表現出色。

也許混合方法更適合您的需求。以下是一個個人例子。

我執行了一個頁面來追蹤我的體育活動。所有追蹤資料都透過幾個指令碼進行維護和插入,更新後,使用 Python Flask 應用程式靜態地重新生成網站。雖然這是一個業餘專案,但這種方法可以擴充套件到更大的設定。原始碼可以在這裡找到:https://github.com/michael-simons/biking3。

A.4 匯入大量資料

使用準備好的 SQL 陳述式,將資料繫結到命名或索引引數,是將資料插入關聯式資料函式庫的首選方法。這不僅因為使用引數可以防止大多數 SQL 注入攻擊,而且出於效能考慮。這些陳述式具有相同的形狀,不需要重複解析,從而提高了效能。

內容解密:

此段落主要介紹瞭如何有效地將大量資料匯入 DuckDB。透過使用準備好的 SQL 陳述式,可以提高資料插入的效率和安全性。這種方法不僅可以防止 SQL 注入攻擊,還可以提升效能,因為這些陳述式具有相同的結構,避免了重複解析的需要。

-- 示例:使用準備好的 SQL 陳述式插入資料
PREPARE my_insert_stmt FROM 'INSERT INTO my_table (id, name) VALUES (?, ?)';
EXECUTE my_insert_stmt(1, 'John');
EXECUTE my_insert_stmt(2, 'Jane');
DEALLOCATE PREPARE my_insert_stmt;

內容解密:

上述 SQL 陳述式展示瞭如何準備、執行和釋放一個準備好的 SQL 陳述式。首先,使用 PREPARE 陳述式準備一個插入陳述式,然後使用 EXECUTE 陳述式執行該陳述式並傳入不同的引數值,最後使用 DEALLOCATE PREPARE 陳述式釋放該準備好的陳述式。這種方法可以有效地批次插入資料,同時避免 SQL 注入風險。

使用 JDBC 驅動程式從 Java 連線 DuckDB

Java 資料函式庫連線(JDBC)API 是 Java 平台上最古老的規範之一,幾乎每一個資料函式庫都有對應的客戶端或驅動程式。JDBC 與 SQL 標準化和關聯式資料函式庫密切相關。因此,DuckDB 也提供了 JDBC 驅動程式。

本章節並非 Java、JDBC 或 JVM 上的依賴管理介紹,而是探討 DuckDB JDBC 驅動程式的結構和特點,以及其功能和限制。如果您打算使用 JDBC 驅動程式匯入大量資料,我們也會進行相關介紹。

DuckDB JDBC 驅動程式的 Maven 相依性

DuckDB JDBC 驅動程式的 Maven 相依性如下:

<dependency>
    <groupId>org.duckdb</groupId>
    <artifactId>duckdb_jdbc</artifactId>
    <version>0.10.0</version>
</dependency>

建議在編譯範圍內包含該驅動程式,以便能夠明確地將一般的 JDBC 連線解封裝成專用的 DuckDB 連線,以存取其特定方法。

Gradle 相依性宣告

對於使用 Gradle 的專案,相依性宣告如下:

dependencies {
    compile 'org.duckdb:duckdb_jdbc:0.10.0'
}

下載 JDBC 驅動程式

為了簡化範例,我們不使用任何建置或依賴管理系統。您可以直接從 Maven 中央倉函式庫下載 duckdb_jdbc-0.10.0.jar

curl -OL https://repo1.maven.org/maven2/org/duckdb/duckdb_jdbc/0.10.0/duckdb_jdbc-0.10.0.jar

下載後,您可以直接執行我們的範例程式 simple.java,無需顯式編譯:

java -cp duckdb_jdbc-0.10.0.jar simple.java

所有範例原始碼都可以在書籍倉函式庫中找到(https://github.com/duckdb-in-action/examples/tree/main/a1)。

JDBC 驅動程式的功能與限制

DuckDB JDBC 驅動程式提供了 java.sql.Driverjava.sql.Statementjava.sql.PreparedStatement 的實作,但不支援可呼叫陳述式、生成鍵的檢索或其他一些 JDBC 規範的細節。如有疑問,您需要閱讀其原始碼以檢查您要使用的 API 是否受支援。

JAR 檔案大小與內容

JAR 檔案的大小約為 65 MB。檢視 JAR 檔案內容後,您會發現它包含了所有主要作業系統的 DuckDB 二進位檔案:

unzip -l duckdb_jdbc-0.10.0.jar

輸出結果顯示了包含的檔案,包括不同平台的原生 DuckDB 二進位檔案,如 libduckdb_java.so_linux_amd64libduckdb_java.so_osx_universal 等。

程式碼解析

以下是一個簡單的 Java 程式範例,展示如何使用 DuckDB JDBC 驅動程式:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class SimpleExample {
    public static void main(String[] args) throws Exception {
        // 載入 JDBC 驅動程式
        Class.forName("org.duckdb.DuckDBDriver");

        // 建立連線
        Connection conn = DriverManager.getConnection("jdbc:duckdb:");

        // 建立陳述式物件
        Statement stmt = conn.createStatement();

        // 執行查詢
        ResultSet rs = stmt.executeQuery("SELECT * FROM my_table");

        // 處理結果集
        while (rs.next()) {
            System.out.println(rs.getString(1));
        }

        // 關閉資源
        rs.close();
        stmt.close();
        conn.close();
    }
}

內容解密:

  1. 載入 JDBC 驅動程式:使用 Class.forName 載入 DuckDB 的 JDBC 驅動程式。
  2. 建立連線:使用 DriverManager.getConnection 方法建立到 DuckDB 的連線。
  3. 建立陳述式物件:透過連線物件建立一個 Statement 物件,用於執行 SQL 陳述式。
  4. 執行查詢:使用 executeQuery 方法執行 SQL 查詢,並傳回結果集。
  5. 處理結果集:遍歷結果集並處理查詢結果。
  6. 關閉資源:關閉結果集、陳述式物件和連線,釋放資源。