This module contains the implementation of the Catalog data types
and functions, and provides the api for the other type checking
modules.

NEW CATALOG!

create new types from scratch
1st version only supports checking type names
fix the read catalog code
continue to ignore schemas
start handling case sensitivity properly
ignore modifiers

= Catalog overview

The main purpose of the catalog is to support typechecking. A
secondary purpose is to be able to support documentation generation.

== What information does the catalog contain?

types:

base types
names of all types
domains
casts
type categories (what is this?)

tables, views, composite types

operators:
  prefix ops
  postfix ops
  binary ops
  regular fns
  aggregate fns
  window fns


triggers

indexes

sequences


> {-# LANGUAGE DeriveDataTypeable,OverloadedStrings #-}
>
> module Database.HsSqlPpp.Internals.Catalog.CatalogInternal
>     (catLookupType
>     ,catLookupTableAndAttrs
>     ,catGetOpsMatchingName
>      -- temp stuff for old typeconversion
>     ,catLookupFns
>     ,catPreferredType
>     ,isOperatorName
>     ,catTypeCategory
>     ,catCast
>     ,catCompositePublicAttrs
>     ,catDomainBaseType
>     ,typeToCatName
>     ) where

>
> --import Control.Monad
> --import Data.List
> --import Data.Data
> --import Data.Char
> import Data.Maybe

> import qualified Data.Map as M
> import qualified Data.Set as S
> import Database.HsSqlPpp.Internals.TypesInternal
> --import Database.HsSqlPpp.Utils.Utils
> import Data.Text (Text)
> import qualified Data.Text as T
> --import qualified Data.Text.Lazy as LT

> import Database.HsSqlPpp.Internals.Catalog.CatalogTypes
> --import Database.HsSqlPpp.Internals.Catalog.BaseCatalog
> import Database.HsSqlPpp.Internals.Catalog.CatalogUtils

-----------------------------------

types:

What information does the catalog store and use on types?

The basic set of types recorded here are:

scalar types - the base types implemented outside of plpgsql

domain types - this is a base type with a constraint (can composite
types be used in a domain?)

enum types - the usual. The values are strings and are case
sensitive. I think you can overload the values so they can be members
of more than one enum type

named composite types - these are structs, created using 'create type
x as', and also a composite type is implicitly created for each table
and view definition with the same name as the table or view

array types - not sure exactly how to handle this, generally, postgres
automatically creates an array type for each scalar, domain, composite
and enum type (any others?), and you can't use arrays of something if
the type of the array isn't in the catalog. So lots of other types
can't be made into arrays (what about arrays of arrays? don't
know). Hssqlppp simply assumes that the array type of any type is
available


None of the types apart from these exist in the same way in postgres -
so unnamed compositetypes, anonymous record type, unknown and pseudo
type are used in less contexts. Maybe one way of looking at them is to
consider them more like type generators which exist simply when they
are used, and don't have to be declared up front. Only the above
privileged types can be used for the types of columns in tables and
views (I think?).

for scalar types, no information is used apart from the fact that a
type with the given name exists and can be referred to.

for domain types, there is also the base type name, and the check
constraint

for enum types, name and the list of labels

for named composite types, you need the name of the type, and the
names and types of the fields

for array types, you only need the base type name, array types don't
have their own separate name

todo: where should this utility live? Probably should be connected
to the dialects

> typeToCatName :: TypeExtra -> Either [TypeError] CatNameExtra
> typeToCatName te = case teType te of
>   ScalarType t -> return
>       $ CatNameExtra t (tePrecision te) (teScale te) (teNullable te)
>   _ -> Left [InternalError "typeToCatName on a non scalar type"]

-----------------------------------------------------------

queries


gets a schema qualified catname, puts in the default 'public' if there
is only one name component. This will be altered when schema search
paths are implemented.

> getCatName2 :: [NameComponent] -> (CatName,CatName)
> -- todo: don't use error here
> getCatName2 [] = error "empty name component in catalog code"
> getCatName2 [a] = ("public",ncStrT a)
> getCatName2 [a,b] = (ncStrT a, ncStrT b)
> getCatName2 (_:xs) = getCatName2 xs


TODO: add inverse of this operation, give a type, returns a typename


> -- | takes a table name, and returns the exact table name (to deal
> -- with quoting), and the public and private attr names
> catLookupTableAndAttrs :: Catalog
>                        -> [NameComponent]
>                        -> Either [TypeError] ((Text,Text),[(Text,TypeExtra)], [(Text,Type)])
> catLookupTableAndAttrs cat nmcs = do
>   let n = getCatName2 nmcs
>   (pu,pv) <- maybe (Left [UnrecognisedRelation n]) Right
>              $ M.lookup n (catTables cat)
>   return (n,pu,pv)



> catGetOpsMatchingName :: Catalog -> [NameComponent] -> [OperatorPrototype]
> catGetOpsMatchingName cat nmcs =
>   let nm = getCatName nmcs
>   in concatMap (\f -> fromMaybe [] $ M.lookup nm $ f cat)
>        [catPrefixOps
>        ,catPostfixOps
>        ,catBinaryOps
>        ,catFunctions
>        ,catAggregateFunctions
>        ,catWindowFunctions]

the TypeConversion module handles checking assignment compatibility,
'resolving result set types', and finding function call matches since
this relies on some heavy algorithms to match postgress really complex
overloading and implicit cast system.


--------------------------------------------------------

old stuff chucked in to support the old typeconversion, to be promoted
to new code or deleted as typeconversion is rewritten



> catCompositePublicAttrs :: Catalog -> [CompositeFlavour] -> Text
>                   -> Either [TypeError] [(Text,TypeExtra)]
> catCompositePublicAttrs cat _flvs ty = do
>    (_,a,_) <- catLookupTableAndAttrs cat [Nmc $ T.unpack ty]
>    return a

> catPreferredType :: Catalog -> Type -> Either [TypeError] Bool
> catPreferredType cat ty =
>   fmap snd $ catGetCategoryInfo cat ty
>
> catCast :: Catalog -> CastContext -> Type -> Type -> Either [TypeError] Bool
> catCast cat ctx from to =
>     case from of
>       t@(DomainType _) -> do
>                 baseType <- catDomainBaseType cat t
>                 cc <- catCast cat ctx baseType to
>                 return $ (baseType == to) ||
>                                (cc || S.member (from, to, ctx) (catCasts cat))
>       _ -> Right $ S.member (from, to, ctx) (catCasts cat)
>
> catDomainBaseType :: Catalog -> Type -> Either [TypeError] Type
> catDomainBaseType cat (ScalarType ty) =
>   case M.lookup ty $ catDomainTypes cat of
>     Just n -> Right $ ScalarType n
>     Nothing -> Left [DomainDefNotFound $ ScalarType ty]
> catDomainBaseType _cat ty = Left [DomainDefNotFound ty]
>
> catLookupFns :: Catalog -> Text -> [OperatorPrototype]
> catLookupFns cat name =
>    catGetOpsMatchingName cat [Nmc $ T.unpack name]

> catTypeCategory :: Catalog -> Type -> Either [TypeError] Text
> catTypeCategory cat ty =
>   fmap fst $ catGetCategoryInfo cat ty

> isOperatorName :: Text -> Bool
> isOperatorName = T.any (`elem` ("+-*/<>=~!@#%^&|`?."::String))

> catGetCategoryInfo :: Catalog -> Type -> Either [TypeError] (Text, Bool)
> catGetCategoryInfo cat ty =
>   case ty of
>     Pseudo (SetOfType _) -> Right ("", False)
>     AnonymousCompositeType _ -> Right ("", False)
>     ArrayType (Pseudo _) -> Right ("A",False)
>     Pseudo _ -> Right ("P",False)
>     _ -> case M.lookup ty $ catTypeCategories cat of
>            Nothing -> Left [InternalError $ "no type category for " ++ show ty]
>            Just x -> Right x