Minha pergunta diz respeito a esse cenário. Estou armazenando texto como um BLOB usando E/S de blob incremental do SQLite na interface Tcl. Outra tabela no banco de dados contém linhas de dados de ponteiro que apontam para segmentos deste BLOB. Como os canais Tcl procuram locais de bytes e não locais de caracteres (o que presumo ser comum, não estou criticando o Tcl), preciso rastrear o byte_start, char_start, byte_length, char_length de cada ponteiro.
Chega uma solicitação ao Tcl que indica que um ponteiro existente (não os dados reais do buffer, mas apenas o próprio ponteiro) precisa ser dividido em dois ponteiros, começando em algum local de caractere. A solicitação vem da UI e esse código não sabe nada sobre a localização dos bytes e realmente nada sobre o BLOB.
Portanto, preciso extrair o segmento do BLOB, convertê-lo em texto e determinar o comprimento de bytes de pelo menos uma das duas novas partes e usar essas informações para determinar o byte inicial e o comprimento de bytes de ambos os novos ponteiros.
Eu usaria algo como abaixo no SQLite, mas, de acordo com o Dr. Hipp, todo o BLOB deve ser lido na memória para usar substr em um 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
;
Portanto, estou usando incrblob em Tcl que este exemplo ilustra. Parece gerar os resultados corretos, mas também parece dar muito trabalho. Por exemplo, o conteúdo está sendo extraído do BLOB apenas para determinar a posição do byte da posição do caractere do ponto de divisão. O conteúdo não é utilizado de outra forma, nem o BLOB está sendo modificado. Se não houvesse o problema de a posição dos bytes ser desconhecida pela UI, o conteúdo não precisaria ser extraído e a operação seria apenas aritmética.
Minhas perguntas são:
- Estou fazendo isso da maneira mais difícil; existe uma abordagem mais simples?
- Pode/deve ser feito mais diretamente no SQLite?
Obrigado por considerar minha pergunta.
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 {}
# Previously, a request had to come in to append this string
# to the BLOB; to the non-zero portion, anyway. This is re-
# quired set-up for the question.
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
# Pre-split pointer data:
# piece char_start char_length byte_start byte_length
# ----- ---------- ----------- ---------- -----------
# orig 0 47 0 57
# Request comes in to split the pointer at the 27th character
# into two pointers: characters 0-26 and 27-end.
# Retrieve the segment of the BLOB. Have to retrieve the full
# piece because do not know where to split the bytes. Convert
# from binary to text. Note [chan read numChars] reads chars,
# but since channel is configured as binary, same as bytes.
chan seek $fdBlob 0 start
set data [encoding convertfrom utf-8 [chan read $fdBlob 57]]
# Split the piece. Need only one or the other, not both.
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."
# Convert the substring back to binary and determine byte length.
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
# Test it out by seeking to the start byte and extracting the tail-end.
chan seek $fdBlob $frontEndByteLen start
set data [encoding convertfrom utf-8 [chan read $fdBlob $tailEndByteLen]]
puts $data
# text cast as a BLOB.
# Then the pointer table will have these new pieces inserted.
# piece char_start char_length byte_start byte_length
# ----- ---------- ----------- ---------- -----------
# front 0 27 0 37
# tail 27 20 37 20
Seu exemplo é como eu abordaria isso, especialmente se o deslocamento do caractere vier de uma UI Tcl. Como o sqlite está em processo, não há nenhuma consideração sobre a latência da rede - é apenas se os cálculos de deslocamento de caracteres estão sendo executados pelo código do sqlite ou pelo Tcl. Tcl é realmente muito bom nisso (gostando muito de strings e tudo mais).
Principalmente se a UI for Tcl, já que os índices de caracteres concordarão em situações como pares substitutos, etc.
Pode ser mais eficiente apenas selecionar o valor do blob, dividir com
string range
comandos e atualizar a linha, mas isso dependerá de detalhes sobre como o encapsulamento do canal do valor db é implementado e qual a complexidade que isso implica. É melhor avaliar seu caso real para saber. Outra consideração é que, emborachan seek
opere em deslocamentos de bytes,chan read
funcione em caracteres, então pode ser mais fácil buscar as peças dessa maneira (principalmente se for a primeira parte que você deseja - basta procurar 0 e ler tantos caracteres)