從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 Pathdef 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.whlpython3 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 mainFile "/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_commandFile "/usr/local/lib/python3.11/dist-packages/duckcp/boot/__init__.py", line 5, in <module>from duckcp.configuration.meta_configuration import enable_metadata_configurationFile "/usr/local/lib/python3.11/dist-packages/duckcp/configuration/meta_configuration.py", line 9, in <module>from duckcp.entity.executor import ExecutorFile "/usr/local/lib/python3.11/dist-packages/duckcp/entity/executor.py", line 69def 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 create2025-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_transformerssqlite> .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 "程序員人數"
fromread_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的結果。