mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-18 13:02:11 +03:00
e0c0043e76
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/9284 GitOrigin-RevId: 2f2cf2ad01900a54e4bdb970205ac0ef313c7e00
315 lines
13 KiB
Haskell
315 lines
13 KiB
Haskell
-- | This module contains worker definitions pertaining to generating 'Select'
|
|
-- AST nodes.
|
|
--
|
|
-- NOTE: 'generateSQLSelect' is mutually recursive with
|
|
-- 'connectionToSelectWith', thus these two cohabitate in this module.
|
|
module Hasura.Backends.Postgres.Translate.Select.Internal.GenerateSelect
|
|
( generateSQLSelect,
|
|
generateSQLSelectFromArrayNode,
|
|
connectionToSelectWith,
|
|
)
|
|
where
|
|
|
|
import Data.HashMap.Strict qualified as HashMap
|
|
import Data.List.NonEmpty qualified as NE
|
|
import Hasura.Backends.Postgres.SQL.DML qualified as S
|
|
import Hasura.Backends.Postgres.SQL.Types
|
|
import Hasura.Backends.Postgres.SQL.Types qualified as S
|
|
import Hasura.Backends.Postgres.Translate.Select.Internal.Aliases (mkBaseTableAlias)
|
|
import Hasura.Backends.Postgres.Translate.Select.Internal.Helpers
|
|
( cursorIdentifier,
|
|
cursorsSelectAlias,
|
|
cursorsSelectAliasIdentifier,
|
|
endCursorIdentifier,
|
|
hasNextPageIdentifier,
|
|
hasPreviousPageIdentifier,
|
|
mkFirstElementExp,
|
|
mkLastElementExp,
|
|
pageInfoSelectAlias,
|
|
startCursorIdentifier,
|
|
)
|
|
import Hasura.Backends.Postgres.Translate.Types
|
|
import Hasura.Prelude
|
|
import Hasura.RQL.IR.Select (ConnectionSlice (SliceFirst, SliceLast))
|
|
|
|
generateSQLSelect ::
|
|
-- | Pre join condition for lateral joins
|
|
S.BoolExp ->
|
|
SelectSource ->
|
|
SelectNode ->
|
|
S.Select
|
|
generateSQLSelect joinCondition selectSource selectNode =
|
|
S.mkSelect
|
|
{ S.selExtr =
|
|
case [S.Extractor e $ Just a | (a, e) <- HashMap.toList extractors] of
|
|
-- If the select list is empty we will generated code which looks like this:
|
|
-- > SELECT FROM ...
|
|
-- This works for postgres, but not for cockroach, which expects a non-empty
|
|
-- select list. So we add a dummy value:
|
|
[] -> S.dummySelectList -- SELECT 1 FROM ...
|
|
exts -> exts,
|
|
S.selFrom = Just $ S.FromExp [joinedFrom],
|
|
S.selOrderBy = nodeOrderBy,
|
|
S.selLimit = S.LimitExp . S.intToSQLExp <$> _ssLimit nodeSlicing,
|
|
S.selOffset = S.OffsetExp . S.int64ToSQLExp <$> _ssOffset nodeSlicing,
|
|
S.selDistinct = nodeDistinctOn
|
|
}
|
|
where
|
|
SelectSource sourcePrefix fromItem whereExp sortAndSlice = selectSource
|
|
SelectNode extractors joinTree = selectNode
|
|
JoinTree objectRelations arrayRelations arrayConnections computedFields = joinTree
|
|
ApplySortingAndSlicing
|
|
(baseOrderBy, baseSlicing, baseDistinctOn)
|
|
(nodeOrderBy, nodeSlicing, nodeDistinctOn) = applySortingAndSlicing sortAndSlice
|
|
|
|
-- this is the table which is aliased as "sourcePrefix.base"
|
|
baseSelect =
|
|
S.mkSelect
|
|
{ S.selExtr = [S.Extractor (S.SEStar Nothing) Nothing],
|
|
S.selFrom = Just $ S.FromExp [fromItem],
|
|
S.selWhere = Just $ injectJoinCond joinCondition whereExp,
|
|
S.selOrderBy = baseOrderBy,
|
|
S.selLimit = S.LimitExp . S.intToSQLExp <$> _ssLimit baseSlicing,
|
|
S.selOffset = S.OffsetExp . S.int64ToSQLExp <$> _ssOffset baseSlicing,
|
|
S.selDistinct = baseDistinctOn
|
|
}
|
|
-- This is why 'SelectSource{sourcePrefix}' needs to be a TableAlias!
|
|
baseSelectAlias = mkBaseTableAlias (S.toTableAlias sourcePrefix)
|
|
baseSelectIdentifier = S.tableAliasToIdentifier baseSelectAlias
|
|
baseFromItem = S.mkSelFromItem baseSelect baseSelectAlias
|
|
|
|
injectJoinCond ::
|
|
S.BoolExp -> -- Join condition
|
|
S.BoolExp -> -- Where condition
|
|
S.WhereFrag -- New where frag
|
|
injectJoinCond joinCond whereCond =
|
|
S.WhereFrag $ S.simplifyBoolExp $ S.BEBin S.AndOp joinCond whereCond
|
|
|
|
-- function to create a joined from item from two from items
|
|
leftOuterJoin current new =
|
|
S.FIJoin
|
|
$ S.JoinExpr current S.LeftOuter new
|
|
$ S.JoinOn
|
|
$ S.BELit True
|
|
|
|
-- this is the from eexp for the final select
|
|
joinedFrom :: S.FromItem
|
|
joinedFrom =
|
|
foldl' leftOuterJoin baseFromItem
|
|
$ map objectRelationToFromItem (HashMap.toList objectRelations)
|
|
<> map arrayRelationToFromItem (HashMap.toList arrayRelations)
|
|
<> map arrayConnectionToFromItem (HashMap.toList arrayConnections)
|
|
<> map computedFieldToFromItem (HashMap.toList computedFields)
|
|
|
|
objectRelationToFromItem ::
|
|
(ObjectRelationSource, SelectNode) -> S.FromItem
|
|
objectRelationToFromItem (objectRelationSource, node) =
|
|
let ObjectRelationSource _ colMapping objectSelectSource = objectRelationSource
|
|
alias = S.toTableAlias $ _ossPrefix objectSelectSource
|
|
source = objectSelectSourceToSelectSource objectSelectSource
|
|
select = generateSQLSelect (mkJoinCond baseSelectIdentifier colMapping) source node
|
|
in S.mkLateralFromItem select alias
|
|
|
|
arrayRelationToFromItem ::
|
|
(ArrayRelationSource, MultiRowSelectNode) -> S.FromItem
|
|
arrayRelationToFromItem (arrayRelationSource, arraySelectNode) =
|
|
let ArrayRelationSource _ colMapping source = arrayRelationSource
|
|
alias = S.toTableAlias $ _ssPrefix source
|
|
select =
|
|
generateSQLSelectFromArrayNode source arraySelectNode
|
|
$ mkJoinCond baseSelectIdentifier colMapping
|
|
in S.mkLateralFromItem select alias
|
|
|
|
arrayConnectionToFromItem ::
|
|
(ArrayConnectionSource, MultiRowSelectNode) -> S.FromItem
|
|
arrayConnectionToFromItem (arrayConnectionSource, arraySelectNode) =
|
|
let selectWith = connectionToSelectWith baseSelectAlias arrayConnectionSource arraySelectNode
|
|
alias = S.toTableAlias $ _ssPrefix $ _acsSource arrayConnectionSource
|
|
in S.FISelectWith (S.Lateral True) selectWith alias
|
|
|
|
computedFieldToFromItem ::
|
|
(ComputedFieldTableSetSource, MultiRowSelectNode) -> S.FromItem
|
|
computedFieldToFromItem (computedFieldTableSource, node) =
|
|
let ComputedFieldTableSetSource _ source = computedFieldTableSource
|
|
internalSelect = generateSQLSelect (S.BELit True) source $ _mrsnSelectNode node
|
|
alias = S.toTableAlias $ _ssPrefix source
|
|
select =
|
|
S.mkSelect
|
|
{ S.selExtr = _mrsnTopExtractors node,
|
|
S.selFrom = Just $ S.FromExp [S.mkSelFromItem internalSelect alias]
|
|
}
|
|
in S.mkLateralFromItem select alias
|
|
|
|
-- | Create a select query
|
|
generateSQLSelectFromArrayNode ::
|
|
SelectSource ->
|
|
MultiRowSelectNode ->
|
|
S.BoolExp ->
|
|
S.Select
|
|
generateSQLSelectFromArrayNode selectSource (MultiRowSelectNode topExtractors selectNode) joinCondition =
|
|
S.mkSelect
|
|
{ S.selExtr = topExtractors,
|
|
S.selFrom =
|
|
Just
|
|
$ S.FromExp
|
|
[ S.mkSelFromItem
|
|
(generateSQLSelect joinCondition selectSource selectNode)
|
|
$ S.toTableAlias
|
|
$ _ssPrefix selectSource
|
|
]
|
|
}
|
|
|
|
mkJoinCond :: S.TableIdentifier -> HashMap PGCol PGCol -> S.BoolExp
|
|
mkJoinCond baseTablepfx colMapn =
|
|
foldl' (S.BEBin S.AndOp) (S.BELit True)
|
|
$ flip map (HashMap.toList colMapn)
|
|
$ \(lCol, rCol) ->
|
|
S.BECompare S.SEQ (S.mkQIdenExp baseTablepfx lCol) (S.mkSIdenExp rCol)
|
|
|
|
connectionToSelectWith ::
|
|
S.TableAlias ->
|
|
ArrayConnectionSource ->
|
|
MultiRowSelectNode ->
|
|
S.SelectWithG S.Select
|
|
connectionToSelectWith rootSelectAlias arrayConnectionSource arraySelectNode =
|
|
let extractionSelect =
|
|
S.mkSelect
|
|
{ S.selExtr = topExtractors,
|
|
S.selFrom = Just $ S.FromExp [S.FIIdentifier finalSelectIdentifier]
|
|
}
|
|
in S.SelectWith fromBaseSelections extractionSelect
|
|
where
|
|
ArrayConnectionSource _ columnMapping maybeSplit maybeSlice selectSource =
|
|
arrayConnectionSource
|
|
MultiRowSelectNode topExtractors selectNode = arraySelectNode
|
|
|
|
rootSelectIdentifier = S.tableAliasToIdentifier rootSelectAlias
|
|
|
|
baseSelectAlias = S.mkTableAlias "__base_select"
|
|
baseSelectIdentifier = S.tableAliasToIdentifier baseSelectAlias
|
|
|
|
splitSelectAlias = S.mkTableAlias "__split_select"
|
|
splitSelectIdentifier = S.tableAliasToIdentifier splitSelectAlias
|
|
|
|
sliceSelectAlias = S.mkTableAlias "__slice_select"
|
|
sliceSelectIdentifier = S.tableAliasToIdentifier sliceSelectAlias
|
|
|
|
finalSelectAlias = S.mkTableAlias "__final_select"
|
|
finalSelectIdentifier = S.tableAliasToIdentifier finalSelectAlias
|
|
|
|
rowNumberIdentifier = Identifier "__row_number"
|
|
rowNumberExp = S.SEUnsafe "(row_number() over (partition by 1))"
|
|
startRowNumberIdentifier = Identifier "__start_row_number"
|
|
endRowNumberIdentifier = Identifier "__end_row_number"
|
|
|
|
startCursorExp = mkFirstElementExp $ S.SEIdentifier cursorIdentifier
|
|
endCursorExp = mkLastElementExp $ S.SEIdentifier cursorIdentifier
|
|
|
|
startRowNumberExp = mkFirstElementExp $ S.SEIdentifier rowNumberIdentifier
|
|
endRowNumberExp = mkLastElementExp $ S.SEIdentifier rowNumberIdentifier
|
|
|
|
fromBaseSelections =
|
|
let joinCond = mkJoinCond rootSelectIdentifier columnMapping
|
|
baseSelectFrom =
|
|
S.mkSelFromItem
|
|
(generateSQLSelect joinCond selectSource selectNode)
|
|
$ S.toTableAlias
|
|
$ _ssPrefix selectSource
|
|
select =
|
|
S.mkSelect
|
|
{ S.selExtr =
|
|
[ S.selectStar,
|
|
S.Extractor rowNumberExp $ Just $ S.toColumnAlias rowNumberIdentifier
|
|
],
|
|
S.selFrom = Just $ S.FromExp [baseSelectFrom]
|
|
}
|
|
in (baseSelectAlias, select) : fromSplitSelection
|
|
|
|
mkStarSelect fromIdentifier =
|
|
S.mkSelect
|
|
{ S.selExtr = [S.selectStar],
|
|
S.selFrom = Just $ S.FromExp [S.FIIdentifier fromIdentifier]
|
|
}
|
|
|
|
fromSplitSelection = case maybeSplit of
|
|
Nothing -> fromSliceSelection baseSelectIdentifier
|
|
Just splitBool ->
|
|
let select =
|
|
(mkStarSelect baseSelectIdentifier) {S.selWhere = Just $ S.WhereFrag splitBool}
|
|
in (splitSelectAlias, select) : fromSliceSelection splitSelectIdentifier
|
|
|
|
fromSliceSelection prevSelect = case maybeSlice of
|
|
Nothing -> fromFinalSelect prevSelect
|
|
Just slice ->
|
|
let select = case slice of
|
|
SliceFirst limit ->
|
|
(mkStarSelect prevSelect)
|
|
{ S.selLimit = (Just . S.LimitExp . S.intToSQLExp) limit
|
|
}
|
|
SliceLast limit ->
|
|
let mkRowNumberOrderBy obType =
|
|
let orderByItem =
|
|
S.OrderByItem (S.SEIdentifier rowNumberIdentifier) (Just obType) Nothing
|
|
in S.OrderByExp $ orderByItem NE.:| []
|
|
|
|
sliceLastSelect =
|
|
(mkStarSelect prevSelect)
|
|
{ S.selLimit = (Just . S.LimitExp . S.intToSQLExp) limit,
|
|
S.selOrderBy = Just $ mkRowNumberOrderBy S.OTDesc
|
|
}
|
|
sliceLastSelectFrom =
|
|
S.mkSelFromItem sliceLastSelect $ sliceSelectAlias
|
|
in S.mkSelect
|
|
{ S.selExtr = [S.selectStar],
|
|
S.selFrom = Just $ S.FromExp [sliceLastSelectFrom],
|
|
S.selOrderBy = Just $ mkRowNumberOrderBy S.OTAsc
|
|
}
|
|
in (sliceSelectAlias, select) : fromFinalSelect sliceSelectIdentifier
|
|
|
|
fromFinalSelect prevSelect =
|
|
let select = mkStarSelect prevSelect
|
|
in (finalSelectAlias, select) : fromCursorSelection
|
|
|
|
fromCursorSelection =
|
|
let extrs =
|
|
[ S.Extractor startCursorExp $ Just $ S.toColumnAlias startCursorIdentifier,
|
|
S.Extractor endCursorExp $ Just $ S.toColumnAlias endCursorIdentifier,
|
|
S.Extractor startRowNumberExp $ Just $ S.toColumnAlias startRowNumberIdentifier,
|
|
S.Extractor endRowNumberExp $ Just $ S.toColumnAlias endRowNumberIdentifier
|
|
]
|
|
select =
|
|
S.mkSelect
|
|
{ S.selExtr = extrs,
|
|
S.selFrom = Just $ S.FromExp [S.FIIdentifier finalSelectIdentifier]
|
|
}
|
|
in (cursorsSelectAlias, select) : fromPageInfoSelection
|
|
|
|
fromPageInfoSelection =
|
|
let hasPrevPage =
|
|
S.SEBool
|
|
$ S.mkExists (S.FIIdentifier baseSelectIdentifier)
|
|
$ S.BECompare S.SLT (S.SEIdentifier rowNumberIdentifier)
|
|
$ S.SESelect
|
|
$ S.mkSelect
|
|
{ S.selFrom = Just $ S.FromExp [S.FIIdentifier cursorsSelectAliasIdentifier],
|
|
S.selExtr = [S.Extractor (S.SEIdentifier startRowNumberIdentifier) Nothing]
|
|
}
|
|
hasNextPage =
|
|
S.SEBool
|
|
$ S.mkExists (S.FIIdentifier baseSelectIdentifier)
|
|
$ S.BECompare S.SGT (S.SEIdentifier rowNumberIdentifier)
|
|
$ S.SESelect
|
|
$ S.mkSelect
|
|
{ S.selFrom = Just $ S.FromExp [S.FIIdentifier cursorsSelectAliasIdentifier],
|
|
S.selExtr = [S.Extractor (S.SEIdentifier endRowNumberIdentifier) Nothing]
|
|
}
|
|
|
|
select =
|
|
S.mkSelect
|
|
{ S.selExtr =
|
|
[ S.Extractor hasPrevPage $ Just $ S.toColumnAlias hasPreviousPageIdentifier,
|
|
S.Extractor hasNextPage $ Just $ S.toColumnAlias hasNextPageIdentifier
|
|
]
|
|
}
|
|
in pure (pageInfoSelectAlias, select)
|