Skip to content

DB API

DB API 提供了一个符合 Python DB-API 2.0 规范的接口。该模块使用 DuckDBPyConnection 对象。他比较特殊的一点是不再提供 Cursor 对象,他们统一都是用 DuckDBPyConnection 对象:

Python
from duckdb import DuckDBPyConnection, DuckDBPyRelation
import pyarrow as pa
import pandas as pd


class DuckDBPyConnection:
    # =========== Connection 规范接口 ===============
    def close(self) -> None:
        """关闭 Connection"""
        pass

    def commit(self) -> DuckDBPyConnection:
        """提交事务中的更改"""
        pass

    def cursor(self) -> DuckDBPyConnection:
        """这个是最特殊的,他返回一个当前 Connection 对象的副本
        也就是他将 Cursor 和 Connection 统一了
        """
        pass

    def rollback(self) -> DuckDBPyConnection:
        """回滚事务"""
        pass

    # =========== Cursor 规范接口 ==================
    # 由于 DuckDBPyConnection 和 Cursor 合并了,所以他也提供了 Cursor 的规范接口
    # 描述返回列
    description: str

    # 执行
    def execute(
        self, query, parameters=None, multiple_parameter_sets: bool = False
    ) -> DuckDBPyConnection:
        pass

    def executemany(self, query, parameters=None) -> DuckDBPyConnection:
        pass

    # 获取数据
    def fetchone(self) -> tuple | None:
        pass

    def fetchmany(self, size: int = 1) -> list:
        pass

    def fetchall(self) -> list:
        pass

    # ============== 规范中没有定义的扩展接口 ============
    # =========== Relational API 相关 =================
    def sql(self, query) -> DuckDBPyRelation:
        """根据 SQL 语句来构造关系对象
        注意只能是 SELECT 语句,其他语句则等价于 execute
        """
        pass

    def table(self, table_name: str) -> DuckDBPyRelation:
        """根据表名来构造关系对象
        等价于 sql('SELECT * FROM table_name")
        """
        pass

    def from_arrow(self, arrow_object) -> DuckDBPyRelation:
        """基于 arrow 类型数据来构建关系对象"""
        pass

    def from_df(self, df) -> DuckDBPyRelation:
        """基于 DataFrame 来构建关系对象"""
        pass

    def read_parquet(*arg, **kwargs) -> DuckDBPyRelation:
        """基于 parquet 文件来构建关系对象"""
        pass

    def read_csv(*arg, **kwargs) -> DuckDBPyRelation:
        """基于 csv 文件来构建关系对象"""
        pass

    def read_json(*arg, **kwargs) -> DuckDBPyRelation:
        """基于 json 文件来构建关系对象"""
        pass

    # =========== 数据源交换相关 ========================
    def fetch_arrow_table(self, rows_per_batch: int = 1000000) -> pa.lib.Table:
        """获取 Arrow Table数据,和 pyarrow 交互"""
        pass

    def fetch_record_batch(
        self, row_per_batch: int = 1000000
    ) -> pa.lib.RecordBatchReader:
        """获取 Arrow RecordBatchReader 数据,和 pyarrow 交互"""
        pass

    def fetch_df(self, date_as_object: bool = False) -> pd.DataFrame:
        """获取 pandas DataFrame 数据,和 pandas 交互"""
        pass

    def fetch_df_chunk(
        self, vectors_per_chunk: int = 1, date_as_object: bool = False
    ) -> pd.DataFrame:
        pass

    # ============= UDF 定义 =======================
    def create_function(
        self,
        func_name: str,  # 函数名(用于 SQL 查询中
        function,  # 真正的函数  Callable
        parameters_type: list = None,  # 列类型列表
        return_type=None,  # 返回值类型 DuckDBPyType
        exception_handling=None,  # 默认引发异常抛出,如果为 'return_null' 则设置为 NULL
    ) -> DuckDBPyConnection:
        """创建 UDF"""
        pass

    def remove_function(self, func_name):
        """移除指定 UDF"""
        pass

我们通常会与标准库实现的 SQLite 驱动来与其他驱动做对比,DuckDB 的数据库驱动有几点不太相同:

  • 最大的区别在于 Connection 和 Cursor 被统一到 DuckDBPyConnection 对象中
  • 大多数方法都是返回了 DuckDBPyConnection 对象本身,这样能够实现链式调用
  • 作为入口对象,构造Relational API的 DuckDBPyRelation 对象也是由 DuckDBPyConnection 提供的

全局属性以及 default_connection

DB API 中规定的全局属性在 DuckDBPyConnection 对象上也都存在,并且由于他类似于 SQLite 存在内存数据库。DuckDB API 甚至直接在模块级别提供了所有的 Connection 和 Cursor 接口。他们实际上就是 default_connection 属性提供的 DuckDBPyConnection 对象提供的。

模块级别的属性和方法包括:

  • apilevel: str: API级别
  • threadsafety: int: 线程安全级别
  • paramstyle: str: 表示接口支持的参数标记格式的类型,默认是 qmark,实际上支持多个
  • default_conection: DuckDBPyConnection: 如果直接在模块级别使用默认使用的就是这个连接,他操作的就是内存数据库
  • 所有由 DuckDBPyConnection 提供的属性和方法

并发处理

DuckDB 支持单进程并发(默认情况)以及多进程只读:

Python
import duckdb

# read_only 启用多进程只读
duckdb.connect('db.duckdb', read_only=True)

DuckDBPyConnection.cursor

并且还有一个比较特殊的就是 DuckDB 的 API 线程安全是 1,即线程可以共享模块,但是不能共享 Connection:

Python
import duckdb

duckdb.threadsafety

    1

因此如果我们要在 DuckDB 中实现并发就需要创建不同的 DuckDBPyConnection 对象。DuckDBPyConnection.cursor() 函数的意义就是从原始的 DuckDBPyConnection 对象复制一个全新的连接对象

DuckDB 的 Python API 中的 Connection.cursor 并不是完全为了符合 DB-API2.0 标准而存在的,它最核心的意义就是在并发中生成能够用于多线程的新的 DuckDBPyConnection 对象