如何通过HDBC将二进制数据放入postgres?

时间:2013-12-10 22:43:58

标签: postgresql haskell hdbc

我有一个Haskell应用程序,作为许多步骤之一,需要在数据库中存储和检索原始二进制blob数据。我并没有完全决定将这些数据存储在普通磁盘文件中,但这确实会导致额外的一系列权限问题,所以我现在想要使用数据库。

我创建了一个包含bytea类型列的表。

我在内存中有一个Lazy Bytestring。

当我这样打电话时

run conn "INSERT INTO documents VALUES (?)" [toSql $ rawData mydoc]
postgres对数据感到有点生气。确切的错误消息是

invalid byte sequence for encoding \"UTF8\": 0xcf72

我也毫无疑问地知道我在数据流中有NUL值。因此,考虑到所有这些,安全地编码数据以进行插入的正确方法是什么?


更新

以下是我的表格

的说明
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)])

1 个答案:

答案 0 :(得分:2)

我相信这是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。因此,来自bytestring的数据应该是有效的utf8,当它看到一个不能作为utf8字符存在的序列时会抱怨。

正确的解决方法是等待HDBC-postgresql人员修复bug,但与此同时,你可以使用这段代码作为解决方法......

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]