
使用 OpenDAL 连接 PostgreSQL
太山不让土壤,故能成其大;河海不择细流,故能就其深;王者不却众庶,故能明其德。
——《史记》
OpenDAL 的项目目标在于,屏蔽具体存储软件的细节,为各种数据应用程序提供统一的“文件资源访问接口”(“One Layer, All Storage”),而在本文中,我们将会阐述如何使用 OpenDAL 项目连接到 PostgreSQL。
(本文将会使用 OpenDAL 的 Rust 接口连接 PostgreSQL)
环境准备
我们假定你的系统工作于 类 Linux
之下,同时已经安装了如下的软件:
git
# 假定系统为 Ubuntu
sudo apt -y install git
PostgreSQL
初始化环境配置
# 假定系统为 Ubuntu
sudo apt -y install gcc make gdb pkg-config libreadline-dev libicu-dev libldap2-dev uuid-dev tcl-dev libperl-dev python3-dev bison flex openssl libssl-dev libpam-dev libxml2-dev libxslt-dev libossp-uuid-dev libselinux-dev gettext
# 下载 PostgreSQL 源代码仓库
git clone git://git.postgresql.org/git/postgresql.git
# 编译 PostgreSQL,安装 PostgreSQL 至用户主目录下
cd postgresql
./configure --prefix=$HOME/postgres --exec-prefix=$HOME/postgres
make install
# 导入环境变量
echo 'export PATH=$PATH:$HOME/postgres:$HOME/postgres/bin' >> $HOME/.bashrc
echo 'export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$HOME/postgres/lib' >> $HOME/.bashrc
# 初始化数据库
initdb $HOME/postgres/data
数据表准备
OpenDAL 将 PostgreSQL 抽象为文件系统的原理如图所示:
因此,对于 PostgreSQL 而言,我们必须准备一张与图中格式相互匹配的数据表,以供 OpenDAL 应用程序使用:
# 运行 PostgreSQL 服务端
postgres -D $HOME/postgres/data
# 使用 psql 连接 PostgreSQL
psql -d postgres
create table example (
/* 用于存储 OpenDAL 文件名 */
filename text,
/* 用于存储 OpenDAL 文件数据 */
filedata bytea,
/* OpenDAL 要求 filename 具有 UNIQUE 约束 */
unique(filename)
);
Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
项目准备
# 创建一个名为 `opendal-postgresql-service` 的新项目
cargo new opendal-postgresql-service
# 进入项目文件夹
cd opendal-postgresql-service
# 引入 OpenDAL 所需内容
cargo add opendal anyhow tokio
# 启动 OpenDAL PostgreSQL 服务特性
cargo add opendal --features services-postgresql
# tokio 为 程序带来异步支持
cargo add tokio --features full
知识准备
正如 Linux 使用 fd(文件描述符)统一描述各类设备一样,OpenDAL 将 Operator
作为统一的描述各类数据存储设备的对象。
一般而言,我们需要通过 OpenDAL 提供出来的各类 Service
(服务用于描述各类可以存储数据资源的软件,如在这里,我们就需要使用 OpenDAL PostgreSQL Service 用以访问 PostgreSQL)构建 Builder
(构建器被视作是一组 Service
的集合),再由此构建出 Operator
(这就像),再使用 Operator
去访问各类数据资源(如果你阅读过 Linux 文件系统的有关原理,相信你可以有一个更为深刻的理解)。
更多论述可以参考OpenDAL 中的有关部分。
具体实践
打开 opendal-postgresql-service
项目下 src
目录中的 main.rs
,依靠 OpenDAL 中有关 PostgreSQL Service 的部分,修订为如下:
use anyhow::Result;
use opendal::services::Postgresql;
use opendal::Operator;
#[tokio::main]
async fn main() -> Result<()> {
let builder = Postgresql::default()
.root("/")
/* 连接到本地 PostgreSQL 的 postgres 数据库上面 */
.connection_string("postgresql://127.0.0.1:5432/postgres")
/* 使用我们前面创建的 example 表 */
.table("example")
/* 存储文件名的数据列 */
.key_field("filename")
/* 存储文件数据的数据列 */
.value_field("filedata");
let op = Operator::new(builder)?.finish();
Ok(())
}
特别注意:OpenDAL 采取了 Lazy Initialization(延迟初始化)策略
根据 Lazy Reader 有关的论述,OpenDAL sends an actual IO request to the storage when Accessor::read() is invoked
(在数据访问器的数据读取工作真正开始以后,OpenDAL 才开始向有关的存储软件发送真正的 I/O 请求),换而言之,如果只是通过不同的服务层(Service)构建操作符(Operator),OpenDAL 将不会向存储软件展开任何访问的工作。
这种做法可以节约很多非必要场景下的资源。
之后,让我们阅读 Reader 和 Write 的有关部分,将代码又修订为如下:
use anyhow::Result;
use opendal::services::Postgresql;
use opendal::Operator;
#[tokio::main]
async fn main() -> Result<()> {
let builder = Postgresql::default()
.root("/")
/* 假定你的用户名为 user_name */
.connection_string("postgresql://wenyi@127.0.0.1:5432/postgres")
/* 使用我们前面创建的 example 表 */
.table("example")
/* 存储文件名的数据列 */
.key_field("filename")
/* 存储文件数据的数据列 */
.value_field("filedata");
let op = Operator::new(builder)?.finish();
/* 将 `Hello PostgreSQL` 写入到一个名为 `postgres.txt` 的文件夹中 */
op.write("postgres.txt", "Hello PostgreSQL").await?;
/* 构建 Reader */
let content = op
.reader_with("postgres.txt")
.await?
;
/* 经由 Reader 获取到一个 Buffer */
let bs = content.read(0..16).await?;
/* 提取 Buffer 中的内容,即为文件内容 */
println!("{:?}", bs.to_bytes());
Ok(())
}
最后,执行如下的命令:
# 假定处于 opendal-postgresql 目录下
cargo build --release
# 执行构建后的程序
# 请保持 PostgreSQL 正常运行
./target/release/opendal-postgresql
写在最后
感谢我的本科生导师,系主任袁国铭老师,感谢中国 PG 分会魏波、王其达老师,感谢开放原子开源基金会张凯老师,感谢 @Manjusaka,@XuanWo,@尚卓燃 的指导与帮助。
期待将来成为一流的开源科学家!