1. Overview
These examples should be kept up to date, but if any are out of date, you can see the latest versions in the examples folder in the repo (the files there should definitely compile and run fine with the latest version of hssqlppp): https://github.com/JakeWheat/hssqlppp/tree/master/examples
TODO: This is a pretty bold claim…
2. Parsing
Here is a program to parse some SQL from a file and print the ast:
> import System.Environment
> import Database.HsSqlPpp.Parser
> main :: IO ()
> main = do
> [f] <- getArgs
> ast <- parseStatementsFromFile f
> print ast
Given the SQL file x.sql:
select * from t;
the result is something like:
$ Parse x.sql Right [QueryStatement (Annotation {asrc = Just ("x.sql",1,1), atype = Nothing, errs = [], stType = Nothing, catUpd = [], fnProt = Nothing, infType = Nothing}) (Select (Annotation {asrc = Just ("x.sql",1,1), atype = Nothing, errs = [], stType = Nothing, catUpd = [], fnProt = Nothing, infType = Nothing}) Dupes (SelectList (Annotation {asrc = Just ("x.sql",1,8), atype = Nothing, errs = [], stType = Nothing, catUpd = [], fnProt = Nothing, infType = Nothing}) [SelExp (Annotation {asrc = Just ("x.sql",1,8), atype = Nothing, errs = [], stType = Nothing, catUpd = [], fnProt = Nothing, infType = Nothing}) (Identifier (Annotation {asrc = Just ("x.sql",1,8), atype = Nothing, errs = [], stType = Nothing, catUpd = [], fnProt = Nothing, infType = Nothing}) "*")]) [Tref (Annotation {asrc = Just ("x.sql",1,15), atype = Nothing, errs = [], stType = Nothing, catUpd = [], fnProt = Nothing, infType = Nothing}) (SQIdentifier (Annotation {asrc = Just ("x.sql",1,15), atype = Nothing, errs = [], stType = Nothing, catUpd = [], fnProt = Nothing, infType = Nothing}) ["t"]) (NoAlias (Annotation {asrc = Just ("x.sql",1,16), atype = Nothing, errs = [], stType = Nothing, catUpd = [], fnProt = Nothing, infType = Nothing}))] Nothing [] Nothing [] Nothing Nothing)]
More readable output from this variation:
> import System.Environment
> import Language.Haskell.Exts
> import Data.Generics.Uniplate.Data
> import Database.HsSqlPpp.Parser
> main :: IO ()
> main = do
> [f] <- getArgs
> ast <- parseStatementsFromFile f
> putStrLn $ showNoAnns ast
> showNoAnns :: Show a => a -> String
> showNoAnns = p stripA
> where
> stripA :: Exp -> Exp
> stripA = transformBi $ \x ->
> case x of
> (Paren (RecConstr (UnQual (Ident "Annotation")) _)) ->
> Con $ UnQual $ Ident "Ann"
> x1 -> x1
> p f s =
> case parseExp (show s) of
> ParseOk ast -> prettyPrint (f ast)
> x -> error $ show x
The annotation values have been replaced with the string 'Ann', and the output indented using haskell-src-exts:
$ Parse2 x.sql Right [QueryStatement Ann (Select Ann Dupes (SelectList Ann [SelExp Ann (Identifier Ann "*")]) [Tref Ann (SQIdentifier Ann ["t"]) (NoAlias Ann)] Nothing [] Nothing [] Nothing Nothing)]
You can see the various parsing functions in the haddock docs here: Database.HsSqlPpp.Parser
3. Typechecking
Here is a program which parses and typechecks a query and outputs the result type:
> import Database.HsSqlPpp.Parser
> import Database.HsSqlPpp.TypeChecker
> import Database.HsSqlPpp.Catalog
> import Database.HsSqlPpp.Types
> import Database.HsSqlPpp.Annotation
> import Database.HsSqlPpp.Ast
> main :: IO ()
> main = do
> let query = "select * from t"
> ast :: QueryExpr
> Right ast = parseQueryExpr "" query
> aast :: QueryExpr
> aast = typeCheckQueryExpr cat ast
> ann :: Annotation
> ann = getAnnotation aast
> ty :: Maybe Type
> ty = atype ann
> print ty
> where
> Right cat = updateCatalog defaultTemplate1Catalog
> [CatCreateTable "t" [("a", typeInt)
> ,("b", typeInt)
> ] []]
Running gives:
$ src-extra/examples/TypeCheck Just (SetOfType (CompositeType [("a",ScalarType "int4"),("b",ScalarType "int4")]))
Typecheck against an existing database:
> import System.Environment
> import Database.HsSqlPpp.Parser
> import Database.HsSqlPpp.TypeChecker
> import Database.HsSqlPpp.Catalog
> import Database.HsSqlPpp.Types
> import Database.HsSqlPpp.Annotation
> import Database.HsSqlPpp.Ast
> import Database.HsSqlPpp.Utils.CatalogReader
> main :: IO ()
> main = do
> [cs] <- getArgs
> cus <- readCatalogFromDatabase cs
> let Right cat = updateCatalog defaultCatalog cus
> query = "select * from t"
> ast :: QueryExpr
> Right ast = parseQueryExpr "" query
> aast :: QueryExpr
> aast = typeCheckQueryExpr cat ast
> ann :: Annotation
> ann = getAnnotation aast
> ty :: Maybe Type
> ty = atype ann
> print ty
Assume the database 'db' has the following table:
create table t (a int, b float);
$ TypeCheckDB "dbname=db" Just (SetOfType (CompositeType [("a",ScalarType "int4"),("b",ScalarType "float8")]))
This uses some extra utils which are only available in the repo at the moment: CatalogReader
4. Show catalog
Bit rough at the moment:
> import System.Environment
> import Data.List
> import Text.Show.Pretty
> import Database.HsSqlPpp.Parser
> import Database.HsSqlPpp.TypeChecker
> import Database.HsSqlPpp.Catalog
> main :: IO ()
> main = do
> [f] <- getArgs
> Right ast <- parseStatementsFromFile f
> let (cat,_) = typeCheckStatements defaultTemplate1Catalog ast
> cc = deconstructCatalog cat \\ deconstructCatalog defaultTemplate1Catalog
> putStrLn $ ppShow cc
Pass in the following sql file:
create table t (a int, b float);
create view v as select a from t;
$ ./ShowCatalog sc.sql [CatCreateTable "t" [("a", ScalarType "int4"), ("b", ScalarType "float8")] [("tableoid", ScalarType "oid"), ("cmax", ScalarType "cid"), ("xmax", ScalarType "xid"), ("cmin", ScalarType "cid"), ("xmin", ScalarType "xid"), ("ctid", ScalarType "tid")], CatCreateView "v" [("a", ScalarType "int4")]]
5. Generating SQL
Here is a program which generates SQL:
> import Database.HsSqlPpp.Annotation
> import Database.HsSqlPpp.Ast
> import Database.HsSqlPpp.Pretty
> data MakeSelect = MakeSelect [String] String
> sqlGen :: MakeSelect -> QueryExpr
> sqlGen (MakeSelect cols tb) =
> Select emptyAnnotation Dupes
> sl tr
> Nothing [] Nothing [] Nothing Nothing
> where
> sl = SelectList emptyAnnotation
> (map si cols)
> tr = [Tref emptyAnnotation
> (Name emptyAnnotation [Nmc tb])
> (NoAlias emptyAnnotation)]
> si i = SelExp emptyAnnotation
> (Identifier emptyAnnotation
> (Nmc i))
> main :: IO ()
> main = do
> let s = MakeSelect ["a", "b"] "t"
> putStrLn $ printQueryExpr $ sqlGen s
$ MakeSelect select a, b from t;
TODO: add about use of helpers and approach in the parse test code
6. Using quasiquotes
> {-# LANGUAGE QuasiQuotes #-}
> import Database.HsSqlPpp.Ast
> import Database.HsSqlPpp.Quote
> import Database.HsSqlPpp.Annotation
> import Database.HsSqlPpp.Pretty
> test :: Statement
> test = [sqlStmt|
>
> create table $(tablename) (
> $(varname) $(typename)
> );
>
> |]
> where
> tablename = "my_table"
> varname = "my_field"
> typename = "text"
> main :: IO ()
> main = putStrLn $ printStatements [test]
The output is:
$ QQ create table my_table ( my_field text );
7. Transforming SQL
The TPC-H qgen program generates the SQL queries for the TPC-H benchmarks and includes an option for SQL Server. For some reason, it doesn’t output the queries in syntax which SQL Server recognises. Here is a short program to take the qgen output and convert it into a format which SQL Server understands. (I haven’t checked carefully to see if the produced SQL is correct.)
Convert qgen output into sql server format
> {-# LANGUAGE QuasiQuotes #-}
> import Data.Generics.Uniplate.Data
> import System.Environment
> import Data.Data
> import Database.HsSqlPpp.Parser
> import Database.HsSqlPpp.Ast
> import Database.HsSqlPpp.Pretty
> import Database.HsSqlPpp.Quote
> import Database.HsSqlPpp.Annotation
> main :: IO ()
> main = do
> [fn] <- getArgs
> f <- readFile fn
> putStrLn $ fixSql f
> fixSql :: String -> String
> fixSql sql =
> let qe = either (error . show) id $ parseStatements "" sql
> qe' = fixSqlAst qe
> in printStatements qe'
> fixSqlAst :: Data a => a -> a
> fixSqlAst = fixDate . fixSubstring . fixExtract . fixIntervals
dateadd(day, -90, ‘1998-12-01’)
Instead of:
date ‘1998-12-01’ - interval ‘90’ day
> fixIntervals :: Data a => a -> a
> fixIntervals = transformBi $ \x -> case x of
> [sqlExpr| $(a) + $(b) |] | Just (i,v,d) <- dateInfo a b ->
> [sqlExpr| dateAdd($i(i),$(v),$s(d))|]
> [sqlExpr| $(a) - $(b) |]| Just (i,v,d) <- dateInfo a b ->
> [sqlExpr| dateAdd($i(i),-$(v),$s(d))|]
> x' -> x'
> where
> dateInfo (TypedStringLit _ (SimpleTypeName _ "date") d)
> (Interval _ v i _)
> | Just i' <- lookup i [(IntervalDay,"day")
> ,(IntervalMonth,"month")
> ,(IntervalYear,"year")]
> = Just (i',NumberLit emptyAnnotation v,d)
> dateInfo _ _ = Nothing
datepart(year,l_shipdate)
Instead of:
extract(year from l_shipdate)
> fixExtract :: Data a => a -> a
> fixExtract = transformBi $ \x -> case x of
> [sqlExpr| extract(year from $(expr) ) |] ->
> [sqlExpr| datepart(year,$(expr)) |]
> x' -> x'
substring(c_phone,1,2)
Instead of:
substring(c_phone from 1 for 2)
> fixSubstring :: Data a => a -> a
> fixSubstring = transformBi $ \x -> case x of
> [sqlExpr| substring($(i) from $(a) for $(b)) |] ->
> [sqlExpr| substring($(i),$(a),$(b)) |]
> x' -> x'
‘1998-12-01’
Instead of:
date ‘1998-12-01’
> fixDate :: Data a => a -> a
> fixDate = transformBi $ \x -> case x of
> TypedStringLit a (SimpleTypeName _ "date") d -> StringLit a d
> x' -> x'
Example, a Q1 query:
select
l_returnflag,
l_linestatus,
sum(l_quantity) as sum_qty,
sum(l_extendedprice) as sum_base_price,
sum(l_extendedprice * (1 - l_discount)) as sum_disc_price,
sum(l_extendedprice * (1 - l_discount) * (1 + l_tax)) as sum_charge,
avg(l_quantity) as avg_qty,
avg(l_extendedprice) as avg_price,
avg(l_discount) as avg_disc,
count(*) as count_order
from
lineitem
where
l_shipdate <= date '1998-12-01' - interval '63' day (3)
group by
l_returnflag,
l_linestatus
order by
l_returnflag,
l_linestatus;
Output from running the program on this sql:
select
l_returnflag,
l_linestatus,
sum(l_quantity) as sum_qty,
sum(l_extendedprice) as sum_base_price,
sum((l_extendedprice * (1 - l_discount))) as sum_disc_price,
sum(((l_extendedprice * (1 - l_discount)) * (1 + l_tax))) as sum_charge,
avg(l_quantity) as avg_qty,
avg(l_extendedprice) as avg_price,
avg(l_discount) as avg_disc,
count(*) as count_order
from
lineitem
where
(l_shipdate <= dateAdd(day,(- (63)),'1998-12-01'))
group by
l_returnflag, l_linestatus
order by
l_returnflag asc, l_linestatus asc;