使用SQLite zeroblob作为仅附加文本缓冲区?

ikfrs5lh  于 2023-10-23  发布在  SQLite
关注(0)|答案(1)|浏览(121)

我试图构建一个用于在SQLite中编辑文本的piece表(通过Tcl),需要一个仅附加文本缓冲区。(我今天早些时候问了一个相关的问题,在意识到我只是非常愚蠢的未能正确编码写入SQLite的文本或在blob通道本身设置编码后删除了它;所以,如果我惹恼了18个好心读到那个长问题的人中的任何一个,我道歉。)
看来,这样做的方法是使用增量blob I/O;所以,我添加了一个固定大小的zeroblob,打开了blob通道,开始向它写入文本。它终于可以处理多字节字符了。
在某些时候,缓冲区的段将不得不被读取并与来自另一个缓冲区的段一起沿着连接到一个新文档中。缓冲区本身是以二进制utf-8和一个片段表,其中每行都有一个起始位置和长度,目前都是以字符而不是字节为单位,因为片段表也与文档相关,所有文档事件都与字符位置和长度相关。(这里我不会让你感到厌烦,但是如果没有跟踪字符的位置和长度,就无法确定哪些片段受到新事件的影响;至少我不知道它怎么能。

sqlite> select * from pieces;
id  buffer  start  len
--  ------  -----  ---
1     o     0      5  
2     a     68     10 
3     o     28     13

我的问题有两部分。
1.使用blob来保存文本作为仅附加缓冲区是一种合理的方法吗?SQLite中有没有更好的选择?
我的计划是,在多次翻转之后,只跟踪piece表中的字符值,因为这样可以使写操作更快,并保持piece表更小(列而不是行)。当缓冲区被“卷起”时,发生两件事。一个是它从幸存的段中创建一个新的缓冲区;并从这些片段创建新的文本文档。我想,根据起始字节和字节长度从缓冲区中提取这些段会很有效。但是blob可以作为文本读入Tcl中,而字符串则可以由片段表的字符起始值和长度值构造(请参见下面的方法1)。然后,字符串可以以二进制形式写入SQLite。或者,可以将blob转换为文本,并在SQLite中使用substr()连接片段并将其转换回blob。(请参见下面的方法3)。
1.第二个问题是,哪个更好:在每个写事件中跟踪字符和字节的开始和长度,还是在汇总时进行这些转换?而且,如果后者更可取,是将blob读入Tcl还是将其转换为SQLite中的文本更好?
我知道“更好”的问题已经结束了,但我所说的更好是指在Tcl中将blob阅读到内存中并循环通过它将片段追加到列表中并最终连接它们方面最有效的,而不是将blob转换为文本并在SQL中使用substr()group_concat()做同样的事情。在Tcl中可能有另一个循环选项,在开始字节上使用chan seek,在字符长度上使用chan read numChars(请参见不完整的方法2)。在这种情况下,可能只需要跟踪开始字节而不是字节长度;但是寻找和阅读与其他方法相比有多有效呢?
也许,跟踪字节和字符不是问题。字节位置是blob的末尾,因为这总是一个只附加缓冲区;而且,根据SQLite文档,blob不必像字符串那样被读入内存来确定它的长度。(编辑:先前的陈述是不准确的,因为尽管关于斑点长度本身是真的,但是zeroblob的长度将始终是原始大小;因此,blob的长度不是最后一次写入的结束位置。)并且一个块的字节长度是[string length [encoding convertto utf-8 $string]]
有几个项目需要考虑。需要存储的数据更多,哪些操作需要最快,以及在SQLite中执行工作(我怀疑它是在C代码的较低级别上完成的)是否会最高效。所有这些都超出了我的知识和经验,除了构建它的三种方式,并看到它是如何工作的。
感谢您考虑我的问题,我将感谢您提供的任何指导。

require sqlite3
set dbname blobs
if { [catch {sqlite3 db $dbname} result] } {
  chan puts stdout "result: $result"
  exit
}
chan puts stdout "Successfully opened database named '${dbname}'."
chan puts stdout "Version is [ db version ]."

db eval {
   create table if not exists textblobs (
      id integer primary key,
      textblob blob
   );
   insert into textblobs values(5, zeroblob(2000));
   create table pieces (
      id integer primary key,
      start integer,
      len integer
   );
   insert into pieces values
      (1,0,5),
      (2,61,10),
      (3,28,13)
   ;
}
set fdBlob [db incrblob textblobs textblob 5]
chan configure $fdBlob -translation binary -encoding utf-8 -buffering none

set testStr {--inserted Hebrew בְּרֵאשִׁ֖ית of char length 50--}
# NOTE These would be four different
# events passed to this code.
chan puts -nonewline $fdBlob "Hello there"
chan puts -nonewline $fdBlob $testStr
chan puts -nonewline $fdBlob ", friends"
chan puts -nonewline $fdBlob "!"

chan puts stdout "\$testStr: $testStr"
chan puts stdout "\[string length \$testStr\]: [string length $testStr]"
chan puts stdout "\[string length \[encoding convertto utf-8 \$testStr]\]:\
    [string length [encoding convertto utf-8 $testStr]]"

chan puts "\[chan seek \$fdBlob 0 start\]: [chan seek $fdBlob 0 start]"
chan puts stdout "copy \$fdBlob stdout -size 2000 written below"
chan copy $fdBlob stdout -size 2000
chan puts stdout ""

chan puts "\[chan seek \$fdBlob 0 start\]: [chan seek $fdBlob 0 start]"
set readblob [chan read $fdBlob]
chan puts stdout "\[chan read \$fdBlob\]: $readblob"
#chan puts stdout "\[encoding convertfrom utf-8 \$readblob\]:\
   [encoding convertfrom utf-8 $readblob]"

chan puts stdout "\[string length \$readblob\]: [string length $readblob]"
#chan puts stdout "\[string bytelength \$readblob\]:\
   [string bytelength $readblob]"

# Method 1
# Result of query is {0 5 61 10 28 13}.
set SQL {\
select
  group_concat(start || ' ' || len, ' ') as pcMap
from pieces
order by id;\
}

db eval $SQL {
  foreach {start len} $pcMap {
    lappend pieces [string range $readblob $start [expr {$start + $len - 1}]]
  }
}
chan puts "Tcl pieces: [join $pieces {}]"

# Method 2
# Does not work currently because chan seek
# is based on bytes not characters; but could
# build the piece table map based on start
# byte and character length, and loop in Tcl
# similar to Method 1, chan seeling to the 
# start byte and chan reading the char length.
chan seek $fdBlob 62 start
chan puts stdout "chan read of 10 characters from seek 62:\
   [chan read $fdBlob 10]"

# Method 3
# Could also build piece table based on bytes
# because substr works on blobs, the difference
# being start and length refer to bytes rather
# than characters; and would nt need to cast
# blobs as text. The issue may be that cannot
# search blobs when want to search this text;
# or, perhaps, the search terms can be converted
# from text to binary and search the blobs.
set SQL {\
select group_concat(piece,'') as pieces
from
 (
  select substr(cast (textblob as text), start+1, len) as piece
  from textblobs, pieces
 );\
}
db eval $SQL {
  chan puts stdout "SQL pieces: $pieces"
}
chan close $fdBlob
db close;

结果如下:

Successfully opened database named 'blobs'.
Version is 3.42.0.
id: 5, textblob: 
$testStr: --inserted Hebrew בְּרֵאשִׁ֖ית of char length 50--
[string length $testStr]: 50
[string length [encoding convertto utf-8 $testStr]]: 62
[chan seek $fdBlob 0 start]: 
copy $fdBlob stdout -size 2000 written below
Hello there--inserted Hebrew בְּרֵאשִׁ֖ית of char length 50--, friends!
[chan seek $fdBlob 0 start]: 
[chan read $fdBlob]: Hello there--inserted Hebrew בְּרֵאשִׁ֖ית of char length 50--, friends!
[string length $readblob]: 1988
Tcl pieces: Hello, friends! בְּרֵאשִׁ֖ית
chan read of 10 characters from seek 62: length 50-
SQL pieces: Hello, friends! בְּרֵאשִׁ֖ית
r7knjye2

r7knjye21#

1.使用blob来保存文本作为仅附加缓冲区是一种合理的方法吗?SQLite中有没有更好的选择?
如果你想让SQLite以某种方式索引你的字符串(例如,用全文搜索引擎),把它放在blob中是没有帮助的。如果在将字符串写入blob之前,您可以知道整个字符串,那么最好这样做;在大多数现代计算机上,长达100 MB的字符串不会有丝毫的压力。这是相当多的文本,它是每字符串/blob。
1.哪个更好:在每个写事件中跟踪字符和字节的开始和长度,还是在汇总时进行这些转换?而且,如果后者更可取,是将blob读入Tcl还是将其转换为SQLite中的文本更好?
你有数据库列,你可以在其中保存任何你能找到帮助的东西。如果有时你需要字节范围,有时你需要字符范围,请保留两者。它在构建时会增加一些复杂性和时间成本,但只要您想执行比写更多的查询,让写承担成本是有意义的。
但是,请检查是否需要在内置的全文搜索扩展虚拟表提供的功能之外重新发明自己的索引方案。(不知道他们如何处理希伯来文。

相关问题