-- | Read and write stuff in hstore columns.
--
-- Insert crap into table:
--
-- @
-- let myStuff = [(\"test\", \"shmest\"), (\"spam\", \"eggs\"), (\"sausage\", \"salad\")]
-- withTransaction conn $ \trans -> run trans (\"INSERT INTO test_hstore (stuff) VALUES (\" ++ hsQuery myStuff ++ \");\") (hsParams myStuff)
-- @
--
-- Parse hstore data:
--
-- @
-- rows <- quickQuery conn \"SELECT * FROM test_hstore;\" []
-- forM_ rows $ \[pk, hstuff] -> print $ (fromSql pk :: Integer, hsParse hstuff)
-- @
--
module Database.HDBC.PostgreSQL.HStore where
import Database.HDBC
import Data.Text (Text)
import Data.Text.Encoding (decodeUtf8)
import qualified Data.Text as T
import qualified Data.Attoparsec.Text as AP
import qualified Data.List as L
import qualified Data.Map as M
-- * Make a query
-- | Generate a placeholder string.
hsQuery :: [(String, String)] -> String
hsQuery = L.intercalate " || " . map (\(_, _) -> "hstore(?, ?)")
-- | Generate a parameter list.
hsParams :: [(String, String)] -> [SqlValue]
hsParams = L.concatMap (\(k, v) -> [toSql k, toSql v])
-- * Parse results
-- | Parse a SqlByteString with hstore data to a Map Text Text.
hsParse :: SqlValue -> M.Map Text Text
hsParse (SqlByteString bs) = case hstoreParser `AP.parseOnly` decodeUtf8 bs of
Left err -> error err
Right val -> val
-- ** Parser internals
-- | Parse hstore-formatted value.
hstoreParser :: AP.Parser (M.Map Text Text)
hstoreParser = do
pairs <- kvPair `AP.sepBy` AP.string (T.pack ", ")
return $! M.fromList pairs
-- | Parse one key-value pair.
kvPair :: AP.Parser (Text, Text)
kvPair = do
key <- doubleQuoted
AP.string $ T.pack "=>"
value <- doubleQuoted
return (key, value)
-- | Grab a value, unquote, unslash.
doubleQuoted :: AP.Parser Text
doubleQuoted = do
AP.char '"'
str <- AP.scan False $ \s c -> if s then Just False
else if c == '"'
then Nothing
else Just (c == '\\')
AP.char '"'
return $! T.replace (T.pack "\\\"") (T.singleton '"') str