duckdb 插件机制研究

本文研究 duckdb 内置的 extensions 工作机制。

插件架构

在 duckdb 源码内,内置了一组原生插件,位于顶层 extension 目录下:

extensions
除此之外,还支持 Out-of-Tree Extension,简单说就是独立的插件,不是集成在源码树中。独立编译好后,可以 运行时载入到 duckdb 中。
这里有一个细节:为了安全,任何 Out-of-Tree Extension,必须通过 duckdb 官方签名后,才能被正确载入。这个可以一定程度上防范载入有安全风险的第三方插件。

插件原理

为了明白原理,先简单看几个插件的核心注册逻辑。以 dbgen、httpfs、parquet 三个插件为例。

  • dbgen
// https://github.com/duckdb/duckdb/blob/main/extension/tpch/tpch_extension.cpp
namespace duckdb {static void LoadInternal(DuckDB &db) {auto &db_instance = *db.instance;TableFunction dbgen_func("dbgen", {}, DbgenFunction, DbgenBind);dbgen_func.named_parameters["sf"] = LogicalType::DOUBLE;dbgen_func.named_parameters["overwrite"] = LogicalType::BOOLEAN;dbgen_func.named_parameters["catalog"] = LogicalType::VARCHAR;dbgen_func.named_parameters["schema"] = LogicalType::VARCHAR;dbgen_func.named_parameters["suffix"] = LogicalType::VARCHAR;dbgen_func.named_parameters["children"] = LogicalType::UINTEGER;dbgen_func.named_parameters["step"] = LogicalType::UINTEGER;ExtensionUtil::RegisterFunction(db_instance, dbgen_func);// create the TPCH pragma that allows us to run the queryauto tpch_func = PragmaFunction::PragmaCall("tpch", PragmaTpchQuery, {LogicalType::BIGINT});ExtensionUtil::RegisterFunction(db_instance, tpch_func);// create the TPCH_QUERIES function that returns the queryTableFunction tpch_query_func("tpch_queries", {}, TPCHQueryFunction, TPCHQueryBind, TPCHInit);ExtensionUtil::RegisterFunction(db_instance, tpch_query_func);// create the TPCH_ANSWERS that returns the query resultTableFunction tpch_query_answer_func("tpch_answers", {}, TPCHQueryAnswerFunction, TPCHQueryAnswerBind, TPCHInit);ExtensionUtil::RegisterFunction(db_instance, tpch_query_answer_func);
}void TpchExtension::Load(DuckDB &db) {LoadInternal(db);
}std::string TpchExtension::GetQuery(int query) {return tpch::DBGenWrapper::GetQuery(query);
}std::string TpchExtension::GetAnswer(double sf, int query) {return tpch::DBGenWrapper::GetAnswer(sf, query);
}std::string TpchExtension::Name() {return "tpch";
}std::string TpchExtension::Version() const {
#ifdef EXT_VERSION_TPCHreturn EXT_VERSION_TPCH;
#elsereturn "";
#endif
}} // namespace duckdbextern "C" {DUCKDB_EXTENSION_API void tpch_init(duckdb::DatabaseInstance &db) {duckdb::DuckDB db_wrapper(db);duckdb::LoadInternal(db_wrapper);
}DUCKDB_EXTENSION_API const char *tpch_version() {return duckdb::DuckDB::LibraryVersion();
}
}
  • httpfs

namespace duckdb {static void LoadInternal(DatabaseInstance &instance) {S3FileSystem::Verify(); // run some tests to see if all the hashes work outauto &fs = instance.GetFileSystem();fs.RegisterSubSystem(make_uniq<HTTPFileSystem>());fs.RegisterSubSystem(make_uniq<HuggingFaceFileSystem>());fs.RegisterSubSystem(make_uniq<S3FileSystem>(BufferManager::GetBufferManager(instance)));auto &config = DBConfig::GetConfig(instance);// Global HTTP config// Single timeout value is used for all 4 types of timeouts, we could split it into 4 if users need thatconfig.AddExtensionOption("http_timeout", "HTTP timeout read/write/connection/retry", LogicalType::UBIGINT,Value(30000));config.AddExtensionOption("http_retries", "HTTP retries on I/O error", LogicalType::UBIGINT, Value(3));config.AddExtensionOption("http_retry_wait_ms", "Time between retries", LogicalType::UBIGINT, Value(100));config.AddExtensionOption("force_download", "Forces upfront download of file", LogicalType::BOOLEAN, Value(false));// Reduces the number of requests made while waiting, for example retry_wait_ms of 50 and backoff factor of 2 will// result in wait times of  0 50 100 200 400...etc.config.AddExtensionOption("http_retry_backoff", "Backoff factor for exponentially increasing retry wait time",LogicalType::FLOAT, Value(4));config.AddExtensionOption("http_keep_alive","Keep alive connections. Setting this to false can help when running into connection failures",LogicalType::BOOLEAN, Value(true));config.AddExtensionOption("enable_server_cert_verification", "Enable server side certificate verification.",LogicalType::BOOLEAN, Value(false));config.AddExtensionOption("ca_cert_file", "Path to a custom certificate file for self-signed certificates.",LogicalType::VARCHAR, Value(""));// Global S3 configconfig.AddExtensionOption("s3_region", "S3 Region", LogicalType::VARCHAR, Value("us-east-1"));config.AddExtensionOption("s3_access_key_id", "S3 Access Key ID", LogicalType::VARCHAR);config.AddExtensionOption("s3_secret_access_key", "S3 Access Key", LogicalType::VARCHAR);config.AddExtensionOption("s3_session_token", "S3 Session Token", LogicalType::VARCHAR);config.AddExtensionOption("s3_endpoint", "S3 Endpoint", LogicalType::VARCHAR);config.AddExtensionOption("s3_url_style", "S3 URL style", LogicalType::VARCHAR, Value("vhost"));config.AddExtensionOption("s3_use_ssl", "S3 use SSL", LogicalType::BOOLEAN, Value(true));config.AddExtensionOption("s3_url_compatibility_mode", "Disable Globs and Query Parameters on S3 URLs",LogicalType::BOOLEAN, Value(false));// S3 Uploader configconfig.AddExtensionOption("s3_uploader_max_filesize", "S3 Uploader max filesize (between 50GB and 5TB)",LogicalType::VARCHAR, "800GB");config.AddExtensionOption("s3_uploader_max_parts_per_file", "S3 Uploader max parts per file (between 1 and 10000)",LogicalType::UBIGINT, Value(10000));config.AddExtensionOption("s3_uploader_thread_limit", "S3 Uploader global thread limit", LogicalType::UBIGINT,Value(50));// HuggingFace optionsconfig.AddExtensionOption("hf_max_per_page", "Debug option to limit number of items returned in list requests",LogicalType::UBIGINT, Value::UBIGINT(0));auto provider = make_uniq<AWSEnvironmentCredentialsProvider>(config);provider->SetAll();CreateS3SecretFunctions::Register(instance);CreateBearerTokenFunctions::Register(instance);
}void HttpfsExtension::Load(DuckDB &db) {LoadInternal(*db.instance);
}
std::string HttpfsExtension::Name() {return "httpfs";
}std::string HttpfsExtension::Version() const {
#ifdef EXT_VERSION_HTTPFSreturn EXT_VERSION_HTTPFS;
#elsereturn "";
#endif
}} // namespace duckdbextern "C" {DUCKDB_EXTENSION_API void httpfs_init(duckdb::DatabaseInstance &db) {LoadInternal(db);
}DUCKDB_EXTENSION_API const char *httpfs_version() {return duckdb::DuckDB::LibraryVersion();
}
}
  • parquet
void ParquetExtension::Load(DuckDB &db) {auto &db_instance = *db.instance;auto &fs = db.GetFileSystem();fs.RegisterSubSystem(FileCompressionType::ZSTD, make_uniq<ZStdFileSystem>());auto scan_fun = ParquetScanFunction::GetFunctionSet();scan_fun.name = "read_parquet";ExtensionUtil::RegisterFunction(db_instance, scan_fun);scan_fun.name = "parquet_scan";ExtensionUtil::RegisterFunction(db_instance, scan_fun);// parquet_metadataParquetMetaDataFunction meta_fun;ExtensionUtil::RegisterFunction(db_instance, MultiFileReader::CreateFunctionSet(meta_fun));// parquet_schemaParquetSchemaFunction schema_fun;ExtensionUtil::RegisterFunction(db_instance, MultiFileReader::CreateFunctionSet(schema_fun));// parquet_key_value_metadataParquetKeyValueMetadataFunction kv_meta_fun;ExtensionUtil::RegisterFunction(db_instance, MultiFileReader::CreateFunctionSet(kv_meta_fun));// parquet_file_metadataParquetFileMetadataFunction file_meta_fun;ExtensionUtil::RegisterFunction(db_instance, MultiFileReader::CreateFunctionSet(file_meta_fun));CopyFunction function("parquet");function.copy_to_bind = ParquetWriteBind;function.copy_to_initialize_global = ParquetWriteInitializeGlobal;function.copy_to_initialize_local = ParquetWriteInitializeLocal;function.copy_to_sink = ParquetWriteSink;function.copy_to_combine = ParquetWriteCombine;function.copy_to_finalize = ParquetWriteFinalize;function.execution_mode = ParquetWriteExecutionMode;function.copy_from_bind = ParquetScanFunction::ParquetReadBind;function.copy_from_function = scan_fun.functions[0];function.prepare_batch = ParquetWritePrepareBatch;function.flush_batch = ParquetWriteFlushBatch;function.desired_batch_size = ParquetWriteDesiredBatchSize;function.file_size_bytes = ParquetWriteFileSize;function.serialize = ParquetCopySerialize;function.deserialize = ParquetCopyDeserialize;function.supports_type = ParquetWriter::TypeIsSupported;function.extension = "parquet";ExtensionUtil::RegisterFunction(db_instance, function);// parquet_keyauto parquet_key_fun = PragmaFunction::PragmaCall("add_parquet_key", ParquetCrypto::AddKey,{LogicalType::VARCHAR, LogicalType::VARCHAR});ExtensionUtil::RegisterFunction(db_instance, parquet_key_fun);auto &config = DBConfig::GetConfig(*db.instance);config.replacement_scans.emplace_back(ParquetScanReplacement);config.AddExtensionOption("binary_as_string", "In Parquet files, interpret binary data as a string.",LogicalType::BOOLEAN);
}std::string ParquetExtension::Name() {return "parquet";
}std::string ParquetExtension::Version() const {
#ifdef EXT_VERSION_PARQUETreturn EXT_VERSION_PARQUET;
#elsereturn "";
#endif
}} // namespace duckdb#ifdef DUCKDB_BUILD_LOADABLE_EXTENSION
extern "C" {DUCKDB_EXTENSION_API void parquet_init(duckdb::DatabaseInstance &db) { // NOLINTduckdb::DuckDB db_wrapper(db);db_wrapper.LoadExtension<duckdb::ParquetExtension>();
}DUCKDB_EXTENSION_API const char *parquet_version() { // NOLINTreturn duckdb::DuckDB::LibraryVersion();
}
}
#endif

上面三个插件里,分别出现了如下注册字眼:

// tpchTableFunction tpch_query_answer_func("tpch_answers", {}, TPCHQueryAnswerFunction, TPCHQueryAnswerBind, TPCHInit);ExtensionUtil::RegisterFunction(db_instance, tpch_query_answer_func);// parquetauto &db_instance = *db.instance;auto &fs = db.GetFileSystem();fs.RegisterSubSystem(FileCompressionType::ZSTD, make_uniq<ZStdFileSystem>());ExtensionUtil::RegisterFunction(db_instance, MultiFileReader::CreateFunctionSet(meta_fun));// httpfsauto &fs = instance.GetFileSystem();fs.RegisterSubSystem(make_uniq<HTTPFileSystem>());fs.RegisterSubSystem(make_uniq<HuggingFaceFileSystem>());fs.RegisterSubSystem(make_uniq<S3FileSystem>(BufferManager::GetBufferManager(instance)));ExtensionUtil::RegisterSecretType(instance, secret_type);

可以看到,ExtensionUtil 在这里面扮演了很重要的角色。ExtensionUtil 支持注册普通函数、聚合函数、Table 函数、PragmaFunction、Collation、Secret 等。对于 Duckdb 的框架,了解到这一层已经差不多了,至于 ExtensionUtil 的实现,大家可以根据自己系统的特性灵活处理,本质上无非就是针对每一个类型的扩展,设计一套可扩展、可查找、高性能的功能集合接口。

class ExtensionUtil {
public://! Register a new scalar function - throw an exception if the function already existsDUCKDB_API static void RegisterFunction(DatabaseInstance &db, ScalarFunction function);//! Register a new scalar function set - throw an exception if the function already existsDUCKDB_API static void RegisterFunction(DatabaseInstance &db, ScalarFunctionSet function);//! Register a new aggregate function - throw an exception if the function already existsDUCKDB_API static void RegisterFunction(DatabaseInstance &db, AggregateFunction function);//! Register a new aggregate function set - throw an exception if the function already existsDUCKDB_API static void RegisterFunction(DatabaseInstance &db, AggregateFunctionSet function);//! Register a new table function - throw an exception if the function already existsDUCKDB_API static void RegisterFunction(DatabaseInstance &db, TableFunction function);//! Register a new table function set - throw an exception if the function already existsDUCKDB_API static void RegisterFunction(DatabaseInstance &db, TableFunctionSet function);//! Register a new pragma function - throw an exception if the function already existsDUCKDB_API static void RegisterFunction(DatabaseInstance &db, PragmaFunction function);//! Register a new pragma function set - throw an exception if the function already existsDUCKDB_API static void RegisterFunction(DatabaseInstance &db, PragmaFunctionSet function);//! Register a CreateSecretFunctionDUCKDB_API static void RegisterFunction(DatabaseInstance &db, CreateSecretFunction function);//! Register a new copy function - throw an exception if the function already existsDUCKDB_API static void RegisterFunction(DatabaseInstance &db, CopyFunction function);//! Register a new macro function - throw an exception if the function already existsDUCKDB_API static void RegisterFunction(DatabaseInstance &db, CreateMacroInfo &info);//! Register a new collationDUCKDB_API static void RegisterCollation(DatabaseInstance &db, CreateCollationInfo &info);//! Returns a reference to the function in the catalog - throws an exception if it does not existDUCKDB_API static ScalarFunctionCatalogEntry &GetFunction(DatabaseInstance &db, const string &name);DUCKDB_API static TableFunctionCatalogEntry &GetTableFunction(DatabaseInstance &db, const string &name);//! Add a function overloadDUCKDB_API static void AddFunctionOverload(DatabaseInstance &db, ScalarFunction function);DUCKDB_API static void AddFunctionOverload(DatabaseInstance &db, ScalarFunctionSet function);DUCKDB_API static void AddFunctionOverload(DatabaseInstance &db, TableFunctionSet function);//! Registers a new typeDUCKDB_API static void RegisterType(DatabaseInstance &db, string type_name, LogicalType type,bind_type_modifiers_function_t bind_type_modifiers = nullptr);//! Registers a new secret typeDUCKDB_API static void RegisterSecretType(DatabaseInstance &db, SecretType secret_type);//! Registers a cast between two typesDUCKDB_API static void RegisterCastFunction(DatabaseInstance &db, const LogicalType &source,const LogicalType &target, BoundCastInfo function,int64_t implicit_cast_cost = -1);
};} // namespace duckdb

另外,还用到了 RegisterSubSystem,这说明对于文件系统,Duckdb 也做了抽象,方便插件化。

instance.GetFileSystem().RegisterSubSystem(make_uniq<HTTPFileSystem>());

总结

插件机制是个好东西。为了你的系统能够获得这个好东西,最好是系统在第一天设计的时候就做好抽象,把每一类系统功能都抽象成集合,每一类功能都支持“运行时可寻址”。

在 OceanBase 中,最容易插件化的是系统函数,因为它数量众多,客观上从第一天起就逼迫 OceanBase 把它做成易扩展、易寻址的样子。

反面例子是聚合函数,OceanBase 在最初的时候,只支持很少的聚合函数,所以并没有对其做抽象,而是直接在 parsser 里就 hardcode 地抽取出了聚合函数信息,这导致在运行时几乎不可能对其做扩展。如下图所示:

src/parser/sql_parser_mysql_mode.y

aggfunc
至于 filesystem 支持、文件格式支持,OceanBase 更没有做好抽象,他们的插件化,也需要做大量改造才可能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/17222.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Star CCM+绘图显示设置

前言 如前文介绍&#xff0c;根据报告创建监视器与绘图后&#xff0c;在绘图中会出现报告绘图。此处可以自定义绘图的格式&#xff0c;如网格显示、字体大小、曲线的粗细等。同时也可以根据需要创建右坐标&#xff0c;分别监测不同类型的函数数值。为此方便后期输出仿真报告。…

哆啦AAA萌也能用HTML画出来?看这里!【完整代码可运行】

关注微信公众号「ClassmateJie」有完整代码以及更多惊喜等待你的发现。 简介/效果展示 你是否曾经想过&#xff0c;那些可爱的哆啦A梦角色是如何被创造出来的呢&#xff1f;今天&#xff0c;我要带你一起探索一个神奇的秘密——用HTML画出哆啦A梦&#xff01; 代码 <!DOCT…

【quarkus系列】创建quarkus第一个应用程序

文章目录 序言环境准备创建项目项目分析程序代码构建访问项目 序言 Quarkus 是一个设计用于 Kubernetes 和云原生环境的 Java 框架&#xff0c;具有快速启动时间、低内存消耗和强大的开发者体验。溪源将带您一步步创建一个简单的 Quarkus 应用程序。 环境准备 在开始之前&am…

Neo4j安装部署及python连接neo4j操作

Neo4j安装部署及python连接neo4j操作 Neo4j安装和环境配置 安装依赖库&#xff1a; sudo apt-get install wget curl nano software-properties-common dirmngr apt-transport-https gnupg gnupg2 ca-certificates lsb-release ubuntu-keyring unzip -y 增加Neo4 GPG key&…

为什么并行化将会是下一代的区块链解决方案?

原文标题&#xff1a;《Parallel Execution: The Next Generation of Blockchains》撰文&#xff1a;Paul Timofeev 、Mike Jin、Gabe Tramble 编译&#xff1a;Chris&#xff0c;Techub News 区块链是虚拟机&#xff0c;一种软件基础的计算模型&#xff0c;它运行在任何人都可…

Pytorch深度学习实践笔记10(b站刘二大人)

&#x1f3ac;个人简介&#xff1a;一个全栈工程师的升级之路&#xff01; &#x1f4cb;个人专栏&#xff1a;pytorch深度学习 &#x1f380;CSDN主页 发狂的小花 &#x1f304;人生秘诀&#xff1a;学习的本质就是极致重复! 《PyTorch深度学习实践》完结合集_哔哩哔哩_bilibi…

C++的第一道门坎:类与对象(一)

1.面向过程与面向对象 1.1面向过程 我们之前学习的C语言就是一种面向过程的语言&#xff0c;面向过程的语言强调的是具体实现的过程&#xff0c;一般用函数来具体实现。我们用面向过程的思想&#xff0c;就可以把炒菜分为以下几个步骤: 1.2面向对象 而对于面向对象的语言而言…

【简单介绍下容器是什么?】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

【Spring】Spring AOP底层原理:JDK动态代理和CGLIB动态代理

目录 1、代理模式 1.1、静态代理 1.2、动态代理 2、JDK 动态代理 2.1、jdk动态代理简介 2.2、使用JDK动态代理机制步骤 3、CGLIB 动态代理 3.1、CGLIB 动态代理的特性 3.2、CGLIB的核心类 3.3、CGLIB 动态代理步骤 4、JDK 和 CGLIB 创建代理对象的区别 ​编辑 1、…

(四)手把手教你内网穿透,实现外网主机访问内网服务器

背景&#xff1a;书接上回&#xff0c; 服务器的使用-CSDN博客 课题组成员都有自己的账号&#xff0c;且能通过内网访问服务器&#xff0c;进行远程连接了。我们知道内网中的主机可以访问公网的主机&#xff0c;反之不可以访问。那么如果课题组成员在家不在内网区域内&#x…

源码编译安装LAMP与部署

目录 一、LAMP架构的简述 1.LAMP搭建时各组件安装顺序 二、编译安装Apache httpd服务 1.关闭防火墙&#xff0c;将安装Apache所需软件包传到/opt目录下 2.安装环境依赖包​编辑 3.配置软件模块 4.编译及安装 5.优化配置文件路径&#xff0c;并把httpd服务的可执行程序文…

基于51单片机的智能灯光控制系统

一.硬件方案 智能灯光控制系统由单片机最小系统、人体感应模块、关照强度模块、灯光控制模块、电源模块和灯泡组成。本文以STC89C52单片机为核心&#xff0c;通过利用光照度和红外人体感应相结合主动与被动的探测方法&#xff0c;现了室内无人或者关照充足时灯光自动光灯&…

Kubernetes 容器资源管理Resources和探针Probe

资源配额 Resources 在 Kubernetes 中&#xff0c;resources 配置用于设置容器的资源请求和限制&#xff0c;以确保集群中的资源&#xff08;如 CPU 和内存&#xff09;得到合理分配和使用。 在之前的pod中&#xff0c;不写 resources 字段。就意味着 Pod 对运行的资源要求“…

Java面试八股之AQS对资源的共享方式

AQS对资源的共享方式 AQS设计了一套灵活的机制&#xff0c;不仅支持独占&#xff08;Exclusive&#xff09;锁模式&#xff0c;也支持共享&#xff08;Shared&#xff09;锁模式&#xff0c;使得资源可以被一个或者多个线程以不同的方式访问。这两种模式通过控制一个内部的vol…

pyqt QTableView表格控件

pyqt QTableView表格控件 QTableView效果代码 QTableView QTableView 是 PyQt中的一个控件&#xff0c;用于显示表格数据。它通常与 QAbstractItemModel 或其子类&#xff08;如 QStandardItemModel&#xff09;一起使用&#xff0c;以提供和管理表格中的数据。 效果 代码 i…

wordpress主题 ACG美化插件v3.4.2支持zibll主题7b2主题美化

独具一格的二次元风格&#xff0c;打造全新的子比美化方向 大部分代码均为CSS、JS做成插件只是为了方便懒人小白站长 后台全功能一览&#xff0c;大部分美化均为网上通用流传&#xff0c;

2.冒泡排序

样例输入 5 8 3 6 4 9 样例输出 3 4 6 8 9 以下是解题答案&#xff1a; class demo1{public static void main(String[] args) {Scanner scnnew Scanner(System.in);int[] array new int[scn.nextInt()];if(array.length>0&&array.length<200){for(int…

python列表访问的深入解析

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、正向索引与负索引的奥秘 二、切片&#xff1a;高效访问多个元素 切片示例 三、切片的…

Java 文件操作和输入输出流

在 Java 编程中&#xff0c;文件操作和输入输出流是非常常见和重要的任务&#xff0c;它们允许你读取和写入文件、处理数据流等。 文件操作概述 文件操作是指对文件进行创建、读取、写入、删除等操作的过程。在 Java 中&#xff0c;文件操作通常涉及到使用文件对象、输入输出…

OpenBayes 一周速览|TripoSR 开源:1 秒即 2D 变 3D、经典 GTZAN 音乐数据集上线

公共资源速递 This Weekly Snapshots &#xff01;5 个数据集&#xff1a; FER2013 面部表情识别数据集 GTZAN 音乐流派分类数据集 MVTec-AD 工业异常检测数据集 UCAS-AOD 遥感目标检测数据集 Oxford 102 Flowers 花卉图片数据集 3 个教程&#xff1a; Latte 全球首个开…