<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Wang Fenjin</title><link>https://www.wangfenjin.com/</link><description>Recent content on Wang Fenjin</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><copyright>Copyright © 2026 Wang Fenjin :: Powered by &lt;a href="http://gohugo.io"&gt;Hugo&lt;/a&gt;</copyright><lastBuildDate>Sat, 21 Feb 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://www.wangfenjin.com/index.xml" rel="self" type="application/rss+xml"/><item><title>SwanLake: An Arrow Flight SQL Datalake Service Built on DuckDB + DuckLake</title><link>https://www.wangfenjin.com/posts/swanlake-en/</link><pubDate>Sat, 21 Feb 2026 00:00:00 +0000</pubDate><guid>https://www.wangfenjin.com/posts/swanlake-en/</guid><description>&lt;p&gt;After handing &lt;a href="https://github.com/duckdb/duckdb-rs"&gt;duckdb-rs&lt;/a&gt; over to the DuckDB team in 2023, one question kept coming back to me:&lt;/p&gt;
&lt;p&gt;If DuckDB is already great in-process, how do we turn that power into a service that is easier to integrate, deploy, and operate?&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/swanlake-io/swanlake"&gt;SwanLake&lt;/a&gt; is my answer to that question.&lt;/p&gt;
&lt;p&gt;It is a Rust-based Arrow Flight SQL server, powered by DuckDB, with DuckLake-oriented extensions for datalake scenarios. In practice, SwanLake is built around a three-part combination: DuckDB + DuckLake + Flight SQL.&lt;/p&gt;</description><content>&lt;p&gt;After handing &lt;a href="https://github.com/duckdb/duckdb-rs"&gt;duckdb-rs&lt;/a&gt; over to the DuckDB team in 2023, one question kept coming back to me:&lt;/p&gt;
&lt;p&gt;If DuckDB is already great in-process, how do we turn that power into a service that is easier to integrate, deploy, and operate?&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/swanlake-io/swanlake"&gt;SwanLake&lt;/a&gt; is my answer to that question.&lt;/p&gt;
&lt;p&gt;It is a Rust-based Arrow Flight SQL server, powered by DuckDB, with DuckLake-oriented extensions for datalake scenarios. In practice, SwanLake is built around a three-part combination: DuckDB + DuckLake + Flight SQL.&lt;/p&gt;
&lt;div style="text-align: center;"&gt;
&lt;img src="https://www.wangfenjin.com/img/swanlake-overview.jpeg" alt="SwanLake project overview" style="width: 760px; max-width: 100%; height: auto;" /&gt;
&lt;/div&gt;
&lt;h2 id="why-i-started-swanlake"&gt;Why I started SwanLake&lt;/h2&gt;
&lt;p&gt;With duckdb-rs, the primary goal was clear: make DuckDB feel natural in Rust. That part worked well, but new constraints became obvious:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Most teams are not single-language; they need one service interface across stacks.&lt;/li&gt;
&lt;li&gt;Real workloads involve object storage, metadata services, and multiple cooperating systems.&lt;/li&gt;
&lt;li&gt;Production systems need observability, not just logs.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So SwanLake was never “just another wrapper”. I wanted a practical analytics service entrypoint.&lt;/p&gt;
&lt;h2 id="architecture"&gt;Architecture&lt;/h2&gt;
&lt;p&gt;You can read SwanLake as a five-layer system:&lt;/p&gt;
&lt;h3 id="1-access-layer-arrow-flight-sql-grpc"&gt;1) Access Layer: Arrow Flight SQL (gRPC)&lt;/h3&gt;
&lt;p&gt;All query/update traffic enters through Flight SQL. This gives us a protocol that is efficient and language-neutral; the Rust/Go/Python examples in the repo validate this layer directly.&lt;/p&gt;
&lt;h3 id="2-session-layer-session-registry"&gt;2) Session Layer: Session Registry&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;swanlake-core&lt;/code&gt; manages connection-scoped sessions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;session IDs are created/reused from &lt;code&gt;peer_addr&lt;/code&gt; or &lt;code&gt;peer_ip&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;prepared statements, transactions, and temp objects remain session-affine,&lt;/li&gt;
&lt;li&gt;max sessions + idle timeout protect server resources.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="3-execution-layer-duckdb"&gt;3) Execution Layer: DuckDB&lt;/h3&gt;
&lt;p&gt;I did not build a new engine. SwanLake wraps DuckDB for service use: each session has an isolated connection, startup preloads &lt;code&gt;ducklake/httpfs/aws/postgres&lt;/code&gt; extensions, and &lt;code&gt;SWANLAKE_DUCKLAKE_INIT_SQL&lt;/code&gt; can inject bootstrap SQL.&lt;/p&gt;
&lt;h3 id="4-datalake-layer-ducklake"&gt;4) Datalake Layer: DuckLake&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://ducklake.select/"&gt;DuckLake&lt;/a&gt; is the key piece. Without it, DuckDB is mainly an excellent local analytical engine. With DuckLake, metadata and object-storage paths can be organized consistently, which makes DuckDB-based datalake services practical.&lt;/p&gt;
&lt;h3 id="5-operations-layer-metrics--status--config"&gt;5) Operations Layer: Metrics + Status + Config&lt;/h3&gt;
&lt;p&gt;Runtime metrics (latency/slow query/errors), status endpoints (&lt;code&gt;/&lt;/code&gt; + &lt;code&gt;status.json&lt;/code&gt;), and env-based configuration (&lt;code&gt;SWANLAKE_*&lt;/code&gt;) form the operational surface. This layer is what makes the system observable and manageable in production.&lt;/p&gt;
&lt;h2 id="observability-was-a-first-class-requirement"&gt;Observability was a first-class requirement&lt;/h2&gt;
&lt;p&gt;SwanLake has a built-in status page (default &lt;code&gt;:4215&lt;/code&gt;) plus &lt;code&gt;status.json&lt;/code&gt; for machine consumption. It exposes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;session counts and idle indicators,&lt;/li&gt;
&lt;li&gt;query/update latency stats (avg, p95, p99),&lt;/li&gt;
&lt;li&gt;slow query and recent error history.&lt;/li&gt;
&lt;/ol&gt;
&lt;div style="text-align: center;"&gt;
&lt;img src="https://www.wangfenjin.com/img/swanlake-status-page.png" alt="SwanLake status page" style="width: 760px; max-width: 100%; height: auto;" /&gt;
&lt;/div&gt;
&lt;p&gt;I added this because these are exactly the signals I want when debugging production behavior.&lt;/p&gt;
&lt;h2 id="how-i-read-the-current-benchmark-data"&gt;How I read the current benchmark data&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;BENCHMARK.md&lt;/code&gt; (CI artifact dated 2026-02-21) includes TPCH results at SF=0.1 where &lt;code&gt;postgres_local_file&lt;/code&gt; outperforms &lt;code&gt;postgres_s3&lt;/code&gt; in that run.&lt;/p&gt;
&lt;div style="max-width: 760px; margin: 0 auto; overflow-x: auto;"&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric (SF=0.1)&lt;/th&gt;
&lt;th style="text-align: right;"&gt;postgres_local_file&lt;/th&gt;
&lt;th style="text-align: right;"&gt;postgres_s3&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Throughput (req/s)&lt;/td&gt;
&lt;td style="text-align: right;"&gt;10.428&lt;/td&gt;
&lt;td style="text-align: right;"&gt;4.867&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avg latency (ms)&lt;/td&gt;
&lt;td style="text-align: right;"&gt;382.751&lt;/td&gt;
&lt;td style="text-align: right;"&gt;818.041&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;p95 latency (ms)&lt;/td&gt;
&lt;td style="text-align: right;"&gt;829.236&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1904.023&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;p99 latency (ms)&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1116.002&lt;/td&gt;
&lt;td style="text-align: right;"&gt;2661.619&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;This is expected directionally: object storage paths usually add more variability.&lt;/p&gt;
&lt;p&gt;One practical point is critical here: when using S3 or other remote object storage, you should usually enable &lt;a href="https://duckdb.org/community_extensions/extensions/cache_httpfs"&gt;&lt;code&gt;cache_httpfs&lt;/code&gt;&lt;/a&gt;, otherwise latency, especially tail latency, can become very unstable.&lt;/p&gt;
&lt;p&gt;This is already reflected in the benchmark workflow configuration. See &lt;a href="https://github.com/swanlake-io/swanlake/blob/main/.github/workflows/performance.yml"&gt;&lt;code&gt;.github/workflows/performance.yml&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;postgres_s3&lt;/code&gt; defaults to &lt;code&gt;BENCHBASE_ENABLE_CACHE_HTTPFS=true&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;postgres_local_file&lt;/code&gt; defaults to &lt;code&gt;BENCHBASE_ENABLE_CACHE_HTTPFS=false&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;the workflow input can override this behavior.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;But I do not think the takeaway is “local is always better”. A better takeaway is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;choose storage tiers based on workload shape,&lt;/li&gt;
&lt;li&gt;run repeated benchmarks and track variance,&lt;/li&gt;
&lt;li&gt;keep performance visibility continuous, not one-off.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="from-duckdb-rs-to-swanlake"&gt;From duckdb-rs to SwanLake&lt;/h2&gt;
&lt;p&gt;For me, duckdb-rs and SwanLake are part of the same line of work.&lt;/p&gt;
&lt;p&gt;duckdb-rs solved: how to use DuckDB elegantly inside Rust applications.&lt;/p&gt;
&lt;p&gt;SwanLake solves: how to provide DuckDB as a shared, deployable, operable service for teams.&lt;/p&gt;
&lt;h2 id="what-i-will-keep-working-on"&gt;What I will keep working on&lt;/h2&gt;
&lt;p&gt;SwanLake is still evolving. My near-term focus is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;more production-oriented reliability and load testing,&lt;/li&gt;
&lt;li&gt;better performance predictability on object storage backends,&lt;/li&gt;
&lt;li&gt;a more consistent developer experience across server and clients.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you used duckdb-rs before, I would love you to try SwanLake and share feedback via issues or PRs.&lt;/p&gt;
&lt;h2 id="references"&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://duckdb.org/"&gt;DuckDB official website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ducklake.select/"&gt;DuckLake official website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://arrow.apache.org/docs/format/FlightSql.html"&gt;Arrow Flight SQL documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://swanlake.io"&gt;SwanLake official website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/swanlake-io/swanlake"&gt;SwanLake source code&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content></item><item><title>SwanLake：一个基于 DuckDB + DuckLake 的 Arrow Flight SQL 数据湖服务</title><link>https://www.wangfenjin.com/posts/swanlake/</link><pubDate>Sat, 21 Feb 2026 00:00:00 +0000</pubDate><guid>https://www.wangfenjin.com/posts/swanlake/</guid><description>&lt;p&gt;2023 年我把 &lt;a href="https://github.com/duckdb/duckdb-rs"&gt;duckdb-rs&lt;/a&gt; 交给 DuckDB 官方维护之后，心里一直有个没做完的题：&lt;/p&gt;
&lt;p&gt;如果 DuckDB 在单机进程里已经足够强，那怎么把这份能力变成一个更容易接入、可部署、可观测的服务？&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/swanlake-io/swanlake"&gt;SwanLake&lt;/a&gt; 就是我给这个问题的答案。&lt;/p&gt;
&lt;p&gt;它本质上是一个基于 Rust 的 Arrow Flight SQL Server，底层执行引擎是 DuckDB，同时围绕 DuckLake 做了数据湖场景的能力扩展。更准确地说，SwanLake 的核心组合是 DuckDB + DuckLake + Arrow Flight SQL。&lt;/p&gt;
&lt;div style="text-align: center;"&gt;
&lt;img src="https://www.wangfenjin.com/img/swanlake-overview.jpeg" alt="SwanLake 项目示意图" style="width: 760px; max-width: 100%; height: auto;" /&gt;
&lt;/div&gt;
&lt;h2 id="为什么是-swanlake"&gt;为什么是 SwanLake&lt;/h2&gt;
&lt;p&gt;我最早做 duckdb-rs 的时候，目标是把 DuckDB 更自然地带到 Rust 生态里。这个目标后来基本实现了，但新的问题也很明确：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;很多团队不是 Rust 单一语言栈，客户端接入方式需要统一。&lt;/li&gt;
&lt;li&gt;业务里常见的不只是“本地查询”，而是对象存储 + 元数据 + 多服务协作。&lt;/li&gt;
&lt;li&gt;线上系统需要可观测性，不能只靠日志排障。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;所以 SwanLake 从一开始就不是“再封一层 API”，而是想做一个可以真正放进生产系统的分析服务入口。&lt;/p&gt;
&lt;h2 id="系统架构"&gt;系统架构&lt;/h2&gt;
&lt;p&gt;SwanLake 可以按 5 层来理解：&lt;/p&gt;
&lt;h3 id="1-接入层arrow-flight-sqlgrpc"&gt;1) 接入层：Arrow Flight SQL（gRPC）&lt;/h3&gt;
&lt;p&gt;服务对外暴露 Flight SQL 接口，查询和更新请求都从这里进入。这个协议的核心价值是跨语言和高吞吐，仓库里的 Rust/Go/Python 示例就是围绕这层展开的。&lt;/p&gt;</description><content>&lt;p&gt;2023 年我把 &lt;a href="https://github.com/duckdb/duckdb-rs"&gt;duckdb-rs&lt;/a&gt; 交给 DuckDB 官方维护之后，心里一直有个没做完的题：&lt;/p&gt;
&lt;p&gt;如果 DuckDB 在单机进程里已经足够强，那怎么把这份能力变成一个更容易接入、可部署、可观测的服务？&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/swanlake-io/swanlake"&gt;SwanLake&lt;/a&gt; 就是我给这个问题的答案。&lt;/p&gt;
&lt;p&gt;它本质上是一个基于 Rust 的 Arrow Flight SQL Server，底层执行引擎是 DuckDB，同时围绕 DuckLake 做了数据湖场景的能力扩展。更准确地说，SwanLake 的核心组合是 DuckDB + DuckLake + Arrow Flight SQL。&lt;/p&gt;
&lt;div style="text-align: center;"&gt;
&lt;img src="https://www.wangfenjin.com/img/swanlake-overview.jpeg" alt="SwanLake 项目示意图" style="width: 760px; max-width: 100%; height: auto;" /&gt;
&lt;/div&gt;
&lt;h2 id="为什么是-swanlake"&gt;为什么是 SwanLake&lt;/h2&gt;
&lt;p&gt;我最早做 duckdb-rs 的时候，目标是把 DuckDB 更自然地带到 Rust 生态里。这个目标后来基本实现了，但新的问题也很明确：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;很多团队不是 Rust 单一语言栈，客户端接入方式需要统一。&lt;/li&gt;
&lt;li&gt;业务里常见的不只是“本地查询”，而是对象存储 + 元数据 + 多服务协作。&lt;/li&gt;
&lt;li&gt;线上系统需要可观测性，不能只靠日志排障。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;所以 SwanLake 从一开始就不是“再封一层 API”，而是想做一个可以真正放进生产系统的分析服务入口。&lt;/p&gt;
&lt;h2 id="系统架构"&gt;系统架构&lt;/h2&gt;
&lt;p&gt;SwanLake 可以按 5 层来理解：&lt;/p&gt;
&lt;h3 id="1-接入层arrow-flight-sqlgrpc"&gt;1) 接入层：Arrow Flight SQL（gRPC）&lt;/h3&gt;
&lt;p&gt;服务对外暴露 Flight SQL 接口，查询和更新请求都从这里进入。这个协议的核心价值是跨语言和高吞吐，仓库里的 Rust/Go/Python 示例就是围绕这层展开的。&lt;/p&gt;
&lt;h3 id="2-会话层session-registry"&gt;2) 会话层：Session Registry&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;swanlake-core&lt;/code&gt; 里有连接级会话管理：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;按 &lt;code&gt;peer_addr&lt;/code&gt; 或 &lt;code&gt;peer_ip&lt;/code&gt; 生成/复用会话 ID。&lt;/li&gt;
&lt;li&gt;prepared statement、事务、临时对象都跟随会话。&lt;/li&gt;
&lt;li&gt;通过最大会话数和空闲超时做资源保护。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="3-执行层duckdb"&gt;3) 执行层：DuckDB&lt;/h3&gt;
&lt;p&gt;执行层没有重新造轮子，而是把 DuckDB 封装成服务可用的执行引擎。每个会话持有独立连接，启动时会加载 &lt;code&gt;ducklake/httpfs/aws/postgres&lt;/code&gt; 扩展，并支持通过 &lt;code&gt;SWANLAKE_DUCKLAKE_INIT_SQL&lt;/code&gt; 注入初始化 SQL。&lt;/p&gt;
&lt;h3 id="4-数据湖层ducklake"&gt;4) 数据湖层：DuckLake&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://ducklake.select/"&gt;DuckLake&lt;/a&gt; 是这个系统最关键的一层。没有 DuckLake，DuckDB 更多是本地分析引擎；有了 DuckLake，元数据和对象存储路径就能以统一方式组织起来，SwanLake 才能把“DuckDB 做数据湖”变成可部署的服务方案。&lt;/p&gt;
&lt;h3 id="5-运维层metrics--status--配置"&gt;5) 运维层：Metrics + Status + 配置&lt;/h3&gt;
&lt;p&gt;运行时指标（延迟、慢查询、错误）、状态页（&lt;code&gt;/&lt;/code&gt; + &lt;code&gt;status.json&lt;/code&gt;）和环境变量配置（&lt;code&gt;SWANLAKE_*&lt;/code&gt;）共同组成了运维面。这个层的目标是让系统上线后可观测、可调优、可回滚。&lt;/p&gt;
&lt;h2 id="可观测性"&gt;可观测性&lt;/h2&gt;
&lt;p&gt;SwanLake 内置了状态页（默认 &lt;code&gt;:4215&lt;/code&gt;）和 &lt;code&gt;status.json&lt;/code&gt;，会展示：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;当前会话数、空闲时长等会话状态。&lt;/li&gt;
&lt;li&gt;Query/Update 延迟统计（平均、P95、P99）。&lt;/li&gt;
&lt;li&gt;慢查询和最近错误。&lt;/li&gt;
&lt;/ol&gt;
&lt;div style="text-align: center;"&gt;
&lt;img src="https://www.wangfenjin.com/img/swanlake-status-page.png" alt="SwanLake 状态页" style="width: 760px; max-width: 100%; height: auto;" /&gt;
&lt;/div&gt;
&lt;p&gt;我做这个页面不是为了“好看”，而是因为这正是我自己排查问题时最想第一时间看到的数据。&lt;/p&gt;
&lt;h2 id="当前-benchmark我怎么看"&gt;当前 benchmark，我怎么看&lt;/h2&gt;
&lt;p&gt;仓库里的 &lt;code&gt;BENCHMARK.md&lt;/code&gt;（2026-02-21）有一组 TPCH（SF=0.1）结果：&lt;code&gt;postgres_local_file&lt;/code&gt; 相比 &lt;code&gt;postgres_s3&lt;/code&gt; 在这轮测试里更快。&lt;/p&gt;
&lt;div style="max-width: 760px; margin: 0 auto; overflow-x: auto;"&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;指标（SF=0.1）&lt;/th&gt;
&lt;th style="text-align: right;"&gt;postgres_local_file&lt;/th&gt;
&lt;th style="text-align: right;"&gt;postgres_s3&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Throughput (req/s)&lt;/td&gt;
&lt;td style="text-align: right;"&gt;10.428&lt;/td&gt;
&lt;td style="text-align: right;"&gt;4.867&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avg latency (ms)&lt;/td&gt;
&lt;td style="text-align: right;"&gt;382.751&lt;/td&gt;
&lt;td style="text-align: right;"&gt;818.041&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;p95 latency (ms)&lt;/td&gt;
&lt;td style="text-align: right;"&gt;829.236&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1904.023&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;p99 latency (ms)&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1116.002&lt;/td&gt;
&lt;td style="text-align: right;"&gt;2661.619&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;这个结果在预期之内：对象存储链路会引入更高的不确定性。&lt;/p&gt;
&lt;p&gt;另外这里有一个非常重要的实践建议：如果后端是 S3 或其他远程对象存储，建议默认启用 &lt;a href="https://duckdb.org/community_extensions/extensions/cache_httpfs"&gt;&lt;code&gt;cache_httpfs&lt;/code&gt;&lt;/a&gt;，否则延迟（尤其 tail latency）会非常不稳定。&lt;/p&gt;
&lt;p&gt;这个策略我已经放进项目基准流程里了，具体可以直接看 &lt;a href="https://github.com/swanlake-io/swanlake/blob/main/.github/workflows/performance.yml"&gt;&lt;code&gt;.github/workflows/performance.yml&lt;/code&gt;&lt;/a&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;postgres_s3&lt;/code&gt; 默认 &lt;code&gt;BENCHBASE_ENABLE_CACHE_HTTPFS=true&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;postgres_local_file&lt;/code&gt; 默认 &lt;code&gt;BENCHBASE_ENABLE_CACHE_HTTPFS=false&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;也可以通过 workflow input 显式覆盖该参数。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;但我不想把它简单总结成“本地一定更好”。更准确的结论是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;你需要根据 workload 做分层（热数据、本地缓存、远端对象存储）。&lt;/li&gt;
&lt;li&gt;你需要反复跑 benchmark 看方差，而不是拿一次结果定架构。&lt;/li&gt;
&lt;li&gt;你需要把指标做成持续可见的数据，而不是一次性报告。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="从-duckdb-rs-到-swanlake"&gt;从 duckdb-rs 到 SwanLake&lt;/h2&gt;
&lt;p&gt;如果说 duckdb-rs 解决的是“如何让开发者在 Rust 里优雅地用 DuckDB”，那 SwanLake 解决的是另一个问题：&lt;/p&gt;
&lt;p&gt;如何把 DuckDB 变成一个团队可共享的、可部署的、可运维的数据服务。&lt;/p&gt;
&lt;p&gt;这两个项目对我来说是一条连续的技术路线，而不是两个孤立项目。&lt;/p&gt;
&lt;h2 id="后面还会做什么"&gt;后面还会做什么&lt;/h2&gt;
&lt;p&gt;SwanLake 还在持续迭代，我接下来会继续重点做几件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;继续补齐生产场景下的稳定性与压测数据。&lt;/li&gt;
&lt;li&gt;优化对象存储场景的性能和可预测性。&lt;/li&gt;
&lt;li&gt;让客户端和服务端的使用体验更统一，降低接入门槛。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果你之前用过 duckdb-rs，我也很欢迎你来试试 SwanLake，提 issue、提 PR、或者直接分享你遇到的问题。&lt;/p&gt;
&lt;h2 id="参考"&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://duckdb.org/"&gt;DuckDB 官网&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ducklake.select/"&gt;DuckLake 官网&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://arrow.apache.org/docs/format/FlightSql.html"&gt;Arrow Flight SQL 官网&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://swanlake.io"&gt;SwanLake 官网&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/swanlake-io/swanlake"&gt;SwanLake 代码&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content></item><item><title>2026 年的软件开发流程，会被 AI 改成什么样？</title><link>https://www.wangfenjin.com/posts/software-engineering-in-2026/</link><pubDate>Wed, 31 Dec 2025 00:00:00 +0000</pubDate><guid>https://www.wangfenjin.com/posts/software-engineering-in-2026/</guid><description>&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;这就意味着宇宙普适的物理规律不存在，那物理学……也不存在了。&amp;ldquo;汪淼从窗外收回目光说。&lt;/p&gt;
&lt;div style="text-align: center;"&gt;——《三体》&lt;/div&gt;
&lt;/blockquote&gt;
&lt;p&gt;多年以后，面对黑色屏幕上闪动的白色光标，我们将会回想起，那个 AI 编程横空出世的 2025。&lt;/p&gt;
&lt;p&gt;2025 年是 AI 编程的大年，从 &lt;a href="https://openrouter.ai/state-of-ai"&gt;OpenRouter 的年终统计&lt;/a&gt; 来看，AI 编程用的 Token 占了整个 Token 使用量的一半左右。
&lt;img src="https://www.wangfenjin.com/img/2025-programming-dominant.png" alt="programing-token"&gt;&lt;/p&gt;
&lt;p&gt;作为“资深程序员”，我今年也贡献了很多的 token 使用量，深切感受到 AI 编程对于软件开发领域带来了不可逆的影响。至少从技术上，我认为这些影响绝大部分都是正向的。&lt;/p&gt;
&lt;p&gt;之前写程序一定程度上是“体力劳动”，不管有多宏大的想法，代码总要一行一行写。 AI 编程让程序员向脑力劳动又前进了一步，我们需要更激进地学习新知识、使用新工具，从这个角度来说，我认为 AI 编程是提高了对计算机从业者的要求，而不是某些媒体鼓吹的“技术平权”。&lt;/p&gt;
&lt;p&gt;这篇文章总结下我对 AI 编程现状的观察，以及对后续发展的预期。&lt;/p&gt;
&lt;h2 id="开发工具"&gt;开发工具&lt;/h2&gt;
&lt;p&gt;首先从开发工具讲起。明星创业公司 Cursor 因为 VS Code + Claude 一夜走红，算是彻底带火了 AI 编程。但是我想讲的不是这个，而是为什么突然所有的公司都开始做 CLI 工具，比如 Claude Code, Codex, Gemini CLI 等等？&lt;/p&gt;
&lt;p&gt;我认为这里有三个方面的原因：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;投入太大： IDE 或者可视化编辑器这一块，VS Code已经是绝对的霸主，这可以说是一个已经解决的问题。如果新产品还从这个方向切入，势必要投入不少精力追赶，这在这个快速迭代的时代是不可想象的。虽然市面上还是有不少基于 VS Code fork 出来的编辑器，但是很难像 Cursor 刚出来的时候给人的惊艳感，包括 Amazon 的 Kiro 刚出来的 Spec 模式确实很不错，但是这些模式很容易被同行快速学习和复制，后续也渐渐陷入平庸。&lt;/li&gt;
&lt;li&gt;产出太小：其实如果投入大但是又是必须的话，那么还是避免不了要去做。但是对模型来说它并不需要一个 IDE，做 AI 编程最重要的是给模型提供一个高效的环境，而这个环境有命令行已经够了。而且这里有个逻辑是，如果你说你在做个新东西，但是又对过往路径有强依赖的话，可能要重新思考这个方向你到底该不该做，因为大概率竞争不过市场上的领先者。&lt;/li&gt;
&lt;li&gt;未来趋势：这个我觉得更加重要。VS Code 这样的编辑器是针对人使用设计的，最大的需求是给人提供便利；但是命令行工具很容易自动化，也很容易和别的工具协同工作。当模型和工具越来越成熟之后，人的参与度会越来越低，而这件事情实际上已经发生了。很多基于 IM、文档、网页等等提供的 AI 编程的工具已经开始被大家使用，我相信这背后肯定是这些 CLI 工具作为支撑。这些工具带来的最大的变化是多会话的管理，因为人同时只能处理一个问题，但是你可以开很多个 CLI 同时让模型做事。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;总结来说，2025 年大部分人应该都能通过大模型加速写代码，我认为 2026 年开发者需要找到自己的工具，&lt;strong&gt;把工作并发起来&lt;/strong&gt;，而且这可能不能单纯靠等待市场上产品成熟，也需要自己做好准备，比如开发规范、测试规范、发布流程等都配套跟上。&lt;/p&gt;</description><content>&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;这就意味着宇宙普适的物理规律不存在，那物理学……也不存在了。&amp;ldquo;汪淼从窗外收回目光说。&lt;/p&gt;
&lt;div style="text-align: center;"&gt;——《三体》&lt;/div&gt;
&lt;/blockquote&gt;
&lt;p&gt;多年以后，面对黑色屏幕上闪动的白色光标，我们将会回想起，那个 AI 编程横空出世的 2025。&lt;/p&gt;
&lt;p&gt;2025 年是 AI 编程的大年，从 &lt;a href="https://openrouter.ai/state-of-ai"&gt;OpenRouter 的年终统计&lt;/a&gt; 来看，AI 编程用的 Token 占了整个 Token 使用量的一半左右。
&lt;img src="https://www.wangfenjin.com/img/2025-programming-dominant.png" alt="programing-token"&gt;&lt;/p&gt;
&lt;p&gt;作为“资深程序员”，我今年也贡献了很多的 token 使用量，深切感受到 AI 编程对于软件开发领域带来了不可逆的影响。至少从技术上，我认为这些影响绝大部分都是正向的。&lt;/p&gt;
&lt;p&gt;之前写程序一定程度上是“体力劳动”，不管有多宏大的想法，代码总要一行一行写。 AI 编程让程序员向脑力劳动又前进了一步，我们需要更激进地学习新知识、使用新工具，从这个角度来说，我认为 AI 编程是提高了对计算机从业者的要求，而不是某些媒体鼓吹的“技术平权”。&lt;/p&gt;
&lt;p&gt;这篇文章总结下我对 AI 编程现状的观察，以及对后续发展的预期。&lt;/p&gt;
&lt;h2 id="开发工具"&gt;开发工具&lt;/h2&gt;
&lt;p&gt;首先从开发工具讲起。明星创业公司 Cursor 因为 VS Code + Claude 一夜走红，算是彻底带火了 AI 编程。但是我想讲的不是这个，而是为什么突然所有的公司都开始做 CLI 工具，比如 Claude Code, Codex, Gemini CLI 等等？&lt;/p&gt;
&lt;p&gt;我认为这里有三个方面的原因：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;投入太大： IDE 或者可视化编辑器这一块，VS Code已经是绝对的霸主，这可以说是一个已经解决的问题。如果新产品还从这个方向切入，势必要投入不少精力追赶，这在这个快速迭代的时代是不可想象的。虽然市面上还是有不少基于 VS Code fork 出来的编辑器，但是很难像 Cursor 刚出来的时候给人的惊艳感，包括 Amazon 的 Kiro 刚出来的 Spec 模式确实很不错，但是这些模式很容易被同行快速学习和复制，后续也渐渐陷入平庸。&lt;/li&gt;
&lt;li&gt;产出太小：其实如果投入大但是又是必须的话，那么还是避免不了要去做。但是对模型来说它并不需要一个 IDE，做 AI 编程最重要的是给模型提供一个高效的环境，而这个环境有命令行已经够了。而且这里有个逻辑是，如果你说你在做个新东西，但是又对过往路径有强依赖的话，可能要重新思考这个方向你到底该不该做，因为大概率竞争不过市场上的领先者。&lt;/li&gt;
&lt;li&gt;未来趋势：这个我觉得更加重要。VS Code 这样的编辑器是针对人使用设计的，最大的需求是给人提供便利；但是命令行工具很容易自动化，也很容易和别的工具协同工作。当模型和工具越来越成熟之后，人的参与度会越来越低，而这件事情实际上已经发生了。很多基于 IM、文档、网页等等提供的 AI 编程的工具已经开始被大家使用，我相信这背后肯定是这些 CLI 工具作为支撑。这些工具带来的最大的变化是多会话的管理，因为人同时只能处理一个问题，但是你可以开很多个 CLI 同时让模型做事。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;总结来说，2025 年大部分人应该都能通过大模型加速写代码，我认为 2026 年开发者需要找到自己的工具，&lt;strong&gt;把工作并发起来&lt;/strong&gt;，而且这可能不能单纯靠等待市场上产品成熟，也需要自己做好准备，比如开发规范、测试规范、发布流程等都配套跟上。&lt;/p&gt;
&lt;h2 id="编程语言"&gt;编程语言&lt;/h2&gt;
&lt;p&gt;其实目前各大编程语言都已经找到了自己的位置：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JS/TS 除了做 web 应用，几乎还能做一切，不得不感叹前端圈真的太卷了！代表作 VS Code&lt;/li&gt;
&lt;li&gt;Python 是数据处理和机器学习领域绝对的王，代表作 PyTorch&lt;/li&gt;
&lt;li&gt;Golang 在各种分布式系统中发光发热，代表作 k8s&lt;/li&gt;
&lt;li&gt;C/C++/Java 等“传统”的编程语言依旧在企业中大行其道，很难被替代&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但是！这个问题我还是实在忍不住要讨论，因为我已经彻底锈化了！这里我不想再列举 Rust 开发的系统或者应用，甚至我也不想提 Rust 已经进入 Linux 内核领域，我觉得大家可以思考一下 AI 编程时代我们需要什么样的编程语言？&lt;/p&gt;
&lt;p&gt;一种论调是，AI 编程时代语言已经不重要了，因为反正都是 AI 写。但是如果都是 AI 写，为什么不用一种，往上能编译成 WASM 给 JS 用，中间可以通过 pyo3 把性能敏感部分用 Rust 写成扩展，让 Python 关键路径飞起来，往下还能写数据库和 Linux 内核的语言？&lt;/p&gt;
&lt;p&gt;AI 编程在很大程度上缓解了 Rust 之前广受诟病的那些问题：陡峭的学习曲线、和编译器的拉扯等。而且用 AI 写过代码的都知道，Rust 编译器这么强的检查对于功能保障太重要了！
一定还会有新的编程语言出来，但是我认为 Rust 就是现在能看到的 AI 编程最佳理想型。&lt;/p&gt;
&lt;h2 id="魔法库"&gt;魔法库&lt;/h2&gt;
&lt;p&gt;魔法库在我这里是个贬义词，魔法库 == 过度设计。最典型的代表，Java 世界的 Spring，各种语言的 ORM 框架，以及炒得火热的 LangChain，其他的项目欢迎自己对号入座。&lt;/p&gt;
&lt;p&gt;当然我不是一味否定他们的价值，流行一定有他的原因，有他的场景和受众，而且 AI 编程之前程序员是“体力劳动”的时代，有个代码库帮我们做点事情稍微减少我们的工作量还是有一定帮助的。在很多常见业务场景里，AI 直接帮你现搓一套简单实现往往已经够用，而且代码简洁清晰，想改就改，不必为魔法库的各种配置和绕路方案买单。&lt;/p&gt;
&lt;p&gt;我认为在 AI 编程的时代，给公共库的开发者和使用者都提出了更高的要求：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对于开发者：这个库的功能足够单一吗？接口是标准的吗？对使用者系统的侵入性大吗？需要复杂的配置和学习成本吗？里面的魔法多吗？理论上来说越标准、越简洁、确定性越高的公共库，越适合被 AI 使用（当然也包括人，但是人有时候容忍度很高）。作为公共库的开发者，需要以各种语言的标准库的标准来要求自己。&lt;/li&gt;
&lt;li&gt;对于使用者：也就是我们大多数人，需要建立自己的审美和世界观。这个库提供了什么功能？是不是最好的实现方式？有没有其他的不对代码太多侵入的方案？我的场景需不需要这么复杂的库？一定程度上，这并不比开发一个公共库更简单，因为理论上你随便写段代码放出去就可以认为自己做了个公共库，但是从使用者角度理论上你需要做好 Fork 任何公共库改为己用的准备，你能不能接受？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;计算机的世界没有魔法，如果有，那我们应该成为参与者而不是旁观方。&lt;/p&gt;
&lt;h2 id="测试"&gt;测试&lt;/h2&gt;
&lt;p&gt;软件测试是软件开发过程中逃不开的话题，过往就有各种测试相关的技术被提出，各有千秋。在 AI 编程的时代，这件事应该要发生一些改变了。&lt;/p&gt;
&lt;p&gt;最大的调整在于，在绝大多数业务系统里，我认为可以把对单元测试的执念放低，把精力更多迁移到集成测试或接口测试上。这里的原因很简单，因为 AI 实在是太会改代码了，在几分钟就能输出大块大块的代码，如果有一堆单元测试，势必会造成很多测试都失效，而且 AI 自己给自己的代码写的单元测试，很难保证能有什么用。&lt;/p&gt;
&lt;p&gt;接口测试不一样，我们可以把测试当成一个一个的产品功能或者说故事：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;功能稳定性得以保证，不管 AI 怎么发挥，就算它还避免不了一些低级错误，只要基本的故事是通的，不至于出现大的问题&lt;/li&gt;
&lt;li&gt;维护成本较低，对于后端开发来说，除非预期中，我们很少会破坏现有的功能；接口测试一旦写好，不管代码细节怎么改，接口行为不能改变&lt;/li&gt;
&lt;li&gt;降低沟通成本，这个测试文件甚至可以作为前后端联调的文档，通过这个文档后端可以告诉前端该怎么使用这些接口，参数和返回值的预期是什么&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;推荐一个 HTTP 测试工具叫 &lt;a href="https://hurl.dev"&gt;HURL&lt;/a&gt; ，谁用谁知道&lt;/p&gt;
&lt;h2 id="code-review"&gt;Code Review&lt;/h2&gt;
&lt;p&gt;大厂里面，代码 Review 是一个永恒的问题，所有人都希望有人 review 他的代码，所有人都不愿意 review 别人的代码。这里最主要的原因在于，reviewer 作为不是写这份代码的人，很可能不了解需求或者代码库的细节，所以往往只能找找变量命名之类的错误，或者提一些代码组织上可有可无的变动，很难给出实际有效的建议。&lt;/p&gt;
&lt;p&gt;AI 就不一样了，只要你开口，他可以孜孜不倦地帮你 review，并且如果你想，他还甚至直接给你把问题修复了，它不怕麻烦，也不怕承担责任（虽然它也不承担）。体感上，AI reviewer 给提交代码的人感觉不是一个”挑刺者“，而是一个帮我们减少问题的好伙伴。&lt;/p&gt;
&lt;p&gt;目前市面上做得好的 code reviewer 不多，包括很多大厂做的工具。但是这里不得不夸一夸 OpenAI 的 Codex，不管是本地 review，还是和 github 集成的工具，都非常有效，真的能发现各种实际的问题，除了慢没毛病。&lt;/p&gt;
&lt;p&gt;我相信 2026 年 code review 会变得越来越成熟可靠，和开发流程的结合也会越来越紧密，毕竟”资深程序员“都知道提交代码之前自己先 review 一遍。&lt;/p&gt;
&lt;h2 id="单体架构"&gt;单体架构&lt;/h2&gt;
&lt;p&gt;微服务架构过去几年已经一定程度上被祛魅了，随着新一波 AI 公司的兴起，以及 AI 编程的发展，我觉得单体架构会重新变得伟大。这里面主要有这样几个原因：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;从 AI 编程的角度，单体架构能更好地让 AI 了解整个系统的上下文，避免各种微服务像魔法一样封装了各种不确定的行为，让 AI 只能靠猜。当然组织良好的微服务或者接口能一定程度上缓解这个问题，但是这会牵涉到第二点&lt;/li&gt;
&lt;li&gt;从维护的角度，单体架构维护成本较低，只要配置好一套 CI/CD 流程，所有人都可以共享，Devops 的投入相对很少。并且开发的维护成本也比较低，如果是微服务想变个接口，还需要各种上下游协调，长期下去接口会变的臃肿和混乱&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其实说到底微服务更多是团队组织上的需求，因为公司拆出来了各种团队，团队间为了有一个清晰的边界，所以服务也需要定义边界。AI 编程的普及首先会让公司对于团队规模的需求极度缩小，在不需要这么大团队规模的前提下，微服务的必要性就没那么高了。&lt;/p&gt;
&lt;h2 id="可观测性"&gt;可观测性&lt;/h2&gt;
&lt;p&gt;这个方向不知道什么原因，看到的相关新闻不多，但是我觉得是一个很好的方向。跟上面提到的代码 Review 类似，可观测性是系统上线后运维很重要的一部分。&lt;/p&gt;
&lt;p&gt;目前我觉得机会很多：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;过往监控系统最为人诟病的就是误报和漏报，一方面是因为监控规则实在晦涩难写（bosun 谁用谁崩溃），另一方面静态规则实在难以适应线上不断变化的场景，所以很多大厂做的“智能监控”实在很难称得上智能。&lt;/li&gt;
&lt;li&gt;另外接入这些监控系统的门槛也不低，metrics 怎么打点，日志怎么格式化，这些长期需要占据研发很多心力&lt;/li&gt;
&lt;li&gt;最后线上的问题往往是架构迭代方向的重要指导，过去往往是线上出事故了再来复盘，如果有 AI 能长期盯盘，防患于未然，那么 AI 又离“资深程序员”更进了一步
随着这一波 AI 应用的野蛮生长进入稳定期，系统可观测性和服务稳定性一定会进入舞台中央，这一块的创业者们准备接受洗礼吧。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="慢就是快"&gt;慢就是快&lt;/h2&gt;
&lt;p&gt;其实软件开发还涉及到很多其他方面，篇幅太长就写这些吧。最后想写一点，就是关于快与慢。&lt;/p&gt;
&lt;p&gt;大模型跟过往工业革命一样，好像把世界变小了，时间变快了。一个最明显的例子，仅仅是过去几个月发布的模型，效果也不错，过了几个月好像就过时了一样。包括媒体上或者社区上，也不断会有人说一个周末做出了什么应用，一个月发布好几个版本，以及不到一年就数十亿美金卖了。所有的东西看起来都那么快在变化，如果不去学就跟不上了。
作为浩瀚宇宙一个渺小的个体，我们应该如何自处？&lt;/p&gt;
&lt;p&gt;我的建议，不要把自己变成别人的生意！不要以为点进去一条 twitter 看看也没什么，不要总在抖音浪费 15s，不要只看到“美颜”后的光鲜，要去看背后的真相。不管看到什么，先问问自己，我为什么看到这个信息？他为什么发这个信息？这个信息对我的影响是什么？这个信息对他的价值是什么？学会这个真的很简单吗？不学这个真的影响很大吗？&lt;/p&gt;
&lt;p&gt;我想变成一个什么样的人？&lt;/p&gt;
&lt;p&gt;没有什么毫无道理的横空出世。如果你觉得别人随随便便就成功了，那是因为你不了解他人在背后的积累和付出。在这样一个快速发展和变化的时代，我们反而应该慢下来思考、学习和沉淀。&lt;/p&gt;
&lt;p&gt;多年以后，当你再面对黑色屏幕上闪动的白色光标时，你便会想起那个遥远的2026。那时，各种技术和工具层出不穷，人们兴奋地看着每天爆出来的新闻，指指点点。&lt;/p&gt;</content></item><item><title>感恩字节</title><link>https://www.wangfenjin.com/posts/bye-dance/</link><pubDate>Mon, 04 Aug 2025 00:00:00 +0000</pubDate><guid>https://www.wangfenjin.com/posts/bye-dance/</guid><description>&lt;p&gt;如果用一个词来总结我在字节的日子，我想我会用成长。用这个词也很自然吧，毕竟初入职场后最重要的十年待在字节，并且还是一家成长这么快的公司，经常我还会反思自己的成长速度远远没有跟上公司的需要。对于字节我只有感恩，他不仅给了我超预期的工资回报，更重要的是给我有挑战的项目帮助我成长和证明自己，也给了我很多可以联系一辈子的朋友。&lt;/p&gt;
&lt;p&gt;我要感谢我在字节的 leader 们，谢谢你们对我的指导和信任，特别是早期的 leader 容忍了我很多不成熟的做法；对一起合作过的同学，过往如果有沟通方式或者做事方式不对的地方，这里说一声抱歉；对于我之前指导过或者帮助过的同学，你们都有一个美好的未来，而且其中有不少已经做到了比我更重要的岗位。&lt;/p&gt;
&lt;p&gt;字节依旧是一家伟大的公司，依旧会有很好的发展，祝福在字节奋斗的小伙伴们！&lt;/p&gt;</description><content>&lt;p&gt;如果用一个词来总结我在字节的日子，我想我会用成长。用这个词也很自然吧，毕竟初入职场后最重要的十年待在字节，并且还是一家成长这么快的公司，经常我还会反思自己的成长速度远远没有跟上公司的需要。对于字节我只有感恩，他不仅给了我超预期的工资回报，更重要的是给我有挑战的项目帮助我成长和证明自己，也给了我很多可以联系一辈子的朋友。&lt;/p&gt;
&lt;p&gt;我要感谢我在字节的 leader 们，谢谢你们对我的指导和信任，特别是早期的 leader 容忍了我很多不成熟的做法；对一起合作过的同学，过往如果有沟通方式或者做事方式不对的地方，这里说一声抱歉；对于我之前指导过或者帮助过的同学，你们都有一个美好的未来，而且其中有不少已经做到了比我更重要的岗位。&lt;/p&gt;
&lt;p&gt;字节依旧是一家伟大的公司，依旧会有很好的发展，祝福在字节奋斗的小伙伴们！&lt;/p&gt;</content></item><item><title>duckdb-rs will be the offical DuckDB rust client</title><link>https://www.wangfenjin.com/posts/duckdb-rs-moving-forward-en/</link><pubDate>Wed, 26 Jul 2023 00:00:00 +0000</pubDate><guid>https://www.wangfenjin.com/posts/duckdb-rs-moving-forward-en/</guid><description>&lt;h2 id="background"&gt;Background&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://duckdb.org/"&gt;DuckDB&lt;/a&gt; is an in-process SQL OLAP database management system implemented in C++. When it was first open-sourced, it was positioned as a columnar database comparable to SQLite, providing the same ease of use. With just a header file and a cpp file, it could be easily embeded in any program, even offering a SQLite-compatible interface, which caught the attention of many people &lt;a href="https://news.ycombinator.com/item?id=24531085"&gt;source&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I started paying attention to DuckDB a long time ago and began writing the first line of &lt;a href="https://github.com/wangfenjin/duckdb-rs"&gt;duckdb-rs&lt;/a&gt; code on June 7, 2021. About a month later, I wrote a &lt;a href="https://www.wangfenjin.com/posts/duckdb-rs/"&gt;blog post&lt;/a&gt; introducing the process of building this library, marking the completion of the initial version. Over the past two years, I have released approximately &lt;a href="https://crates.io/crates/duckdb"&gt;19 versions&lt;/a&gt;, get more than 200 stars in GitHub.&lt;/p&gt;</description><content>&lt;h2 id="background"&gt;Background&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://duckdb.org/"&gt;DuckDB&lt;/a&gt; is an in-process SQL OLAP database management system implemented in C++. When it was first open-sourced, it was positioned as a columnar database comparable to SQLite, providing the same ease of use. With just a header file and a cpp file, it could be easily embeded in any program, even offering a SQLite-compatible interface, which caught the attention of many people &lt;a href="https://news.ycombinator.com/item?id=24531085"&gt;source&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I started paying attention to DuckDB a long time ago and began writing the first line of &lt;a href="https://github.com/wangfenjin/duckdb-rs"&gt;duckdb-rs&lt;/a&gt; code on June 7, 2021. About a month later, I wrote a &lt;a href="https://www.wangfenjin.com/posts/duckdb-rs/"&gt;blog post&lt;/a&gt; introducing the process of building this library, marking the completion of the initial version. Over the past two years, I have released approximately &lt;a href="https://crates.io/crates/duckdb"&gt;19 versions&lt;/a&gt;, get more than 200 stars in GitHub.&lt;/p&gt;
&lt;p&gt;In the past year, there have been many requirements and ideas for optimization, but I found myself lacking the time, and the number of received issues has been increasing. As a result, I will transfer this library to &lt;a href="https://github.com/duckdb"&gt;DuckDB&lt;/a&gt; offical organization, believing that make duckdb-rs an official client will lead to further progress and bigger success. Also I&amp;rsquo;d like to take this chance to thanks Mark and Hannes for building DuckDB and agree to accept duckdb-rs as the official rust client.&lt;/p&gt;
&lt;p&gt;This blog post summarizes the main tasks I have undertaken during my maintenance period and points out areas that I believe can be improved.&lt;/p&gt;
&lt;h2 id="key-decisions"&gt;Key Decisions&lt;/h2&gt;
&lt;p&gt;This library is the Rust client of DuckDB, so the primary audience interested in this library are users who appreciate DuckDB and use the Rust tech stack. Below are some key factors that I consider contributed to the &amp;ldquo;success&amp;rdquo; of this library:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Initial version based on &lt;a href="https://github.com/rusqlite/rusqlite"&gt;rusqlite&lt;/a&gt; development. As a Rust beginner myself, I had previously only worked on one Rust project, and this was my second time using Rust. Based on rusqlite, a mature repository, allowed me to quickly obtain a usable version. Additionally, the code structure and API design had already been validated, reducing the likelihood of taking wrong turns. Moreover, the overall code quality could be reasonably assured.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Data exchange based on the &lt;a href="https://github.com/apache/arrow-rs"&gt;arrow&lt;/a&gt; format. Arrow is now considered the columnar storage data exchange standard and is used in many open-source projects. DuckDB has good support for arrow as well. Although DuckDB has its native C interface, using the arrow format for data exchange allows relatively stable interactions between Rust and the C API. This approach ensures that we won&amp;rsquo;t need to make frequent changes due to DuckDB iterations, thus reducing maintenance efforts and minimizing the impact of interface changes on users.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Robust CI process. I believe that all open-source projects should strive for this. With a CI process, we can ensure that the code merged into the master branch was error-free. The CI process also included memory leak detection, avoiding potential safety issues introduced by the FFI. The release process was automated as well, with crates being automatically published by tagging.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="notable-mrs"&gt;Notable MRs&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve selected a few MRs that I consider significant and that weren&amp;rsquo;t contributed by me:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/wangfenjin/duckdb-rs/pull/1"&gt;Add github workflow&lt;/a&gt;: This was the first MR to add CI checks, which was very meaningful as I used to push directly to master before CI was in place.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/wangfenjin/duckdb-rs/pull/32"&gt;Add r2d2 connection pool&lt;/a&gt;: This MR added a connection pool, improving the library&amp;rsquo;s performance.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/wangfenjin/duckdb-rs/pull/127"&gt;Rework bundled compilation to support included extensions&lt;/a&gt;: This was the largest MR and allowed the library to support extensions, reworking the logic of bundling DuckDB source code to include various extensions without requiring additional installations.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/wangfenjin/duckdb-rs/pull/169"&gt;Feat: Develop query polars&lt;/a&gt;: This MR added support for converting query results into polars data structures. &lt;a href="https://github.com/pola-rs/polars"&gt;polars&lt;/a&gt; is a popular data processing tool written in Rust. This feature bridged the gap between DuckDB and polars.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Apart from the daily maintenance, I didn&amp;rsquo;t contribute to the development of major features significantly. &lt;a href="https://github.com/wangfenjin/duckdb-rs/pull/138"&gt;Support table function&lt;/a&gt; can be considered one, and I believe writing extensions in Rust is simpler and safer compared to C/C++.&lt;/p&gt;
&lt;h2 id="outstanding-issues"&gt;Outstanding Issues&lt;/h2&gt;
&lt;p&gt;Due to limited time and resources, there are still some unresolved issues in this library:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Better documentation. Writing documentation has always been a headache for me since English is not my strong suit. While this library inherited some documentation from rusqlite, it lacks ongoing maintenance, especially regarding documentation specific to DuckDB features. Good documentation and blog posts are key to the success of an open-source project.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Support for more data types. There are two categories of data types: those mapped to Rust data types for results, which are not a high priority since arrow-rs already provides comprehensive data types for users working with arrow data. The other category is query parameters, where we need to support a wider range of data types for better user convenience. Currently, we only support some basic query data types.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Improved support for data insertion. Columnar databases require the ability to insert data in batches, such as using DuckDB&amp;rsquo;s built-in append interface or supporting insertion of arrow data.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Compilation process optimization. As DuckDB&amp;rsquo;s features expand, the compilation process for this library has become slower and resource-consuming, resulting in larger build artifacts.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Support for specific DuckDB interfaces, such as streaming query or relation API. These have been raised as issues by some users.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To achieve the same level as DuckDB in terms of documentation and interfaces, there is still much work to be done.&lt;/p&gt;
&lt;h2 id="future-plans"&gt;Future Plans&lt;/h2&gt;
&lt;p&gt;With the publication of this article, it means I am no longer the maintainer of duckdb-rs. However, this does not mean that I will no longer contribute code to duckdb-rs. I will continue to follow DuckDB and duckdb-rs and contribute code in my spare time.&lt;/p&gt;
&lt;p&gt;If I have time, I may also work on other projects based on duckdb-rs, such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Creating a Rust extension for DuckDB to become a vector database&lt;/li&gt;
&lt;li&gt;Or building a storage server based on duckdb-rs primarily using the &lt;a href="https://github.com/apache/arrow-rs/tree/master/arrow-flight"&gt;arrow-flight&lt;/a&gt; protocol. If I have even more time, I might add support for Raft to enable distribution. I&amp;rsquo;m not sure how useful these projects would be, but they sound like fun.&lt;/li&gt;
&lt;li&gt;Another possibility is creating a distributed data processing tool, using DuckDB as intermediate data storage or for computation acceleration.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;p&gt;Translated from &lt;a href="https://www.wangfenjin.com/posts/duckdb-rs-moving-forward/"&gt;CN Version&lt;/a&gt; using ChatGPT and polished manually.&lt;/p&gt;
&lt;/blockquote&gt;</content></item><item><title>duckdb-rs 即将成为 DuckDB 官方 rust 客户端</title><link>https://www.wangfenjin.com/posts/duckdb-rs-moving-forward/</link><pubDate>Wed, 26 Jul 2023 00:00:00 +0000</pubDate><guid>https://www.wangfenjin.com/posts/duckdb-rs-moving-forward/</guid><description>&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://duckdb.org/"&gt;DuckDB&lt;/a&gt; 是一个 C++ 编写的单机版嵌入式分析型数据库。它刚开源的时候是对标 SQLite 的列存数据库，并提供与 SQLite 一样的易用性，编译成一个头文件和一个 cpp 文件就可以在程序中使用，甚至提供与 SQLite 兼容的接口，因此受到了很多人的&lt;a href="https://news.ycombinator.com/item?id=24531085"&gt;关注&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;我很久之前就开始关注 DuckDB，并在 2021-06-07 开始写第一行 &lt;a href="https://github.com/wangfenjin/duckdb-rs"&gt;duckdb-rs&lt;/a&gt; 的代码，在 一个多月后写了一篇&lt;a href="https://www.wangfenjin.com/posts/duckdb-rs/"&gt;博客&lt;/a&gt;介绍了构建这个库的过程，算是实现了第一个版本。到今天差不多2年的时间，前后发布了&lt;a href="https://crates.io/crates/duckdb"&gt;19个版本&lt;/a&gt;，收获了 200 多个star。&lt;/p&gt;
&lt;p&gt;最近一年其实还有很多需求和想法去做优化，但是发现自己并没有那么多时间，收到的 issue 也越来越多。经过沟通，我会把这个库转给 &lt;a href="https://github.com/duckdb"&gt;DuckDB&lt;/a&gt; 官方来维护，相信 duckdb-rs 一定会发展得越来越好。同时也非常感谢 Mark 和 Hannes 愿意接手这个仓库并把它作为官方的 rust 客户端。&lt;/p&gt;
&lt;p&gt;这篇博客总结下我维护的这段时间主要做的事，以及我认为可以改善的点，算是对过去的总结和对未来的憧憬。&lt;/p&gt;
&lt;h2 id="关键决策"&gt;关键决策&lt;/h2&gt;
&lt;p&gt;这个库是 duckdb 的 rust 客户端，所以关注这个库的群体首先是认可 duckdb 的用户，其次因为他们是 rust 技术栈。下面我列举一些我认为是让这个库“成功”的一些关键点。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;初始版本基于 &lt;a href="https://github.com/rusqlite/rusqlite"&gt;rusqlite&lt;/a&gt; 开发。因为我也是一个 rust 初学者，之前只拿 rust 做过一个项目，这是第二次使用 rust。基于 rustqlite 这样一个成熟的仓库做改造，能让我很快得到一个可用的版本，快速建立信心；另外整个程序的组织，API 的设计都已经经过了验证，不容易走弯路；整体的代码质量也能有基本保障。&lt;/li&gt;
&lt;li&gt;基于 &lt;a href="https://github.com/apache/arrow-rs"&gt;arrow&lt;/a&gt; 格式来交换数据。arrow 现在基本上算是列存储的数据交换标准，在很多开源项目中都有使用，duckdb 对 arrow 的支持也比较完善。虽然 duckdb 有自己的原生 C 接口，但是基于 arrow 格式来做数据交换，能让 rust 和 c-api 调用相对稳定，不会因为 duckdb 迭代导致 C 接口的变更，我们也需要一直变更，一定程度上减轻了维护的工作量，也减少了接口变更对用户的影响。&lt;/li&gt;
&lt;li&gt;完善的 CI 流程，我认为所有的开源项目都应该要做到这一点。因为继承自 rusqlite，这个库从一开始就有 CI 流程，能保证合并到 master 的代码是没问题的，并且 CI 里面还有关于内存泄漏的检测，避免了 ffi 带了的可能不安全的问题。发布过程也是自动化的，只要打个 tag 就自动发布到 crate。CI 的机制保障了任何感兴趣的人都可以提交 MR 并得到检验，也保证自己如果长时间不维护了不至于都不知道从哪里开始改。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="几个-mr"&gt;几个 MR&lt;/h2&gt;
&lt;p&gt;下面我挑选几个我认为比较关键的，并且不是我贡献的 MR：&lt;/p&gt;</description><content>&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://duckdb.org/"&gt;DuckDB&lt;/a&gt; 是一个 C++ 编写的单机版嵌入式分析型数据库。它刚开源的时候是对标 SQLite 的列存数据库，并提供与 SQLite 一样的易用性，编译成一个头文件和一个 cpp 文件就可以在程序中使用，甚至提供与 SQLite 兼容的接口，因此受到了很多人的&lt;a href="https://news.ycombinator.com/item?id=24531085"&gt;关注&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;我很久之前就开始关注 DuckDB，并在 2021-06-07 开始写第一行 &lt;a href="https://github.com/wangfenjin/duckdb-rs"&gt;duckdb-rs&lt;/a&gt; 的代码，在 一个多月后写了一篇&lt;a href="https://www.wangfenjin.com/posts/duckdb-rs/"&gt;博客&lt;/a&gt;介绍了构建这个库的过程，算是实现了第一个版本。到今天差不多2年的时间，前后发布了&lt;a href="https://crates.io/crates/duckdb"&gt;19个版本&lt;/a&gt;，收获了 200 多个star。&lt;/p&gt;
&lt;p&gt;最近一年其实还有很多需求和想法去做优化，但是发现自己并没有那么多时间，收到的 issue 也越来越多。经过沟通，我会把这个库转给 &lt;a href="https://github.com/duckdb"&gt;DuckDB&lt;/a&gt; 官方来维护，相信 duckdb-rs 一定会发展得越来越好。同时也非常感谢 Mark 和 Hannes 愿意接手这个仓库并把它作为官方的 rust 客户端。&lt;/p&gt;
&lt;p&gt;这篇博客总结下我维护的这段时间主要做的事，以及我认为可以改善的点，算是对过去的总结和对未来的憧憬。&lt;/p&gt;
&lt;h2 id="关键决策"&gt;关键决策&lt;/h2&gt;
&lt;p&gt;这个库是 duckdb 的 rust 客户端，所以关注这个库的群体首先是认可 duckdb 的用户，其次因为他们是 rust 技术栈。下面我列举一些我认为是让这个库“成功”的一些关键点。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;初始版本基于 &lt;a href="https://github.com/rusqlite/rusqlite"&gt;rusqlite&lt;/a&gt; 开发。因为我也是一个 rust 初学者，之前只拿 rust 做过一个项目，这是第二次使用 rust。基于 rustqlite 这样一个成熟的仓库做改造，能让我很快得到一个可用的版本，快速建立信心；另外整个程序的组织，API 的设计都已经经过了验证，不容易走弯路；整体的代码质量也能有基本保障。&lt;/li&gt;
&lt;li&gt;基于 &lt;a href="https://github.com/apache/arrow-rs"&gt;arrow&lt;/a&gt; 格式来交换数据。arrow 现在基本上算是列存储的数据交换标准，在很多开源项目中都有使用，duckdb 对 arrow 的支持也比较完善。虽然 duckdb 有自己的原生 C 接口，但是基于 arrow 格式来做数据交换，能让 rust 和 c-api 调用相对稳定，不会因为 duckdb 迭代导致 C 接口的变更，我们也需要一直变更，一定程度上减轻了维护的工作量，也减少了接口变更对用户的影响。&lt;/li&gt;
&lt;li&gt;完善的 CI 流程，我认为所有的开源项目都应该要做到这一点。因为继承自 rusqlite，这个库从一开始就有 CI 流程，能保证合并到 master 的代码是没问题的，并且 CI 里面还有关于内存泄漏的检测，避免了 ffi 带了的可能不安全的问题。发布过程也是自动化的，只要打个 tag 就自动发布到 crate。CI 的机制保障了任何感兴趣的人都可以提交 MR 并得到检验，也保证自己如果长时间不维护了不至于都不知道从哪里开始改。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="几个-mr"&gt;几个 MR&lt;/h2&gt;
&lt;p&gt;下面我挑选几个我认为比较关键的，并且不是我贡献的 MR：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://github.com/wangfenjin/duckdb-rs/pull/1"&gt;Add github workflow&lt;/a&gt;，之前我都是直接 push master，这是第一个 MR 添加 CI 检测，非常有意义！&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/wangfenjin/duckdb-rs/pull/32"&gt;add r2d2 connection pool&lt;/a&gt;，添加连接池。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/wangfenjin/duckdb-rs/pull/127"&gt;Rework bundled compilation to support included extensions&lt;/a&gt;，收到最大的一个 MR，为了支持 extension，重做了 bundle duckdb 源码的逻辑，让这个仓库也能打包进去各种扩展而不用额外安装。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/wangfenjin/duckdb-rs/pull/169"&gt;Feat: Develop query polars&lt;/a&gt;，支持把 query 的结果转成 polars 的数据结构，&lt;a href="https://github.com/pola-rs/polars"&gt;polars&lt;/a&gt; 是目前 rust 写的一个非常流行的数据处理工具，这个功能打通了 duckdb 和 polars。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;我自己除了日常维护之外，实际上大的功能开发比较少，&lt;a href="https://github.com/wangfenjin/duckdb-rs/pull/138"&gt;Support table function&lt;/a&gt; 算是一个，并且我认为基于 rust 写扩展远比基于 c/c++ 来写更简单，更安全！&lt;/p&gt;
&lt;h2 id="遗留问题"&gt;遗留问题&lt;/h2&gt;
&lt;p&gt;因为精力有限，这个库还有一些问题需要解决：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;更好的文档。因为我的英语也是半路出家，所以写文档一直是想起来就头疼的问题。这个库因为是基于 rusqlite，所以继承了一部分文档，所以基本质量还在，但是后续缺少维护，特别是针对 duckdb 特性的一些文档资料比较少。好的文档和博客也是开源项目成功的关键。&lt;/li&gt;
&lt;li&gt;支持更多数据类型。这里的数据类型分两类，一类是对于结果，映射到 rust 的数据类型，这部分的需求倒是不高优，特别是用户如果是使用的 arrow 数据的话，arrow-rs 本身有完整的数据类型；另一类是查询参数，这部分需要支持更多的数据类型绑定，方便用户使用。目前我们只支持了一些基础的数据类型。&lt;/li&gt;
&lt;li&gt;更完善的数据插入支持。列存数据库需要有批量插入数据的能力，比如 duckdb 自带的 append 接口，或者支持插入 arrow 的数据等，目前这一块支持得不太好。&lt;/li&gt;
&lt;li&gt;编译过程优化。随着 duckdb 功能丰富，这个库的编译也越来越慢，对资源的消耗也越来越多，编译的产物也越来越大。&lt;/li&gt;
&lt;li&gt;一些 duckdb 或者列存特定的接口支持，比如 streaming query 或者 relation api，这些都有人提过 issue。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;从文档和接口上，要达到和 duckdb 一样的水准，还有不少工作要做。&lt;/p&gt;
&lt;h2 id="后续计划"&gt;后续计划&lt;/h2&gt;
&lt;p&gt;这篇文章发布的时候，意味着我不再是 duckdb-rs 的维护者。但是这不代表着后续我不再给 duckdb-rs 贡献代码，我还是会继续关注 duckdb 和 duckdb-rs，并且在闲暇的时候贡献一些代码。&lt;/p&gt;
&lt;p&gt;如果有时间还可以基于 duckdb-rs 做一些其他的项目，比如用 rust 给 duckdb 做一个向量数据库的扩展，或者基于 duckdb-rs 搭建一个存储的 server，主要是基于 &lt;a href="https://github.com/apache/arrow-rs/tree/master/arrow-flight"&gt;arrow-flight&lt;/a&gt; 协议，如果再有时间还可以加上 raft 支持分布式。不知道有什么用，但是感觉是个很好玩的项目。也可以考虑做一个分布式数据处理的工具，用 duckdb 做中间数据的存储或者计算加速等。&lt;/p&gt;</content></item><item><title>基于 apache-arrow 的 duckdb rust 客户端</title><link>https://www.wangfenjin.com/posts/duckdb-rs/</link><pubDate>Tue, 27 Jul 2021 00:00:00 +0000</pubDate><guid>https://www.wangfenjin.com/posts/duckdb-rs/</guid><description>&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://duckdb.org/"&gt;duckdb&lt;/a&gt; 是一个 C++ 编写的单机版嵌入式分析型数据库。它刚开源的时候是对标 SQLite 的列存数据库，并提供与 SQLite 一样的易用性，编译成一个头文件和一个 cpp 文件就可以在程序中使用，甚至提供与 SQLite 兼容的接口，因此受到了很多人的&lt;a href="https://news.ycombinator.com/item?id=24531085"&gt;关注&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;本文介绍笔者近期开发的 duckdb-rs 库，让大家可以很方便地在 rust 代码库中使用 duckdb 的功能。&lt;/p&gt;
&lt;h2 id="libduckdb-sys"&gt;libduckdb-sys&lt;/h2&gt;
&lt;p&gt;了解过 rust 的同学可能知道，rust 提供了 &lt;a href="https://doc.rust-lang.org/nomicon/ffi.html"&gt;ffi&lt;/a&gt; 的方式与其他语言互通。因为 duckdb 本身是 C++ 编写的，想要在 rust 里面使用 duckdb，就需要考虑 ffi 的问题。而基于 ffi 对其他语言程序封装的基础库，一般会被命名为 libxxx-sys，这也就是 libduckdb-sys 的由来。&lt;/p&gt;
&lt;p&gt;为了方便大家使用，duckdb 提供了 C++ 原生接口，C 接口，以及与 SQLite3 兼容的 C 接口。我在做 libduckdb-sys 的时候对这三种接口都尝试过，相关的讨论可以参见 &lt;a href="https://github.com/duckdb/duckdb/issues/949"&gt;Rust Support&lt;/a&gt;，我这里介绍一下当时的情况。&lt;/p&gt;
&lt;h3 id="基于-sqlite3-接口"&gt;基于 SQLite3 接口&lt;/h3&gt;
&lt;p&gt;最开始我使用的是 SQLite3 的接口，原因主要有三个：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;我对 SQLite 比较熟悉，想必用起来会比较方便；&lt;/li&gt;
&lt;li&gt;觉得 SQLite 的接口被广泛使用，接口比较稳定，以后不至于大改；&lt;/li&gt;
&lt;li&gt;也许是最重要的一点，市面上已经有 SQLite 的 rust 封装&lt;a href="https://github.com/rusqlite/rusqlite"&gt;rusqlite&lt;/a&gt;，基于 SQLite 的接口应该能最大程度复用 rusqlite 的代码。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;尝试之后确实发现很快能把程序跑起来，基本的功能也能使用。但是随着进一步的深入以及对 duckdb 更多的了解，发现了一些弊端：&lt;/p&gt;</description><content>&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://duckdb.org/"&gt;duckdb&lt;/a&gt; 是一个 C++ 编写的单机版嵌入式分析型数据库。它刚开源的时候是对标 SQLite 的列存数据库，并提供与 SQLite 一样的易用性，编译成一个头文件和一个 cpp 文件就可以在程序中使用，甚至提供与 SQLite 兼容的接口，因此受到了很多人的&lt;a href="https://news.ycombinator.com/item?id=24531085"&gt;关注&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;本文介绍笔者近期开发的 duckdb-rs 库，让大家可以很方便地在 rust 代码库中使用 duckdb 的功能。&lt;/p&gt;
&lt;h2 id="libduckdb-sys"&gt;libduckdb-sys&lt;/h2&gt;
&lt;p&gt;了解过 rust 的同学可能知道，rust 提供了 &lt;a href="https://doc.rust-lang.org/nomicon/ffi.html"&gt;ffi&lt;/a&gt; 的方式与其他语言互通。因为 duckdb 本身是 C++ 编写的，想要在 rust 里面使用 duckdb，就需要考虑 ffi 的问题。而基于 ffi 对其他语言程序封装的基础库，一般会被命名为 libxxx-sys，这也就是 libduckdb-sys 的由来。&lt;/p&gt;
&lt;p&gt;为了方便大家使用，duckdb 提供了 C++ 原生接口，C 接口，以及与 SQLite3 兼容的 C 接口。我在做 libduckdb-sys 的时候对这三种接口都尝试过，相关的讨论可以参见 &lt;a href="https://github.com/duckdb/duckdb/issues/949"&gt;Rust Support&lt;/a&gt;，我这里介绍一下当时的情况。&lt;/p&gt;
&lt;h3 id="基于-sqlite3-接口"&gt;基于 SQLite3 接口&lt;/h3&gt;
&lt;p&gt;最开始我使用的是 SQLite3 的接口，原因主要有三个：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;我对 SQLite 比较熟悉，想必用起来会比较方便；&lt;/li&gt;
&lt;li&gt;觉得 SQLite 的接口被广泛使用，接口比较稳定，以后不至于大改；&lt;/li&gt;
&lt;li&gt;也许是最重要的一点，市面上已经有 SQLite 的 rust 封装&lt;a href="https://github.com/rusqlite/rusqlite"&gt;rusqlite&lt;/a&gt;，基于 SQLite 的接口应该能最大程度复用 rusqlite 的代码。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;尝试之后确实发现很快能把程序跑起来，基本的功能也能使用。但是随着进一步的深入以及对 duckdb 更多的了解，发现了一些弊端：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;虽说 duckdb 是想最大程度兼容 SQLite，但是毕竟一个是行存一个是列存，有区别在所难免，接口肯定也没办法做到 100% 兼容；&lt;/li&gt;
&lt;li&gt;有一个区别需要特别提出来，SQLite 是&lt;a href="https://www.sqlite.org/datatype3.html"&gt;动态数据类型&lt;/a&gt;，而 duckdb 是静态类型，也就是说在 SQLite 中你可以认为所有的数据都是存成 Text，在读取的时候根据 schema 来解析数据；而 duckdb 是会根据数据类型来存储数据，并且根据列存的特性做一些存储优化。有了这个区别之后，如果我们使用 SQLite 的接口的话，会做一些不必要的数据格式转换，性能有损，程序也不直观。&lt;/li&gt;
&lt;li&gt;duckdb 可以被编译成一个 so 使用，如果想使用 SQLite 的接口，需要再编译一个 sqlite3_api_wrapper 出来，两个库合作才能使用 SQLite 的接口，这给程序分发引入了额外的负担；另外目前 duckdb 在 release 的时候没有自带 sqlite3_api_wrapper，需要用户自己去编译，使用上又多了一些不便。&lt;/li&gt;
&lt;li&gt;由于上面的封装的问题，数据类型的问题，以及通过 SQLite 接口查询 duckdb 的数据时候，结果集会被复制一遍，资源占用必定上升。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;基于上面一些原因，我最终放弃了基于 SQLite 接口来开发，转而尝试使用原生的 C++ 或者 C 接口。&lt;/p&gt;
&lt;h3 id="基于-c-接口"&gt;基于 C++ 接口&lt;/h3&gt;
&lt;p&gt;既然为了性能和接口丰富性，使用 C++ 接口当然是首选，毕竟 duckdb 本身主要都是拿 C++ 开发的，duckdb 的 &lt;a href="https://github.com/duckdb/duckdb/tree/master/tools/pythonpkg"&gt;python 封装&lt;/a&gt; 也是拿 C++ 接口来做的。&lt;/p&gt;
&lt;p&gt;市面上也有方便 rust 与 C++ 交互的一些代码库，比如 &lt;a href="https://github.com/dtolnay/cxx"&gt;cxx&lt;/a&gt; 和 &lt;a href="https://github.com/google/autocxx"&gt;autocxx&lt;/a&gt;。其中 autocxx 入手门槛低使用上更简单，而 cxx 的可定制性更强，功能更丰富。在尝试了几次之后发现了一些问题，主要还是 rust ffi 只能支持部分的 C++ 语法，大部分情况下可能是够用的，但是对于 duckdb 这样比较大型的数据库代码，还是有很多不支持的地方。除非自己再基于现有的 C++ 接口封装一份支持 cxx 的版本，否则就算这一次编译过了，也很难保证以后 duckdb 的作者以后不会引入其他的特性导致不能兼容。&lt;/p&gt;
&lt;p&gt;而 rust 基于 C 语言的 ffi 是原生支持的，所以最终还是下定决心基于 C 接口来开发。&lt;/p&gt;
&lt;h3 id="基于-c-接口-1"&gt;基于 C 接口&lt;/h3&gt;
&lt;p&gt;因为有 rusqlite 作为参考，所以很快实现了基于 C 接口的版本。简单来说，主要是通过 &lt;a href="https://github.com/eqrion/cbindgen"&gt;cbindgen&lt;/a&gt;、&lt;a href="https://doc.rust-lang.org/cargo/reference/build-scripts.html"&gt;build.rs&lt;/a&gt; 和 rust 的 &lt;a href="https://doc.rust-lang.org/cargo/reference/features.html"&gt;features&lt;/a&gt; 功能来实现。其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;cbindgen 用于生成基于 C 接口的 rust 代码，方便 rust 其他程序使用&lt;/li&gt;
&lt;li&gt;build.rs 和 features 用于控制整个编译流程，用户可以根据需要是当场编译依赖库，还是使用机器上已经安装好的版本&lt;/li&gt;
&lt;li&gt;build.rs 中还可以选择使用 &lt;a href="https://crates.io/crates/cc"&gt;cc&lt;/a&gt; 来实时编译 duckdb 实现，这样其他使用 rust 封装的人不用关心 duckdb 的安装问题&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;应该说这是一个很通用的提供 C 接口 rust 封装的解决方案，感兴趣的同学可以 &lt;a href="https://github.com/wangfenjin/duckdb-rs/tree/main/libduckdb-sys"&gt;参考&lt;/a&gt;。&lt;/p&gt;
&lt;h2 id="duckdb-rs"&gt;duckdb-rs&lt;/h2&gt;
&lt;p&gt;完成了 libduckdb-sys 之后其实只是第一步，因为这样生成的代码都是 unsafe 代码，具体的使用例子可以参考 &lt;a href="https://github.com/wangfenjin/duckdb-rs/blob/main/libduckdb-sys/src/lib.rs"&gt;lib.rs&lt;/a&gt; 中的测试代码。但是我们使用 rust 主要是为了他的安全性，rust 希望我们尽量减少 unsafe 的使用。所以一般的 rust 封装都会基于 libxxx-sys 提供一个内存安全的版本，这就是 duckdb-rs 的部分。&lt;/p&gt;
&lt;h3 id="小试牛刀"&gt;小试牛刀&lt;/h3&gt;
&lt;p&gt;还是因为有 rusqlite 的参考，所以花了一些时间终于实现了最初始的版本，并且我已经把这个版本发布到 &lt;a href="https://crates.io/crates/duckdb"&gt;crates.io&lt;/a&gt; 上了。这个版本的目标是基于 rusqlite 做最小的改动，并删掉 SQLite 特有的功能，让整个程序跑起来。完成之后效果不错，下面是文档中给的一个使用范例：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; duckdb::{params, Connection, Result};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#[derive(Debug)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Person&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id: &lt;span style="color:#66d9ef"&gt;i32&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; name: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; data: Option&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Vec&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;u8&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;()&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; conn &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Connection::open_in_memory()&lt;span style="color:#f92672"&gt;?&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; conn.execute_batch(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;r&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;CREATE SEQUENCE seq;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; CREATE TABLE person (
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; id INTEGER PRIMARY KEY DEFAULT NEXTVAL(&amp;#39;seq&amp;#39;),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; name TEXT NOT NULL,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; data BLOB
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;&lt;/span&gt;)&lt;span style="color:#f92672"&gt;?&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; me &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Person {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; name: &lt;span style="color:#e6db74"&gt;&amp;#34;Steven&amp;#34;&lt;/span&gt;.to_string(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; data: None,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; conn.execute(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;INSERT INTO person (name, data) VALUES (?, ?)&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;params!&lt;/span&gt;[me.name, me.data],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )&lt;span style="color:#f92672"&gt;?&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; stmt &lt;span style="color:#f92672"&gt;=&lt;/span&gt; conn.prepare(&lt;span style="color:#e6db74"&gt;&amp;#34;SELECT id, name, data FROM person&amp;#34;&lt;/span&gt;)&lt;span style="color:#f92672"&gt;?&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; person_iter &lt;span style="color:#f92672"&gt;=&lt;/span&gt; stmt.query_map([], &lt;span style="color:#f92672"&gt;|&lt;/span&gt;row&lt;span style="color:#f92672"&gt;|&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(Person {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id: &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt;.get(&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)&lt;span style="color:#f92672"&gt;?&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; name: &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt;.get(&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;)&lt;span style="color:#f92672"&gt;?&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; data: &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt;.get(&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;)&lt;span style="color:#f92672"&gt;?&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })&lt;span style="color:#f92672"&gt;?&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; person &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; person_iter {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Found person &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{:?}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, person.unwrap());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以看到，接口设计非常优雅，代码也非常符合 rust 的风格，使用上也非常方便。实现过程中发现有些 duckdb 的 C 接口还不支持的部分，我也通过提 issue 或者 PR 去解决了。这里必须要提一点，duckdb 的维护者非常耐心，不管是回答问题还是 review 代码都非常专业。&lt;/p&gt;
&lt;p&gt;剩下的问题有一个是之前提到的，duckdb 是静态类型的数据，所以需要支持很多数据类型，这里面工作量不小。另外，因为我之前也有关注 &lt;a href="https://arrow.apache.org/"&gt;Apache Arrow&lt;/a&gt;，做过 OLAP 数据库的同学可能知道，Apache Arrow 是一个通用的列式内存格式，方便在内存中做大数据量的计算或者传输，有很多 OLAP 数据引擎都在用。刚好 duckdb 也支持 arrow 格式，所以就想尝试使用 arrow 格式来查询数据，这样至少有两个好处，一个是这样我们就可以暴露 arrow 格式的数据给用户，在使用的时候就可以用上 arrow 生态的其他功能，有可能会产生一些化学反应；另外 arrow 也是有丰富的数据类型和明确的定义，反正我们是要支持很多数据类型的，现在的 C 接口本身也不完善，用 arrow 格式反而更加清晰。&lt;/p&gt;
&lt;h3 id="通过-apache-arrow-查询数据"&gt;通过 Apache Arrow 查询数据&lt;/h3&gt;
&lt;p&gt;基于上面的考虑，我把目标又看向了 &lt;a href="https://github.com/apache/arrow-rs"&gt;arrow-rs&lt;/a&gt;，并给 duckdb 的 C 接口也加上了 &lt;a href="https://github.com/duckdb/duckdb/pull/1978"&gt;arrow 的功能&lt;/a&gt;，最终在 duckdb-rs 中实现了通过 Arrow 格式来查询数据，实现参见 &lt;a href="https://github.com/wangfenjin/duckdb-rs/pull/8"&gt;这里&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;实现之后，之前通过行来读取数据的接口完全不变，还能直接查询到 Arrow 格式的数据，下面是一个测试的例子：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;test_query_arrow_record_batch_large&lt;/span&gt;() -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;()&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; db &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Connection::open_in_memory().unwrap();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; db.execute_batch(&lt;span style="color:#e6db74"&gt;&amp;#34;BEGIN TRANSACTION&amp;#34;&lt;/span&gt;)&lt;span style="color:#f92672"&gt;?&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; db.execute_batch(&lt;span style="color:#e6db74"&gt;&amp;#34;CREATE TABLE test(t INTEGER);&amp;#34;&lt;/span&gt;)&lt;span style="color:#f92672"&gt;?&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; _ &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&lt;span style="color:#f92672"&gt;..&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;300&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; db.execute_batch(&lt;span style="color:#e6db74"&gt;&amp;#34;INSERT INTO test VALUES (1); INSERT INTO test VALUES (2); INSERT INTO test VALUES (3); INSERT INTO test VALUES (4); INSERT INTO test VALUES (5);&amp;#34;&lt;/span&gt;)&lt;span style="color:#f92672"&gt;?&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; db.execute_batch(&lt;span style="color:#e6db74"&gt;&amp;#34;END TRANSACTION&amp;#34;&lt;/span&gt;)&lt;span style="color:#f92672"&gt;?&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; rbs &lt;span style="color:#f92672"&gt;=&lt;/span&gt; db.query_arrow(&lt;span style="color:#e6db74"&gt;&amp;#34;select t from test order by t&amp;#34;&lt;/span&gt;, [])&lt;span style="color:#f92672"&gt;?&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;assert_eq!&lt;/span&gt;(rbs.len(), &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;assert_eq!&lt;/span&gt;(rbs.iter().map(&lt;span style="color:#f92672"&gt;|&lt;/span&gt;rb&lt;span style="color:#f92672"&gt;|&lt;/span&gt; rb.num_rows()).sum::&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;usize&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;(), &lt;span style="color:#ae81ff"&gt;1500&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;assert_eq!&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; rbs.iter()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .map(&lt;span style="color:#f92672"&gt;|&lt;/span&gt;rb&lt;span style="color:#f92672"&gt;|&lt;/span&gt; rb
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .column(&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .as_any()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .downcast_ref::&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Int32Array&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .unwrap()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .iter()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .map(&lt;span style="color:#f92672"&gt;|&lt;/span&gt;i&lt;span style="color:#f92672"&gt;|&lt;/span&gt; i.unwrap())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .sum::&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;i32&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .sum::&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;i32&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;4500&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以看到，我们查询到 Arrow 格式的数据之后，还能通过 arrow-rs 中提供的其他能力做进一步的计算，十分方便。&lt;/p&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;本文主要介绍了 duckdb-rs 的设计和实现，笔者之前有一些开发 OLAP 数据的经验，但是对于 rust 算是新手，之前虽然写过一些但是没有深入学习，做这个项目也有一个目的是为了重新学习一下 rust。好在有 rusqlite 作为参考，所以没有碰到特别多语言层面的问题。&lt;/p&gt;
&lt;p&gt;希望这篇文章对于其他对 rust 和数据库感兴趣的同学有一些帮助。同时这个库还有很多没解决的问题，比如支持更多的数据类型，支持连接池，支持更快的数据导入接口等等，我已经建了一些 issues，感兴趣的同学可以回复 &lt;a href="https://github.com/wangfenjin/duckdb-rs/issues"&gt;issue&lt;/a&gt; 认领，我也会竭力提供需要的帮助，大家一起讨论和学习。&lt;/p&gt;
&lt;h2 id="参考"&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;duckdb 的官网：https://duckdb.org/&lt;/li&gt;
&lt;li&gt;duckdb 的代码库：https://github.com/duckdb/duckdb&lt;/li&gt;
&lt;li&gt;SQLite 的 rust 封装，duckdb-rs 也是基于它改的：https://github.com/rusqlite/rusqlite&lt;/li&gt;
&lt;li&gt;duckdb-rs 的代码库：https://github.com/wangfenjin/duckdb-rs&lt;/li&gt;
&lt;li&gt;Apache Arrow 的 rust 实现：https://github.com/apache/arrow-rs&lt;/li&gt;
&lt;/ul&gt;</content></item><item><title>Simple: SQLite3 结巴分词插件</title><link>https://www.wangfenjin.com/posts/simple-jieba-tokenizer/</link><pubDate>Sun, 21 Feb 2021 00:00:00 +0000</pubDate><guid>https://www.wangfenjin.com/posts/simple-jieba-tokenizer/</guid><description>&lt;p&gt;一年前开发 simple 分词器，实现了微信在两篇文章中描述的，基于 SQLite 支持中文和拼音的搜索方案。具体背景参见&lt;a href="https://www.wangfenjin.com/posts/simple-tokenizer/"&gt;这篇文章&lt;/a&gt;。项目发布后受到了一些朋友的关注，后续也发布了一些改进，提升了项目易用性。&lt;/p&gt;
&lt;p&gt;最近重新体验微信客户端搜索功能，发现对于中文的搜索已经不是基于单字命中，而是更精准的基于词组。比如搜索“法国”，之前如果句子中有“法”和“国”两个字时也会命中，所以如果一句话里包含“国法”就会被命中，但是这跟“法国”没有任何关系。&lt;/p&gt;
&lt;p&gt;本文描述对 simple 分词器添加的基于词组命中的实现，从而实现更好的查找效果。另外本文也会基于之前在 issue 中大家提到的问题，提供一个怎么使用 SQLite FTS 表的建议。&lt;/p&gt;
&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;p&gt;先简单回顾一下之前的实现，因为结巴分词只跟中文有关，所以本文会略去拼音的部分。&lt;/p&gt;
&lt;p&gt;搜索主要分为两部分，建立索引和命中索引。为了实现中文的搜索，我们先把句子按照单字拆分，按照单字建立索引；然后对于用户的输入，也同样按照单字拆分，这样 query 就能命中索引了。为了支持词组搜索，再按照单字拆分就很难满足需求了，所以可以考虑的方案是要么改索引，要么改 query。如果改索引的话会有一些问题，比如如果用户就输入了一个字比如“国”，但是我们建索引的时候把“法国”放到了一起，那“国”字就命中不了了，所以最好是保持单字索引不变，通过改写 query 来达到检索词组的效果。&lt;/p&gt;
&lt;h2 id="实现"&gt;实现&lt;/h2&gt;
&lt;p&gt;simple 分词器之前提供了一个 simple_query() 函数来帮助用户生成 query，我们也可以加一个新的函数来实现词组的功能。经过简单的调研，我们发现 &lt;a href="https://github.com/yanyiwu/cppjieba"&gt;cppjieba&lt;/a&gt; 用 C++ 实现了结巴分词的功能，很适用与我们的需求。&lt;/p&gt;
&lt;p&gt;所以我实现了一个新的函数叫做 jieba_query() ，它的使用方式跟 simple_query() 一样，内部实现时，我们会先使用 cppjieba 对输入进行分词，再根据分词的结果构建 SQLite3 能理解的 query ，从而实现了词组匹配的功能。具体的逻辑可以参考 &lt;a href="https://github.com/wangfenjin/simple/pull/35"&gt;这里&lt;/a&gt; 。对于不需要结巴分词功能的用户，可以在编译的时候使用 &lt;code&gt;-DSIMPLE_WITH_JIEBA=OFF&lt;/code&gt; 关闭结巴分词的功能，这样能减少编译文件的大小，方便客户端对文件大小敏感的场景使用。&lt;/p&gt;
&lt;h2 id="使用"&gt;使用&lt;/h2&gt;
&lt;p&gt;本文想着重介绍一下 SQLite3 FTS5 功能使用的问题，这些问题都是有朋友在项目的 issue 中提到过的，都是非常好的问题，但是也说明有不少人对怎么使用 FTS 表不太清楚，希望本文能解决一些疑惑。&lt;/p&gt;
&lt;p&gt;首先第一点，FTS5 表虽然是一个虚拟表，提供了全文搜索的功能，但是它整体还是跳不出 SQL 的范畴，所以其实很多用法和其他 SQL 表是一样的，当然它也跳不出 SQL 的限制。比如有一个 issue 问如果表中有多列的时候，能不能检索全表，但是只返回命中的那些列？答案是不行的，因为按照 SQL 的语法规则，SELECT 语句后面必须显示说明你想要 SELECT 哪些列，所以结果列是必须用户指定的，如果我们像知道哪些列命中了，只能通过其他一些手段，感兴趣的朋友可以看这个 &lt;a href="https://github.com/wangfenjin/simple/issues/36"&gt;issue36&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;另外 simple 分词器提供了不少额外的功能，比如 simple_query() 和 simple_highlight() 等辅助函数，但是它并不影响我们使用原有 FTS5 的功能，比如如果想按照相关度排序，FTS5 自带的 &lt;code&gt;order by rank&lt;/code&gt; 功能还是可以继续可以使用，也就是说 &lt;a href="https://www.sqlite.org/fts5.html"&gt;FTS5 页面&lt;/a&gt; 提供的所有功能都是可以和 simple 分词器一起使用的。&lt;/p&gt;</description><content>&lt;p&gt;一年前开发 simple 分词器，实现了微信在两篇文章中描述的，基于 SQLite 支持中文和拼音的搜索方案。具体背景参见&lt;a href="https://www.wangfenjin.com/posts/simple-tokenizer/"&gt;这篇文章&lt;/a&gt;。项目发布后受到了一些朋友的关注，后续也发布了一些改进，提升了项目易用性。&lt;/p&gt;
&lt;p&gt;最近重新体验微信客户端搜索功能，发现对于中文的搜索已经不是基于单字命中，而是更精准的基于词组。比如搜索“法国”，之前如果句子中有“法”和“国”两个字时也会命中，所以如果一句话里包含“国法”就会被命中，但是这跟“法国”没有任何关系。&lt;/p&gt;
&lt;p&gt;本文描述对 simple 分词器添加的基于词组命中的实现，从而实现更好的查找效果。另外本文也会基于之前在 issue 中大家提到的问题，提供一个怎么使用 SQLite FTS 表的建议。&lt;/p&gt;
&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;p&gt;先简单回顾一下之前的实现，因为结巴分词只跟中文有关，所以本文会略去拼音的部分。&lt;/p&gt;
&lt;p&gt;搜索主要分为两部分，建立索引和命中索引。为了实现中文的搜索，我们先把句子按照单字拆分，按照单字建立索引；然后对于用户的输入，也同样按照单字拆分，这样 query 就能命中索引了。为了支持词组搜索，再按照单字拆分就很难满足需求了，所以可以考虑的方案是要么改索引，要么改 query。如果改索引的话会有一些问题，比如如果用户就输入了一个字比如“国”，但是我们建索引的时候把“法国”放到了一起，那“国”字就命中不了了，所以最好是保持单字索引不变，通过改写 query 来达到检索词组的效果。&lt;/p&gt;
&lt;h2 id="实现"&gt;实现&lt;/h2&gt;
&lt;p&gt;simple 分词器之前提供了一个 simple_query() 函数来帮助用户生成 query，我们也可以加一个新的函数来实现词组的功能。经过简单的调研，我们发现 &lt;a href="https://github.com/yanyiwu/cppjieba"&gt;cppjieba&lt;/a&gt; 用 C++ 实现了结巴分词的功能，很适用与我们的需求。&lt;/p&gt;
&lt;p&gt;所以我实现了一个新的函数叫做 jieba_query() ，它的使用方式跟 simple_query() 一样，内部实现时，我们会先使用 cppjieba 对输入进行分词，再根据分词的结果构建 SQLite3 能理解的 query ，从而实现了词组匹配的功能。具体的逻辑可以参考 &lt;a href="https://github.com/wangfenjin/simple/pull/35"&gt;这里&lt;/a&gt; 。对于不需要结巴分词功能的用户，可以在编译的时候使用 &lt;code&gt;-DSIMPLE_WITH_JIEBA=OFF&lt;/code&gt; 关闭结巴分词的功能，这样能减少编译文件的大小，方便客户端对文件大小敏感的场景使用。&lt;/p&gt;
&lt;h2 id="使用"&gt;使用&lt;/h2&gt;
&lt;p&gt;本文想着重介绍一下 SQLite3 FTS5 功能使用的问题，这些问题都是有朋友在项目的 issue 中提到过的，都是非常好的问题，但是也说明有不少人对怎么使用 FTS 表不太清楚，希望本文能解决一些疑惑。&lt;/p&gt;
&lt;p&gt;首先第一点，FTS5 表虽然是一个虚拟表，提供了全文搜索的功能，但是它整体还是跳不出 SQL 的范畴，所以其实很多用法和其他 SQL 表是一样的，当然它也跳不出 SQL 的限制。比如有一个 issue 问如果表中有多列的时候，能不能检索全表，但是只返回命中的那些列？答案是不行的，因为按照 SQL 的语法规则，SELECT 语句后面必须显示说明你想要 SELECT 哪些列，所以结果列是必须用户指定的，如果我们像知道哪些列命中了，只能通过其他一些手段，感兴趣的朋友可以看这个 &lt;a href="https://github.com/wangfenjin/simple/issues/36"&gt;issue36&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;另外 simple 分词器提供了不少额外的功能，比如 simple_query() 和 simple_highlight() 等辅助函数，但是它并不影响我们使用原有 FTS5 的功能，比如如果想按照相关度排序，FTS5 自带的 &lt;code&gt;order by rank&lt;/code&gt; 功能还是可以继续可以使用，也就是说 &lt;a href="https://www.sqlite.org/fts5.html"&gt;FTS5 页面&lt;/a&gt; 提供的所有功能都是可以和 simple 分词器一起使用的。&lt;/p&gt;
&lt;p&gt;最后也是最重要的一个问题，FTS5 表到底该怎么用？有一个 &lt;a href="https://github.com/wangfenjin/simple/issues/26"&gt;issue26&lt;/a&gt; 提到的问题非常好，我把它放到这里：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;《微信全文搜索优化之路》一文中针对索引表的介绍，我对索引有几个问题想请教一下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;业务表是正常的程序的数据表，还要再为了全文搜索再多建立一份索引表，是吗？我直接将我的业务数据表在创建的时候按虚表建立行吗？（例如create virtual table tablename using fts5(列名1,列名2,tokenize = &amp;lsquo;simple&amp;rsquo;)）&lt;/li&gt;
&lt;li&gt;如果再多建立一份索引表，那是不是每一个业务表和对应的索引表的表字段是完全相同？&lt;/li&gt;
&lt;li&gt;如果再多建立一份索引表，那数据库的大小是不是加倍了，尤其是把文件或图片影片存入数据库的情况（BLOB类型）？&lt;/li&gt;
&lt;li&gt;原文中【为了解决业务变化而带来的表结构修改问题，微信把业务属性数字化】，这也是我想要的，能否帮助贴下原文中提到的【索引表-IndexTable】和【数据表-MetaTable】的建表语句？&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;核心的问题是：想让表数据支持全文搜索，需要把数据复制一份吗？这样会不会导致数据库膨胀？在用户的手机上我们可不想占用太多无谓的空间。&lt;/p&gt;
&lt;h3 id="externel-content-table"&gt;externel content table&lt;/h3&gt;
&lt;p&gt;其实这个问题在 FTS5 的官方文档中已经给出了解决方案那就是 &lt;a href="https://www.sqlite.org/fts5.html#external_content_and_contentless_tables"&gt;externel content table&lt;/a&gt;，大家也可以参考 &lt;a href="https://kimsereylam.com/sqlite/2020/03/06/full-text-search-with-sqlite.html"&gt;这篇文章&lt;/a&gt; 。&lt;/p&gt;
&lt;p&gt;它的意思是我们可以建一张普通表，然后再建一张 FTS5 表来支持全文索引，这张虚拟表本身不会存储真实的数据，如果 SELECT 语句用到具体的内容，都会通过关联关系去原表获取，这样就不存在数据重复的问题了。但是这里就会涉及到数据一致性的问题，怎么保证原表的数据和索引表是一致的呢？通过 trigger 也就是触发器来实现：对于原表的增删改，都会通过触发器同步到 FTS 表。这样基本上就完美解决了上面用户的问题。&lt;/p&gt;
&lt;p&gt;可能有人会问为什么不直接用 FTS5 表呢？这样普通表就不用了，也省了触发器的逻辑。原因是 FTS 表提供了全文索引的能力，但是它也有限制，对于基于 ID 或者其他普通索引的请求它是不支持的，如果我们想有一个时间列并且基于时间列索引排序，FTS表就不行，还是需要普通表。通过普通表和 FTS 表结合的方案，我们就能同时使用两者的能力。&lt;/p&gt;
&lt;h3 id="微信的方案"&gt;微信的方案&lt;/h3&gt;
&lt;p&gt;需要注意的是，微信并没有使用上面提到的方案，而是单独建了一张打平的索引表，把所有需要全文索引的数据放到一张单独的表里面，再通过外键关联到具体的业务去。这样的好处在微信的文章中有所提及，主要是其他关联的表结构变更的时候，FTS 表不用动，这样很容易添加想要搜索的字段，只需把该字段写入 FTS 表及关联关系的表就行，表结构见下图：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.wangfenjin.com/img/wechat-fts5.jpeg" alt="wechat-fts5"&gt;&lt;/p&gt;
&lt;p&gt;个人觉得对于微信这个复杂度的业务，可以考虑这个方案，毕竟需要搜索的信息非常多，这样方便各个业务复用搜索能力。但是对于大部分的业务，用 external content table 可能是更简单的方案，毕竟在数据写入和读取的时候都更快更方便，微信的方案在数据操作流程上会复杂不少，需要逻辑上做更多的封装。&lt;/p&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;上面主要介绍了 simple 分词器最新的功能，基于结巴分词实现基于词组的搜索功能，实现更精准的匹配。另外也介绍了在实际项目中使用 FTS 表的方案，希望对大家有所助益。&lt;/p&gt;
&lt;h2 id="reference"&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Simple 分词器: &lt;a href="https://github.com/wangfenjin/simple"&gt;https://github.com/wangfenjin/simple&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;sqlite 官方文档：&lt;a href="https://www.sqlite.org/fts5.html"&gt;https://www.sqlite.org/fts5.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;微信全文搜索优化之路：&lt;a href="https://cloud.tencent.com/developer/article/1006159"&gt;https://cloud.tencent.com/developer/article/1006159&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;微信移动端的全文检索多音字问题解决方案：&lt;a href="https://cloud.tencent.com/developer/article/1198371"&gt;https://cloud.tencent.com/developer/article/1198371&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Simple: 一个支持中文和拼音搜索的 sqlite fts5插件：&lt;a href="https://www.wangfenjin.com/posts/simple-tokenizer/"&gt;https://www.wangfenjin.com/posts/simple-tokenizer/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Full Text Search With Sqlite SQLite：&lt;a href="https://kimsereylam.com/sqlite/2020/03/06/full-text-search-with-sqlite.html"&gt;https://kimsereylam.com/sqlite/2020/03/06/full-text-search-with-sqlite.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content></item><item><title>xeus-clickhouse: Jupyter 的 ClickHouse 内核</title><link>https://www.wangfenjin.com/posts/jupyter-clickhouse/</link><pubDate>Sun, 28 Jun 2020 00:00:00 +0000</pubDate><guid>https://www.wangfenjin.com/posts/jupyter-clickhouse/</guid><description>&lt;p&gt;在科学计算领域，Jupyter 是一个使用非常广泛的集成开发环境，它支持多种主流的编程语言比如 Python, C++, R 或者 Julia。同时，数据科学最重要的还是数据，而 SQL 是操作数据最直观的语言。前段时间看到&lt;a href="https://blog.jupyter.org/a-jupyter-kernel-for-sqlite-9549c5dcf551"&gt;一篇文章&lt;/a&gt;，有人给 sqlite 做了一个 jupyter 的内核，感觉很有意思。所以我尝试给 ClickHouse 做了一个 jupyter 的内核，目前已经有了一个可以试用的版本，下面做一个简单介绍。&lt;/p&gt;
&lt;h2 id="现状"&gt;现状&lt;/h2&gt;
&lt;p&gt;新内核允许用户用 ClickHouse SQL 的语法直接操作远程 CH 数据库，通过一些扩展操作比如 &lt;code&gt;%CONNECT&lt;/code&gt; 支持与 ch cli 一样的连接参数，后续也有计划使用 jupyter magics 支持更多的数据可视化操作。&lt;/p&gt;
&lt;p&gt;项目参考了 jupyter sqlite 内核的实现方式，是基于 &lt;a href="https://github.com/jupyter-xeus/xeus"&gt;xeus&lt;/a&gt; 框架来实现的。xeus 是一个 c++ 的 lib 库，它对 jupyter 的内核做了很好的封装，我们只需要专注于内核相关的功能就可以了。目前对于 ch 的操作基于 clickhouse-cpp 来实现，它是 ch 的 cpp 客户端。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.wangfenjin.com/img/ch-sql.gif" alt="ch-sql"&gt;&lt;/p&gt;
&lt;p&gt;目前实现处于早期阶段，但是基础功能已经可用。它支持了几乎 CH 所有 SQL 语法，具体例子可以参考 &lt;a href="https://github.com/wangfenjin/xeus-clickhouse/blob/master/examples/clickhouse.ipynb"&gt;clickhouse.ipynb&lt;/a&gt;。xeus-clickhouse 在 jupyter notebook 和 jupyter lab 中以 HTML 表格的形式展示数据；在 jupyter console 中，我们使用 tabulate 库只做纯文本的表格。&lt;/p&gt;</description><content>&lt;p&gt;在科学计算领域，Jupyter 是一个使用非常广泛的集成开发环境，它支持多种主流的编程语言比如 Python, C++, R 或者 Julia。同时，数据科学最重要的还是数据，而 SQL 是操作数据最直观的语言。前段时间看到&lt;a href="https://blog.jupyter.org/a-jupyter-kernel-for-sqlite-9549c5dcf551"&gt;一篇文章&lt;/a&gt;，有人给 sqlite 做了一个 jupyter 的内核，感觉很有意思。所以我尝试给 ClickHouse 做了一个 jupyter 的内核，目前已经有了一个可以试用的版本，下面做一个简单介绍。&lt;/p&gt;
&lt;h2 id="现状"&gt;现状&lt;/h2&gt;
&lt;p&gt;新内核允许用户用 ClickHouse SQL 的语法直接操作远程 CH 数据库，通过一些扩展操作比如 &lt;code&gt;%CONNECT&lt;/code&gt; 支持与 ch cli 一样的连接参数，后续也有计划使用 jupyter magics 支持更多的数据可视化操作。&lt;/p&gt;
&lt;p&gt;项目参考了 jupyter sqlite 内核的实现方式，是基于 &lt;a href="https://github.com/jupyter-xeus/xeus"&gt;xeus&lt;/a&gt; 框架来实现的。xeus 是一个 c++ 的 lib 库，它对 jupyter 的内核做了很好的封装，我们只需要专注于内核相关的功能就可以了。目前对于 ch 的操作基于 clickhouse-cpp 来实现，它是 ch 的 cpp 客户端。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.wangfenjin.com/img/ch-sql.gif" alt="ch-sql"&gt;&lt;/p&gt;
&lt;p&gt;目前实现处于早期阶段，但是基础功能已经可用。它支持了几乎 CH 所有 SQL 语法，具体例子可以参考 &lt;a href="https://github.com/wangfenjin/xeus-clickhouse/blob/master/examples/clickhouse.ipynb"&gt;clickhouse.ipynb&lt;/a&gt;。xeus-clickhouse 在 jupyter notebook 和 jupyter lab 中以 HTML 表格的形式展示数据；在 jupyter console 中，我们使用 tabulate 库只做纯文本的表格。&lt;/p&gt;
&lt;h2 id="未来"&gt;未来&lt;/h2&gt;
&lt;p&gt;对于 xeus-clickhouse 未来的规划是，先打磨好稳定性，目前已知的还有一个非法字符导致内核崩溃的问题，已经提交 issue 给 xeus 仓库；另外clickhouse-cpp 不支持 ssl 连接。除了基础功能的打磨，还计划通过支持更多的 jupyter magic 来实现数据的可视化渲染，提供更方便的数据可视化能力。&lt;/p&gt;
&lt;h2 id="使用"&gt;使用&lt;/h2&gt;
&lt;p&gt;我制作了一个 Docker 镜像发布在 &lt;a href="https://hub.docker.com/r/wangfenjin/xeus-clickhouse"&gt;docker-hub&lt;/a&gt;，不需要安装任何环境就可以试用：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# start jupyter with clickhouse kernal&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run -p 8888:8888 wangfenjin/xeus-clickhouse:v0.1.0
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# start a local clickhouse for testing&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run -d --name jupyter-clickhouse-server -p 8123:8123 --ulimit nofile&lt;span style="color:#f92672"&gt;=&lt;/span&gt;262144:262144 yandex/clickhouse-server
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# open the example/clickhouse.ipynb and connect to local server by &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# %CONNECT --host host.docker.internal --port 8123&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在 docker 里面连接另外一个 docker 中的 ch 可能会有问题，感觉是目前 clickhouse-cpp 对于网络的处理不太完善。感兴趣的同学也可以下载代码自己编译，具体的编译流程见 &lt;a href="https://github.com/wangfenjin/xeus-clickhouse"&gt;github&lt;/a&gt; 仓库。欢迎大家试用！&lt;/p&gt;</content></item><item><title>用 od 查看 ClickHouse 的索引文件</title><link>https://www.wangfenjin.com/posts/clickhouse-od/</link><pubDate>Wed, 18 Mar 2020 00:00:00 +0000</pubDate><guid>https://www.wangfenjin.com/posts/clickhouse-od/</guid><description>&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;p&gt;学习 ClickHouse (后面简称 CH) 的时候，会对 CH 到底怎么组织磁盘上的 MergeTree 文件有很多疑惑。关于 MergeTree 的介绍可以参考[1]，但是如果想具体看下磁盘上的文件，没有现成的工具。本文参考 [2] 介绍通过 od 查看磁盘文件的方法，感兴趣的话可以自己试一下，会对 MergeTree 有更深的理解。&lt;/p&gt;
&lt;p&gt;本文以 &lt;a href="https://clickhouse.tech/docs/en/getting_started/tutorial/"&gt;官方Tutorial&lt;/a&gt; 中的 hits_v1 表为例来说明。下面主要描述怎么看 primary.idx 文件和 [column].mrk 文件。在 MergeTree 数据结构中，primary.idx 可认为是一级索引，mrk 文件是用作定位具体文件偏移量的，他们的行数是相同且一一对应。&lt;/p&gt;
&lt;h2 id="查看-primaryidx"&gt;查看 primary.idx&lt;/h2&gt;
&lt;p&gt;primary.idx 里面的文件是把主键的索引写入到磁盘文件中，hits_v1 的主键为 order by 语句中的字段，即 &lt;code&gt;ORDER BY (CounterID, EventDate, intHash32(UserID))&lt;/code&gt;，CounterID 类型是 uint32，存储为4字节；EventDate 类型是 Date，存储是 2字节整型；intHash32 是4字节整型。CH 的文件内容非常紧凑，每个字段是紧挨着写入的，没有其他类似空格符等浪费。所以 primary.idx 的存储格式是 4+2+4，然后每隔 8192 行写一行索引。查看内容的方法为：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# sql 选择第一行索引的内容&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Select CounterID,toRelativeDayNum&lt;span style="color:#f92672"&gt;(&lt;/span&gt;EventDate&lt;span style="color:#f92672"&gt;)&lt;/span&gt;,intHash32&lt;span style="color:#f92672"&gt;(&lt;/span&gt;UserID&lt;span style="color:#f92672"&gt;)&lt;/span&gt; from tutorial.hits_v1 limit 0,1;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# od 查看 3 个字段&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;od -An -i -j &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; -N &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt; primary.idx
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;od -An -i -j &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt; -N &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; primary.idx
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;od -An -i -j &lt;span style="color:#ae81ff"&gt;6&lt;/span&gt; -N &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt; primary.idx
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 类似的，sql 选择第二行索引的内容&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Select CounterID,toRelativeDayNum&lt;span style="color:#f92672"&gt;(&lt;/span&gt;EventDate&lt;span style="color:#f92672"&gt;)&lt;/span&gt;,intHash32&lt;span style="color:#f92672"&gt;(&lt;/span&gt;UserID&lt;span style="color:#f92672"&gt;)&lt;/span&gt; from tutorial.hits_v1 limit 8192,1;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# od 查看 3 个字段&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;od -An -i -j &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; -N &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt; primary.idx
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;od -An -i -j &lt;span style="color:#ae81ff"&gt;14&lt;/span&gt; -N &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; primary.idx
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;od -An -i -j &lt;span style="color:#ae81ff"&gt;16&lt;/span&gt; -N &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt; primary.idx
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;关于 od 的选项介绍如下：&lt;/p&gt;</description><content>&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;p&gt;学习 ClickHouse (后面简称 CH) 的时候，会对 CH 到底怎么组织磁盘上的 MergeTree 文件有很多疑惑。关于 MergeTree 的介绍可以参考[1]，但是如果想具体看下磁盘上的文件，没有现成的工具。本文参考 [2] 介绍通过 od 查看磁盘文件的方法，感兴趣的话可以自己试一下，会对 MergeTree 有更深的理解。&lt;/p&gt;
&lt;p&gt;本文以 &lt;a href="https://clickhouse.tech/docs/en/getting_started/tutorial/"&gt;官方Tutorial&lt;/a&gt; 中的 hits_v1 表为例来说明。下面主要描述怎么看 primary.idx 文件和 [column].mrk 文件。在 MergeTree 数据结构中，primary.idx 可认为是一级索引，mrk 文件是用作定位具体文件偏移量的，他们的行数是相同且一一对应。&lt;/p&gt;
&lt;h2 id="查看-primaryidx"&gt;查看 primary.idx&lt;/h2&gt;
&lt;p&gt;primary.idx 里面的文件是把主键的索引写入到磁盘文件中，hits_v1 的主键为 order by 语句中的字段，即 &lt;code&gt;ORDER BY (CounterID, EventDate, intHash32(UserID))&lt;/code&gt;，CounterID 类型是 uint32，存储为4字节；EventDate 类型是 Date，存储是 2字节整型；intHash32 是4字节整型。CH 的文件内容非常紧凑，每个字段是紧挨着写入的，没有其他类似空格符等浪费。所以 primary.idx 的存储格式是 4+2+4，然后每隔 8192 行写一行索引。查看内容的方法为：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# sql 选择第一行索引的内容&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Select CounterID,toRelativeDayNum&lt;span style="color:#f92672"&gt;(&lt;/span&gt;EventDate&lt;span style="color:#f92672"&gt;)&lt;/span&gt;,intHash32&lt;span style="color:#f92672"&gt;(&lt;/span&gt;UserID&lt;span style="color:#f92672"&gt;)&lt;/span&gt; from tutorial.hits_v1 limit 0,1;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# od 查看 3 个字段&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;od -An -i -j &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; -N &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt; primary.idx
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;od -An -i -j &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt; -N &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; primary.idx
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;od -An -i -j &lt;span style="color:#ae81ff"&gt;6&lt;/span&gt; -N &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt; primary.idx
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 类似的，sql 选择第二行索引的内容&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Select CounterID,toRelativeDayNum&lt;span style="color:#f92672"&gt;(&lt;/span&gt;EventDate&lt;span style="color:#f92672"&gt;)&lt;/span&gt;,intHash32&lt;span style="color:#f92672"&gt;(&lt;/span&gt;UserID&lt;span style="color:#f92672"&gt;)&lt;/span&gt; from tutorial.hits_v1 limit 8192,1;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# od 查看 3 个字段&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;od -An -i -j &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; -N &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt; primary.idx
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;od -An -i -j &lt;span style="color:#ae81ff"&gt;14&lt;/span&gt; -N &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; primary.idx
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;od -An -i -j &lt;span style="color:#ae81ff"&gt;16&lt;/span&gt; -N &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt; primary.idx
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;关于 od 的选项介绍如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;-An 是不让输出偏移只输出文本内容&lt;/li&gt;
&lt;li&gt;-i 是说把选择的位当作长度为 4 的整型输出；对于 EventDate 虽然存储的是 2， 但是我们把它当作 4 位输出也没问题，主要控制在 -j -N&lt;/li&gt;
&lt;li&gt;-j 偏移的起始字节数&lt;/li&gt;
&lt;li&gt;-N 从偏移量开始读取的字节数&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;-A,-j, -N 这几个选项是必须有的，-i 得看数据类型是啥，还支持其他的比如字符、浮点类型等&lt;/p&gt;
&lt;p&gt;从上面可以看到，想查看 primary.idx 的文件，需要知道主键的排列顺序和主键的类型，没办法像 parquet tools 一样很简单地写一个通用程序来直接查看文件。&lt;/p&gt;
&lt;h2 id="查看-columnmrk"&gt;查看 [column].mrk&lt;/h2&gt;
&lt;p&gt;mrk 文件是辅助定位 bin 文件设置的。bin 文件被分成小的数据块，每个数据块压缩后存放到一起。可以参考从 [1] 中的截图：
&lt;img src="https://www.wangfenjin.com/img/ch-bin.jpeg" alt="ch-bin-file"&gt;&lt;/p&gt;
&lt;p&gt;mrk 文件行数与 idx 文件一致，每行包含两个固定为 8 字节的整型，第一个整型是 [column].bin 文件的偏移量定位到具体的数据块，第二个整型是把数据块解压后定位解压后的文件偏移。查看 mrk 的脚本如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt; -z &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$1&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;]&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#e6db74"&gt;&amp;#34;Missing filename, mrk.sh file.mrk&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; exit &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;filename&lt;span style="color:#f92672"&gt;=&lt;/span&gt;$1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;len&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;wc -c &amp;lt; $filename&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;offset&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;maxline&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;$((&lt;/span&gt;len/16&lt;span style="color:#66d9ef"&gt;))&lt;/span&gt; -gt $maxline &lt;span style="color:#f92672"&gt;]&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$filename&lt;span style="color:#e6db74"&gt; first &lt;/span&gt;$maxline&lt;span style="color:#e6db74"&gt; lines:&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$filename&lt;span style="color:#e6db74"&gt; content:&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;while&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;$((&lt;/span&gt;offset+16&lt;span style="color:#66d9ef"&gt;))&lt;/span&gt; -le $len &lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt; $maxline -gt &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;]&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; line&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;od -An -t d8 -j $offset -N &lt;span style="color:#ae81ff"&gt;8&lt;/span&gt; $filename&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;,&amp;#34;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;od -An -t d8 -j &lt;span style="color:#66d9ef"&gt;$((&lt;/span&gt;offset+8&lt;span style="color:#66d9ef"&gt;))&lt;/span&gt; -N &lt;span style="color:#ae81ff"&gt;8&lt;/span&gt; $filename&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo $line
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; offset&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$((&lt;/span&gt;$offset&lt;span style="color:#f92672"&gt;+&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;16&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; maxline&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$((&lt;/span&gt;$maxline&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;查看 mrk 文件的脚本是通用的，传入文件名就可以了。它的正确性也是可以验证的，比如对于 UserID 这个字段，它是 uint64 型，即占用 8 个字节，8192 行就是 65536 个字节；刚好 bin 文件中数据块的默认最小值是 65536，所以会发现 UserID.mrk 文件第二列的值永远为 0，因为刚好解压缩后的偏移量是 0。&lt;/p&gt;
&lt;p&gt;对于 CounterID.mrk 文件，它是 int32 占用 4 个字节，所以能看到第二列的值是可能出现非 0 的。&lt;/p&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;CH 没有提供简单的方案查看 idx 和 mrk 文件的内容，我们可以通过 od 来模拟实现，能帮助我们更好了解 MergeTree 这个数据结构。&lt;/p&gt;
&lt;h2 id="参考"&gt;参考&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;朱凯老师关于 MergeTree 的介绍：https://github.com/ClickHouse/clickhouse-presentations/blob/master/meetup32/朱凯.ppt&lt;/li&gt;
&lt;li&gt;一个俄语的 PPT 提到 od 的使用，想自己看 PPT 的话可以用 Google 翻译：https://github.com/ClickHouse/clickhouse-presentations/blob/master/meetup27/adaptive_index_granularity.pdf&lt;/li&gt;
&lt;/ol&gt;</content></item><item><title>Spacemacs Intro</title><link>https://www.wangfenjin.com/posts/spacemacs-intro/</link><pubDate>Thu, 12 Mar 2020 00:00:00 +0000</pubDate><guid>https://www.wangfenjin.com/posts/spacemacs-intro/</guid><description>&lt;p&gt;Intro video: &lt;a href="https://www.ixigua.com/i6803300850765660676/"&gt;https://www.ixigua.com/i6803300850765660676/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;What&amp;rsquo;s spacemacs?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A emacs configuration framework&lt;/li&gt;
&lt;li&gt;Support both emacs and vim editing styles&lt;/li&gt;
&lt;li&gt;Great programming tool&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="project-management-spcp"&gt;Project management: SPC+p&lt;/h2&gt;
&lt;p&gt;projectile&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;SPC+p+p&lt;/li&gt;
&lt;li&gt;SPC+p+f&lt;/li&gt;
&lt;li&gt;SPC+p+t&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="search-spcs"&gt;Search: SPC+s&lt;/h2&gt;
&lt;p&gt;ripgrep&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;ul&gt;
&lt;li&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SPC+*&lt;/li&gt;
&lt;li&gt;SPC+/&lt;/li&gt;
&lt;li&gt;SPC+s+s&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="navigation"&gt;Navigation:&lt;/h2&gt;
&lt;p&gt;lsp&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Code: gd, SPC+j+i&lt;/li&gt;
&lt;li&gt;Buffer: SPC+b&lt;/li&gt;
&lt;li&gt;Window: SPC+w, ALT+num&lt;/li&gt;
&lt;li&gt;SPC+a&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="editing-vim"&gt;Editing: vim&lt;/h2&gt;
&lt;p&gt;evil&lt;/p&gt;
&lt;h2 id="version-control-magit"&gt;Version Control: magit&lt;/h2&gt;
&lt;h2 id="shell-spc"&gt;Shell: SPC+'&lt;/h2&gt;
&lt;p&gt;eshell&lt;/p&gt;
&lt;h2 id="help-spc-spch"&gt;Help: SPC+?, SPC+h&lt;/h2&gt;</description><content>&lt;p&gt;Intro video: &lt;a href="https://www.ixigua.com/i6803300850765660676/"&gt;https://www.ixigua.com/i6803300850765660676/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;What&amp;rsquo;s spacemacs?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A emacs configuration framework&lt;/li&gt;
&lt;li&gt;Support both emacs and vim editing styles&lt;/li&gt;
&lt;li&gt;Great programming tool&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="project-management-spcp"&gt;Project management: SPC+p&lt;/h2&gt;
&lt;p&gt;projectile&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;SPC+p+p&lt;/li&gt;
&lt;li&gt;SPC+p+f&lt;/li&gt;
&lt;li&gt;SPC+p+t&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="search-spcs"&gt;Search: SPC+s&lt;/h2&gt;
&lt;p&gt;ripgrep&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;ul&gt;
&lt;li&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SPC+*&lt;/li&gt;
&lt;li&gt;SPC+/&lt;/li&gt;
&lt;li&gt;SPC+s+s&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="navigation"&gt;Navigation:&lt;/h2&gt;
&lt;p&gt;lsp&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Code: gd, SPC+j+i&lt;/li&gt;
&lt;li&gt;Buffer: SPC+b&lt;/li&gt;
&lt;li&gt;Window: SPC+w, ALT+num&lt;/li&gt;
&lt;li&gt;SPC+a&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="editing-vim"&gt;Editing: vim&lt;/h2&gt;
&lt;p&gt;evil&lt;/p&gt;
&lt;h2 id="version-control-magit"&gt;Version Control: magit&lt;/h2&gt;
&lt;h2 id="shell-spc"&gt;Shell: SPC+'&lt;/h2&gt;
&lt;p&gt;eshell&lt;/p&gt;
&lt;h2 id="help-spc-spch"&gt;Help: SPC+?, SPC+h&lt;/h2&gt;</content></item><item><title>Simple: 一个支持中文和拼音搜索的 sqlite fts5插件</title><link>https://www.wangfenjin.com/posts/simple-tokenizer/</link><pubDate>Sun, 08 Mar 2020 00:00:00 +0000</pubDate><guid>https://www.wangfenjin.com/posts/simple-tokenizer/</guid><description>&lt;p&gt;之前的工作关系，需要在手机上支持中文和拼音搜索。由于手机上存储数据一般都是用 sqlite，所以是基于 sqlite3 fts5 来实现。这段时间再次入门 c++，所以想用 c++ 实现一下，一来用于练手，二来当时做的时候发现网络上这方面开源的实现不多，也造福下其他人。&lt;/p&gt;
&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;p&gt;搜索现在几乎是每个 APP 必备的功能，用户已经习惯了搜索框搜一下，避免到处去找。搜索也是帮助用户查找旧信息，发现新功能的一个重要手段。平常我们用微信的时候经常会搜索联系人和聊天记录，发现微信这一块做的还是非常好的。关于微信的全文搜索，可以看看这两篇文章：&lt;a href="https://juejin.im/entry/59e6cd266fb9a0451968ab02"&gt;微信全文搜索优化之路&lt;/a&gt; 和 &lt;a href="https://cloud.tencent.com/developer/article/1198371"&gt;微信移动端的全文检索多音字问题解决方案&lt;/a&gt; 。&lt;/p&gt;
&lt;p&gt;第一篇文章主要是问题和原理的概述，第二篇文章是核心分词器的实现。我写的这个项目主要是实现了 simple 分词器，并提供一些辅助函数帮助使用。&lt;/p&gt;
&lt;h2 id="simple-分词器"&gt;Simple 分词器&lt;/h2&gt;
&lt;p&gt;搜索的核心是建倒排索引，建索引的核心是分词器。 跟名字一下，Simple 分词器的规则非常简单：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;空白符跳过&lt;/li&gt;
&lt;li&gt;连续的数字作为整体是一个索引&lt;/li&gt;
&lt;li&gt;连续的英文字母作为整体并转换成小写索引&lt;/li&gt;
&lt;li&gt;中文字单独建索引，并且把中文字转成拼音后也建搜索，这样就能同时支持中文和拼音检索。另外把拼音首字母也建索引，这样搜索 zjl 就能命中 &amp;ldquo;周杰伦&amp;rdquo;。&lt;/li&gt;
&lt;li&gt;其他字符统一单独建索引，这样搜索 😊 也能搜到&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;上面的 5 条都比较好理解，关于中文为什么这么做（而不是连续的中文一起建索引），是由于客户端搜索的需求决定的。具体可以参考上面微信的两篇文章。&lt;/p&gt;
&lt;p&gt;有了上面的规则，代码写起来就很简单了，&lt;a href="https://github.com/wangfenjin/simple/blob/a9234eb7169d98522ff07f42e0e9f9aa603bbebd/src/simple_tokenizer.cc#L104"&gt;核心逻辑&lt;/a&gt; 30 行就解决了。这块代码运行效率也比较高，一遍扫描 O(n) 的复杂度就完成了分词操作。&lt;/p&gt;
&lt;h2 id="query-拆分"&gt;query 拆分&lt;/h2&gt;
&lt;p&gt;索引建好之后，query 需要根据分词规则来写才能查询到数据。比如根据上面的逻辑：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;如果查数字，我们要把搜索词当作前缀来用，比如用户搜索 123， query 就需要换成 123*，这样如果索引里面有 12345 也能被搜索出来&lt;/li&gt;
&lt;li&gt;对于英文，除了要当作前缀，还需要把搜索词转成小写，比如用护搜索 Hello，query 就需要换成 hello*, 这样如果索引里面有 HelloWorld 也能被命中&lt;/li&gt;
&lt;li&gt;对于中文和其他字符，都要拆成单个的才能命中索引&lt;/li&gt;
&lt;li&gt;最后对于拼音（其实我们没办法区分英文和拼音，统一当作拼音处理就行），需要把拼音按照规则拆分，因为我们的拼音索引是单字建立的。这样如果用户搜索 &amp;ldquo;zhangliangy&amp;rdquo;，拼音就可以被拆成 &amp;lsquo;zhang AND liang AND y*&amp;rsquo;，从而命中&amp;quot;张靓颖&amp;quot;。具体规则微信的文章中也有详述。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;可以看到 query 词重构的逻辑也比较多，在之前的项目中没有好的办法，所以是自己在应用层代码里面组装好了 query 再给 sqlite 去搜的，这样其实不太方便。在这个项目中，我实现了一个 simple_query 的字符串函数，输入一个 string，它会给转换成组装好的搜索词，用法跟使用 sqlite 内置函数一样，这样就方便很多了，下面是一个例子：&lt;/p&gt;</description><content>&lt;p&gt;之前的工作关系，需要在手机上支持中文和拼音搜索。由于手机上存储数据一般都是用 sqlite，所以是基于 sqlite3 fts5 来实现。这段时间再次入门 c++，所以想用 c++ 实现一下，一来用于练手，二来当时做的时候发现网络上这方面开源的实现不多，也造福下其他人。&lt;/p&gt;
&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;p&gt;搜索现在几乎是每个 APP 必备的功能，用户已经习惯了搜索框搜一下，避免到处去找。搜索也是帮助用户查找旧信息，发现新功能的一个重要手段。平常我们用微信的时候经常会搜索联系人和聊天记录，发现微信这一块做的还是非常好的。关于微信的全文搜索，可以看看这两篇文章：&lt;a href="https://juejin.im/entry/59e6cd266fb9a0451968ab02"&gt;微信全文搜索优化之路&lt;/a&gt; 和 &lt;a href="https://cloud.tencent.com/developer/article/1198371"&gt;微信移动端的全文检索多音字问题解决方案&lt;/a&gt; 。&lt;/p&gt;
&lt;p&gt;第一篇文章主要是问题和原理的概述，第二篇文章是核心分词器的实现。我写的这个项目主要是实现了 simple 分词器，并提供一些辅助函数帮助使用。&lt;/p&gt;
&lt;h2 id="simple-分词器"&gt;Simple 分词器&lt;/h2&gt;
&lt;p&gt;搜索的核心是建倒排索引，建索引的核心是分词器。 跟名字一下，Simple 分词器的规则非常简单：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;空白符跳过&lt;/li&gt;
&lt;li&gt;连续的数字作为整体是一个索引&lt;/li&gt;
&lt;li&gt;连续的英文字母作为整体并转换成小写索引&lt;/li&gt;
&lt;li&gt;中文字单独建索引，并且把中文字转成拼音后也建搜索，这样就能同时支持中文和拼音检索。另外把拼音首字母也建索引，这样搜索 zjl 就能命中 &amp;ldquo;周杰伦&amp;rdquo;。&lt;/li&gt;
&lt;li&gt;其他字符统一单独建索引，这样搜索 😊 也能搜到&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;上面的 5 条都比较好理解，关于中文为什么这么做（而不是连续的中文一起建索引），是由于客户端搜索的需求决定的。具体可以参考上面微信的两篇文章。&lt;/p&gt;
&lt;p&gt;有了上面的规则，代码写起来就很简单了，&lt;a href="https://github.com/wangfenjin/simple/blob/a9234eb7169d98522ff07f42e0e9f9aa603bbebd/src/simple_tokenizer.cc#L104"&gt;核心逻辑&lt;/a&gt; 30 行就解决了。这块代码运行效率也比较高，一遍扫描 O(n) 的复杂度就完成了分词操作。&lt;/p&gt;
&lt;h2 id="query-拆分"&gt;query 拆分&lt;/h2&gt;
&lt;p&gt;索引建好之后，query 需要根据分词规则来写才能查询到数据。比如根据上面的逻辑：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;如果查数字，我们要把搜索词当作前缀来用，比如用户搜索 123， query 就需要换成 123*，这样如果索引里面有 12345 也能被搜索出来&lt;/li&gt;
&lt;li&gt;对于英文，除了要当作前缀，还需要把搜索词转成小写，比如用护搜索 Hello，query 就需要换成 hello*, 这样如果索引里面有 HelloWorld 也能被命中&lt;/li&gt;
&lt;li&gt;对于中文和其他字符，都要拆成单个的才能命中索引&lt;/li&gt;
&lt;li&gt;最后对于拼音（其实我们没办法区分英文和拼音，统一当作拼音处理就行），需要把拼音按照规则拆分，因为我们的拼音索引是单字建立的。这样如果用户搜索 &amp;ldquo;zhangliangy&amp;rdquo;，拼音就可以被拆成 &amp;lsquo;zhang AND liang AND y*&amp;rsquo;，从而命中&amp;quot;张靓颖&amp;quot;。具体规则微信的文章中也有详述。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;可以看到 query 词重构的逻辑也比较多，在之前的项目中没有好的办法，所以是自己在应用层代码里面组装好了 query 再给 sqlite 去搜的，这样其实不太方便。在这个项目中，我实现了一个 simple_query 的字符串函数，输入一个 string，它会给转换成组装好的搜索词，用法跟使用 sqlite 内置函数一样，这样就方便很多了，下面是一个例子：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;-- 完整例子：https://github.com/wangfenjin/simple/blob/master/test.sql
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;-- load so file
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.&lt;span style="color:#66d9ef"&gt;load&lt;/span&gt; libsimple.so
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;-- set tokenize to simple
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;CREATE&lt;/span&gt; VIRTUAL &lt;span style="color:#66d9ef"&gt;TABLE&lt;/span&gt; t1 &lt;span style="color:#66d9ef"&gt;USING&lt;/span&gt; fts5(x, tokenize &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;simple&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;-- add some values into the table
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;insert&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;into&lt;/span&gt; t1 &lt;span style="color:#66d9ef"&gt;values&lt;/span&gt; (&lt;span style="color:#e6db74"&gt;&amp;#34;周杰伦 Jay Chou:最美的不是下雨天，是曾与你躲过雨的屋檐&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;-- query result: [周杰伦] Jay Chou:最美的不是下雨天，是曾与你躲过雨的屋檐
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;select&lt;/span&gt; simple_highlight(t1, &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;[&amp;#39;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;]&amp;#39;&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;from&lt;/span&gt; t1 &lt;span style="color:#66d9ef"&gt;where&lt;/span&gt; x &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt; simple_query(&lt;span style="color:#e6db74"&gt;&amp;#39;zhoujiel&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以看到， match 后面用 simple_query 这个函数，传入用户输入的搜索词就可以用了。&lt;/p&gt;
&lt;p&gt;另外 sql 中还有一个 simple_highlight 函数，它的作用和内置的 highlight 函数一样，只是它会把连续命中的词一起高亮。比如对于文档&amp;quot;周杰伦&amp;quot;，如果搜索词是 &amp;lsquo;zhou AND jie&amp;rsquo;，那么 highlight 函数会返回 &amp;ldquo;[周][杰]伦&amp;rdquo;，simple_highlight 会返回 &amp;ldquo;[周杰]伦&amp;rdquo;。&lt;/p&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;最后说几句关于 sqlite fts5 的使用的问题。个人建议通过 trigger 的方式来维护索引的这张表，具体使用的方式可以在官方文章中搜索 trigger 找到例子。这样使用的好处是没有复杂的逻辑去保证文档数据和索引数据一致，微信的文章中很大一部分复杂度在描述怎么保证数据一致的问题。他们可能有自己的业务复杂性，但是对于一般的场景来说， trigger 是最好的方式。&lt;/p&gt;
&lt;p&gt;从这个项目我们能学到：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;怎么给 sqlite3 做一个支持中文和拼音的 fts5 拓展&lt;/li&gt;
&lt;li&gt;怎么给 sqlite3 添加用户自定义的函数&lt;/li&gt;
&lt;li&gt;在一个项目中同时使用 c 和 c++ ，并合理处理边界问题&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;大家可以下载使用，也可以根据自己的需求去改进，定制更多的函数和策略。&lt;/p&gt;
&lt;h2 id="reference"&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Simple 分词器: &lt;a href="https://github.com/wangfenjin/simple"&gt;https://github.com/wangfenjin/simple&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;sqlite 官方文档：&lt;a href="https://www.sqlite.org/fts5.html"&gt;https://www.sqlite.org/fts5.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;微信全文搜索优化之路：&lt;a href="https://juejin.im/entry/59e6cd266fb9a0451968ab02"&gt;https://juejin.im/entry/59e6cd266fb9a0451968ab02&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;微信移动端的全文检索多音字问题解决方案：&lt;a href="https://cloud.tencent.com/developer/article/1198371"&gt;https://cloud.tencent.com/developer/article/1198371&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content></item><item><title>About</title><link>https://www.wangfenjin.com/about/</link><pubDate>Sun, 01 Mar 2020 00:00:00 +0000</pubDate><guid>https://www.wangfenjin.com/about/</guid><description>&lt;h1 id="hi-there"&gt;Hi there&lt;/h1&gt;
&lt;p&gt;github: &lt;a href="https://github.com/wangfenjin"&gt;https://github.com/wangfenjin&lt;/a&gt;&lt;/p&gt;</description><content>&lt;h1 id="hi-there"&gt;Hi there&lt;/h1&gt;
&lt;p&gt;github: &lt;a href="https://github.com/wangfenjin"&gt;https://github.com/wangfenjin&lt;/a&gt;&lt;/p&gt;</content></item><item><title>Showcase</title><link>https://www.wangfenjin.com/posts/my-first-post/</link><pubDate>Wed, 18 Jul 2018 00:00:00 +0000</pubDate><guid>https://www.wangfenjin.com/posts/my-first-post/</guid><description>&lt;h2 id="header-2"&gt;Header 2&lt;/h2&gt;
&lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam nec interdum metus. Aenean rutrum ligula sodales ex auctor, sed tempus dui mollis. Curabitur ipsum dui, aliquet nec commodo at, tristique eget ante. &lt;strong&gt;Donec quis dolor nec nunc mollis interdum vel in purus&lt;/strong&gt;. Sed vitae leo scelerisque, sollicitudin elit sed, congue ante. In augue nisl, vestibulum commodo est a, tristique porttitor est. Proin laoreet iaculis ornare. Nullam ut neque quam.&lt;/p&gt;</description><content>&lt;h2 id="header-2"&gt;Header 2&lt;/h2&gt;
&lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam nec interdum metus. Aenean rutrum ligula sodales ex auctor, sed tempus dui mollis. Curabitur ipsum dui, aliquet nec commodo at, tristique eget ante. &lt;strong&gt;Donec quis dolor nec nunc mollis interdum vel in purus&lt;/strong&gt;. Sed vitae leo scelerisque, sollicitudin elit sed, congue ante. In augue nisl, vestibulum commodo est a, tristique porttitor est. Proin laoreet iaculis ornare. Nullam ut neque quam.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Fusce pharetra suscipit orci nec tempor. Quisque vitae sem sit amet sem mollis consequat. Sed at imperdiet lorem. Vestibulum pharetra faucibus odio, ac feugiat tellus sollicitudin at. Pellentesque varius tristique mi imperdiet dapibus. Duis orci odio, sodales lacinia venenatis sit amet, feugiat et diam.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="header-3"&gt;Header 3&lt;/h3&gt;
&lt;p&gt;Nulla libero turpis, lacinia vitae cursus ut, auctor dictum nisl. Fusce varius felis nec sem ullamcorper, at convallis nisi vestibulum. Duis risus odio, porta sit amet placerat mollis, tincidunt non mauris. Suspendisse fringilla, &lt;code&gt;odio a dignissim pharetra&lt;/code&gt;, est urna sollicitudin urna, eu scelerisque magna ex vitae tellus.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-css" data-lang="css"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/* PostCSS code */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;pre&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;background&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;#1a1a1d&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;padding&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;20&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;px&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;border-radius&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;8&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;px&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;font-size&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;rem&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;overflow&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;auto&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;@media&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;(--phone)&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;white-space&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;pre-wrap&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;word-wrap&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;break-word&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;code&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;background&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;none&lt;/span&gt; &lt;span style="color:#75715e"&gt;!important&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;color&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;#ccc&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;padding&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;font-size&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;inherit&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// JS code
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;menuTrigger&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; document.&lt;span style="color:#a6e22e"&gt;querySelector&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;.menu-trigger&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;menu&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; document.&lt;span style="color:#a6e22e"&gt;querySelector&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;.menu&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;mobileQuery&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;getComputedStyle&lt;/span&gt;(document.&lt;span style="color:#a6e22e"&gt;body&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;getPropertyValue&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;--phoneWidth&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;isMobile&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; () =&amp;gt; window.&lt;span style="color:#a6e22e"&gt;matchMedia&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;mobileQuery&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;matches&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;isMobileMenu&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; () =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;menuTrigger&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;classList&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;toggle&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;hidden&amp;#39;&lt;/span&gt;, &lt;span style="color:#f92672"&gt;!&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;isMobile&lt;/span&gt;())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;menu&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;classList&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;toggle&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;hidden&amp;#39;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;isMobile&lt;/span&gt;())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;isMobileMenu&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;menuTrigger&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;addEventListener&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;click&amp;#39;&lt;/span&gt;, () =&amp;gt; &lt;span style="color:#a6e22e"&gt;menu&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;classList&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;toggle&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;hidden&amp;#39;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;window.&lt;span style="color:#a6e22e"&gt;addEventListener&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;resize&amp;#39;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;isMobileMenu&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;&amp;lt;!-- HTML code --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;&lt;span style="color:#f92672"&gt;section&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;main&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;h1&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;title&amp;#34;&lt;/span&gt;&amp;gt;{{ .Title }}&amp;lt;/&lt;span style="color:#f92672"&gt;h1&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {{ range .Pages }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {{ .Render &amp;#34;summary&amp;#34;}}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {{ end }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/&lt;span style="color:#f92672"&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/&lt;span style="color:#f92672"&gt;section&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="header-4"&gt;Header 4&lt;/h4&gt;
&lt;p&gt;Curabitur scelerisque felis viverra varius scelerisque. Ut enim libero, molestie gravida blandit at, mollis ornare tellus. Cras arcu mi, ultrices vel pulvinar vel, volutpat eu tortor. Nullam nec eros quis massa ultrices iaculis sed in metus. Praesent sollicitudin sem sit amet orci tempor gravida.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Maecenas elementum vitae nibh vitae porttitor.&lt;/li&gt;
&lt;li&gt;Aenean consequat, risus ut cursus placerat, arcu nulla sodales risus, ut molestie tellus tellus et dui.&lt;/li&gt;
&lt;li&gt;Integer imperdiet turpis vitae lacus imperdiet, ut ornare ligula auctor. Integer in mi eu velit vehicula suscipit eget vulputate nulla.&lt;/li&gt;
&lt;li&gt;Etiam vitae enim quis velit lobortis placerat a ut sem.
&lt;ul&gt;
&lt;li&gt;Curabitur lobortis ante sit amet orci pulvinar, sollicitudin viverra nunc accumsan.&lt;/li&gt;
&lt;li&gt;Praesent fermentum orci quis leo facilisis posuere.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Aliquam erat volutpat. In hac habitasse platea dictumst. Nunc ut tincidunt mauris. Sed at gravida risus, id semper magna. Nullam vitae enim mattis, sodales neque non, pharetra elit. Cras sit amet sagittis augue, et finibus turpis. Ut tempus tincidunt diam vel pharetra. Nulla porttitor odio sit amet nulla scelerisque, quis aliquam mi imperdiet. Sed tincidunt dui vel tellus vestibulum rhoncus. Donec tempus ultrices velit.&lt;/p&gt;</content></item></channel></rss>