我有一个Haskell应用程序,作为许多步骤之一,需要在数据库中存储和检索原始二进制Blob数据。我并不是完全决定将数据存储在纯磁盘文件中,但这确实开始导致另一轮权限问题,所以现在我想使用数据库。
我创建了一个带有bytea
类型的列的表。
我的内存中有一个惰性字节串。
当我这样打电话时
run conn "INSERT INTO documents VALUES (?)" [toSql $ rawData mydoc]
postgres对数据有点生气。确切的错误消息是
invalid byte sequence for encoding \"UTF8\": 0xcf72
我也毫无疑问地知道我在数据流中有NUL值。因此,考虑到所有这些,安全地编码数据以便插入的正确方法是什么?
更新
这是我的 table 的描述
db=> \d+ documents
Table "public.documents"
Column | Type | Modifiers | Storage | Description
-----------------+-----------------------------+-----------+----------+-------------
id | character varying(16) | not null | extended |
importtime | timestamp without time zone | not null | plain |
filename | character varying(255) | not null | extended |
data | bytea | not null | extended |
recordcount | integer | not null | plain |
parsesuccessful | boolean | not null | plain |
Indexes:
"documents_pkey" PRIMARY KEY, btree (id)
这是一个模块的全文,它演示了添加jamsdidh的代码后出现的当前问题。我的错误消息已从上面的编码问题更改为“类型为bytea的无效输入语法”。
module DBMTest where
import qualified Data.Time.Clock as Clock
import Database.HDBC.PostgreSQL
import Database.HDBC
import Data.ByteString.Internal
import Data.ByteString hiding (map)
import Data.Char
import Data.Word8
import Numeric
exampleData = pack ([0..65536] :: [Word8]) :: ByteString
safeEncode :: ByteString -> ByteString
safeEncode x = pack (convert' =<< unpack x)
where
convert' :: Word8 -> [Word8]
convert' 92 = [92, 92]
convert' x | x >= 32 && x < 128 = [x]
convert' x = 92:map c2w (showIntAtBase 8 intToDigit x "")
runTest = do
conn <- connectPostgreSQL "dbname=db"
t <- Clock.getCurrentTime
withTransaction conn
(\conn -> run conn
"INSERT INTO documents (id, importTime, filename, data, recordCount, parseSuccessful) VALUES (?, ?, ?, ?, ?, ?)"
[toSql (15 :: Int),
toSql t,
toSql ("Demonstration data" :: String),
toSql $ safeEncode exampleData,
toSql (15 :: Int),
toSql (True :: Bool)])
最佳答案
我相信这是HDBC-postgresql中的错误。我可以解释为什么会这样,并为您提供一个经过整理和测试的变通方法。
我希望HDBC-postgresql将字节字符串转换为要插入的适当格式,但是您可以快速验证它是否期望字节字符串保存数据的八进制退格转义值。例如,
run conn "INSERT INTO documents VALUES (?)" [toSql $ B.pack [92, 0x31, 0x30, 0x31]]
将单个字符“A”插入数据库!仅当您意识到[92、0x31、0x30、0x31]是“\101”的ascii表示,而“\101”是“A”的八进制表示时,这才有意义。由于八进制的退格字符串保证直接传递32-127范围内的值(有关详细信息,请参阅注释中提供的Richard Huxton链接),因此,插入查询对于标准英文文本而言确实可以正常工作,可能会被忽略...
run conn "INSERT INTO documents VALUES (?)" [toSql $ B.pack [65]]
还插入“A”。不能保证大于127的值会起作用,并且会根据所使用的字符编码进行解释。如果查看HDBC-postgresql代码或查询日志,可以看到它正在将变量“client_encoding”设置为utf8。因此,从字节串输入的数据应被认为是有效的utf8,并在看到一个不能作为utf8字符存在的序列时提示。
正确的解决方法是等待HDBC-postgresql专家修复该错误,但与此同时,您可以将此代码用作解决方法...。
import Data.ByteString.Internal
import Data.Char
import Data.Word8
import Numeric
import Text.Printf
convert::B.ByteString->B.ByteString
convert x = B.pack (convert' =<< B.unpack x)
where
convert'::Word8->[Word8]
convert' 92 = [92, 92]
convert' x | x >= 32 && x < 128 = [x]
convert' x = 92:map c2w (printf "%03o" x)
现在您可以使用
run conn "INSERT INTO documents VALUES (?)" [toSql $ convert $ rawData mydoc]
关于postgresql - 如何通过HDBC将二进制数据放入postgres?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/20506922/