2022-04-22 16:38:35 +03:00
|
|
|
-- | This module defines functions that translate from the Postgres IR into
|
|
|
|
-- Postgres SQL AST.
|
|
|
|
--
|
|
|
|
-- NOTE: These functions 'processAnnAggregateSelect', 'processAnnSimpleSelect',
|
|
|
|
-- 'processConnectionSelect', are all mutually recursive.
|
2022-04-22 20:18:20 +03:00
|
|
|
--
|
|
|
|
-- These functions are generally called from the top level functions in
|
|
|
|
-- Translate.Select, and the call stack looks like:
|
|
|
|
--
|
|
|
|
-- * 'selectQuerySQL' -> 'mkSQLSelect' -> 'processAnnSimpleSelect' -> 'processSelectParams'/'processAnnFields'
|
|
|
|
--
|
|
|
|
-- * 'selectAggregateQuerySQL' -> 'mkAggregateSelect' -> 'processAnnAggregateSelect' -> 'processSelectParams'/'processAnnFields'
|
|
|
|
--
|
|
|
|
-- * 'connetionSelectQuerySQL' -> 'mkConnectionSelect' -> 'processConnectionSelection' -> 'processSelectParams'
|
|
|
|
--
|
|
|
|
-- 'SelectSource' consists of a prefix, a source, a boolean conditional
|
|
|
|
-- expression, and info on whether sorting or slicing is done (needed to handle
|
|
|
|
-- the LIMIT optimisation)
|
|
|
|
module Hasura.Backends.Postgres.Translate.Select.Internal.Process
|
2022-04-22 16:38:35 +03:00
|
|
|
( processAnnAggregateSelect,
|
|
|
|
processAnnSimpleSelect,
|
|
|
|
processConnectionSelect,
|
|
|
|
)
|
|
|
|
where
|
|
|
|
|
2023-04-26 18:42:13 +03:00
|
|
|
import Data.HashMap.Strict qualified as HashMap
|
2022-04-22 16:38:35 +03:00
|
|
|
import Data.List.NonEmpty qualified as NE
|
|
|
|
import Data.Text.Extended (ToTxt (toTxt))
|
2023-05-31 18:41:24 +03:00
|
|
|
import Data.Text.NonEmpty qualified as TNE
|
2022-04-22 16:38:35 +03:00
|
|
|
import Hasura.Backends.Postgres.SQL.DML qualified as S
|
2023-05-31 18:41:24 +03:00
|
|
|
import Hasura.Backends.Postgres.SQL.Types
|
2022-04-22 16:38:35 +03:00
|
|
|
import Hasura.Backends.Postgres.Translate.BoolExp (toSQLBoolExp)
|
|
|
|
import Hasura.Backends.Postgres.Translate.Column (toJSONableExp)
|
2022-04-22 20:18:20 +03:00
|
|
|
import Hasura.Backends.Postgres.Translate.Select.AnnotatedFieldJSON
|
|
|
|
import Hasura.Backends.Postgres.Translate.Select.Internal.Aliases
|
2022-10-11 13:42:15 +03:00
|
|
|
( contextualizeBaseTableColumn,
|
|
|
|
mkAnnOrderByAlias,
|
2022-04-22 16:38:35 +03:00
|
|
|
mkArrayRelationAlias,
|
|
|
|
mkArrayRelationSourcePrefix,
|
2022-10-11 13:42:15 +03:00
|
|
|
mkBaseTableIdentifier,
|
|
|
|
mkComputedFieldTableIdentifier,
|
2022-04-22 16:38:35 +03:00
|
|
|
mkObjectRelationTableAlias,
|
|
|
|
mkOrderByFieldName,
|
|
|
|
)
|
2022-04-22 20:18:20 +03:00
|
|
|
import Hasura.Backends.Postgres.Translate.Select.Internal.Extractor
|
|
|
|
( aggregateFieldsToExtractorExps,
|
|
|
|
asJsonAggExtr,
|
|
|
|
withJsonAggExtr,
|
|
|
|
)
|
|
|
|
import Hasura.Backends.Postgres.Translate.Select.Internal.Helpers
|
2022-04-22 16:38:35 +03:00
|
|
|
( cursorIdentifier,
|
|
|
|
cursorsSelectAliasIdentifier,
|
|
|
|
encodeBase64,
|
|
|
|
endCursorIdentifier,
|
|
|
|
fromTableRowArgs,
|
2023-05-23 17:46:27 +03:00
|
|
|
fromTableRowArgsDon'tAddBase,
|
2022-04-22 16:38:35 +03:00
|
|
|
hasNextPageIdentifier,
|
|
|
|
hasPreviousPageIdentifier,
|
2023-04-13 19:10:38 +03:00
|
|
|
nativeQueryNameToAlias,
|
2022-04-22 16:38:35 +03:00
|
|
|
pageInfoSelectAliasIdentifier,
|
|
|
|
startCursorIdentifier,
|
|
|
|
withForceAggregation,
|
|
|
|
)
|
2022-04-22 20:18:20 +03:00
|
|
|
import Hasura.Backends.Postgres.Translate.Select.Internal.JoinTree
|
2022-04-22 16:38:35 +03:00
|
|
|
( withWriteArrayConnection,
|
|
|
|
withWriteArrayRelation,
|
|
|
|
withWriteComputedFieldTableSet,
|
|
|
|
withWriteObjectRelation,
|
|
|
|
)
|
2022-04-22 20:18:20 +03:00
|
|
|
import Hasura.Backends.Postgres.Translate.Select.Internal.OrderBy (processOrderByItems)
|
2022-04-22 16:38:35 +03:00
|
|
|
import Hasura.Backends.Postgres.Translate.Types
|
Clean Relay's code, break schema cycles, introduce Node ID V2
## Motivation
This PR rewrites most of Relay to achieve the following:
- ~~fix a bug in which the same node id could refer to two different tables in the schema~~
- remove one of the few remaining uses of the source cache in the schema building code
In doing so, it also:
- simplifies the `BackendSchema` class by removing `node` from it,
- makes it much easier for other backends to support Relay,
- documents, re-organizes, and clarifies the code.
## Description
This PR introduces a new `NodeId` version ~~, and adapts the Postgres code to always generate this V2 version~~. This new id contains the source name, in addition to the table name, in order to disambiguate similar table names across different sources (which is now possible with source customization). In doing so, it now explicitly handles that case for V1 node ids, and returns an explicit error message instead of running the risk of _silently returning the wrong information_.
Furthermore, it adapts `nodeField` to support multiple backends; most of the code was trivial to generalize, and as a result it lowers the cost of entry for other backends, that now only need to support `AFNodeId` in their translation layer.
Finally, it removes one more cycle in the schema building code, by using the same trick we used for remote relationships instead of using the memoization trick of #4576.
## Remaining work
- ~~[ ]write a Changelog entry~~
- ~~[x] adapt all tests that were asserting on an old node id~~
## Future work
This PR was adapted from its original form to avoid a breaking change: while it introduces a Node ID V2, we keep generating V1 IDs and the parser rejects V2 IDs. It will be easy to make the switch at a later data in a subsequent PR.
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4593
GitOrigin-RevId: 88e5cb91e8b0646900547fa8c7c0e1463de267a1
2022-06-07 16:35:26 +03:00
|
|
|
import Hasura.GraphQL.Schema.Node (currentNodeIdVersion, nodeIdVersionInt)
|
2023-04-13 19:10:38 +03:00
|
|
|
import Hasura.NativeQuery.IR (NativeQuery (..))
|
2022-04-22 16:38:35 +03:00
|
|
|
import Hasura.Prelude
|
2022-04-27 16:57:28 +03:00
|
|
|
import Hasura.RQL.IR.BoolExp
|
2022-04-22 16:38:35 +03:00
|
|
|
import Hasura.RQL.IR.OrderBy (OrderByItemG (OrderByItemG, obiColumn))
|
|
|
|
import Hasura.RQL.IR.Select
|
2022-04-27 16:57:28 +03:00
|
|
|
import Hasura.RQL.Types.Backend
|
2023-04-24 21:35:48 +03:00
|
|
|
import Hasura.RQL.Types.BackendType
|
2022-04-27 16:57:28 +03:00
|
|
|
import Hasura.RQL.Types.Column
|
|
|
|
import Hasura.RQL.Types.Common
|
2023-05-17 17:02:09 +03:00
|
|
|
import Hasura.RQL.Types.NamingCase (NamingCase)
|
2022-04-27 16:57:28 +03:00
|
|
|
import Hasura.RQL.Types.Relationships.Local
|
2023-04-24 18:17:15 +03:00
|
|
|
import Hasura.RQL.Types.Schema.Options qualified as Options
|
2022-04-22 16:38:35 +03:00
|
|
|
|
|
|
|
processSelectParams ::
|
|
|
|
forall pgKind m.
|
2022-07-14 20:57:28 +03:00
|
|
|
( MonadReader Options.StringifyNumbers m,
|
2023-05-31 18:41:24 +03:00
|
|
|
MonadState NativeQueryFreshIdStore m,
|
2023-01-27 17:36:35 +03:00
|
|
|
MonadWriter SelectWriter m,
|
2022-04-22 16:38:35 +03:00
|
|
|
Backend ('Postgres pgKind)
|
|
|
|
) =>
|
|
|
|
SourcePrefixes ->
|
|
|
|
FieldName ->
|
|
|
|
SimilarArrayFields ->
|
|
|
|
SelectFrom ('Postgres pgKind) ->
|
|
|
|
PermissionLimitSubQuery ->
|
|
|
|
TablePerm ('Postgres pgKind) ->
|
|
|
|
SelectArgs ('Postgres pgKind) ->
|
|
|
|
m
|
|
|
|
( SelectSource,
|
2022-07-18 12:44:17 +03:00
|
|
|
[(S.ColumnAlias, S.SQLExp)],
|
2022-04-22 16:38:35 +03:00
|
|
|
Maybe S.SQLExp -- Order by cursor
|
|
|
|
)
|
|
|
|
processSelectParams
|
|
|
|
sourcePrefixes
|
|
|
|
fieldAlias
|
|
|
|
similarArrFields
|
|
|
|
selectFrom
|
|
|
|
permLimitSubQ
|
|
|
|
tablePermissions
|
|
|
|
tableArgs = do
|
|
|
|
(additionalExtrs, selectSorting, cursorExp) <-
|
2022-10-11 13:42:15 +03:00
|
|
|
processOrderByItems (identifierToTableIdentifier thisSourcePrefix) fieldAlias similarArrFields distM orderByM
|
2023-05-31 18:41:24 +03:00
|
|
|
let prefix = identifierToTableIdentifier $ _pfBase sourcePrefixes
|
|
|
|
(whereSource, fromItem) <- selectFromToQual prefix selectFrom
|
|
|
|
let finalWhere =
|
2023-05-24 16:51:56 +03:00
|
|
|
toSQLBoolExp whereSource
|
|
|
|
$ maybe permFilter (andAnnBoolExps permFilter) whereM
|
2022-04-22 16:38:35 +03:00
|
|
|
sortingAndSlicing = SortingAndSlicing selectSorting selectSlicing
|
|
|
|
selectSource =
|
|
|
|
SelectSource
|
|
|
|
thisSourcePrefix
|
|
|
|
fromItem
|
|
|
|
finalWhere
|
|
|
|
sortingAndSlicing
|
|
|
|
pure
|
|
|
|
( selectSource,
|
|
|
|
additionalExtrs,
|
|
|
|
cursorExp
|
|
|
|
)
|
|
|
|
where
|
|
|
|
thisSourcePrefix = _pfThis sourcePrefixes
|
|
|
|
SelectArgs whereM orderByM inpLimitM offsetM distM = tableArgs
|
|
|
|
TablePerm permFilter permLimit = tablePermissions
|
|
|
|
selectSlicing = SelectSlicing finalLimit offsetM
|
|
|
|
finalLimit =
|
|
|
|
-- if sub query is required, then only use input limit
|
|
|
|
-- because permission limit is being applied in subquery
|
|
|
|
-- else compare input and permission limits
|
|
|
|
case permLimitSubQ of
|
|
|
|
PLSQRequired _ -> inpLimitM
|
|
|
|
PLSQNotRequired -> compareLimits
|
|
|
|
|
|
|
|
compareLimits =
|
|
|
|
case (inpLimitM, permLimit) of
|
|
|
|
(inpLim, Nothing) -> inpLim
|
|
|
|
(Nothing, permLim) -> permLim
|
2022-06-21 14:11:08 +03:00
|
|
|
(Just inp, Just perm) -> Just (min inp perm)
|
2022-04-22 16:38:35 +03:00
|
|
|
|
2023-05-31 18:41:24 +03:00
|
|
|
selectFromToQual :: TableIdentifier -> SelectFrom ('Postgres pgKind) -> m (S.Qual, S.FromItem)
|
|
|
|
selectFromToQual prefix = \case
|
|
|
|
FromTable table -> pure $ (S.QualTable table, S.FISimple table Nothing)
|
|
|
|
FromIdentifier i -> do
|
|
|
|
let ti = TableIdentifier $ unFIIdentifier i
|
|
|
|
pure $ (S.QualifiedIdentifier ti Nothing, S.FIIdentifier ti)
|
|
|
|
FromFunction qf args defListM -> do
|
|
|
|
let fi =
|
|
|
|
S.FIFunc
|
|
|
|
$ S.FunctionExp qf (fromTableRowArgs prefix args)
|
|
|
|
$ Just
|
|
|
|
$ S.mkFunctionAlias
|
|
|
|
qf
|
|
|
|
(fmap (fmap (first S.toColumnAlias)) defListM)
|
|
|
|
pure $ (S.QualifiedIdentifier (TableIdentifier $ qualifiedObjectToText qf) Nothing, fi)
|
2023-04-27 17:02:55 +03:00
|
|
|
FromStoredProcedure {} -> error "selectFromToQual: FromStoredProcedure"
|
2023-05-10 18:13:56 +03:00
|
|
|
FromNativeQuery nq -> do
|
2023-05-31 18:41:24 +03:00
|
|
|
cteName <- fromNativeQuery nq
|
|
|
|
let ta = S.tableAliasToIdentifier cteName
|
|
|
|
pure $ (S.QualifiedIdentifier ta Nothing, S.FIIdentifier ta)
|
2023-01-27 17:36:35 +03:00
|
|
|
|
2023-05-31 18:41:24 +03:00
|
|
|
fromNativeQuery ::
|
|
|
|
forall pgKind m.
|
|
|
|
( MonadWriter SelectWriter m,
|
|
|
|
MonadState NativeQueryFreshIdStore m
|
|
|
|
) =>
|
|
|
|
NativeQuery ('Postgres pgKind) S.SQLExp ->
|
|
|
|
m S.TableAlias
|
|
|
|
fromNativeQuery nq = do
|
|
|
|
freshId <- nqNextFreshId <$> get
|
|
|
|
modify succ
|
|
|
|
|
|
|
|
-- we are going to cram our SQL in a CTE, and this is what we will call it
|
|
|
|
let cteName = nativeQueryNameToAlias (nqRootFieldName nq) freshId
|
2023-01-27 17:36:35 +03:00
|
|
|
|
2023-05-31 18:41:24 +03:00
|
|
|
-- emit the query itself to the Writer
|
|
|
|
tell
|
|
|
|
$ mempty
|
|
|
|
{ _swCustomSQLCTEs =
|
|
|
|
CustomSQLCTEs (HashMap.singleton cteName (nqInterpolatedQuery nq))
|
|
|
|
}
|
|
|
|
|
|
|
|
return cteName
|
2022-04-22 16:38:35 +03:00
|
|
|
|
|
|
|
processAnnAggregateSelect ::
|
|
|
|
forall pgKind m.
|
2022-07-14 20:57:28 +03:00
|
|
|
( MonadReader Options.StringifyNumbers m,
|
2023-01-27 17:36:35 +03:00
|
|
|
MonadWriter SelectWriter m,
|
2023-05-31 18:41:24 +03:00
|
|
|
MonadState NativeQueryFreshIdStore m,
|
2022-04-22 16:38:35 +03:00
|
|
|
Backend ('Postgres pgKind),
|
|
|
|
PostgresAnnotatedFieldJSON pgKind
|
|
|
|
) =>
|
|
|
|
SourcePrefixes ->
|
|
|
|
FieldName ->
|
|
|
|
AnnAggregateSelect ('Postgres pgKind) ->
|
|
|
|
m
|
|
|
|
( SelectSource,
|
2023-04-26 18:42:13 +03:00
|
|
|
HashMap.HashMap S.ColumnAlias S.SQLExp,
|
2022-04-22 16:38:35 +03:00
|
|
|
S.Extractor
|
|
|
|
)
|
|
|
|
processAnnAggregateSelect sourcePrefixes fieldAlias annAggSel = do
|
|
|
|
(selectSource, orderByAndDistinctExtrs, _) <-
|
|
|
|
processSelectParams
|
|
|
|
sourcePrefixes
|
|
|
|
fieldAlias
|
|
|
|
similarArrayFields
|
|
|
|
tableFrom
|
|
|
|
permLimitSubQuery
|
|
|
|
tablePermissions
|
|
|
|
tableArgs
|
2022-10-11 13:42:15 +03:00
|
|
|
let thisSourcePrefix = identifierToTableIdentifier $ _pfThis sourcePrefixes
|
2022-04-22 16:38:35 +03:00
|
|
|
processedFields <- forM aggSelFields $ \(fieldName, field) ->
|
|
|
|
(fieldName,)
|
|
|
|
<$> case field of
|
|
|
|
TAFAgg aggFields ->
|
|
|
|
pure
|
|
|
|
( aggregateFieldsToExtractorExps thisSourcePrefix aggFields,
|
2023-05-23 17:46:27 +03:00
|
|
|
aggregateFieldToExp thisSourcePrefix aggFields strfyNum
|
2022-04-22 16:38:35 +03:00
|
|
|
)
|
|
|
|
TAFNodes _ annFields -> do
|
2022-07-19 09:55:42 +03:00
|
|
|
annFieldExtr <- processAnnFields thisSourcePrefix fieldName similarArrayFields annFields tCase
|
2022-04-22 16:38:35 +03:00
|
|
|
pure
|
|
|
|
( [annFieldExtr],
|
2023-05-24 16:51:56 +03:00
|
|
|
withJsonAggExtr permLimitSubQuery (orderByForJsonAgg selectSource)
|
|
|
|
$ S.toColumnAlias
|
|
|
|
$ toIdentifier fieldName
|
2022-04-22 16:38:35 +03:00
|
|
|
)
|
|
|
|
TAFExp e ->
|
|
|
|
pure
|
|
|
|
( [],
|
|
|
|
withForceAggregation S.textTypeAnn $ S.SELit e
|
|
|
|
)
|
|
|
|
|
|
|
|
let topLevelExtractor =
|
2023-05-24 16:51:56 +03:00
|
|
|
flip S.Extractor (Just $ S.toColumnAlias $ toIdentifier fieldAlias)
|
|
|
|
$ S.applyJsonBuildObj
|
|
|
|
$ flip concatMap (map (second snd) processedFields)
|
|
|
|
$ \(FieldName fieldText, fieldExp) -> [S.SELit fieldText, fieldExp]
|
2022-04-22 16:38:35 +03:00
|
|
|
nodeExtractors =
|
2023-05-24 16:51:56 +03:00
|
|
|
HashMap.fromList
|
|
|
|
$ concatMap (fst . snd) processedFields
|
|
|
|
<> orderByAndDistinctExtrs
|
2022-04-22 16:38:35 +03:00
|
|
|
|
|
|
|
pure (selectSource, nodeExtractors, topLevelExtractor)
|
|
|
|
where
|
2022-07-19 09:55:42 +03:00
|
|
|
AnnSelectG aggSelFields tableFrom tablePermissions tableArgs strfyNum tCase = annAggSel
|
2022-04-22 16:38:35 +03:00
|
|
|
permLimit = _tpLimit tablePermissions
|
|
|
|
orderBy = _saOrderBy tableArgs
|
|
|
|
permLimitSubQuery = mkPermissionLimitSubQuery permLimit aggSelFields orderBy
|
2023-05-24 16:51:56 +03:00
|
|
|
similarArrayFields = HashMap.unions
|
|
|
|
$ flip map (map snd aggSelFields)
|
|
|
|
$ \case
|
2022-04-22 16:38:35 +03:00
|
|
|
TAFAgg _ -> mempty
|
|
|
|
TAFNodes _ annFlds ->
|
|
|
|
mkSimilarArrayFields annFlds orderBy
|
|
|
|
TAFExp _ -> mempty
|
|
|
|
|
|
|
|
mkPermissionLimitSubQuery ::
|
|
|
|
Maybe Int ->
|
|
|
|
TableAggregateFields ('Postgres pgKind) ->
|
|
|
|
Maybe (NE.NonEmpty (AnnotatedOrderByItem ('Postgres pgKind))) ->
|
|
|
|
PermissionLimitSubQuery
|
|
|
|
mkPermissionLimitSubQuery permLimit' aggFields orderBys =
|
|
|
|
case permLimit' of
|
|
|
|
Nothing -> PLSQNotRequired
|
|
|
|
Just limit ->
|
|
|
|
if hasAggregateField || hasAggOrderBy
|
|
|
|
then PLSQRequired limit
|
|
|
|
else PLSQNotRequired
|
|
|
|
where
|
2023-05-24 16:51:56 +03:00
|
|
|
hasAggregateField = flip any (map snd aggFields)
|
|
|
|
$ \case
|
2022-04-22 16:38:35 +03:00
|
|
|
TAFAgg _ -> True
|
|
|
|
_ -> False
|
|
|
|
|
|
|
|
hasAggOrderBy = case orderBys of
|
|
|
|
Nothing -> False
|
2023-05-24 16:51:56 +03:00
|
|
|
Just l -> flip any (concatMap toList $ toList l)
|
|
|
|
$ \case
|
2022-04-22 16:38:35 +03:00
|
|
|
AOCArrayAggregation {} -> True
|
|
|
|
_ -> False
|
|
|
|
|
|
|
|
processAnnFields ::
|
|
|
|
forall pgKind m.
|
2022-07-14 20:57:28 +03:00
|
|
|
( MonadReader Options.StringifyNumbers m,
|
2023-01-27 17:36:35 +03:00
|
|
|
MonadWriter SelectWriter m,
|
2023-05-31 18:41:24 +03:00
|
|
|
MonadState NativeQueryFreshIdStore m,
|
2022-04-22 16:38:35 +03:00
|
|
|
Backend ('Postgres pgKind),
|
|
|
|
PostgresAnnotatedFieldJSON pgKind
|
|
|
|
) =>
|
2022-10-11 13:42:15 +03:00
|
|
|
TableIdentifier ->
|
2022-04-22 16:38:35 +03:00
|
|
|
FieldName ->
|
|
|
|
SimilarArrayFields ->
|
|
|
|
AnnFields ('Postgres pgKind) ->
|
2022-07-19 09:55:42 +03:00
|
|
|
Maybe NamingCase ->
|
2022-07-18 12:44:17 +03:00
|
|
|
m (S.ColumnAlias, S.SQLExp)
|
2022-07-19 09:55:42 +03:00
|
|
|
processAnnFields sourcePrefix fieldAlias similarArrFields annFields tCase = do
|
2022-04-22 16:38:35 +03:00
|
|
|
fieldExps <- forM annFields $ \(fieldName, field) ->
|
|
|
|
(fieldName,)
|
|
|
|
<$> case field of
|
|
|
|
AFExpression t -> pure $ S.SELit t
|
Clean Relay's code, break schema cycles, introduce Node ID V2
## Motivation
This PR rewrites most of Relay to achieve the following:
- ~~fix a bug in which the same node id could refer to two different tables in the schema~~
- remove one of the few remaining uses of the source cache in the schema building code
In doing so, it also:
- simplifies the `BackendSchema` class by removing `node` from it,
- makes it much easier for other backends to support Relay,
- documents, re-organizes, and clarifies the code.
## Description
This PR introduces a new `NodeId` version ~~, and adapts the Postgres code to always generate this V2 version~~. This new id contains the source name, in addition to the table name, in order to disambiguate similar table names across different sources (which is now possible with source customization). In doing so, it now explicitly handles that case for V1 node ids, and returns an explicit error message instead of running the risk of _silently returning the wrong information_.
Furthermore, it adapts `nodeField` to support multiple backends; most of the code was trivial to generalize, and as a result it lowers the cost of entry for other backends, that now only need to support `AFNodeId` in their translation layer.
Finally, it removes one more cycle in the schema building code, by using the same trick we used for remote relationships instead of using the memoization trick of #4576.
## Remaining work
- ~~[ ]write a Changelog entry~~
- ~~[x] adapt all tests that were asserting on an old node id~~
## Future work
This PR was adapted from its original form to avoid a breaking change: while it introduces a Node ID V2, we keep generating V1 IDs and the parser rejects V2 IDs. It will be easy to make the switch at a later data in a subsequent PR.
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4593
GitOrigin-RevId: 88e5cb91e8b0646900547fa8c7c0e1463de267a1
2022-06-07 16:35:26 +03:00
|
|
|
AFNodeId _ sn tn pKeys -> pure $ mkNodeId sn tn pKeys
|
2022-04-22 16:38:35 +03:00
|
|
|
AFColumn c -> toSQLCol c
|
|
|
|
AFObjectRelation objSel -> withWriteObjectRelation $ do
|
2023-05-31 13:32:57 +03:00
|
|
|
let AnnRelationSelectG relName relMapping nullable annObjSel = objSel
|
2023-05-10 18:13:56 +03:00
|
|
|
AnnObjectSelectG objAnnFields target targetFilter = annObjSel
|
2023-05-31 18:41:24 +03:00
|
|
|
(objRelSourcePrefix, ident, filterExp) <- case target of
|
2023-05-10 18:13:56 +03:00
|
|
|
FromNativeQuery nq -> do
|
2023-05-31 18:41:24 +03:00
|
|
|
cteName <- fromNativeQuery nq
|
|
|
|
let nativeQueryIdentifier = S.tableAliasToIdentifier cteName
|
|
|
|
|
2023-05-10 18:13:56 +03:00
|
|
|
pure
|
2023-05-31 18:41:24 +03:00
|
|
|
( mkObjectRelationTableAlias
|
|
|
|
sourcePrefix
|
|
|
|
( relName
|
|
|
|
{ getRelTxt =
|
|
|
|
getRelTxt relName
|
|
|
|
<> TNE.mkNonEmptyTextUnsafe
|
|
|
|
( getIdenTxt
|
|
|
|
$ S.getTableAlias cteName
|
|
|
|
)
|
|
|
|
}
|
|
|
|
),
|
|
|
|
S.FIIdentifier nativeQueryIdentifier,
|
|
|
|
toSQLBoolExp (S.QualifiedIdentifier nativeQueryIdentifier Nothing) targetFilter
|
2023-05-10 18:13:56 +03:00
|
|
|
)
|
|
|
|
FromTable tableFrom -> do
|
|
|
|
pure
|
2023-05-31 18:41:24 +03:00
|
|
|
( mkObjectRelationTableAlias sourcePrefix relName,
|
|
|
|
S.FISimple tableFrom Nothing,
|
|
|
|
toSQLBoolExp (S.QualTable tableFrom) targetFilter
|
2023-05-10 18:13:56 +03:00
|
|
|
)
|
|
|
|
other -> error $ "processAnnFields: " <> show other
|
2023-05-31 18:41:24 +03:00
|
|
|
let sourcePrefixes = mkSourcePrefixes objRelSourcePrefix
|
|
|
|
selectSource = ObjectSelectSource (_pfThis sourcePrefixes) ident filterExp
|
|
|
|
objRelSource = ObjectRelationSource relName relMapping selectSource nullable
|
|
|
|
annFieldsExtr <- processAnnFields (identifierToTableIdentifier $ _pfThis sourcePrefixes) fieldName HashMap.empty objAnnFields tCase
|
|
|
|
pure
|
|
|
|
( objRelSource,
|
|
|
|
HashMap.fromList [annFieldsExtr],
|
|
|
|
S.mkQIdenExp objRelSourcePrefix fieldName
|
|
|
|
)
|
2022-04-22 16:38:35 +03:00
|
|
|
AFArrayRelation arrSel -> do
|
|
|
|
let arrRelSourcePrefix = mkArrayRelationSourcePrefix sourcePrefix fieldAlias similarArrFields fieldName
|
|
|
|
arrRelAlias = mkArrayRelationAlias fieldAlias similarArrFields fieldName
|
2022-07-19 09:55:42 +03:00
|
|
|
processArrayRelation (mkSourcePrefixes arrRelSourcePrefix) fieldName arrRelAlias arrSel tCase
|
2022-04-22 16:38:35 +03:00
|
|
|
pure $ S.mkQIdenExp arrRelSourcePrefix fieldName
|
|
|
|
AFComputedField _ _ (CFSScalar scalar caseBoolExpMaybe) -> do
|
|
|
|
computedFieldSQLExp <- fromScalarComputedField scalar
|
|
|
|
-- The computed field is conditionally outputed depending
|
|
|
|
-- on the presence of `caseBoolExpMaybe` and the value it
|
|
|
|
-- evaluates to. `caseBoolExpMaybe` will be set only in the
|
|
|
|
-- case of an inherited role.
|
|
|
|
-- See [SQL generation for inherited role]
|
|
|
|
case caseBoolExpMaybe of
|
|
|
|
Nothing -> pure computedFieldSQLExp
|
|
|
|
Just caseBoolExp ->
|
|
|
|
let boolExp =
|
2023-05-24 16:51:56 +03:00
|
|
|
S.simplifyBoolExp
|
|
|
|
$ toSQLBoolExp (S.QualifiedIdentifier baseTableIdentifier Nothing)
|
|
|
|
$ _accColCaseBoolExpField
|
|
|
|
<$> caseBoolExp
|
2022-04-22 16:38:35 +03:00
|
|
|
in pure $ S.SECond boolExp computedFieldSQLExp S.SENull
|
|
|
|
AFComputedField _ _ (CFSTable selectTy sel) -> withWriteComputedFieldTableSet $ do
|
|
|
|
let computedFieldSourcePrefix =
|
2022-10-11 13:42:15 +03:00
|
|
|
mkComputedFieldTableIdentifier sourcePrefix fieldName
|
2022-04-22 16:38:35 +03:00
|
|
|
(selectSource, nodeExtractors) <-
|
|
|
|
processAnnSimpleSelect
|
|
|
|
(mkSourcePrefixes computedFieldSourcePrefix)
|
|
|
|
fieldName
|
|
|
|
PLSQNotRequired
|
|
|
|
sel
|
|
|
|
let computedFieldTableSetSource = ComputedFieldTableSetSource fieldName selectSource
|
|
|
|
extractor =
|
2023-05-24 16:51:56 +03:00
|
|
|
asJsonAggExtr selectTy (S.toColumnAlias fieldName) PLSQNotRequired
|
|
|
|
$ orderByForJsonAgg selectSource
|
2022-04-22 16:38:35 +03:00
|
|
|
pure
|
|
|
|
( computedFieldTableSetSource,
|
|
|
|
extractor,
|
|
|
|
nodeExtractors,
|
|
|
|
S.mkQIdenExp computedFieldSourcePrefix fieldName
|
|
|
|
)
|
|
|
|
|
|
|
|
pure $ annRowToJson @pgKind fieldAlias fieldExps
|
|
|
|
where
|
2022-10-11 13:42:15 +03:00
|
|
|
mkSourcePrefixes newPrefix = SourcePrefixes (tableIdentifierToIdentifier newPrefix) (tableIdentifierToIdentifier sourcePrefix)
|
2022-04-22 16:38:35 +03:00
|
|
|
|
2022-10-11 13:42:15 +03:00
|
|
|
baseTableIdentifier = mkBaseTableIdentifier sourcePrefix
|
2022-04-22 16:38:35 +03:00
|
|
|
|
|
|
|
toSQLCol :: AnnColumnField ('Postgres pgKind) S.SQLExp -> m S.SQLExp
|
|
|
|
toSQLCol (AnnColumnField col typ asText colOpM caseBoolExpMaybe) = do
|
|
|
|
strfyNum <- ask
|
|
|
|
let sqlExpression =
|
2023-05-24 16:51:56 +03:00
|
|
|
withColumnOp colOpM
|
|
|
|
$ S.mkQIdenExp baseTableIdentifier col
|
2022-04-22 16:38:35 +03:00
|
|
|
finalSQLExpression =
|
|
|
|
-- Check out [SQL generation for inherited role]
|
|
|
|
case caseBoolExpMaybe of
|
|
|
|
Nothing -> sqlExpression
|
|
|
|
Just caseBoolExp ->
|
|
|
|
let boolExp =
|
2023-05-24 16:51:56 +03:00
|
|
|
S.simplifyBoolExp
|
|
|
|
$ toSQLBoolExp (S.QualifiedIdentifier baseTableIdentifier Nothing)
|
|
|
|
$ _accColCaseBoolExpField
|
|
|
|
<$> caseBoolExp
|
2022-04-22 16:38:35 +03:00
|
|
|
in S.SECond boolExp sqlExpression S.SENull
|
2022-07-19 09:55:42 +03:00
|
|
|
pure $ toJSONableExp strfyNum typ asText tCase finalSQLExpression
|
2022-04-22 16:38:35 +03:00
|
|
|
|
|
|
|
fromScalarComputedField :: ComputedFieldScalarSelect ('Postgres pgKind) S.SQLExp -> m S.SQLExp
|
|
|
|
fromScalarComputedField computedFieldScalar = do
|
|
|
|
strfyNum <- ask
|
2023-05-24 16:51:56 +03:00
|
|
|
pure
|
|
|
|
$ toJSONableExp strfyNum (ColumnScalar ty) False Nothing
|
|
|
|
$ withColumnOp colOpM
|
|
|
|
$ S.SEFunction
|
|
|
|
$ S.FunctionExp fn (fromTableRowArgs sourcePrefix args) Nothing
|
2022-04-22 16:38:35 +03:00
|
|
|
where
|
|
|
|
ComputedFieldScalarSelect fn args ty colOpM = computedFieldScalar
|
|
|
|
|
Clean Relay's code, break schema cycles, introduce Node ID V2
## Motivation
This PR rewrites most of Relay to achieve the following:
- ~~fix a bug in which the same node id could refer to two different tables in the schema~~
- remove one of the few remaining uses of the source cache in the schema building code
In doing so, it also:
- simplifies the `BackendSchema` class by removing `node` from it,
- makes it much easier for other backends to support Relay,
- documents, re-organizes, and clarifies the code.
## Description
This PR introduces a new `NodeId` version ~~, and adapts the Postgres code to always generate this V2 version~~. This new id contains the source name, in addition to the table name, in order to disambiguate similar table names across different sources (which is now possible with source customization). In doing so, it now explicitly handles that case for V1 node ids, and returns an explicit error message instead of running the risk of _silently returning the wrong information_.
Furthermore, it adapts `nodeField` to support multiple backends; most of the code was trivial to generalize, and as a result it lowers the cost of entry for other backends, that now only need to support `AFNodeId` in their translation layer.
Finally, it removes one more cycle in the schema building code, by using the same trick we used for remote relationships instead of using the memoization trick of #4576.
## Remaining work
- ~~[ ]write a Changelog entry~~
- ~~[x] adapt all tests that were asserting on an old node id~~
## Future work
This PR was adapted from its original form to avoid a breaking change: while it introduces a Node ID V2, we keep generating V1 IDs and the parser rejects V2 IDs. It will be easy to make the switch at a later data in a subsequent PR.
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4593
GitOrigin-RevId: 88e5cb91e8b0646900547fa8c7c0e1463de267a1
2022-06-07 16:35:26 +03:00
|
|
|
mkNodeId :: SourceName -> QualifiedTable -> PrimaryKeyColumns ('Postgres pgKind) -> S.SQLExp
|
|
|
|
mkNodeId _sourceName (QualifiedObject tableSchema tableName) pkeyColumns =
|
2022-04-22 16:38:35 +03:00
|
|
|
let columnInfoToSQLExp pgColumnInfo =
|
2023-05-24 16:51:56 +03:00
|
|
|
toJSONableExp Options.Don'tStringifyNumbers (ciType pgColumnInfo) False Nothing
|
|
|
|
$ S.mkQIdenExp (mkBaseTableIdentifier sourcePrefix)
|
|
|
|
$ ciColumn pgColumnInfo
|
2022-04-22 16:38:35 +03:00
|
|
|
in -- See Note [Relay Node id].
|
2023-05-24 16:51:56 +03:00
|
|
|
encodeBase64
|
|
|
|
$ flip S.SETyAnn S.textTypeAnn
|
|
|
|
$ S.applyJsonBuildArray
|
|
|
|
$ [ S.intToSQLExp $ nodeIdVersionInt currentNodeIdVersion,
|
|
|
|
S.SELit (getSchemaTxt tableSchema),
|
|
|
|
S.SELit (toTxt tableName)
|
|
|
|
]
|
|
|
|
<> map columnInfoToSQLExp (toList pkeyColumns)
|
2022-04-22 16:38:35 +03:00
|
|
|
|
2023-05-23 17:46:27 +03:00
|
|
|
withColumnOp :: Maybe S.ColumnOp -> S.SQLExp -> S.SQLExp
|
|
|
|
withColumnOp colOpM sqlExp = case colOpM of
|
|
|
|
Nothing -> sqlExp
|
|
|
|
Just (S.ColumnOp opText cExp) -> S.mkSQLOpExp opText sqlExp cExp
|
|
|
|
|
2022-04-22 16:38:35 +03:00
|
|
|
mkSimilarArrayFields ::
|
|
|
|
forall pgKind v.
|
|
|
|
(Backend ('Postgres pgKind), Eq v) =>
|
|
|
|
AnnFieldsG ('Postgres pgKind) Void v ->
|
|
|
|
Maybe (NE.NonEmpty (AnnotatedOrderByItemG ('Postgres pgKind) v)) ->
|
|
|
|
SimilarArrayFields
|
|
|
|
mkSimilarArrayFields annFields maybeOrderBys =
|
2023-05-24 16:51:56 +03:00
|
|
|
HashMap.fromList
|
|
|
|
$ flip map allTuples
|
|
|
|
$ \(relNameAndArgs, fieldName) -> (fieldName, getSimilarFields relNameAndArgs)
|
2022-04-22 16:38:35 +03:00
|
|
|
where
|
|
|
|
getSimilarFields relNameAndArgs = map snd $ filter ((== relNameAndArgs) . fst) allTuples
|
|
|
|
allTuples = arrayRelationTuples <> aggOrderByRelationTuples
|
|
|
|
arrayRelationTuples =
|
|
|
|
let arrayFields = mapMaybe getAnnArr annFields
|
2023-05-24 16:51:56 +03:00
|
|
|
in flip map arrayFields
|
|
|
|
$ \(f, relSel) -> (getArrayRelNameAndSelectArgs relSel, f)
|
2022-04-22 16:38:35 +03:00
|
|
|
|
|
|
|
getAnnArr ::
|
|
|
|
(a, AnnFieldG ('Postgres pgKind) r v) ->
|
|
|
|
Maybe (a, ArraySelectG ('Postgres pgKind) r v)
|
|
|
|
getAnnArr (f, annFld) = case annFld of
|
|
|
|
AFArrayRelation (ASConnection _) -> Nothing
|
|
|
|
AFArrayRelation ar -> Just (f, ar)
|
|
|
|
_ -> Nothing
|
|
|
|
|
|
|
|
aggOrderByRelationTuples =
|
|
|
|
let mkItem (relName, fieldName) =
|
|
|
|
( (relName, noSelectArgs),
|
|
|
|
fieldName
|
|
|
|
)
|
2023-05-24 16:51:56 +03:00
|
|
|
in map mkItem
|
|
|
|
$ maybe
|
2022-04-22 16:38:35 +03:00
|
|
|
[]
|
|
|
|
(mapMaybe (fetchAggOrderByRels . obiColumn) . toList)
|
|
|
|
maybeOrderBys
|
|
|
|
|
|
|
|
fetchAggOrderByRels (AOCArrayAggregation ri _ _) =
|
|
|
|
Just (riName ri, mkOrderByFieldName $ riName ri)
|
|
|
|
fetchAggOrderByRels _ = Nothing
|
|
|
|
|
|
|
|
getArrayRelNameAndSelectArgs ::
|
|
|
|
ArraySelectG ('Postgres pgKind) r v ->
|
|
|
|
(RelName, SelectArgsG ('Postgres pgKind) v)
|
|
|
|
getArrayRelNameAndSelectArgs = \case
|
|
|
|
ASSimple r -> (_aarRelationshipName r, _asnArgs $ _aarAnnSelect r)
|
|
|
|
ASAggregate r -> (_aarRelationshipName r, _asnArgs $ _aarAnnSelect r)
|
|
|
|
ASConnection r -> (_aarRelationshipName r, _asnArgs $ _csSelect $ _aarAnnSelect r)
|
|
|
|
|
|
|
|
processArrayRelation ::
|
|
|
|
forall pgKind m.
|
2022-07-14 20:57:28 +03:00
|
|
|
( MonadReader Options.StringifyNumbers m,
|
2023-01-27 17:36:35 +03:00
|
|
|
MonadWriter SelectWriter m,
|
2023-05-31 18:41:24 +03:00
|
|
|
MonadState NativeQueryFreshIdStore m,
|
2022-04-22 16:38:35 +03:00
|
|
|
Backend ('Postgres pgKind),
|
|
|
|
PostgresAnnotatedFieldJSON pgKind
|
|
|
|
) =>
|
|
|
|
SourcePrefixes ->
|
|
|
|
FieldName ->
|
2022-07-18 12:44:17 +03:00
|
|
|
S.TableAlias ->
|
2022-04-22 16:38:35 +03:00
|
|
|
ArraySelect ('Postgres pgKind) ->
|
2022-07-19 09:55:42 +03:00
|
|
|
Maybe NamingCase ->
|
2022-04-22 16:38:35 +03:00
|
|
|
m ()
|
2022-07-19 09:55:42 +03:00
|
|
|
processArrayRelation sourcePrefixes fieldAlias relAlias arrSel _tCase =
|
2022-04-22 16:38:35 +03:00
|
|
|
case arrSel of
|
|
|
|
ASSimple annArrRel -> withWriteArrayRelation $ do
|
2023-05-31 13:32:57 +03:00
|
|
|
let AnnRelationSelectG _ colMapping _ sel = annArrRel
|
2022-04-22 16:38:35 +03:00
|
|
|
permLimitSubQuery =
|
|
|
|
maybe PLSQNotRequired PLSQRequired $ _tpLimit $ _asnPerm sel
|
|
|
|
(source, nodeExtractors) <-
|
|
|
|
processAnnSimpleSelect sourcePrefixes fieldAlias permLimitSubQuery sel
|
|
|
|
let topExtr =
|
|
|
|
asJsonAggExtr
|
|
|
|
JASMultipleRows
|
2022-07-18 12:44:17 +03:00
|
|
|
(S.toColumnAlias fieldAlias)
|
2022-04-22 16:38:35 +03:00
|
|
|
permLimitSubQuery
|
|
|
|
$ orderByForJsonAgg source
|
|
|
|
pure
|
|
|
|
( ArrayRelationSource relAlias colMapping source,
|
|
|
|
topExtr,
|
|
|
|
nodeExtractors,
|
|
|
|
()
|
|
|
|
)
|
|
|
|
ASAggregate aggSel -> withWriteArrayRelation $ do
|
2023-05-31 13:32:57 +03:00
|
|
|
let AnnRelationSelectG _ colMapping _ sel = aggSel
|
2022-04-22 16:38:35 +03:00
|
|
|
(source, nodeExtractors, topExtr) <-
|
|
|
|
processAnnAggregateSelect sourcePrefixes fieldAlias sel
|
|
|
|
pure
|
|
|
|
( ArrayRelationSource relAlias colMapping source,
|
|
|
|
topExtr,
|
|
|
|
nodeExtractors,
|
|
|
|
()
|
|
|
|
)
|
|
|
|
ASConnection connSel -> withWriteArrayConnection $ do
|
2023-05-31 13:32:57 +03:00
|
|
|
let AnnRelationSelectG _ colMapping _ sel = connSel
|
2022-04-22 16:38:35 +03:00
|
|
|
(source, topExtractor, nodeExtractors) <-
|
|
|
|
processConnectionSelect sourcePrefixes fieldAlias relAlias colMapping sel
|
|
|
|
pure
|
|
|
|
( source,
|
|
|
|
topExtractor,
|
|
|
|
nodeExtractors,
|
|
|
|
()
|
|
|
|
)
|
|
|
|
|
2023-05-23 17:46:27 +03:00
|
|
|
aggregateFieldToExp ::
|
|
|
|
TableIdentifier ->
|
|
|
|
AggregateFields ('Postgres pgKind) S.SQLExp ->
|
|
|
|
Options.StringifyNumbers ->
|
|
|
|
S.SQLExp
|
|
|
|
aggregateFieldToExp sourcePrefix aggFlds strfyNum = jsonRow
|
2022-04-22 16:38:35 +03:00
|
|
|
where
|
|
|
|
jsonRow = S.applyJsonBuildObj (concatMap aggToFlds aggFlds)
|
|
|
|
withAls fldName sqlExp = [S.SELit fldName, sqlExp]
|
|
|
|
aggToFlds (FieldName t, fld) = withAls t $ case fld of
|
|
|
|
AFCount cty -> S.SECount cty
|
|
|
|
AFOp aggOp -> aggOpToObj aggOp
|
|
|
|
AFExp e -> S.SELit e
|
|
|
|
|
|
|
|
aggOpToObj (AggregateOp opText flds) =
|
|
|
|
S.applyJsonBuildObj $ concatMap (colFldsToExtr opText) flds
|
|
|
|
|
2023-05-23 17:46:27 +03:00
|
|
|
colFldsToExtr opText (FieldName t, SFCol col ty) =
|
2022-04-22 16:38:35 +03:00
|
|
|
[ S.SELit t,
|
2023-05-24 16:51:56 +03:00
|
|
|
toJSONableExp strfyNum ty False Nothing
|
|
|
|
$ S.SEFnApp opText [S.SEIdentifier $ toIdentifier col] Nothing
|
2022-04-22 16:38:35 +03:00
|
|
|
]
|
2023-05-23 17:46:27 +03:00
|
|
|
colFldsToExtr opText (FieldName t, SFComputedField _cfName (ComputedFieldScalarSelect {..})) =
|
|
|
|
[ S.SELit t,
|
2023-05-24 16:51:56 +03:00
|
|
|
toJSONableExp strfyNum (ColumnScalar _cfssType) False Nothing
|
|
|
|
$ S.SEFnApp
|
2023-05-23 17:46:27 +03:00
|
|
|
opText
|
2023-05-24 16:51:56 +03:00
|
|
|
[ withColumnOp _cfssScalarArguments
|
|
|
|
$ S.SEFunction
|
|
|
|
$ S.FunctionExp _cfssFunction (fromTableRowArgsDon'tAddBase sourcePrefix _cfssArguments) Nothing
|
2023-05-23 17:46:27 +03:00
|
|
|
]
|
|
|
|
Nothing
|
|
|
|
]
|
|
|
|
colFldsToExtr _ (FieldName t, SFExp e) =
|
2022-04-22 16:38:35 +03:00
|
|
|
[S.SELit t, S.SELit e]
|
|
|
|
|
|
|
|
processAnnSimpleSelect ::
|
|
|
|
forall pgKind m.
|
2022-07-14 20:57:28 +03:00
|
|
|
( MonadReader Options.StringifyNumbers m,
|
2023-05-31 18:41:24 +03:00
|
|
|
MonadState NativeQueryFreshIdStore m,
|
2023-01-27 17:36:35 +03:00
|
|
|
MonadWriter SelectWriter m,
|
2022-04-22 16:38:35 +03:00
|
|
|
Backend ('Postgres pgKind),
|
|
|
|
PostgresAnnotatedFieldJSON pgKind
|
|
|
|
) =>
|
|
|
|
SourcePrefixes ->
|
|
|
|
FieldName ->
|
|
|
|
PermissionLimitSubQuery ->
|
|
|
|
AnnSimpleSelect ('Postgres pgKind) ->
|
|
|
|
m
|
|
|
|
( SelectSource,
|
2023-04-26 18:42:13 +03:00
|
|
|
HashMap.HashMap S.ColumnAlias S.SQLExp
|
2022-04-22 16:38:35 +03:00
|
|
|
)
|
|
|
|
processAnnSimpleSelect sourcePrefixes fieldAlias permLimitSubQuery annSimpleSel = do
|
|
|
|
(selectSource, orderByAndDistinctExtrs, _) <-
|
|
|
|
processSelectParams
|
|
|
|
sourcePrefixes
|
|
|
|
fieldAlias
|
|
|
|
similarArrayFields
|
|
|
|
tableFrom
|
|
|
|
permLimitSubQuery
|
|
|
|
tablePermissions
|
|
|
|
tableArgs
|
2023-01-27 17:36:35 +03:00
|
|
|
annFieldsExtr <-
|
|
|
|
processAnnFields
|
|
|
|
(identifierToTableIdentifier $ _pfThis sourcePrefixes)
|
|
|
|
fieldAlias
|
|
|
|
similarArrayFields
|
|
|
|
annSelFields
|
|
|
|
tCase
|
2023-04-26 18:42:13 +03:00
|
|
|
let allExtractors = HashMap.fromList $ annFieldsExtr : orderByAndDistinctExtrs
|
2022-04-22 16:38:35 +03:00
|
|
|
pure (selectSource, allExtractors)
|
|
|
|
where
|
2022-07-19 09:55:42 +03:00
|
|
|
AnnSelectG annSelFields tableFrom tablePermissions tableArgs _ tCase = annSimpleSel
|
2022-04-22 16:38:35 +03:00
|
|
|
similarArrayFields =
|
|
|
|
mkSimilarArrayFields annSelFields $ _saOrderBy tableArgs
|
|
|
|
|
|
|
|
processConnectionSelect ::
|
|
|
|
forall pgKind m.
|
2022-07-14 20:57:28 +03:00
|
|
|
( MonadReader Options.StringifyNumbers m,
|
2023-01-27 17:36:35 +03:00
|
|
|
MonadWriter SelectWriter m,
|
2023-05-31 18:41:24 +03:00
|
|
|
MonadState NativeQueryFreshIdStore m,
|
2022-04-22 16:38:35 +03:00
|
|
|
Backend ('Postgres pgKind),
|
|
|
|
PostgresAnnotatedFieldJSON pgKind
|
|
|
|
) =>
|
|
|
|
SourcePrefixes ->
|
|
|
|
FieldName ->
|
2022-07-18 12:44:17 +03:00
|
|
|
S.TableAlias ->
|
2023-04-26 18:42:13 +03:00
|
|
|
HashMap.HashMap PGCol PGCol ->
|
2022-04-22 16:38:35 +03:00
|
|
|
ConnectionSelect ('Postgres pgKind) Void S.SQLExp ->
|
|
|
|
m
|
|
|
|
( ArrayConnectionSource,
|
|
|
|
S.Extractor,
|
2023-04-26 18:42:13 +03:00
|
|
|
HashMap.HashMap S.ColumnAlias S.SQLExp
|
2022-04-22 16:38:35 +03:00
|
|
|
)
|
|
|
|
processConnectionSelect sourcePrefixes fieldAlias relAlias colMapping connectionSelect = do
|
|
|
|
(selectSource, orderByAndDistinctExtrs, maybeOrderByCursor) <-
|
|
|
|
processSelectParams
|
|
|
|
sourcePrefixes
|
|
|
|
fieldAlias
|
|
|
|
similarArrayFields
|
|
|
|
selectFrom
|
|
|
|
permLimitSubQuery
|
|
|
|
tablePermissions
|
|
|
|
tableArgs
|
|
|
|
|
2022-07-18 12:44:17 +03:00
|
|
|
let mkCursorExtractor = (S.toColumnAlias cursorIdentifier,) . (`S.SETyAnn` S.textTypeAnn)
|
2022-04-22 16:38:35 +03:00
|
|
|
cursorExtractors = case maybeOrderByCursor of
|
|
|
|
Just orderByCursor -> [mkCursorExtractor orderByCursor]
|
|
|
|
Nothing ->
|
|
|
|
-- Extract primary key columns from base select along with cursor expression.
|
|
|
|
-- Those columns are required to perform connection split via a WHERE clause.
|
|
|
|
mkCursorExtractor primaryKeyColumnsObjectExp : primaryKeyColumnExtractors
|
|
|
|
(topExtractorExp, exps) <- flip runStateT [] $ processFields selectSource
|
2022-07-18 12:44:17 +03:00
|
|
|
let topExtractor = S.Extractor topExtractorExp $ Just $ S.toColumnAlias fieldIdentifier
|
2023-04-26 18:42:13 +03:00
|
|
|
allExtractors = HashMap.fromList $ cursorExtractors <> exps <> orderByAndDistinctExtrs
|
2022-04-22 16:38:35 +03:00
|
|
|
arrayConnectionSource =
|
|
|
|
ArrayConnectionSource
|
|
|
|
relAlias
|
|
|
|
colMapping
|
|
|
|
(mkSplitBoolExp <$> maybeSplit)
|
|
|
|
maybeSlice
|
|
|
|
selectSource
|
|
|
|
pure
|
|
|
|
( arrayConnectionSource,
|
|
|
|
topExtractor,
|
|
|
|
allExtractors
|
|
|
|
)
|
|
|
|
where
|
|
|
|
ConnectionSelect _ primaryKeyColumns maybeSplit maybeSlice select = connectionSelect
|
2022-07-19 09:55:42 +03:00
|
|
|
AnnSelectG fields selectFrom tablePermissions tableArgs _ tCase = select
|
2022-04-22 16:38:35 +03:00
|
|
|
fieldIdentifier = toIdentifier fieldAlias
|
2022-10-11 13:42:15 +03:00
|
|
|
thisPrefix = identifierToTableIdentifier $ _pfThis sourcePrefixes
|
2022-04-22 16:38:35 +03:00
|
|
|
permLimitSubQuery = PLSQNotRequired
|
|
|
|
|
|
|
|
primaryKeyColumnsObjectExp =
|
2023-05-24 16:51:56 +03:00
|
|
|
S.applyJsonBuildObj
|
|
|
|
$ flip concatMap (toList primaryKeyColumns)
|
|
|
|
$ \pgColumnInfo ->
|
|
|
|
[ S.SELit $ getPGColTxt $ ciColumn pgColumnInfo,
|
|
|
|
toJSONableExp Options.Don'tStringifyNumbers (ciType pgColumnInfo) False tCase
|
|
|
|
$ S.mkQIdenExp (mkBaseTableIdentifier thisPrefix)
|
|
|
|
$ ciColumn pgColumnInfo
|
|
|
|
]
|
2022-04-22 16:38:35 +03:00
|
|
|
|
|
|
|
primaryKeyColumnExtractors =
|
2023-05-24 16:51:56 +03:00
|
|
|
flip map (toList primaryKeyColumns)
|
|
|
|
$ \pgColumnInfo ->
|
2022-04-22 16:38:35 +03:00
|
|
|
let pgColumn = ciColumn pgColumnInfo
|
2022-10-11 13:42:15 +03:00
|
|
|
in ( contextualizeBaseTableColumn thisPrefix pgColumn,
|
|
|
|
S.mkQIdenExp (mkBaseTableIdentifier thisPrefix) pgColumn
|
2022-04-22 16:38:35 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
mkSplitBoolExp (firstSplit NE.:| rest) =
|
|
|
|
S.BEBin S.OrOp (mkSplitCompareExp firstSplit) $ mkBoolExpFromRest firstSplit rest
|
|
|
|
where
|
|
|
|
mkBoolExpFromRest previousSplit =
|
|
|
|
S.BEBin S.AndOp (mkEqualityCompareExp previousSplit) . \case
|
|
|
|
[] -> S.BELit False
|
|
|
|
(thisSplit : remainingSplit) -> mkSplitBoolExp (thisSplit NE.:| remainingSplit)
|
|
|
|
|
|
|
|
mkSplitCompareExp (ConnectionSplit kind v (OrderByItemG obTyM obCol _)) =
|
|
|
|
let obAlias = mkAnnOrderByAlias thisPrefix fieldAlias similarArrayFields obCol
|
|
|
|
obTy = fromMaybe S.OTAsc obTyM
|
|
|
|
compareOp = case (kind, obTy) of
|
|
|
|
(CSKAfter, S.OTAsc) -> S.SGT
|
|
|
|
(CSKAfter, S.OTDesc) -> S.SLT
|
|
|
|
(CSKBefore, S.OTAsc) -> S.SLT
|
|
|
|
(CSKBefore, S.OTDesc) -> S.SGT
|
|
|
|
in S.BECompare compareOp (S.SEIdentifier $ toIdentifier obAlias) v
|
|
|
|
|
|
|
|
mkEqualityCompareExp (ConnectionSplit _ v orderByItem) =
|
|
|
|
let obAlias =
|
2023-05-24 16:51:56 +03:00
|
|
|
mkAnnOrderByAlias thisPrefix fieldAlias similarArrayFields
|
|
|
|
$ obiColumn orderByItem
|
2022-04-22 16:38:35 +03:00
|
|
|
in S.BECompare S.SEQ (S.SEIdentifier $ toIdentifier obAlias) v
|
|
|
|
|
2023-05-24 16:51:56 +03:00
|
|
|
similarArrayFields = HashMap.unions
|
|
|
|
$ flip map (map snd fields)
|
|
|
|
$ \case
|
2022-04-22 16:38:35 +03:00
|
|
|
ConnectionTypename {} -> mempty
|
|
|
|
ConnectionPageInfo {} -> mempty
|
2023-05-24 16:51:56 +03:00
|
|
|
ConnectionEdges edges -> HashMap.unions
|
|
|
|
$ flip map (map snd edges)
|
|
|
|
$ \case
|
2022-04-22 16:38:35 +03:00
|
|
|
EdgeTypename {} -> mempty
|
|
|
|
EdgeCursor {} -> mempty
|
|
|
|
EdgeNode annFields ->
|
|
|
|
mkSimilarArrayFields annFields $ _saOrderBy tableArgs
|
|
|
|
|
|
|
|
mkSimpleJsonAgg rowExp ob =
|
|
|
|
let jsonAggExp = S.SEFnApp "json_agg" [rowExp] ob
|
|
|
|
in S.SEFnApp "coalesce" [jsonAggExp, S.SELit "[]"] Nothing
|
|
|
|
|
|
|
|
processFields ::
|
2023-05-31 18:41:24 +03:00
|
|
|
forall n n' t.
|
|
|
|
( MonadState [(S.ColumnAlias, S.SQLExp)] n,
|
|
|
|
-- Constraints for 'processAnnFields':
|
|
|
|
n ~ (t n'),
|
|
|
|
MonadTrans t,
|
|
|
|
MonadState NativeQueryFreshIdStore n',
|
|
|
|
MonadWriter SelectWriter n',
|
|
|
|
MonadReader Options.StringifyNumbers n'
|
2022-04-22 16:38:35 +03:00
|
|
|
) =>
|
|
|
|
SelectSource ->
|
|
|
|
n S.SQLExp
|
|
|
|
processFields selectSource =
|
2023-05-24 16:51:56 +03:00
|
|
|
fmap (S.applyJsonBuildObj . concat)
|
|
|
|
$ forM fields
|
|
|
|
$ \(FieldName fieldText, field) ->
|
|
|
|
(S.SELit fieldText :)
|
|
|
|
. pure
|
|
|
|
<$> case field of
|
|
|
|
ConnectionTypename t -> pure $ withForceAggregation S.textTypeAnn $ S.SELit t
|
|
|
|
ConnectionPageInfo pageInfoFields -> pure $ processPageInfoFields pageInfoFields
|
|
|
|
ConnectionEdges edges ->
|
|
|
|
fmap (flip mkSimpleJsonAgg (orderByForJsonAgg selectSource) . S.applyJsonBuildObj . concat)
|
|
|
|
$ forM edges
|
|
|
|
$ \(FieldName edgeText, edge) ->
|
|
|
|
(S.SELit edgeText :)
|
|
|
|
. pure
|
|
|
|
<$> case edge of
|
|
|
|
EdgeTypename t -> pure $ S.SELit t
|
|
|
|
EdgeCursor -> pure $ encodeBase64 $ S.SEIdentifier (toIdentifier cursorIdentifier)
|
|
|
|
EdgeNode annFields -> do
|
|
|
|
let edgeFieldName =
|
|
|
|
FieldName
|
|
|
|
$ getFieldNameTxt fieldAlias
|
|
|
|
<> "."
|
|
|
|
<> fieldText
|
|
|
|
<> "."
|
|
|
|
<> edgeText
|
|
|
|
edgeFieldIdentifier = toIdentifier edgeFieldName
|
2023-05-31 18:41:24 +03:00
|
|
|
annFieldsExtrExp <- lift $ processAnnFields thisPrefix edgeFieldName similarArrayFields annFields tCase
|
2023-05-24 16:51:56 +03:00
|
|
|
modify' (<> [annFieldsExtrExp])
|
|
|
|
pure $ S.SEIdentifier edgeFieldIdentifier
|
2022-04-22 16:38:35 +03:00
|
|
|
|
|
|
|
processPageInfoFields infoFields =
|
2023-05-24 16:51:56 +03:00
|
|
|
S.applyJsonBuildObj
|
|
|
|
$ flip concatMap infoFields
|
|
|
|
$ \(FieldName fieldText, field) -> (:) (S.SELit fieldText) $ pure case field of
|
|
|
|
PageInfoTypename t -> withForceAggregation S.textTypeAnn $ S.SELit t
|
|
|
|
PageInfoHasNextPage ->
|
|
|
|
withForceAggregation S.boolTypeAnn
|
|
|
|
$ mkSingleFieldSelect (S.SEIdentifier hasNextPageIdentifier) pageInfoSelectAliasIdentifier
|
|
|
|
PageInfoHasPreviousPage ->
|
|
|
|
withForceAggregation S.boolTypeAnn
|
|
|
|
$ mkSingleFieldSelect (S.SEIdentifier hasPreviousPageIdentifier) pageInfoSelectAliasIdentifier
|
|
|
|
PageInfoStartCursor ->
|
|
|
|
withForceAggregation S.textTypeAnn
|
|
|
|
$ encodeBase64
|
|
|
|
$ mkSingleFieldSelect (S.SEIdentifier startCursorIdentifier) cursorsSelectAliasIdentifier
|
|
|
|
PageInfoEndCursor ->
|
|
|
|
withForceAggregation S.textTypeAnn
|
|
|
|
$ encodeBase64
|
|
|
|
$ mkSingleFieldSelect (S.SEIdentifier endCursorIdentifier) cursorsSelectAliasIdentifier
|
2022-04-22 16:38:35 +03:00
|
|
|
where
|
|
|
|
mkSingleFieldSelect field fromIdentifier =
|
|
|
|
S.SESelect
|
|
|
|
S.mkSelect
|
|
|
|
{ S.selExtr = [S.Extractor field Nothing],
|
|
|
|
S.selFrom = Just $ S.FromExp [S.FIIdentifier fromIdentifier]
|
|
|
|
}
|