Как поместить двоичные данные в postgres через HDBC?
У меня есть приложение на Haskell, которое, как один из многих шагов, должно хранить и извлекать необработанные данные двоичных двоичных объектов в базу данных. Я не совсем выше, решив вместо этого хранить эти данные в виде простых файлов на диске, но это начинает приводить к дополнительному раунду проблем с разрешениями, поэтому сейчас я хочу перейти к базе данных.
Я создал таблицу со столбцом типа 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 ответ
Я считаю, что это ошибка в HDBC-postgresql. Я могу объяснить, почему я так думаю, и могу дать вам обходной путь, который я собрал и протестировал.
Я ожидаю, что HDBC-postgresql преобразует байтовую строку в соответствующий формат для вставки, но вы можете быстро убедиться, что она вместо этого ожидает, что эта байтовая строка будет содержать восьмеричные значения данных в восьмеричной области. Например,
run conn "INSERT INTO documents VALUES (?)" [toSql $ B.pack [92, 0x31, 0x30, 0x31]]
вставляет один символ "А" в базу данных! Это имеет смысл только тогда, когда вы понимаете, что [92, 0x31, 0x30, 0x31] является представлением ascii для "\101", а "\ 101" является восьмеричным представлением для "A". Поскольку строки в восьмеричном обратном пространстве гарантируют возможность прямой передачи значений в диапазоне 32-127 (подробности см. По ссылке, предоставленной Ричардом Хакстоном в комментариях), запрос вставки действительно работает правильно для стандартного английского текста, и мог остаться незамеченным....
run conn "INSERT INTO documents VALUES (?)" [toSql $ B.pack [65]]
также вставляет "А". Значения выше 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]