You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何利用incrblob根据字符位置确定SQLite BLOB段字节位置并拆分指针?

问题:基于字符位置拆分SQLite BLOB指针的高效实现

场景背景

在Tcl接口中,我使用SQLite的增量BLOB I/O将文本存储为BLOB,数据库中另一张表保存指向该BLOB各段的指针数据行。由于Tcl通道仅支持按字节位置寻址而非字符位置,因此需要为每个指针跟踪byte_start、char_start、byte_length、char_length字段。

现在有UI请求要求将现有指针(仅操作指针,不修改实际缓冲区数据)从某个字符位置拆分为两个指针,而UI完全不了解字节位置和BLOB的细节。为此需要提取对应BLOB段、转换为文本,确定新片段的字节长度,进而计算两个新指针的起始字节和字节长度。

弃用的SQLite方案

我原本计划用SQL语句实现,但根据Dr.Hipp的说明,对BLOB使用substr操作时会将整个BLOB读入内存,因此放弃该方案:

select
   octet_length(
      substr(
         cast(
            substr(
               buffer,
               byte_start,
               byte_length
            ) as text
         ),
         1,
         :len
      )
   )
from
   mem.pt_buffers
where
       doc_id = :doc_id
   and buffer_id = :buffer_id_s
;

初始Tcl实现(可行但流程繁琐)

改用Tcl的incrblob实现,能得到正确结果,但流程冗余——仅为确定拆分点的字节位置就需要提取完整BLOB片段,而片段本身无需修改或其他用途。若UI知晓字节位置,仅需算术计算即可完成。

完整代码如下:

package require sqlite3
sqlite3 db
db eval {create table test (id integer, data blob);}
db eval {insert into test values (1, zeroblob(100));}
puts [db eval {select id, cast(data as text) from test;}]
# 1 {}

# 预先向BLOB写入测试文本(问题场景的前置准备)
set fdBlob [db incrblob main test data 1]
chan configure $fdBlob -translation binary -buffering none
set bindata [encoding convertto utf-8\
     {This is some הַ / נָּבִ֑יא text cast as a BLOB.}]
chan puts -nonewline $fdBlob $bindata

puts [db eval {
   select
      length(data),
      length(cast(data as text)),
      length(:bindata)
   from
      test
   ;
}]
# 100 47 57

# 拆分前的指针数据:
# piece  char_start char_length byte_start byte_length
# -----  ---------- ----------- ---------- -----------
# orig        0          47           0         57

# 请求:将指针在第27个字符位置拆分,得到0-26和27-末尾两个片段

# 读取完整BLOB段并转换为文本(因未知字节拆分点)
chan seek $fdBlob 0 start
set data [encoding convertfrom utf-8 [chan read $fdBlob 57]]

# 拆分文本片段
set frontEnd [string range $data 0 26]
set tailEnd [string range $data 27 end]
puts "\"$frontEnd\" \"$tailEnd\""
# "This is some הַ / נָּבִ֑יא " "text cast as a BLOB."

# 转换回二进制并计算字节长度
set frontEndByteLen [string length [encoding convertto utf-8 $frontEnd]]
set tailEndByteLen [expr {57-$frontEndByteLen}]
puts "Front-end data: byte_start: 0 byte_length $frontEndByteLen"
puts "Tail-end data: byte_start: $frontEndByteLen byte_length $tailEndByteLen"
# Front-end data: byte_start: 0 byte_length 37
# Tail-end data: byte_start: 37 byte_length 20

# 验证拆分结果
chan seek $fdBlob $frontEndByteLen start
set data [encoding convertfrom utf-8 [chan read $fdBlob $tailEndByteLen]]
puts $data
# text cast as a BLOB.

# 最终指针表将插入两个新条目:
# piece  char_start char_length byte_start byte_length
# -----  ---------- ----------- ---------- -----------
# front       0          27           0         37
# tail       27          20          37         20

核心疑问

  1. 当前实现是否过于复杂?有没有更简便的方案?
  2. 是否可以/应该将更多逻辑直接在SQLite中完成?

优化后的Tcl实现

后续发现可以利用Tcl通道的特性简化流程:虽然chan seek按字节偏移操作,但chan read支持按字符读取。只需将通道重新配置为-encoding utf-8,即可直接通过incrblob I/O读取指定字符数的片段,无需读取完整内容再拆分。

优化后的代码:

# Version 2
set frontEnd_byte_start 0
set frontEnd_char_length 27
chan configure $fdBlob -encoding utf-8 -buffering none
chan seek $fdBlob $frontEnd_byte_start start
set frontEndText [chan read $fdBlob $frontEnd_char_length]
set frontEnd_byte_length [string length [encoding convertto utf-8 $frontEndText]]
set tailEnd_byte_start [expr {$frontEnd_byte_start + $frontEnd_byte_length}]
puts "frontEnd text: $frontEndText"
puts "frontEnd byte length: $frontEnd_byte_length"
puts "tailEnd byte start: $tailEnd_byte_start"

chan seek $fdBlob $tailEnd_byte_start start
set data [chan read $fdBlob 20]
puts $data

# 输出结果:
# frontEnd text: This is some הַ / נָּבִ֑יא 
# frontEnd byte length: 37
# tailEnd byte start: 37
# text cast as a BLOB.

内容的提问来源于stack exchange,提问作者Gary

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 00:07:02