8个版本 (4个重大更改)
0.5.0 | 2024年8月12日 |
---|---|
0.4.2 | 2024年6月20日 |
0.4.1 | 2024年4月14日 |
0.4.0 | 2024年2月26日 |
0.1.1 | 2024年2月5日 |
#300 in 数据库接口
每月 5,746 次下载
用于 lutra
180KB
4K SLoC
Connector Arrow
一个灵活的数据库客户端,可以将数据转换为Apache Arrow格式,适用于各种数据库。
这是通过定义API特质(即 Connection
)并为各种数据库客户端Crate(例如 rusqlite::Connection
)实现它们来实现的。connector_arrow
将数据库视为“Arrow格式的数据存储”,符合数据互操作性的理念。
关键特性
- 查询:查询数据库并以Apache Arrow格式检索结果。
- 查询参数:使用Arrow类型系统进行查询参数。
- 时间和容器类型:正确处理时间和容器类型。
- 模式检查:查询特定表的数据库模式。
- 模式迁移:基本模式迁移命令。
- 追加:将
arrow::record_batch::RecordBatch
写入数据库表。
基于 ConnectorX,但专注于成为Rust库,而不是Python库。这意味着这个crate
- 使用最少的依赖(甚至禁用了默认功能),
- 不支持多个目标,只支持 arrow,
- 不包括并行性,但允许下游crate自行实现它,
- 不包括连接池,但允许下游crate自行实现它。
类似于 ADBC,但使用纯、安全的Rust编写,无需动态链接C库。
支持矩阵
RDBMS | SQLite | DuckDB | PostgreSQL | MySQL | Microsoft SQL Server |
---|---|---|---|---|---|
功能 | src_sqlite |
src_duckdb |
src_postgres |
src_mysql |
src_tiberius |
依赖项 | rusqlite | duckdb | postgres | mysql | tiberius |
查询 | x | x | x | x | x |
查询参数 | x | ||||
模式获取 | x | x | x | x | |
模式编辑 | x | x | x | x | |
追加 | x | x | x | x | |
往返:null & bool | x | x | x | x | |
往返:int | x | x | x | x | |
往返:uint | x | x | x | x | |
往返:float | x | x | x | x | |
往返:decimal | x | x | |||
往返:timestamp | x | x | x | ||
往返:date | x | x | |||
往返:time | x | x | |||
往返:持续时间 | x | x | |||
往返:间隔 | |||||
往返:UTF8 | x | x | x | x | |
往返:二进制 | x | x | x | x | |
往返:空 | x | x | x | ||
容器 | |||||
二进制回退 | x | x |
默认情况下,所有源都没有启用,请使用功能来启用它们。
类型强制转换
将关系数据从Apache Arrow转换过来和转换过去存在一个固有问题:任何数据库的类型系统都不会与箭头类型系统一一对应。在实践中这意味着
- 在查询数据时,多个数据库类型可能会映射到单个箭头类型(例如,在PostgreSQL中,
VARCHAR
和TEXT
都将映射到LargeUtf8
), - 在推送数据时,多个箭头类型可能会映射到单个数据库类型(例如,在PostgreSQL中,
Utf8
和LargeUtf8
都将映射到TEXT
)。
因此,将数据推送到数据库并查询回来的往返操作必须转换至少一些类型。我们称此过程为“类型强制转换”,并由 Connection::coerce_type
进行记录。
在设计映射时,我们
- 保证往返不会丢失信息(即数字丢失精度),
- 优先考虑正确性和可预测性而不是存储效率。
例如,大多数数据库不支持对 UInt8
进行排序。
- 通过将值限制在范围
0..127
内,将其存储到Int8
中,这会丢失信息。 - 通过减去 128(即只是将字节重新解释为
Int8
)将其存储到Int8
中。这种强制转换将非常高效,因为新类型不会比原始类型更大,但它会令人困惑,因为值 0 将转换为 -128,而值 255 将转换为 127。 - 存储到
Int16
中。这会使用更多空间,但它不会丢失信息或改变存储值的含义。因此,connector_arrow
将UInt8 => Int16
、UInt16 => Int32
、UInt32 => Int64
和UInt64 => Decimal128(20, 0)
进行强制转换。
动态类型与静态类型
在转换两种类型系统时,静态类型参数与动态类型参数不匹配也是一个问题。例如,箭头类型 Decimal128(20, 4)
静态地定义了精度和小数位数,在模式中,这意味着它必须对所有数组中的值相同。另一方面,PostgreSQL 类型 NUMERIC
具有动态精度和小数位数,这意味着每个值可能有一对不同的参数。PostgreSQL 允许使用 NUMERIC(20, 4)
静态地指定参数,但这仅适用于列定义,而不是查询结果。即使直接从表中选择,结果也只会包含此列是 NUMERIC
的信息。
这个问题在具有完全动态类型系统的SQLite中尤为普遍。这意味着任何表或结果列都可能包含多种不同的类型。虽然可以声明表列类型,但这些信息根本不会被验证(即,你可以将类型设置为 PEANUT_BUTTER(42)
),它只用于确定列的 类型亲和度。
以下是一些解决方案:
- 转换为其他类型。对于PostgreSQL的
NUMERIC
,这意味着将其转换为十进制文本表示形式,编码为Utf8
。这通常很慢,且不方便使用。 - 缓冲和推断。如果我们能够接收和缓冲所有结果数据,我们可以从数据中推断类型信息。如果我们想要支持流式处理结果,这显然是不可能的,因为这会抵消流式处理的全部好处。可能存在值类型不统一的情况。在这种情况下,我们可以完全拒绝结果并通知用户在返回结果之前将数据转换为统一类型。我们也可以尝试将值转换为某种统一类型,但这通常很慢,容易出错,而且可能无法实现。
- 从第一批数据中推断。如果我们不想排除流式处理,我们可以选择只缓冲第一批数据并从中推断类型。如果随后的任何一批数据类型不同,我们再次有选择:拒绝或转换。
目前,connector_arrow
没有一种通用的方法来解决此问题。SQLite连接器使用选项2,而其他连接器不支持具有动态类型参数的类型。
解决此问题的首选方法是选项3:从第一批数据中推断并拒绝不统一的数据类型。这将导致用户看到更多错误。我们以观察结果来证明这一决策,即由于两种类型系统之间的转换不是微不足道的,将数据静默转换为另一种类型将导致下游行为不可预测,这无论如何都需要用户的注意。通过早期发现问题,我们可以提供信息丰富的错误消息和有关如何显式指定结果类型的提示。
依赖关系
~13–32MB
~478K SLoC