postgresql-simple-bind-0.1.0.0: src/Database/PostgreSQL/Simple/Bind/Representation.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE FlexibleInstances #-}
{-|
Module: Database.PostgreSQL.Simple.Bind.Representation
Copyright: (c) 2016 Al Zohali
License: GPL3
Maintainer: Al Zohali <zohl@fmap.me>
Stability: experimental
An algebraic data type that (partially) represents function declaration based on
<http://www.postgresql.org/docs/9.5/static/sql-createfunction.html PostgreSQL documentation>.
-}
module Database.PostgreSQL.Simple.Bind.Representation (
PGFunction(..)
, PGArgument(..)
, PGColumn(..)
, PGResult(..)
, parsePGFunction
) where
import Prelude hiding (takeWhile, length)
import Control.Applicative
import Data.Attoparsec.Text
import Data.Text (Text, toLower, pack, unpack, append, length)
-- | Representation of a function's argument (name, type, is optional).
data PGArgument = PGArgument String String Bool deriving (Show, Eq)
-- | Representation of a PostrgeSQL function signature (schema, name, arguments, result).
data PGFunction = PGFunction String String [PGArgument] PGResult deriving (Show, Eq)
-- | Representation of a resultant's column (name, type).
data PGColumn = PGColumn String String deriving (Show, Eq)
-- | Representation of a function's return value.
data PGResult = PGSingle String
| PGSetOf String
| PGTable [PGColumn] deriving (Show, Eq)
-- | Takes PostgreSQL function signature and represent it as an algebraic data type.
parsePGFunction :: Text -> PGFunction
parsePGFunction s = result where
result = case (parseOnly parseFunction s) of
Right x -> x
ss = skipSpace
parseFunction = do
ss *> asciiCI "function"
schema <- ss *> ((parseIdentifier <* (char '.')) <|> (string ""))
name <- ss *> parseIdentifier
args <- ss *> char '(' *> (parseArgs `sepBy` (char ','))
ret <- ss *> char ')' *> parseReturn
return $ PGFunction (unpack schema) (unpack name) args ret
parseArgs = do
name <- ss *> parseIdentifier
datatype <- ss *> parseType
def <- ss *> ((asciiCI "default" <|> string "=") *> (takeTill (inClass ",)")) <|> (string ""))
-- WARNING: parsing default values requires ability to parse almost arbitraty expressions.
-- Here is a quick and dirty implementation of the parser.
return $ PGArgument (unpack name) (unpack datatype) ((length def) > 0)
parseCols = do
name <- ss *> parseIdentifier
datatype <- ss *> parseType <* ss
return $ PGColumn (unpack name) (unpack datatype)
parseReturn = do
ret <- ss *> asciiCI "returns" *> ss *> (
asciiCI "setof"
<|> asciiCI "table"
<|> parseType)
case toLower(ret) of
"setof" -> (PGSetOf . unpack) <$> (ss *> parseType)
"table" -> PGTable <$> (ss *> char '(' *> parseCols `sepBy` (char ',') <* ss <* char ')')
t -> return $ PGSingle (unpack t)
parseIdentifier = do
s1 <- takeWhile1 (inClass "a-zA-Z_")
s2 <- takeWhile (inClass "a-zA-Z_0-9$")
return $ toLower $ s1 `append` s2
parseType = toLower <$> (foldr1 (<|>) $
(map asciiCI [ "double precision" ])
++ (map (\s -> (asciiCI s <* ss <* (parseModifiers 1))) ["bit", "character varying"])
++ (map (\s -> (asciiCI s <* ss <* (parseModifiers 2))) ["numeric", "decimal"])
++ (map parseTime ["timestamp", "time"])
++ [parseInterval, parseIdentifier <* (parseModifiers 4)])
-- WARNING: user defined types can have more complex modifiers.
-- The argument of parseModifiers might be a subject to change.
parseTime s = do
base <- asciiCI s <* ss <* (parseModifiers 1)
tz <- ss *> ((asciiCI "with time zone") <|> (asciiCI "without time zone") <|> (string ""))
return $ case tz of
"" -> base
_ -> base `append` " " `append` tz
parseInterval = do
base <- (asciiCI "interval") <* ss
fields <- foldr1 (<|>) $ map asciiCI [
"year to month"
, "day to hour"
, "day to minute"
, "day to second"
, "hour to minute"
, "hour to second"
, "minute to second"
, "year"
, "month"
, "day"
, "hour"
, "minute"
, "second"
, ""]
ss *> (parseModifiers 1)
return $ case fields of
"" -> base
_ -> base `append` " " `append` fields
parseModifiers n = ((char '(') *>(foldl (*>)
(ss *> decimal *> ss)
(replicate (n - 1) (char ',' *> ss *> decimal *> ss)))
*> (char ')') *> (string ""))
<|> (case n of
1 -> (string "")
_ -> (parseModifiers (n - 1)))