content: {-# LANGUAGE OverloadedStrings #-}
  
  -- | Utility function for describing a table in the database.
  module Database.PostgreSQL.Describe (
    ColumnInfo(..), describeTable
    ) where
  
  import Control.Monad
  import qualified Data.ByteString as S
  import Data.Int
  import qualified Data.Vector as V
  import qualified Database.PostgreSQL.Simple.TypeInfo.Static as PG
  import Database.PostgreSQL.Simple
  import Database.PostgreSQL.Simple.TypeInfo
  import Database.PostgreSQL.Simple.Types
  
  data ColumnInfo = ColumnInfo {
      colNum :: !Int16
      -- ^ Internal column number used by PostgreSQL.  Generally these
      -- will be consecutive starting from 1, but this may not be the
      -- case if you have altered a table to delete columns.
    , colName :: S.ByteString
      -- ^ Name of the column
    , colType :: !TypeInfo
      -- ^ Type of the column
    , colNotNull :: !Bool
      -- ^ If 'True', the database cannot contain null.  (This
      -- constraint should always be accurate.)
    , colPrimary :: !Bool
      -- ^ 'True' if this column (and only this column) constitutes the
      -- primary key of the table.  Always 'False' if the primary key
      -- comprises multiple columns (even if this is one of those
      -- columns).
    , colUnique :: !Bool
      -- ^ 'True' if there is a uniqueness constraint on this column.
      -- Not 'True' if this column is part of a uniqueness constraint
      -- involving multiple columns.  (Such multi-column uniqueness
      -- constraints are not reported by this interface.)
    , colReferences :: !(Maybe S.ByteString)
      -- ^ If this there is a foreign key constraint on this column (and
      -- the constraint does not span multiple columns), report the
      -- table referenced by this column.
    } deriving (Show)
  
  defColInfo :: ColumnInfo
  defColInfo = ColumnInfo {
      colNum = 0
    , colName = S.empty
    , colType = PG.void
    , colNotNull = False
    , colPrimary = False
    , colUnique = False
    , colReferences = Nothing
    }
  
  -- | Returns a list of 'ColumnInfo' structures for a particular table.
  -- Not all information about a table is returned.  In particular,
  -- constraints that span columns are ignored.
  describeTable :: Connection -> S.ByteString -> IO [ColumnInfo]
  describeTable cn t = do
    [(Only tbloid)] <- query cn "select oid from pg_class where relname = ?"
                       (Only t)
    cs0 <- query cn "select attnum, attname, atttypid, attnotnull\
                    \ from pg_attribute\
                    \ where attrelid = ? and attisdropped = 'f' and attnum > 0\
                    \ order by attnum"
                    (Only (tbloid :: Oid))
    cs1 <- forM cs0 $ \ (num, name, typ, notnull) -> do
      ti <- getTypeInfo cn typ
      return defColInfo {
        colNum = num, colName = name, colType = ti, colNotNull = notnull
      }
    constraints <- query cn "select contype, conkey, relname\
                            \ from pg_constraint left join pg_class\
                            \ on confrelid = pg_class.oid\
                            \ where conrelid = ?"
                            (Only tbloid)
    let _ = constraints :: [(String, V.Vector Int16, Maybe S.ByteString)]
    return $ map (\c -> foldl appConstr c constraints) cs1
      where appConstr ci (ct, ck, mn)
              | V.length ck == 1, colNum ci == ck V.! 0 = appConstr1 ci ct mn
              | otherwise                           = ci
            appConstr1 ci "p" _ = ci { colPrimary = True }
            appConstr1 ci "u" _ = ci { colUnique = True }
            appConstr1 ci "f" n@(Just _) = ci { colReferences = n }
            appConstr1 ci _ _ = ci
  