packages feed

hssqlppp-0.2.0: Database/HsSqlPpp/AstInternals/TypeChecking/Dml.ag

{-
Copyright 2009 Jake Wheat

This file contains the type checking code for dml, currently insert,
update and delete.

================================================================================

= insert

check the insert data is the correct type. Doesn't cope with columns
with default values at the moment.

-}

SEM Statement
    | Insert
        loc.tpe =
            dependsOnRTpe [getTypeAnnotation @insData.annotatedTree] $ do
              @loc.columnTypes
              Right $ Pseudo Void
        loc.statementType =
            leftToEmpty (\ct -> [StatementType (getPlaceholderTypes @loc.insDataAddedInferredTypes) (fromMaybe [] @returning.listType)]) @loc.columnTypes

        loc.columnTypes =
            do
            tys <- unwrapSetOfComposite $
                   getTypeAnnotation @insData.annotatedTree
            checkColumnConsistency @lhs.cat
                                   @table
                                   @targetCols.strings
                                   tys
        loc.insDataAddedInferredTypes =
            case @insData.annotatedTree of
              Values ann [exl] ->
                  let fargs = map snd (fromRight [] @loc.columnTypes) ++ repeat TypeCheckFailed
                      newExl = map (\(ex,ty) -> updateAnnotation (++ [InferredType ty]) ex) $ zip exl fargs
                  in Values ann [newExl]
              x -> x

        loc.backTree = Insert @ann @table @targetCols.annotatedTree
                              @loc.insDataAddedInferredTypes @returning.annotatedTree
        loc.catUpdates = []

-- inject the ids into the returning part

SEM Statement
    | Insert
        returning.lib =
            fromRight @lhs.lib $ do
              atts <- catCompositeAttrs @lhs.cat relationComposites @table
              updateBindings @lhs.lib @lhs.cat [LibStackIDs [("", atts)]]

{-
================================================================================

= update

check the set clause assignments are ok, and inject ids into the where part

-}

SEM Statement
    | Update
        loc.tpe =
            do
            checkRelationExists @lhs.cat @table
            dependsOnRTpe (map snd @assigns.pairs) $ do
              @loc.columnTypes
              liftErrors @assigns.rowSetErrors
              return $ Pseudo Void
        loc.statementType =
            leftToEmpty (\ct -> [StatementType (getPlaceholderTypes @loc.assignWInferredTypes
                                                ++ getPlaceholderTypes @whr.annotatedTree)
                                               (fromMaybe [] @returning.listType)])
               @loc.columnTypes

        loc.columnTypes =
            checkColumnConsistency @lhs.cat
                                   @table
                                   (map fst @assigns.pairs)
                                   @assigns.pairs
        loc.assignWInferredTypes : SetClauseList
        loc.assignWInferredTypes =
            let colTypes :: [Type]
                colTypes = (map snd $ fromRight [] @loc.columnTypes)
            in setInferredTypesG colTypes @assigns.annotatedTree

        loc.backTree = Update @ann
                              @table
                              @loc.assignWInferredTypes
                              @whr.annotatedTree
                              @returning.annotatedTree
        loc.catUpdates = []

{

setInferredTypesG :: Data a => [Type] -> a -> a
setInferredTypesG tys x =
  evalState (transformBiM f x) tys
  where
    f (p@(Placeholder _)) = do
         y:ys <- get
         put ys
         return $ updateAnnotation (++ [InferredType y]) p
    f z = return z
}

-- local identifier bindings: pass the table attribute names and types
-- into the where expression, and the set clause expressions

SEM Statement
    | Update
        loc.lib =
             fromRight @lhs.lib $ do
             ct <- catCompositeAttrs @lhs.cat
                                     relationComposites
                                     @table
             updateBindings @lhs.lib @lhs.cat [LibStackIDs [("", ct)]]
        whr.lib = @loc.lib
        assigns.lib = @loc.lib
        returning.lib = @loc.lib

{-
== set clauses

small complication is slightly hacky code to deal with row set
assignments, where we assign from a multiple attribute subselect into
multiple columns - todo: check if we need so much special casing for
this: should be able to reuse the funcall typing of row set equality
(trade one hack for another, squinting so that assignment looks like
an equality check - since it type checks the same we might be ok). If
we do this, we only need to expand the row sets out to produce a
single string,type list at the end.

-}

ATTR SetClauseList [||pairs : {[(String,Type)]}
                      rowSetErrors : {[TypeError]}]

SEM SetClauseList
  | Cons lhs.pairs = @hd.pairs ++ @tl.pairs
         lhs.rowSetErrors = maybeToList @hd.rowSetError ++ @tl.rowSetErrors
  | Nil lhs.pairs = []
        lhs.rowSetErrors = []

ATTR SetClause [||pairs : {[(String,Type)]}
                  rowSetError : {Maybe TypeError}]

SEM SetClause
    | SetClause
        lhs.pairs = [(@att, getTypeAnnotation @val.annotatedTree)]
        lhs.rowSetError = Nothing
    | RowSetClause
        loc.rowSetError =
          let atts = @atts.strings
              types = getRowTypes @vals.typeList
          in if length atts /= length types
               then Just WrongNumberOfColumns
               else Nothing
        lhs.pairs = zip @atts.strings $ getRowTypes @vals.typeList

{
getRowTypes :: [Type] -> [Type]
getRowTypes [AnonymousRecordType ts] = ts
getRowTypes ts = ts
}

{-
================================================================================

= delete

pretty simple, mainly just need to inject ids into the where part

-}

SEM Statement
    | Delete
        loc.tpe =
            checkRelationExists @lhs.cat @table >>
            Right (Pseudo Void)
        loc.statementType = [StatementType (getPlaceholderTypes @whr.annotatedTree)
                                           (fromMaybe [] @returning.listType)]

        loc.backTree = Delete @ann @table @whr.annotatedTree @returning.annotatedTree
        loc.catUpdates = []


SEM Statement
    | Delete
        loc.lib =
            fromRight @lhs.lib $ do
            columnTypes <- catCompositeAttrs @lhs.cat relationComposites @table
            updateBindings @lhs.lib @lhs.cat [LibStackIDs [("", columnTypes)]]
        whr.lib = @loc.lib
        returning.lib = @loc.lib

{-
================================================================================
-}
{

--small shortcut to help produce better errors?
checkRelationExists :: Catalog -> String -> Either [TypeError] ()
checkRelationExists cat tbl =
    catCompositeDef cat relationComposites tbl >>
    return ()

--used by both insert and update
checkColumnConsistency :: Catalog ->  String -> [String] -> [(String,Type)]
                       -> Either [TypeError] [(String,Type)]
checkColumnConsistency cat tbl cols' insNameTypePairs = do
  ttcols <- lowerize <$> catCompositePublicAttrs cat [] tbl
  let cols = if null cols'
               then map fst ttcols
               else map (map toLower) cols'
  errorWhen (length insNameTypePairs /= length cols) [WrongNumberOfColumns]
  let nonMatchingColumns = cols \\ map fst ttcols
  errorWhen (not $ null nonMatchingColumns) $
       map UnrecognisedIdentifier nonMatchingColumns
  let targetNameTypePairs = map (\l -> (l,fromJust $ lookup l ttcols)) cols
        --check the types of the insdata match the column targets
        --name datatype columntype
      typeTriples = map (\((a,b),c) -> (a,b,c)) $
                    zip targetNameTypePairs $
                    map snd insNameTypePairs
      errs :: [TypeError]
      errs = concat $ lefts $
             map (\(_,b,c) -> checkAssignmentValid cat c b) typeTriples
  liftErrors errs
  return targetNameTypePairs
  where
    lowerize = map (\(a,b) -> (map toLower a,b))

}