从pypi网站Duckcp页面下载duckcp-0.1.1-py3-none-any.whl
一开始用的Python 3.11.2环境。
继续沿用上文打补丁的方法,得到一个支持python3.11.1的安装包。
因为缺少zip压缩工具,使用python程序来完成对修改后文件的重新压缩。
import os
import zipfile
from pathlib import Path
def zip_directory_contents(source_dir, output_zip):
"""
压缩目录下的所有内容到ZIP文件(不包括根目录本身)
:param source_dir: 要压缩的目录路径
:param output_zip: 输出的ZIP文件路径
"""
source_path = Path(source_dir).resolve()
with zipfile.ZipFile(output_zip, 'w', zipfile.ZIP_DEFLATED) as zipf:
for root, dirs, files in os.walk(source_dir):
# 计算相对于源目录的路径
rel_path = os.path.relpath(root, start=source_path)
for file in files:
file_path = Path(root) / file
# 在ZIP中存储的相对路径(去掉最外层的a/)
arcname = os.path.join(rel_path, file)
zipf.write(file_path, arcname=arcname)
# 确保空目录也被包含
if not files and not dirs:
# 添加空目录(必须以/结尾)
rel_dir = os.path.relpath(root, start=source_path) + '/'
zipf.writestr(rel_dir, '')
zip_directory_contents('/par/whl/', '/par/whloutput_zip')
将得到的zip文件重命名为原始文件名,然后安装
mv /par/whloutput_zip /par/duckcp-0.1.1-py3-none-any.whl
python3 pip.pyz install /par/duckcp-0.1.1-py3-none-any.whl --break-system-packages -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple
安装过程没有报错。
1.创建配置元数据库
duckcp meta create
Traceback (most recent call last):
File "/usr/local/bin/duckcp", line 5, in <module>
from duckcp import main
File "/usr/local/lib/python3.11/dist-packages/duckcp/__init__.py", line 5, in <module>
from duckcp.boot import app, meta_command, repository_command, storage_command, transformer_command, task_command
File "/usr/local/lib/python3.11/dist-packages/duckcp/boot/__init__.py", line 5, in <module>
from duckcp.configuration.meta_configuration import enable_metadata_configuration
File "/usr/local/lib/python3.11/dist-packages/duckcp/configuration/meta_configuration.py", line 9, in <module>
from duckcp.entity.executor import Executor
File "/usr/local/lib/python3.11/dist-packages/duckcp/entity/executor.py", line 69
def records[T: tuple[Any, ...]](self, sql: str, *parameters: Any, constructor: RecordConstructorProtocol[T] = None) -> list[T]:
^
SyntaxError: expected '('
创建元数据库报错,应该就是不支持python3.11版本。改用Python 3.13.1,这一步顺利完成。需要说明,这些步骤都是由于我的环境和软件要求的Python版本不同引起的,如果版本正确, 这些问题都不会发生。
duckcp meta create
2025-07-28 07:02:08.448 INFO duckcp.service.meta_service#meta_create : 配置文件(/root/.config/com.yinfn.duckcp/configuration.db)初始化 ]8;id=117526;file:///usr/local/lib/python3.13/site-packages/duckcp/service/meta_service.pymeta_service.py]8;;:]8;id=511219;file:///usr/local/lib/python3.13/site-packages/duckcp/service/meta_service.py#2020]8;;
2025-07-28 07:02:08.452 INFO duckcp.service.meta_service#meta_create : 执行脚本(001-repositories.sql) ]8;id=289077;file:///usr/local/lib/python3.13/site-packages/duckcp/service/meta_service.pymeta_service.py]8;;:]8;id=303663;file:///usr/local/lib/python3.13/site-packages/duckcp/service/meta_service.py#2424]8;;
命令输出的信息有点多,向软件作者张泽鹏先生请教,他说可以用-q选项减少输出。但我们首次使用时,信息多更有利于排查问题。
2.创建源和目标存储库
因为我要实现从csv文件汇总数据,所以要在配置元数据库中登记csv文件存储位置。
通过以下命令完成
duckcp repository create 文件仓库 -k file --folder data
作为数据源的csv文件,直接复制自duckcp例子,因为复制出来的字符分隔符是tab字符,所以改名为progs.tsv
id name language
1 Joe Java
2 Alice JavaScript
3 Leon C/C++
4 William Java
5 James C/C++
6 Enson C/C++
对于目标,我一开始用的命令是,
duckcp repository create sqlite -k sqlite --folder data --file sqlite.db
以为这样就能把data目录下的sqlite.db数据库作为目标,其实不然,打开配置元数据库检查,里面的内容如下。
./sqlite3 $HOME/.config/com.yinfn.duckcp/configuration.db
sqlite> .tables
credentials snapshots tasks transformers
repositories storages tasks_transformers
sqlite> .header on
sqlite> select * from repositories;
id|kind|code|properties|created_at|updated_at
1|file|文件仓库|{"folder":"/par/data"}|2025-07-28 07:18:32|2025-07-28 07:18:32
2|sqlite|sqlite|{"file":"/par/sqlite.db","folder":"/par/data"}|2025-07-28 07:20:25|2025-07-28 07:20:25
sqlite所在行的properties列显示不符合预期。
张泽鹏先生说,数据库无需使用–folder参数,只要在–file中写明完整路径即可。所以正确命令如下:
duckcp repository create sqlite2 -k sqlite --file data/sqlite.db
因为duckcp本质上是一个ETL工具,它不会帮我们建库,所以要事先手工用sqlite软件实际创建data目录下的sqlite.db数据库,并建立保存目标数据的表。
./sqlite3 data/sqlite.db
sqlite> create table prog_sum(language varchar,cnt int);
sqlite> .exit
3.创建存储单元
存储单元这个名字的含义是数据库里的表的别名,我一开始漏掉了这一步,执行后面的命令就出错了main : 目标仓库(sqlite2)的存储单元(prog_sum)不存在
。
duckcp storage create prog_sum -r sqlite2 --table prog_sum
4.创建数据迁移任务
第一步是建立一个sql脚本,它负责读取源表(本例是Csv文件)执行汇总。
我直接照搬duckcp例子中的脚本,改了文件名。保存为data/trans.sql
select
"language" as "编程语言",
count(*) as "程序员人数"
from
read_csv('progs.tsv')
group by
"language"
order by
"程序员人数" desc
然后执行命令
duckcp transformer create 数据统计 -s 文件仓库 -t sqlite2 -o prog_sum -f data/trans.sql
5.执行数据迁移任务
duckcp transformer execute 数据统计
2025-07-28 08:28:36.086 INFO duckcp.transform.database_transform#database_transform : 清空表(DELETE FROM "prog_sum") ]8;id=758441;file:///usr/local/lib/python3.13/site-packages/duckcp/transform/database_transform.pydatabase_transform.py]8;;:]8;id=170976;file:///usr/local/lib/python3.13/site-packages/duckcp/transform/database_transform.py#3030]8;;
2025-07-28 08:28:36.221 INFO duckcp.transform.database_transform#database_transform : 批量添加数据(INSERT INTO "prog_sum" ("编程语言", "程序员人数") VALUES (?, ?)) ]8;id=941332;file:///usr/local/lib/python3.13/site-packages/duckcp/transform/database_transform.pydatabase_transform.py]8;;:]8;id=508716;file:///usr/local/lib/python3.13/site-packages/duckcp/transform/database_transform.py#3535]8;;
2025-07-28 08:28:36.224 ERROR duckcp#main : table prog_sum has no column named 编程语言
又报错了,但这是最后一个错误,提示信息也很明确,只要把目标表的列名改成和上述sql一模一样的列名就行了。
./sqlite3 data/sqlite.db
sqlite> alter table prog_sum rename language to "编程语言";
sqlite> alter table prog_sum rename cnt to "程序员人数";
sqlite> .exit
再次执行任务,当看到transformer_execute : 从仓库(文件仓库)迁移数据到仓库(sqlite2)的存储单元(prog_sum)
信息就表明数据迁移成功,此时可以用sqlite打开目标表,结果符合预期。
sqlite> select * from prog_sum;
C/C++|3
Java|2
JavaScript|1
下面验证源文件更新后,数据重新汇总的结果被转移到目标表。
./duckdb131
DuckDB v1.3.1 (Ossivalis) 2063dda3e6
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
D select * from 'data/progs.tsv';
┌───────┬─────────┬────────────┐
│ id │ name │ language │
│ int64 │ varchar │ varchar │
├───────┼─────────┼────────────┤
│ 1 │ Joe │ Java │
│ 2 │ Alice │ JavaScript │
│ 3 │ Leon │ C/C++ │
│ 4 │ William │ Java │
│ 5 │ James │ C/C++ │
│ 6 │ Enson │ C/C++ │
└───────┴─────────┴────────────┘
D copy( from 'data/progs.tsv' union all select 7,'zhang3','sql') to 'data/progs.tsv';
D select * from 'data/progs.tsv';
┌───────┬─────────┬────────────┐
│ id │ name │ language │
│ int64 │ varchar │ varchar │
├───────┼─────────┼────────────┤
│ 1 │ Joe │ Java │
│ 2 │ Alice │ JavaScript │
│ 3 │ Leon │ C/C++ │
│ 4 │ William │ Java │
│ 5 │ James │ C/C++ │
│ 6 │ Enson │ C/C++ │
│ 7 │ zhang3 │ sql │
└───────┴─────────┴────────────┘
D .exit
root@217449ea9d61:/par# duckcp transformer execute 数据统计
2025-07-28 08:51:51.005 INFO duckcp.transform.database_transform#database_transform : 清空表(DELETE FROM "prog_sum") ]8;id=353910;file:///usr/local/lib/python3.13/site-packages/duckcp/transform/database_transform.pydatabase_transform.py]8;;:]8;id=259843;file:///usr/local/lib/python3.13/site-packages/duckcp/transform/database_transform.py#3030]8;;
2025-07-28 08:51:51.123 INFO duckcp.transform.database_transform#database_transform : 批量添加数据(INSERT INTO "prog_sum" ("编程语言", "程序员人数") VALUES (?, ?)) ]8;id=595510;file:///usr/local/lib/python3.13/site-packages/duckcp/transform/database_transform.pydatabase_transform.py]8;;:]8;id=232644;file:///usr/local/lib/python3.13/site-packages/duckcp/transform/database_transform.py#3535]8;;
2025-07-28 08:51:51.323 INFO duckcp.service.transformer_service#transformer_execute : 从仓库(文件仓库)迁移数据到仓库(sqlite2)的存储单元(prog_sum) ]8;id=489086;file:///usr/local/lib/python3.13/site-packages/duckcp/service/transformer_service.pytransformer_service.py]8;;:]8;id=370598;file:///usr/local/lib/python3.13/site-packages/duckcp/service/transformer_service.py#273273]8;;
root@217449ea9d61:/par# ./sqlite3 data/sqlite.db
SQLite version 3.42.0 2023-05-16 12:36:15
Enter ".help" for usage hints.
sqlite> .header on
sqlite> select * from prog_sum;
编程语言|程序员人数
C/C++|3
Java|2
sql|1
JavaScript|1
可见,结果多出了sql|1这行,它就是数据源插入zhang3的结果。