> {- | Functions to convert sql asts to valid SQL source code. Includes
>    a function - 'printSqlAnn' - to output the annotations from a tree
>    in comments in the outputted SQL source.
>
>    Produces sort of readable code, but mainly just written to produce
>    reparsable text. Could do with some work to make the outputted text
>    layout better.
> -}
> {-# LANGUAGE PatternGuards #-}
> module Database.HsSqlPpp.Pretty (
>                       --convert a sql ast to text
>                       printStatements
>                      ,printStatementsAnn
>                      ,printQueryExpr
>                       --convert a single expression parse node to text
>                      ,printScalarExpr
>                      ,printQueryExprNice
>                      )
>     where
>
> import Text.PrettyPrint
> import Data.Char
> --import Data.List
> import Data.Maybe
>
> import Database.HsSqlPpp.Ast
> import Database.HsSqlPpp.Annotation
> import Database.HsSqlPpp.Catalog
> import Database.HsSqlPpp.Utils.Utils
-------------------------------------------------------------------------------- Public functions
> -- | convert an ast back to valid SQL source, it's also almost human readable.
> printStatements :: StatementList -> String
> printStatements = printStatementsAnn (const "")
>
> -- | convert the ast back to valid source, and convert any annotations to
> -- text using the function provided and interpolate the output of
> -- this function(inside comments) with the SQL source.
> printStatementsAnn :: (Annotation -> String) -> StatementList -> String
> printStatementsAnn f ast = render $ vcat (map (convStatement False True f) ast) <> text "\n"
>
> printQueryExpr :: QueryExpr -> String
> printQueryExpr ast = render (convQueryExpr False True True ast <> statementEnd True)
> -- | Testing function, pretty print an expression
> printScalarExpr :: ScalarExpr -> String
> printScalarExpr = render . convExp False
> -- | Try harder to make the output human readable, not necessary correct
> -- sql output at the moment
> printQueryExprNice :: QueryExpr -> String
> printQueryExprNice ast = render (convQueryExpr True True True ast <> statementEnd True)
------------------------------------------------------------------------------- Conversion routines - convert Sql asts into Docs
> -- Statements
>
> convStatement :: Bool -> Bool -> (Annotation -> String) -> Statement -> Doc
>
> -- selects
>
> convStatement nice se ca (QueryStatement ann s) =
>   convPa ca ann <+>
>   convQueryExpr nice True True s <> statementEnd se
>
> --dml
>
> convStatement nice se pa (Insert ann tb atts idata rt) =
>   convPa pa ann <+>
>   text "insert into" <+> convDqi tb
>   <+> ifNotEmpty (parens . sepCsvMap text) atts
>   $+$ convQueryExpr nice True True idata
>   $+$ convReturning nice rt
>   <> statementEnd se
>
> convStatement nice se ca (Update ann tb scs fr wh rt) =
>    convPa ca ann <+>
>    text "update" <+> convDqi tb <+> text "set"
>    <+> sepCsvMap (convSet nice) scs
>    <+> ifNotEmpty (\_ -> text "from" <+> sepCsvMap (convTref nice) fr) fr
>    <+> convWhere nice wh
>    $+$ convReturning nice rt <> statementEnd se
>
> convStatement nice se ca (Delete ann tbl us wh rt) =
>    convPa ca ann <+>
>    text "delete from" <+> convDqi tbl
>    <+> ifNotEmpty (\_ -> text "using" <+> sepCsvMap (convTref nice) us) us
>    <+> convWhere nice wh
>    $+$ convReturning nice rt
>    <> statementEnd se
>
> convStatement _nice se ca (Truncate ann names ri casc) =
>     convPa ca ann <+>
>     text "truncate"
>     <+> sepCsvMap text names
>     <+> text (case ri of
>                       RestartIdentity -> "restart identity"
>                       ContinueIdentity -> "continue identity")
>     <+> convCasc casc
>     <> statementEnd se
>
> -- ddl
>
> convStatement nice se ca (CreateTable ann tbl atts cns) =
>     convPa ca ann <+>
>     text "create table"
>     <+> text tbl <+> lparen
>     $+$ nest 2 (vcat (csv (map convAttDef atts ++ map (convCon nice) cns)))
>     $+$ rparen <> statementEnd se
>     where
>       convAttDef (AttributeDef _ n t def cons) =
>         text n <+> convTypeName t
>         <+> maybeConv (\e -> text "default" <+> convExp nice e) def
>         <+> hsep (map cCons cons)
>       cCons (NullConstraint _ cn) =
>         mname cn <+> text "null"
>       cCons (NotNullConstraint _ cn) =
>         mname cn <+> text "not null"
>       cCons (RowCheckConstraint _ cn ew) =
>         mname cn <+> text "check" <+> parens (convExp nice ew)
>       cCons (RowUniqueConstraint _ cn) =
>         mname cn <+> text "unique"
>       cCons (RowPrimaryKeyConstraint _ cn) =
>         mname cn <+> text "primary key"
>       cCons (RowReferenceConstraint _ cn tb att ondel onupd) =
>         mname cn <+> text "references" <+> text tb
>         <+> maybeConv (parens . text) att
>         <+> text "on delete" <+> convCasc ondel
>         <+> text "on update" <+> convCasc onupd
>
> convStatement nice se ca (AlterTable ann name act) =
>     convPa ca ann <+>
>     text "alter table" <+> text name
>     <+> hcatCsvMap convAct act <> statementEnd se
>     where
>       convAct (AlterColumnDefault _ nm def) =
>           text "alter column" <+> text nm
>           <+> text "set default" <+> convExp nice def
>       convAct (AddConstraint _ con) =
>           text "add " <+> convCon nice con
>
> convStatement _nice se ca (CreateSequence ann nm incr _ _ start cache) =
>     convPa ca ann <+>
>     text "create sequence" <+> text nm <+>
>     text "increment" <+> text (show incr) <+>
>     text "no minvalue" <+>
>     text "no maxvalue" <+>
>     text "start" <+> text (show start) <+>
>     text "cache" <+> text (show cache) <> statementEnd se
>
> convStatement _nice se ca (AlterSequence ann nm o) =
>     convPa ca ann <+>
>     text "alter sequence" <+> text nm
>     <+> text "owned by" <+> convDqi o <> statementEnd se
>
> convStatement nice se ca (CreateTableAs ann t sel) =
>     convPa ca ann <+>
>     text "create table"
>     <+> text t <+> text "as"
>     $+$ convQueryExpr nice True True sel
>     <> statementEnd se
>
> convStatement nice se ca (CreateFunction ann name args retType rep lang body vol) =
>     convPa ca ann <+>
>     text ("create " ++ (case rep of
>                          Replace -> "or replace "
>                          _ -> "") ++ "function")
>     <+> text name
>     <+> parens (sepCsvMap convParamDef args)
>     <+> text "returns" <+> convTypeName retType <+> text "as" <+> text "$$"
>     $+$ convFnBody body
>     $+$ text "$$" <+> text "language"
>     <+> text (case lang of
>                         Sql -> "sql"
>                         Plpgsql -> "plpgsql")
>     <+> text (case vol of
>                        Volatile -> "volatile"
>                        Stable -> "stable"
>                        Immutable -> "immutable")
>     <> statementEnd se
>     where
>       convFnBody (SqlFnBody ann1 sts) =
>         convPa ca ann1 <+>
>         convNestedStatements nice ca sts
>       convFnBody (PlpgsqlFnBody ann1 blk) =
>           convPa ca ann1 <+>
>           convStatement nice True ca blk
>       convParamDef (ParamDef _ n t) = text n <+> convTypeName t
>       convParamDef  (ParamDefTp _ t) = convTypeName t
>
> convStatement nice se ca (Block ann lb decls sts) =
>   convPa ca ann <+>
>   convLabel lb <>
>   ifNotEmpty (\l -> text "declare"
>                   $+$ nest 2 (vcat $ map convVarDef l)) decls
>   $+$ text "begin"
>   $+$ convNestedStatements nice ca sts
>   $+$ text "end" <> statementEnd se
>   where
>       convVarDef (VarDef _ n t v) =
>         text n <+> convTypeName t
>         <+> maybeConv (\x -> text ":=" <+> convExp nice x) v <> semi
>       convVarDef (VarAlias _ n n1) =
>         text n <+> text "alias for" <+> text n1 <> semi
>       convVarDef (ParamAlias _ n p) =
>         text n <+> text "alias for $" <> text (show p) <> semi
>
>
> convStatement nice se ca (CreateView ann name cols sel) =
>     convPa ca ann <+>
>     text "create view" <+> text name
>     <> case cols of
>          Nothing -> empty
>          Just cs -> parens (sepCsvMap text cs)
>     <+> text "as"
>     $+$ nest 2 (convQueryExpr nice True True sel) <> statementEnd se
>
> convStatement nice se ca (CreateDomain ann name tp n ex) =
>     convPa ca ann <+>
>     text "create domain" <+> text name <+> text "as"
>     <+> convTypeName tp <+> cname <+> checkExp ex <> statementEnd se
>     where
>       checkExp = maybeConv (\e -> text "check" <+> parens (convExp nice e))
>       cname = if n == ""
>                then empty
>                else text "constraint" <+> text n
>
> convStatement _nice se ca (DropFunction ann ifExists fns casc) =
>   convPa ca ann <+>
>   text "drop function"
>   <+> convIfExists ifExists
>   <+> sepCsvMap doFunction fns
>   <+> convCasc casc
>   <> statementEnd se
>   where
>     doFunction (name,types) =
>       text name <> parens (sepCsvMap convTypeName types)
>
> convStatement _nice se ca (DropSomething ann dropType ifExists names casc) =
>     convPa ca ann <+>
>     text "drop"
>     <+> text (case dropType of
>                 Table -> "table"
>                 View -> "view"
>                 Domain -> "domain"
>                 Type -> "type")
>     <+> convIfExists ifExists
>     <+> sepCsvMap text names
>     <+> convCasc casc
>     <> statementEnd se
>
> convStatement _nice se ca (CreateType ann name atts) =
>     convPa ca ann <+>
>     text "create type" <+> text name <+> text "as" <+> lparen
>     $+$ nest 2 (vcat (csv
>           (map (\(TypeAttDef _ n t) -> text n <+> convTypeName t)  atts)))
>     $+$ rparen <> statementEnd se
>
> convStatement _nice se ca (CreateLanguage ann name) =
>     convPa ca ann <+>
>     text "create language" <+> text name <> statementEnd se
>
> convStatement nice se ca (CreateTrigger ann name wh events tbl firing fnName fnArgs) =
>     convPa ca ann <+>
>     text "create trigger" <+> text name
>     <+> text (case wh of
>                       TriggerBefore -> "before"
>                       TriggerAfter -> "after")
>     <+> evs
>     <+> text "on" <+> text tbl
>     <+> text "for" <+> text (case firing of
>                                         EachRow -> "row"
>                                         EachStatement -> "statement")
>     <+> text "execute procedure" <+> text fnName
>     <> parens (sepCsvMap (convExp nice) fnArgs) <> statementEnd se
>     where
>       evs = sep $ punctuate (text " or ") $ map
>             (text . (\e -> case e of
>                                 TInsert -> "insert"
>                                 TUpdate -> "update"
>                                 TDelete -> "delete")) events
>
> -- plpgsql
>
> convStatement _nice se ca (NullStatement ann) =
>   convPa ca ann <+> text "null" <> statementEnd se
> convStatement _nice se ca (ExitStatement ann lb) =
>   convPa ca ann <+> text "exit"
>     <+> maybe empty text lb <> statementEnd se
>
> convStatement _ _se _ca (Into _ann _str _into (QueryStatement _annq _s)) = error "no select into"
> convStatement nice se ca (Into ann str into st) =
>   convPa ca ann <+>
>   convStatement nice False ca st
>   <+> text "into"
>   <> (if str
>       then empty <+> text "strict"
>       else empty)
>   <+> sepCsvMap (convExp nice) into
>   <> statementEnd se
>   --fixme, should be insert,update,delete,execute
> convStatement nice se ca (Assignment ann name val) =
>     convPa ca ann <+>
>     convExp nice name <+> text ":=" <+> convExp nice val <> statementEnd se
>
> convStatement nice se ca (Return ann ex) =
>     convPa ca ann <+>
>     text "return" <+> maybeConv (convExp nice) ex <> statementEnd se
>
> convStatement nice se ca (ReturnNext ann ex) =
>     convPa ca ann <+>
>     text "return" <+> text "next" <+> convExp nice ex <> statementEnd se
>
> convStatement nice se ca (ReturnQuery ann sel) =
>     convPa ca ann <+>
>     text "return" <+> text "query"
>     <+> convQueryExpr nice True True sel <> statementEnd se
>
> convStatement nice se ca (Raise ann rt st exps) =
>     convPa ca ann <+>
>     text "raise"
>     <+> case rt of
>                 RNotice -> text "notice"
>                 RException -> text "exception"
>                 RError -> text "error"
>     <+> convExp nice (StringLit emptyAnnotation st)
>     <> ifNotEmpty (\e -> comma <+> csvExp nice e) exps
>     <> statementEnd se
>
> convStatement nice se ca (ForQueryStatement ann lb i sel stmts) =
>     convPa ca ann <+>
>     convLabel lb <>
>     text "for" <+> convExp nice i <+> text "in"
>     <+> convQueryExpr nice True True sel <+> text "loop"
>     $+$ convNestedStatements nice ca stmts
>     $+$ text "end loop" <> statementEnd se
>
> convStatement nice se ca (ForIntegerStatement ann lb var st en stmts) =
>     convPa ca ann <+>
>     convLabel lb <>
>     text "for" <+> convExp nice var <+> text "in"
>     <+> convExp nice st <+> text ".." <+> convExp nice en <+> text "loop"
>     $+$ convNestedStatements nice ca stmts
>     $+$ text "end loop" <> statementEnd se
>
> convStatement nice se ca (WhileStatement ann lb ex stmts) =
>     convPa ca ann <+>
>     convLabel lb <>
>     text "while" <+> convExp nice ex <+> text "loop"
>     $+$ convNestedStatements nice ca stmts
>     $+$ text "end loop" <> statementEnd se
> convStatement nice se ca (LoopStatement ann lb stmts) =
>     convPa ca ann <+>
>     convLabel lb <>
>     text "loop"
>     $+$ convNestedStatements nice ca stmts
>     $+$ text "end loop" <> statementEnd se
>
> convStatement _nice se ca (ContinueStatement ann lb) =
>     convPa ca ann <+> text "continue"
>       <+> maybe empty text lb <> statementEnd se
> convStatement nice se ca (Perform ann f@(FunCall _ _ _)) =
>     convPa ca ann <+>
>     text "perform" <+> convExp nice f <> statementEnd se
> convStatement _ _ _ (Perform _ x) =
>    error $ "internal error: convStatement not supported for " ++ show x
>
> convStatement _nice se ca (Copy ann tb cols src) =
>     convPa ca ann <+>
>     text "copy" <+> text tb
>     <+> ifNotEmpty (parens . sepCsvMap text) cols
>     <+> text "from"
>     <+> case src of
>                  CopyFilename s -> quotes $ text s <> statementEnd se
>                  Stdin -> text "stdin" <> text ";"
>
> convStatement _ _ ca (CopyData ann s) =
>     convPa ca ann <+>
>     text s <> text "\\." <> newline
>
> convStatement nice se ca (If ann conds els) =
>    convPa ca ann <+>
>    text "if" <+> convCond (head conds)
>    $+$ vcat (map (\c -> text "elseif" <+> convCond c) $ tail conds)
>    $+$ ifNotEmpty (\e -> text "else" $+$ convNestedStatements nice ca e) els
>    $+$ text "end if" <> statementEnd se
>     where
>       convCond (ex, sts) = convExp nice ex <+> text "then"
>                            $+$ convNestedStatements nice ca sts
> convStatement nice se ca (Execute ann s) =
>     convPa ca ann <+>
>     text "execute" <+> convExp nice s <> statementEnd se
>
>
> convStatement nice se ca (CaseStatementSimple ann c conds els) =
>     convPa ca ann <+>
>     text "case" <+> convExp nice c
>     $+$ nest 2 (
>                 vcat (map (uncurry convWhenSt) conds)
>                 $+$ convElseSt els
>                 ) $+$ text "end case" <> statementEnd se
>     where
>       convWhenSt ex sts = text "when" <+> sepCsvMap (convExp nice) ex
>                           <+> text "then" $+$ convNestedStatements nice ca sts
>       convElseSt = ifNotEmpty (\s -> text "else"
>                                      $+$ convNestedStatements nice ca s)
> convStatement nice se ca (CaseStatement ann conds els) =
>     convPa ca ann <+>
>     text "case"
>     $+$ nest 2 (
>                 vcat (map (uncurry convWhenSt) conds)
>                 $+$ convElseSt els
>                 ) $+$ text "end case" <> statementEnd se
>     where
>       convWhenSt ex sts = text "when" <+> sepCsvMap (convExp nice) ex
>                           <+> text "then" $+$ convNestedStatements nice ca sts
>       convElseSt = ifNotEmpty (\s -> text "else"
>                                      $+$ convNestedStatements nice ca s)
>
> -- misc
>
> convStatement _nice se _ (Set _ n vs) =
>   text "set" <+> text n <+> text "="
>   <+> sepCsvMap (text . dv) vs <> statementEnd se
>   where
>     dv (SetStr _ s) = "'" ++ s ++ "'"
>     dv (SetId _ i) = i
>     dv (SetNum _ nm) = show nm
>
> convStatement _nice se _ (Notify _ n) =
>   text "notify" <+> text n  <> statementEnd se
>
> statementEnd :: Bool -> Doc
> statementEnd b = if b
>                  then semi <> newline
>                  else empty
------------------------------------------------------------------------------- Statement components
> -- selects
>
> convQueryExpr :: Bool -> Bool -> Bool -> QueryExpr -> Doc
> convQueryExpr nice writeSelect _ (Select _ dis l tb wh grp hav
>                                 order lim off) =
>   (text (if writeSelect then "select" else "")
>          <+> (case dis of
>                  Dupes -> empty
>                  Distinct -> text "distinct"))
>   $+$ nest 2 (vcat $ catMaybes
>   [Just $ nest 2 $ convSelList nice l
>   ,Just $ if null tb
>           then empty
>           else text "from" $+$ nest 2 (sepCsvMap (convTref nice) tb)
>   ,Just $ convWhere nice wh
>   ,case grp of
>      [] -> Nothing
>      g -> Just $ text "group by" $+$ nest 2 (sepCsvMap (convExp nice) g)
>   ,flip fmap hav $ \h -> text "having" $+$ nest 2 (convExp nice h)
>   ,case order of
>      [] -> Nothing
>      o -> Just $ text "order by"
>                   $+$ nest 2 (sepCsvMap (\(oe,od) -> convExp nice oe
>                                               <+> convDir od) o)
>   ,flip fmap lim $ \lm -> text "limit" <+> convExp nice lm
>   ,flip fmap off $ \offs -> text "offset" <+> convExp nice offs
>   ])
>
> convQueryExpr nice writeSelect topLev (CombineQueryExpr _ tp s1 s2) =
>   let p = convQueryExpr nice writeSelect False s1
>           $+$ (case tp of
>                        Except -> text "except"
>                        Union -> text "union"
>                        UnionAll -> text "union" <+> text "all"
>                        Intersect -> text "intersect")
>           $+$ convQueryExpr nice True False s2
>   in if topLev then p else parens p
> convQueryExpr nice _ _ (Values _ expss) =
>   text "values" $$ nest 2 (vcat $ csv $ map (parens . csvExp nice) expss)
> convQueryExpr nice _ _ (WithQueryExpr _ wqs ex) =
>   text "with" $$ nest 2 (vcat $ csv $ map pwq wqs)
>        $+$ convQueryExpr nice True False ex
>   where
>     pwq (WithQuery _ nm cs ex1) =
>       text nm <> case cs of
>                    Nothing -> empty
>                    Just cs' -> parens $ sepCsvMap text cs'
>       <+> text "as"
>       <+> parens (convQueryExpr nice True False ex1)
>
> convTref :: Bool -> TableRef -> Doc
> convTref nice (Tref _ f@(SQIdentifier _ t) (TableAlias _ ta))
>   | nice, last t == ta = convDqi f
>   -- slightly bad hack:
> convTref nice (Tref _ f@(SQIdentifier _ t) (FullAlias _ ta _))
>   | nice, last t == ta = convDqi f
> convTref nice (Tref _ f a) = convDqi f <+> convTrefAlias nice a
> convTref nice (JoinTref _ t1 nat jt t2 ex a) =
>         parens (convTref nice t1
>         $+$ (case nat of
>                       Natural -> text "natural"
>                       Unnatural -> empty)
>         <+> text (case jt of
>                           Inner -> "inner"
>                           Cross -> "cross"
>                           LeftOuter -> "left outer"
>                           RightOuter -> "right outer"
>                           FullOuter -> "full outer")
>         <+> text "join"
>         <+> convTref nice t2
>         <+> maybeConv (nest 2 . convJoinScalarExpr) ex
>         <+> convTrefAlias nice a)
>         where
>           convJoinScalarExpr (JoinOn _ e) = text "on" <+> convExp nice e
>           convJoinScalarExpr (JoinUsing _ ids) =
>               text "using" <+> parens (sepCsvMap text ids)
>
> convTref nice (SubTref _ sub alias) =
>         parens (convQueryExpr nice True True sub)
>         <+> text "as" <+> convTrefAlias nice alias
> convTref nice (FunTref _ f@(FunCall _ _ _) a) = convExp nice f <+> convTrefAlias nice a
> convTref _nice (FunTref _ x _) =
>       error $ "internal error: node not supported in function tref: "
>             ++ show x
>
> convTrefAlias :: Bool -> TableAlias -> Doc
> convTrefAlias _ (NoAlias _) = empty
> convTrefAlias _ (TableAlias _ t) = text t
> -- hack this out for now. When the type checking is fixed, can try
> -- to eliminate unneeded aliases?
> convTrefAlias nice (FullAlias _ t s) =
>   text t <> (if nice
>              then empty
>              else parens (sepCsvMap text s))
> convDir :: Direction -> Doc
> convDir d = text $ case d of
>                           Asc -> "asc"
>                           Desc -> "desc"
>
> convWhere :: Bool -> Maybe ScalarExpr -> Doc
> convWhere nice (Just ex) = text "where" $+$ nest 2 (convExp nice ex)
> convWhere _ Nothing = empty
>
> convSelList :: Bool -> SelectList -> Doc
> convSelList nice (SelectList _ ex) =
>   sepCsvMap convSelItem ex
>   -- <+> ifNotEmpty (\i -> text "into" <+> hcatCsvMap convExp i) into
>   where
>     -- try to avoid printing alias if not necessary
>     convSelItem (SelectItem _ ex1@(QIdentifier _ _ i) nm) | nice, i == nm = convExpSl nice ex1
>     convSelItem (SelectItem _ ex1@(Identifier _ i) nm) | nice, i == nm = convExpSl nice ex1
>     convSelItem (SelectItem _ ex1 nm) = convExpSl nice ex1 <+> text "as" <+> text nm
>     convSelItem (SelExp _ e) = convExpSl nice e
>
> convCasc :: Cascade -> Doc
> convCasc casc = text $ case casc of
>                                  Cascade -> "cascade"
>                                  Restrict -> "restrict"
>
> convDqi :: SQIdentifier -> Doc
> convDqi (SQIdentifier _ is) = hcat $ punctuate (text ".") $ map text is
> -- ddl
>
> convCon :: Bool -> Constraint -> Doc
> convCon _nice (UniqueConstraint _ n c) =
>         mname n <+> text "unique"
>         <+> parens (sepCsvMap text c)
> convCon _nice (PrimaryKeyConstraint _ n p) =
>         mname n <+>
>         text "primary key"
>         <+> parens (sepCsvMap text p)
> convCon nice (CheckConstraint _ n c) =
>         mname n <+> text "check" <+> parens (convExp nice c)
> convCon _nice (ReferenceConstraint _ n at tb rat ondel onupd) =
>         mname n <+>
>         text "foreign key" <+> parens (sepCsvMap text at)
>         <+> text "references" <+> text tb
>         <+> ifNotEmpty (parens . sepCsvMap text) rat
>         <+> text "on update" <+> convCasc onupd
>         <+> text "on delete" <+> convCasc ondel
>
> mname :: String -> Doc
> mname n = if n == ""
>           then empty
>           else text "constraint" <+> text n
>
> convReturning :: Bool -> Maybe SelectList -> Doc
> convReturning nice l = case l of
>                 Nothing -> empty
>                 Just ls -> nest 2 (text "returning" <+> convSelList nice ls)
>
> convIfExists :: IfExists -> Doc
> convIfExists i = case i of
>                         Require -> empty
>                         IfExists -> text "if exists"
>
> -- plpgsql
>
> convNestedStatements :: Bool -> (Annotation -> String) -> StatementList -> Doc
> convNestedStatements nice pa = nest 2 . vcat . map (convStatement nice True pa)
>
> convTypeName :: TypeName -> Doc
> convTypeName (SimpleTypeName _ s) = text s
> convTypeName (PrecTypeName _ s i) = text s <> parens(integer i)
> convTypeName (Prec2TypeName _ s i i1) = text s <> parens (sepCsv [integer i, integer i1])
> convTypeName (ArrayTypeName _ t) = convTypeName t <> text "[]"
> convTypeName (SetOfTypeName _ t) = text "setof" <+> convTypeName t
>
> -- expressions
>
> convExp :: Bool -> ScalarExpr -> Doc
> convExp _ (Identifier _ i) =
>   if quotesNeeded
>      then text $ "\"" ++ i ++ "\""
>      else text i
>   where
>     --needs some work - quotes needed if contains invalid unquoted
>     --chars, or maybe if matches keyword or similar
>     quotesNeeded = case i of
>                      x:_ | not (isLetter x || x `elem` "_*") -> True
>                      _ | all okChar i -> False
>                        | otherwise -> True
>                    where
>                      okChar x =isAlphaNum x || x `elem` "*_."
> convExp nice (QIdentifier a i1@(Identifier _ _) i) = convExp nice i1 <> text "." <> convExp nice (Identifier a i)
> convExp nice (QIdentifier a e i) = parens (convExp nice e) <> text "." <> convExp nice (Identifier a i)
> --convExp (PIdentifier _ i) = parens $ convExp i
> convExp _ (NumberLit _ n) = text n
> convExp _ (StringLit _ s) = -- needs some thought about using $$?
>                           text "'" <> text replaceQuotes <> text "'"
>                           where
>                             replaceQuotes = replace "'" "''" s {-if tag == "'"
>                                               then replace "'" "''" s
>                                               else s-}
>
> convExp nice (FunCall _ n es) =
>     --check for special operators
>    case n of
>      "!and" | nice, [a,b] <- es -> doLeftAnds a b
>      "!arrayctor" -> text "array" <> brackets (csvExp nice es)
>      "!between" -> convExp nice (head es) <+> text "between"
>                    <+> parens (convExp nice (es !! 1))
>                   <+> text "and"
>                   <+> parens (convExp nice (es !! 2))
>      "!substring" -> text "substring"
>                      <> parens (convExp nice (head es)
>                                 <+> text "from" <+> convExp nice (es !! 1)
>                                 <+> text "for" <+> convExp nice (es !! 2))
>      "!arraysub" -> case es of
>                        (Identifier _ i : es1) -> text i
>                                                  <> brackets (csvExp nice es1)
>                        _ -> parens (convExp nice (head es))
>                             <> brackets (csvExp nice (tail es))
>      "!rowctor" -> text "row" <> parens (sepCsvMap (convExp nice) es)
>      "."   -- special case to avoid ws around '.'. Don't know if this is important
>            -- or just cosmetic
>          | [a,b] <- es -> convExp nice a <> text "." <> convExp nice b
>      _ | isOperatorName n ->
>         case forceRight (getOperatorType defaultTemplate1Catalog n) of
>                           BinaryOp ->
>                               let e1d = convExp nice (head es)
>                                   opd = text $ filterKeyword n
>                                   e2d = convExp nice (es !! 1)
>                               in parens (if n `elem` ["!and", "!or"]
>                                          then vcat [e1d, opd <+> e2d]
>                                          else e1d <+> opd <+> e2d)
>                           PrefixOp -> parens (text (if n == "u-"
>                                                        then "-"
>                                                        else filterKeyword n)
>                                                <+> parens (convExp nice (head es)))
>                           PostfixOp -> parens (convExp nice (head es)
>                                        <+> text (filterKeyword n))
>        | otherwise -> text n <> parens (csvExp nice es)
>    where
>      filterKeyword t = case t of
>                          "!and" -> "and"
>                          "!or" -> "or"
>                          "!not" -> "not"
>                          "!isnull" -> "is null"
>                          "!isnotnull" -> "is not null"
>                          "!like" -> "like"
>                          "!notlike" -> "not like"
>                          x -> x
>      -- try to write a series of ands in a vertical line with slightly less parens
>      doLeftAnds a b = let as = and' a
>                       in vcat ((convExp nice (head as)
>                                 : map (\x -> text "and" <+> convExp nice x) (tail as))
>                                ++ [text "and" <+> convExp nice b])
>      and' a = case a of
>                 FunCall _ "!and" [x,y] -> and' x ++ and' y
>                 _ -> [a]
>
> convExp _ (BooleanLit _ b) = bool b
> convExp nice (InPredicate _ att t lst) =
>   convExp nice att <+> (if not t then text "not" else empty) <+> text "in"
>   <+> parens (case lst of
>                        InList _ expr -> csvExp nice expr
>                        InQueryExpr _ sel -> convQueryExpr nice True True sel)
> convExp nice (LiftOperator _ op flav args) =
>   convExp nice (head args) <+> text op
>   <+> text (case flav of
>               LiftAny -> "any"
>               LiftAll -> "all")
>   <+> parens (convExp nice $ head $ tail args)
> convExp nice (ScalarSubQuery _ s) = parens (convQueryExpr nice True True s)
> convExp _ (NullLit _) = text "null"
> convExp nice (WindowFn _ fn part order asc frm) =
>   convExp nice fn <+> text "over"
>   <+> parens (if hp || ho
>               then (if hp
>                     then text "partition by" <+> csvExp nice part
>                     else empty)
>                     <+> (if ho
>                          then text "order by" <+> csvExp nice order
>                               <+> convDir asc
>                          else empty)
>                     <+> convFrm
>               else empty)
>   where
>     hp = not (null part)
>     ho = not (null order)
>     convFrm = case frm of
>                 FrameUnboundedPreceding -> text "range unbounded preceding"
>                 FrameUnboundedFull -> text "range between unbounded preceding and unbounded following"
>                 FrameRowsUnboundedPreceding -> text "rows unbounded preceding"
>
> convExp nice (Case _ whens els) =
>   text "case"
>   $+$ nest 2 (vcat (map convWhen whens)
>               $+$ maybeConv (\e -> text "else" <+> convExp nice e) els)
>   $+$ text "end"
>       where
>         convWhen (ex1, ex2) =
>             text "when" <+> sepCsvMap (convExp nice) ex1
>             <+> text "then" <+> convExp nice ex2
>
> convExp nice (CaseSimple _ val whens els) =
>   text "case" <+> convExp nice val
>   $+$ nest 2 (vcat (map convWhen whens)
>               $+$ maybeConv (\e -> text "else" <+> convExp nice e) els)
>   $+$ text "end"
>       where
>         convWhen (ex1, ex2) =
>             text "when" <+> sepCsvMap (convExp nice) ex1
>             <+> text "then" <+> convExp nice ex2
>
> convExp _ (PositionalArg _ a) = text "$" <> integer a
> convExp _ (Placeholder _) = text "?"
> convExp nice (Exists _ s) =
>   text "exists" <+> parens (convQueryExpr nice True True s)
> convExp nice (Cast _ ex t) = text "cast" <> parens (convExp nice ex
>                                              <+> text "as"
>                                              <+> convTypeName t)
> convExp nice (TypedStringLit a t s) =
>   convTypeName t <+> convExp nice (StringLit a s)
> convExp nice (Interval a v f p) =
>   text "interval" <+> convExp nice (StringLit a v)
>   <+> convIntervalField <+> convPrec
>   where
>     convIntervalField =
>       text $ case f of
>                     IntervalYear -> "year"
>                     IntervalMonth -> "month"
>                     IntervalDay -> "day"
>                     IntervalHour -> "hour"
>                     IntervalMinute -> "minute"
>                     IntervalSecond -> "second"
>                     IntervalYearToMonth -> "year to month"
>                     IntervalDayToHour -> "day to hour"
>                     IntervalDayToMinute -> "day to minute"
>                     IntervalDayToSecond -> "day to second"
>                     IntervalHourToMinute -> "hour to minute"
>                     IntervalHourToSecond -> "hour to second"
>                     IntervalMinuteToSecond -> "minute to second"
>     convPrec = case p of
>                  Nothing -> empty
>                  Just i -> parens (int i)
> convExp nice (Extract _ f e) =
>   text "extract"
>   <> parens (text convField <+> text "from" <+> convExp nice e)
>   where
>     convField =
>       case f of
>              ExtractCentury -> "century"
>              ExtractDay -> "day"
>              ExtractDecade -> "decade"
>              ExtractDow -> "dow"
>              ExtractDoy -> "doy"
>              ExtractEpoch -> "epoch"
>              ExtractHour -> "hour"
>              ExtractIsodow -> "isodow"
>              ExtractIsoyear -> "isoyear"
>              ExtractMicroseconds -> "microseconds"
>              ExtractMillennium -> "millennium"
>              ExtractMilliseconds -> "milliseconds"
>              ExtractMinute -> "minute"
>              ExtractMonth -> "month"
>              ExtractQuarter -> "quarter"
>              ExtractSecond -> "second"
>              ExtractTimezone -> "timezone"
>              ExtractTimezoneHour -> "timezone_hour"
>              ExtractTimezoneMinute -> "timezone_minute"
>              ExtractWeek -> "week"
>              ExtractYear -> "year"
> convExpSl :: Bool ->  ScalarExpr -> Doc
> convExpSl nice (FunCall _ "." es) | [a@(Identifier _ _), b] <- es =
>   parens (convExpSl nice a) <> text "." <> convExpSl nice b
> convExpSl nice x = convExp nice x
>
> convSet :: Bool -> ScalarExpr -> Doc
> convSet nice (FunCall _ "=" [Identifier _ a, e]) =
>   text a <+> text "=" <+> convExp nice e
> convSet nice (FunCall _ "=" [a, b]) | (FunCall _ "!rowctor" is1) <- a
>                                 ,(FunCall _ "!rowctor" is2) <- b =
>   rsNoRow is1 <+> text "=" <+> rsNoRow is2
>   where
>     rsNoRow is = parens (sepCsvMap (convExp nice) is)
> convSet _ a = error $ "bad expression in set in update: " ++ show a
>
> --utils
>
> -- convert a list of expressions to horizontal csv
>
> csvExp :: Bool -> [ScalarExpr] -> Doc
> csvExp nice = hcatCsvMap (convExp nice)
>
> maybeConv :: (t -> Doc) -> Maybe t -> Doc
> maybeConv f c =
>     case c of
>       Nothing -> empty
>       Just a -> f a
>
> csv :: [Doc] -> [Doc]
> csv = punctuate comma
>
> hcatCsv :: [Doc] -> Doc
> hcatCsv = hcat . csv
> sepCsv :: [Doc] -> Doc
> sepCsv = sep . csv
>
> ifNotEmpty :: ([a] -> Doc) -> [a] -> Doc
> ifNotEmpty c l = if null l then empty else c l
>
> hcatCsvMap :: (a -> Doc) -> [a] -> Doc
> hcatCsvMap ex = hcatCsv . map ex
> sepCsvMap :: (a -> Doc) -> [a] -> Doc
> sepCsvMap ex = sepCsv . map ex
> --vcatCsvMap :: (a -> Doc) -> [a] -> Doc
> --vcatCsvMap ex = vcat . csv . map ex
>
> bool :: Bool -> Doc
> bool b = if b then text "true" else text "false"
>
> newline :: Doc
> newline = text "\n"
>
> convPa :: (Annotation -> String) -> Annotation -> Doc
> convPa ca a = let s = ca a
>               in if s == ""
>                    then empty
>                    else text "/*\n" <+> text s
>                         <+> text "*/\n"
> convLabel :: Maybe String -> Doc
> convLabel =
>   maybe empty (\l -> text "<<"
>                      <+> text l
>                      <+> text ">>" <> text "\n")