mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 08:02:15 +03:00
Add support for customising function root field names
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/2468 Co-authored-by: Philip Lykke Carlsen <358550+plcplc@users.noreply.github.com> GitOrigin-RevId: 5ff85bb02e4e651376a40914b7ae0aabc8524a05
This commit is contained in:
parent
826020d796
commit
42cd2e69c0
11
CHANGELOG.md
11
CHANGELOG.md
@ -2,8 +2,17 @@
|
||||
|
||||
## Next release
|
||||
(Add entries below in the order of server, console, cli, docs, others)
|
||||
- server: add support for openapi json of REST Endpoints
|
||||
|
||||
### Function field names customization (#7405)
|
||||
It is now possible to specify the GraphQL names of tracked SQL functions in
|
||||
Postgres sources, and different names may be given to the `_aggregate` and
|
||||
suffix-less versions. Aliases may be set by both
|
||||
`/v1/metadata/pg_track_function` and the new API endpoint
|
||||
`/v1/metadata/pg_set_function_customization.`
|
||||
|
||||
### Bug fixes and improvements
|
||||
|
||||
- server: add support for openapi json of REST Endpoints
|
||||
- server: enable inherited roles by default in the graphql-engine
|
||||
- server: support MSSQL insert mutations
|
||||
- console: fix v2 metadata imports
|
||||
|
@ -125,7 +125,7 @@ Args syntax
|
||||
* - comment
|
||||
- false
|
||||
- String
|
||||
- Comment for the function. This comment would replace the auto-generated
|
||||
- Comment for the function. This comment would replace the auto-generated
|
||||
comment for the function field in the GraphQL schema.
|
||||
|
||||
.. _pg_untrack_function:
|
||||
@ -173,6 +173,62 @@ Args syntax
|
||||
- :ref:`SourceName <SourceName>`
|
||||
- Name of the source database of the function (default: ``default``)
|
||||
|
||||
.. _pg_set_function_customization:
|
||||
|
||||
pg_set_function_customization
|
||||
-----------------------------
|
||||
|
||||
``pg_set_function_customization`` allows you to customize any given function with
|
||||
a custom name and custom root fields of an already tracked
|
||||
function. This will **replace** the already present customization.
|
||||
|
||||
Set the configuration for a function called ``search_articles``:
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
POST /v1/metadata HTTP/1.1
|
||||
Content-Type: application/json
|
||||
X-Hasura-Role: admin
|
||||
|
||||
{
|
||||
"type": "pg_set_function_customization",
|
||||
"args": {
|
||||
"function": "search_articles",
|
||||
"source": "default",
|
||||
"configuration": {
|
||||
"custom_root_fields": {
|
||||
"function": "FindArticles",
|
||||
"function_aggregate": "FindArticlesAgg"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.. _pg_set_function_customization_syntax:
|
||||
|
||||
Args syntax
|
||||
^^^^^^^^^^^
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Key
|
||||
- Required
|
||||
- Schema
|
||||
- Description
|
||||
* - function
|
||||
- true
|
||||
- :ref:`FunctionName <FunctionName>`
|
||||
- Name of the function
|
||||
* - configuration
|
||||
- false
|
||||
- :ref:`Function Configuration <function_configuration>`
|
||||
- Configuration for the function
|
||||
* - source
|
||||
- false
|
||||
- :ref:`SourceName <SourceName>`
|
||||
- Name of the source database of the function (default: ``default``)
|
||||
|
||||
.. _pg_create_function_permission:
|
||||
|
||||
pg_create_function_permission
|
||||
|
@ -137,6 +137,11 @@ The various types of queries are listed in the following table:
|
||||
- 1
|
||||
- Remove a Postgres SQL function
|
||||
|
||||
* - :ref:`pg_set_function_customization <pg_set_function_customization>`
|
||||
- :ref:`pg_set_function_customization_args <pg_set_function_customization_syntax>`
|
||||
- 1
|
||||
- Set function customization of an already tracked Postgres function
|
||||
|
||||
* - :ref:`pg_create_function_permission`
|
||||
- :ref:`pg_create_function_permission_args <pg_create_function_permission_syntax>`
|
||||
- 1
|
||||
|
@ -558,6 +558,27 @@ Custom Root Fields
|
||||
- ``String``
|
||||
- Customise the ``delete_<table-name>_by_pk`` root field
|
||||
|
||||
.. _custom_function_root_fields:
|
||||
|
||||
Custom Function Root Fields
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Key
|
||||
- Required
|
||||
- Schema
|
||||
- Description
|
||||
* - function
|
||||
- false
|
||||
- ``String``
|
||||
- Customise the ``<function-name>`` root field
|
||||
* - function_aggregate
|
||||
- false
|
||||
- ``String``
|
||||
- Customise the ``<function-name>_aggregete`` root field
|
||||
|
||||
.. _InsertPermission:
|
||||
|
||||
InsertPermission
|
||||
@ -1593,6 +1614,16 @@ Function Configuration
|
||||
- Required
|
||||
- Schema
|
||||
- Description
|
||||
* - custom_name
|
||||
- false
|
||||
- ``String``
|
||||
- Customise the ``<function-name>`` with the provided custom name value.
|
||||
The GraphQL nodes for the function will be generated according to the custom name.
|
||||
* - custom_root_fields
|
||||
- false
|
||||
- :ref:`Custom Function Root Fields <custom_function_root_fields>`
|
||||
- Customise the root fields
|
||||
|
||||
* - session_argument
|
||||
- false
|
||||
- `String`
|
||||
|
@ -61,7 +61,7 @@ buildFunctionInfo ::
|
||||
RawFunctionInfo ('Postgres pgKind) ->
|
||||
Maybe Text ->
|
||||
m (FunctionInfo ('Postgres pgKind), SchemaDependency)
|
||||
buildFunctionInfo source qf systemDefined FunctionConfig {..} permissions rawFuncInfo comment =
|
||||
buildFunctionInfo source qf systemDefined fc@FunctionConfig {..} permissions rawFuncInfo comment =
|
||||
either (throw400 NotSupported . showErrors) pure
|
||||
=<< MV.runValidateT validateFunction
|
||||
where
|
||||
@ -112,11 +112,17 @@ buildFunctionInfo source qf systemDefined FunctionConfig {..} permissions rawFun
|
||||
|
||||
inputArguments <- makeInputArguments
|
||||
|
||||
funcGivenName <- functionGraphQLName @('Postgres pgKind) qf `onLeft` throwError
|
||||
|
||||
let retTable = typeToTable returnType
|
||||
retJsonAggSelect = bool JASSingleObject JASMultipleRows retSet
|
||||
|
||||
functionInfo =
|
||||
FunctionInfo
|
||||
qf
|
||||
(getFunctionGQLName funcGivenName fc)
|
||||
(getFunctionArgsGQLName funcGivenName fc)
|
||||
(getFunctionAggregateGQLName funcGivenName fc)
|
||||
systemDefined
|
||||
funVol
|
||||
exposeAs
|
||||
|
@ -197,17 +197,15 @@ buildFunctionRelayQueryFields ::
|
||||
SelPermInfo ('Postgres pgKind) ->
|
||||
m [FieldParser n (QueryRootField UnpreparedValue)]
|
||||
buildFunctionRelayQueryFields sourceName sourceInfo queryTagsConfig functionName functionInfo tableName pkeyColumns selPerms = do
|
||||
funcName <- functionGraphQLName @('Postgres pgKind) functionName `onLeft` throwError
|
||||
let mkRF =
|
||||
RFDB sourceName
|
||||
. AB.mkAnyBackend
|
||||
. SourceConfigWith sourceInfo queryTagsConfig
|
||||
. QDBR
|
||||
fieldName = funcName <> $$(G.litName "_connection")
|
||||
fieldDesc = Just $ G.Description $ "execute function " <> functionName <<> " which returns " <>> tableName
|
||||
fmap afold $
|
||||
optionalFieldParser (mkRF . QDBConnection) $
|
||||
selectFunctionConnection sourceName functionInfo fieldName fieldDesc pkeyColumns selPerms
|
||||
selectFunctionConnection sourceName functionInfo fieldDesc pkeyColumns selPerms
|
||||
|
||||
----------------------------------------------------------------
|
||||
-- Individual components
|
||||
|
@ -177,27 +177,29 @@ buildFunctionQueryFields ::
|
||||
SelPermInfo b ->
|
||||
m [FieldParser n (QueryRootField UnpreparedValue)]
|
||||
buildFunctionQueryFields sourceName sourceInfo queryTagsConfig functionName functionInfo tableName selPerms = do
|
||||
funcName <- functionGraphQLName @b functionName `onLeft` throwError
|
||||
let mkRF =
|
||||
RFDB sourceName
|
||||
. AB.mkAnyBackend
|
||||
. SourceConfigWith sourceInfo queryTagsConfig
|
||||
. QDBR
|
||||
|
||||
-- select function
|
||||
funcDesc =
|
||||
Just . G.Description $
|
||||
flip fromMaybe (_fiComment functionInfo) $ "execute function " <> functionName <<> " which returns " <>> tableName
|
||||
-- select function agg
|
||||
funcAggName = funcName <> $$(G.litName "_aggregate")
|
||||
|
||||
funcAggDesc = Just $ G.Description $ "execute function " <> functionName <<> " and query aggregates on result of table type " <>> tableName
|
||||
|
||||
queryResultType =
|
||||
case _fiJsonAggSelect functionInfo of
|
||||
JASMultipleRows -> QDBMultipleRows
|
||||
JASSingleObject -> QDBSingleRow
|
||||
|
||||
catMaybes
|
||||
<$> sequenceA
|
||||
[ requiredFieldParser (mkRF . queryResultType) $ selectFunction sourceName functionInfo funcName funcDesc selPerms,
|
||||
optionalFieldParser (mkRF . QDBAggregation) $ selectFunctionAggregate sourceName functionInfo funcAggName funcAggDesc selPerms
|
||||
[ requiredFieldParser (mkRF . queryResultType) $ selectFunction sourceName functionInfo funcDesc selPerms,
|
||||
optionalFieldParser (mkRF . QDBAggregation) $ selectFunctionAggregate sourceName functionInfo funcAggDesc selPerms
|
||||
]
|
||||
|
||||
buildFunctionMutationFields ::
|
||||
@ -212,16 +214,17 @@ buildFunctionMutationFields ::
|
||||
SelPermInfo b ->
|
||||
m [FieldParser n (MutationRootField UnpreparedValue)]
|
||||
buildFunctionMutationFields sourceName sourceInfo queryTagsConfig functionName functionInfo tableName selPerms = do
|
||||
funcName <- functionGraphQLName @b functionName `onLeft` throwError
|
||||
let mkRF =
|
||||
RFDB sourceName
|
||||
. AB.mkAnyBackend
|
||||
. SourceConfigWith sourceInfo queryTagsConfig
|
||||
. MDBR
|
||||
|
||||
funcDesc = Just $ G.Description $ "execute VOLATILE function " <> functionName <<> " which returns " <>> tableName
|
||||
|
||||
jsonAggSelect = _fiJsonAggSelect functionInfo
|
||||
catMaybes
|
||||
<$> sequenceA
|
||||
[ requiredFieldParser (mkRF . MDBFunction jsonAggSelect) $ selectFunction sourceName functionInfo funcName funcDesc selPerms
|
||||
[ requiredFieldParser (mkRF . MDBFunction jsonAggSelect) $ selectFunction sourceName functionInfo funcDesc selPerms
|
||||
-- TODO: do we want aggregate mutation functions?
|
||||
]
|
||||
|
@ -536,34 +536,31 @@ selectFunction ::
|
||||
SourceName ->
|
||||
-- | SQL function info
|
||||
FunctionInfo b ->
|
||||
-- | field display name
|
||||
G.Name ->
|
||||
-- | field description, if any
|
||||
Maybe G.Description ->
|
||||
-- | select permissions of the target table
|
||||
SelPermInfo b ->
|
||||
m (FieldParser n (SelectExp b))
|
||||
selectFunction sourceName function fieldName description selectPermissions = do
|
||||
selectFunction sourceName fi@FunctionInfo {..} description selectPermissions = do
|
||||
stringifyNum <- asks $ qcStringifyNum . getter
|
||||
let tableName = _fiReturnType function
|
||||
tableInfo <- askTableInfo sourceName tableName
|
||||
tableInfo <- askTableInfo sourceName _fiReturnType
|
||||
tableArgsParser <- tableArguments sourceName tableInfo selectPermissions
|
||||
functionArgsParser <- customSQLFunctionArgs function
|
||||
selectionSetParser <- returnFunctionParser sourceName tableInfo selectPermissions
|
||||
functionArgsParser <- customSQLFunctionArgs fi _fiGQLName _fiGQLArgsName
|
||||
let argsParser = liftA2 (,) functionArgsParser tableArgsParser
|
||||
pure $
|
||||
P.subselection fieldName description argsParser selectionSetParser
|
||||
P.subselection _fiGQLName description argsParser selectionSetParser
|
||||
<&> \((funcArgs, tableArgs'), fields) ->
|
||||
IR.AnnSelectG
|
||||
{ IR._asnFields = fields,
|
||||
IR._asnFrom = IR.FromFunction (_fiName function) funcArgs Nothing,
|
||||
IR._asnFrom = IR.FromFunction _fiSQLName funcArgs Nothing,
|
||||
IR._asnPerm = tablePermissionsInfo selectPermissions,
|
||||
IR._asnArgs = tableArgs',
|
||||
IR._asnStrfyNum = stringifyNum
|
||||
}
|
||||
where
|
||||
returnFunctionParser =
|
||||
case _fiJsonAggSelect function of
|
||||
case _fiJsonAggSelect of
|
||||
JASSingleObject -> tableSelectionSet
|
||||
JASMultipleRows -> tableSelectionList
|
||||
|
||||
@ -574,22 +571,19 @@ selectFunctionAggregate ::
|
||||
SourceName ->
|
||||
-- | SQL function info
|
||||
FunctionInfo b ->
|
||||
-- | field display name
|
||||
G.Name ->
|
||||
-- | field description, if any
|
||||
Maybe G.Description ->
|
||||
-- | select permissions of the target table
|
||||
SelPermInfo b ->
|
||||
m (Maybe (FieldParser n (AggSelectExp b)))
|
||||
selectFunctionAggregate sourceName function fieldName description selectPermissions = runMaybeT do
|
||||
let tableName = _fiReturnType function
|
||||
selectFunctionAggregate sourceName fi@FunctionInfo {..} description selectPermissions = runMaybeT do
|
||||
guard $ spiAllowAgg selectPermissions
|
||||
xNodesAgg <- hoistMaybe $ nodesAggExtension @b
|
||||
tableInfo <- askTableInfo sourceName tableName
|
||||
tableInfo <- askTableInfo sourceName _fiReturnType
|
||||
stringifyNum <- asks $ qcStringifyNum . getter
|
||||
tableGQLName <- getTableGQLName tableInfo
|
||||
tableArgsParser <- lift $ tableArguments sourceName tableInfo selectPermissions
|
||||
functionArgsParser <- lift $ customSQLFunctionArgs function
|
||||
functionArgsParser <- lift $ customSQLFunctionArgs fi _fiGQLAggregateName _fiGQLArgsName
|
||||
aggregateParser <- lift $ tableAggregationFields sourceName tableInfo selectPermissions
|
||||
selectionName <- lift $ pure tableGQLName <&> (<> $$(G.litName "_aggregate"))
|
||||
nodesParser <- lift $ tableSelectionList sourceName tableInfo selectPermissions
|
||||
@ -604,11 +598,11 @@ selectFunctionAggregate sourceName function fieldName description selectPermissi
|
||||
IR.TAFAgg <$> P.subselection_ $$(G.litName "aggregate") Nothing aggregateParser
|
||||
]
|
||||
pure $
|
||||
P.subselection fieldName description argsParser aggregationParser
|
||||
P.subselection _fiGQLAggregateName description argsParser aggregationParser
|
||||
<&> \((funcArgs, tableArgs'), fields) ->
|
||||
IR.AnnSelectG
|
||||
{ IR._asnFields = fields,
|
||||
IR._asnFrom = IR.FromFunction (_fiName function) funcArgs Nothing,
|
||||
IR._asnFrom = IR.FromFunction _fiSQLName funcArgs Nothing,
|
||||
IR._asnPerm = tablePermissionsInfo selectPermissions,
|
||||
IR._asnArgs = tableArgs',
|
||||
IR._asnStrfyNum = stringifyNum
|
||||
@ -621,8 +615,6 @@ selectFunctionConnection ::
|
||||
SourceName ->
|
||||
-- | SQL function info
|
||||
FunctionInfo ('Postgres pgKind) ->
|
||||
-- | field display name
|
||||
G.Name ->
|
||||
-- | field description, if any
|
||||
Maybe G.Description ->
|
||||
-- | primary key columns of the target table
|
||||
@ -630,13 +622,13 @@ selectFunctionConnection ::
|
||||
-- | select permissions of the target table
|
||||
SelPermInfo ('Postgres pgKind) ->
|
||||
m (Maybe (FieldParser n (ConnectionSelectExp ('Postgres pgKind))))
|
||||
selectFunctionConnection sourceName function fieldName description pkeyColumns selectPermissions =
|
||||
selectFunctionConnection sourceName fi@FunctionInfo {..} description pkeyColumns selectPermissions = do
|
||||
let fieldName = _fiGQLName <> $$(G.litName "_connection")
|
||||
for (relayExtension @('Postgres pgKind)) \xRelayInfo -> do
|
||||
stringifyNum <- asks $ qcStringifyNum . getter
|
||||
let tableName = _fiReturnType function
|
||||
tableInfo <- askTableInfo sourceName tableName
|
||||
tableInfo <- askTableInfo sourceName _fiReturnType
|
||||
tableConnectionArgsParser <- tableConnectionArgs pkeyColumns sourceName tableInfo selectPermissions
|
||||
functionArgsParser <- customSQLFunctionArgs function
|
||||
functionArgsParser <- customSQLFunctionArgs fi _fiGQLName _fiGQLArgsName
|
||||
selectionSetParser <- tableConnectionSelectionSet sourceName tableInfo selectPermissions
|
||||
let argsParser = liftA2 (,) functionArgsParser tableConnectionArgsParser
|
||||
pure $
|
||||
@ -650,7 +642,7 @@ selectFunctionConnection sourceName function fieldName description pkeyColumns s
|
||||
IR._csSelect =
|
||||
IR.AnnSelectG
|
||||
{ IR._asnFields = fields,
|
||||
IR._asnFrom = IR.FromFunction (_fiName function) funcArgs Nothing,
|
||||
IR._asnFrom = IR.FromFunction _fiSQLName funcArgs Nothing,
|
||||
IR._asnPerm = tablePermissionsInfo selectPermissions,
|
||||
IR._asnArgs = args,
|
||||
IR._asnStrfyNum = stringifyNum
|
||||
@ -1412,8 +1404,18 @@ remoteRelationshipField remoteFieldInfo = runMaybeT do
|
||||
customSQLFunctionArgs ::
|
||||
(BackendSchema b, MonadSchema n m, MonadTableInfo r m) =>
|
||||
FunctionInfo b ->
|
||||
G.Name ->
|
||||
G.Name ->
|
||||
m (InputFieldsParser n (IR.FunctionArgsExpTableRow b (UnpreparedValue b)))
|
||||
customSQLFunctionArgs FunctionInfo {..} = functionArgs (FTACustomFunction _fiName) _fiInputArgs
|
||||
customSQLFunctionArgs FunctionInfo {..} functionName functionArgsName =
|
||||
functionArgs
|
||||
( FTACustomFunction $
|
||||
CustomFunctionNames
|
||||
{ cfnFunctionName = functionName,
|
||||
cfnArgsName = functionArgsName
|
||||
}
|
||||
)
|
||||
_fiInputArgs
|
||||
|
||||
-- | Parses the arguments to the underlying sql function of a computed field or
|
||||
-- a custom function. All arguments to the underlying sql function are parsed
|
||||
@ -1463,8 +1465,8 @@ functionArgs functionTrackedAs (toList -> inputArgs) = do
|
||||
computedFieldGQLName <- textToName $ computedFieldNameToText computedFieldName
|
||||
tableGQLName <- getTableGQLName @b tableInfo
|
||||
pure $ computedFieldGQLName <> $$(G.litName "_") <> tableGQLName <> $$(G.litName "_args")
|
||||
FTACustomFunction functionName ->
|
||||
fmap (<> $$(G.litName "_args")) $ functionGraphQLName @b functionName `onLeft` throwError
|
||||
FTACustomFunction (CustomFunctionNames {cfnArgsName}) ->
|
||||
pure cfnArgsName
|
||||
let fieldName = $$(G.litName "args")
|
||||
fieldDesc =
|
||||
case functionTrackedAs of
|
||||
@ -1472,8 +1474,8 @@ functionArgs functionTrackedAs (toList -> inputArgs) = do
|
||||
G.Description $
|
||||
"input parameters for computed field "
|
||||
<> computedFieldName <<> " defined on table " <>> tableName
|
||||
FTACustomFunction functionName ->
|
||||
G.Description $ "input parameters for function " <>> functionName
|
||||
FTACustomFunction (CustomFunctionNames {cfnFunctionName}) ->
|
||||
G.Description $ "input parameters for function " <>> cfnFunctionName
|
||||
objectParser =
|
||||
P.object objectName Nothing (sequenceA argumentParsers) `P.bind` \arguments -> do
|
||||
-- After successfully parsing, we create a dictionary of the parsed fields
|
||||
|
@ -2,7 +2,7 @@
|
||||
-- Description: Create/delete SQL functions to/from Hasura metadata.
|
||||
module Hasura.RQL.DDL.Schema.Function where
|
||||
|
||||
import Control.Lens ((^.))
|
||||
import Control.Lens ((.~), (^.))
|
||||
import Data.Aeson
|
||||
import Data.HashMap.Strict qualified as Map
|
||||
import Data.HashMap.Strict.InsOrd qualified as OMap
|
||||
@ -289,3 +289,37 @@ runDropFunctionPermission (FunctionPermissionArgument functionName source role)
|
||||
)
|
||||
$ dropFunctionPermissionInMetadata @b source functionName role
|
||||
pure successMsg
|
||||
|
||||
-- | Represents the payload of the API command 'pg_set_function_customization'.
|
||||
--
|
||||
-- See the Hasura API reference for a detailed description.
|
||||
data SetFunctionCustomization b = SetFunctionCustomization
|
||||
{ _sfcSource :: SourceName,
|
||||
_sfcFunction :: FunctionName b,
|
||||
_sfcConfiguration :: FunctionConfig
|
||||
}
|
||||
|
||||
deriving instance Backend b => Show (SetFunctionCustomization b)
|
||||
|
||||
deriving instance Backend b => Eq (SetFunctionCustomization b)
|
||||
|
||||
instance (Backend b) => FromJSON (SetFunctionCustomization b) where
|
||||
parseJSON = withObject "set function customization" $ \o ->
|
||||
SetFunctionCustomization
|
||||
<$> o .:? "source" .!= defaultSource
|
||||
<*> o .: "function"
|
||||
<*> o .: "configuration"
|
||||
|
||||
-- | Changes the custom names of a function. Used in the API command 'pg_set_function_customization'.
|
||||
runSetFunctionCustomization ::
|
||||
forall b m.
|
||||
(QErrM m, CacheRWM m, MetadataM m, Backend b, BackendMetadata b) =>
|
||||
SetFunctionCustomization b ->
|
||||
m EncJSON
|
||||
runSetFunctionCustomization (SetFunctionCustomization source function config) = do
|
||||
void $ askFunInfo @b source function
|
||||
buildSchemaCacheFor
|
||||
(MOSourceObjId source $ AB.mkAnyBackend $ SMOFunction @b function)
|
||||
$ MetadataModifier $
|
||||
((functionMetadataSetter @b source function) . fmConfiguration) .~ config
|
||||
return successMsg
|
||||
|
@ -16,6 +16,7 @@ module Hasura.RQL.Types
|
||||
askTabInfoSource,
|
||||
askTableCoreInfo,
|
||||
askTableCoreInfoSource,
|
||||
askFunInfo,
|
||||
askFieldInfoMap,
|
||||
askFieldInfoMapSource,
|
||||
assertColumnExists,
|
||||
@ -117,6 +118,22 @@ askTabInfo sourceName tableName = do
|
||||
where
|
||||
errMsg = "table " <> tableName <<> " does not exist in source: " <> sourceNameToText sourceName
|
||||
|
||||
-- | Tries to extract the function information from the metadata.
|
||||
--
|
||||
-- If the function does not exist, will throw a 'not-exists' error.
|
||||
askFunInfo ::
|
||||
forall b m.
|
||||
(QErrM m, CacheRM m, Backend b) =>
|
||||
SourceName ->
|
||||
FunctionName b ->
|
||||
m (FunctionInfo b)
|
||||
askFunInfo sourceName functionName = do
|
||||
rawSchemaCache <- askSchemaCache
|
||||
unsafeFunctionInfo sourceName functionName (scSources rawSchemaCache)
|
||||
`onNothing` throw400 NotExists errMsg
|
||||
where
|
||||
errMsg = "function " <> functionName <<> " does not exist in source: " <> sourceNameToText sourceName
|
||||
|
||||
askTabInfoSource ::
|
||||
forall b m.
|
||||
(QErrM m, TableInfoRM b m, Backend b) =>
|
||||
|
@ -16,6 +16,7 @@ import Hasura.RQL.Types.Backend
|
||||
import Hasura.RQL.Types.Common
|
||||
import Hasura.RQL.Types.Function
|
||||
import Hasura.SQL.Backend
|
||||
import Language.GraphQL.Draft.Syntax (Name)
|
||||
|
||||
newtype ComputedFieldName = ComputedFieldName {unComputedFieldName :: NonEmptyText}
|
||||
deriving (Show, Eq, Ord, NFData, FromJSON, ToJSON, ToJSONKey, Q.ToPrepArg, ToTxt, Hashable, Q.FromCol, Generic, Cacheable)
|
||||
@ -90,10 +91,19 @@ instance ToJSON FunctionSessionArgument where
|
||||
toJSON (FunctionSessionArgument argName _) = toJSON argName
|
||||
|
||||
data FunctionTrackedAs (b :: BackendType)
|
||||
= FTAComputedField !ComputedFieldName !SourceName !(TableName b)
|
||||
| FTACustomFunction !(FunctionName b)
|
||||
= FTAComputedField ComputedFieldName SourceName (TableName b)
|
||||
| FTACustomFunction CustomFunctionNames
|
||||
deriving (Generic)
|
||||
|
||||
-- | The function name and input arguments name for the "args" field parser.
|
||||
--
|
||||
-- > function_name(args: args_name)
|
||||
data CustomFunctionNames = CustomFunctionNames
|
||||
{ cfnFunctionName :: Name,
|
||||
cfnArgsName :: Name
|
||||
}
|
||||
deriving (Show, Eq, Generic)
|
||||
|
||||
deriving instance Backend b => Show (FunctionTrackedAs b)
|
||||
|
||||
deriving instance Backend b => Eq (FunctionTrackedAs b)
|
||||
|
@ -5,6 +5,7 @@ import Data.Aeson
|
||||
import Data.Aeson.Casing
|
||||
import Data.Aeson.TH
|
||||
import Data.Char (toLower)
|
||||
import Data.List.Extended as LE
|
||||
import Data.Sequence qualified as Seq
|
||||
import Data.Text qualified as T
|
||||
import Data.Text.Extended
|
||||
@ -14,6 +15,7 @@ import Hasura.RQL.Types.Backend
|
||||
import Hasura.RQL.Types.Common
|
||||
import Hasura.SQL.Backend
|
||||
import Hasura.Session
|
||||
import Language.GraphQL.Draft.Syntax qualified as G
|
||||
|
||||
-- | https://www.postgresql.org/docs/current/xfunc-volatility.html
|
||||
data FunctionVolatility
|
||||
@ -104,26 +106,70 @@ $(deriveJSON hasuraJSON ''FunctionPermissionInfo)
|
||||
|
||||
type FunctionPermissionsMap = HashMap RoleName FunctionPermissionInfo
|
||||
|
||||
-- | Custom root fields for functions. When set, will be the names exposed
|
||||
-- to the user in the schema.
|
||||
--
|
||||
-- See rfcs/function-root-field-customisation.md for more information.
|
||||
data FunctionCustomRootFields = FunctionCustomRootFields
|
||||
{ _fcrfFunction :: Maybe G.Name,
|
||||
_fcrfFunctionAggregate :: Maybe G.Name
|
||||
}
|
||||
deriving (Show, Eq, Generic)
|
||||
|
||||
instance NFData FunctionCustomRootFields
|
||||
|
||||
instance Cacheable FunctionCustomRootFields
|
||||
|
||||
$(deriveToJSON hasuraJSON {omitNothingFields = True} ''FunctionCustomRootFields)
|
||||
|
||||
instance FromJSON FunctionCustomRootFields where
|
||||
parseJSON = withObject "Object" $ \obj -> do
|
||||
function <- obj .:? "function"
|
||||
functionAggregate <- obj .:? "function_aggregate"
|
||||
|
||||
case (function, functionAggregate) of
|
||||
(Just f, Just fa)
|
||||
| f == fa ->
|
||||
fail $
|
||||
T.unpack $
|
||||
"the following custom root field names are duplicated: "
|
||||
<> toTxt f <<> " and " <>> toTxt fa
|
||||
_ ->
|
||||
pure ()
|
||||
|
||||
pure $ FunctionCustomRootFields function functionAggregate
|
||||
|
||||
-- | A function custom root fields without custom names set. This is the default.
|
||||
emptyFunctionCustomRootFields :: FunctionCustomRootFields
|
||||
emptyFunctionCustomRootFields =
|
||||
FunctionCustomRootFields
|
||||
{ _fcrfFunction = Nothing,
|
||||
_fcrfFunctionAggregate = Nothing
|
||||
}
|
||||
|
||||
-- | Tracked SQL function metadata. See 'buildFunctionInfo'.
|
||||
data FunctionInfo (b :: BackendType) = FunctionInfo
|
||||
{ _fiName :: !(FunctionName b),
|
||||
_fiSystemDefined :: !SystemDefined,
|
||||
_fiVolatility :: !FunctionVolatility,
|
||||
{ _fiSQLName :: FunctionName b,
|
||||
_fiGQLName :: G.Name,
|
||||
_fiGQLArgsName :: G.Name,
|
||||
_fiGQLAggregateName :: G.Name,
|
||||
_fiSystemDefined :: SystemDefined,
|
||||
_fiVolatility :: FunctionVolatility,
|
||||
-- | In which part of the schema should this function be exposed?
|
||||
--
|
||||
-- See 'mkFunctionInfo' and '_fcExposedAs'.
|
||||
_fiExposedAs :: !FunctionExposedAs,
|
||||
_fiInputArgs :: !(Seq.Seq (FunctionInputArgument b)),
|
||||
_fiExposedAs :: FunctionExposedAs,
|
||||
_fiInputArgs :: Seq.Seq (FunctionInputArgument b),
|
||||
-- | NOTE: when a table is created, a new composite type of the same name is
|
||||
-- automatically created; so strictly speaking this field means "the function
|
||||
-- returns the composite type corresponding to this table".
|
||||
_fiReturnType :: !(TableName b),
|
||||
_fiReturnType :: TableName b,
|
||||
-- | this field represents the description of the function as present on the database
|
||||
_fiDescription :: !(Maybe Text),
|
||||
_fiPermissions :: !FunctionPermissionsMap,
|
||||
_fiDescription :: Maybe Text,
|
||||
-- | Roles to which the function is accessible
|
||||
_fiJsonAggSelect :: !JsonAggSelect,
|
||||
_fiComment :: !(Maybe Text)
|
||||
_fiPermissions :: FunctionPermissionsMap,
|
||||
_fiJsonAggSelect :: JsonAggSelect,
|
||||
_fiComment :: Maybe Text
|
||||
}
|
||||
deriving (Generic)
|
||||
|
||||
@ -136,6 +182,56 @@ instance (Backend b) => ToJSON (FunctionInfo b) where
|
||||
|
||||
$(makeLenses ''FunctionInfo)
|
||||
|
||||
-- | Apply function name customization to function arguments, as detailed in
|
||||
-- 'rfcs/function-root-field-customisation.md'. We want the different
|
||||
-- variations of a function (i.e. basic, aggregate) to share the same type name
|
||||
-- for their arguments.
|
||||
getFunctionArgsGQLName ::
|
||||
-- | The GQL version of the DB name of the function
|
||||
G.Name ->
|
||||
FunctionConfig ->
|
||||
G.Name
|
||||
getFunctionArgsGQLName
|
||||
funcGivenName
|
||||
FunctionConfig {..} =
|
||||
fromMaybe funcGivenName _fcCustomName <> $$(G.litName "_args")
|
||||
|
||||
-- | Apply function name customization to the basic function variation, as
|
||||
-- detailed in 'rfcs/function-root-field-customisation.md'.
|
||||
getFunctionGQLName ::
|
||||
G.Name ->
|
||||
FunctionConfig ->
|
||||
G.Name
|
||||
getFunctionGQLName
|
||||
funcGivenName
|
||||
FunctionConfig
|
||||
{ _fcCustomRootFields = FunctionCustomRootFields {..},
|
||||
..
|
||||
} =
|
||||
choice
|
||||
[ _fcrfFunction,
|
||||
_fcCustomName
|
||||
]
|
||||
& fromMaybe funcGivenName
|
||||
|
||||
-- | Apply function name customization to the aggregate function variation, as
|
||||
-- detailed in 'rfcs/function-root-field-customisation.md'.
|
||||
getFunctionAggregateGQLName ::
|
||||
G.Name ->
|
||||
FunctionConfig ->
|
||||
G.Name
|
||||
getFunctionAggregateGQLName
|
||||
funcGivenName
|
||||
FunctionConfig
|
||||
{ _fcCustomRootFields = FunctionCustomRootFields {..},
|
||||
..
|
||||
} =
|
||||
choice
|
||||
[ _fcrfFunctionAggregate,
|
||||
_fcCustomName <&> (<> $$(G.litName "_aggregate"))
|
||||
]
|
||||
& fromMaybe (funcGivenName <> $$(G.litName "_aggregate"))
|
||||
|
||||
getInputArgs :: FunctionInfo b -> Seq.Seq (FunctionArg b)
|
||||
getInputArgs =
|
||||
Seq.fromList . mapMaybe (^? _IAUserProvided) . toList . _fiInputArgs
|
||||
@ -144,15 +240,18 @@ type FunctionCache b = HashMap (FunctionName b) (FunctionInfo b) -- info of all
|
||||
|
||||
-- Metadata requests related types
|
||||
|
||||
-- | Tracked function configuration, and payload of the 'track_function' API call.
|
||||
-- | Tracked function configuration, and payload of the 'pg_track_function' and
|
||||
-- 'pg_set_function_customization' API calls.
|
||||
data FunctionConfig = FunctionConfig
|
||||
{ _fcSessionArgument :: !(Maybe FunctionArgName),
|
||||
{ _fcSessionArgument :: Maybe FunctionArgName,
|
||||
-- | In which top-level field should we expose this function?
|
||||
--
|
||||
-- The user might omit this, in which case we'll infer the location from the
|
||||
-- SQL functions volatility. See 'mkFunctionInfo' or the @track_function@ API
|
||||
-- docs for details of validation, etc.
|
||||
_fcExposedAs :: !(Maybe FunctionExposedAs)
|
||||
_fcExposedAs :: Maybe FunctionExposedAs,
|
||||
_fcCustomRootFields :: FunctionCustomRootFields,
|
||||
_fcCustomName :: Maybe G.Name
|
||||
}
|
||||
deriving (Show, Eq, Generic)
|
||||
|
||||
@ -160,11 +259,19 @@ instance NFData FunctionConfig
|
||||
|
||||
instance Cacheable FunctionConfig
|
||||
|
||||
$(deriveJSON hasuraJSON {omitNothingFields = True} ''FunctionConfig)
|
||||
instance FromJSON FunctionConfig where
|
||||
parseJSON = withObject "FunctionConfig" $ \obj ->
|
||||
FunctionConfig
|
||||
<$> obj .:? "session_argument"
|
||||
<*> obj .:? "exposed_as"
|
||||
<*> obj .:? "custom_root_fields" .!= emptyFunctionCustomRootFields
|
||||
<*> obj .:? "custom_name"
|
||||
|
||||
$(deriveToJSON hasuraJSON {omitNothingFields = True} ''FunctionConfig)
|
||||
|
||||
-- | The default function config; v1 of the API implies this.
|
||||
emptyFunctionConfig :: FunctionConfig
|
||||
emptyFunctionConfig = FunctionConfig Nothing Nothing
|
||||
emptyFunctionConfig = FunctionConfig Nothing Nothing emptyFunctionCustomRootFields Nothing
|
||||
|
||||
-- Lists are used to model overloaded functions.
|
||||
type DBFunctionsMetadata b = HashMap (FunctionName b) [RawFunctionInfo b]
|
||||
|
@ -489,6 +489,16 @@ tableMetadataSetter ::
|
||||
tableMetadataSetter source table =
|
||||
metaSources . ix source . toSourceMetadata . smTables . ix table
|
||||
|
||||
-- | A lens setter for the metadata of a specific function as identified by
|
||||
-- the source name and function name.
|
||||
functionMetadataSetter ::
|
||||
(BackendMetadata b) =>
|
||||
SourceName ->
|
||||
FunctionName b ->
|
||||
ASetter' Metadata (FunctionMetadata b)
|
||||
functionMetadataSetter source function =
|
||||
metaSources . ix source . toSourceMetadata . smFunctions . ix function
|
||||
|
||||
data MetadataNoSources = MetadataNoSources
|
||||
{ _mnsTables :: !(Tables ('Postgres 'Vanilla)),
|
||||
_mnsFunctions :: !(Functions ('Postgres 'Vanilla)),
|
||||
|
@ -86,7 +86,8 @@ tablePermissionsCommands =
|
||||
]
|
||||
functionCommands =
|
||||
[ commandParser "track_function" $ RMTrackFunction . mkAnyBackend @b,
|
||||
commandParser "untrack_function" $ RMUntrackFunction . mkAnyBackend @b
|
||||
commandParser "untrack_function" $ RMUntrackFunction . mkAnyBackend @b,
|
||||
commandParser "set_function_customization" $ RMSetFunctionCustomization . mkAnyBackend @b
|
||||
]
|
||||
functionPermissionsCommands =
|
||||
[ commandParser "create_function_permission" $ RMCreateFunctionPermission . mkAnyBackend @b,
|
||||
|
@ -85,6 +85,7 @@ data RQLMetadataV1
|
||||
| -- Functions
|
||||
RMTrackFunction !(AnyBackend TrackFunctionV2)
|
||||
| RMUntrackFunction !(AnyBackend UnTrackFunction)
|
||||
| RMSetFunctionCustomization (AnyBackend SetFunctionCustomization)
|
||||
| -- Functions permissions
|
||||
RMCreateFunctionPermission !(AnyBackend FunctionPermissionArgument)
|
||||
| RMDropFunctionPermission !(AnyBackend FunctionPermissionArgument)
|
||||
@ -395,6 +396,7 @@ runMetadataQueryV1M env currentResourceVersion = \case
|
||||
RMRenameSource q -> runRenameSource q
|
||||
RMTrackTable q -> dispatchMetadata runTrackTableV2Q q
|
||||
RMUntrackTable q -> dispatchMetadata runUntrackTableQ q
|
||||
RMSetFunctionCustomization q -> dispatchMetadata runSetFunctionCustomization q
|
||||
RMSetTableCustomization q -> dispatchMetadata runSetTableCustomization q
|
||||
RMPgSetTableIsEnum q -> runSetExistingTableIsEnumQ q
|
||||
RMCreateInsertPermission q -> dispatchMetadata runCreatePerm q
|
||||
|
@ -49,6 +49,7 @@ data RQLMetadataV1
|
||||
| -- Functions
|
||||
RMTrackFunction !(AnyBackend TrackFunctionV2)
|
||||
| RMUntrackFunction !(AnyBackend UnTrackFunction)
|
||||
| RMSetFunctionCustomization (AnyBackend SetFunctionCustomization)
|
||||
| -- Functions permissions
|
||||
RMCreateFunctionPermission !(AnyBackend FunctionPermissionArgument)
|
||||
| RMDropFunctionPermission !(AnyBackend FunctionPermissionArgument)
|
||||
|
@ -0,0 +1,349 @@
|
||||
# # Overview
|
||||
# In this file, we are testing that:
|
||||
#
|
||||
# * We can supply custom names to `pg_track_function` and have them applied in the schema
|
||||
# * Name aliases are applied correctly for all the possible specified cases, see https://github.com/hasura/graphql-engine-mono/pull/2419
|
||||
# * We can omit custom names from `pg_track_function`
|
||||
# * We can set and unset custom names with `set_function_customization`
|
||||
# * We test that we don't introduce name clashes
|
||||
|
||||
|
||||
- description: Define the SQL function the tests will be using
|
||||
url: /v2/query
|
||||
status: 200
|
||||
query:
|
||||
type: run_sql
|
||||
args:
|
||||
sql: |
|
||||
create function unaliased_function(t test)
|
||||
returns setof post as $$
|
||||
select *
|
||||
from post
|
||||
where
|
||||
title ilike ('%' || t.name || '%')
|
||||
$$ language sql stable;
|
||||
|
||||
|
||||
# Test: We can supply (all) aliases via `pg_track_function`, and the names are applied.
|
||||
|
||||
- description: Test that we supply all custom names to `pg_track_function`.
|
||||
url: /v1/metadata
|
||||
status: 200
|
||||
response:
|
||||
message: success
|
||||
query:
|
||||
type: pg_track_function
|
||||
args:
|
||||
function: unaliased_function
|
||||
configuration:
|
||||
custom_root_fields:
|
||||
function: nameBase
|
||||
function_aggregate: nameAggregate
|
||||
|
||||
- description: Execute the function without the alias, which should fail
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
query:
|
||||
query: |
|
||||
query {
|
||||
unaliased_function (args: {t: "(1, hasura,311cf381-71e7-449b-bac5-86cd6deafd5b)"}) {
|
||||
title
|
||||
content
|
||||
}
|
||||
}
|
||||
response:
|
||||
"errors": [
|
||||
{
|
||||
"message": "field \"unaliased_function\" not found in type: 'query_root'",
|
||||
"extensions": {
|
||||
"code": "validation-failed",
|
||||
"path": "$.selectionSet.unaliased_function"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
- description: Execute the function by its alias, which should succeed
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
query:
|
||||
query: |
|
||||
query {
|
||||
nameBase (args: {t: "(1, hasura,311cf381-71e7-449b-bac5-86cd6deafd5b)"}) {
|
||||
title
|
||||
content
|
||||
}
|
||||
}
|
||||
response:
|
||||
data:
|
||||
nameBase:
|
||||
- title: post by hasura
|
||||
content: content for post
|
||||
|
||||
- description: Execute the function with aggregation by its aggregation-alias, which should succeed
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
query:
|
||||
query: |
|
||||
query {
|
||||
nameAggregate (args: {t: "(1, hasura,311cf381-71e7-449b-bac5-86cd6deafd5b)"}) {
|
||||
aggregate {
|
||||
count
|
||||
}
|
||||
}
|
||||
}
|
||||
response:
|
||||
data:
|
||||
nameAggregate:
|
||||
aggregate:
|
||||
count: 1
|
||||
|
||||
- description: Teardown the test of fully given aliases
|
||||
url: /v1/metadata
|
||||
status: 200
|
||||
response:
|
||||
message: success
|
||||
query:
|
||||
type: pg_untrack_function
|
||||
args:
|
||||
name: unaliased_function
|
||||
|
||||
|
||||
# Test: We can supply just the base alias via `pg_track_function`, and the names are applied.
|
||||
|
||||
- description: Test that we supply all custom names to `pg_track_function`.
|
||||
url: /v1/metadata
|
||||
status: 200
|
||||
response:
|
||||
message: success
|
||||
query:
|
||||
type: pg_track_function
|
||||
args:
|
||||
function: unaliased_function
|
||||
configuration:
|
||||
custom_name: aliased
|
||||
|
||||
- description: Execute the function without the alias, which should fail
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
query:
|
||||
query: |
|
||||
query {
|
||||
unaliased_function (args: {t: "(1, hasura,311cf381-71e7-449b-bac5-86cd6deafd5b)"}) {
|
||||
title
|
||||
content
|
||||
}
|
||||
}
|
||||
response:
|
||||
"errors": [
|
||||
{
|
||||
"message": "field \"unaliased_function\" not found in type: 'query_root'",
|
||||
"extensions": {
|
||||
"code": "validation-failed",
|
||||
"path": "$.selectionSet.unaliased_function"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
- description: Execute the function by its alias, which should succeed
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
query:
|
||||
query: |
|
||||
query {
|
||||
aliased (args: {t: "(1, hasura,311cf381-71e7-449b-bac5-86cd6deafd5b)"}) {
|
||||
title
|
||||
content
|
||||
}
|
||||
}
|
||||
response:
|
||||
data:
|
||||
aliased:
|
||||
- title: post by hasura
|
||||
content: content for post
|
||||
|
||||
- description: Execute the function with aggregation by its aggregation-alias, which should succeed
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
query:
|
||||
query: |
|
||||
query {
|
||||
aliased_aggregate (args: {t: "(1, hasura,311cf381-71e7-449b-bac5-86cd6deafd5b)"}) {
|
||||
aggregate {
|
||||
count
|
||||
}
|
||||
}
|
||||
}
|
||||
response:
|
||||
data:
|
||||
aliased_aggregate:
|
||||
aggregate:
|
||||
count: 1
|
||||
|
||||
|
||||
# Test: Using set_function_customization
|
||||
|
||||
- description: Test that we fail on non-existing functions
|
||||
url: /v1/metadata
|
||||
status: 400
|
||||
response:
|
||||
code: not-exists
|
||||
error: 'function "non_existing_function" does not exist in source: default'
|
||||
path: $.args
|
||||
query:
|
||||
type: pg_set_function_customization
|
||||
args:
|
||||
function: non_existing_function
|
||||
configuration: { }
|
||||
|
||||
- description: Test that we fail on duplicate custom root fields
|
||||
url: /v1/metadata
|
||||
status: 400
|
||||
response:
|
||||
code: parse-failed
|
||||
error: "Error when parsing command set_function_customization.\n\
|
||||
See our documentation at https://hasura.io/docs/latest/graphql/core/api-reference/metadata-api/index.html#metadata-apis.\n\
|
||||
Internal error message: the following custom root field names are duplicated: \"duplicate_name\" and \"duplicate_name\""
|
||||
path: $.args.configuration.custom_root_fields
|
||||
query:
|
||||
type: pg_set_function_customization
|
||||
args:
|
||||
function: unaliased_function
|
||||
configuration:
|
||||
custom_root_fields:
|
||||
function: duplicate_name
|
||||
function_aggregate: duplicate_name
|
||||
|
||||
- description: Test that we can clear the customized names
|
||||
url: /v1/metadata
|
||||
status: 200
|
||||
response:
|
||||
message: success
|
||||
query:
|
||||
type: pg_set_function_customization
|
||||
args:
|
||||
function: unaliased_function
|
||||
configuration: { }
|
||||
|
||||
- descripion: Execute the function without the alias, which should now work
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
query:
|
||||
query: |
|
||||
query {
|
||||
unaliased_function (args: {t: "(1, hasura,311cf381-71e7-449b-bac5-86cd6deafd5b)"}) {
|
||||
title
|
||||
content
|
||||
}
|
||||
}
|
||||
response:
|
||||
data:
|
||||
unaliased_function:
|
||||
- title: post by hasura
|
||||
content: content for post
|
||||
|
||||
- description: Test that we can set customized names using `set_function_customization`
|
||||
url: /v1/metadata
|
||||
status: 200
|
||||
response:
|
||||
message: success
|
||||
query:
|
||||
type: pg_set_function_customization
|
||||
args:
|
||||
function: unaliased_function
|
||||
configuration:
|
||||
custom_root_fields:
|
||||
function: nameBaseOther
|
||||
|
||||
- description: Execute the function by its alias
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
query:
|
||||
query: |
|
||||
query {
|
||||
nameBaseOther (args: {t: "(1, hasura,311cf381-71e7-449b-bac5-86cd6deafd5b)"}) {
|
||||
title
|
||||
content
|
||||
}
|
||||
}
|
||||
response:
|
||||
data:
|
||||
nameBaseOther:
|
||||
- title: post by hasura
|
||||
content: content for post
|
||||
|
||||
- description: Execute the function with aggregation (unaliased), which should succeed.
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
query:
|
||||
query: |
|
||||
query {
|
||||
unaliased_function_aggregate (args: {t: "(1, hasura,311cf381-71e7-449b-bac5-86cd6deafd5b)"}) {
|
||||
aggregate {
|
||||
count
|
||||
}
|
||||
}
|
||||
}
|
||||
response:
|
||||
data:
|
||||
unaliased_function_aggregate:
|
||||
aggregate:
|
||||
count: 1
|
||||
|
||||
# Test: We reject name clashes: A function is aliased to an existing (unaliased) function.
|
||||
# Note: We are not going to go to great lengths to ensure all the different
|
||||
# conceivable ways collisions could be introduced are avoided, because there is
|
||||
# a general mechanism in the code that builds the schema cache which ensures no
|
||||
# collisions appear in the final, generated schema.
|
||||
# (issue https://github.com/hasura/graphql-engine-mono/issues/1868 has a bit
|
||||
# more info on this subject)
|
||||
|
||||
- description: "Setup: Define a separate function to cause clashes"
|
||||
url: /v2/query
|
||||
status: 200
|
||||
query:
|
||||
type: run_sql
|
||||
args:
|
||||
sql: |
|
||||
create function unaliased_function2(t test)
|
||||
returns setof post as $$
|
||||
select *
|
||||
from post
|
||||
where
|
||||
title ilike ('%' || t.name || '%')
|
||||
$$ language sql stable;
|
||||
|
||||
- description: "Setup: Track the new function"
|
||||
url: /v1/metadata
|
||||
status: 200
|
||||
response:
|
||||
message: success
|
||||
query:
|
||||
type: pg_track_function
|
||||
args:
|
||||
function: unaliased_function2
|
||||
|
||||
- description: "Setup: clear up aliases of unaliased_function"
|
||||
url: /v1/metadata
|
||||
status: 200
|
||||
response:
|
||||
message: success
|
||||
query:
|
||||
type: pg_set_function_customization
|
||||
args:
|
||||
function: unaliased_function
|
||||
configuration: {}
|
||||
|
||||
- description: Test that we cannot alias `unaliased_function2` to `unaliased_function`
|
||||
url: /v1/metadata
|
||||
status: 500
|
||||
response:
|
||||
code: unexpected
|
||||
error: 'found duplicate fields in selection set: unaliased_function_aggregate, unaliased_function'
|
||||
path: $.args
|
||||
|
||||
query:
|
||||
type: pg_set_function_customization
|
||||
args:
|
||||
function: unaliased_function2
|
||||
configuration:
|
||||
custom_name: unaliased_function
|
@ -1019,6 +1019,9 @@ class TestGraphQLQueryFunctions:
|
||||
def test_tracking_function_with_composite_type_argument(self, hge_ctx):
|
||||
check_query_f(hge_ctx, self.dir() + '/track_non_base_function_arg_type.yaml')
|
||||
|
||||
def test_tracking_function_with_customized_names(self, hge_ctx):
|
||||
check_query_f(hge_ctx, self.dir() + '/track_customised_names.yaml')
|
||||
|
||||
@classmethod
|
||||
def dir(cls):
|
||||
return 'queries/graphql_query/functions'
|
||||
|
Loading…
Reference in New Issue
Block a user