Created
August 12, 2016 21:06
-
-
Save singpolyma/f5de70c79a9636981a4ffb7ccd66eff0 to your computer and use it in GitHub Desktop.
*-simple helpers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module SqlSimpleHelpers where | |
import Prelude hiding (all) | |
import Control.Applicative ((<*>)) | |
import Data.Tagged (Tagged(..), asTaggedTypeOf) | |
import Data.Text (Text) | |
import qualified Data.Text as T | |
class SqlColumns a where | |
columns :: Tagged a [Text] -- ^ Columns representend in the Haskell data, in order used in ToRow/FromRow | |
class SqlTable a where | |
table :: Tagged a Text -- ^ Name of table data will normally come from for this type | |
class SqlPrimaryKey a where | |
primaryKey :: Tagged a Text -- ^ The column that is the primary key for this data | |
instance (SqlColumns a) => SqlColumns [a] where | |
columns = result | |
where | |
[w] = undefined `asTaggedTypeOf` result | |
result = Tagged (w `witness` columns) | |
instance (SqlTable a) => SqlTable [a] where | |
table = result | |
where | |
[w] = undefined `asTaggedTypeOf` result | |
result = Tagged (w `witness` table) | |
instance (SqlPrimaryKey a) => SqlPrimaryKey [a] where | |
primaryKey = result | |
where | |
[w] = undefined `asTaggedTypeOf` result | |
result = Tagged (w `witness` primaryKey) | |
-- | Use this when your data type does not contain the primary key, but you want to access it | |
data WithPrimaryKey key model = WithPrimaryKey key model | |
-- TODO: there are obvious ToRow/FromRow instances for WithPrimaryKey, but then we need to depend on the *-simple packages | |
instance (SqlColumns model, SqlPrimaryKey model) => SqlColumns (WithPrimaryKey key model) where | |
columns = result | |
where | |
WithPrimaryKey _ w = undefined `asTaggedTypeOf` result | |
result = Tagged ((w `witness` primaryKey) : (w `witness` columns)) | |
instance (SqlTable model) => SqlTable (WithPrimaryKey key model) where | |
table = result | |
where | |
WithPrimaryKey _ w = undefined `asTaggedTypeOf` result | |
result = Tagged (w `witness` table) | |
instance (SqlPrimaryKey model) => SqlPrimaryKey (WithPrimaryKey key model) where | |
primaryKey = result | |
where | |
WithPrimaryKey _ w = undefined `asTaggedTypeOf` result | |
result = Tagged (w `witness` primaryKey) | |
-- TODO: these "with" are great for insert/select one, but not great for more complex queries | |
with :: (Text -> a -> b) -> Tagged a Text -> a -> b | |
with f (Tagged q) v = f q v | |
with_ :: (Text -> m a) -> Tagged a Text -> m a | |
with_ f (Tagged q) = f q | |
withKey :: (Text -> key -> m a) -> Tagged a Text -> key -> m a | |
withKey f (Tagged q) k = f q k | |
insert :: (SqlTable a, SqlColumns a) => Tagged a Text | |
insert = Tagged (\t c -> T.concat [ | |
T.pack "INSERT INTO ", | |
t, | |
T.pack " (", | |
T.intercalate (T.pack ",") c, | |
T.pack ") VALUES (", | |
T.intercalate (T.pack ",") (map (const $ T.pack "?") c), | |
T.pack ")" | |
]) <*> table <*> columns | |
all :: (SqlTable a, SqlColumns a) => Tagged a Text | |
all = Tagged (\t c -> T.concat [ | |
T.pack "SELECT ", | |
T.intercalate (T.pack ",") c, | |
T.pack " FROM ", | |
t | |
]) <*> table <*> columns | |
one :: (SqlTable a, SqlColumns a, SqlPrimaryKey a) => Tagged a Text | |
one = Tagged (\q p -> T.concat [ | |
q, | |
T.pack " WHERE ", | |
p, | |
T.pack " = ?" | |
]) <*> all <*> primaryKey | |
update :: (SqlTable a, SqlColumns a) => Tagged a Text | |
update = Tagged (\t c -> T.concat [ | |
T.pack "UPDATE ", | |
t, | |
T.pack " SET ", | |
T.intercalate (T.pack ",") | |
(map (\name -> T.concat [name, T.pack " = c.", name]) c), | |
T.pack " FROM (VALUES (", | |
T.intercalate (T.pack ",") (map (const $ T.pack "?") c), | |
T.pack ")) AS c(", | |
T.intercalate (T.pack ",") c, | |
T.pack ")" | |
]) <*> table <*> columns | |
-- | Just for playing around in GHCI | |
printExecute :: (Show v) => Text -> v -> IO () | |
printExecute q v = print (q, v) | |
-- | Just for playing around in GHCI | |
-- Produces undefined because we're not actually doing the query | |
printQuery :: (Show v) => Text -> v -> IO a | |
printQuery q v = print (q, v) >> return undefined | |
-- | Just for playing around in GHCI | |
-- Produces undefined because we're not actually doing the query | |
printQuery_ :: Text -> IO a | |
printQuery_ q = print q >> return undefined | |
witness :: w -> Tagged w a -> a | |
witness _ (Tagged x) = x | |
{- | |
EXAMPLE | |
data User = User { | |
name :: Text, | |
age :: Int | |
} deriving (Show) | |
instance FromRow User where | |
fromRow = User <$> field <*> field | |
instance ToRow User where | |
toRow (User name age)= [toField name, toField age] | |
instance SqlColumns User where | |
columns = Tagged [T.pack "name", T.pack "age"] | |
instance SqlTable User where | |
table = Tagged (T.pack "users") | |
instance SqlPrimaryKey User where | |
primaryKey = Tagged (T.pack "user_id") | |
BEST USE CASES FOR WITH | |
with printExecute insert (User (T.pack "Dave") 42) | |
with printExecute insert [User (T.pack "Bob") 54, User (T.pack "Steve") 23] | |
withKey printQuery one (Only 1) :: IO User | |
THESE WORK BUT NEED MORE THOUGHT | |
with_ printQuery_ all :: IO [User] | |
with printExecute update [User (T.pack "Dave") 42, User (T.pack "Bob") 15] | |
-} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment