packages feed

hssqlppp-0.1.0: Database/HsSqlPpp/Tests/ParserTests.lhs

Copyright 2009 Jake Wheat

The automated tests, uses hunit to check a bunch of text expressions
and sql statements parse to the correct tree, and then checks pretty
printing and then reparsing gives the same tree. The code was mostly
written in a tdd style, which the coverage of the tests reflects.

There are no tests for invalid sql at the moment.

temporarily disabled because of haddock issue:
{-# LANGUAGE QuasiQuotes #-}

> module Database.HsSqlPpp.Tests.ParserTests (parserTests) where

> import Test.HUnit
> import Test.Framework
> import Test.Framework.Providers.HUnit
> import Data.Char
> import Data.Generics

> -- import Database.HsSqlPpp.Here

> import Database.HsSqlPpp.Ast.Ast
> import Database.HsSqlPpp.Ast.Annotation
> import Database.HsSqlPpp.Parsing.Parser
> import Database.HsSqlPpp.PrettyPrinter.PrettyPrinter


> data Item = Expressions [(String, Expression)]
>           | Statements [(String, [Statement])]
>           | PlpgsqlStatements [(String, [Statement])]
>           | Group String [Item]

> parserTests :: [Test.Framework.Test]
> parserTests = itemToTft parserTestData

> parserTestData :: Item
> parserTestData =
>   Group "parserTests" [

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

expressions

>    Group "parse expressions" [
>     Group "basic expressions" [Expressions [
>       p "1" (IntegerLit [] 1)
>      ,p "-1" (FunCall [] "u-" [IntegerLit [] 1])
>      ,p "1.1" (FloatLit [] 1.1)
>      ,p "-1.1" (FunCall [] "u-" [FloatLit [] 1.1])
>      ,p " 1 + 1 " (FunCall [] "+" [IntegerLit [] 1
>                               ,IntegerLit [] 1])
>      ,p "1+1+1" (FunCall [] "+" [FunCall [] "+" [IntegerLit [] 1
>                                         ,IntegerLit [] 1]
>                             ,IntegerLit [] 1])
>      ]]


>    ,Group "parens" [Expressions [

check some basic parens use wrt naked values and row constructors
these tests reflect how pg seems to interpret the variants.

>       p "(1)" (IntegerLit [] 1)
>      ,p "row ()" (FunCall [] "!rowctor" [])
>      ,p "row (1)" (FunCall [] "!rowctor" [IntegerLit [] 1])
>      ,p "row (1,2)" (FunCall [] "!rowctor" [IntegerLit [] 1,IntegerLit [] 2])
>      ,p "(1,2)" (FunCall [] "!rowctor" [IntegerLit [] 1,IntegerLit [] 2])
>      ]]

>    ,Group "more basic expressions" [Expressions [

test some more really basic expressions

>       p "'test'" (stringQ "test")
>      ,p "''" (stringQ "")
>      ,p "hello" (Identifier [] "hello")
>      ,p "helloTest" (Identifier [] "helloTest")
>      ,p "hello_test" (Identifier [] "hello_test")
>      ,p "\"this is an identifier\"" (Identifier [] "this is an identifier")
>      ,p "hello1234" (Identifier [] "hello1234")
>      ,p "true" (BooleanLit [] True)
>      ,p "false" (BooleanLit [] False)
>      ,p "null" (NullLit [])
>      ]]

>    ,Group "array ctor and selector" [Expressions [
>       p "array[1,2]" (FunCall [] "!arrayctor" [IntegerLit [] 1, IntegerLit [] 2])
>      ,p "a[1]" (FunCall [] "!arraysub" [Identifier [] "a", IntegerLit [] 1])

>      ]]

>    ,Group "simple operators" [Expressions [
>       p "1 + tst1" (FunCall [] "+" [IntegerLit [] 1
>                                ,Identifier [] "tst1"])
>      ,p "tst1 + 1" (FunCall [] "+" [Identifier [] "tst1"
>                                ,IntegerLit [] 1])
>      ,p "tst + tst1" (FunCall [] "+" [Identifier [] "tst"
>                                  ,Identifier [] "tst1"])
>      ,p "'a' || 'b'" (FunCall [] "||" [stringQ "a"
>                                   ,stringQ "b"])
>      ,p "'stuff'::text" (Cast [] (stringQ "stuff") (SimpleTypeName [] "text"))
>      ,p "245::float(24)" (Cast [] (IntegerLit [] 245) (PrecTypeName [] "float" 24))

>      ,p "245::double precision" (Cast [] (IntegerLit [] 245) (SimpleTypeName [] "double precision"))

>      ,p "a between 1 and 3"
>         (FunCall [] "!between" [Identifier [] "a", IntegerLit [] 1, IntegerLit [] 3])
>      ,p "cast(a as text)"
>         (Cast [] (Identifier [] "a") (SimpleTypeName [] "text"))
>      ,p "@ a"
>         (FunCall [] "@" [Identifier [] "a"])

>      ,p "substring(a from 0 for 3)"
>         (FunCall [] "!substring" [Identifier [] "a", IntegerLit [] 0, IntegerLit [] 3])

>      ,p "substring(a from 0 for (5 - 3))"
>         (FunCall [] "!substring" [Identifier [] "a",IntegerLit [] 0,
>          FunCall [] "-" [IntegerLit [] 5,IntegerLit [] 3]])
>      ,p "a like b"
>         (FunCall [] "!like" [Identifier [] "a", Identifier [] "b"])
>      ]]

>    ,Group "function calls" [Expressions [
>       p "fn()" (FunCall [] "fn" [])
>      ,p "fn(1)" (FunCall [] "fn" [IntegerLit [] 1])
>      ,p "fn('test')" (FunCall [] "fn" [stringQ "test"])
>      ,p "fn(1,'test')" (FunCall [] "fn" [IntegerLit [] 1, stringQ "test"])
>      ,p "fn('test')" (FunCall [] "fn" [stringQ "test"])
>      ]]

>    ,Group "simple whitespace sanity checks" [Expressions [
>       p "fn (1)" (FunCall [] "fn" [IntegerLit [] 1])
>      ,p "fn( 1)" (FunCall [] "fn" [IntegerLit [] 1])
>      ,p "fn(1 )" (FunCall [] "fn" [IntegerLit [] 1])
>      ,p "fn(1) " (FunCall [] "fn" [IntegerLit [] 1])

>      ]]

>    ,Group "null stuff" [Expressions [
>       p "not null" (FunCall [] "!not" [NullLit []])
>      ,p "a is null" (FunCall [] "!isnull" [Identifier [] "a"])
>      ,p "a is not null" (FunCall [] "!isnotnull" [Identifier [] "a"])

>      ,p "not not true" (FunCall [] "!not"
>                          [FunCall [] "!not"
>                           [BooleanLit [] True]])
>      ]]

>    ,Group "case expressions" [Expressions [
>       p -- [$here|
>          "case when a,b then 3\n\
>          \     when c then 4\n\
>          \     else 5\n\
>          \end"
>          --    |]
>         (Case [] [([Identifier [] "a", Identifier [] "b"], IntegerLit [] 3)
>               ,([Identifier [] "c"], IntegerLit [] 4)]
>          (Just $ IntegerLit [] 5))

>      ,p  "case 1 when 2 then 3 else 4 end"
>         (CaseSimple [] (IntegerLit [] 1)
>            [([IntegerLit [] 2], IntegerLit [] 3)]
>          (Just $ IntegerLit [] 4))
>      ]]

>    ,Group "positional args" [Expressions [
>       p "$1" (PositionalArg [] 1)
>      ,p "?" (Placeholder [])
>      ,p "a = ?" (FunCall [] "=" [Identifier [] "a",Placeholder []])
>      ]]

>    ,Group "exists" [Expressions [
>       p "exists (select 1 from a)"
>       (Exists [] (selectFrom [SelExp [] (IntegerLit [] 1)] (Tref [] "a" NoAlias)))
>      ]]

>    ,Group "in variants" [Expressions [
>       p "t in (1,2)"
>       (InPredicate [] (Identifier [] "t") True (InList [] [IntegerLit [] 1,IntegerLit [] 2]))
>      ,p "t not in (1,2)"
>       (InPredicate [] (Identifier [] "t") False (InList [] [IntegerLit [] 1,IntegerLit [] 2]))
>      ,p "(t,u) in (1,2)"
>       (InPredicate [] (FunCall [] "!rowctor" [Identifier [] "t",Identifier [] "u"]) True
>        (InList [] [IntegerLit [] 1,IntegerLit [] 2]))
>      ,p "3 = any (array[1,2])"
>       (LiftOperator [] "=" LiftAny [IntegerLit [] 3
>                                     ,FunCall [] "!arrayctor" [IntegerLit [] 1
>                                                              ,IntegerLit [] 2]])
>      ,p "3 = all (array[1,2,4])"
>       (LiftOperator [] "=" LiftAll [IntegerLit [] 3
>                                     ,FunCall [] "!arrayctor" [IntegerLit [] 1
>                                                              ,IntegerLit [] 2
>                                                              ,IntegerLit [] 4]])
>      ]]

>    ,Group "comparison operators" [Expressions [
>       p "a < b"
>       (FunCall [] "<" [Identifier [] "a", Identifier [] "b"])
>      ,p "a <> b"
>       (FunCall [] "<>" [Identifier [] "a", Identifier [] "b"])
>      ,p "a != b"
>       (FunCall [] "<>" [Identifier [] "a", Identifier [] "b"])
>      ]]

test some string parsing, want to check single quote behaviour,
and dollar quoting, including nesting.

>    ,Group "string parsing" [Expressions [
>       p "''" (stringQ "")
>      ,p "''''" (stringQ "'")
>      ,p "'test'''" (stringQ "test'")
>      ,p "'''test'" (stringQ "'test")
>      ,p "'te''st'" (stringQ "te'st")
>      ,p "$$test$$" (StringLit [] "$$" "test")
>      ,p "$$te'st$$" (StringLit [] "$$" "te'st")
>      ,p "$st$test$st$" (StringLit [] "$st$" "test")
>      ,p "$outer$te$$yup$$st$outer$" (StringLit [] "$outer$" "te$$yup$$st")
>      ,p "'spl$$it'" (stringQ "spl$$it")
>      ]]
>      ]

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

select statements

>   ,Group "simple select statements" [
>     Group "select no table" [Statements [
>       p "select 1;" [SelectStatement [] $ selectE (SelectList [] [SelExp [] (IntegerLit [] 1)] [])]
>      ]]
>    ,Group "select from table" [Statements [
>       p "select * from tbl;"
>       [SelectStatement [] $ selectFrom (selIL ["*"]) (Tref [] "tbl" NoAlias)]
>      ,p "select a,b from tbl;"
>       [SelectStatement [] $ selectFrom (selIL ["a", "b"]) (Tref [] "tbl" NoAlias)]

>      ,p "select a,b from inf.tbl;"
>       [SelectStatement [] $ selectFrom (selIL ["a", "b"]) (Tref [] "inf.tbl" NoAlias)]

>      ,p "select distinct * from tbl;"
>       [SelectStatement [] $ Select [] Distinct (SelectList [] (selIL ["*"]) []) [Tref [] "tbl" NoAlias]
>        Nothing [] Nothing [] Nothing Nothing]

>      ,p "select a from tbl where b=2;"
>       [SelectStatement [] $ selectFromWhere
>         (selIL ["a"])
>         (Tref [] "tbl" NoAlias)
>         (FunCall [] "="
>          [Identifier [] "b", IntegerLit [] 2])]
>      ,p "select a from tbl where b=2 and c=3;"
>       [SelectStatement [] $ selectFromWhere
>         (selIL ["a"])
>         (Tref [] "tbl" NoAlias)
>         (FunCall [] "!and"
>          [FunCall [] "="  [Identifier [] "b", IntegerLit [] 2]
>          ,FunCall [] "=" [Identifier [] "c", IntegerLit [] 3]])]

>      ]]

>    ,Group "more select statements" [Statements [
>       p "select a from tbl\n\
>         \except\n\
>         \select a from tbl1;"
>       [SelectStatement [] $ CombineSelect [] Except
>        (selectFrom (selIL ["a"]) (Tref [] "tbl" NoAlias))
>        (selectFrom (selIL ["a"]) (Tref [] "tbl1" NoAlias))]
>      ,p "select a from tbl where true\n\
>         \except\n\
>         \select a from tbl1 where true;"
>       [SelectStatement [] $ CombineSelect [] Except
>        (selectFromWhere (selIL ["a"]) (Tref [] "tbl" NoAlias) (BooleanLit [] True))
>        (selectFromWhere (selIL ["a"]) (Tref [] "tbl1" NoAlias) (BooleanLit [] True))]
>      ,p "select a from tbl\n\
>         \union\n\
>         \select a from tbl1;"
>       [SelectStatement [] $ CombineSelect [] Union
>        (selectFrom (selIL ["a"]) (Tref [] "tbl" NoAlias))
>        (selectFrom (selIL ["a"]) (Tref [] "tbl1" NoAlias))]
>      ,p "select a from tbl\n\
>         \union all\n\
>         \select a from tbl1;"
>       [SelectStatement [] $ CombineSelect [] UnionAll
>        (selectFrom (selIL ["a"]) (Tref [] "tbl" NoAlias))
>        (selectFrom (selIL ["a"]) (Tref [] "tbl1" NoAlias))]

>      ,p "(select 1 union select 2) union select 3;"
>       [SelectStatement []
>        (CombineSelect [] Union
>         (CombineSelect [] Union
>          (selectE (SelectList [] [SelExp [] (IntegerLit [] 1)] []))
>          (selectE (SelectList [] [SelExp [] (IntegerLit [] 2)] [])))
>         (selectE (SelectList [] [SelExp [] (IntegerLit [] 3)] [])))]

>      ,p "select 1 union (select 2 union select 3);"
>       [SelectStatement []
>        (CombineSelect [] Union
>         (selectE (SelectList [] [SelExp [] (IntegerLit [] 1)] []))
>         (CombineSelect [] Union
>          (selectE (SelectList [] [SelExp [] (IntegerLit [] 2)] []))
>          (selectE (SelectList [] [SelExp [] (IntegerLit [] 3)] []))))]

>      ,p "select a as b from tbl;"
>       [SelectStatement [] $ selectFrom [SelectItem [] (Identifier [] "a") "b"] (Tref [] "tbl" NoAlias)]
>      ,p "select a + b as b from tbl;"
>       [SelectStatement [] $ selectFrom
>        [SelectItem []
>         (FunCall [] "+"
>          [Identifier [] "a", Identifier [] "b"]) "b"]
>        (Tref [] "tbl" NoAlias)]
>      ,p "select a.* from tbl a;"
>       [SelectStatement [] $ selectFrom (selIL ["a.*"]) (Tref [] "tbl" (TableAlias "a"))]

>      ,p "select a.* from tbl a(b,c);"
>       [SelectStatement [] $ selectFrom (selIL ["a.*"]) (Tref [] "tbl" (FullAlias "a" ["b","c"]))]

>      ,p "select * from t1 a, t2 b;"
>             [SelectStatement []
>              (Select [] Dupes
>               (SelectList []
>                [SelExp [] (Identifier [] "*")] [])
>               [Tref [] "t1" (TableAlias "a"),Tref [] "t2" (TableAlias "b")]
>               Nothing [] Nothing [] Nothing Nothing)]
>      ,p "select a from b inner join c on b.a=c.a;"
>       [SelectStatement [] $ selectFrom
>        (selIL ["a"])
>        (JoinedTref [] (Tref [] "b" NoAlias) Unnatural Inner (Tref [] "c" NoAlias)
>           (Just (JoinOn []
>            (FunCall [] "=" [Identifier [] "b.a", Identifier [] "c.a"]))) NoAlias)]
>      ,p "select a from b inner join c as d on b.a=d.a;"
>       [SelectStatement [] $ selectFrom
>        (selIL ["a"])
>        (JoinedTref [] (Tref [] "b" NoAlias) Unnatural Inner (Tref [] "c" (TableAlias "d"))
>           (Just (JoinOn []
>            (FunCall [] "=" [Identifier [] "b.a", Identifier [] "d.a"]))) NoAlias)]

>      ,p "select a from b inner join c using(d,e);"
>       [SelectStatement [] $ selectFrom
>        (selIL ["a"])
>        (JoinedTref [] (Tref [] "b" NoAlias) Unnatural Inner (Tref [] "c" NoAlias)
>           (Just (JoinUsing [] ["d","e"])) NoAlias)]

>      ,p "select a from b natural inner join c;"
>       [SelectStatement [] $ selectFrom
>        (selIL ["a"])
>        (JoinedTref [] (Tref [] "b" NoAlias) Natural Inner (Tref [] "c" NoAlias) Nothing NoAlias)]
>      ,p "select a from b left outer join c;"
>       [SelectStatement [] $ selectFrom
>        (selIL ["a"])
>        (JoinedTref [] (Tref [] "b" NoAlias) Unnatural LeftOuter (Tref [] "c" NoAlias) Nothing NoAlias)]
>      ,p "select a from b full outer join c;"
>       [SelectStatement [] $ selectFrom
>        (selIL ["a"])
>        (JoinedTref [] (Tref [] "b" NoAlias) Unnatural FullOuter (Tref [] "c" NoAlias) Nothing NoAlias)]
>      ,p "select a from b right outer join c;"
>       [SelectStatement [] $ selectFrom
>        (selIL ["a"])
>        (JoinedTref [] (Tref [] "b" NoAlias) Unnatural RightOuter (Tref [] "c" NoAlias) Nothing NoAlias)]
>      ,p "select a from b cross join c;"
>       [SelectStatement [] $ selectFrom
>        (selIL ["a"])
>        (JoinedTref [] (Tref [] "b" NoAlias) Unnatural Cross (Tref [] "c" NoAlias) Nothing NoAlias)]

>      ,p "select a from (b natural join c);"
>       [SelectStatement [] $ selectFrom
>        (selIL ["a"])
>        (JoinedTref [] (Tref [] "b" NoAlias) Natural Inner (Tref [] "c" NoAlias) Nothing NoAlias)]

>      ,p "select x from ((a cross join b) cross join c);"
>        [SelectStatement []
>         (selectFrom (selIL ["x"])
>          (JoinedTref []
>          (JoinedTref []
>           (Tref [] "a" NoAlias)
>            Unnatural Cross
>           (Tref [] "b" NoAlias)
>           Nothing NoAlias)
>          Unnatural Cross
>          (Tref [] "c" NoAlias)
>          Nothing NoAlias))]

>      ,p "select x from (a cross join (b cross join c));"
>        [SelectStatement []
>         (selectFrom (selIL ["x"])
>          (JoinedTref []
>           (Tref [] "a" NoAlias)
>           Unnatural Cross
>           (JoinedTref []
>            (Tref [] "b" NoAlias)
>            Unnatural Cross
>            (Tref [] "c" NoAlias)
>            Nothing NoAlias)
>           Nothing NoAlias))]

>      ,p "select x from ((a cross join b) cross join c);"
>        [SelectStatement []
>         (selectFrom (selIL ["x"])
>          (JoinedTref []
>          (JoinedTref []
>           (Tref [] "a" NoAlias)
>            Unnatural Cross
>           (Tref [] "b" NoAlias)
>           Nothing NoAlias)
>          Unnatural Cross
>          (Tref [] "c" NoAlias)
>          Nothing NoAlias))]

>      ,p "select x from (a cross join b) cross join c;"
>        [SelectStatement []
>         (selectFrom (selIL ["x"])
>          (JoinedTref []
>          (JoinedTref []
>           (Tref [] "a" NoAlias)
>            Unnatural Cross
>           (Tref [] "b" NoAlias)
>           Nothing NoAlias)
>          Unnatural Cross
>          (Tref [] "c" NoAlias)
>          Nothing NoAlias))]

>      ,p "select x from ((a cross join b) cross join c) cross join d;"
>        [SelectStatement []
>         (selectFrom (selIL ["x"])
>          (JoinedTref []
>           (JoinedTref []
>            (JoinedTref []
>             (Tref [] "a" NoAlias)
>             Unnatural Cross
>             (Tref [] "b" NoAlias)
>             Nothing NoAlias)
>            Unnatural Cross
>            (Tref [] "c" NoAlias)
>            Nothing NoAlias)
>           Unnatural Cross
>           (Tref [] "d" NoAlias)
>           Nothing NoAlias))]


>      ,p "select a from b\n\
>         \    inner join c\n\
>         \      on true\n\
>         \    inner join d\n\
>         \      on 1=1;"
>       [SelectStatement [] $ selectFrom
>        [SelExp [] (Identifier [] "a")]
>        (JoinedTref []
>         (JoinedTref [] (Tref [] "b" NoAlias) Unnatural Inner (Tref [] "c" NoAlias)
>          (Just $ JoinOn [] (BooleanLit [] True)) NoAlias)
>         Unnatural Inner (Tref [] "d" NoAlias)
>         (Just  $ JoinOn [] (FunCall [] "="
>                [IntegerLit [] 1, IntegerLit [] 1])) NoAlias)]

>      ,p "select row_number() over(order by a) as place from tbl;"
>       [SelectStatement [] $ selectFrom [SelectItem []
>                    (WindowFn []
>                     (FunCall [] "row_number" [])
>                     []
>                     [Identifier [] "a"] Asc FrameUnboundedPreceding)
>                    "place"]
>        (Tref [] "tbl" NoAlias)]
>      ,p "select row_number() over(order by a asc) as place from tbl;"
>       [SelectStatement [] $ selectFrom [SelectItem []
>                    (WindowFn []
>                     (FunCall [] "row_number" [])
>                     []
>                     [Identifier [] "a"] Asc FrameUnboundedPreceding)
>                    "place"]
>        (Tref [] "tbl" NoAlias)]
>      ,p "select row_number() over(order by a desc) as place from tbl;"
>       [SelectStatement [] $ selectFrom [SelectItem []
>                    (WindowFn []
>                     (FunCall [] "row_number" [])
>                     []
>                     [Identifier [] "a"] Desc FrameUnboundedPreceding)
>                    "place"]
>        (Tref [] "tbl" NoAlias)]
>      ,p "select row_number()\n\
>         \over(partition by (a,b) order by c) as place\n\
>         \from tbl;"
>       [SelectStatement [] $ selectFrom [SelectItem []
>                    (WindowFn []
>                     (FunCall [] "row_number" [])
>                     [FunCall [] "!rowctor" [Identifier [] "a",Identifier [] "b"]]
>                     [Identifier [] "c"] Asc FrameUnboundedPreceding)
>                    "place"]
>        (Tref [] "tbl" NoAlias)]

>      ,p "select * from a natural inner join (select * from b) as a;"
>       [SelectStatement [] $ selectFrom
>        (selIL ["*"])
>        (JoinedTref [] (Tref [] "a" NoAlias) Natural
>         Inner (SubTref [] (selectFrom
>                         (selIL ["*"])
>                         (Tref [] "b" NoAlias)) (TableAlias "a"))
>         Nothing NoAlias)]

>      ,p "select * from a order by c;"
>       [SelectStatement [] $ Select []  Dupes
>        (sl (selIL ["*"]))
>        [Tref [] "a" NoAlias]
>        Nothing [] Nothing [(Identifier [] "c",Asc)] Nothing Nothing]

>      ,p "select *\n\
>            \from Adventure\n\
>            \order by Clicks desc, AdventureID;"
>       [SelectStatement [] $ Select [] Dupes
>        (sl (selIL ["*"]))
>        [Tref [] "Adventure" NoAlias]
>        Nothing [] Nothing [(Identifier [] "Clicks",Desc)
>                           ,(Identifier [] "AdventureID",Asc)] Nothing Nothing]

>      ,p "select * from a order by c,d asc;"
>       [SelectStatement [] $ Select [] Dupes
>        (sl (selIL ["*"]))
>        [Tref [] "a" NoAlias]
>        Nothing [] Nothing [(Identifier [] "c", Asc)
>                           ,(Identifier [] "d", Asc)] Nothing Nothing]

>      ,p "select * from a order by c,d desc;"
>       [SelectStatement [] $ Select [] Dupes
>        (sl (selIL ["*"]))
>        [Tref [] "a" NoAlias]
>        Nothing [] Nothing [(Identifier [] "c", Asc)
>                           ,(Identifier [] "d", Desc)] Nothing Nothing]

>      ,p "select * from a order by c limit 1;"
>       [SelectStatement [] $ Select [] Dupes
>        (sl (selIL ["*"]))
>        [Tref [] "a" NoAlias]
>        Nothing [] Nothing [(Identifier [] "c",Asc)] (Just (IntegerLit [] 1)) Nothing]

>      ,p "select * from a order by c offset 3;"
>       [SelectStatement [] $ Select [] Dupes
>        (sl (selIL ["*"]))
>        [Tref [] "a" NoAlias]
>        Nothing [] Nothing [(Identifier [] "c",Asc)] Nothing (Just $ IntegerLit [] 3)]

>      ,p "select a from (select b from c) as d;"
>         [SelectStatement [] $ selectFrom
>          (selIL ["a"])
>          (SubTref [] (selectFrom
>                    (selIL ["b"])
>                    (Tref [] "c" NoAlias))
>           (TableAlias "d"))]

>      ,p "select * from gen();"
>         [SelectStatement [] $ selectFrom (selIL ["*"]) (TrefFun [] (FunCall [] "gen" []) NoAlias)]
>      ,p "select * from gen() as t;"
>       [SelectStatement [] $ selectFrom
>        (selIL ["*"])
>        (TrefFun [] (FunCall [] "gen" [])(TableAlias  "t"))]

>      ,p "select a, count(b) from c group by a;"
>         [SelectStatement [] $ Select [] Dupes
>          (sl [selI "a", SelExp [] (FunCall [] "count" [Identifier [] "b"])])
>          [Tref [] "c" NoAlias] Nothing [Identifier [] "a"]
>          Nothing [] Nothing Nothing]

>      ,p "select a, count(b) as cnt from c group by a having cnt > 4;"
>         [SelectStatement [] $ Select [] Dupes
>          (sl [selI "a", SelectItem [] (FunCall [] "count" [Identifier [] "b"]) "cnt"])
>          [Tref [] "c" NoAlias] Nothing [Identifier [] "a"]
>          (Just $ FunCall [] ">" [Identifier [] "cnt", IntegerLit [] 4])
>          [] Nothing Nothing]

>      ,p "select a from (select 1 as a, 2 as b) x;"
>         [SelectStatement [] $ selectFrom
>          [selI "a"]
>          (SubTref [] (selectE $ SelectList []
>                                [SelectItem [] (IntegerLit [] 1) "a"
>                                ,SelectItem [] (IntegerLit [] 2) "b"] [])
>                   (TableAlias "x"))]
>      ]]

>    ,Group "multiple statements" [Statements [
>       p "select 1;\nselect 2;" [SelectStatement [] $ selectE $ sl [SelExp [] (IntegerLit [] 1)]
>                                ,SelectStatement [] $ selectE $ sl [SelExp [] (IntegerLit [] 2)]]
>      ]]

>    ,Group "comments" [Statements [
>       p "" []
>      ,p "-- this is a test" []
>      ,p "/* this is\n\
>         \a test*/" []
>      ,p "select 1;\n\
>         \-- this is a test\n\
>         \select -- this is a test\n\
>         \2;" [SelectStatement [] $ selectE $ sl [SelExp [] (IntegerLit [] 1)]
>              ,SelectStatement [] $ selectE $ sl [SelExp [] (IntegerLit [] 2)]
>              ]
>      ,p "select 1;\n\
>         \/* this is\n\
>         \a test*/\n\
>         \select /* this is a test*/2;"
>                     [SelectStatement [] $ selectE $ sl [SelExp [] (IntegerLit [] 1)]
>                     ,SelectStatement [] $ selectE $ sl [SelExp [] (IntegerLit [] 2)]
>                     ]
>      ]]
>      ]

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

dml statements

>    ,Group "dml" [
>      Group "insert" [Statements [
>       p "insert into testtable\n\
>         \(columna,columnb)\n\
>         \values (1,2);\n"
>        [Insert []
>         "testtable"
>         ["columna", "columnb"]
>         (Values [] [[IntegerLit [] 1, IntegerLit [] 2]])
>         Nothing]

multi row insert, test the stand alone values statement first, maybe
that should be in the select section?

>      ,p "values (1,2), (3,4);"
>      [SelectStatement [] $ Values [] [[IntegerLit [] 1, IntegerLit [] 2]
>              ,[IntegerLit [] 3, IntegerLit [] 4]]]

>      ,p "insert into testtable\n\
>         \(columna,columnb)\n\
>         \values (1,2), (3,4);\n"
>       [Insert []
>         "testtable"
>         ["columna", "columnb"]
>         (Values [] [[IntegerLit [] 1, IntegerLit [] 2]
>                 ,[IntegerLit [] 3, IntegerLit [] 4]])
>         Nothing]

insert from select

>      ,p "insert into a\n\
>          \    select b from c;"
>       [Insert [] "a" []
>        (selectFrom [selI "b"] (Tref [] "c" NoAlias))
>        Nothing]

>      ,p "insert into testtable\n\
>         \(columna,columnb)\n\
>         \values (1,2) returning id;\n"
>       [Insert []
>         "testtable"
>         ["columna", "columnb"]
>         (Values [] [[IntegerLit [] 1, IntegerLit [] 2]])
>         (Just $ sl [selI "id"])]
>      ]]

>     ,Group "update" [Statements [
>       p "update tb\n\
>         \  set x = 1, y = 2;"
>       [Update [] "tb" [SetClause [] "x" (IntegerLit [] 1)
>                    ,SetClause [] "y" (IntegerLit [] 2)]
>        Nothing Nothing]
>      ,p "update tb\n\
>         \  set x = 1, y = 2 where z = true;"
>       [Update [] "tb" [SetClause [] "x" (IntegerLit [] 1)
>                    ,SetClause [] "y" (IntegerLit [] 2)]
>        (Just $ FunCall [] "="
>         [Identifier [] "z", BooleanLit [] True])
>        Nothing]
>      ,p "update tb\n\
>         \  set x = 1, y = 2 returning id;"
>       [Update [] "tb" [SetClause [] "x" (IntegerLit [] 1)
>                    ,SetClause [] "y" (IntegerLit [] 2)]
>        Nothing (Just $ sl [selI "id"])]
>      ,p "update pieces\n\
>         \set a=b returning tag into r.tag;"
>       [Update [] "pieces" [SetClause [] "a" (Identifier [] "b")]
>        Nothing (Just (SelectList []
>                       [SelExp [] (Identifier [] "tag")]
>                       ["r.tag"]))]
>      ,p "update tb\n\
>         \  set (x,y) = (1,2);"
>       [Update [] "tb" [RowSetClause []
>                     ["x","y"]
>                     [IntegerLit [] 1,IntegerLit [] 2]]
>        Nothing Nothing]
>      ]]

>     ,Group "delete" [Statements [
>       p "delete from tbl1 where x = true;"
>       [Delete [] "tbl1" (Just $ FunCall [] "="
>                                [Identifier [] "x", BooleanLit [] True])
>        Nothing]
>      ,p "delete from tbl1 where x = true returning id;"
>       [Delete [] "tbl1" (Just $ FunCall [] "="
>                                [Identifier [] "x", BooleanLit [] True])
>        (Just $ sl [selI "id"])]
>      ]]

>     ,Group "truncate" [Statements [
>       p "truncate test;"
>        [Truncate [] ["test"] ContinueIdentity Restrict]

>      ,p "truncate table test, test2 restart identity cascade;"
>        [Truncate [] ["test","test2"] RestartIdentity Cascade]
>      ]]

copy, bit crap at the moment

>     ,Group "copy" [Statements [
>       p "copy tbl(a,b) from stdin;\n\
>         \bat\tt\n\
>         \bear\tf\n\
>         \\\.\n"
>       [Copy [] "tbl" ["a", "b"] Stdin
>        ,CopyData [] "\
>         \bat\tt\n\
>         \bear\tf\n"]
>      ]]

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

ddl statements

>    ,Group "ddl" [
>      Group "simple tables" [Statements [
>       p "create table test (\n\
>         \  fielda text,\n\
>         \  fieldb int\n\
>         \);"
>       [CreateTable []
>        "test"
>        [att "fielda" "text"
>        ,att "fieldb" "int"
>        ]
>        []]
>      ,p "create table tbl (\n\
>         \  fld boolean default false);"
>       [CreateTable [] "tbl" [AttributeDef [] "fld" (SimpleTypeName [] "boolean")
>                           (Just $ BooleanLit [] False) []][]]

>      ,p "create table tbl as select 1;"
>       [CreateTableAs [] "tbl"
>        (selectE (SelectList [] [SelExp [] (IntegerLit [] 1)] []))]


>      ,p "alter table a alter column b set default 1;"
>       [AlterTable [] "a" [AlterColumnDefault [] "b" (IntegerLit [] 1)]]

>      ,p "alter table a add constraint unique(b);"
>       [AlterTable [] "a" [AddConstraint [] (UniqueConstraint [] "" ["b"])]]
>      ]]

>     ,Group "others" [Statements [
>       p "create view v1 as\n\
>         \select a,b from t;"
>       [CreateView []
>        "v1"
>        (selectFrom [selI "a", selI "b"] (Tref [] "t" NoAlias))]
>      ,p "create domain td as text check (value in ('t1', 't2'));"
>       [CreateDomain [] "td" (SimpleTypeName [] "text") ""
>        (Just (InPredicate [] (Identifier [] "value") True
>               (InList [] [stringQ "t1" ,stringQ "t2"])))]
>      ,p "create type tp1 as (\n\
>         \  f1 text,\n\
>         \  f2 text\n\
>         \);"
>       [CreateType [] "tp1" [TypeAttDef [] "f1" (SimpleTypeName [] "text")
>                         ,TypeAttDef [] "f2" (SimpleTypeName [] "text")]]

>      ,p "create sequence s start with 5 increment by 4 no maxvalue no minvalue cache 1;"
>         [CreateSequence [] "s" 4 1 ((2::Integer) ^ (63::Integer) - 1) 5 1]

>      ,p "alter sequence s owned by a.b;"
>         [AlterSequence [] "s" "a.b"]

>      ,p "create trigger tr\n\
>          \after insert or delete on tb\n\
>          \for each statement\n\
>          \execute procedure fb();"
>         [CreateTrigger [] "tr" TriggerAfter [TInsert,TDelete] "tb" EachStatement "fb" []]
>      ]]

>     ,Group "drops" [Statements [
>       p "drop domain t;"
>       [DropSomething [] Domain Require ["t"] Restrict]
>      ,p "drop domain if exists t,u cascade;"
>       [DropSomething [] Domain IfExists ["t", "u"] Cascade]
>      ,p "drop domain t restrict;"
>       [DropSomething [] Domain Require ["t"] Restrict]

>      ,p "drop type t;"
>       [DropSomething [] Type Require ["t"] Restrict]
>      ,p "drop table t;"
>       [DropSomething [] Table Require ["t"] Restrict]
>      ,p "drop view t;"
>       [DropSomething [] View Require ["t"] Restrict]

>      ]]

>     ,Group "constraints" [
>       Group "nulls" [Statements [
>       p "create table t1 (\n\
>         \ a text null\n\
>         \);"
>         [CreateTable [] "t1" [AttributeDef [] "a" (SimpleTypeName [] "text")
>                            Nothing [NullConstraint [] ""]]
>          []]
>      ,p "create table t1 (\n\
>         \ a text not null\n\
>         \);"
>         [CreateTable [] "t1" [AttributeDef [] "a" (SimpleTypeName [] "text")
>                            Nothing [NotNullConstraint [] ""]]
>          []]
>      ]]

>      ,Group "unique" [Statements [
>       p "create table t1 (\n\
>         \ x int,\n\
>         \ y int,\n\
>         \ unique (x,y)\n\
>         \);"
>         [CreateTable [] "t1" [att "x" "int"
>                           ,att "y" "int"]
>          [UniqueConstraint [] "" ["x","y"]]]

test arbitrary ordering

>      ,p "create table t1 (\n\
>         \ x int,\n\
>         \ unique (x),\n\
>         \ y int\n\
>         \);"
>         [CreateTable [] "t1" [att "x" "int"
>                           ,att "y" "int"]
>          [UniqueConstraint [] "" ["x"]]]

unique row

>      ,p "create table t1 (\n\
>         \ x int unique\n\
>         \);"
>         [CreateTable [] "t1" [AttributeDef [] "x" (SimpleTypeName [] "int") Nothing
>                            [RowUniqueConstraint [] ""]][]]

>      ,p "create table t1 (\n\
>         \ x int unique not null\n\
>         \);"
>         [CreateTable [] "t1" [AttributeDef [] "x" (SimpleTypeName [] "int") Nothing
>                            [RowUniqueConstraint [] ""
>                            ,NotNullConstraint [] ""]][]]

quick sanity check

>      ,p "create table t1 (\n\
>         \ x int not null unique\n\
>         \);"
>         [CreateTable [] "t1" [AttributeDef [] "x" (SimpleTypeName [] "int") Nothing
>                            [NotNullConstraint [] ""
>                            ,RowUniqueConstraint [] ""]][]]
>      ]]

>      ,Group "primary key" [Statements [
>       p "create table t1 (\n\
>         \ x int primary key\n\
>         \);"
>         [CreateTable [] "t1" [AttributeDef [] "x" (SimpleTypeName [] "int") Nothing
>                            [RowPrimaryKeyConstraint [] ""]][]]

>      ,p "create table t1 (\n\
>         \ x int,\n\
>         \ y int,\n\
>         \ primary key (x,y)\n\
>         \);"
>         [CreateTable [] "t1" [att "x" "int"
>                           ,att "y" "int"]
>          [PrimaryKeyConstraint [] "" ["x", "y"]]]

>      ]]

>      ,Group "check" [Statements [
>       p "create table t (\n\
>         \f text check (f in('a', 'b'))\n\
>         \);"
>         [CreateTable [] "t"
>          [AttributeDef [] "f" (SimpleTypeName [] "text") Nothing
>           [RowCheckConstraint [] "" (InPredicate []
>                                   (Identifier [] "f") True
>                                   (InList [] [stringQ "a", stringQ "b"]))]] []]

>      ,p "create table t1 (\n\
>         \ x int,\n\
>         \ y int,\n\
>         \ check (x>y)\n\
>         \);"
>         [CreateTable [] "t1" [att "x" "int"
>                           ,att "y" "int"]
>          [CheckConstraint [] "" (FunCall [] ">" [Identifier [] "x", Identifier [] "y"])]]
>      ]]

>      ,Group "misc" [Statements [
>       p "create table t (\n\
>         \f text not null unique check (f in('a', 'b'))\n\
>         \);"
>         [CreateTable [] "t"
>          [AttributeDef [] "f" (SimpleTypeName [] "text") Nothing
>           [NotNullConstraint [] ""
>            ,RowUniqueConstraint [] ""
>            ,RowCheckConstraint [] "" (InPredicate []
>                                    (Identifier [] "f") True
>                                    (InList [] [stringQ "a"
>                                            ,stringQ "b"]))]] []]

>      ]]

>      ,Group "references" [Statements [
>       p "create table t1 (\n\
>         \ x int references t2\n\
>         \);"
>         [CreateTable [] "t1" [AttributeDef [] "x" (SimpleTypeName [] "int") Nothing
>                            [RowReferenceConstraint [] "" "t2" Nothing
>                             Restrict Restrict]][]]

>      ,p "create table t1 (\n\
>         \ x int references t2(y)\n\
>         \);"
>         [CreateTable [] "t1" [AttributeDef [] "x" (SimpleTypeName [] "int") Nothing
>                            [RowReferenceConstraint [] "" "t2" (Just "y")
>                             Restrict Restrict]][]]


>      ,p "create table t1 (\n\
>         \ x int,\n\
>         \ y int,\n\
>         \ foreign key (x,y) references t2\n\
>         \);"
>         [CreateTable [] "t1" [att "x" "int"
>                           ,att "y" "int"]
>          [ReferenceConstraint [] "" ["x", "y"] "t2" []
>           Restrict Restrict]]

>      ,p "create table t1 (\n\
>         \ x int,\n\
>         \ y int,\n\
>         \ foreign key (x,y) references t2(z,w)\n\
>         \);"
>         [CreateTable [] "t1" [att "x" "int"
>                           ,att "y" "int"]
>          [ReferenceConstraint [] "" ["x", "y"] "t2" ["z", "w"]
>           Restrict Restrict]]

>      ,p "create table t1 (\n\
>         \ x int references t2 on delete cascade\n\
>         \);"
>         [CreateTable [] "t1" [AttributeDef [] "x" (SimpleTypeName [] "int") Nothing
>                            [RowReferenceConstraint [] "" "t2" Nothing
>                             Cascade Restrict]][]]

>      ,p "create table t1 (\n\
>         \ x int references t2 on update cascade\n\
>         \);"
>         [CreateTable [] "t1" [AttributeDef [] "x" (SimpleTypeName [] "int") Nothing
>                            [RowReferenceConstraint [] "" "t2" Nothing
>                             Restrict Cascade]][]]

>      ,p "create table t1 (\n\
>         \ x int,\n\
>         \ y int,\n\
>         \ foreign key (x,y) references t2 on update cascade on delete cascade\n\
>         \);"
>         [CreateTable [] "t1" [att "x" "int"
>                           ,att "y" "int"]
>          [ReferenceConstraint [] "" ["x", "y"] "t2" []
>           Cascade Cascade]]

>      ]]
>      ]]
>    ,Group "functions" [
>      Group "basics" [Statements [
>       p "create function t1(text) returns text as $$\n\
>         \select a from t1 where b = $1;\n\
>         \$$ language sql stable;"
>       [CreateFunction [] "t1" [ParamDefTp [] $ SimpleTypeName [] "text"]
>        (SimpleTypeName [] "text") Sql "$$"
>        (SqlFnBody []
>         [SelectStatement [] $ selectFromWhere [SelExp [] (Identifier [] "a")] (Tref [] "t1" NoAlias)
>          (FunCall [] "="
>           [Identifier [] "b", PositionalArg [] 1])])
>        Stable]
>      ,p "create function fn() returns void as $$\n\
>         \declare\n\
>         \  a int;\n\
>         \  b text;\n\
>         \begin\n\
>         \  null;\n\
>         \end;\n\
>         \$$ language plpgsql volatile;"
>       [CreateFunction [] "fn" [] (SimpleTypeName [] "void") Plpgsql "$$"
>        (PlpgsqlFnBody [] [VarDef [] "a" (SimpleTypeName [] "int") Nothing
>                          ,VarDef [] "b" (SimpleTypeName [] "text") Nothing]
>         [NullStatement []])
>        Volatile]
>      ,p "create function fn() returns void as $$\n\
>         \declare\n\
>         \  a int;\n\
>         \  b text;\n\
>         \begin\n\
>         \  null;\n\
>         \end;\n\
>         \$$ language plpgsql volatile;"
>       [CreateFunction [] "fn" [] (SimpleTypeName [] "void") Plpgsql "$$"
>        (PlpgsqlFnBody [] [VarDef [] "a" (SimpleTypeName [] "int") Nothing
>                          ,VarDef [] "b" (SimpleTypeName [] "text") Nothing]
>         [NullStatement []])
>        Volatile]
>      ,p "create function fn(a text[]) returns int[] as $$\n\
>         \declare\n\
>         \  b xtype[] := '{}';\n\
>         \begin\n\
>         \  null;\n\
>         \end;\n\
>         \$$ language plpgsql immutable;"
>       [CreateFunction [] "fn"
>        [ParamDef [] "a" $ ArrayTypeName [] $ SimpleTypeName [] "text"]
>        (ArrayTypeName [] $ SimpleTypeName [] "int") Plpgsql "$$"
>        (PlpgsqlFnBody []
>         [VarDef [] "b" (ArrayTypeName [] $ SimpleTypeName [] "xtype") (Just $ stringQ "{}")]
>         [NullStatement []])
>        Immutable]
>      ,p "create function fn() returns void as '\n\
>         \declare\n\
>         \  a int := 3;\n\
>         \begin\n\
>         \  null;\n\
>         \end;\n\
>         \' language plpgsql stable;"
>       [CreateFunction [] "fn" [] (SimpleTypeName [] "void") Plpgsql "'"
>        (PlpgsqlFnBody [] [VarDef [] "a" (SimpleTypeName [] "int") (Just $ IntegerLit [] 3)]
>         [NullStatement []])
>        Stable]
>      ,p "create function fn() returns setof int as $$\n\
>         \begin\n\
>         \  null;\n\
>         \end;\n\
>         \$$ language plpgsql stable;"
>       [CreateFunction [] "fn" []
>        (SetOfTypeName [] $ SimpleTypeName [] "int") Plpgsql "$$"
>        (PlpgsqlFnBody [] [] [NullStatement []])
>        Stable]
>      ,p "create function fn() returns void as $$\n\
>         \begin\n\
>         \  null;\n\
>         \end\n\
>         \$$ language plpgsql stable;"
>       [CreateFunction [] "fn" []
>        (SimpleTypeName [] "void") Plpgsql "$$"
>        (PlpgsqlFnBody [] [] [NullStatement []])
>        Stable]
>      ,p "drop function test(text);"
>       [DropFunction [] Require [("test",[SimpleTypeName [] "text"])] Restrict]
>      ,p "drop function if exists a(),test(text) cascade;"
>       [DropFunction [] IfExists [("a",[])
>                           ,("test",[SimpleTypeName [] "text"])] Cascade]
>     ]]

>     ,Group "simple plpgsql statements" [PlpgsqlStatements [
>       p "success := true;"
>       [Assignment [] "success" (BooleanLit [] True)]
>      ,p "success = true;"
>       [Assignment [] "success" (BooleanLit [] True)]
>      ,p "return true;"
>       [Return [] $ Just (BooleanLit [] True)]
>      ,p "return;"
>       [Return [] Nothing]
>      ,p "return next 1;"
>       [ReturnNext [] $ IntegerLit [] 1]
>      ,p "return query select a from b;"
>       [ReturnQuery [] $ selectFrom [selI "a"] (Tref [] "b" NoAlias)]
>      ,p "raise notice 'stuff %', 1;"
>       [Raise [] RNotice "stuff %" [IntegerLit [] 1]]
>      ,p "perform test();"
>       [Perform [] $ FunCall [] "test" []]
>      ,p "perform test(a,b);"
>       [Perform [] $ FunCall [] "test" [Identifier [] "a", Identifier [] "b"]]
>      ,p "perform test(r.relvar_name || '_and_stuff');"
>       [Perform [] $ FunCall [] "test" [
>                     FunCall [] "||" [Identifier [] "r.relvar_name"
>                                 ,stringQ "_and_stuff"]]]
>      ,p "select into a,b c,d from e;"
>       [SelectStatement [] $ Select [] Dupes (SelectList [] [selI "c", selI "d"] ["a", "b"])
>                   [Tref [] "e" NoAlias] Nothing [] Nothing [] Nothing Nothing]
>      ,p "select c,d into a,b from e;"
>       [SelectStatement [] $ Select [] Dupes (SelectList [] [selI "c", selI "d"] ["a", "b"])
>                   [Tref [] "e" NoAlias] Nothing [] Nothing [] Nothing Nothing]

>      ,p "execute s;"
>       [Execute [] (Identifier [] "s")]
>      ,p "execute s into r;"
>       [ExecuteInto [] (Identifier [] "s") ["r"]]

>      ,p "continue;" [ContinueStatement []]
>     ]]

>     ,Group "other plpgsql statements" [PlpgsqlStatements [
>       p "for r in select a from tbl loop\n\
>         \null;\n\
>         \end loop;"
>       [ForSelectStatement [] "r" (selectFrom  [selI "a"] (Tref [] "tbl" NoAlias))
>        [NullStatement []]]
>      ,p "for r in select a from tbl where true loop\n\
>         \null;\n\
>         \end loop;"
>       [ForSelectStatement [] "r"
>        (selectFromWhere [selI "a"] (Tref [] "tbl" NoAlias) (BooleanLit [] True))
>        [NullStatement []]]
>      ,p "for r in 1 .. 10 loop\n\
>         \null;\n\
>         \end loop;"
>       [ForIntegerStatement [] "r"
>        (IntegerLit [] 1) (IntegerLit [] 10)
>        [NullStatement []]]

>      ,p "if a=b then\n\
>         \  update c set d = e;\n\
>         \end if;"
>       [If [] [((FunCall [] "=" [Identifier [] "a", Identifier [] "b"])
>           ,[Update [] "c" [SetClause [] "d" (Identifier [] "e")] Nothing Nothing])]
>        []]
>      ,p "if true then\n\
>         \  null;\n\
>         \else\n\
>         \  null;\n\
>         \end if;"
>       [If [] [((BooleanLit [] True),[NullStatement []])]
>        [NullStatement []]]
>      ,p "if true then\n\
>         \  null;\n\
>         \elseif false then\n\
>         \  return;\n\
>         \end if;"
>       [If [] [((BooleanLit [] True), [NullStatement []])
>           ,((BooleanLit [] False), [Return [] Nothing])]
>        []]
>      ,p "if true then\n\
>         \  null;\n\
>         \elseif false then\n\
>         \  return;\n\
>         \elseif false then\n\
>         \  return;\n\
>         \else\n\
>         \  return;\n\
>         \end if;"
>       [If [] [((BooleanLit [] True), [NullStatement []])
>           ,((BooleanLit [] False), [Return [] Nothing])
>           ,((BooleanLit [] False), [Return [] Nothing])]
>        [Return [] Nothing]]
>      ,p "case a\n\
>         \  when b then null;\n\
>         \  when c,d then null;\n\
>         \  else null;\n\
>         \end case;"
>      [CaseStatement [] (Identifier [] "a")
>       [([Identifier [] "b"], [NullStatement []])
>       ,([Identifier [] "c", Identifier [] "d"], [NullStatement []])]
>       [NullStatement []]]

>     ]]

>    ,Group "misc" [Statements [
>       p "SET search_path TO my_schema, public;"
>         [Set [] "search_path" [SetId [] "my_schema"
>                               ,SetId [] "public"]]
>      ,p "SET t1 = 3;"
>         [Set [] "t1" [SetNum [] 3]]
>      ,p "SET t1 = 'stuff';"
>         [Set [] "t1" [SetStr [] "stuff"]]
>      ,p "create language plpgsql;"
>         [CreateLanguage [] "plpgsql"]

>     ]]

>     ]]]

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

shortcuts for constructing test data and asts

> p :: t -> t1 -> (t, t1)
> p a b = (a,b)

> stringQ :: String -> Expression
> stringQ = StringLit [] "'"

> selectFrom :: SelectItemList
>            -> TableRef
>            -> SelectExpression
> selectFrom selList frm = Select [] Dupes (SelectList [] selList [])
>                            [frm] Nothing [] Nothing [] Nothing Nothing

> selectE :: SelectList -> SelectExpression
> selectE selList = Select [] Dupes selList
>                     [] Nothing [] Nothing [] Nothing Nothing

> selIL :: [String] -> [SelectItem]
> selIL = map selI

> selI :: String -> SelectItem
> selI = SelExp [] . Identifier []

> sl :: SelectItemList -> SelectList
> sl a = SelectList [] a []

> selectFromWhere :: SelectItemList
>                 -> TableRef
>                 -> Expression
>                 -> SelectExpression
> selectFromWhere selList frm whr =
>     Select [] Dupes (SelectList [] selList [])
>                [frm] (Just whr) [] Nothing [] Nothing Nothing

> att :: String -> String -> AttributeDef
> att n t = AttributeDef [] n (SimpleTypeName [] t) Nothing []


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

Unit test helpers

> itemToTft :: Item -> [Test.Framework.Test]
> itemToTft (Expressions es) = map (uncurry testParseExpression) es
> itemToTft (PlpgsqlStatements es) = map (uncurry testParsePlpgsqlStatements) es
> itemToTft (Statements es) = map (uncurry testParseStatements) es
> itemToTft (Group s is) = [testGroup s $ concatMap itemToTft is]


> testParseExpression :: String -> Expression -> Test.Framework.Test
> testParseExpression src ast = parseUtil src ast
>                                  (parseExpression "") printExpression

> testParseStatements :: String -> [Statement] -> Test.Framework.Test
> testParseStatements src ast = parseUtil src ast (parseSql "") printSql

> testParsePlpgsqlStatements :: String -> [Statement] -> Test.Framework.Test
> testParsePlpgsqlStatements src ast = parseUtil src ast (parsePlpgsql "") printSql

> parseUtil :: (Show t, Eq b, Show b, Data b) =>
>              String
>           -> b
>           -> (String -> Either t b)
>           -> (b -> String)
>           -> Test.Framework.Test
> parseUtil src ast parser printer = testCase ("parse " ++ src) $
>   case parser src of
>     Left er -> assertFailure $ show er
>     Right ast' -> do
>       assertEqual ("parse " ++ src) ast $ stripAnnotations ast'
>       case parser (printer ast) of
>         Left er -> assertFailure $ "reparse\n" ++ show er ++ "\n" -- ++ pp ++ "\n"
>         Right ast'' -> assertEqual ("reparse " ++ printer ast) ast $ stripAnnotations ast''

TODO
new idea for testing:
parsesql -> ast1
parse, pretty print, parse -> ast2
load into pg, pg_dump, parse -> ast3
parse, pretty print, load into pg, pg_dump, parse -> ast4
check all these asts are the same