packages feed

selda-postgresql 0.1.7.3 → 0.1.8.0

raw patch · 3 files changed

+204/−163 lines, 3 filesdep +postgresql-binarydep +selda-jsondep +timedep −transformersdep ~basedep ~seldaPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: postgresql-binary, selda-json, time, uuid-types

Dependencies removed: transformers

Dependency ranges changed: base, selda

API changes (from Hackage documentation)

+ Database.Selda.PostgreSQL: data PG
+ Database.Selda.PostgreSQL: instance Database.Selda.JSON.JSONBackend Database.Selda.PostgreSQL.PG
- Database.Selda.PostgreSQL: pgOpen :: (MonadIO m, MonadMask m) => PGConnectInfo -> m SeldaConnection
+ Database.Selda.PostgreSQL: pgOpen :: (MonadIO m, MonadMask m) => PGConnectInfo -> m (SeldaConnection PG)
- Database.Selda.PostgreSQL: pgOpen' :: (MonadIO m, MonadMask m) => Maybe Text -> ByteString -> m SeldaConnection
+ Database.Selda.PostgreSQL: pgOpen' :: (MonadIO m, MonadMask m) => Maybe Text -> ByteString -> m (SeldaConnection PG)
- Database.Selda.PostgreSQL: seldaClose :: MonadIO m => SeldaConnection -> m ()
+ Database.Selda.PostgreSQL: seldaClose :: MonadIO m => SeldaConnection b -> m ()
- Database.Selda.PostgreSQL: withPostgreSQL :: (MonadIO m, MonadThrow m, MonadMask m) => PGConnectInfo -> SeldaT m a -> m a
+ Database.Selda.PostgreSQL: withPostgreSQL :: (MonadIO m, MonadMask m) => PGConnectInfo -> SeldaT PG m a -> m a

Files

selda-postgresql.cabal view
@@ -1,5 +1,5 @@ name:                selda-postgresql-version:             0.1.7.3+version:             0.1.8.0 synopsis:            PostgreSQL backend for the Selda database EDSL. description:         PostgreSQL backend for the Selda database EDSL.                      Requires the PostgreSQL @libpq@ development libraries to be@@ -28,17 +28,18 @@     OverloadedStrings     CPP   build-depends:-      base       >=4.8     && <5-    , bytestring >=0.9     && <0.11-    , exceptions >=0.8     && <0.11-    , selda      >=0.3.0.0 && <0.4-    , text       >=1.0     && <1.3+      base       >=4.9 && <5+    , bytestring >=0.9 && <0.11+    , exceptions >=0.8 && <0.11+    , selda      >=0.4 && <0.5+    , selda-json >=0.1 && <0.2+    , text       >=1.0 && <1.3   if !flag(haste)     build-depends:-      postgresql-libpq >=0.9 && <0.10-  if impl(ghc < 7.11)-    build-depends:-      transformers  >=0.4 && <0.6+        postgresql-binary >=0.12 && <0.13+      , postgresql-libpq  >=0.9  && <0.10+      , time              >=1.5  && <1.10+      , uuid-types        >=1.0  && <1.1   hs-source-dirs:     src   default-language:
src/Database/Selda/PostgreSQL.hs view
@@ -1,27 +1,39 @@ {-# LANGUAGE OverloadedStrings, RecordWildCards, GADTs, CPP #-} -- | PostgreSQL backend for Selda. module Database.Selda.PostgreSQL-  ( PGConnectInfo (..)+  ( PG, PGConnectInfo (..)   , withPostgreSQL, on, auth   , pgOpen, pgOpen', seldaClose   , pgConnString, pgPPConfig   ) where-import qualified Data.ByteString.Char8 as BS-import Data.Dynamic-import Data.Foldable (for_)+#if !MIN_VERSION_base(4, 11, 0) import Data.Monoid+#endif+import Data.ByteString (ByteString) import qualified Data.Text as T-import Data.Text.Encoding-import Database.Selda.Backend-import Control.Monad (forM_, void)+import Database.Selda.Backend hiding (toText)+import Database.Selda.JSON+import Database.Selda.Unsafe as Selda (cast, operator) import Control.Monad.Catch import Control.Monad.IO.Class  #ifndef __HASTE__+import Control.Monad (void)+import qualified Data.ByteString as BS (foldl')+import qualified Data.ByteString.Char8 as BS (pack, unpack)+import Data.Dynamic+import Data.Foldable (for_)+import Data.Text.Encoding import Database.Selda.PostgreSQL.Encoding import Database.PostgreSQL.LibPQ hiding (user, pass, db, host) #endif +data PG++instance JSONBackend PG where+  (~>) = operator "->"+  jsonToText = Selda.cast+ -- | PostgreSQL connection information. data PGConnectInfo = PGConnectInfo   { -- | Host to connect to.@@ -69,7 +81,7 @@ infixl 4 `auth`  -- | Convert `PGConnectInfo` into `ByteString`-pgConnString :: PGConnectInfo -> BS.ByteString+pgConnString :: PGConnectInfo -> ByteString #ifdef __HASTE__ pgConnString PGConnectInfo{..} = error "pgConnString called in JS context" #else@@ -91,8 +103,10 @@ -- | Perform the given computation over a PostgreSQL database. --   The database connection is guaranteed to be closed when the computation --   terminates.-withPostgreSQL :: (MonadIO m, MonadThrow m, MonadMask m)-               => PGConnectInfo -> SeldaT m a -> m a+withPostgreSQL :: (MonadIO m, MonadMask m)+               => PGConnectInfo+               -> SeldaT PG m a+               -> m a #ifdef __HASTE__ withPostgreSQL _ _ = return $ error "withPostgreSQL called in JS context" #else@@ -102,13 +116,18 @@ -- | Open a new PostgreSQL connection. The connection will persist across --   calls to 'runSeldaT', and must be explicitly closed using 'seldaClose' --   when no longer needed.-pgOpen :: (MonadIO m, MonadMask m) => PGConnectInfo -> m SeldaConnection-#ifdef __HASTE__-pgOpen _ = return $ error "pgOpen called in JS context"-#else+pgOpen :: (MonadIO m, MonadMask m) => PGConnectInfo -> m (SeldaConnection PG) pgOpen ci = pgOpen' (pgSchema ci) (pgConnString ci) -pgOpen' :: (MonadIO m, MonadMask m) => Maybe T.Text -> BS.ByteString -> m SeldaConnection+pgPPConfig :: PPConfig+pgOpen' :: (MonadIO m, MonadMask m)+        => Maybe T.Text+        -> ByteString+        -> m (SeldaConnection PG)+#ifdef __HASTE__+pgOpen' _ _ = return $ error "pgOpen' called in JS context"+pgPPConfig = error "pgPPConfig evaluated in JS context"+#else pgOpen' schema connStr =   bracketOnError (liftIO $ connectdb connStr) (liftIO . finish) $ \conn -> do     st <- liftIO $ status conn@@ -129,7 +148,6 @@         [ "unable to connect to postgres server: " ++ show f         ] -pgPPConfig :: PPConfig pgPPConfig = defPPConfig     { ppType = pgColType defPPConfig     , ppTypeHook = pgTypeHook@@ -146,15 +164,19 @@     pgTypeHook :: SqlTypeRep -> [ColAttr] -> (SqlTypeRep -> T.Text) -> T.Text     pgTypeHook ty attrs fun       | isGenericIntPrimaryKey ty attrs = pgColTypePK pgPPConfig TRowID-      | otherwise = fun ty+      | otherwise                       = pgTypeRenameHook fun ty +    pgTypeRenameHook _ TDateTime = "timestamp with time zone"+    pgTypeRenameHook _ TTime     = "time with time zone"+    pgTypeRenameHook f ty        = f ty+     pgColAttrsHook :: SqlTypeRep -> [ColAttr] -> ([ColAttr] -> T.Text) -> T.Text     pgColAttrsHook ty attrs fun-      | isGenericIntPrimaryKey ty attrs = fun [Primary]-      | otherwise = fun $ filter (/= AutoIncrement) attrs+      | isGenericIntPrimaryKey ty attrs = fun [AutoPrimary Strong]+      | otherwise = fun attrs      bigserialQue :: [ColAttr]-    bigserialQue = [Primary,AutoIncrement,Required,Unique]+    bigserialQue = [AutoPrimary Strong, Required]      -- For when we use 'autoPrimaryGen' on 'Int' field     isGenericIntPrimaryKey :: SqlTypeRep -> [ColAttr] -> Bool@@ -162,7 +184,7 @@  -- | Create a `SeldaBackend` for PostgreSQL `Connection` pgBackend :: Connection   -- ^ PostgreSQL connection object.-          -> SeldaBackend+          -> SeldaBackend PG pgBackend c = SeldaBackend   { runStmt         = \q ps -> right <$> pgQueryRunner c False q ps   , runStmtWithPK   = \q ps -> left <$> pgQueryRunner c True q ps@@ -186,7 +208,7 @@ disableFKs c True = do     void $ pgQueryRunner c False "BEGIN TRANSACTION;" []     void $ pgQueryRunner c False create []-    void $ pgQueryRunner c False drop []+    void $ pgQueryRunner c False dropTbl []   where     create = mconcat       [ "create table if not exists __selda_dropped_fks ("@@ -194,7 +216,7 @@       , "        sql text"       , ");"       ]-    drop = mconcat+    dropTbl = mconcat       [ "do $$ declare t record;"       , "begin"       , "    for t in select conrelid::regclass::varchar table_name, conname constraint_name,"@@ -225,20 +247,35 @@       , "end $$;"       ] -pgGetTableInfo :: Connection -> T.Text -> IO [ColumnInfo]+pgGetTableInfo :: Connection -> T.Text -> IO TableInfo pgGetTableInfo c tbl = do     Right (_, vals) <- pgQueryRunner c False tableinfo []     if null vals       then do-        pure []+        pure $ TableInfo [] [] []       else do-        Right (_, [[SqlString pk]]) <- pgQueryRunner c False pkquery []-        Right (_, uniques) <- pgQueryRunner c False uniquequery []+        Right (_, pkInfo) <- pgQueryRunner c False pkquery []+        Right (_, us) <- pgQueryRunner c False uniquequery []+        let uniques = map splitNames us         Right (_, fks) <- pgQueryRunner c False fkquery []         Right (_, ixs) <- pgQueryRunner c False ixquery []-        mapM (describe pk fks (map toText ixs) (map toText uniques)) vals+        colInfos <- mapM (describe fks (map toText ixs)) vals+        x <- pure $ TableInfo+          { tableColumnInfos = colInfos+          , tableUniqueGroups = map (map mkColName) uniques+          , tablePrimaryKey = [mkColName pk | [SqlString pk] <- pkInfo]+          }+        pure x   where+    splitNames = breakNames . toText+    -- TODO: this is super ugly; should really be fixed+    breakNames s =+      case T.break (== '"') s of+        (n, ns) | T.null n  -> []+                | T.null ns -> [n]+                | otherwise -> n : breakNames (T.tail ns)     toText [SqlString s] = s+    toText _             = error "toText: unreachable"     tableinfo = mconcat       [ "SELECT column_name, data_type, is_nullable "       , "FROM information_schema.columns "@@ -249,16 +286,18 @@       , "FROM pg_index i "       , "JOIN pg_attribute a ON a.attrelid = i.indrelid "       , "  AND a.attnum = ANY(i.indkey) "-      , "WHERE i.indrelid = '", tbl, "'::regclass "+      , "WHERE i.indrelid = '\"", tbl, "\"'::regclass "       , "  AND i.indisprimary;"       ]     uniquequery = mconcat-      [ "SELECT a.attname "+      [ "SELECT string_agg(a.attname, '\"') "       , "FROM pg_index i "       , "JOIN pg_attribute a ON a.attrelid = i.indrelid "       , "  AND a.attnum = ANY(i.indkey) "-      , "WHERE i.indrelid = '", tbl, "'::regclass "-      , "  AND i.indisunique;"+      , "WHERE i.indrelid = '\"", tbl, "\"'::regclass "+      , "  AND i.indisunique "+      , "  AND NOT i.indisprimary "+      , "GROUP BY i.indkey;"       ]     fkquery = mconcat       [ "SELECT kcu.column_name, ccu.table_name, ccu.column_name "@@ -278,30 +317,31 @@       , "and a.attrelid = t.oid "       , "and a.attnum = ANY(ix.indkey) "       , "and t.relkind = 'r' "+      , "and not ix.indisunique "+      , "and not ix.indisprimary "+      , "and t.relkind = 'r' "       , "and t.relname = '", tbl , "';"       ]-    describe pk fks ixs us [SqlString name, SqlString ty, SqlString nullable] =+    describe fks ixs [SqlString name, SqlString ty, SqlString nullable] =       return $ ColumnInfo         { colName = mkColName name-        , colType = mkTypeRep (pk == name) ty'-        , colIsPK = pk == name-        , colIsAutoIncrement = ty' == "bigserial"-        , colIsUnique = name `elem` us-        , colIsNullable = readBool (encodeUtf8 (T.toLower nullable))+        , colType = mkTypeRep ty'+        , colIsAutoPrimary = ty' == "bigserial"+        , colIsNullable = readBool nullable         , colHasIndex = name `elem` ixs         , colFKs =-            [ (mkTableName tbl, mkColName col)-            | [SqlString cname, SqlString tbl, SqlString col] <- fks+            [ (mkTableName tblname, mkColName col)+            | [SqlString cname, SqlString tblname, SqlString col] <- fks             , name == cname             ]         }       where ty' = T.toLower ty-    describe _ _ _ _ results =+    describe _ _ results =       throwM $ SqlError $ "bad result from table info query: " ++ show results  pgQueryRunner :: Connection -> Bool -> T.Text -> [Param] -> IO (Either Int (Int, [[SqlValue]])) pgQueryRunner c return_lastid q ps = do-    mres <- execParams c (encodeUtf8 q') [fromSqlValue p | Param p <- ps] Text+    mres <- execParams c (encodeUtf8 q') [fromSqlValue p | Param p <- ps] Binary     unlessError c errmsg mres $ \res -> do       if return_lastid         then Left <$> getLastId res@@ -311,12 +351,12 @@     q' | return_lastid = q <> " RETURNING LASTVAL();"        | otherwise     = q -    getLastId res = (readInt . maybe "0" id) <$> getvalue res 0 0+    getLastId res = (maybe 0 id . fmap readInt) <$> getvalue res 0 0  pgRun :: Connection -> Dynamic -> [Param] -> IO (Int, [[SqlValue]]) pgRun c hdl ps = do     let Just sid = fromDynamic hdl :: Maybe StmtID-    mres <- execPrepared c (BS.pack $ show sid) (map mkParam ps) Text+    mres <- execPrepared c (BS.pack $ show sid) (map mkParam ps) Binary     unlessError c errmsg mres $ getRows   where     errmsg = "error executing prepared statement"@@ -327,16 +367,19 @@ -- | Get all rows from a result. getRows :: Result -> IO (Int, [[SqlValue]]) getRows res = do-  rows <- ntuples res-  cols <- nfields res-  types <- mapM (ftype res) [0..cols-1]-  affected <- cmdTuples res-  result <- mapM (getRow res types cols) [0..rows-1]-  pure $ case affected of-    Just "" -> (0, result)-    Just s  -> (readInt s, result)-    _       -> (0, result)+    rows <- ntuples res+    cols <- nfields res+    types <- mapM (ftype res) [0..cols-1]+    affected <- cmdTuples res+    result <- mapM (getRow res types cols) [0..rows-1]+    pure $ case affected of+      Just "" -> (0, result)+      Just s  -> (bsToPositiveInt s, result)+      _       -> (0, result)+  where+    bsToPositiveInt = BS.foldl' (\a x -> a*10+fromIntegral x-48) 0 + -- | Get all columns for the given row. getRow :: Result -> [Oid] -> Column -> Row -> IO [SqlValue] getRow res types cols row = do@@ -379,21 +422,21 @@     , maybe "" ((": " ++) . BS.unpack) me     ] -mkTypeRep :: Bool -> T.Text ->  Either T.Text SqlTypeRep-mkTypeRep True  "bigint"           = Right TRowID-mkTypeRep True  "bigserial"        = Right TRowID-mkTypeRep True  "int8"             = Right TRowID-mkTypeRep _ispk "int8"             = Right TInt-mkTypeRep _ispk "bigint"           = Right TInt-mkTypeRep _ispk "float8"           = Right TFloat-mkTypeRep _ispk "double precision" = Right TFloat-mkTypeRep _ispk "timestamp"        = Right TDateTime-mkTypeRep _ispk "bytea"            = Right TBlob-mkTypeRep _ispk "text"             = Right TText-mkTypeRep _ispk "boolean"          = Right TBool-mkTypeRep _ispk "date"             = Right TDate-mkTypeRep _ispk "time"             = Right TTime-mkTypeRep _ispk typ                = Left typ+mkTypeRep :: T.Text ->  Either T.Text SqlTypeRep+mkTypeRep "bigserial"                = Right TRowID+mkTypeRep "int8"                     = Right TInt+mkTypeRep "bigint"                   = Right TInt+mkTypeRep "float8"                   = Right TFloat+mkTypeRep "double precision"         = Right TFloat+mkTypeRep "timestamp with time zone" = Right TDateTime+mkTypeRep "bytea"                    = Right TBlob+mkTypeRep "text"                     = Right TText+mkTypeRep "boolean"                  = Right TBool+mkTypeRep "date"                     = Right TDate+mkTypeRep "time with time zone"      = Right TTime+mkTypeRep "uuid"                     = Right TUUID+mkTypeRep "jsonb"                    = Right TJSON+mkTypeRep typ                        = Left typ  -- | Custom column types for postgres. pgColType :: PPConfig -> SqlTypeRep -> T.Text@@ -402,16 +445,18 @@ pgColType _ TFloat    = "FLOAT8" pgColType _ TDateTime = "TIMESTAMP" pgColType _ TBlob     = "BYTEA"+pgColType _ TUUID     = "UUID"+pgColType _ TJSON     = "JSONB" pgColType cfg t       = ppType cfg t  -- | Custom attribute types for postgres. pgColAttr :: ColAttr -> T.Text-pgColAttr Primary       = "PRIMARY KEY"-pgColAttr AutoIncrement = ""-pgColAttr Required      = "NOT NULL"-pgColAttr Optional      = "NULL"-pgColAttr Unique        = "UNIQUE"-pgColAttr (Indexed _)   = ""+pgColAttr Primary         = ""+pgColAttr (AutoPrimary _) = "PRIMARY KEY"+pgColAttr Required        = "NOT NULL"+pgColAttr Optional        = "NULL"+pgColAttr Unique          = "UNIQUE"+pgColAttr (Indexed _)     = ""  -- | Custom column types (primary key position) for postgres. pgColTypePK :: PPConfig -> SqlTypeRep -> T.Text
src/Database/Selda/PostgreSQL/Encoding.hs view
@@ -1,34 +1,36 @@ {-# LANGUAGE GADTs, BangPatterns, OverloadedStrings, CPP #-} -- | Encoding/decoding for PostgreSQL. module Database.Selda.PostgreSQL.Encoding-  ( toSqlValue, fromSqlValue, fromSqlType-  , readInt, readBool+  ( toSqlValue, fromSqlValue, fromSqlType, readInt, readBool   ) where #ifdef __HASTE__ -toSqlValue, fromSqlValue, fromSqlType, readInt :: a+toSqlValue, fromSqlValue, fromSqlType, readInt, readBool :: a toSqlValue = undefined fromSqlValue = undefined fromSqlType = undefined readInt = undefined+readBool = undefined  #else +import Control.Applicative ((<|>)) import qualified Data.ByteString as BS-import Data.ByteString.Builder-import Data.ByteString.Char8 (unpack)-import qualified Data.ByteString.Char8 as BSC (map) import qualified Data.ByteString.Lazy as LBS import Data.Char (toLower)-import qualified Data.Text as Text-import Data.Text.Encoding-import Database.PostgreSQL.LibPQ (Oid (..), Format (..))+import qualified Data.Text as T+import Data.Time (utc, localToUTCTimeOfDay)+import Database.PostgreSQL.LibPQ (Oid (..), Format (Binary)) import Database.Selda.Backend-import Unsafe.Coerce+import PostgreSQL.Binary.Encoding as Enc+import PostgreSQL.Binary.Decoding as Dec+import qualified Data.UUID.Types as UUID (toByteString)+import Data.Int (Int16, Int32, Int64)  -- | OIDs for all types used by Selda. blobType, boolType, intType, int32Type, int16Type, textType, doubleType,-  dateType, timeType, timestampType, nameType, varcharType :: Oid+  dateType, timeType, timestampType, nameType, varcharType, uuidType,+  jsonbType :: Oid boolType      = Oid 16 intType       = Oid 20 int32Type     = Oid 23@@ -37,24 +39,37 @@ nameType      = Oid 19 doubleType    = Oid 701 dateType      = Oid 1082-timeType      = Oid 1083-timestampType = Oid 1114+timeType      = Oid 1266+timestampType = Oid 1184 blobType      = Oid 17 varcharType   = Oid 1043+uuidType      = Oid 2950+jsonbType     = Oid 3802 +bytes :: Enc.Encoding -> BS.ByteString+bytes = Enc.encodingBytes+ -- | Convert a parameter into an postgres parameter triple. fromSqlValue :: Lit a -> Maybe (Oid, BS.ByteString, Format)-fromSqlValue (LBool b)     = Just (boolType, toBS $ if b then word8 1 else word8 0, Binary)-fromSqlValue (LInt n)      = Just (intType, toBS $ int64BE (fromIntegral n), Binary)-fromSqlValue (LDouble f)   = Just (doubleType, toBS $ int64BE (unsafeCoerce f), Binary)-fromSqlValue (LText s)     = Just (textType, encodeUtf8 $ Text.filter (/= '\0') s, Binary)-fromSqlValue (LDateTime s) = Just (timestampType, encodeUtf8 s, Text)-fromSqlValue (LTime s)     = Just (timeType, encodeUtf8 s, Text)-fromSqlValue (LDate s)     = Just (dateType, encodeUtf8 s, Text)-fromSqlValue (LBlob b)     = Just (blobType, b, Binary)+fromSqlValue (LBool b)     = Just (boolType, bytes $ Enc.bool b, Binary)+fromSqlValue (LInt n)      = Just ( intType+                                  , bytes $ Enc.int8_int64 $ fromIntegral n+                                  , Binary)+fromSqlValue (LDouble f)   = Just (doubleType, bytes $ Enc.float8 f, Binary)+fromSqlValue (LText s)     = Just (textType, bytes $ Enc.text_strict s, Binary)+fromSqlValue (LDateTime t) = Just ( timestampType+                                  , bytes $ Enc.timestamptz_int t+                                  , Binary)+fromSqlValue (LTime t)     = Just (timeType, bytes $ Enc.timetz_int (t, utc), Binary)+fromSqlValue (LDate d)     = Just (dateType, bytes $ Enc.date d, Binary)+fromSqlValue (LUUID x)     = Just (uuidType, bytes $ Enc.uuid x, Binary)+fromSqlValue (LBlob b)     = Just (blobType, bytes $ Enc.bytea_strict b, Binary) fromSqlValue (LNull)       = Nothing fromSqlValue (LJust x)     = fromSqlValue x-fromSqlValue (LCustom l)   = fromSqlValue l+fromSqlValue (LCustom TJSON (LBlob b)) = Just ( jsonbType+                                              , bytes $ Enc.jsonb_bytes b+                                              , Binary)+fromSqlValue (LCustom _ l) = fromSqlValue l  -- | Get the corresponding OID for an SQL type representation. fromSqlType :: SqlTypeRep -> Oid@@ -67,71 +82,51 @@ fromSqlType TTime     = timeType fromSqlType TBlob     = blobType fromSqlType TRowID    = intType+fromSqlType TUUID     = uuidType+fromSqlType TJSON     = jsonbType  -- | Convert the given postgres return value and type to an @SqlValue@. toSqlValue :: Oid -> BS.ByteString -> SqlValue toSqlValue t val-  | t == boolType    = SqlBool $ readBool (BSC.map toLower val)-  | t == intType     = SqlInt $ readInt val-  | t == int32Type   = SqlInt $ readInt val-  | t == int16Type   = SqlInt $ readInt val-  | t == doubleType  = SqlFloat $ read (unpack val)-  | t == blobType    = SqlBlob $ pgDecode val-  | t `elem` textish = SqlString (decodeUtf8 val)-  | otherwise        = error $ "BUG: result with unknown type oid: " ++ show t+  | t == boolType      = SqlBool    $ parse Dec.bool val+  | t == intType       = SqlInt     $ fromIntegral $ parse (Dec.int :: Value Int64) val+  | t == int32Type     = SqlInt     $ fromIntegral $ parse (Dec.int :: Value Int32) val+  | t == int16Type     = SqlInt     $ fromIntegral $ parse (Dec.int :: Value Int16) val+  | t == doubleType    = SqlFloat   $ parse Dec.float8 val+  | t == blobType      = SqlBlob    $ parse Dec.bytea_strict val+  | t == uuidType      = SqlBlob    $ uuid2bs $ parse Dec.uuid val+  | t == timestampType = SqlUTCTime $ parse parseTimestamp val+  | t == timeType      = SqlTime    $ toTime $ parse parseTime val+  | t == dateType      = SqlDate    $ parse Dec.date val+  | t == jsonbType     = SqlBlob    $ parse (Dec.jsonb_bytes pure) val+  | t `elem` textish   = SqlString  $ parse Dec.text_strict val+  | otherwise          = error $ "BUG: result with unknown type oid: " ++ show t   where-    -- PostgreSQL hex strings are of the format \xdeadbeefdeadbeefdeadbeef...-    pgDecode s-      | BS.index s 0 == 92 && BS.index s 1 == 120 =-        BS.pack $ go $ BS.drop 2 s-      | otherwise =-        error $ "bad blob string from postgres: " ++ show s-      where-        hex n x =-          case BS.index x n of-            c | c >= 97   -> c - 87 -- c >= 'a'-              | c >= 65   -> c - 55 -- c >= 'A'-              | otherwise -> c - 48 -- c is numeric-        go x-          | BS.length x >= 2 = (16*hex 0 x + (hex 1 x)) : go (BS.drop 2 x)-          | otherwise        = []-    textish = [textType, timestampType, timeType, dateType, nameType, varcharType]+    parseTimestamp = Dec.timestamptz_int <|> Dec.timestamptz_float+    parseTime = Dec.timetz_int <|> Dec.timetz_float+    toTime (tod, tz) = snd $ localToUTCTimeOfDay tz tod+    uuid2bs = LBS.toStrict . UUID.toByteString+    textish = [textType, nameType, varcharType] --- | Attempt to make sense of a bool-ish value.---   Note that values should all be in lowercase.-readBool :: BS.ByteString -> Bool-readBool "f"     = False-readBool "0"     = False-readBool "false" = False-readBool "n"     = False-readBool "no"    = False-readBool "off"   = False-readBool _       = True+parse :: Value a -> BS.ByteString -> a+parse p x =+  case valueParser p x of+    Right x' -> x'+    Left _   -> error "unable to decode value" --- | Read an integer from a strict bytestring.---   Assumes that the bytestring does, in fact, contain an integer.+-- | Read an Int from a binary encoded pgint8. readInt :: BS.ByteString -> Int-readInt s-  | BS.head s == asciiDash = negate $! go 1 0-  | otherwise              = go 0 0-  where-    !len = BS.length s-    !asciiZero = 48-    !asciiDash = 45-    go !i !acc-      | len > i   = go (i+1) (acc * 10 + fromIntegral (BS.index s i - asciiZero))-      | otherwise = acc+readInt = fromIntegral . parse (Dec.int :: Value Int64) --- | Reify a builder to a strict bytestring.-toBS :: Builder -> BS.ByteString-toBS = unChunk . toLazyByteString+readBool :: T.Text -> Bool+readBool = go . T.map toLower+  where+    go "f"     = False+    go "0"     = False+    go "false" = False+    go "n"     = False+    go "no"    = False+    go "off"   = False+    go _       = True --- | Convert a lazy bytestring to a strict one.---   Avoids the copying overhead of 'LBS.toStrict' when there's only a single---   chunk, which should always be the case when serializing single parameters.-unChunk :: LBS.ByteString -> BS.ByteString-unChunk bs =-  case LBS.toChunks bs of-    [bs'] -> bs'-    bss   -> BS.concat bss #endif