packages feed

haskelldb-th (empty) → 1.0

raw patch · 4 files changed

+326/−0 lines, 4 filesdep +basedep +haskelldbdep +mtlsetup-changed

Dependencies added: base, haskelldb, mtl, template-haskell

Files

+ LICENSE view
@@ -0,0 +1,25 @@+Copyright (c) 2009, Justin Bailey+All rights reserved.++Redistribution and use in source and binary forms, with or without modification,+are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright notice,+      this list of conditions and the following disclaimer.+    * Redistributions in binary form must reproduce the above copyright notice,+      this list of conditions and the following disclaimer in the documentation+      and/or other materials provided with the distribution.+    * The names of its contributors may be used to endorse or promote+      products derived from this software without specific prior+      written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,4 @@+#!/usr/bin/env runghc++import Distribution.Simple+main = defaultMain
+ haskelldb-th.cabal view
@@ -0,0 +1,27 @@+Name: haskelldb-th+Version: 1.0+Cabal-version: >= 1.2+Build-type: Simple+Homepage: http://patch-tag.com/publicrepos/haskelldb-th+Copyright: The authors+Maintainer: jgbailey@gmail.com+Category: Database+License-File: LICENSE+Author: "Justin Bailey " <jgbailey@gmail.com>+License: BSD3+Synopsis: Template Haskell utilities for HaskellDB.+Description: +  Utility functions that make defining tables, fields, and queries in+  HaskellDB simpler.+  +Flag split-base++Library+  Build-depends: mtl+  Build-depends: base >= 3.0, haskelldb >= 0.11, template-haskell >= 2.2.0.0+  Extensions:+    TemplateHaskell+  Exposed-Modules:+        Database.HaskellDB.CodeGen+  Hs-source-dirs: src+  ghc-options: -O2
+ src/Database/HaskellDB/CodeGen.hs view
@@ -0,0 +1,270 @@+{-# LANGUAGE ScopedTypeVariables, TemplateHaskell #-}
+{- |
+HaskellDB is a great library for writing type-safe SQL queries. That type safety
+comes with a significant boilerplate overhead, though. This library provides utilities
+that help reduce that boilerplate by generating the definitions, types and instances
+that HaskellDB expects to work with. It also provides some functions for building
+records for projections which otherwise can be quite cumbersome.
+-}
+module Database.HaskellDB.CodeGen (
+  -- * Field definition.
+  mkField, mkFieldWithName, mkDBDirectField,
+  -- * Table definition.
+  mkDBDirectTable, mkDBDirectTableWithName, mkDBDirectTableType
+  -- * Debugging utilities.
+  , printQ, Language.Haskell.TH.Ppr()
+  -- * Record creation (for projection).
+  , mkRecord)
+
+where
+
+import Database.HaskellDB (Attr, Expr, (<<), (!))
+import Database.HaskellDB.HDBRec (RecCons(..), RecNil(..), (#), FieldTag)
+import Database.HaskellDB.DBLayout (mkAttr, baseTable, hdbMakeEntry, Table, fieldName)
+
+import Data.Char (isUpper, isLower, toUpper, toLower)
+import Language.Haskell.TH 
+import qualified Language.Haskell.TH as TH
+
+-- | Creates a compile-time field declaration. The name given
+-- will be used to create the type and the name of the field returned. It
+-- will also be used to create a /field name/@Field@ function which
+-- can be used to place the field in a projection.
+--
+-- For example, @mkField \"fooBar\" [t|Int|]@ produces the following declarations:
+--
+-- > data FooBar = FooBar
+-- > instance FieldTag FooBar where fieldName _ = "fooBar"
+-- > fooBarField :: Attr FooBar Int
+-- > fooBarField = mkAttr FooBar
+--
+-- @mkField \"Bar_Foo\" [t|Bool|]@ would produce:
+--
+-- > data Bar_Foo = Bar_Foo
+-- > instance FieldTag Bar_Foo where fieldName _ = "bar_Foo"
+-- > bar_fooField :: Attr Bar_Foo Bool
+-- > bar_fooField = mkAttr Bar_Foo
+--
+mkField :: String -- ^ Name to base field 
+  -> TH.TypeQ -- ^ The type of the field.
+  -> Q [Dec]
+mkField [] t = error "Can't generate field from an empty string."
+mkField f@(c:cs) typ = 
+  let
+      fieldName = if isUpper c then f else (toUpper c) : cs
+      colName = if isLower c then f else (toLower c) : tail cs
+  in mkFieldWithName fieldName colName typ
+
+-- | Creates a compile time field declaration using the given
+-- arguments for the type and column name of the field. The @typN@ argument
+-- is used to produce the /field name/@Field@ function which can be used to
+-- add the field to a projection.
+--
+-- For example, @mkFieldWithName \"FooBar\" \"foo_bar\" [t|Int|]@ gives:
+--
+-- > data FooBar = FooBar
+-- > instance FieldTag FooBar where fieldName _ = "foo_bar"
+-- > fooBarField :: Attr FooBar Int
+-- > fooBarField = mkAttr FooBar
+--
+-- Note that an error will occur if @typN@ is not a proper type/constructor name.
+mkFieldWithName :: String -- ^ @typN@ - Name of the type representing the field. Must be a legal type name.
+  -> String -- ^ Column name for the field.
+  -> TH.TypeQ -- ^ Type of the field.
+  -> Q [Dec]
+mkFieldWithName [] _ _ = error "Can't create a field with no name."
+mkFieldWithName _ [] _ = error "Can't create a field with no column name."
+mkFieldWithName typN colName typeQ =
+  let attrF = (toLower (head typN) : tail typN) ++ "Field"
+  in mkFieldWithNameAndConstructor typN attrF colName typeQ
+
+-- | Creates necessary data and function declarations to represent
+-- the given field. Used internally by other make functions. All strings given must
+-- be legal, as they will not be transformed in any way.
+mkFieldWithNameAndConstructor :: String -- ^ Name of the type and constructor for this field. Must be a legal type name.
+  -> String -- ^ Name of the function which makes an attribute for this field (i.e, Attr <typeName> <fieldType>). Must be a legal function name.
+  -> String -- ^ Name of the column this field represents. Can be any string, though it should match the database column name.
+  -> TH.TypeQ -- ^ The type of the field.
+  -> Q [Dec]
+mkFieldWithNameAndConstructor typN attrF colName typeQ = do
+      colType <- typeQ
+      let typeName = mkName typN
+          attrName = mkName attrF
+          -- data declaration representing this field.
+          fieldD = DataD [] typeName [] [NormalC typeName []] []
+          -- instance declaration in FieldTag class for this field.
+          fieldI = InstanceD [] (AppT (ConT ''FieldTag)
+                                   (ConT typeName)) [FunD 'fieldName{-'-} [Clause [WildP]
+                                                      (NormalB (LitE (StringL colName))) []]]
+          -- Type signature for the function which creates an Attr value for the field.
+          fieldS = SigD attrName (AppT (AppT (ConT ''Attr) (ConT typeName)) colType)
+          -- actual function.
+          fieldF = ValD (VarP attrName) (NormalB (AppE (VarE 'mkAttr) --' 
+                                                              (ConE typeName))) []
+      return [fieldD, fieldI, fieldS, fieldF]
+
+-- | Creates a field definition in the style DBDirect uses. The @colName@
+-- argument is used to create the data definition, type synonym, and field function. The @typeQ@ argument specifies
+-- the type of the field. 
+--
+-- For example, @mkDBDirectField \"fooBar\" [t|Bool|]@ will produce:
+--
+-- > data FooBar = FooBar
+-- > instance FieldTag FooBar where fieldName _ = "fooBar"
+-- > fooBar :: Attr FooBar Bool
+-- > fooBar = mkAttr FooBar
+-- 
+mkDBDirectField :: String -- ^ @colName@ - column name. Will be used for a type and function name (with appropriate casing), so must be a legal name.
+  -> TH.TypeQ -- ^ @typeQ@ - The type of the field.
+  -> Q [Dec]
+mkDBDirectField colName typeQ =
+  let typeN = toUpper (head colName) : tail colName
+      attrN = toLower (head colName) : tail colName
+  in mkFieldWithNameAndConstructor typeN attrN colName typeQ
+
+-- | Creates definitions for a table and all its fields in the style of DBDirect.
+-- Takes a table name and a list of fields (as types). Generates
+-- a table type and a function to construct the table. The function
+-- will be the tablename in lower case. The type alias will
+-- be the table name with the first character capitalized.
+--
+-- For example:
+--
+-- > mkDBDirectTable "Table1" [("Col1", [t|Int|])
+-- >                          , ("Col2", [t|Bool|])]
+--
+-- Will produce
+--
+-- > type Table1 = (RecCons Col1 (Expr Int)
+-- >                (RecCons Col2 (Expr Bool)))
+-- > 
+-- > table1 :: Table Table1
+-- > table1 = baseTable "Table1" $
+-- >          hdbMakeEntry Col1 #
+-- >          hdbMakeEntry Col2
+-- >
+-- > data Col1 = Col1
+-- > instance FieldTag Col1 where fieldName _ = "col1"
+-- > col1 :: Attr Col1 Int
+-- > col1 = mkAttr Col1
+-- >
+-- > data Col2 = Col2
+-- > instance FieldTag Col2 where fieldName _ = "col2"
+-- > col2 :: Attr Col2 Int
+-- > col2 = mkAttr Col2
+-- 
+mkDBDirectTable :: String -- ^ Name of the table. Will be used to create the table type
+                   -- synonym and table creation function (with appropriate casing), so must be a
+                   -- legal name
+  -> [(String, TH.TypeQ)] -- Each pair is the field type (e.g., Bus_unit_id) and the type the field can hold (e.g., Int)
+  -> Q [Dec]
+mkDBDirectTable tbl f = mkDBDirectTableWithName tbl tbl f 
+
+-- | Creates definitions for a table and all its fields in the style of DBDirect.
+-- Takes a table name and a list of fields (as types). Generates
+-- a table type and a function to construct the table. The function
+-- will be the tablename in lower case. The type alias will
+-- be the table name with the first character capitalized. See "mkDBirectTable" for an example, as
+-- that function calls this one.
+mkDBDirectTableWithName :: String -- ^ Name of the table type to create. Will be cased properly but
+                                  -- must be a legal name.
+  -> String -- ^ Name of table to use in actual SQL generated. 
+  -> [(String, TH.TypeQ)] -- Each pair is the field type (e.g., Bus_unit_id) and the type the field can hold (e.g., Int).
+  -> Q [Dec]
+mkDBDirectTableWithName [] _ _= error "Can't create a table with no name."
+mkDBDirectTableWithName _ [] _= error "Can't create a table wiht no real name specified."
+mkDBDirectTableWithName _ _ [] = error "Can't create a table with no columns."
+mkDBDirectTableWithName tbl sqlTbl f = do
+  let fields = map (\(n, t) -> (mkName . capitalize $ n, t)) f -- make names of fields
+      -- Table type name
+      tblTN = mkName ((toUpper . head $ tbl) : tail tbl)
+      -- Table creation function name
+      tblFN = mkName ((toLower . head $ tbl) : tail tbl)
+      -- Get constructor name for each field 
+      fieldCons = map fst fields  
+      -- Build the type of the table from the fields given.
+      tblType = foldr (\(n,e) exp -> appT (appT (appT (conT ''RecCons) (conT n)) (appT (conT ''Expr) e)) exp) (conT ''RecNil) fields
+      capitalize (c:cs) = toUpper c : cs
+      capitalize cs = cs
+      lower (c:cs) = toLower c : cs
+      lower cs = cs
+      -- Builds the expression for creating each column in the table.
+      -- i.e., (hdbMakeEntry A # hdbMakeEntry B # ...)
+      tblCols :: [Name] -> ExpQ
+      tblCols (f:[]) =  appE [|hdbMakeEntry|] (conE f)
+      tblCols (f:fs) = appE (appE [|(#)|] (appE [|hdbMakeEntry|] (conE f))) (tblCols fs)
+      -- Body of the table creation function
+      tblBodyD = [| baseTable sqlTbl $(tblCols fieldCons)|]
+      -- Type signature for the table creation function
+      tblSigD = sigD tblFN (appT (conT ''Table) (conT tblTN))
+      -- Declaration of table creation function
+      tblConD = valD (varP tblFN) (normalB tblBodyD) []
+  -- Create the type synonmym representing for our table.
+  tblTypeD :: [Q Dec] <- do { ts <- mkDBDirectTableType (nameBase tblTN) fields; return $! map return ts; }
+  -- get field declarations. Ugliness due to mkDBDirectField returning Q [Dec]
+  fieldDecls :: [Q Dec] <- do { fs <- mapM (\(n, t) -> mkDBDirectField (lower . nameBase $ n) t) fields; return $! map return (concat fs) }
+  -- Return field and table declarations
+  sequence $ fieldDecls ++ tblTypeD ++ [tblSigD, tblConD]
+
+-- | Creates a type synonym for a table with the name given, using
+-- the list of fields given. See "mkDBDirectField" for an example, as that function
+-- calls this one to create the type signature.
+mkDBDirectTableType :: String -- ^ Name of the type synonym. Must be a legal name and properly cased.
+  -> [(Name, TH.TypeQ)] -- ^ List of fields in the table. Must be legal, properly cased fields.
+  -> Q [Dec] -- ^ The type synonym declaration.
+mkDBDirectTableType tblTN fields = do
+    -- Create the type synonmym representing for our table.
+    syn <- tySynD (mkName tblTN) [] tblType
+    return [syn]
+  where
+    -- Build the type of the table from the fields given.
+    tblType = foldr (\(n,e) exp -> appT (appT (appT (conT ''RecCons) (conT n)) (appT (conT ''Expr) e)) exp) (conT ''RecNil) fields
+
+
+{- | Create a record from the list of tables and fields given. The first
+ element of each tuple is a table. The second is a list of fields from
+ that table which will be in the new record. The record will be constructed
+ in the order of tables and fields given.
+
+ This is a Template Haskell function which must be spliced in and is
+ intended to use the @'@ (quote) facility for capturing names. An example:
+ 
+>  qry = do
+>    dtls <- pts_dlvry_order_dtls;
+>    status <- table pts_dlvry_status_v;
+>    restrict (status ! Pts_dlvry_status_v.pts_dlvry_order_dtl_id .==. dtls ! Pts_dlvry_order_dtl_v.pts_dlvry_order_dtl_id)
+>    project $(mkRecord [('dtls, ['Pts_dlvry_order_dtl_v.prt_desc
+>                         , 'Pts_dlvry_order_dtl_v.pts_dlvry_order_hdr_id
+>                         , 'Pts_dlvry_order_dtl_v.prt_id
+>                         , 'Pts_dlvry_order_dtl_v.pts_dlvry_order_dtl_id])
+>                       ,('status, ['Pts_dlvry_status_v.item_stat_nbr])])
+ 
+The type of qry is then
+
+>          (RecCons Pts_dlvry_order_dtl_v.Prt_desc (Expr (Maybe BStr40))
+>          (RecCons Pts_dlvry_order_dtl_v.Pts_dlvry_order_hdr_id (Expr (Maybe Int))
+>          (RecCons Pts_dlvry_order_dtl_v.Prt_id (Expr (Maybe BStr30))
+>          (RecCons Pts_dlvry_order_dtl_v.Pts_dlvry_order_dtl_id (Expr (Maybe Int))
+>          (RecCons Pts_dlvry_status_v.Item_stat_nbr (Expr (Maybe Int)) RecNil))))))
+
+If other fields need to be added to the record, they must come before the call
+to mkRecord:
+
+>    project $ filtered << someTable ! someField #
+>              $(mkRecord [('dtls, ['Pts_dlvry_order_dtl_v.prt_desc
+>                                    , 'Pts_dlvry_order_dtl_v.pts_dlvry_order_hdr_id
+>                                    , 'Pts_dlvry_order_dtl_v.prt_id
+>                                    , 'Pts_dlvry_order_dtl_v.pts_dlvry_order_dtl_id])
+>                          ,('status, ['Pts_dlvry_status_v.item_stat_nbr])])
+
+-}
+mkRecord :: [(TH.Name, [TH.Name])] -> ExpQ
+mkRecord = projTable . concatMap (\(table, fields) -> zip (repeat table) fields) 
+  where
+    projTable ([]) = [|id|]
+    projTable ((table, f):rest) = [|$(varE f) << $(varE table) ! $(varE f) # $(projTable rest)|]
+  
+-- | Prints a TH syntax declaration.
+printQ f = do
+  s <- runQ f
+  print s