hssqlppp-0.0.6: Database/HsSqlPpp/TypeChecking/AstUtils.lhs
Copyright 2009 Jake Wheat
This file contains a bunch of small low level utilities to help with
type checking.
> {-# OPTIONS_HADDOCK hide #-}
> module Database.HsSqlPpp.TypeChecking.AstUtils
> (
> OperatorType(..)
> ,getOperatorType
> ,checkTypes
> ,chainTypeCheckFailed
> ,errorToTypeFail
> ,errorToTypeFailF
> ,checkErrorList
> ,getErrors
> ,typeSmallInt,typeBigInt,typeInt,typeNumeric,typeFloat4
> ,typeFloat8,typeVarChar,typeChar,typeBool
> ,canonicalizeTypeName
> ,checkTypeExists
> ,lookupTypeByName
> ,keywordOperatorTypes
> ,specialFunctionTypes
> ,isArrayType
> ,unwrapArray
> ,unwrapSetOfComposite
> ,unwrapSetOf
> ,unwrapComposite
> ,consComposite
> ,unwrapRowCtor
> ) where
> import Data.Maybe
> import Data.List
> import Control.Monad.Error
> import Database.HsSqlPpp.TypeChecking.TypeType
> import Database.HsSqlPpp.TypeChecking.Scope
> import Database.HsSqlPpp.TypeChecking.DefaultScope
================================================================================
= getOperatorType
used by the pretty printer, not sure this is a very good design
for now, assume that all the overloaded operators that have the
same name are all either binary, prefix or postfix, otherwise the
getoperatortype would need the types of the arguments to determine
the operator type, and the parser would have to be a lot cleverer
this is why binary @ operator isn't currently supported
> data OperatorType = BinaryOp | PrefixOp | PostfixOp
> deriving (Eq,Show)
> getOperatorType :: String -> OperatorType
> getOperatorType s = case () of
> _ | any (\(x,_,_) -> x == s) (scopeBinaryOperators defaultScope) ->
> BinaryOp
> | any (\(x,_,_) -> x == s ||
> (x=="-" && s=="u-"))
> (scopePrefixOperators defaultScope) ->
> PrefixOp
> | any (\(x,_,_) -> x == s) (scopePostfixOperators defaultScope) ->
> PostfixOp
> | s `elem` ["!and", "!or","!like"] -> BinaryOp
> | s `elem` ["!not"] -> PrefixOp
> | s `elem` ["!isNull", "!isNotNull"] -> PostfixOp
> | otherwise ->
> error $ "don't know flavour of operator " ++ s
================================================================================
= type checking utils
== checkErrors
if we find a typecheckfailed in the list, then propagate that, else
use the final argument.
> checkTypes :: [Type] -> Either [TypeError] Type -> Either [TypeError] Type
> checkTypes (TypeCheckFailed:_) _ = Right TypeCheckFailed
> checkTypes (_:ts) r = checkTypes ts r
> checkTypes [] r = r
small variant, not sure if both are needed
> chainTypeCheckFailed :: [Type] -> Either a Type -> Either a Type
> chainTypeCheckFailed a b =
> if any (==TypeCheckFailed) a
> then Right TypeCheckFailed
> else b
convert an 'either [typeerror] type' to a type
> errorToTypeFail :: Either [TypeError] Type -> Type
> errorToTypeFail tpe = case tpe of
> Left _ -> TypeCheckFailed
> Right t -> t
convert an 'either [typeerror] x' to a type, using an x->type function
> errorToTypeFailF :: (t -> Type) -> Either [TypeError] t -> Type
> errorToTypeFailF f tpe = case tpe of
> Left _ -> TypeCheckFailed
> Right t -> f t
used to pass a regular type on iff the list of errors is null
> checkErrorList :: [TypeError] -> Type -> Either [TypeError] Type
> checkErrorList es t = if null es
> then Right t
> else Left es
extract errors from an either, gives empty list if right
> getErrors :: Either [TypeError] Type -> [TypeError]
> getErrors e = either id (const []) e
===============================================================================
= basic types
random notes on pg types:
== domains:
the point of domains is you can't put constraints on types, but you
can wrap a type up in a domain and put a constraint on it there.
== literals/selectors
source strings are parsed as unknown type: they can be implicitly cast
to almost any type in the right context.
rows ctors can also be implicitly cast to any composite type matching
the elements (now sure how exactly are they matched? - number of
elements, type compatibility of elements, just by context?).
string literals are not checked for valid syntax currently, but this
will probably change so we can type check string literals statically.
Postgres defers all checking to runtime, because it has to cope with
custom data types. This code will allow adding a grammar checker for
each type so you can optionally check the string lits statically.
== notes on type checking types
=== basic type checking
Currently can lookup type names against a default template1 list of
types, or against the current list in a database (which is read before
processing and sql code).
todo: collect type names from current source file to check against
A lot of the infrastructure to do this is already in place. We also
need to do this for all other definitions, etc.
=== Type aliases
Some types in postgresql have multiple names. I think this is
hardcoded in the pg parser.
For the canonical name in this code, we use the name given in the
postgresql pg_type catalog relvar.
TODO: Change the ast canonical names: where there is a choice, prefer
the sql standard name, where there are multiple sql standard names,
choose the most concise or common one, so the ast will use different
canonical names to postgresql.
planned ast canonical names:
numbers:
int2, int4/integer, int8 -> smallint, int, bigint
numeric, decimal -> numeric
float(1) to float(24), real -> float(24)
float, float(25) to float(53), double precision -> float
serial, serial4 -> int
bigserial, serial8 -> bigint
character varying(n), varchar(n)-> varchar(n)
character(n), char(n) -> char(n)
TODO:
what about PrecTypeName? - need to fix the ast and parser (found out
these are called type modifiers in pg)
also, what can setof be applied to - don't know if it can apply to an
array or setof type
array types have to match an exact array type in the catalog, so we
can't create an arbitrary array of any type. Not sure if this is
handled quite correctly in this code.
=== canonical type name support
Introduce some aliases to protect client code if/when the ast
canonical names are changed:
> typeSmallInt,typeBigInt,typeInt,typeNumeric,typeFloat4,
> typeFloat8,typeVarChar,typeChar,typeBool :: Type
> typeSmallInt = ScalarType "int2"
> typeBigInt = ScalarType "int8"
> typeInt = ScalarType "int4"
> typeNumeric = ScalarType "numeric"
> typeFloat4 = ScalarType "float4"
> typeFloat8 = ScalarType "float8"
> typeVarChar = ScalarType "varchar"
> typeChar = ScalarType "char"
> typeBool = ScalarType "bool"
this converts the name of a type to its canonical name
> canonicalizeTypeName :: String -> String
> canonicalizeTypeName s =
> case () of
> _ | s `elem` smallIntNames -> "int2"
> | s `elem` intNames -> "int4"
> | s `elem` bigIntNames -> "int8"
> | s `elem` numericNames -> "numeric"
> | s `elem` float4Names -> "float4"
> | s `elem` float8Names -> "float8"
> | s `elem` varcharNames -> "varchar"
> | s `elem` charNames -> "char"
> | s `elem` boolNames -> "bool"
> | otherwise -> s
> where
> smallIntNames = ["int2", "smallint"]
> intNames = ["int4", "integer", "int"]
> bigIntNames = ["int8", "bigint"]
> numericNames = ["numeric", "decimal"]
> float4Names = ["real", "float4"]
> float8Names = ["double precision", "float"]
> varcharNames = ["character varying", "varchar"]
> charNames = ["character", "char"]
> boolNames = ["boolean", "bool"]
> checkTypeExists :: Scope -> Type -> Either [TypeError] Type
> checkTypeExists scope t =
> if t `elem` scopeTypes scope
> then Right t
> else Left [UnknownTypeError t]
> liftME :: a -> Maybe b -> Either a b
> liftME d m = case m of
> Nothing -> Left d
> Just b -> Right b
> lookupTypeByName :: Scope -> String -> Either [TypeError] Type
> lookupTypeByName scope name =
> liftME [UnknownTypeName name] $
> lookup name (scopeTypeNames scope)
================================================================================
Internal errors
TODO: work out monad transformers and try to use these. Want to throw
an internal error when a programming error is detected (instead of
e.g. letting the haskell runtime throw a pattern match failure), then
catch it in the top level type check routines in ast.ag, convert it to
a regular either style error, all without dropping into IO.
This isn't used at the moment.
> data TInternalError = TInternalError String
> deriving (Eq, Ord, Show)
> instance Error TInternalError where
> noMsg = TInternalError "oh noes!"
> strMsg = TInternalError
================================================================================
types for the keyword operators, for use in findCallMatch. Not sure
where these should live but probably not here.
> keywordOperatorTypes :: [(String,[Type],Type)]
> keywordOperatorTypes = [
> ("!and", [typeBool, typeBool], typeBool)
> ,("!or", [typeBool, typeBool], typeBool)
> ,("!like", [ScalarType "text", ScalarType "text"], typeBool)
> ,("!not", [typeBool], typeBool)
> ,("!isNull", [Pseudo AnyElement], typeBool)
> ,("!isNotNull", [Pseudo AnyElement], typeBool)
> ,("!arrayCtor", [Pseudo AnyElement], Pseudo AnyArray) -- not quite right,
> -- needs variadic support
> -- currently works via a special case
> -- in the type checking code
> ,("!between", [Pseudo AnyElement
> ,Pseudo AnyElement
> ,Pseudo AnyElement], Pseudo AnyElement)
> ,("!substring", [ScalarType "text",typeInt,typeInt], ScalarType "text")
> ,("!arraySub", [Pseudo AnyArray,typeInt], Pseudo AnyElement)
> ]
special functions, stuck in here at random also, these look like
functions, but don't appear in the postgresql catalog.
> specialFunctionTypes :: [(String,[Type],Type)]
> specialFunctionTypes = [
> ("coalesce", [Pseudo AnyElement],
> Pseudo AnyElement) -- needs variadic support to be correct,
> -- uses special case in type checking
> -- currently
> ,("nullif", [Pseudo AnyElement, Pseudo AnyElement], Pseudo AnyElement)
> ,("greatest", [Pseudo AnyElement], Pseudo AnyElement) --variadic, blah
> ,("least", [Pseudo AnyElement], Pseudo AnyElement) --also
> ]
================================================================================
utilities for working with Types
> isArrayType :: Type -> Bool
> isArrayType (ArrayType _) = True
> isArrayType _ = False
> unwrapArray :: Type -> Type
> unwrapArray (ArrayType t) = t
> unwrapArray x = error $ "internal error: can't get types from non array " ++ show x
> unwrapSetOfComposite :: Type -> Type
> unwrapSetOfComposite (SetOfType a@(UnnamedCompositeType _)) = a
> unwrapSetOfComposite x = error $ "internal error: tried to unwrapSetOfComposite on " ++ show x
> unwrapSetOf :: Type -> Type
> unwrapSetOf (SetOfType a) = a
> unwrapSetOf x = error $ "internal error: tried to unwrapSetOf on " ++ show x
> unwrapComposite :: Type -> [(String,Type)]
> unwrapComposite (UnnamedCompositeType a) = a
> unwrapComposite x = error $ "internal error: cannot unwrapComposite on " ++ show x
> consComposite :: (String,Type) -> Type -> Type
> consComposite l (UnnamedCompositeType a) =
> UnnamedCompositeType (l:a)
> consComposite a b = error $ "internal error: called consComposite on " ++ show (a,b)
> unwrapRowCtor :: Type -> [Type]
> unwrapRowCtor (RowCtor a) = a
> unwrapRowCtor x = error $ "internal error: cannot unwrapRowCtor on " ++ show x