这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 71 additions & 10 deletions docs/graphql/manual/queries/custom-functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ First install the `pg_trgm <https://www.postgresql.org/docs/current/pgtrgm.html>
Next create a GIN (or GIST) index in your database for the columns you'll be querying:

.. code-block:: sql
CREATE INDEX address_gin_idx ON property

CREATE INDEX address_gin_idx ON property
USING GIN ((unit || ' ' || num || ' ' || street || ' ' || city || ' ' || region || ' ' || postcode) gin_trgm_ops);

And finally create the custom SQL function in the Hasura console:
Expand All @@ -127,7 +127,7 @@ And finally create the custom SQL function in the Hasura console:
RETURNS SETOF property AS $$
SELECT *
FROM property
WHERE
WHERE
search <% (unit || ' ' || num || ' ' || street || ' ' || city || ' ' || region || ' ' || postcode)
ORDER BY
similarity(search, (unit || ' ' || num || ' ' || street || ' ' || city || ' ' || region || ' ' || postcode)) DESC
Expand Down Expand Up @@ -178,6 +178,8 @@ Assuming the ``property`` table is being tracked, you can use the custom functio
}
}

.. _custom_functions_postgis:

Example: PostGIS functions
**************************

Expand All @@ -187,7 +189,7 @@ Say you have 2 tables, for user and landmark location data, with the following d
popular spatial database extension,* `PostGIS <https://postgis.net/>`__):

.. code-block:: sql

-- User location data
CREATE TABLE user_location (
user_id INTEGER PRIMARY KEY,
Expand All @@ -211,9 +213,8 @@ doesn't exist, let's first create this table and then create our location search
- create and track the following table:

.. code-block:: sql

-- SETOF table

-- SETOF table
CREATE TABLE user_landmarks (
user_id INTEGER,
location GEOGRAPHY(Point),
Expand All @@ -224,9 +225,8 @@ doesn't exist, let's first create this table and then create our location search

.. code-block:: plpgsql

-- function returns a list of landmarks near a user based on the
-- function returns a list of landmarks near a user based on the
-- input arguments distance_kms and userid

CREATE FUNCTION search_landmarks_near_user(userid integer, distance_kms integer)
RETURNS SETOF user_landmarks AS $$
SELECT A.user_id, A.location,
Expand Down Expand Up @@ -320,7 +320,7 @@ As with tables, arguments like ``where``, ``limit``, ``order_by``, ``offset``, e
function-based queries.

**For example**, limit the number of articles returned by the function defined in the text-search example above:

.. code-block:: graphql

query {
Expand All @@ -334,11 +334,72 @@ function-based queries.
}
}

Using argument default values for custom functions
**************************************************

If you omit an argument in ``args`` input field then GraphQL Engine executes the SQL function without the argument.
Hence, the function will use the default value of that argument set in its definition.

**For example:** In the above :ref:`PostGIS functions example <custom_functions_postgis>`, the function
definition can be updated as follows:

.. code-block:: plpgsql

-- input arguments distance_kms (default: 2) and userid
CREATE FUNCTION search_landmarks_near_user(userid integer, distance_kms integer default 2)

Search nearby landmarks with ``distance_kms`` default value which is 2 kms:

.. graphiql::
:view_only:
:query:
query {
search_landmarks_near_user(
args: {userid: 3}
){
user_id
location
nearby_landmarks
}
}
:response:
{
"data": {
"search_landmarks_near_user": [
{
"user_id": 3,
"location": {
"type": "Point",
"crs": {
"type": "name",
"properties": {
"name": "urn:ogc:def:crs:EPSG::4326"
}
},
"coordinates": [
12.9406589,
77.6185572
]
},
"nearby_landmarks": [
{
"id": 3,
"name": "blue tokai",
"type": "coffee shop",
"location": "0101000020E61000004E74A785DCF22940BE44060399665340"
}
]
}
]
}
}


Permissions for custom function queries
---------------------------------------

Access control permissions configured for the ``SETOF`` table of a function are also applicable to the function itself.

**For example**, in our text-search example above, if the role ``user`` doesn't have the requisite permissions to view
the table ``article``, a validation error will be thrown if the ``search_articles`` query is run using the ``user``
role.
role.
27 changes: 13 additions & 14 deletions server/src-lib/Hasura/GraphQL/Resolve/Select.hs
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ convertSelect
)
=> SelOpCtx -> Field -> m QueryRootFldUnresolved
convertSelect opCtx fld =
withPathK "selectionSet" $ fmap QRFSimple $
withPathK "selectionSet" $ QRFSimple <$>
fromField qt permFilter permLimit fld
where
SelOpCtx qt _ permFilter permLimit = opCtx
Expand All @@ -316,7 +316,7 @@ convertSelectByPKey
)
=> SelPkOpCtx -> Field -> m QueryRootFldUnresolved
convertSelectByPKey opCtx fld =
withPathK "selectionSet" $ fmap QRFPk $
withPathK "selectionSet" $ QRFPk <$>
fromFieldByPKey qt colArgMap permFilter fld
where
SelPkOpCtx qt _ permFilter colArgMap = opCtx
Expand Down Expand Up @@ -400,22 +400,21 @@ convertAggSelect
)
=> SelOpCtx -> Field -> m QueryRootFldUnresolved
convertAggSelect opCtx fld =
withPathK "selectionSet" $ fmap QRFAgg $
withPathK "selectionSet" $ QRFAgg <$>
fromAggField qt permFilter permLimit fld
-- return $ RS.selectAggQuerySQL selData
where
SelOpCtx qt _ permFilter permLimit = opCtx

parseFunctionArgs
::( MonadError QErr m)
=> FuncArgSeq -> AnnInpVal -> m [AnnPGVal]
parseFunctionArgs argSeq val =
flip withObject val $ \nTy obj ->
fmap toList $ forM argSeq $ \(FuncArgItem argName) -> do
argVal <- onNothing (OMap.lookup argName obj) $ throw500 $
"argument " <> showName argName <> " required in input type "
<> showNamedTy nTy
asPGColVal argVal
=> FuncArgSeq -> AnnInpVal -> m [UnresolvedVal]
parseFunctionArgs argSeq val = fmap catMaybes $
flip withObject val $ \_ obj ->
fmap toList $ forM argSeq $ \(FuncArgItem argName) ->
forM (OMap.lookup argName obj) $ fmap (maybe nullSQL UVPG) . asPGColValM
where
nullSQL = UVSQL $ S.SEUnsafe "NULL"

fromFuncQueryField
:: (MonadError QErr m)
Expand All @@ -425,7 +424,7 @@ fromFuncQueryField
-> m (RS.AnnFnSelG s UnresolvedVal)
fromFuncQueryField fn qf argSeq fld = fieldAsPath fld $ do
funcArgsM <- withArgM (_fArguments fld) "args" $ parseFunctionArgs argSeq
let funcArgs = maybe [] (map UVPG) funcArgsM
let funcArgs = fromMaybe [] funcArgsM
RS.AnnFnSel qf funcArgs <$> fn fld

convertFuncQuerySimple
Expand All @@ -437,7 +436,7 @@ convertFuncQuerySimple
)
=> FuncQOpCtx -> Field -> m QueryRootFldUnresolved
convertFuncQuerySimple funcOpCtx fld =
withPathK "selectionSet" $ fmap QRFFnSimple $
withPathK "selectionSet" $ QRFFnSimple <$>
fromFuncQueryField (fromField qt permFilter permLimit) qf argSeq fld
where
FuncQOpCtx qt _ permFilter permLimit qf argSeq = funcOpCtx
Expand All @@ -451,7 +450,7 @@ convertFuncQueryAgg
)
=> FuncQOpCtx -> Field -> m QueryRootFldUnresolved
convertFuncQueryAgg funcOpCtx fld =
withPathK "selectionSet" $ fmap QRFFnAgg $
withPathK "selectionSet" $ QRFFnAgg <$>
fromFuncQueryField (fromAggField qt permFilter permLimit) qf argSeq fld
where
FuncQOpCtx qt _ permFilter permLimit qf argSeq = funcOpCtx
Expand Down
4 changes: 2 additions & 2 deletions server/src-lib/Hasura/GraphQL/Schema.hs
Original file line number Diff line number Diff line change
Expand Up @@ -592,8 +592,8 @@ mkFuncArgsInp funcInfo =
argInps = procFuncArgs funcArgs mkInpVal

mkInpVal ty t =
InpValInfo Nothing (G.Name t) Nothing $ G.toGT $
G.toNT $ mkScalarTy ty
InpValInfo Nothing (G.Name t) Nothing $
G.toGT $ mkScalarTy ty

-- table_set_input
mkUpdSetTy :: QualifiedTable -> G.NamedType
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
description: Run queries on SQL my_add function with null input values
url: /v1/graphql
status: 200
response:
data:
my_add:
- result: 5
my_add_null:
- result:
my_add_default:
- result: 12
query:
query: |
query {
my_add(
args: {first: 2 second: 3}
){
result
}
# input null value
my_add_null: my_add(
args: {first: null second: 2}
){
result
}
# omiting 'second' parameter to consider
# it's default value in postgres
my_add_default: my_add(
args: {first: 2}
){
result
}
}
21 changes: 21 additions & 0 deletions server/tests-py/queries/graphql_query/functions/setup.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,26 @@ args:
name: get_test
schema: public

# custom add sql function
- type: run_sql
args:
sql: |
CREATE TABLE integer_column
(
result integer
);

CREATE FUNCTION my_add (first integer, second integer DEFAULT 10)
RETURNS SETOF integer_column as $$
SELECT q.* FROM (VALUES (first + second)) as q
$$ LANGUAGE SQL STABLE;

- type: track_table
args:
name: integer_column
schema: public

- type: track_function
args:
name: my_add
schema: public
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ args:
sql: |
DROP TABLE post cascade;
DROP TABLE test cascade;
DROP TABLE integer_column cascade;
cascade: true
3 changes: 3 additions & 0 deletions server/tests-py/test_graphql_queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,9 @@ def test_overloading_function_error(self, hge_ctx):
def test_query_get_test_uuid(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/query_get_test_uuid.yaml')

def test_query_my_add(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + '/query_my_add.yaml')

@classmethod
def dir(cls):
return 'queries/graphql_query/functions'