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

Sav*_*nel 5 postgresql haskell hdbc

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

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

我在内存中有一个Lazy Bytestring.

当我这样打电话的时候

run conn "INSERT INTO documents VALUES (?)" [toSql $ rawData mydoc]
Run Code Online (Sandbox Code Playgroud)

postgres对数据有点生气.确切的错误消息是

invalid byte sequence for encoding \"UTF8\": 0xcf72
Run Code Online (Sandbox Code Playgroud)

我也毫无疑问地知道我在数据流中有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)
Run Code Online (Sandbox Code Playgroud)

这是一个模块的全文,演示了添加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)])
Run Code Online (Sandbox Code Playgroud)

jam*_*idh 2

我相信这是 HDBC-postgresql 中的一个错误。我可以解释为什么我认为是这样,并且可以为您提供我整理并测试的解决方法。


我希望 HDBC-postgresql 将字节串转换为要插入的适当格式,但您可以快速验证它是否期望字节串保存数据的八进制退格转义值。例如,

run conn "INSERT INTO documents VALUES (?)" [toSql $ B.pack [92, 0x31, 0x30, 0x31]]
Run Code Online (Sandbox Code Playgroud)

将单个字符“A”插入数据库!仅当您意识到 [92, 0x31, 0x30, 0x31] 是“\101”的 ascii 表示,而“\101”是“A”的八进制表示时,这才有意义。因为八进制退格转义字符串保证允许直接传递 32-127 范围内的值(有关详细信息,请参阅注释中提供的链接 Richard Huxton),插入查询实际上对于标准英文文本可以正常工作并且可能会被忽视......

run conn "INSERT INTO documents VALUES (?)" [toSql $ B.pack [65]]
Run Code Online (Sandbox Code Playgroud)

还插入“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 Code Online (Sandbox Code Playgroud)

现在你可以使用

run conn "INSERT INTO documents VALUES (?)" [toSql $ convert $ rawData mydoc]
Run Code Online (Sandbox Code Playgroud)