如何利用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
核心疑问
- 当前实现是否过于复杂?有没有更简便的方案?
- 是否可以/应该将更多逻辑直接在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
相关产品推荐
相关产品推荐

