This module contains the code to manage local identifier bindings
during the type checking process. This is used for e.g. looking up the
types of parameter and variable references in plpgsql functions, and
for looking up the types of identifiers in select expressions.
This module exposes the internals of the localbindings datatype for
testing.
The lookups to support are a single identifier, or to give a star
expansion.
Some notes on lookups
all lookups are case insensitive
start by searching the head of the lookup update list and working down
the code here handles resolving the types of join columns when they
are not the same, and the update routine returns error if the join columns are not compatible
the code here handles expanding record types so that the components can be looked up
The local bindings is arranged as a stack. To append to this stack,
you use the LocalBindingsUpdate type. This is designed to be as easy as
possible for clients to use, so as much logic as possible is pushed
into the innards of this module, in particular most of the logic for
working with joins is in here.
The basic idea of the stack is at each level, there is a list of
qualified and unqualified names and types, to look up individual
ids. Some of the lookups map to ambiguous identifier errors. Also at
each level is a list of star expansions, one for each correlation name
in scope, and one for an unqualified star.
>
>
> module Database.HsSqlPpp.Internals.TypeChecking.LocalBindingsInternal
> (
> LocalBindingsUpdate(..)
> ,LocalBindings(..)
> ,Source
> ,FullId
> ,SimpleId
> ,IDLookup
>
> ,LocalBindingsLookup(..)
> ,emptyBindings
> ,lbUpdate
>
> ,lbLookupID
> ,lbLookupIDInType
> --,lbUpdateDot
> ,ppLocalBindings
> ,ppLbls
> ,createLocalBindings
> ,getUnqualifiedBindings
> ,joinBindings
> ,lookupLocalBinding
> ) where
>
>
>
>
>
>
>
>
>
>
> import Database.HsSqlPpp.Internals.TypeType
>
> import Database.HsSqlPpp.Internals.Catalog.CatalogInternal
>
> import Database.HsSqlPpp.Internals.TypeChecking.ErrorUtils
>
> data LocalBindings = LocalBindingsError
> | LocalBindings [((String,String),Maybe Type)]
> createLocalBindings :: Maybe [(String,[(String,Maybe Type)])] -> LocalBindings
> createLocalBindings i =
> maybe LocalBindingsError mb i
> where
> mb b = LocalBindings
> $ flip concatMap b $ \(qn,cs) -> flip map cs $ \(c,t) -> ((qn,c), t)
> getUnqualifiedBindings :: LocalBindings -> [(String,Maybe Type)]
> getUnqualifiedBindings (LocalBindings ls) =
> map unwrap ls
> where
> unwrap ((_,n),t) = (n,t)
> getUnqualifiedBindings LocalBindingsError = []
> joinBindings :: LocalBindings -> LocalBindings -> LocalBindings
> joinBindings (LocalBindings a) (LocalBindings b) = LocalBindings $ a ++ b
> joinBindings LocalBindingsError _ = LocalBindingsError
> joinBindings _ LocalBindingsError = LocalBindingsError
> lookupLocalBinding :: LocalBindings -> String -> String -> E (Maybe Type)
> lookupLocalBinding LocalBindingsError _ _ = Right Nothing
> lookupLocalBinding (LocalBindings lb) q i =
> case lookup (q,i) lb of
> Just t -> Right t
> Nothing -> if (q == "") || any (==q) (map (fst . fst) lb)
> then Left [UnrecognisedIdentifier i]
> else Left [UnrecognisedCorrelationName q]
---------------------------------------------
The data type to represent a set of local bindings in scope. The list
of updates used to create the local bindings is saved for debugging/
information.
>
Each layer of the local bindings stack is
a map from (correlation name, id name) to source,correlation name, id
name, type tuple, or a type error, used e.g. to represent ambigious
ids, etc.;
and a map from correlation name to a list of these tuples to handle
star expansions.
Missing correlation names are represented by an empty string for the
correlation name.
> type Source = String
>
> type FullId = (Source,[String],Type)
> type SimpleId = (String,Type)
> type IDLookup = ([String], E FullId)
>
>
> data LocalBindingsLookup = LocalBindingsLookup [IDLookup]
> deriving (Eq,Show)
This is the local bindings update that users of this module use.
> data LocalBindingsUpdate = LBIds {source :: Source
> ,correlationName :: Maybe String
> ,lbids :: [SimpleId]}
> | LBTref {source :: Source
> ,talias :: String
> ,lbids :: [SimpleId]
> ,lbsysids :: [SimpleId]}
> | LBJoinTref {source :: Source
> ,jtref1 :: LocalBindingsUpdate
> ,jtref2 :: LocalBindingsUpdate
> ,joinIds :: Either () [String]
>
> ,jalias :: Maybe String}
> deriving Show
>
> emptyBindings :: LocalBindings
> emptyBindings = LocalBindings []
================================================================================
> ppLocalBindings :: LocalBindings -> String
> ppLocalBindings = error "ppLocalBindings"
>
>
> ppLbls :: LocalBindingsLookup -> String
> ppLbls = error "ppLbls"
>
>
>
================================================================================
> lbUpdate :: Catalog -> LocalBindingsUpdate -> LocalBindings -> E LocalBindings
> lbUpdate _ _ lb = return lb
>
>
LBIds doesn't support any star expansion, and doesn't support
accessing the whole set of ids as a composite via cn
>
tref - used for a non join table reference, supports accessing public
fields under the alias name as a composite, and also supports system
id lookups. The star expansions are all the non system ids qualified and unqualified
> updateStuff _ (LBTref src al ids sids) =
>
>
> return (unQuals ++ quals ++ [comp])
>
> where
> allIds = ids ++ sids
> unQuals = map (\(n,t) -> ([n], Right (src,[al,n], t))) allIds
> quals = map (\(n,t) -> ([al,n], Right (src,[al,n], t))) allIds
> comp = ([al], Right (src, [al], CompositeType ids))
> pids = map (\(n,t) -> (src,[al,n],t)) ids
LBJoinTref {source :: Source
,jtref1 :: LocalBindingsUpdate
,jtref2 :: LocalBindingsUpdate
,joinIds :: Either () [String] -- left () represents natural join
-- right [] represents no join ids
,jalias :: Maybe String}
>
How to get the lbs for a join:
First get the info for the two sub trefs:
> ids1 <- updateStuff cat u1
> ids2 <- updateStuff cat u2
split these apart so we have the unqualified lookups and star expands
separately
> let (uids1,qids1) = splitLkps ids1
> (uids2,qids2) = splitLkps ids2
We need some information: the names and types of the join ids, and the
names of any remaining ambiguous identifiers:
> let jnames :: [String]
> jnames = case jnames' of
> Right ns -> ns
> Left () -> intersect (map fst uids1)
> (map fst uids2)
>
> let jids = flip map jnames $ \i -> (i,fromJust $ lookup [i] ids1)
First: get the names of the join ids: this is the explicit list in the
case of a using join, or the commonly named fields in a natural
join. We get the commonly named fields from the unqualified star
expansions so we don't include system attributes in a natural join.
Then check: make sure explicit join id list is in both trefs, and
resolve the types of the join ids.
If there is no alias:
work out the lookups: the qualified lookups stay the same (doesn't
properly deal with the same correlation names coming from 2 trefs at
the moment).
get the list of duplicate ids: uses the same code as getting the
natural join id list, - there will be none if this is a natural
join. Otherwise, get the list of common ids and remove any using ids
from this list. TODO?: system attributes can't be referenced through a
join unqualified?
The unqualified lookups: start with the join ids and types, then add
all the ids from each subtref except the ones which match the
duplicate ids. Add these dups which now lookup to left ambiguous
reference.
work out the star expansion: the qualified star expansions stay the
same
for the unqualified star expansion: similar to lookups. Start with the
join ids, but then add all the non join ids from each table - so we
might have more than one column with the same name.
If there is an alias:
do the same as above for the unqualified ids/starexpand, add these
again under the given alias, and don't pass through qualified lookups
or star expands.
>
>
>
>
>
>
>
>
>
> return (ids1 ++ ids1, se1 ++ se2)
> where
>
> splitLkps :: [([String], E FullId)]
> -> ([(String, E FullId)],[([String], E FullId)])
> splitLkps = partitionEithers . (map $ \x -> case x of
> ([n],t) -> Left (n,t)
> z -> Right z)} }
================================================================================
>
================================================================================
> lbLookupID :: LocalBindings -> [String] -> E FullId
> lbLookupID = error "lbLookupID"
> lbLookupIDInType :: Catalog -> LocalBindings -> Type -> String -> E FullId
> lbLookupIDInType = error "lbLookupIDInType"
================================================================================
> lbUpdateDot :: Catalog -> String -> LocalBindings -> E LocalBindings
> lbUpdateDot cat i lb = do
> (_,_,c) <- lbLookupID lb i
> f <- lmt $ expandComposite cat True c
> lbUpdate cat (LBIds "dot qual" Nothing f) emptyBindings
>
(Source, [String], Type)'
against inferred type `(String, Type)'
> expandComposite :: Catalog -> Bool -> Type -> Maybe [(String,Type)]
> expandComposite cat b (SetOfType t) = expandComposite cat b t
> expandComposite cat b (PgRecord (Just t)) = expandComposite cat b t
> expandComposite _ _ (CompositeType fs) = Just fs
> expandComposite cat b (NamedCompositeType n) = etmt $ (if b
> then catCompositeAttrs
> else catCompositePublicAttrs) cat [] n
> expandComposite _ _ _ = Nothing
================================================================================
>
>
------------------------------------------------------------
wrapper for the proper lookupid function, this is for backwards
compatibility with the old lookup code
>
================================================================================
wrapped for the proper expand star routine, for compatibility with the
old implementation of local bindings
> lbExpandStar :: LocalBindings -> String -> E [SimpleId]
> lbExpandStar lb cor =
> fmap stripAll $ lbExpandStar1 lb cor
> where
> strip (_,_,n,t) = (n,t)
> stripAll = map strip
>
> lbExpandStar1 :: LocalBindings -> String -> E [FullId]
> lbExpandStar1 (LocalBindings _ lkps) cor' =
> exSt lkps
> where
> cor = mtl cor'
> exSt ((LocalBindingsLookup _ lst):ls) =
> case lookup cor lst of
> Just s -> s
> Nothing -> exSt ls
> exSt [] = Left [UnrecognisedCorrelationName cor]
================================================================================
This is where constructing the local bindings lookup stacks is done
> lbUpdate :: Catalog -> LocalBindingsUpdate -> LocalBindings -> E LocalBindings
> lbUpdate cat lbu' (LocalBindings lbus lkps) = do
> lbl <- makeStack cat lbu
> lbl1 <- expandComposites cat lbl
>
> return $ LocalBindings (lbu':lbus) (lbl1:lkps)
> where
> lbu = lowerise lbu'
>
>
> lowerise (LBIds src ids) =
> LBIds src (mtll ids)
> lowerise (LBJoinIds t1 t2 ji a) =
> LBJoinIds (lowerise t1) (lowerise t2) (fmap mtll1 ji) (mtl a)
> lowerise (LBParallel lbu1 lbu2) =
> LBParallel (lowerise lbu1) (lowerise lbu2)
> mtll = map (\(n,t) -> (mtl n, t))
> mtll1 = map (\l -> mtl l)
>
> makeStack :: Catalog -> LocalBindingsUpdate -> E LocalBindingsLookup
> makeStack _ (LBIds src ids) =
> Right $ LocalBindingsLookup doIds doStar
> where
> doIds :: [((String,String)
> ,E FullId)]
> doIds =
> map (makeLookup "")
> (case cor of
> "" -> []
> _ -> map addDetails ids ++ map addDetails iids)
> ++ map (makeLookup cor) (map addDetails ids ++ map addDetails iids)
> where
> makeLookup c1 (s,_,n,t)= ((c1,n), Right (s,cor,n,t))
> doStar :: [(String, E [FullId])]
> doStar = case cor of
> "" -> []
> _ -> [("",Right $ map addDetails ids)]
> ++ [(cor,Right $ map addDetails ids)]
> addDetails (n,t) = (src,cor,n,t)
>
> makeStack cat (LBJoinIds t1 t2 jns a) = do
>
>
>
> (LocalBindingsLookup i1 s1) <- makeStack cat t1
> (LocalBindingsLookup i2 s2) <- makeStack cat t2
>
> let jns' = case jns of
> Left () ->
>
>
>
>
>
> let ic1 :: [FullId]
> ic1 = fromRight [] $ maybe (Right []) id $ lookup "" s1
> ic2 = fromRight [] $ maybe (Right []) id $ lookup "" s2
> third (_,_,n,_) = n
> ii1 :: [String]
> ii1 = map third ic1
> ii2 = map third ic2
> in intersect ii1 ii2
> Right x -> x
>
>
> isJid ((_,n),_) = (n `elem` jns')
> removeJids = filter (not . isJid)
> i1' = removeJids i1
> i2' = removeJids i2
> jids <- M.sequence $ joinIDTypes i1 i2 jns'
>
> let jidsLk :: [IDLookup]
> jidsLk = if null i1 || null i2
> then [] --error?
> else let (_,Right (sc1,c1,_,_)) = head i1
> (_,Right (sc2,c2,_,_)) = head i2
> in flip concatMap jids $ \(n,t) -> [(("",n), Right (sc1,c1,n,t))
> ,((c1,n), Right (sc1,c1,n,t))
> ,((c2,n), Right (sc2,c2,n,t))
> ]
>
>
> newIdLookups = (jidsLk ++ (combineAddAmbiguousErrors i1' i2'))
>
>
>
> se = combineStarExpansions s1 s2
> removeJids1 :: StarLookup -> StarLookup
> removeJids1 (k,ids) = (k, fmap (filter (\(_,_,n,_) -> n `notElem` jns')) ids)
> prependJids :: StarLookup -> StarLookup
> prependJids (c, lkps) = case lkps of
> Left _ -> (c,lkps)
> Right r | null r -> (c,lkps)
> Right r -> let (s,c1,_,_) = head r
> ids = map (\(n,t) -> (s,c1,n,t)) jids
> in (c, fmap (ids++) lkps)
> newStarExpansion = map (prependJids . removeJids1) se
>
>
>
> if a == ""
> then return $ LocalBindingsLookup newIdLookups newStarExpansion
> else return $ LocalBindingsLookup (aliasIds newIdLookups) (aliasExps newStarExpansion)
> where
> aliasIds :: [IDLookup] -> [IDLookup]
> aliasIds lkps = let trimmed = filter (\((c,_),_) -> c == "") lkps
> aliased = map (\(c,i) -> (c, fmap replaceCName i)) trimmed
> in aliased ++ map (\((_,n),i) -> ((a,n),i)) aliased
> aliasExps :: [StarLookup] -> [StarLookup]
> aliasExps lkps = let is = fromMaybe (error "localbindingsinternal.makestack : fromJust") $
> lookup "" lkps
> aliased = fmap (map replaceCName) is
> in [("",aliased), (a, aliased)]
> replaceCName (s,_,n,t) = (s,a,n,t)
> joinIDTypes :: [IDLookup] -> [IDLookup] -> [String] -> [E (String,Type)]
> joinIDTypes i1 i2 = map (joinIDType i1 i2)
> joinIDType :: [IDLookup] -> [IDLookup] -> String -> E (String,Type)
> joinIDType i1 i2 s = do
> (_,_,_,ty1) <- fromMaybe (Left [MissingJoinAttribute]) $
> lookup ("",s) i1
> (_,_,_,ty2) <- fromMaybe (Left [MissingJoinAttribute]) $
> lookup ("",s) i2
> ty <- resolveResultSetType cat [ty1,ty2]
> return (s,ty)
>
> makeStack cat (LBParallel u1 u2) = do
>
>
>
> (LocalBindingsLookup i1 s1) <- makeStack cat u1
> (LocalBindingsLookup i2 s2) <- makeStack cat u2
> return $ LocalBindingsLookup (combineAddAmbiguousErrors i1 i2) $ combineStarExpansions s1 s2
>
> combineStarExpansions :: [StarLookup] -> [StarLookup] -> [StarLookup]
> combineStarExpansions s1 s2 =
> let p :: [(String, [(String, E [FullId])])]
> p = npartition fst (s1 ++ s2)
> in flip map p $ \(a,b) -> (a,concat <$> M.sequence (map snd b))
>
> combineAddAmbiguousErrors :: [IDLookup] -> [IDLookup] -> [IDLookup]
> combineAddAmbiguousErrors i1 i2 =
> let commonIds = intersect (map fst i1) (map fst i2)
> removeCommonIds = filter (\a -> fst a `notElem` commonIds)
> fi1 = removeCommonIds i1
> fi2 = removeCommonIds i2
> errors = map (\(c,n) -> ((c,n),Left [AmbiguousIdentifier $ showID c n])) commonIds
> in fi1 ++ fi2 ++ errors
===============================================================================
> mtl :: String -> String
> mtl = map toLower
>
> showID :: String -> String -> String
> showID cor i = if cor == "" then i else cor ++ "." ++ i
================================================================================
expand composites
slightly dodgy - run through all the unqualified ids in the idlookups, and if any
have a composite type, add each element of that composite under the
correlation name of the idlookup itself, and add a star expansion for
that name also. This pretends that using a correlation name, composite
name and id name as a three part id isn't possible
> expandComposites :: Catalog -> LocalBindingsLookup -> E LocalBindingsLookup
> expandComposites cat (LocalBindingsLookup idlkp stlkp) = do
> let unqids = filter (\((a,_),_) -> a == "") idlkp
> strip = map snd unqids
> getComposites = filter (\(_,_,_,t) -> isCt t) $ rights strip
> comps <- mapM compExp getComposites
> let sts = map toStarLookup comps
> Right (LocalBindingsLookup (idlkp ++ (concat comps)) (stlkp ++ sts))
> where
> isCt (SetOfType t) = isCompositeType t
> isCt t = isCompositeType t
> getCompFields :: Type -> E [(String,Type)]
> getCompFields (SetOfType t) = getCompFields t
> getCompFields (PgRecord Nothing) = Right []
> getCompFields (PgRecord (Just t)) = getCompFields t
> getCompFields (CompositeType f) = return f
> getCompFields (NamedCompositeType s) = catCompositePublicAttrs cat [] s
> getCompFields (AnonymousRecordType _) = Right []
> getCompFields _ = Right []
> compExp :: FullId -> E [IDLookup]
> compExp (s,_,n,t) = do
> f <- getCompFields t
> return $ flip map f $ \(n1,t1) -> ((n,n1),Right (s,n,n1,t1))
> toStarLookup :: [IDLookup] -> StarLookup
> toStarLookup ids =
> let fids::[FullId]
> fids = rights $ map snd ids
> (_,c,_,_) = if null fids then (undefined,"ERROR",undefined,undefined) else head fids
> in (c,Right fids)}