5

多くの手順の 1 つとして、生のバイナリ BLOB データをデータベースに保存および取得する必要がある Haskell アプリケーションがあります。代わりに、そのデータをプレーンディスクファイルに保存することを完全に決定しているわけではありませんが、それにより、追加のアクセス許可の問題が発生し始めるので、今はデータベースを使用したいと考えています.

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 のコードを追加した後に私が抱えている現在の問題を示すモジュールの全文です。私のエラーメッセージは、上記のエンコーディングの問題から「バイトタイプの入力構文が無効です」に変わりました。

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 がバイト文字列を適切な形式に変換して挿入することを期待しますが、バイト文字列が 8 進バックスペースでエスケープされたデータの値を保持することを期待していることをすぐに確認できます。例えば、

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

単一の文字「A」をデータベースに挿入します! これは、[92, 0x31, 0x30, 0x31] が "\101" の ASCII 表現であり、"\101" が 'A' の 8 進数表現であることを理解している場合にのみ意味があります。8 進数のバックスペースでエスケープされた文字列は、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 に答える