graphql-engine/server/src-lib/Hasura/Backends/Postgres/Translate/Select/Internal/GenerateSelect.hs
Samir Talwar 342391f39d Upgrade Ormolu to v0.5.
This upgrades the version of Ormolu required by the HGE repository to v0.5.0.1, and reformats all code accordingly.

Ormolu v0.5 reformats code that uses infix operators. This is mostly useful, adding newlines and indentation to make it clear which operators are applied first, but in some cases, it's unpleasant. To make this easier on the eyes, I had to do the following:

* Add a few fixity declarations (search for `infix`)
* Add parentheses to make precedence clear, allowing Ormolu to keep everything on one line
* Rename `relevantEq` to `(==~)` in #6651 and set it to `infix 4`
* Add a few _.ormolu_ files (thanks to @hallettj for helping me get started), mostly for Autodocodec operators that don't have explicit fixity declarations

In general, I think these changes are quite reasonable. They mostly affect indentation.

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/6675
GitOrigin-RevId: cd47d87f1d089fb0bc9dcbbe7798dbceedcd7d83
2022-11-02 20:55:13 +00:00

314 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 HM
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) <- HM.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 (HM.toList objectRelations)
<> map arrayRelationToFromItem (HM.toList arrayRelations)
<> map arrayConnectionToFromItem (HM.toList arrayConnections)
<> map computedFieldToFromItem (HM.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 (HM.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)