DeepSeek总结的DuckDB Elasticsearch 扩展概述

DuckDB Elasticsearch 扩展

原文地址:https://github.com/tlinhart/duckdb-elasticsearch

一个 DuckDB 扩展,允许直接使用 SQL 查询 Elasticsearch 索引。无需 ETL 管道或数据移动,即可为您的 Elasticsearch 数据带来 SQL 分析能力。

概述

此扩展提供了一个表函数,使您可以:

  • 使用熟悉的 SQL 语法查询 Elasticsearch 索引。
  • 利用 DuckDB 的查询优化器进行谓词、投影和限制条件下推。
  • 将 Elasticsearch 数据与本地表、Parquet 文件或其他数据源进行连接。

该扩展能够自动从 Elasticsearch 索引映射中推断模式,处理类型转换,并支持高级功能,如嵌套对象、地理类型和多索引查询。

功能特性

查询优化

  • 谓词下推 -- WHERE 子句自动转换为 Elasticsearch Query DSL 并在服务器端执行,减少数据传输。
  • 投影下推 -- 仅通过 _source 过滤获取请求的列。
  • 限制条件下推 -- LIMITOFFSET 子句通过优化器扩展下推到 Elasticsearch。

自动模式推断

  • 模式在查询时从 Elasticsearch 索引映射中推断。
  • 支持多索引查询(例如 logs-*),并自动合并映射。
  • 通过采样文档检测数组字段。
  • 未映射/动态字段被收集到一个 JSON 列中。

类型支持

  • 全面支持 Elasticsearch 标量类型(textkeywordintegerfloatdatebooleanip 等)。
  • 嵌套对象映射为 DuckDB 的 STRUCT 类型。
  • 嵌套数组映射为 LIST(STRUCT(...)) 类型。
  • 地理类型(geo_pointgeo_shape)转换为 GeoJSON 格式。
  • WKT 几何字符串自动解析和转换。

可靠性

  • 使用 Scroll API 高效检索大型结果集。
  • 对暂时性错误具有自动重试和指数退避机制。
  • 可配置的超时和重试参数。
  • 支持 SSL/TLS 和可选的证书验证。

安装

安装 Elasticsearch 扩展最简单的方法是从 DuckDB 社区扩展 仓库安装:

sql 复制代码
INSTALL elasticsearch FROM community;
LOAD elasticsearch;

从源代码构建

先决条件

  • C++11 兼容的编译器
  • CMake 3.5 或更高版本
  • vcpkg(用于依赖管理)
  • Ninja(推荐用于并行化构建过程)
  • ccache(推荐用于缓存编译结果和加速重建)

克隆仓库

shell 复制代码
git clone --recurse-submodules https://github.com/tlinhart/duckdb-elasticsearch.git
cd duckdb-elasticsearch

--recurse-submodules 标志是必需的,用于拉取 DuckDB 核心和扩展 CI 工具子模块。如果您已经克隆了仓库但没有包含子模块,请运行:

shell 复制代码
git submodule update --init --recursive

设置 vcpkg

此 DuckDB 扩展使用 vcpkg 管理外部依赖。设置步骤如下:

shell 复制代码
git clone https://github.com/microsoft/vcpkg.git
cd vcpkg
git checkout ce613c41372b23b1f51333815feb3edd87ef8a8b
./bootstrap-vcpkg.sh -disableMetrics
export VCPKG_TOOLCHAIN_PATH=$(pwd)/scripts/buildsystems/vcpkg.cmake

设置 VCPKG_TOOLCHAIN_PATH 环境变量后,构建系统将自动使用 vcpkg。依赖关系在 vcpkg.json 中声明。

使用 Make 构建

构建扩展最简单的方法是使用 Make:

shell 复制代码
make

这将创建静态和可加载扩展的发布版本构建。

DuckDB 扩展在构建过程中会构建 DuckDB 本身,以便于测试和分发。为了显著加速重建,强烈建议安装 Ninja 和 ccache。构建系统会自动检测并使用 ccache 缓存构建工件。要使用 Ninja 并行化构建:

shell 复制代码
GEN=ninja make

要限制并行作业的数量(如果内存不足):

shell 复制代码
CMAKE_BUILD_PARALLEL_LEVEL=4 GEN=ninja make

构建产生的主要二进制文件有:

  • build/release/duckdb -- 已预加载扩展的 DuckDB shell。
  • build/release/test/unittest -- 链接了扩展的测试运行器。
  • build/release/extension/elasticsearch/elasticsearch.duckdb_extension -- 可加载的扩展二进制文件,即分发的版本。

运行测试

Elasticsearch 扩展在 test 目录下配备了全面的测试套件。构建完成后运行测试:

shell 复制代码
make test

有关集成测试设置的更多信息,请参阅 <test/README.md>。

加载扩展

要运行扩展代码,只需启动已构建并预加载扩展的 shell:

shell 复制代码
./build/release/duckdb

或者,使用 -unsigned 标志启动 DuckDB shell 并手动加载扩展:

sql 复制代码
LOAD 'build/release/extension/elasticsearch/elasticsearch.duckdb_extension';

表函数

elasticsearch_query

elasticsearch_query 表函数允许查询 Elasticsearch 索引。

参数

下表列出了该函数支持的参数:

参数名称 类型 默认值 描述
host VARCHAR localhost(必需) Elasticsearch 主机名或 IP 地址
port INTEGER 9200 Elasticsearch HTTP 端口
index VARCHAR --(必需) 索引名称或模式(例如 logs-*
query VARCHAR -- 可选的 Elasticsearch 查询子句
username VARCHAR -- HTTP 基本认证的用户名
password VARCHAR -- HTTP 基本认证的密码
use_ssl BOOLEAN false 使用 HTTPS 代替 HTTP
verify_ssl BOOLEAN true 验证 SSL 证书
timeout INTEGER 30000 请求超时时间(毫秒)
max_retries INTEGER 3 对暂时性错误的最大重试次数
retry_interval INTEGER 100 初始重试等待时间(毫秒)
retry_backoff_factor DOUBLE 2.0 指数退避乘数
sample_size INTEGER 100 用于数组检测的文档采样数量

query 参数接受一个 Elasticsearch 查询子句(例如 {"match": {"name": "alice"}}),而不是完整的请求体。如果提供,该查询会与从 SQL WHERE 子句下推的任何过滤器使用 bool.must 合并。

输出模式

elasticsearch_query 函数返回一个包含以下内容的表:

  1. _id (VARCHAR) -- Elasticsearch 文档 ID。
  2. 映射字段 -- 索引映射中每个字段对应的列,具有推断的类型。
  3. _unmapped_ (JSON) -- 一个 JSON 对象,包含存在于文档中但未在映射中定义的任何字段。
工作原理
  1. 绑定阶段 -- 从 Elasticsearch 获取索引映射,推断 DuckDB 模式,并可选择采样文档以检测数组字段。
  2. 谓词下推 -- DuckDB 的优化器将 WHERE 子句下推到扩展,扩展将其转换为 Elasticsearch Query DSL。
  3. 投影下推 -- 只有请求的列包含在 _source 过滤中。
  4. 限制条件下推 -- LIMITOFFSET 子句通过优化器扩展下推。
  5. 扫描阶段 -- 使用 Scroll API 执行优化后的查询,分批获取文档,并将 JSON 转换为 DuckDB 值。
示例

获取所有文档的基本查询:

sql 复制代码
SELECT * FROM elasticsearch_query(
    host := 'localhost',
    index := 'test',
    username := 'elastic',
    password := 'test'
);

包含谓词和投影下推的查询:

sql 复制代码
SELECT name, amount FROM elasticsearch_query(
    host := 'localhost',
    index := 'test',
    username := 'elastic',
    password := 'test'
)
WHERE deprecated = true;

该扩展会将其转换为以下 Elasticsearch 查询:

json 复制代码
{
  "query": {
    "term": { "deprecated": true }
  },
  "_source": ["name", "amount"]
}

基础查询与 SQL 过滤器结合:

sql 复制代码
SELECT name, price FROM elasticsearch_query(
    host := 'localhost',
    index := 'test',
    username := 'elastic',
    password := 'test',
    query := '{"exists": {"field": "employee"}}'
)
WHERE price BETWEEN 2000 AND 6000;

基础查询和 SQL 过滤器使用 bool.must 合并:

json 复制代码
{
  "query": {
    "bool": {
      "must": [
        { "exists": { "field": "employee" } },
        {
          "bool": {
            "must": [
              { "range": { "price": { "gte": 2000 } } },
              { "range": { "price": { "lte": 6000 } } }
            ]
          }
        }
      ]
    }
  },
  "_source": ["price", "name"]
}

谓词下推

以下 SQL 表达式会被转换为 Elasticsearch Query DSL:

SQL 表达式 Elasticsearch 查询
column = value {"term": {"column": value}}
column != value {"bool": {"must_not": {"term": {"column": value}}}}
column < value {"range": {"column": {"lt": value}}}
column > value {"range": {"column": {"gt": value}}}
column <= value {"range": {"column": {"lte": value}}}
column >= value {"range": {"column": {"gte": value}}}
column IN (a, b, c) {"terms": {"column": [a, b, c]}}
column LIKE 'prefix%' {"prefix": {"column": "prefix"}}
column LIKE '%suffix' {"wildcard": {"column": {"value": "*suffix"}}}
column LIKE '%pattern%' {"wildcard": {"column": {"value": "*pattern*"}}}
column ILIKE 'pattern' 不区分大小写的通配符查询
column IS NULL {"bool": {"must_not": {"exists": {"field": "column"}}}}
column IS NOT NULL {"exists": {"field": "column"}}

下表总结了谓词下推的行为:

字段类型 =, != <, >, <=, >= IN LIKE, ILIKE IS NULL, IS NOT NULL
numeric 已下推 已下推 已下推 不适用 已下推
date 已下推 已下推 已下推 不适用 已下推
boolean 已下推 不适用 已下推 不适用 已下推
keyword 已下推 已下推 已下推 已下推 已下推
text with .keyword 已下推 已下推 已下推 已下推 已下推
text without .keyword 错误 错误 错误 错误 已下推
nested object fields 已下推 已下推 已下推 已下推 已下推
array element access 过滤 过滤 过滤 过滤 过滤

已下推 -- 过滤器已转换为 Elasticsearch Query DSL。
错误 -- 抛出错误。
过滤 -- 过滤器无法下推;在扫描后由 DuckDB 的 FILTER 操作符处理。
不适用 -- 此字段类型不适用。

Elasticsearch 的 text 字段是经过分析的(分词化的),不支持 term 这样的精确匹配查询。对于具有 .keyword 子字段的字段,过滤器会自动重定向到 .keyword 子字段进行精确匹配。对于没有 .keyword 子字段的字段,会抛出错误。一种可能的解决方案是向 Elasticsearch 映射添加 .keyword 子字段。另一种解决方法是使用 query 参数:

sql 复制代码
SELECT * FROM elasticsearch_query(
    host := 'localhost',
    index := 'test',
    query := '{"match": {"description": "wireless headphones"}}'
);

投影下推和过滤器剪枝

执行查询时,扩展通过仅请求实际需要的列来优化数据传输:

  • 投影下推 -- 只有 SELECT 子句(以及查询的其他部分)中引用的列包含在 Elasticsearch 的 _source 过滤中。这通过从响应中排除不必要的字段,减少了网络带宽和解析开销。
  • 过滤器剪枝 -- 仅用于已下推 WHERE 子句过滤器的列会从 _source 请求中排除。由于这些过滤器在 Elasticsearch 服务器端进行评估,因此无需将这些字段的实际值传输回 DuckDB。

考虑以下查询:

sql 复制代码
SELECT title, amount FROM elasticsearch_query(...)
WHERE in_stock = true AND category = 'electronics';

如果两个过滤器都下推到 Elasticsearch,_source 过滤将只包含 ["title", "amount"]in_stockcategory 列会被剪枝,因为在服务器端过滤后就不再需要它们的值。

限制和偏移量下推

LIMITOFFSET 子句通过优化器扩展下推到 Elasticsearch。这意味着:

  • 无需滚动遍历所有文档即可高效获取小型结果集。
  • 当下推成功时,优化器会从查询计划中移除 LIMIT 节点。
  • 对于 LIMIT N OFFSET M,扩展会获取 N + M 个文档并跳过前 M 个。

类型映射

下表总结了 Elasticsearch 到 DuckDB 的类型映射:

Elasticsearch 类型 DuckDB 类型 备注
text VARCHAR 经过分析的文本;使用 .keyword 进行精确匹配
keyword VARCHAR 未经分析,精确值
long BIGINT 64 位有符号整数
integer INTEGER 32 位有符号整数
short SMALLINT 16 位有符号整数
byte TINYINT 8 位有符号整数
double DOUBLE 64 位浮点数
float FLOAT 32 位浮点数
half_float FLOAT 16 位浮点数
boolean BOOLEAN 真/假
date TIMESTAMP 从 ISO8601 或纪元时间解析
ip VARCHAR IP 地址作为字符串
geo_point VARCHAR 转换为 GeoJSON Point 类型
geo_shape VARCHAR 转换为相关的 GeoJSON 几何类型
object STRUCT(...) 嵌套属性成为结构体字段
nested LIST(STRUCT(...)) 始终视为对象数组

数组处理

Elasticsearch 映射不区分标量字段和数组。扩展通过采样文档检测数组:

  • 采样 sample_size 个文档。
  • 如果任何文档的某个字段具有数组值,则将类型包装在 LIST(...) 中。
  • 设置 sample_size := 0 以禁用数组检测(所有字段将被视为标量)。

地理空间类型

geo_point 值转换为 GeoJSON Point 类型:

输入格式 示例 输出
object {"lat": 40.7128, "lon": -74.006} {"type":"Point","coordinates":[-74.006,40.7128]}
array [-74.006, 40.7128] {"type":"Point","coordinates":[-74.006,40.7128]}
string "40.7128,-74.006" {"type":"Point","coordinates":[-74.006,40.7128]}
WKT "POINT (-74.006 40.7128)" {"type":"Point","coordinates":[-74.006,40.7128]}

geo_shape 值转换为相关的 GeoJSON 几何类型:

输入格式 支持的类型
GeoJSON PointLineStringPolygonMultiPointMultiLineString
WKT POINTLINESTRINGPOLYGONMULTIPOINTMULTILINESTRING

未映射字段

_unmapped_ 列(JSON 类型)捕获存在于文档中但未在索引映射中定义的字段。这在以下情况下很有用:

  • 索引的 dynamic 设置为 true,且文档包含临时字段。
  • 不同文档具有不同的结构。
  • 在定义严格模式之前想要探索数据。

以下查询展示了如何从 _unmapped_ 列中提取值:

sql 复制代码
SELECT _unmapped_->>'$.extra.note' FROM elasticsearch_query(...)
WHERE _unmapped_ IS NOT NULL;

HTTP 日志记录

该扩展支持 DuckDB 的 HTTP 日志记录 功能。启用它以调试发送到 Elasticsearch 的请求:

sql 复制代码
CALL enable_logging('HTTP', storage = 'stdout');

SELECT * FROM elasticsearch_query(...);
相关推荐
春日见10 小时前
拉取与合并:如何让个人分支既包含你昨天的修改,也包含 develop 最新更新
大数据·人工智能·深度学习·elasticsearch·搜索引擎
Elastic 中国社区官方博客12 小时前
如何防御你的 RAG 系统免受上下文投毒攻击
大数据·运维·人工智能·elasticsearch·搜索引擎·ai·全文检索
YangYang9YangYan13 小时前
2026中专大数据与会计专业数据分析发展路径
大数据·数据挖掘·数据分析
W1333090890714 小时前
工业大数据方向,CDA证书和工业数据工程师证哪个更实用?
大数据
Re.不晚14 小时前
可视化大数据——淘宝母婴购物数据【含详细代码】
大数据·阿里云·云计算
未定义.22114 小时前
第2篇:请求实战!覆盖GET/POST/请求头/参数全场景
java·python·http·servlet·自动化·jenkins
Elastic 中国社区官方博客14 小时前
Elasticsearch:交易搜索 - AI Agent builder
大数据·人工智能·elasticsearch·搜索引擎·ai·全文检索
SQL必知必会15 小时前
使用 SQL 进行 RFM 客户细分分析
大数据·数据库·sql
YangYang9YangYan15 小时前
2026大专大数据技术专业学数据分析指南
大数据·数据挖掘·数据分析