5

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

我创建了一个包含 type 列的表bytea

我在内存中有一个惰性字节串。

当我这样打电话时

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)])
4

1 回答 1

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。因此,来自字节串的数据应该是有效的 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]
于 2013-12-15T19:42:27.807 回答