hssqlppp-0.0.10: 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.
> module Database.HsSqlPpp.Tests.ParserTests (parserTests) where
> import Test.HUnit
> import Test.Framework
> import Test.Framework.Providers.HUnit
> import Data.Char
> import Database.HsSqlPpp.Ast.Ast
> import Database.HsSqlPpp.Ast.Annotation
> import Database.HsSqlPpp.Ast.Annotation
> import Database.HsSqlPpp.Parsing.Parser
> import Database.HsSqlPpp.PrettyPrinter.PrettyPrinter
> parserTests :: Test.Framework.Test
> parserTests =
> testGroup "parserTests" [
================================================================================
uses a whole bunch of shortcuts (at the bottom of main) to make this
code more concise. Could probably use a few more.
> testGroup "parse expression"
> (mapExpr [
start with some really basic expressions, we just use the expression
parser rather than the full sql statement parser. (the expression parser
requires a single expression followed by eof.)
> 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])
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])
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 [])
array selector
> ,p "array[1,2]" (FunCall [] "!arrayCtor" [IntegerLit [] 1, IntegerLit [] 2])
array subscripting
> ,p "a[1]" (FunCall [] "!arraySub" [Identifier [] "a", IntegerLit [] 1])
we just produce a ast, so no type checking or anything like that is
done
some operator tests
> ,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 "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"])
some function call tests
> ,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"])
simple whitespace sanity checks
> ,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])
null stuff
> ,p "not null" (FunCall [] "!not" [NullLit []])
> ,p "a is null" (FunCall [] "!isNull" [Identifier [] "a"])
> ,p "a is not null" (FunCall [] "!isNotNull" [Identifier [] "a"])
some slightly more complex stuff
> ,p "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))
positional args used in sql and sometimes plpgsql functions
> ,p "$1" (PositionalArg [] 1)
> ,p "exists (select 1 from a)"
> (Exists [] (selectFrom [SelExp [] (IntegerLit [] 1)] (Tref [] "a")))
> (Exists (makeSelect {
> selSelectList = sle [(IntegerLit [] 1)]
> ,selTref = Just $ Tref "a"}))
selectFrom [SelExp [] (IntegerLit [] 1)] (Tref "a")))
in variants, including using row constructors
> ,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 "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.
> ,testGroup "string parsing"
> (mapExpr [
> 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")
> ])
================================================================================
first statement, pretty simple
> ,testGroup "select expression"
> (mapSql [
> p "select 1;" [SelectStatement [] $ selectE (SelectList [] [SelExp [] (IntegerLit [] 1)] [])]
> ])
================================================================================
test a whole bunch more select statements
> ,testGroup "select from table"
> (mapSql [
> p "select * from tbl;"
> [SelectStatement [] $ selectFrom (selIL ["*"]) (Tref [] "tbl")]
> ,p "select a,b from tbl;"
> [SelectStatement [] $ selectFrom (selIL ["a", "b"]) (Tref [] "tbl")]
> ,p "select a,b from inf.tbl;"
> [SelectStatement [] $ selectFrom (selIL ["a", "b"]) (Tref [] "inf.tbl")]
> ,p "select distinct * from tbl;"
> [SelectStatement [] $ Select [] Distinct (SelectList [] (selIL ["*"]) []) [Tref [] "tbl"]
> Nothing [] Nothing [] Nothing Nothing]
> ,p "select a from tbl where b=2;"
> [SelectStatement [] $ selectFromWhere
> (selIL ["a"])
> (Tref [] "tbl")
> (FunCall [] "="
> [Identifier [] "b", IntegerLit [] 2])]
> ,p "select a from tbl where b=2 and c=3;"
> [SelectStatement [] $ selectFromWhere
> (selIL ["a"])
> (Tref [] "tbl")
> (FunCall [] "!and"
> [FunCall [] "=" [Identifier [] "b", IntegerLit [] 2]
> ,FunCall [] "=" [Identifier [] "c", IntegerLit [] 3]])]
> ,p "select a from tbl\n\
> \except\n\
> \select a from tbl1;"
> [SelectStatement [] $ CombineSelect [] Except
> (selectFrom (selIL ["a"]) (Tref [] "tbl"))
> (selectFrom (selIL ["a"]) (Tref [] "tbl1"))]
> ,p "select a from tbl where true\n\
> \except\n\
> \select a from tbl1 where true;"
> [SelectStatement [] $ CombineSelect [] Except
> (selectFromWhere (selIL ["a"]) (Tref [] "tbl") (BooleanLit [] True))
> (selectFromWhere (selIL ["a"]) (Tref [] "tbl1") (BooleanLit [] True))]
> ,p "select a from tbl\n\
> \union\n\
> \select a from tbl1;"
> [SelectStatement [] $ CombineSelect [] Union
> (selectFrom (selIL ["a"]) (Tref [] "tbl"))
> (selectFrom (selIL ["a"]) (Tref [] "tbl1"))]
> ,p "select a from tbl\n\
> \union all\n\
> \select a from tbl1;"
> [SelectStatement [] $ CombineSelect [] UnionAll
> (selectFrom (selIL ["a"]) (Tref [] "tbl"))
> (selectFrom (selIL ["a"]) (Tref [] "tbl1"))]
> ,p "select a as b from tbl;"
> [SelectStatement [] $ selectFrom [SelectItem [] (Identifier [] "a") "b"] (Tref [] "tbl")]
> ,p "select a + b as b from tbl;"
> [SelectStatement [] $ selectFrom
> [SelectItem []
> (FunCall [] "+"
> [Identifier [] "a", Identifier [] "b"]) "b"]
> (Tref [] "tbl")]
> ,p "select a.* from tbl a;"
> [SelectStatement [] $ selectFrom (selIL ["a.*"]) (TrefAlias [] "tbl" "a")]
> ,p "select a from b inner join c on b.a=c.a;"
> [SelectStatement [] $ selectFrom
> (selIL ["a"])
> (JoinedTref [] (Tref [] "b") Unnatural Inner (Tref [] "c")
> (Just (JoinOn []
> (FunCall [] "=" [Identifier [] "b.a", Identifier [] "c.a"]))))]
> ,p "select * from t1 a, t2 b;"
> [SelectStatement []
> (Select [] Dupes
> (SelectList []
> [SelExp [] (Identifier [] "*")] [])
> [TrefAlias [] "t1" "a",TrefAlias [] "t2" "b"]
> Nothing [] Nothing [] Nothing Nothing)]
> ,p "select a from b inner join c as d on b.a=d.a;"
> [SelectStatement [] $ selectFrom
> (selIL ["a"])
> (JoinedTref [] (Tref [] "b") Unnatural Inner (TrefAlias [] "c" "d")
> (Just (JoinOn []
> (FunCall [] "=" [Identifier [] "b.a", Identifier [] "d.a"]))))]
> ,p "select a from b inner join c using(d,e);"
> [SelectStatement [] $ selectFrom
> (selIL ["a"])
> (JoinedTref [] (Tref [] "b") Unnatural Inner (Tref [] "c")
> (Just (JoinUsing [] ["d","e"])))]
> ,p "select a from b natural inner join c;"
> [SelectStatement [] $ selectFrom
> (selIL ["a"])
> (JoinedTref [] (Tref [] "b") Natural Inner (Tref [] "c") Nothing)]
> ,p "select a from b left outer join c;"
> [SelectStatement [] $ selectFrom
> (selIL ["a"])
> (JoinedTref [] (Tref [] "b") Unnatural LeftOuter (Tref [] "c") Nothing)]
> ,p "select a from b full outer join c;"
> [SelectStatement [] $ selectFrom
> (selIL ["a"])
> (JoinedTref [] (Tref [] "b") Unnatural FullOuter (Tref [] "c") Nothing)]
> ,p "select a from b right outer join c;"
> [SelectStatement [] $ selectFrom
> (selIL ["a"])
> (JoinedTref [] (Tref [] "b") Unnatural RightOuter (Tref [] "c") Nothing)]
> ,p "select a from b cross join c;"
> [SelectStatement [] $ selectFrom
> (selIL ["a"])
> (JoinedTref [] (Tref [] "b") Unnatural Cross (Tref [] "c") Nothing)]
> ,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") Unnatural Inner (Tref [] "c")
> (Just $ JoinOn [] (BooleanLit [] True)))
> Unnatural Inner (Tref [] "d")
> (Just $ JoinOn [] (FunCall [] "="
> [IntegerLit [] 1, IntegerLit [] 1])))]
> ,p "select row_number() over(order by a) as place from tbl;"
> [SelectStatement [] $ selectFrom [SelectItem []
> (WindowFn []
> (FunCall [] "row_number" [])
> []
> [Identifier [] "a"] Asc)
> "place"]
> (Tref [] "tbl")]
> ,p "select row_number() over(order by a asc) as place from tbl;"
> [SelectStatement [] $ selectFrom [SelectItem []
> (WindowFn []
> (FunCall [] "row_number" [])
> []
> [Identifier [] "a"] Asc)
> "place"]
> (Tref [] "tbl")]
> ,p "select row_number() over(order by a desc) as place from tbl;"
> [SelectStatement [] $ selectFrom [SelectItem []
> (WindowFn []
> (FunCall [] "row_number" [])
> []
> [Identifier [] "a"] Desc)
> "place"]
> (Tref [] "tbl")]
> ,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)
> "place"]
> (Tref [] "tbl")]
> ,p "select * from a natural inner join (select * from b) as a;"
> [SelectStatement [] $ selectFrom
> (selIL ["*"])
> (JoinedTref [] (Tref [] "a") Natural
> Inner (SubTref [] (selectFrom
> (selIL ["*"])
> (Tref [] "b")) "a")
> Nothing)]
> ,p "select * from a order by c;"
> [SelectStatement [] $ Select [] Dupes
> (sl (selIL ["*"]))
> [Tref [] "a"]
> 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"]
> 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"]
> 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"]
> 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"]
> 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"]
> 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"))
> "d")]
> ,p "select * from gen();"
> [SelectStatement [] $ selectFrom (selIL ["*"]) (TrefFun [] $ FunCall [] "gen" [])]
> ,p "select * from gen() as t;"
> [SelectStatement [] $ selectFrom
> (selIL ["*"])
> (TrefFunAlias [] (FunCall [] "gen" []) "t")]
> ,p "select a, count(b) from c group by a;"
> [SelectStatement [] $ Select [] Dupes
> (sl [selI "a", SelExp [] (FunCall [] "count" [Identifier [] "b"])])
> [Tref [] "c"] 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"] 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"] [])
> "x")]
> ])
================================================================================
one sanity check for parsing multiple statements
> ,testGroup "multiple statements"
> (mapSql [
> p "select 1;\nselect 2;" [SelectStatement [] $ selectE $ sl [SelExp [] (IntegerLit [] 1)]
> ,SelectStatement [] $ selectE $ sl [SelExp [] (IntegerLit [] 2)]]
> ])
================================================================================
test comment behaviour
> ,testGroup "comments"
> (mapSql [
> p "" []
> ,p "-- this is a test" []
> ,p "/* this is\n\
> \a test*/" []
maybe some people actually put block comments inside parts of
statements when they program?
> ,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
> ,testGroup "dml"
> (mapSql [
simple insert
> 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"))
> 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"])]
updates
> ,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]
delete
> ,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"])]
> ,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
> ,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"]
> ])
================================================================================
some ddl
> ,testGroup "create"
> (mapSql [
create table tests
> 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)] []))]
other creates
> ,p "create view v1 as\n\
> \select a,b from t;"
> [CreateView []
> "v1"
> (selectFrom [selI "a", selI "b"] (Tref [] "t"))]
> ,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")]]
drops
> ,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]
> ])
constraints
> ,testGroup "constraints"
> (mapSql [
nulls
> 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 []]]
> []]
unique table
> ,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 []]][]]
primary key row, table
> ,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"]]]
check row, table
> ,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"])]]
row, whole load of constraints, todo: add reference here
> ,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"]))]] []]
reference row, table
> ,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 delete cascade on update cascade\n\
> \);"
> [CreateTable [] "t1" [att "x" "int"
> ,att "y" "int"]
> [ReferenceConstraint [] ["x", "y"] "t2" []
> Cascade Cascade]]
> ])
================================================================================
test functions
> ,testGroup "functions"
> (mapSql [
> p "create function t1(text) returns text as $$\n\
> \select a from t1 where b = $1;\n\
> \$$ language sql stable;"
> [CreateFunction [] Sql "t1" [ParamDefTp [] $ SimpleTypeName [] "text"]
> (SimpleTypeName [] "text") "$$"
> (SqlFnBody []
> [SelectStatement [] $ selectFromWhere [SelExp [] (Identifier [] "a")] (Tref [] "t1")
> (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 [] Plpgsql "fn" [] (SimpleTypeName [] "void") "$$"
> (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 [] Plpgsql "fn" [] (SimpleTypeName [] "void") "$$"
> (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 [] Plpgsql "fn"
> [ParamDef [] "a" $ ArrayTypeName [] $ SimpleTypeName [] "text"]
> (ArrayTypeName [] $ SimpleTypeName [] "int") "$$"
> (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 [] Plpgsql "fn" [] (SimpleTypeName [] "void") "'"
> (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 [] Plpgsql "fn" []
> (SetOfTypeName [] $ SimpleTypeName [] "int") "$$"
> (PlpgsqlFnBody [] [] [NullStatement []])
> Stable]
> ,p "create function fn() returns void as $$\n\
> \begin\n\
> \ null;\n\
> \end\n\
> \$$ language plpgsql stable;"
> [CreateFunction [] Plpgsql "fn" []
> (SimpleTypeName [] "void") "$$"
> (PlpgsqlFnBody [] [] [NullStatement []])
> Stable]
> ,p "drop function test(text);"
> [DropFunction [] Require [("test",["text"])] Restrict]
> ,p "drop function if exists a(),test(text) cascade;"
> [DropFunction [] IfExists [("a",[])
> ,("test",["text"])] Cascade]
> ])
================================================================================
test non sql plpgsql statements
> ,testGroup "plpgsqlStatements"
> (mapPlpgsql [
simple statements
> 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")]
> ,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"] 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"] Nothing [] Nothing [] Nothing Nothing]
> ,p "execute s;"
> [Execute [] (Identifier [] "s")]
> ,p "execute s into r;"
> [ExecuteInto [] (Identifier [] "s") ["r"]]
> ,p "continue;" [ContinueStatement []]
complicated statements
> ,p "for r in select a from tbl loop\n\
> \null;\n\
> \end loop;"
> [ForSelectStatement [] "r" (selectFrom [selI "a"] (Tref [] "tbl"))
> [NullStatement []]]
> ,p "for r in select a from tbl where true loop\n\
> \null;\n\
> \end loop;"
> [ForSelectStatement [] "r"
> (selectFromWhere [selI "a"] (Tref [] "tbl") (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 []]]
> ])
> --,testProperty "random expression" prop_expression_ppp
> -- ,testProperty "random statements" prop_statements_ppp
> ]
> where
> mapExpr = map $ uncurry checkParseExpression
> mapSql = map $ uncurry checkParse
> mapPlpgsql = map $ uncurry checkParsePlpgsql
> p a b = (a,b)
> selIL = map selI
> selI = SelExp [] . Identifier []
> sl a = SelectList [] a []
> selectE selList = Select [] Dupes selList
> [] Nothing [] Nothing [] Nothing Nothing
> selectFrom selList frm =
> Select [] Dupes (SelectList [] selList [])
> [frm] Nothing [] Nothing [] Nothing Nothing
> selectFromWhere selList frm whr =
> Select [] Dupes (SelectList [] selList [])
> [frm] (Just whr) [] Nothing [] Nothing Nothing
> stringQ = StringLit [] "'"
> att n t = AttributeDef [] n (SimpleTypeName [] t) Nothing []
================================================================================
Unit test helpers
parse and then pretty print and parse a statement
> checkParse :: String -> [Statement] -> Test.Framework.Test
> checkParse src ast = parseUtil1 src ast parseSql
parse and then pretty print and parse an expression
> checkParseExpression :: String -> Expression -> Test.Framework.Test
> checkParseExpression src ast = parseUtil src ast
> parseExpression printExpression
> checkParsePlpgsql :: String -> [Statement] -> Test.Framework.Test
> checkParsePlpgsql src ast = parseUtil1 src ast parsePlpgsql
> parseUtil :: (Show t, Eq b, Show b, Data b, Annotated b) =>
> String
> -> b
> -> (String -> Either t b)
> -> (b -> String)
> -> Test.Framework.Test
> parseUtil src ast parser printer = testCase ("parse " ++ src) $ do
> let ast' = case parser src of
> Left er -> error $ show er
> Right l -> l
> assertEqual ("parse " ++ src) ast $ stripAnnotations ast'
> -- pretty print then parse to check
> let pp = printer ast
> let ast'' = case parser pp of
> Left er -> error $ "reparse\n" ++ show er ++ "\n" -- ++ pp ++ "\n"
> Right l -> l
> assertEqual ("reparse " ++ pp) ast $ stripAnnotations ast''
> parseUtil1 :: (Show a) => String
> -> [Statement]
> -> (String -> Either a StatementList)
> -> Test.Framework.Test
> parseUtil1 src ast parser = testCase ("parse " ++ src) $ do
> let ast' = case parser src of
> Left er -> error $ show er
> Right l -> l
> assertEqual ("parse " ++ src) ast $ map stripAnnotations ast'
> -- pretty print then parse to check
> let pp = printSql ast'
> let ast'' = case parser pp of
> Left er -> error $ "reparse\n" ++ show er ++ "\n" -- ++ pp ++ "\n"
> Right l -> l
> assertEqual ("reparse " ++ pp) ast $ map stripAnnotations ast''