【问题标题】:How do a put binary data into postgres through HDBC?如何通过 HDBC 将二进制数据放入 postgres?
【发布时间】:2013-12-18 03:14:47
【问题描述】:

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

我创建了一个包含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)])

【问题讨论】:

  • 我从未使用过 HDBC,但我使用 postgresql-simple 取得了成功? (虽然我还没有尝试过使用 bytea 数据。)
  • 到目前为止,没有。我已经有很多代码并且已经掌握了这个 API。我现在负担不起转换的费用​​。
  • 在 PostgreSQL 端开启查询日志,看看数据是如何被转义的。它应该是八进制(旧格式)或十六进制(新格式)。 postgresql.org/docs/current/static/datatype-binary.html#AEN5318
  • 您使用的是 HDBC-postgresql 还是 HDBC-odbc?我建议您尝试使用 HDBC-odbc 来使用 postgresql,因为绑定更发达(更快)。
  • 我相信这也是 postgresql-simple 中的一个问题。至少我得到了与我认为本质上相同的代码相同的错误。

标签: postgresql haskell hdbc


【解决方案1】:

我相信这是 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 范围内的值(有关详细信息,请参阅 cmets 中提供的链接 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]

【讨论】:

  • 我已经向 hdbc-postgresql 提交了一个错误报告,如果我认为这是一个错误,他们很快就会纠正我!见github.com/hdbc/hdbc-postgresql/issues/33
  • 我已经实现了这一点,不同之处在于我的 ByteString 是一个惰性字节串。现在我得到 postgres 告诉我,我为类型 bytea 提供了无效的输入语法。查询日志中没有其他信息,即使我已将日志记录设置为“全部”。虽然我也将日志记录设置为 syslog,但在这种情况下它发送的数据可能较少。
  • @SavanniD'Gerinel- 你能用严格的字节串让它工作吗?如果我知道这一点,调试会更容易......
  • 不,我不能。我添加了大量示例代码来说明我在做什么。如果你愿意,我还可以添加库版本。
  • @SavanniD'Gerinel- 我的代码中有一个错误,并修复了它....showIntAtBase 8 intToDigit x "" 不添加前导零,因此低于 32 的字节被破坏(记住,32-无论如何,127只是通过了)。此外,在您的示例中,您使用了[1..65536]::[Word8]。这不会像你想象的那样做。请记住,Word8 只从 0 到 255,而奇怪的是 Haskell 只是截断大于 255 的数字的转换而不是抱怨,所以([0..65536]::[Word8]) = ([0..0]::[Word8]) = [0]。如果要测试全范围的字符,请使用[0..255]::[Word8]
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-07-24
  • 2011-12-13
  • 2013-01-19
  • 1970-01-01
  • 2012-07-23
  • 1970-01-01
  • 2012-01-02
相关资源
最近更新 更多