add basic ARCHITECTURE.md (#2503)
* refactor: move ApiRequest a top-level module * refactor: rename DbStructure to SchemaCache * refactor: GucHeader inside Response * refactor: admin app to Workers
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
{-# LANGUAGE DeriveAnyClass #-}
|
||||
{-# LANGUAGE DeriveGeneric #-}
|
||||
|
||||
module PostgREST.SchemaCache.Identifiers
|
||||
( QualifiedIdentifier(..)
|
||||
, Schema
|
||||
, TableName
|
||||
, FieldName
|
||||
, dumpQi
|
||||
, toQi
|
||||
) where
|
||||
|
||||
import qualified Data.Aeson as JSON
|
||||
import qualified Data.Text as T
|
||||
|
||||
import Protolude
|
||||
|
||||
|
||||
-- | Represents a pg identifier with a prepended schema name "schema.table".
|
||||
-- When qiSchema is "", the schema is defined by the pg search_path.
|
||||
data QualifiedIdentifier = QualifiedIdentifier
|
||||
{ qiSchema :: Schema
|
||||
, qiName :: TableName
|
||||
}
|
||||
deriving (Eq, Ord, Generic, JSON.ToJSON, JSON.ToJSONKey)
|
||||
|
||||
instance Hashable QualifiedIdentifier
|
||||
|
||||
dumpQi :: QualifiedIdentifier -> Text
|
||||
dumpQi (QualifiedIdentifier s i) =
|
||||
(if T.null s then mempty else s <> ".") <> i
|
||||
|
||||
-- TODO: Handle a case where the QI comes like this: "my.fav.schema"."my.identifier"
|
||||
-- Right now it only handles the schema.identifier case
|
||||
toQi :: Text -> QualifiedIdentifier
|
||||
toQi txt = case T.drop 1 <$> T.breakOn "." txt of
|
||||
(i, "") -> QualifiedIdentifier mempty i
|
||||
(s, i) -> QualifiedIdentifier s i
|
||||
|
||||
type Schema = Text
|
||||
type TableName = Text
|
||||
type FieldName = Text
|
||||
@@ -0,0 +1,91 @@
|
||||
{-# LANGUAGE DeriveAnyClass #-}
|
||||
{-# LANGUAGE DeriveGeneric #-}
|
||||
|
||||
module PostgREST.SchemaCache.Proc
|
||||
( PgType(..)
|
||||
, ProcDescription(..)
|
||||
, ProcParam(..)
|
||||
, ProcVolatility(..)
|
||||
, ProcsMap
|
||||
, RetType(..)
|
||||
, procReturnsScalar
|
||||
, procReturnsSingle
|
||||
, procReturnsVoid
|
||||
, procTableName
|
||||
) where
|
||||
|
||||
import qualified Data.Aeson as JSON
|
||||
import qualified Data.HashMap.Strict as HM
|
||||
|
||||
import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..),
|
||||
Schema, TableName)
|
||||
|
||||
import Protolude
|
||||
|
||||
data PgType
|
||||
= Scalar
|
||||
| Composite QualifiedIdentifier
|
||||
deriving (Eq, Ord, Generic, JSON.ToJSON)
|
||||
|
||||
data RetType
|
||||
= Single PgType
|
||||
| SetOf PgType
|
||||
deriving (Eq, Ord, Generic, JSON.ToJSON)
|
||||
|
||||
data ProcVolatility
|
||||
= Volatile
|
||||
| Stable
|
||||
| Immutable
|
||||
deriving (Eq, Ord, Generic, JSON.ToJSON)
|
||||
|
||||
data ProcDescription = ProcDescription
|
||||
{ pdSchema :: Schema
|
||||
, pdName :: Text
|
||||
, pdDescription :: Maybe Text
|
||||
, pdParams :: [ProcParam]
|
||||
, pdReturnType :: Maybe RetType
|
||||
, pdVolatility :: ProcVolatility
|
||||
, pdHasVariadic :: Bool
|
||||
}
|
||||
deriving (Eq, Generic, JSON.ToJSON)
|
||||
|
||||
data ProcParam = ProcParam
|
||||
{ ppName :: Text
|
||||
, ppType :: Text
|
||||
, ppReq :: Bool
|
||||
, ppVar :: Bool
|
||||
}
|
||||
deriving (Eq, Ord, Generic, JSON.ToJSON)
|
||||
|
||||
-- Order by least number of params in the case of overloaded functions
|
||||
instance Ord ProcDescription where
|
||||
ProcDescription schema1 name1 des1 prms1 rt1 vol1 hasVar1 `compare` ProcDescription schema2 name2 des2 prms2 rt2 vol2 hasVar2
|
||||
| schema1 == schema2 && name1 == name2 && length prms1 < length prms2 = LT
|
||||
| schema2 == schema2 && name1 == name2 && length prms1 > length prms2 = GT
|
||||
| otherwise = (schema1, name1, des1, prms1, rt1, vol1, hasVar1) `compare` (schema2, name2, des2, prms2, rt2, vol2, hasVar2)
|
||||
|
||||
-- | A map of all procs, all of which can be overloaded(one entry will have more than one ProcDescription).
|
||||
-- | It uses a HashMap for a faster lookup.
|
||||
type ProcsMap = HM.HashMap QualifiedIdentifier [ProcDescription]
|
||||
|
||||
procReturnsScalar :: ProcDescription -> Bool
|
||||
procReturnsScalar proc = case proc of
|
||||
ProcDescription{pdReturnType = Just (Single Scalar)} -> True
|
||||
ProcDescription{pdReturnType = Just (SetOf Scalar)} -> True
|
||||
_ -> False
|
||||
|
||||
procReturnsSingle :: ProcDescription -> Bool
|
||||
procReturnsSingle proc = case proc of
|
||||
ProcDescription{pdReturnType = Just (Single _)} -> True
|
||||
_ -> False
|
||||
|
||||
procReturnsVoid :: ProcDescription -> Bool
|
||||
procReturnsVoid proc = case proc of
|
||||
ProcDescription{pdReturnType = Nothing} -> True
|
||||
_ -> False
|
||||
|
||||
procTableName :: ProcDescription -> Maybe TableName
|
||||
procTableName proc = case pdReturnType proc of
|
||||
Just (SetOf (Composite qi)) -> Just $ qiName qi
|
||||
Just (Single (Composite qi)) -> Just $ qiName qi
|
||||
_ -> Nothing
|
||||
@@ -0,0 +1,64 @@
|
||||
{-# LANGUAGE DeriveAnyClass #-}
|
||||
{-# LANGUAGE DeriveGeneric #-}
|
||||
|
||||
module PostgREST.SchemaCache.Relationship
|
||||
( Cardinality(..)
|
||||
, Relationship(..)
|
||||
, Junction(..)
|
||||
, RelationshipsMap
|
||||
) where
|
||||
|
||||
import qualified Data.Aeson as JSON
|
||||
import qualified Data.HashMap.Strict as HM
|
||||
|
||||
import PostgREST.SchemaCache.Identifiers (FieldName,
|
||||
QualifiedIdentifier, Schema)
|
||||
|
||||
import Protolude
|
||||
|
||||
|
||||
-- | Relationship between two tables.
|
||||
data Relationship = Relationship
|
||||
{ relTable :: QualifiedIdentifier
|
||||
, relForeignTable :: QualifiedIdentifier
|
||||
, relIsSelf :: Bool -- ^ Whether is a self relationship
|
||||
, relCardinality :: Cardinality
|
||||
, relTableIsView :: Bool
|
||||
, relFTableIsView :: Bool
|
||||
}
|
||||
| ComputedRelationship
|
||||
{ relFunction :: QualifiedIdentifier
|
||||
, relTable :: QualifiedIdentifier
|
||||
, relForeignTable :: QualifiedIdentifier
|
||||
, relToOne :: Bool
|
||||
, relIsSelf :: Bool
|
||||
}
|
||||
deriving (Eq, Ord, Generic, JSON.ToJSON)
|
||||
|
||||
-- | The relationship cardinality
|
||||
-- | https://en.wikipedia.org/wiki/Cardinality_(data_modeling)
|
||||
data Cardinality
|
||||
= O2M {relCons :: FKConstraint, relColumns :: [(FieldName, FieldName)]}
|
||||
-- ^ one-to-many
|
||||
| M2O {relCons :: FKConstraint, relColumns :: [(FieldName, FieldName)]}
|
||||
-- ^ many-to-one
|
||||
| O2O {relCons :: FKConstraint, relColumns :: [(FieldName, FieldName)]}
|
||||
-- ^ one-to-one, this is a refinement over M2O so operating on it is pretty much the same as M2O
|
||||
| M2M Junction
|
||||
-- ^ many-to-many
|
||||
deriving (Eq, Ord, Generic, JSON.ToJSON)
|
||||
|
||||
type FKConstraint = Text
|
||||
|
||||
-- | Junction table on an M2M relationship
|
||||
data Junction = Junction
|
||||
{ junTable :: QualifiedIdentifier
|
||||
, junConstraint1 :: FKConstraint
|
||||
, junConstraint2 :: FKConstraint
|
||||
, junColumns1 :: [(FieldName, FieldName)]
|
||||
, junColumns2 :: [(FieldName, FieldName)]
|
||||
}
|
||||
deriving (Eq, Ord, Generic, JSON.ToJSON)
|
||||
|
||||
-- | Key based on the source table and the foreign table schema
|
||||
type RelationshipsMap = HM.HashMap (QualifiedIdentifier, Schema) [Relationship]
|
||||
@@ -0,0 +1,49 @@
|
||||
{-# LANGUAGE DeriveAnyClass #-}
|
||||
{-# LANGUAGE DeriveGeneric #-}
|
||||
|
||||
module PostgREST.SchemaCache.Table
|
||||
( Column(..)
|
||||
, Table(..)
|
||||
, TablesMap
|
||||
) where
|
||||
|
||||
import qualified Data.Aeson as JSON
|
||||
import qualified Data.HashMap.Strict as HM
|
||||
|
||||
import PostgREST.SchemaCache.Identifiers (FieldName,
|
||||
QualifiedIdentifier (..),
|
||||
Schema, TableName)
|
||||
|
||||
import Protolude
|
||||
|
||||
|
||||
data Table = Table
|
||||
{ tableSchema :: Schema
|
||||
, tableName :: TableName
|
||||
, tableDescription :: Maybe Text
|
||||
-- TODO Find a better way to separate tables and views
|
||||
, tableIsView :: Bool
|
||||
-- The following fields identify what can be done on the table/view, they're not related to the privileges granted to it
|
||||
, tableInsertable :: Bool
|
||||
, tableUpdatable :: Bool
|
||||
, tableDeletable :: Bool
|
||||
, tablePKCols :: [FieldName]
|
||||
, tableColumns :: [Column]
|
||||
}
|
||||
deriving (Show, Ord, Generic, JSON.ToJSON)
|
||||
|
||||
instance Eq Table where
|
||||
Table{tableSchema=s1,tableName=n1} == Table{tableSchema=s2,tableName=n2} = s1 == s2 && n1 == n2
|
||||
|
||||
data Column = Column
|
||||
{ colName :: FieldName
|
||||
, colDescription :: Maybe Text
|
||||
, colNullable :: Bool
|
||||
, colType :: Text
|
||||
, colMaxLen :: Maybe Int32
|
||||
, colDefault :: Maybe Text
|
||||
, colEnum :: [Text]
|
||||
}
|
||||
deriving (Eq, Show, Ord, Generic, JSON.ToJSON)
|
||||
|
||||
type TablesMap = HM.HashMap QualifiedIdentifier Table
|
||||
Reference in New Issue
Block a user