This module represents part of the bound names environment used in the
type checker. It doesn't cover the stuff that is contained in the
catalog (so it is slightly misnamed), but focuses only on identifiers
introduced by things like tablerefs, sub selects, plpgsql parameters
and variables, etc.
> {-# LANGUAGE DeriveDataTypeable,TupleSections,ScopedTypeVariables,OverloadedStrings #-}
> module Database.HsSqlPpp.Internals.TypeChecking.Environment
> (
> Environment
> ,JoinType(..)
>
> ,emptyEnvironment
> ,isEmptyEnv
> ,envCreateTrefEnvironment
> ,createJoinTrefEnvironment
> ,envSelectListEnvironment
> ,createCorrelatedSubqueryEnvironment
> ,createTrefAliasedEnvironment
> ,brokeEnvironment
> ,orderByEnvironment
>
> ,envLookupIdentifier
> ,envExpandStar
> ) where
> import Data.Data
> import Data.Char
>
> import Control.Monad
> import Control.Arrow
> import Data.List
>
>
> import Database.HsSqlPpp.Internals.TypesInternal
> import Database.HsSqlPpp.Internals.TypeChecking.TypeConversion.TypeConversion
> import Database.HsSqlPpp.Internals.Catalog.CatalogInternal
> import Database.HsSqlPpp.Internals.Catalog.CatalogTypes hiding (ncStr)
> import Data.Generics.Uniplate.Data
> import Data.Text (Text)
> import qualified Data.Text as T
Alex:
Not sure that handling of USING joins (and specifically of join keys) is correct, but
don't know, have to investigate all the connections in the code.
One problem is that types for the purpose of comparison are different from the output types.
Moreover, major DBs are imprecise in their definitions of USING joins.
For instance, what Postgre writes about output columns seems to be related only to the
star expansion. I still can explicitly reference a key column from the inner table
in the output expression, with the use of table name or alias. Or can I not?
Have to look at ANSI.
More precisely, we have:
1. Type check join keys to see that they are compatible.
2. If needed, create additional columns that would exist for the purpose of comparison
only. The scalar expressions that create them are casts from join keys.
3. If a join key is used in an output scalar expression, the original' key type is used.
For a qualified column reference, including qualified star expansion, original' type
is same as original type, except that it is possibly cast to nullable for an inner table
of an outer join.
For star expansion:
The original' type is defined strictly for LeftOuter and RightOuter.
It is unclear what to do in case of Inner and FullOuter if the types differ (but, as we
already know, are compatible).
There are two choices:
1. Reject the query.
2. Resolve the types and return the result type.
For now, I'll do the 2nd thing, let's see what happens.
And I don't implement the rest of this plan either, of course:
- I convert things to nullable for outer joins, but don't separate between
condition columns and output columns, of course;
- For Right Outer Joins, I reverse the order of input environments in listBindingsTypes,
for the purpose of getting the key types.
And now I see that TypeExtra's in join ids are not actually used.
>
>
>
>
>
> data Environment =
>
>
>
> EmptyEnvironment
>
>
> | SimpleTref (Text,Text) [(Text,TypeExtra)] [(Text,TypeExtra)]
>
> | JoinTref [(Text,TypeExtra)]
> JoinType
> Environment Environment
>
> | SelectListEnv [(Text,TypeExtra)]
>
> | CSQEnv Environment
> Environment
>
> | TrefAlias Text (Maybe [Text]) Environment
> | BrokeEnvironment
>
>
>
> | OrderByEnvironment Environment Environment
> deriving (Data,Typeable,Show,Eq)
>
> data JoinType = Inner | LeftOuter | RightOuter | FullOuter
> deriving (Data,Typeable,Show,Eq)
Create/ update functions, these are shortcuts to create environment variables,
the main purpose is to encapsulate looking up information in the
catalog and combining environment values with updates
TODO: remove the create prefixes
> emptyEnvironment :: Environment
> emptyEnvironment = EmptyEnvironment
> isEmptyEnv :: Environment -> Bool
> isEmptyEnv EmptyEnvironment = True
> isEmptyEnv _ = False
> envCreateTrefEnvironment :: Catalog -> [NameComponent] -> Either [TypeError] Environment
> envCreateTrefEnvironment cat tbnm = do
> (nm,pub,prv) <- catLookupTableAndAttrs cat tbnm
> return $ SimpleTref nm pub (second mkTypeExtraNN `map` prv)
> envSelectListEnvironment :: [(Text,TypeExtra)] -> Either [TypeError] Environment
> envSelectListEnvironment cols =
> return $ SelectListEnv $ map (first $ T.map toLower) cols
>
> createJoinTrefEnvironment :: Catalog
> -> Environment
> -> Environment
> -> JoinType
> -> Maybe [NameComponent]
>
> -> Either [TypeError] Environment
> createJoinTrefEnvironment cat tref0 tref1 jt jsc = do
>
> (jids::[Text]) <- case jsc of
> Nothing -> do
> j0 <- fmap (map (snd . fst)) $ envExpandStar Nothing tref0
> j1 <- fmap (map (snd . fst)) $ envExpandStar Nothing tref1
> return $ j0 `intersect` j1
> Just x -> return $ map nmcString x
>
> jts <- forM jids $ \i -> do
> (_,t0) <- envLookupIdentifier [QNmc $ T.unpack i] tref0
> (_,t1) <- envLookupIdentifier [QNmc $ T.unpack i] tref1
> let adjustTypeExtra te = case jt of
> Inner -> te
> LeftOuter -> t0
> RightOuter -> t1
> FullOuter -> mkNullable te
> fmap ((i,) . adjustTypeExtra) $ resolveResultSetTypeExtra cat [t0,t1]
>
>
>
> return $ JoinTref jts jt tref0 tref1
> createCorrelatedSubqueryEnvironment :: Environment -> Environment -> Environment
> createCorrelatedSubqueryEnvironment = CSQEnv
> createTrefAliasedEnvironment :: Text -> Maybe [Text] -> Environment -> Environment
> createTrefAliasedEnvironment = TrefAlias
>
>
> brokeEnvironment :: Environment
> brokeEnvironment = BrokeEnvironment
> isBroken :: Environment -> Bool
> isBroken env = not $ null [() | BrokeEnvironment <- universeBi env]
> orderByEnvironment :: Environment -> Environment -> Environment
> orderByEnvironment = OrderByEnvironment
The main hard work is done in the query functions: so the idea is that
the update functions create environment values which contain the
context free contributions of each part of the ast to the current
environment, and these query functions do all the work of resolving
implicit correlation names, ambigous identifiers, etc.
for each environment type, provide two functions which do identifier
lookup and star expansion
> listBindingsTypes :: Environment -> ((Maybe Text,Text) -> [((Text,Text),TypeExtra)]
> ,Maybe Text -> [((Text,Text),TypeExtra)]
> )
> listBindingsTypes EmptyEnvironment = (const [],const [])
> listBindingsTypes BrokeEnvironment = (const [],const [])
> listBindingsTypes (TrefAlias ta Nothing env) =
> (\(q,n) -> if q `elem` [Nothing, Just ta]
> then req $ fst (listBindingsTypes env) (Nothing,n)
> else []
> ,\q -> if q `elem` [Nothing, Just ta]
> then req $ snd (listBindingsTypes env) Nothing
> else [])
> where
> req = map (\((_,i),t) -> ((ta,i),t))
> listBindingsTypes (TrefAlias ta (Just cs) env) =
> (\(q,n) ->
> if q `elem` [Nothing, Just ta]
> then
>
> case elemIndex n cs of
> Just i -> let s :: [((Text, Text), TypeExtra)]
> s = (snd (listBindingsTypes env) Nothing)
> in
>
>
> map (\((_,_j),t) -> ((ta,n),t)) $ take 1 $ drop i s
> Nothing -> []
> else []
> ,\q -> if q `elem` [Nothing, Just ta]
> then let
>
>
>
> repColNames = map Just cs ++ repeat Nothing
> aliasize :: [((Text, Text), TypeExtra)] -> [((Text, Text), TypeExtra)]
> aliasize =
> flip zipWith repColNames (\r ((_,n),t) ->
> case r of
> Just r' -> ((ta,r'),t)
> Nothing -> ((ta,n),t))
> in aliasize $ snd (listBindingsTypes env) Nothing
> else [])
> where
>
> _req = map (\((_,i),t) -> ((ta,i),t))
FIXME!!! (_,nm) ?
> listBindingsTypes (SimpleTref (_,nm) pus pvs) =
> (\(q,n) -> let m (n',_) = (q `elem` [Nothing,Just nm])
> && n == n'
> in addQual nm $ filter m $ pus ++ pvs
> ,\q -> case () of
> _ | q `elem` [Nothing, Just nm] -> addQual nm pus
> | otherwise -> [])
> listBindingsTypes (JoinTref jids jt env0 env1) =
> (idens,starexp)
> where
> idens k = let [iOuter,iInner] = (if jt==RightOuter then reverse else id) [is0 k, is1 k]
> in if not (null iOuter) && snd k `elem` jnames
> then iOuter
> else iOuter ++ iInner
> _useResolvedType tr@((q,n),_) = case lookup n jids of
> Just t' -> ((q,n),t')
> Nothing -> tr
> jnames = map fst jids
> isJ ((_,n),_) = n `elem` jnames
todo: use useResolvedType
unqualified star:
reorder the ids so that the join columns are first
> starexp Nothing = let (aj,anj) = partition isJ (st0 Nothing)
> bnj = filter (not . isJ) (st1 Nothing)
> in aj ++ anj ++ bnj
> starexp q@(Just _) =
> let s0 = st0 q
> s1 = st1 q
> in case (s0,s1) of
>
>
> (_:_,[]) -> s0
> ([], _:_) -> s1
>
>
> _ -> let (aj,anj) = partition isJ s0
> bnj = filter (not . isJ) s1
> in aj ++ anj ++ bnj
> (is0,st0) = (if jt `elem` [RightOuter,FullOuter] then addNullability else id)
> $ listBindingsTypes env0
> (is1,st1) = (if jt `elem` [LeftOuter,FullOuter] then addNullability else id)
> $ listBindingsTypes env1
> addNullability = (map (second mkNullable) .) *** (map (second mkNullable) .)
selectlistenv: not quite right, but should always have an alias so the
empty qualifier never gets very far
> listBindingsTypes (SelectListEnv is) =
> (\(_,n) -> addQual "" $ filter ((==n).fst) is
> ,const $ addQual "" is)
not quite right, see queryexprs.ag
> listBindingsTypes (OrderByEnvironment sl tr) =
> (\i ->
>
>
>
>
> case (fst (listBindingsTypes tr) i
> ,fst (listBindingsTypes sl) i) of
> ([],x) -> x
> (y,_) -> y
> ,const [])
csq just uses standard shadowing for iden lookup
for star expand, the outer env is ignored
> listBindingsTypes (CSQEnv outerenv env) =
> (\k -> case (fst (listBindingsTypes env) k
> ,fst (listBindingsTypes outerenv) k) of
> (x,_) | not (null x) -> x
> (_, x) | not (null x) -> x
> _ -> []
> ,snd $ listBindingsTypes env)
> addQual :: Text -> [(Text,TypeExtra)] -> [((Text,Text),TypeExtra)]
> addQual q = map (\(n,t) -> ((q,n),t))
use listBindingsTypes to implement expandstar and lookupid
> envExpandStar:: Maybe NameComponent -> Environment
> -> Either [TypeError] [((Text,Text),TypeExtra)]
> envExpandStar = envExpandStar2
>
> envExpandStar2 :: Maybe NameComponent -> Environment -> Either [TypeError] [((Text,Text),TypeExtra)]
> envExpandStar2 nmc env =
> if isBroken env
> then Left []
> else
> let st = snd (listBindingsTypes env) $ fmap nmcString nmc
> in if null st
> then case nmc of
> Just x -> Left [UnrecognisedCorrelationName $ nmcString x]
> Nothing -> Left [BadStarExpand]
> else Right st
> nmcString :: NameComponent -> Text
> nmcString (QNmc n) = T.pack n
> nmcString (Nmc n) = T.pack $ map toLower n
>
> nmcString (AntiNameComponent _) = error "tried to get ncstr of antinamecomponent"
> envLookupIdentifier :: [NameComponent] -> Environment
> -> Either [TypeError] ((Text,Text), TypeExtra)
> envLookupIdentifier nmc env =
> if isBroken env
> then Left []
> else do
> k <- case nmc of
> [a,b] -> Right (Just $ nmcString a, nmcString b)
> [b] -> Right (Nothing, nmcString b)
> [_,_,_] -> Left [SchemadColumnName "an identifier cannot be used with an explicit schema name, please use only a correlation name without a schema name (you can use a table reference alias to disambiguate if you need to)."]
> [_,_,_,_] -> Left [DbSchemadColumnName "an identifier cannot be used with an explicit database name and schema name, please use only a correlation name without a schema name (you can use a table reference alias to disambiguate if you need to)."]
> _ -> Left [InternalError "too many nmc components in envlookupiden"]
> case (fst $ listBindingsTypes env) k of
> [] -> Left [UnrecognisedIdentifier $ nmcString $ last nmc]
> [x] -> Right $ keepCasehack x
> _ -> Left [AmbiguousIdentifier $ nmcString $ last nmc]
> where
> keepCasehack ((na,nb),t) =
> case nmc of
> [a,b] -> let x = ((keepcase a na,keepcase b nb),t)
> in x
> [b] -> ((na,keepcase b nb),t)
> _ -> error "too many nmc components in envlookupiden(2)"
> keepcase orig new =
> if T.map toLower new == nmcString orig
> then noLower orig
> else new
> noLower (QNmc n) = T.pack n
> noLower (Nmc n) = T.pack n
> noLower (AntiNameComponent n) = error $ "bad antinamecomponent in Environment.envLookupIdentifier.noLower " ++ n
adding for plpgsql notes:
additional envs
* parameter in function
* declaration in function block
* implicit integer loop var in for loop
* set explicit record type in for loop/ assignment to record type
* for constraints in create table, create domain
Write tests to quickly check each bit of code which uses these using
the full typechecking:
update: sets, where, returning
select: tref -> select list, where, group by, order by
join: out to tref, into on expression
implicit variable in for loop
record type in for loop
record type in assignment
record type in select into
delete where and returning
block declarations
constraints in create table, create domain
parameters in function body
statementlist: pass on record updates?
insert: columns?, returning