这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
55d6066
generate the nodes according to the identifier (if exists)
codingkarthik Aug 28, 2020
0c9445a
generate constraint names for tables with identifiers
codingkarthik Sep 3, 2020
2e426ca
add query and mutation tests with custom graphQL table identifier
codingkarthik Sep 3, 2020
d7ae2cf
remove unused import
codingkarthik Sep 3, 2020
ea8caf6
fix the metadata order failing test
codingkarthik Sep 3, 2020
9f97768
update the teardown of metadata_order test
codingkarthik Sep 3, 2020
61cdef0
add introspection test
codingkarthik Sep 3, 2020
1431f1d
add new API set_table_configuration
codingkarthik Sep 4, 2020
1f28be6
add tests for set_table_configuration
codingkarthik Sep 4, 2020
0533ab6
add docs
codingkarthik Sep 4, 2020
b391170
update changelog
codingkarthik Sep 4, 2020
45e73b2
refactor getTableName to getTableDisplayName
codingkarthik Sep 4, 2020
4f439d1
modify the introspection tests
codingkarthik Sep 4, 2020
a7d5fc4
undo the changes of generating custom constraint names
codingkarthik Sep 4, 2020
0786d94
Merge branch 'master' into custom-table-identifier
codingkarthik Sep 4, 2020
27cd8c0
change the error message after merging safeSelectionSet changes
codingkarthik Sep 4, 2020
2b9785d
add a note about constraint names expected to be GraphQL compliant
codingkarthik Sep 4, 2020
07bc4df
Update docs/graphql/core/api-reference/schema-metadata-api/table-view…
codingkarthik Sep 16, 2020
66b6161
Update docs/graphql/core/api-reference/schema-metadata-api/table-view…
codingkarthik Sep 16, 2020
312769b
Update docs/graphql/core/api-reference/schema-metadata-api/table-view…
codingkarthik Sep 16, 2020
c2092e0
Update docs/graphql/core/api-reference/schema-metadata-api/table-view…
codingkarthik Sep 16, 2020
0d69329
Update docs/graphql/core/api-reference/schema-metadata-api/table-view…
codingkarthik Sep 16, 2020
4238610
Update docs/graphql/core/api-reference/schema-metadata-api/table-view…
codingkarthik Sep 16, 2020
f1ed3a3
apply couple of docs suggestions
codingkarthik Sep 16, 2020
69c589d
Merge branch 'master' into custom-table-identifier
codingkarthik Sep 16, 2020
b3eead6
Merge branch 'master' into custom-table-identifier
codingkarthik Oct 27, 2020
c1a38ac
refactor `getTableDisplayName` to `getTableGQLName`
codingkarthik Oct 27, 2020
172d118
remove the identifier field from SetTableCustomFields API
codingkarthik Oct 27, 2020
6e1bceb
apply minor suggestion
codingkarthik Oct 27, 2020
eee4254
refactoring in names of the API
codingkarthik Oct 27, 2020
df45464
update set_table_configuration to set_table_customization in docs
codingkarthik Oct 27, 2020
ae75d24
change the format of specifying an external link
codingkarthik Oct 27, 2020
b6af36e
update CHANGELOG
codingkarthik Oct 27, 2020
fbb746e
add deprecated indicator with `set_table_custom_fields`
codingkarthik Oct 28, 2020
8fc740d
Merge branch 'master' into custom-table-identifier
codingkarthik Oct 29, 2020
944a904
don't throw error while tracking tables with non-compliant GraphQL names
codingkarthik Oct 29, 2020
c226fea
tables with custom names are valid graphQLtables
codingkarthik Oct 29, 2020
0737280
minor no-op refactor
codingkarthik Oct 29, 2020
aafeb98
Merge branch 'master' into custom-table-identifier
0x777 Oct 29, 2020
f4f0d18
Merge branch 'master' into custom-table-identifier
0x777 Oct 29, 2020
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ This release contains the [PDV refactor (#4111)](https://github.com/hasura/graph
**NOTE:** If you have event triggers with names greater than 42 chars, then you should update their names to avoid running into Postgres identifier limit bug (#5786)
- server: validate remote schema queries (fixes #4143)
- server: fix issue with tracking custom functions that return `SETOF` materialized view (close #5294) (#5945)
- server: introduce optional custom table name in table configuration to track the table according to the custom name. The `set_table_custom_fields` API has been deprecated, A new API `set_table_customization` has been added to set the configuration. (#3811)
- server: allow remote relationships with union, interface and enum type fields as well (fixes #5875) (#6080)
- console: allow user to cascade Postgres dependencies when dropping Postgres objects (close #5109) (#5248)
- console: mark inconsistent remote schemas in the UI (close #5093) (#5181)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,15 @@ The various types of queries are listed in the following table:
- 2
- Add a table/view with configuration

* - :ref:`set_table_custom_fields <set_table_custom_fields>`
* - :ref:`set_table_customization <set_table_customization>`
- :ref:`set_table_customization_args <set_table_customization_syntax>`
- 1
- Set table customization of an already tracked table

* - :ref:`set_table_custom_fields <set_table_custom_fields>` (deprecated)
- :ref:`set_table_custom_fields_args <set_table_custom_fields_args_syntax>`
- 2
- Set custom fields to an already tracked table
- Set custom fields to an already tracked table (deprecated)

* - :ref:`untrack_table`
- :ref:`untrack_table_args <untrack_table_syntax>`
Expand Down
118 changes: 116 additions & 2 deletions docs/graphql/core/api-reference/schema-metadata-api/table-view.rst
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,45 @@ Add a table/view ``author``:
}
}

A table can be tracked with a ``custom name``. This can be useful when a table
name is not GraphQL compliant, like ``Users Address``. A ``custom name`` like
``users_address`` will complement the ``"Users Address"``
table, so that it can be added to the GraphQL schema.

.. code-block:: http

POST /v1/query HTTP/1.1
Content-Type: application/json
X-Hasura-Role: admin

{
"type": "track_table",
"version": 2,
"args": {
"table": "Author Details",
"configuration": {
"custom_name": "author_details"
}
}
}

The GraphQL nodes and typenames
that are generated will be according to the ``identifier``. For example, in this case,
the nodes generated will be:

- ``users_address``
- ``users_address_one``
- ``users_address_aggregate``
- ``insert_users_address``
- ``insert_users_address_one``
- ``update_users_address``
- ``update_users_address_by_pk``
- ``delete_users_address``
- ``delete_users_address_by_pk``

.. note::
graphql-engine requires the constraint names (if any) of a table to be GraphQL `compliant <https://spec.graphql.org/June2018/#sec-Names>`__ in order to be able to track it.

.. _track_table_args_syntax_v2:

Args syntax
Expand Down Expand Up @@ -182,6 +221,11 @@ Table Config
- Required
- Schema
- Description
* - custom_name
- false
- ``String``
- Customise the ``<table-name>`` with the provided custom name value
. The GraphQL nodes for the table will be generated according to the custom name.
* - custom_root_fields
- false
- :ref:`Custom Root Fields <custom_root_fields>`
Expand Down Expand Up @@ -242,13 +286,19 @@ Custom Root Fields

.. _set_table_custom_fields:

set_table_custom_fields
-----------------------
set_table_custom_fields (deprecated)
------------------------------------

``set_table_custom_fields`` has been deprecated. Use the
:ref:`set_table_configuration <set_table_configuration>` API to set the custom
table fields.

``set_table_custom_fields`` in version ``2`` sets the custom root fields and
custom column names of already tracked table. This will **replace** the already
present custom fields configuration.



Set custom fields for table/view ``author``:

.. code-block:: http
Expand Down Expand Up @@ -304,6 +354,70 @@ Args syntax
- :ref:`CustomColumnNames`
- Customise the column fields

.. _set_table_customization:

set_table_customization
-----------------------

``set_table_customization`` allows to customize any given table with
a custom name, custom root fields and custom column names of an already tracked
table. This will **replace** the already present customization.

:ref:`set_table_custom_fields <set_table_custom_fields>` has been deprecated in
favour of this API.

Set configuration for table/view ``author``:

.. code-block:: http

POST /v1/query HTTP/1.1
Content-Type: application/json
X-Hasura-Role: admin

{
"type": "set_table_customization",
"args": {
"table": "author_details",
"configuration": {
"identifier": "author",
"custom_root_fields": {
"select": "Authors",
"select_by_pk": "Author",
"select_aggregate": "AuthorAggregate",
"insert": "AddAuthors",
"insert_one":"AddAuthor",
"update": "UpdateAuthors",
"update_by_pk": "UpdateAuthor",
"delete": "DeleteAuthors",
"delete_by_pk": "DeleteAuthor"
},
"custom_column_names": {
"id": "authorId"
}
}
}
}

.. _set_table_customization_syntax:

Args syntax
^^^^^^^^^^^

.. list-table::
:header-rows: 1

* - Key
- Required
- Schema
- Description
* - table
- true
- :ref:`TableName <TableName>`
- Name of the table
* - configuration
- false
- :ref:`TableConfig <table_config>`
- Configuration for the table/view

.. _untrack_table:

Expand Down
4 changes: 4 additions & 0 deletions server/src-lib/Hasura/Backends/Postgres/SQL/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ module Hasura.Backends.Postgres.SQL.Types
, qualifiedObjectToText
, snakeCaseQualifiedObject
, qualifiedObjectToName
, isGraphQLCompliantTableName

, PGScalarType(..)
, WithScalarType(..)
Expand Down Expand Up @@ -241,6 +242,9 @@ qualifiedObjectToName objectName = do
"cannot include " <> objectName <<> " in the GraphQL schema because " <> textName
<<> " is not a valid GraphQL identifier"

isGraphQLCompliantTableName :: ToTxt a => QualifiedObject a -> Bool
isGraphQLCompliantTableName = isJust . G.mkName . snakeCaseQualifiedObject

type QualifiedTable = QualifiedObject TableName

type QualifiedFunction = QualifiedObject FunctionName
Expand Down
20 changes: 19 additions & 1 deletion server/src-lib/Hasura/GraphQL/Parser/Class.hs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Hasura.Prelude

import qualified Data.HashMap.Strict as Map
import qualified Language.Haskell.TH as TH
import qualified Language.GraphQL.Draft.Syntax as G

import Data.Has
import Data.Parser.JSONPath
Expand All @@ -16,7 +17,7 @@ import Type.Reflection (Typeable)
import Hasura.Backends.Postgres.SQL.Types
import {-# SOURCE #-} Hasura.GraphQL.Parser.Internal.Parser
import Hasura.RQL.Types.Error
import Hasura.RQL.Types.Table (TableCache, TableInfo)
import Hasura.RQL.Types.Table
import Hasura.SQL.Backend
import Hasura.Session (RoleName)

Expand Down Expand Up @@ -124,6 +125,23 @@ askTableInfo tableName = do
-- supposed to ensure that all dependencies are resolved.
tableInfo `onNothing` throw500 ("askTableInfo: no info for " <>> tableName)

-- | Helper function to get the table GraphQL name. A table may have an
-- identifier configured with it. When the identifier exists, the GraphQL nodes
-- that are generated according to the identifier. For example: Let's say,
-- we have a table called `users address`, the name of the table is not GraphQL
-- compliant so we configure the table with a GraphQL compliant name,
-- say `users_address`
-- The generated top-level nodes of this table will be like `users_address`,
-- `insert_users_address` etc
getTableGQLName
:: MonadTableInfo r m
=> QualifiedTable
-> m G.Name
getTableGQLName table = do
tableInfo <- askTableInfo table
let tableCustomName = _tcCustomName . _tciCustomConfig . _tiCoreInfo $ tableInfo
maybe (qualifiedObjectToName table) pure tableCustomName

-- | A wrapper around 'memoizeOn' that memoizes a function by using its argument
-- as the key.
memoize
Expand Down
60 changes: 35 additions & 25 deletions server/src-lib/Hasura/GraphQL/Schema.hs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,17 @@ buildGQLContext =
tableFilter = not . isSystemDefined . _tciSystemDefined
functionFilter = not . isSystemDefined . fiSystemDefined

graphQLTableFilter tableName tableInfo =
-- either the table name should be GraphQL compliant
-- or it should have a GraphQL custom name set with it
isGraphQLCompliantTableName tableName
|| (isJust . _tcCustomName . _tciCustomConfig . _tiCoreInfo $ tableInfo)

validTables = Map.filter (tableFilter . _tiCoreInfo) allTables
-- Only tables that have GraphQL compliant names will be added to the schema.
-- We allow tables which don't have GraphQL compliant names, so that RQL CRUD
-- operations can be performed on them
graphQLTables = Map.filterWithKey graphQLTableFilter validTables
validFunctions = Map.elems $ Map.filter functionFilter allFunctions

allActionInfos = Map.elems allActions
Expand All @@ -87,12 +97,12 @@ buildGQLContext =
SQLGenCtx{ stringifyNum } <- askSQLGenCtx
let gqlContext =
(,)
<$> queryWithIntrospection (Set.fromMap $ validTables $> ())
<$> queryWithIntrospection (Set.fromMap $ graphQLTables $> ())
validFunctions mempty mempty
allActionInfos nonObjectCustomTypes
<*> mutation (Set.fromMap $ validTables $> ()) mempty
<*> mutation (Set.fromMap $ graphQLTables $> ()) mempty
allActionInfos nonObjectCustomTypes
flip runReaderT (adminRoleName, validTables, Frontend, QueryContext stringifyNum queryType queryRemotesMap) $
flip runReaderT (adminRoleName, graphQLTables, Frontend, QueryContext stringifyNum queryType queryRemotesMap) $
P.runSchemaT gqlContext

-- build the admin context so that we can check against name clashes with remotes
Expand Down Expand Up @@ -164,19 +174,19 @@ buildGQLContext =
queryRemotes = concatMap (piQuery . snd) remotes
mutationRemotes = concatMap (concat . piMutation . snd) remotes
queryHasuraOrRelay = case queryType of
QueryHasura -> queryWithIntrospection (Set.fromMap $ validTables $> ())
QueryHasura -> queryWithIntrospection (Set.fromMap $ graphQLTables $> ())
validFunctions queryRemotes mutationRemotes
allActionInfos nonObjectCustomTypes
QueryRelay -> relayWithIntrospection (Set.fromMap $ validTables $> ()) validFunctions
QueryRelay -> relayWithIntrospection (Set.fromMap $ graphQLTables $> ()) validFunctions

buildContextForRoleAndScenario :: RoleName -> Scenario -> m GQLContext
buildContextForRoleAndScenario roleName scenario = do
SQLGenCtx{ stringifyNum } <- askSQLGenCtx
let gqlContext = GQLContext
<$> (finalizeParser <$> queryHasuraOrRelay)
<*> (fmap finalizeParser <$> mutation (Set.fromList $ Map.keys validTables) mutationRemotes
<*> (fmap finalizeParser <$> mutation (Set.fromList $ Map.keys graphQLTables) mutationRemotes
allActionInfos nonObjectCustomTypes)
flip runReaderT (roleName, validTables, scenario, QueryContext stringifyNum queryType queryRemotesMap) $
flip runReaderT (roleName, graphQLTables, scenario, QueryContext stringifyNum queryType queryRemotesMap) $
P.runSchemaT gqlContext

buildContextForRole :: RoleName -> m (RoleContext GQLContext)
Expand Down Expand Up @@ -216,14 +226,14 @@ query' allTables allFunctions allRemotes allActions nonObjectCustomTypes = do
selectPerms <- tableSelectPermissions table
customRootFields <- _tcCustomRootFields . _tciCustomConfig . _tiCoreInfo <$> askTableInfo table
for selectPerms \perms -> do
displayName <- qualifiedObjectToName table
tableGQLName <- getTableGQLName table
let fieldsDesc = G.Description $ "fetch data from the table: " <>> table
aggName = displayName <> $$(G.litName "_aggregate")
aggName = tableGQLName <> $$(G.litName "_aggregate")
aggDesc = G.Description $ "fetch aggregated fields from the table: " <>> table
pkName = displayName <> $$(G.litName "_by_pk")
pkName = tableGQLName <> $$(G.litName "_by_pk")
pkDesc = G.Description $ "fetch data from the table: " <> table <<> " using primary key columns"
catMaybes <$> sequenceA
[ requiredFieldParser (RFDB . QDBSimple) $ selectTable table (fromMaybe displayName $ _tcrfSelect customRootFields) (Just fieldsDesc) perms
[ requiredFieldParser (RFDB . QDBSimple) $ selectTable table (fromMaybe tableGQLName $ _tcrfSelect customRootFields) (Just fieldsDesc) perms
, mapMaybeFieldParser (RFDB . QDBPrimaryKey) $ selectTableByPk table (fromMaybe pkName $ _tcrfSelectByPk customRootFields) (Just pkDesc) perms
, mapMaybeFieldParser (RFDB . QDBAggregation) $ selectTableAggregate table (fromMaybe aggName $ _tcrfSelectAggregate customRootFields) (Just aggDesc) perms
]
Expand All @@ -232,12 +242,12 @@ query' allTables allFunctions allRemotes allActions nonObjectCustomTypes = do
functionName = fiName function
selectPerms <- tableSelectPermissions targetTable
for selectPerms \perms -> do
displayName <- qualifiedObjectToName functionName
tableGQLName <- qualifiedObjectToName functionName
let functionDesc = G.Description $ "execute function " <> functionName <<> " which returns " <>> targetTable
aggName = displayName <> $$(G.litName "_aggregate")
aggName = tableGQLName <> $$(G.litName "_aggregate")
aggDesc = G.Description $ "execute function " <> functionName <<> " and query aggregates on result of table type " <>> targetTable
catMaybes <$> sequenceA
[ requiredFieldParser (RFDB . QDBSimple) $ selectFunction function displayName (Just functionDesc) perms
[ requiredFieldParser (RFDB . QDBSimple) $ selectFunction function tableGQLName (Just functionDesc) perms
, mapMaybeFieldParser (RFDB . QDBAggregation) $ selectFunctionAggregate function aggName (Just aggDesc) perms
]
actionParsers <- for allActions $ \actionInfo ->
Expand Down Expand Up @@ -275,8 +285,8 @@ relayQuery' allTables allFunctions = do
pkeyColumns <- MaybeT $ (^? tiCoreInfo.tciPrimaryKey._Just.pkColumns)
<$> askTableInfo table
selectPerms <- MaybeT $ tableSelectPermissions table
displayName <- qualifiedObjectToName table
let fieldName = displayName <> $$(G.litName "_connection")
tableGQLName <- getTableGQLName table
let fieldName = tableGQLName <> $$(G.litName "_connection")
fieldDesc = Just $ G.Description $ "fetch data from the table: " <>> table
lift $ selectTableConnection table fieldName fieldDesc pkeyColumns selectPerms

Expand All @@ -287,8 +297,8 @@ relayQuery' allTables allFunctions = do
pkeyColumns <- MaybeT $ (^? tiCoreInfo.tciPrimaryKey._Just.pkColumns)
<$> askTableInfo returnTable
selectPerms <- MaybeT $ tableSelectPermissions returnTable
displayName <- qualifiedObjectToName functionName
let fieldName = displayName <> $$(G.litName "_connection")
tableGQLName <- qualifiedObjectToName functionName
let fieldName = tableGQLName <> $$(G.litName "_connection")
fieldDesc = Just $ G.Description $ "execute function " <> functionName
<<> " which returns " <>> returnTable
lift $ selectFunctionConnection function fieldName fieldDesc pkeyColumns selectPerms
Expand Down Expand Up @@ -485,7 +495,7 @@ mutation
mutation allTables allRemotes allActions nonObjectCustomTypes = do
mutationParsers <- for (toList allTables) \table -> do
tableCoreInfo <- _tiCoreInfo <$> askTableInfo table
displayName <- qualifiedObjectToName table
tableGQLName <- getTableGQLName table
tablePerms <- tablePermissions table
for tablePerms \permissions -> do
let customRootFields = _tcCustomRootFields $ _tciCustomConfig tableCoreInfo
Expand All @@ -500,9 +510,9 @@ mutation allTables allRemotes allActions nonObjectCustomTypes = do
then Nothing
else return insertPermission
inserts <- fmap join $ whenMaybe (isMutable viIsInsertable viewInfo) $ for scenarioInsertPermissionM \insertPerms -> do
let insertName = $$(G.litName "insert_") <> displayName
let insertName = $$(G.litName "insert_") <> tableGQLName
insertDesc = G.Description $ "insert data into the table: " <>> table
insertOneName = $$(G.litName "insert_") <> displayName <> $$(G.litName "_one")
insertOneName = $$(G.litName "insert_") <> tableGQLName <> $$(G.litName "_one")
insertOneDesc = G.Description $ "insert a single row into the table: " <>> table
insert <- insertIntoTable table (fromMaybe insertName $ _tcrfInsert customRootFields) (Just insertDesc) insertPerms selectPerms (_permUpd permissions)
-- select permissions are required for InsertOne: the
Expand All @@ -514,9 +524,9 @@ mutation allTables allRemotes allActions nonObjectCustomTypes = do
pure $ fmap (RFDB . MDBInsert) insert : maybe [] (pure . fmap (RFDB . MDBInsert)) insertOne

updates <- fmap join $ whenMaybe (isMutable viIsUpdatable viewInfo) $ for (_permUpd permissions) \updatePerms -> do
let updateName = $$(G.litName "update_") <> displayName
let updateName = $$(G.litName "update_") <> tableGQLName
updateDesc = G.Description $ "update data of the table: " <>> table
updateByPkName = $$(G.litName "update_") <> displayName <> $$(G.litName "_by_pk")
updateByPkName = $$(G.litName "update_") <> tableGQLName <> $$(G.litName "_by_pk")
updateByPkDesc = G.Description $ "update single row of the table: " <>> table
update <- updateTable table (fromMaybe updateName $ _tcrfUpdate customRootFields) (Just updateDesc) updatePerms selectPerms
-- likewise; furthermore, primary keys can only be tested in
Expand All @@ -527,9 +537,9 @@ mutation allTables allRemotes allActions nonObjectCustomTypes = do
pure $ fmap (RFDB . MDBUpdate) <$> catMaybes [update, updateByPk]

deletes <- fmap join $ whenMaybe (isMutable viIsDeletable viewInfo) $ for (_permDel permissions) \deletePerms -> do
let deleteName = $$(G.litName "delete_") <> displayName
let deleteName = $$(G.litName "delete_") <> tableGQLName
deleteDesc = G.Description $ "delete data from the table: " <>> table
deleteByPkName = $$(G.litName "delete_") <> displayName <> $$(G.litName "_by_pk")
deleteByPkName = $$(G.litName "delete_") <> tableGQLName <> $$(G.litName "_by_pk")
deleteByPkDesc = G.Description $ "delete single row from the table: " <>> table
delete <- deleteFromTable table (fromMaybe deleteName $ _tcrfDelete customRootFields) (Just deleteDesc) deletePerms selectPerms

Expand Down
Loading