Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a00f803
initial commit; minimal working example
mgmeier Nov 4, 2016
f56b8fc
complete db schema and types; start adding relations
mgmeier Nov 5, 2016
3e5a9ea
deletes, updates; updatedAt housekeeping
mgmeier Nov 5, 2016
ebd9467
inserts; auditlog; refactor
mgmeier Nov 7, 2016
e2ea126
more inserts; WIP auditlog; start domainAPI
mgmeier Nov 10, 2016
6f69c56
expand role API; inner join queries
mgmeier Nov 11, 2016
5ff5b3b
variadic updates
mgmeier Nov 11, 2016
4f9470c
finish insert/update boilerplate; complete domain API
mgmeier Nov 12, 2016
28fb780
changed json to jsonb in default value
saurabhnanda Nov 13, 2016
a51527a
add wrapper type for variadic args; use Data.Default
mgmeier Nov 15, 2016
4eb48e9
change mapping to use Data.Text as default text type
mgmeier Nov 15, 2016
59f564b
fixup!
mgmeier Nov 15, 2016
e6b88dd
jsonDiff helper for auditing; trying direct inserts with identity pro…
mgmeier Nov 18, 2016
63249c7
investigated jsonb and enums
mgmeier Nov 18, 2016
e27c383
audit_log entries; insert into ... returning *;
mgmeier Nov 18, 2016
3da0f73
multiway-join
mgmeier Nov 19, 2016
ae634d9
refactor DBInterface + DomainAPI; simplify updates; better audit logic
mgmeier Nov 19, 2016
8cd48a9
jsonb mapping working w/ patched relational-schemas
mgmeier Nov 20, 2016
151e03b
mapping of Postgres enum type to Haskell works!
mgmeier Nov 21, 2016
fee8b20
retrieve enum values from DB at compile time
mgmeier Nov 25, 2016
fc2184c
ENUM deriving working (proof-of-concept)
mgmeier Nov 25, 2016
a505a94
Merge branch 'master' into feature/RelationalRecord
mgmeier Nov 26, 2016
c88be35
ENUM derivation now working beautifully
mgmeier Nov 26, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
ENUM deriving working (proof-of-concept)
  • Loading branch information
mgmeier committed Nov 25, 2016
commit fc2184c688bfbe5ca772b0cc8fb474a5df40eafc
1 change: 1 addition & 0 deletions RelationalRecord/RelationalRecord.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ executable RelationalRecord
relational-query,
relational-schemas >= 0.1.3.2,
persistable-record,
th-data-compat,
HDBC-session,
HDBC,
HDBC-postgresql,
Expand Down
82 changes: 70 additions & 12 deletions RelationalRecord/src/Helpers/DefineEnum.hs
Original file line number Diff line number Diff line change
@@ -1,38 +1,96 @@
{-# LANGUAGE TemplateHaskell, LambdaCase #-}
{-# LANGUAGE TemplateHaskell, LambdaCase, ViewPatterns #-}

module Helpers.DefineEnum where
module Helpers.DefineEnum (defineEnum) where

import DataSource

import Language.Haskell.TH (Q, runIO, Name, TypeQ, Dec)
import Language.Haskell.TH.Name.CamelCase (varCamelcaseName)
import Language.Haskell.TH.Lib.Extra (reportWarning, reportError)
import Language.Haskell.TH
import Language.Haskell.TH.Name.CamelCase
import Language.Haskell.TH.Compat.Data (dataD')

import Database.HDBC.Session (withConnectionIO)
import Database.HDBC (IConnection, SqlValue, safeFromSql, quickQuery')
import Database.HDBC (SqlValue(..), safeFromSql, quickQuery')

import Data.Either
import Database.Record
import Database.Record.TH (deriveNotNullType)

import Data.ByteString.Char8 as B (pack, unpack)
import Data.Either (partitionEithers)
import Control.Arrow ((&&&))


-- select enumlabel from pg_type join pg_enum on (enumtypid = typelem) where typname = '_test_enum' order by enumsortorder asc;

defineEnum :: String -> Q [Dec]
defineEnum enumName =
runIO getInfo >>= \case
[] -> die $ "no ENUM found on DB named " ++ enumName
vs -> case partitionEithers [safeFromSql (head v) | v <- vs, (not . null) v] of
([], vs'') -> generateEnum enumName vs''
(errs, _) -> die $ concatMap show errs
(errs, _) -> die $ unlines $ map show errs

where
die err = reportError ("defineEnum: " ++ err) >> return []
enumName' = '_' : enumName
query = "SELECT enumlabel FROM pg_type JOIN pg_enum ON (enumtypid=typelem) \
\WHERE typname='" ++ enumName' ++ "' ORDER BY enumsortorder ASC;"
getInfo :: IO [[SqlValue]]
getInfo = withConnectionIO getDataSource $ \conn' -> quickQuery' conn' query []
getInfo =
withConnectionIO getDataSource $ \conn' -> quickQuery' conn' query []


sqlShow :: Show a => a -> SqlValue
sqlShow = SqlByteString . B.pack . show

sqlRead :: Read a => SqlValue -> a
sqlRead = \case
SqlByteString bs -> read . B.unpack $ bs
SqlString s -> read s
_ -> read ""

genSqlInstances :: ConName -> DecsQ
genSqlInstances (conName -> name) =
[d| instance ToSql SqlValue $(conT name) where
recordToSql = valueRecordToSql sqlShow
instance FromSql SqlValue $(conT name) where
recordFromSql = valueRecordFromSql sqlRead|]
{-
genMapping :: String -> ConName -> DecsQ
genMapping eName (conName -> name) =
[d| mapping :: (String, TypeQ)
mapping = (eName , $(varT name))|]
-}

generateEnum :: String -> [String] -> Q [Dec]
generateEnum enumName enumVals =
reportWarning ('\n' : enumName ++ " ->\n" ++ unlines enumVals) >> return []
generateEnum enumName enumVals = do
typeDecl <- genEnumType name vals
showDecl <- genShow name (zip vals enumVals)
readDecl <- genRead name
sqlDecl <- genSqlInstances name
persDecl <- deriveNotNullType (conT $ conName name)
return $ typeDecl : showDecl : readDecl ++ sqlDecl ++ persDecl
where
name = conCamelcaseName enumName
vals = map conCamelcaseName enumVals

genShow :: ConName -> [(ConName, String)] -> DecQ
genShow (conName -> name) pairs =
instanceD (cxt [])
(appT (conT ''Show) (conT name))
[funD (mkName "show") $ map genClause pairs]
where
genClause (k, v) = clause [(conP (conName k) [])] (normalB [|v|]) []

genRead :: ConName -> DecsQ
genRead (conName -> name) =
[d|instance Read $(conT name) where
readsPrec _ v = case lookup v dict of
Just res -> [(res, [])]
Nothing -> []
where
dict = map (show &&& id) [minBound .. maxBound]|]

genEnumType :: ConName -> [ConName] -> DecQ
genEnumType (conName -> name) cons =
dataD' (cxt []) name [] cons' [''Eq, ''Enum, ''Bounded]
where
cons' = map (\n -> normalC (conName n) []) cons
40 changes: 0 additions & 40 deletions RelationalRecord/src/Types/Enum.hs
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,4 @@ module Types.Enum where

import Helpers.DefineEnum

-- import Database.HDBC.Query.TH (makeRecordPersistableDefault)
import Database.Record
import Database.Record.TH (deriveNotNullType)

import Database.HDBC.SqlValue
import Data.ByteString.Char8 (unpack)
import Data.Char (toUpper, toLower)

$(defineEnum "test_enum")

-- TODO
-- This Haskell wrapper type is *not* type-safe, conversion might result
-- in inconsistencies or even a runtime error when the underlying DB type
-- has been changed :(

-- possible solution: build a custom TH action, select * from the
-- above defined table, creating the datatype below

-- But at least, mapping of a Postgres enum is working!


-- this definition must be verbatim to the enum type def in schema.sql !
data TestEnum = Inactive | Active | New deriving (Show, Read, Eq)


instance FromSql SqlValue TestEnum where
recordFromSql = valueRecordFromSql sqlRead

instance ToSql SqlValue TestEnum where
recordToSql = valueRecordToSql sqlShow


sqlRead :: Read a => SqlValue -> a
sqlRead (SqlString (s:ss)) = read $ toUpper s : ss
sqlRead (SqlByteString s) = sqlRead (SqlString $ unpack s)
sqlRead _ = undefined

sqlShow :: Show a => a -> SqlValue
sqlShow val = let (s:ss) = show val in SqlString $ toLower s : ss

$(deriveNotNullType [t| TestEnum |])
2 changes: 2 additions & 0 deletions RelationalRecord/stack.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ packages:
- relational-record
- relational-schemas



# The following packages have been ignored due to incompatibility with the
# resolver compiler, dependency conflicts with other packages
# or unsatisfied dependencies.
Expand Down