这周诞生了一个很有意思的想法,如果 databend 能够支持这样的 query:

COPY INTO books
  FROM 's3://databendcloud/loaddata/books.csv'
  file_format = (type = 'CSV' field_delimiter = ','  record_delimiter = '\n' skip_header = 0);

那为什么不能支持这样呢?

COPY INTO books
  FROM 'https://databend.rs/loaddata/books.csv'
  file_format = (type = 'CSV' field_delimiter = ','  record_delimiter = '\n' skip_header = 0);

非常自然,仿佛本来就该如此!


于是我在 OpenDAL 中增加了 HTTP Backend 的支持:feat: Implement http service support。但是这还远远算不上实用,因为大多数时候用户都希望读一批文件而不是单个 URL。以 GH Archive 为例,它存储了 Github 所有 event 每小时的存档,通过这样的 URL 暴露出来:

https://data.gharchive.org/2015-01-01-15.json.gz

GH Archive 给出了通过 wget 来批量下载这些文件的脚本:

wget https://data.gharchive.org/2015-01-{01..31}-{0..23}.json.gz

用户需要将这些文件都下载到本地,解压缩然后再导入到数据库中,这中间存在着大量没有必要的开销。我们能不能直接支持批量下载一批 HTTP 链接呢?比如说:

COPY INTO books
  FROM 'https://data.gharchive.org/' PATTERN="2015-01-{01..31}-{0..23}.json.gz"
  file_format = (type = 'JSON' compression=GZIP);

更自然了!

问题分析

HTTP Backend 相比于 S3 这样的 backend 最大的问题在于 HTTP 规范没有规定如何列取指定目录下的文件,所以我们无法像本地文件系统这样列取出本地的文件,然后再跟指定的表达式做匹配。相反,我们需要支持基于指定的表达式来构建出一份索引并使用这份索引来获取文件。

实际上 curl/wget 正是这样实现的,在 curl/src/tool_urlglob.c 中可以看到 curl 是这样处理的(感谢开源):

  • 最多支持 10 * 1024 个
  • 只支持特定的 glob 集合,比如 {a,b,c}[000..123]
  • 不支持通配符匹配,比如 ?***

我们也可以这么做!

globiter

为此我开发了 globiter,它的功能非常简单,把一个 glob 表达式转化为迭代器:

let pattern = Pattern::parse("https://example.com/{a,b,c}/file/{x,y,z}")?;
for i in pattern.iter() {
    println("{i}")
}
// Output:
// https://example.com/a/file/x
// https://example.com/a/file/y
// https://example.com/a/file/z
// ...
// https://example.com/c/file/z

有了 globiter 的帮助,我们可以根据用户输入的 pattern 静态地构建出一份索引并用来抓取文件了~

更多想法

从任意 Git repo copy 文件会很酷吗?

COPY INTO books
  FROM 'git://github.com/databendcloud/loaddata@main'
  file_format = (type = 'CSV' field_delimiter = ','  record_delimiter = '\n' skip_header = 0);

支持 Github native 的表达式呢?

COPY INTO books
  FROM 'github://databendcloud/loaddata@main'
  file_format = (type = 'CSV' field_delimiter = ','  record_delimiter = '\n' skip_header = 0);

支持 Gitlab 也非常自然是不是?

COPY INTO books
  FROM 'gitlab://databendcloud/loaddata@main'
  file_format = (type = 'CSV' field_delimiter = ','  record_delimiter = '\n' skip_header = 0);

这样的话,从 FTP/FTPS 获取数据看起来也非常有意思:

COPY INTO books
  FROM 'ftp://192.168.100.12/loaddata'
  file_format = (type = 'CSV' field_delimiter = ','  record_delimiter = '\n' skip_header = 0);

我们还有 WebDAV 是不是?

COPY INTO books
  FROM 'webdav://test.nextcloud.com/loaddata'
  file_format = (type = 'CSV' field_delimiter = ','  record_delimiter = '\n' skip_header = 0);

从 Dropbox 下载数据感觉也挺正常的?

COPY INTO books
  FROM 'dropbox://path/to/dir'
  file_format = (type = 'CSV' field_delimiter = ','  record_delimiter = '\n' skip_header = 0);

Dropbox 支持了,那 Onedrive / iCloud / Google Drive 也很自然吧?

COPY INTO books
  FROM 'icloud://path/to/dir'
  file_format = (type = 'CSV' field_delimiter = ','  record_delimiter = '\n' skip_header = 0);

或许我们可以支持 Stage 之间的直接数据迁移?

COPY BETWEEN dropbox://path/to/dir onedrive://path/to/dir

事情逐渐变得有意思起来,有些迫不及待了~