module PostgREST.Types where
import Protolude
import qualified GHC.Show
import Data.Aeson
import qualified Data.ByteString.Lazy as BL
import qualified Data.CaseInsensitive as CI
import qualified Data.HashMap.Strict as M
import Data.Tree
import qualified Data.Vector as V
import PostgREST.RangeQuery (NonnegRange)
import Network.HTTP.Types.Header (hContentType, Header)
data ContentType = CTApplicationJSON | CTTextCSV | CTOpenAPI
| CTSingularJSON | CTOctetStream
| CTAny | CTOther ByteString deriving Eq
data ApiRequestError = ActionInappropriate
| InvalidBody ByteString
| InvalidRange
| ParseRequestError Text Text
| UnknownRelation
| NoRelationBetween Text Text
| UnsupportedVerb
deriving (Show, Eq)
data DbStructure = DbStructure {
dbTables :: [Table]
, dbColumns :: [Column]
, dbRelations :: [Relation]
, dbPrimaryKeys :: [PrimaryKey]
, dbProcs :: M.HashMap Text ProcDescription
, pgVersion :: PgVersion
} deriving (Show, Eq)
data PgArg = PgArg {
pgaName :: Text
, pgaType :: Text
, pgaReq :: Bool
} deriving (Show, Eq)
data PgType = Scalar QualifiedIdentifier | Composite QualifiedIdentifier deriving (Eq, Show)
data RetType = Single PgType | SetOf PgType deriving (Eq, Show)
data ProcVolatility = Volatile | Stable | Immutable
deriving (Eq, Show)
data ProcDescription = ProcDescription {
pdName :: Text
, pdDescription :: Maybe Text
, pdArgs :: [PgArg]
, pdReturnType :: RetType
, pdVolatility :: ProcVolatility
} deriving (Show, Eq)
type Schema = Text
type TableName = Text
type SqlQuery = Text
type SqlFragment = Text
type RequestBody = BL.ByteString
data Table = Table {
tableSchema :: Schema
, tableName :: TableName
, tableDescription :: Maybe Text
, tableInsertable :: Bool
} deriving (Show, Ord)
newtype ForeignKey = ForeignKey { fkCol :: Column } deriving (Show, Eq, Ord)
data Column =
Column {
colTable :: Table
, colName :: Text
, colDescription :: Maybe Text
, colPosition :: Int32
, colNullable :: Bool
, colType :: Text
, colUpdatable :: Bool
, colMaxLen :: Maybe Int32
, colPrecision :: Maybe Int32
, colDefault :: Maybe Text
, colEnum :: [Text]
, colFK :: Maybe ForeignKey
} deriving (Show, Ord)
type Synonym = (Column,Column)
data PrimaryKey = PrimaryKey {
pkTable :: Table
, pkName :: Text
} deriving (Show, Eq)
data OrderDirection = OrderAsc | OrderDesc deriving (Eq)
instance Show OrderDirection where
show OrderAsc = "asc"
show OrderDesc = "desc"
data OrderNulls = OrderNullsFirst | OrderNullsLast deriving (Eq)
instance Show OrderNulls where
show OrderNullsFirst = "nulls first"
show OrderNullsLast = "nulls last"
data OrderTerm = OrderTerm {
otTerm :: Field
, otDirection :: Maybe OrderDirection
, otNullOrder :: Maybe OrderNulls
} deriving (Show, Eq)
data QualifiedIdentifier = QualifiedIdentifier {
qiSchema :: Schema
, qiName :: TableName
} deriving (Show, Eq)
data RelationType = Child | Parent | Many | Root deriving (Show, Eq)
data Relation = Relation {
relTable :: Table
, relColumns :: [Column]
, relFTable :: Table
, relFColumns :: [Column]
, relType :: RelationType
, relLTable :: Maybe Table
, relLCols1 :: Maybe [Column]
, relLCols2 :: Maybe [Column]
} deriving (Show, Eq)
newtype PayloadJSON = PayloadJSON (V.Vector Object)
deriving (Show, Eq)
unPayloadJSON :: PayloadJSON -> V.Vector Object
unPayloadJSON (PayloadJSON objs) = objs
data Proxy = Proxy {
proxyScheme :: Text
, proxyHost :: Text
, proxyPort :: Integer
, proxyPath :: Text
} deriving (Show, Eq)
type Operator = Text
operators :: M.HashMap Operator SqlFragment
operators = M.union (M.fromList [
("eq", "="),
("gte", ">="),
("gt", ">"),
("lte", "<="),
("lt", "<"),
("neq", "<>"),
("like", "LIKE"),
("ilike", "ILIKE"),
("in", "IN"),
("is", "IS"),
("cs", "@>"),
("cd", "<@"),
("ov", "&&"),
("sl", "<<"),
("sr", ">>"),
("nxr", "&<"),
("nxl", "&>"),
("adj", "-|-"),
("@>", "@>"),
("<@", "<@")]) ftsOperators
ftsOperators :: M.HashMap Operator SqlFragment
ftsOperators = M.fromList [
("@@", "@@ to_tsquery"),
("fts", "@@ to_tsquery"),
("plfts", "@@ plainto_tsquery"),
("phfts", "@@ phraseto_tsquery")
]
data OpExpr = OpExpr Bool Operation deriving (Eq, Show)
data Operation = Op Operator SingleVal |
In ListVal |
Fts Operator (Maybe Language) SingleVal |
Join QualifiedIdentifier ForeignKey deriving (Eq, Show)
type Language = Text
type SingleVal = Text
type ListVal = [Text]
data LogicOperator = And | Or deriving Eq
instance Show LogicOperator where
show And = "AND"
show Or = "OR"
data LogicTree = Expr Bool LogicOperator [LogicTree] | Stmnt Filter deriving (Show, Eq)
type FieldName = Text
type JsonPath = [Text]
type Field = (FieldName, Maybe JsonPath)
type Alias = Text
type Cast = Text
type NodeName = Text
type RpcQParam = (Text, Text)
newtype GucHeader = GucHeader (Text, Text)
instance FromJSON GucHeader where
parseJSON (Object o) = case headMay (M.toList o) of
Just (k, String s) | M.size o == 1 -> pure $ GucHeader (k, s)
| otherwise -> mzero
_ -> mzero
parseJSON _ = mzero
toHeaders :: [GucHeader] -> [Header]
toHeaders = map $ \(GucHeader (k, v)) -> (CI.mk $ toS k, toS v)
type RelationDetail = Text
type SelectItem = (Field, Maybe Cast, Maybe Alias, Maybe RelationDetail)
type EmbedPath = [Text]
data Filter = Filter { field::Field, opExpr::OpExpr } deriving (Show, Eq)
data ReadQuery = Select { select::[SelectItem], from::[TableName], where_::[LogicTree], order::Maybe [OrderTerm], range_::NonnegRange } deriving (Show, Eq)
data MutateQuery = Insert { in_::TableName, qPayload::PayloadJSON, returning::[FieldName] }
| Delete { in_::TableName, where_::[LogicTree], returning::[FieldName] }
| Update { in_::TableName, qPayload::PayloadJSON, where_::[LogicTree], returning::[FieldName] } deriving (Show, Eq)
type ReadNode = (ReadQuery, (NodeName, Maybe Relation, Maybe Alias, Maybe RelationDetail))
type ReadRequest = Tree ReadNode
type MutateRequest = MutateQuery
data DbRequest = DbRead ReadRequest | DbMutate MutateRequest
instance Eq Table where
Table{tableSchema=s1,tableName=n1} == Table{tableSchema=s2,tableName=n2} = s1 == s2 && n1 == n2
instance Eq Column where
Column{colTable=t1,colName=n1} == Column{colTable=t2,colName=n2} = t1 == t2 && n1 == n2
toHeader :: ContentType -> Header
toHeader ct = (hContentType, toMime ct <> "; charset=utf-8")
toMime :: ContentType -> ByteString
toMime CTApplicationJSON = "application/json"
toMime CTTextCSV = "text/csv"
toMime CTOpenAPI = "application/openapi+json"
toMime CTSingularJSON = "application/vnd.pgrst.object+json"
toMime CTOctetStream = "application/octet-stream"
toMime CTAny = "*/*"
toMime (CTOther ct) = ct
data PgVersion = PgVersion {
pgvNum :: Int32
, pgvName :: Text
} deriving (Eq, Ord, Show)
sourceCTEName :: SqlFragment
sourceCTEName = "pg_source"