2021-09-24 01:56:37 +03:00
|
|
|
-- | This module defines the BackendSchema class, that a backend must implement
|
|
|
|
-- for its schema to be generated. From the top level of the schema down to its
|
|
|
|
-- leaf values, every component has a matching function in the
|
|
|
|
-- schema. Combinators in other modules provide a default implementation at all
|
|
|
|
-- layers.
|
|
|
|
--
|
|
|
|
-- Consider, for example, the following query, for a given table "author":
|
|
|
|
--
|
|
|
|
-- query {
|
|
|
|
-- author(where: {id: {_eq: 2}}) {
|
|
|
|
-- name
|
|
|
|
-- }
|
|
|
|
-- }
|
|
|
|
--
|
|
|
|
-- The chain of functions leading to a parser for this RootField will be along
|
|
|
|
-- the lines of:
|
|
|
|
--
|
|
|
|
-- > buildTableQueryFields
|
|
|
|
-- > selectTable
|
|
|
|
-- > tableArgs
|
|
|
|
-- > tableWhere
|
|
|
|
-- > boolExp
|
|
|
|
-- > comparisonExp
|
|
|
|
-- > columnParser
|
|
|
|
-- > tableSelectionSet
|
|
|
|
-- > fieldSelection
|
|
|
|
--
|
|
|
|
-- Several of those steps are part of the class, meaning that a backend can
|
|
|
|
-- customize part of this tree without having to reimplement all of it. For
|
|
|
|
-- instance, a backend that supports a different set ot table arguments can
|
|
|
|
-- choose to reimplement @tableArgs@, but can still use @tableWhere@ in its
|
|
|
|
-- custom implementation.
|
2020-12-01 18:50:18 +03:00
|
|
|
module Hasura.GraphQL.Schema.Backend where
|
|
|
|
|
2021-09-24 01:56:37 +03:00
|
|
|
import Data.Has
|
|
|
|
import Hasura.Base.Error
|
|
|
|
import Hasura.GraphQL.Parser hiding (Type)
|
|
|
|
import Hasura.GraphQL.Schema.Common
|
|
|
|
import Hasura.Prelude
|
|
|
|
import Hasura.RQL.IR
|
2021-10-01 15:52:19 +03:00
|
|
|
import Hasura.RQL.IR.Insert qualified as IR
|
2021-09-24 01:56:37 +03:00
|
|
|
import Hasura.RQL.IR.Select qualified as IR
|
|
|
|
import Hasura.RQL.IR.Update qualified as IR
|
|
|
|
import Hasura.RQL.Types hiding (EnumValueInfo)
|
|
|
|
import Language.GraphQL.Draft.Syntax (Nullability)
|
|
|
|
import Language.GraphQL.Draft.Syntax qualified as G
|
2020-12-01 18:50:18 +03:00
|
|
|
|
2021-02-03 19:17:20 +03:00
|
|
|
-- TODO: it might make sense to add those constraints to MonadSchema directly?
|
|
|
|
type MonadBuildSchema b r m n =
|
2021-09-24 01:56:37 +03:00
|
|
|
( Backend b,
|
|
|
|
BackendSchema b,
|
|
|
|
MonadError QErr m,
|
|
|
|
MonadSchema n m,
|
|
|
|
MonadTableInfo r m,
|
|
|
|
MonadRole r m,
|
|
|
|
Has QueryContext r
|
2021-02-03 19:17:20 +03:00
|
|
|
)
|
|
|
|
|
2020-12-01 18:50:18 +03:00
|
|
|
class Backend b => BackendSchema (b :: BackendType) where
|
2021-02-03 19:17:20 +03:00
|
|
|
-- top level parsers
|
2021-09-24 01:56:37 +03:00
|
|
|
buildTableQueryFields ::
|
|
|
|
MonadBuildSchema b r m n =>
|
|
|
|
SourceName ->
|
|
|
|
SourceConfig b ->
|
|
|
|
Maybe QueryTagsConfig ->
|
|
|
|
TableName b ->
|
|
|
|
TableInfo b ->
|
|
|
|
G.Name ->
|
|
|
|
SelPermInfo b ->
|
|
|
|
m [FieldParser n (QueryRootField UnpreparedValue)]
|
|
|
|
buildTableRelayQueryFields ::
|
|
|
|
MonadBuildSchema b r m n =>
|
|
|
|
SourceName ->
|
|
|
|
SourceConfig b ->
|
|
|
|
Maybe QueryTagsConfig ->
|
|
|
|
TableName b ->
|
|
|
|
TableInfo b ->
|
|
|
|
G.Name ->
|
|
|
|
NESeq (ColumnInfo b) ->
|
|
|
|
SelPermInfo b ->
|
|
|
|
m [FieldParser n (QueryRootField UnpreparedValue)]
|
|
|
|
buildTableInsertMutationFields ::
|
|
|
|
MonadBuildSchema b r m n =>
|
|
|
|
SourceName ->
|
|
|
|
SourceConfig b ->
|
|
|
|
Maybe QueryTagsConfig ->
|
|
|
|
TableName b ->
|
|
|
|
TableInfo b ->
|
|
|
|
G.Name ->
|
|
|
|
InsPermInfo b ->
|
|
|
|
Maybe (SelPermInfo b) ->
|
|
|
|
Maybe (UpdPermInfo b) ->
|
|
|
|
m [FieldParser n (MutationRootField UnpreparedValue)]
|
|
|
|
buildTableUpdateMutationFields ::
|
|
|
|
MonadBuildSchema b r m n =>
|
|
|
|
SourceName ->
|
|
|
|
SourceConfig b ->
|
|
|
|
Maybe QueryTagsConfig ->
|
|
|
|
TableName b ->
|
|
|
|
TableInfo b ->
|
|
|
|
G.Name ->
|
|
|
|
UpdPermInfo b ->
|
|
|
|
Maybe (SelPermInfo b) ->
|
|
|
|
m [FieldParser n (MutationRootField UnpreparedValue)]
|
|
|
|
buildTableDeleteMutationFields ::
|
|
|
|
MonadBuildSchema b r m n =>
|
|
|
|
SourceName ->
|
|
|
|
SourceConfig b ->
|
|
|
|
Maybe QueryTagsConfig ->
|
|
|
|
TableName b ->
|
|
|
|
TableInfo b ->
|
|
|
|
G.Name ->
|
|
|
|
DelPermInfo b ->
|
|
|
|
Maybe (SelPermInfo b) ->
|
|
|
|
m [FieldParser n (MutationRootField UnpreparedValue)]
|
|
|
|
buildFunctionQueryFields ::
|
|
|
|
MonadBuildSchema b r m n =>
|
|
|
|
SourceName ->
|
|
|
|
SourceConfig b ->
|
|
|
|
Maybe QueryTagsConfig ->
|
|
|
|
FunctionName b ->
|
|
|
|
FunctionInfo b ->
|
|
|
|
TableName b ->
|
|
|
|
SelPermInfo b ->
|
|
|
|
m [FieldParser n (QueryRootField UnpreparedValue)]
|
|
|
|
buildFunctionRelayQueryFields ::
|
|
|
|
MonadBuildSchema b r m n =>
|
|
|
|
SourceName ->
|
|
|
|
SourceConfig b ->
|
|
|
|
Maybe QueryTagsConfig ->
|
|
|
|
FunctionName b ->
|
|
|
|
FunctionInfo b ->
|
|
|
|
TableName b ->
|
|
|
|
NESeq (ColumnInfo b) ->
|
|
|
|
SelPermInfo b ->
|
|
|
|
m [FieldParser n (QueryRootField UnpreparedValue)]
|
|
|
|
buildFunctionMutationFields ::
|
|
|
|
MonadBuildSchema b r m n =>
|
|
|
|
SourceName ->
|
|
|
|
SourceConfig b ->
|
|
|
|
Maybe QueryTagsConfig ->
|
|
|
|
FunctionName b ->
|
|
|
|
FunctionInfo b ->
|
|
|
|
TableName b ->
|
|
|
|
SelPermInfo b ->
|
|
|
|
m [FieldParser n (MutationRootField UnpreparedValue)]
|
2021-02-03 19:17:20 +03:00
|
|
|
|
2021-06-15 18:53:20 +03:00
|
|
|
-- table components
|
2021-09-24 01:56:37 +03:00
|
|
|
tableArguments ::
|
|
|
|
MonadBuildSchema b r m n =>
|
|
|
|
SourceName ->
|
|
|
|
TableInfo b ->
|
|
|
|
SelPermInfo b ->
|
|
|
|
m (InputFieldsParser n (IR.SelectArgsG b (UnpreparedValue b)))
|
2021-06-15 18:53:20 +03:00
|
|
|
|
2021-10-01 15:52:19 +03:00
|
|
|
-- | Make a parser for relationships. Default implementaton elides
|
|
|
|
-- relationships altogether.
|
|
|
|
mkRelationshipParser ::
|
|
|
|
MonadBuildSchema b r m n =>
|
|
|
|
SourceName ->
|
|
|
|
RelInfo b ->
|
|
|
|
m (Maybe (InputFieldsParser n (Maybe (IR.AnnotatedInsert b (UnpreparedValue b)))))
|
|
|
|
mkRelationshipParser _ _ = pure Nothing
|
|
|
|
|
2021-02-03 19:17:20 +03:00
|
|
|
-- backend extensions
|
2021-09-24 01:56:37 +03:00
|
|
|
relayExtension :: Maybe (XRelay b)
|
2021-06-09 16:02:15 +03:00
|
|
|
nodesAggExtension :: Maybe (XNodesAgg b)
|
2021-02-03 19:17:20 +03:00
|
|
|
|
|
|
|
-- individual components
|
2021-09-24 01:56:37 +03:00
|
|
|
columnParser ::
|
|
|
|
(MonadSchema n m, MonadError QErr m) =>
|
|
|
|
ColumnType b ->
|
|
|
|
Nullability ->
|
|
|
|
m (Parser 'Both n (ValueWithOrigin (ColumnValue b)))
|
|
|
|
|
2021-10-01 15:52:19 +03:00
|
|
|
-- | Creates a parser for the "_on_conflict" object of the given table.
|
|
|
|
--
|
|
|
|
-- This object is used to generate the "ON CONFLICT" SQL clause: what should be
|
|
|
|
-- done if an insert raises a conflict? It may not always exist: it can't be
|
|
|
|
-- created if there aren't any unique or primary keys constraints. However, if
|
|
|
|
-- there are no columns for which the current role has update permissions, we
|
|
|
|
-- must still accept an empty list for `update_columns`; we do this by adding a
|
|
|
|
-- placeholder value to the enum (see 'tableUpdateColumnsEnum').
|
|
|
|
--
|
|
|
|
-- The default implementation elides on_conflict support.
|
|
|
|
conflictObject ::
|
|
|
|
MonadBuildSchema b r m n =>
|
|
|
|
SourceName ->
|
|
|
|
TableInfo b ->
|
|
|
|
Maybe (SelPermInfo b) ->
|
|
|
|
UpdPermInfo b ->
|
|
|
|
m (Maybe (Parser 'Input n (XOnConflict b, IR.ConflictClauseP1 b (UnpreparedValue b))))
|
|
|
|
conflictObject _ _ _ _ = pure Nothing
|
|
|
|
|
2020-12-01 18:50:18 +03:00
|
|
|
-- | The "path" argument for json column fields
|
2021-09-24 01:56:37 +03:00
|
|
|
jsonPathArg ::
|
|
|
|
MonadParse n =>
|
|
|
|
ColumnType b ->
|
|
|
|
InputFieldsParser n (Maybe (IR.ColumnOp b))
|
|
|
|
|
|
|
|
orderByOperators ::
|
|
|
|
NonEmpty (Definition EnumValueInfo, (BasicOrderType b, NullsOrderType b))
|
|
|
|
comparisonExps ::
|
|
|
|
MonadBuildSchema b r m n =>
|
|
|
|
ColumnType b ->
|
|
|
|
m (Parser 'Input n [ComparisonExp b])
|
|
|
|
updateOperators ::
|
|
|
|
(MonadSchema n m, MonadTableInfo r m) =>
|
|
|
|
TableInfo b ->
|
|
|
|
UpdPermInfo b ->
|
|
|
|
m (Maybe (InputFieldsParser n [(Column b, IR.UpdOpExpG (UnpreparedValue b))]))
|
2020-12-01 18:50:18 +03:00
|
|
|
mkCountType :: Maybe Bool -> Maybe [Column b] -> CountType b
|
|
|
|
aggregateOrderByCountType :: ScalarType b
|
2021-09-24 01:56:37 +03:00
|
|
|
|
2020-12-01 18:50:18 +03:00
|
|
|
-- | Computed field parser
|
2021-09-24 01:56:37 +03:00
|
|
|
computedField ::
|
|
|
|
MonadBuildSchema b r m n =>
|
|
|
|
SourceName ->
|
|
|
|
ComputedFieldInfo b ->
|
|
|
|
TableName b ->
|
|
|
|
SelPermInfo b ->
|
|
|
|
m (Maybe (FieldParser n (AnnotatedField b)))
|
|
|
|
|
2020-12-01 18:50:18 +03:00
|
|
|
-- | The 'node' root field of a Relay request.
|
2021-09-24 01:56:37 +03:00
|
|
|
node ::
|
|
|
|
MonadBuildSchema b r m n =>
|
|
|
|
m (Parser 'Output n (HashMap (TableName b) (SourceName, SourceConfig b, SelPermInfo b, PrimaryKeyColumns b, AnnotatedFields b)))
|
2020-12-01 18:50:18 +03:00
|
|
|
|
2021-02-08 13:39:59 +03:00
|
|
|
-- SQL literals
|
|
|
|
columnDefaultValue :: Column b -> SQLExpression b
|
|
|
|
|
2021-10-01 15:52:19 +03:00
|
|
|
-- Extra insert data
|
|
|
|
getExtraInsertData :: TableInfo b -> ExtraInsertData b
|
|
|
|
|
2020-12-01 18:50:18 +03:00
|
|
|
type ComparisonExp b = OpExpG b (UnpreparedValue b)
|
2021-02-03 19:17:20 +03:00
|
|
|
|
|
|
|
data BackendExtension b = BackendExtension
|
2021-09-24 01:56:37 +03:00
|
|
|
{ backendRelay :: Maybe (XRelay b),
|
|
|
|
backendNodesAgg :: Maybe (XNodesAgg b)
|
2021-02-03 19:17:20 +03:00
|
|
|
}
|