> {- | 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 -- hiding (ncStr) > 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 (statement False True f) ast) <> text "\n" > > printQueryExpr :: QueryExpr -> String > printQueryExpr ast = render (queryExpr False True True Nothing ast <> statementEnd True) > -- | Testing function, pretty print an expression > printScalarExpr :: ScalarExpr -> String > printScalarExpr = render . scalExpr False > -- | Try harder to make the output human readable, not necessary correct > -- sql output at the moment > printQueryExprNice :: QueryExpr -> String > printQueryExprNice ast = render (queryExpr True True True Nothing ast <> statementEnd True) ------------------------------------------------------------------------------- Conversion routines - convert Sql asts into Docs > -- Statements > > statement :: Bool -> Bool -> (Annotation -> String) -> Statement -> Doc > statement _nice _se _ca (AntiStatement s) = text $ "$(" ++ s ++ ")" > > -- selects > > statement nice se ca (QueryStatement ann s) = > annot ca ann <+> > queryExpr nice True True Nothing s <> statementEnd se > > --dml > > statement nice se pa (Insert ann tb atts idata rt) = > annot pa ann <+> > text "insert into" <+> name tb > <+> ifNotEmpty (parens . sepCsvMap nmc) atts > $+$ queryExpr nice True True Nothing idata > $+$ returning nice rt > <> statementEnd se > > statement nice se ca (Update ann tb scs fr wh rt) = > annot ca ann <+> > text "update" <+> name tb <+> text "set" > <+> sepCsvMap (set nice) scs > <+> ifNotEmpty (\_ -> text "from" <+> sepCsvMap (tref nice) fr) fr > <+> whr nice wh > $+$ returning nice rt <> statementEnd se > > statement nice se ca (Delete ann tbl us wh rt) = > annot ca ann <+> > text "delete from" <+> name tbl > <+> ifNotEmpty (\_ -> text "using" <+> sepCsvMap (tref nice) us) us > <+> whr nice wh > $+$ returning nice rt > <> statementEnd se > > statement _nice se ca (Truncate ann names ri casc) = > annot ca ann <+> > text "truncate" > <+> sepCsvMap name names > <+> text (case ri of > RestartIdentity -> "restart identity" > ContinueIdentity -> "continue identity") > <+> cascade casc > <> statementEnd se > > -- ddl > > statement nice se ca (CreateTable ann tbl atts cns) = > annot ca ann <+> > text "create table" > <+> name tbl <+> lparen > $+$ nest 2 (vcat (csv (map attrDef atts ++ map (constraint nice) cns))) > $+$ rparen <> statementEnd se > where > attrDef (AttributeDef _ n t def cons) = > nmc n <+> typeName t > <+> maybePrint (\e -> text "default" <+> scalExpr 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 (scalExpr 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" <+> name tb > <+> maybePrint (parens . nmc) att > <+> text "on delete" <+> cascade ondel > <+> text "on update" <+> cascade onupd > > statement nice se ca (AlterTable ann nm act) = > annot ca ann <+> > text "alter table" <+> name nm > <+> hcatCsvMap alterAction act <> statementEnd se > where > alterAction (AlterColumnDefault _ cnm def) = > text "alter column" <+> nmc cnm > <+> text "set default" <+> scalExpr nice def > alterAction (AddConstraint _ con) = > text "add " <+> constraint nice con > > statement _nice se ca (CreateSequence ann nm incr _ _ start cache) = > annot ca ann <+> > text "create sequence" <+> name nm <+> > text "increment" <+> text (show incr) <+> > text "no minvalue" <+> > text "no maxvalue" <+> > text "start" <+> text (show start) <+> > text "cache" <+> text (show cache) <> statementEnd se > > statement _nice se ca (AlterSequence ann nm o) = > annot ca ann <+> > text "alter sequence" <+> name nm > <+> text "owned by" <+> name o <> statementEnd se > > statement nice se ca (CreateTableAs ann t sel) = > annot ca ann <+> > text "create table" > <+> name t <+> text "as" > $+$ queryExpr nice True True Nothing sel > <> statementEnd se > > statement nice se ca (CreateFunction ann nm args retType rep lang body vol) = > annot ca ann <+> > text ("create " ++ (case rep of > Replace -> "or replace " > _ -> "") ++ "function") > <+> name nm > <+> parens (sepCsvMap paramDefn args) > <+> text "returns" <+> typeName retType <+> text "as" <+> text "$$" > $+$ functionBody 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 > functionBody (SqlFnBody ann1 sts) = > annot ca ann1 <+> > nestedStatements nice ca sts > functionBody (PlpgsqlFnBody ann1 blk) = > annot ca ann1 <+> > statement nice True ca blk > paramDefn (ParamDef _ n t) = nmc n <+> typeName t > paramDefn (ParamDefTp _ t) = typeName t > > statement nice se ca (Block ann lb decls sts) = > annot ca ann <+> > label lb <> > ifNotEmpty (\l -> text "declare" > $+$ nest 2 (vcat $ map varDefn l)) decls > $+$ text "begin" > $+$ nestedStatements nice ca sts > $+$ text "end" <> statementEnd se > where > varDefn (VarDef _ n t v) = > nmc n <+> typeName t > <+> maybePrint (\x -> text ":=" <+> scalExpr nice x) v <> semi > varDefn (VarAlias _ n n1) = > nmc n <+> text "alias for" <+> name n1 <> semi > varDefn (ParamAlias _ n p) = > nmc n <+> text "alias for $" <> text (show p) <> semi > > > statement nice se ca (CreateView ann nm cols sel) = > annot ca ann <+> > text "create view" <+> name nm > <> case cols of > Nothing -> empty > Just cs -> parens (sepCsvMap nmc cs) > <+> text "as" > $+$ nest 2 (queryExpr nice True True Nothing sel) <> statementEnd se > > statement nice se ca (CreateDomain ann nm tp n ex) = > annot ca ann <+> > text "create domain" <+> name nm <+> text "as" > <+> typeName tp <+> cname <+> checkExp ex <> statementEnd se > where > checkExp = maybePrint (\e -> text "check" <+> parens (scalExpr nice e)) > cname = if n == "" > then empty > else text "constraint" <+> text n > > statement _nice se ca (DropFunction ann ifE fns casc) = > annot ca ann <+> > text "drop function" > <+> ifExists ifE > <+> sepCsvMap doFunction fns > <+> cascade casc > <> statementEnd se > where > doFunction (nm,types) = > name nm <> parens (sepCsvMap typeName types) > > statement _nice se ca (DropSomething ann dropType ifE names casc) = > annot ca ann <+> > text "drop" > <+> text (case dropType of > Table -> "table" > View -> "view" > Domain -> "domain" > Type -> "type") > <+> ifExists ifE > <+> sepCsvMap name names > <+> cascade casc > <> statementEnd se > > statement _nice se ca (CreateType ann nm atts) = > annot ca ann <+> > text "create type" <+> name nm <+> text "as" <+> lparen > $+$ nest 2 (vcat (csv > (map (\(TypeAttDef _ n t) -> nmc n <+> typeName t) atts))) > $+$ rparen <> statementEnd se > > statement _nice se ca (CreateLanguage ann nm) = > annot ca ann <+> > text "create language" <+> text nm <> statementEnd se > > statement nice se ca (CreateTrigger ann nm wh events tbl firing fnName fnArgs) = > annot ca ann <+> > text "create trigger" <+> nmc nm > <+> text (case wh of > TriggerBefore -> "before" > TriggerAfter -> "after") > <+> evs > <+> text "on" <+> name tbl > <+> text "for" <+> text (case firing of > EachRow -> "row" > EachStatement -> "statement") > <+> text "execute procedure" <+> name fnName > <> parens (sepCsvMap (scalExpr nice) fnArgs) <> statementEnd se > where > evs = sep $ punctuate (text " or ") $ map > (text . (\e -> case e of > TInsert -> "insert" > TUpdate -> "update" > TDelete -> "delete" > AntiTriggerEvent s -> "$(" ++ s ++ ")")) events > > -- plpgsql > > statement _nice se ca (NullStatement ann) = > annot ca ann <+> text "null" <> statementEnd se > statement _nice se ca (ExitStatement ann lb) = > annot ca ann <+> text "exit" > <+> maybe empty text lb <> statementEnd se > > statement nice se ca (Into ann str is (QueryStatement _ q)) = > annot ca ann <+> > queryExpr nice True True (Just (str,is)) q <> statementEnd se > statement nice se ca (Into ann str into st) = > annot ca ann <+> > statement nice False ca st > <+> text "into" > <> (if str > then empty <+> text "strict" > else empty) > <+> sepCsvMap name into > <> statementEnd se > --fixme, should be insert,update,delete,execute > statement nice se ca (Assignment ann nm val) = > annot ca ann <+> > name nm <+> text ":=" <+> scalExpr nice val <> statementEnd se > > statement nice se ca (Return ann ex) = > annot ca ann <+> > text "return" <+> maybePrint (scalExpr nice) ex <> statementEnd se > > statement nice se ca (ReturnNext ann ex) = > annot ca ann <+> > text "return" <+> text "next" <+> scalExpr nice ex <> statementEnd se > > statement nice se ca (ReturnQuery ann sel) = > annot ca ann <+> > text "return" <+> text "query" > <+> queryExpr nice True True Nothing sel <> statementEnd se > > statement nice se ca (Raise ann rt st exps) = > annot ca ann <+> > text "raise" > <+> case rt of > RNotice -> text "notice" > RException -> text "exception" > RError -> text "error" > <+> scalExpr nice (StringLit emptyAnnotation st) > <> ifNotEmpty (\e -> comma <+> csvExp nice e) exps > <> statementEnd se > > statement nice se ca (ForQueryStatement ann lb i sel stmts) = > annot ca ann <+> > label lb <> > text "for" <+> nmc i <+> text "in" > <+> queryExpr nice True True Nothing sel <+> text "loop" > $+$ nestedStatements nice ca stmts > $+$ text "end loop" <> statementEnd se > > statement nice se ca (ForIntegerStatement ann lb var st en stmts) = > annot ca ann <+> > label lb <> > text "for" <+> nmc var <+> text "in" > <+> scalExpr nice st <+> text ".." <+> scalExpr nice en <+> text "loop" > $+$ nestedStatements nice ca stmts > $+$ text "end loop" <> statementEnd se > > statement nice se ca (WhileStatement ann lb ex stmts) = > annot ca ann <+> > label lb <> > text "while" <+> scalExpr nice ex <+> text "loop" > $+$ nestedStatements nice ca stmts > $+$ text "end loop" <> statementEnd se > statement nice se ca (LoopStatement ann lb stmts) = > annot ca ann <+> > label lb <> > text "loop" > $+$ nestedStatements nice ca stmts > $+$ text "end loop" <> statementEnd se > > statement _nice se ca (ContinueStatement ann lb) = > annot ca ann <+> text "continue" > <+> maybe empty text lb <> statementEnd se > statement nice se ca (Perform ann f@(FunCall _ _ _)) = > annot ca ann <+> > text "perform" <+> scalExpr nice f <> statementEnd se > statement _ _ _ (Perform _ x) = > error $ "internal error: statement not supported for " ++ show x > > statement _nice se ca (Copy ann tb cols src) = > annot ca ann <+> > text "copy" <+> name tb > <+> ifNotEmpty (parens . sepCsvMap nmc) cols > <+> text "from" > <+> case src of > CopyFilename s -> quotes $ text s <> statementEnd se > Stdin -> text "stdin" <> text ";" > > statement _ _ ca (CopyData ann s) = > annot ca ann <+> > text s <> text "\\." <> newline > > statement nice se ca (If ann conds els) = > annot ca ann <+> > text "if" <+> constraintd (head conds) > $+$ vcat (map (\c -> text "elseif" <+> constraintd c) $ tail conds) > $+$ ifNotEmpty (\e -> text "else" $+$ nestedStatements nice ca e) els > $+$ text "end if" <> statementEnd se > where > constraintd (ex, sts) = scalExpr nice ex <+> text "then" > $+$ nestedStatements nice ca sts > statement nice se ca (Execute ann s) = > annot ca ann <+> > text "execute" <+> scalExpr nice s <> statementEnd se > > > statement nice se ca (CaseStatementSimple ann c conds els) = > annot ca ann <+> > text "case" <+> scalExpr nice c > $+$ nest 2 ( > vcat (map (uncurry whenStatement) conds) > $+$ elseStatement els > ) $+$ text "end case" <> statementEnd se > where > whenStatement ex sts = text "when" <+> sepCsvMap (scalExpr nice) ex > <+> text "then" $+$ nestedStatements nice ca sts > elseStatement = ifNotEmpty (\s -> text "else" > $+$ nestedStatements nice ca s) > statement nice se ca (CaseStatement ann conds els) = > annot ca ann <+> > text "case" > $+$ nest 2 ( > vcat (map (uncurry whenStatement) conds) > $+$ elseStatement els > ) $+$ text "end case" <> statementEnd se > where > whenStatement ex sts = text "when" <+> sepCsvMap (scalExpr nice) ex > <+> text "then" $+$ nestedStatements nice ca sts > elseStatement = ifNotEmpty (\s -> text "else" > $+$ nestedStatements nice ca s) > > -- misc > > statement _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 > > statement _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 > > queryExpr :: Bool -> Bool -> Bool -> Maybe (Bool,[Name]) -> QueryExpr -> Doc > queryExpr nice writeSelect _ intoi (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 > [fmap (\(str,is) -> text "into" > <+> (if str > then text "strict" > else empty) > <+> sepCsvMap name is) intoi > ,Just $ nest 2 $ selectList nice l > ,Just $ if null tb > then empty > else text "from" $+$ nest 2 (sepCsvMap (tref nice) tb) > ,Just $ whr nice wh > ,case grp of > [] -> Nothing > g -> Just $ text "group by" $+$ nest 2 (sepCsvMap (scalExpr nice) g) > ,flip fmap hav $ \h -> text "having" $+$ nest 2 (scalExpr nice h) > ,Just $ orderBy nice order > ,flip fmap lim $ \lm -> text "limit" <+> scalExpr nice lm > ,flip fmap off $ \offs -> text "offset" <+> scalExpr nice offs > ]) > > queryExpr nice writeSelect topLev _ (CombineQueryExpr _ tp s1 s2) = > let p = queryExpr nice writeSelect False Nothing s1 > $+$ (case tp of > Except -> text "except" > Union -> text "union" > UnionAll -> text "union" <+> text "all" > Intersect -> text "intersect") > $+$ queryExpr nice True False Nothing s2 > in if topLev then p else parens p > queryExpr nice _ _ _ (Values _ expss) = > text "values" $$ nest 2 (vcat $ csv $ map (parens . csvExp nice) expss) > queryExpr nice _ _ _ (WithQueryExpr _ wqs ex) = > text "with" $$ nest 2 (vcat $ csv $ map pwq wqs) > $+$ queryExpr nice True False Nothing ex > where > pwq (WithQuery _ nm cs ex1) = > nmc nm <> case cs of > Nothing -> empty > Just cs' -> parens $ sepCsvMap nmc cs' > <+> text "as" > <+> parens (queryExpr nice True False Nothing ex1) > name :: Name -> Doc > name (Name _ ns) = nmcs ns > nmcs :: [NameComponent] -> Doc > nmcs ns = hcat $ punctuate (text ".") $ map nmc ns > nmc :: NameComponent -> Doc > nmc (Nmc ns) = text ns > nmc (QNmc ns) = doubleQuotes $ text ns > > tref :: Bool -> TableRef -> Doc > {-tref nice (Tref _ f@(SQIdentifier _ t) (TableAlias _ ta)) > | nice, last t == ta = name f > -- slightly bad hack: > tref nice (Tref _ f@(SQIdentifier _ t) (FullAlias _ ta _)) > | nice, last t == ta = name f-} > tref nice (Tref _ f a) = name f <+> trefAlias nice a > tref nice (JoinTref _ t1 nat jt t2 ex a) = > parens (tref 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" > <+> tref nice t2 > <+> maybePrint (nest 2 . joinScalarExpr) ex > <+> trefAlias nice a) > where > joinScalarExpr (JoinOn _ e) = text "on" <+> scalExpr nice e > joinScalarExpr (JoinUsing _ ids) = > text "using" <+> parens (sepCsvMap nmc ids) > > tref nice (SubTref _ sub alias) = > parens (queryExpr nice True True Nothing sub) > <+> text "as" <+> trefAlias nice alias > tref nice (FunTref _ f@(FunCall _ _ _) a) = scalExpr nice f <+> trefAlias nice a > tref _nice (FunTref _ x _) = > error $ "internal error: node not supported in function tref: " > ++ show x > > trefAlias :: Bool -> TableAlias -> Doc > trefAlias _ (NoAlias _) = empty > trefAlias _ (TableAlias _ t) = nmc t > -- hack this out for now. When the type checking is fixed, can try > -- to eliminate unneeded aliases? > trefAlias nice (FullAlias _ t s) = > nmc t <> (if nice > then empty > else parens (sepCsvMap nmc s)) > direction :: Direction -> Doc > direction d = text $ case d of > Asc -> "asc" > Desc -> "desc" > > whr :: Bool -> Maybe ScalarExpr -> Doc > whr nice (Just ex) = text "where" $+$ nest 2 (scalExpr nice ex) > whr _ Nothing = empty > > selectList :: Bool -> SelectList -> Doc > selectList nice (SelectList _ ex) = > sepCsvMap selectItem ex > -- <+> ifNotEmpty (\i -> text "into" <+> hcatCsvMap scalExpr i) into > where > -- try to avoid printing alias if not necessary > selectItem (SelectItem _ ex1@(QIdentifier _ is) nm) | nice, last is == nm = scalExprSl nice ex1 > selectItem (SelectItem _ ex1@(Identifier _ i) nm) | nice, i == nm = scalExprSl nice ex1 > selectItem (SelectItem _ ex1 nm) = scalExprSl nice ex1 <+> text "as" <+> nmc nm > selectItem (SelExp _ e) = scalExprSl nice e > > cascade :: Cascade -> Doc > cascade casc = text $ case casc of > Cascade -> "cascade" > Restrict -> "restrict" > -- ddl > > constraint :: Bool -> Constraint -> Doc > constraint _nice (UniqueConstraint _ n c) = > mname n <+> text "unique" > <+> parens (sepCsvMap nmc c) > constraint _nice (PrimaryKeyConstraint _ n p) = > mname n <+> > text "primary key" > <+> parens (sepCsvMap nmc p) > constraint nice (CheckConstraint _ n c) = > mname n <+> text "check" <+> parens (scalExpr nice c) > constraint _nice (ReferenceConstraint _ n at tb rat ondel onupd) = > mname n <+> > text "foreign key" <+> parens (sepCsvMap nmc at) > <+> text "references" <+> name tb > <+> ifNotEmpty (parens . sepCsvMap nmc) rat > <+> text "on update" <+> cascade onupd > <+> text "on delete" <+> cascade ondel > > mname :: String -> Doc > mname n = if n == "" > then empty > else text "constraint" <+> text n > > returning :: Bool -> Maybe SelectList -> Doc > returning nice l = case l of > Nothing -> empty > Just ls -> nest 2 (text "returning" <+> selectList nice ls) > > ifExists :: IfExists -> Doc > ifExists i = case i of > Require -> empty > IfExists -> text "if exists" > > -- plpgsql > > nestedStatements :: Bool -> (Annotation -> String) -> StatementList -> Doc > nestedStatements nice pa = nest 2 . vcat . map (statement nice True pa) > > typeName :: TypeName -> Doc > typeName (SimpleTypeName _ s) = text s > typeName (PrecTypeName _ s i) = text s <> parens(integer i) > typeName (Prec2TypeName _ s i i1) = text s <> parens (sepCsv [integer i, integer i1]) > typeName (ArrayTypeName _ t) = typeName t <> text "[]" > typeName (SetOfTypeName _ t) = text "setof" <+> typeName t > > -- expressions > > scalExpr :: Bool -> ScalarExpr -> Doc > scalExpr _ (AntiScalarExpr s) = text $ "$(" ++ s ++ ")" > scalExpr _ (Star _) = text "*" > scalExpr _ (QStar _ i) = nmc i <> text ".*" > scalExpr _ (Identifier _ i) = nmc 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` "*_."-} > scalExpr _nice (QIdentifier _a [i1, i]) = parens (nmc i1) <> text "." <> nmc i > scalExpr _nice (QIdentifier _a _) = error "only supports 2 part qualified identifers atm" > --scalExpr nice (QIdentifier a e i) = parens (scalExpr nice e) <> text "." <> scalExpr nice (Identifier a i) > --scalExpr (PIdentifier _ i) = parens $ scalExpr i > scalExpr _ (NumberLit _ n) = text n > scalExpr _ (StringLit _ s) = -- needs some thought about using $$? > text "'" <> text replaceQuotes <> text "'" > where > replaceQuotes = replace "'" "''" s {-if tag == "'" > then replace "'" "''" s > else s-} > > scalExpr nice (FunCall _ n es) = > --check for special operators > case getTName n of > Just "!and" | nice, [a,b] <- es -> doLeftAnds a b > Just "!arrayctor" -> text "array" <> brackets (csvExp nice es) > Just "!between" -> scalExpr nice (head es) <+> text "between" > <+> parens (scalExpr nice (es !! 1)) > <+> text "and" > <+> parens (scalExpr nice (es !! 2)) > Just "!substring" -> text "substring" > <> parens (scalExpr nice (head es) > <+> text "from" <+> scalExpr nice (es !! 1) > <+> text "for" <+> scalExpr nice (es !! 2)) > Just "!arraysub" -> case es of > (Identifier _ i : es1) -> nmc i > <> brackets (csvExp nice es1) > _ -> parens (scalExpr nice (head es)) > <> brackets (csvExp nice (tail es)) > Just "!rowctor" -> text "row" <> parens (sepCsvMap (scalExpr nice) es) > Just "." -- special case to avoid ws around '.'. Don't know if this is important > -- or just cosmetic > | [a,b] <- es -> parens (scalExpr nice a) <> text "." <> scalExpr nice b > Just n' | isOperatorName n' -> > case forceRight (getOperatorType defaultTemplate1Catalog n') of > BinaryOp -> > let e1d = scalExpr nice (head es) > opd = text $ filterKeyword n' > e2d = scalExpr 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 (scalExpr nice (head es))) > PostfixOp -> parens (scalExpr nice (head es) > <+> text (filterKeyword n')) > _ -> name 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 ((scalExpr nice (head as) > : map (\x -> text "and" <+> scalExpr nice x) (tail as)) > ++ [text "and" <+> scalExpr nice b]) > and' a = case a of > FunCall _ f [x,y] | Just "!and" <- getTName f -> and' x ++ and' y > _ -> [a] > > scalExpr _ (BooleanLit _ b) = bool b > scalExpr nice (InPredicate _ att t lst) = > scalExpr nice att <+> (if not t then text "not" else empty) <+> text "in" > <+> parens (case lst of > InList _ expr -> csvExp nice expr > InQueryExpr _ sel -> queryExpr nice True True Nothing sel) > scalExpr nice (LiftOperator _ op flav args) = > scalExpr nice (head args) <+> text op > <+> text (case flav of > LiftAny -> "any" > LiftAll -> "all") > <+> parens (scalExpr nice $ head $ tail args) > scalExpr nice (ScalarSubQuery _ s) = parens (queryExpr nice True True Nothing s) > scalExpr _ (NullLit _) = text "null" > scalExpr nice (WindowFn _ fn part order frm) = > scalExpr nice fn <+> text "over" > <+> parens (if hp || ho > then (if hp > then text "partition by" <+> csvExp nice part > else empty) > <+> orderBy nice order > <+> frameStuff > else empty) > where > hp = not (null part) > ho = not (null order) > frameStuff = case frm of > FrameUnboundedPreceding -> text "range unbounded preceding" > FrameUnboundedFull -> text "range between unbounded preceding and unbounded following" > FrameRowsUnboundedPreceding -> text "rows unbounded preceding" > > scalExpr nice (AggregateFn _ d (FunCall _ fn es) o) = > name fn <> parens ((case d of > Dupes -> text "all" > Distinct -> text "distinct") > <+> csvExp nice es > <+> orderBy nice o) > scalExpr _ (AggregateFn _ _ _ _) = error "bad syntax for aggregate function" > scalExpr nice (Case _ whens els) = > text "case" > $+$ nest 2 (vcat (map whn whens) > $+$ maybePrint (\e -> text "else" <+> scalExpr nice e) els) > $+$ text "end" > where > whn (ex1, ex2) = > text "when" <+> sepCsvMap (scalExpr nice) ex1 > <+> text "then" <+> scalExpr nice ex2 > > scalExpr nice (CaseSimple _ val whens els) = > text "case" <+> scalExpr nice val > $+$ nest 2 (vcat (map whn whens) > $+$ maybePrint (\e -> text "else" <+> scalExpr nice e) els) > $+$ text "end" > where > whn (ex1, ex2) = > text "when" <+> sepCsvMap (scalExpr nice) ex1 > <+> text "then" <+> scalExpr nice ex2 > > scalExpr _ (PositionalArg _ a) = text "$" <> integer a > scalExpr _ (Placeholder _) = text "?" > scalExpr nice (Exists _ s) = > text "exists" <+> parens (queryExpr nice True True Nothing s) > scalExpr nice (Cast _ ex t) = text "cast" <> parens (scalExpr nice ex > <+> text "as" > <+> typeName t) > scalExpr nice (TypedStringLit a t s) = > typeName t <+> scalExpr nice (StringLit a s) > scalExpr nice (Interval a v f p) = > text "interval" <+> scalExpr nice (StringLit a v) > <+> intervalField <+> precision > where > intervalField = > 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" > precision = case p of > Nothing -> empty > Just i -> parens (int i) > scalExpr nice (Extract _ f e) = > text "extract" > <> parens (text field <+> text "from" <+> scalExpr nice e) > where > field = > 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" > scalExprSl :: Bool -> ScalarExpr -> Doc > scalExprSl nice (FunCall _ f es) | Just "." <- getTName f > , [a@(Identifier _ _), b] <- es = > parens (scalExprSl nice a) <> text "." <> scalExprSl nice b > scalExprSl nice x = scalExpr nice x > > set :: Bool -> SetClause -> Doc > set nice (SetClause _ a e) = > -- (FunCall _ "=" [Identifier _ a, e]) = > nmc a <+> text "=" <+> scalExpr nice e > {-set nice (FunCall _ "=" [a, b]) | (FunCall _ "!rowctor" is1) <- a > ,(FunCall _ "!rowctor" is2) <- b = > rsNoRow is1 <+> text "=" <+> rsNoRow is2 > where > rsNoRow is = parens (sepCsvMap (scalExpr nice) is) > set _ a = error $ "bad expression in set in update: " ++ show a-} > set nice (MultiSetClause _ is (FunCall _ f es)) | Just "!rowctor" <- getTName f = > parens (sepCsvMap nmc is) <+> text "=" > <+> parens (sepCsvMap (scalExpr nice) es) > set _ 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 (scalExpr nice) > > maybePrint :: (t -> Doc) -> Maybe t -> Doc > maybePrint 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 > orderBy :: Bool -> [(ScalarExpr,Direction)] -> Doc > orderBy _ [] = empty > orderBy nice os = > text "order by" > $+$ nest 2 (sepCsvMap (\(oe,od) -> scalExpr nice oe > <+> direction od) os) > --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" > > annot :: (Annotation -> String) -> Annotation -> Doc > annot ca a = let s = ca a > in if s == "" > then empty > else text "/*\n" <+> text s > <+> text "*/\n" > label :: Maybe String -> Doc > label = > maybe empty (\l -> text "<<" > <+> text l > <+> text ">>" <> text "\n") util: to be removed when outputting names is fixed > getTName :: Name -> Maybe String > getTName (Name _ [n]) = Just $ ncStr n > getTName _ = Nothing