mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 17:02:49 +03:00
Split up module Hasura.Backend.Postgres.Translate.Select
into sub-modules
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4334 GitOrigin-RevId: d083512f3c4e534a10e571eeab10308ad45cc7a0
This commit is contained in:
parent
4f9a08239d
commit
9a557ceeee
@ -506,6 +506,18 @@ library
|
|||||||
, Hasura.Backends.Postgres.Translate.Mutation
|
, Hasura.Backends.Postgres.Translate.Mutation
|
||||||
, Hasura.Backends.Postgres.Translate.Returning
|
, Hasura.Backends.Postgres.Translate.Returning
|
||||||
, Hasura.Backends.Postgres.Translate.Select
|
, Hasura.Backends.Postgres.Translate.Select
|
||||||
|
, Hasura.Backends.Postgres.Translate.Select.Aggregate
|
||||||
|
, Hasura.Backends.Postgres.Translate.Select.Aliases
|
||||||
|
, Hasura.Backends.Postgres.Translate.Select.AnnotatedFieldJSON
|
||||||
|
, Hasura.Backends.Postgres.Translate.Select.Aux1
|
||||||
|
, Hasura.Backends.Postgres.Translate.Select.Connection
|
||||||
|
, Hasura.Backends.Postgres.Translate.Select.Extractor
|
||||||
|
, Hasura.Backends.Postgres.Translate.Select.GenerateSelect
|
||||||
|
, Hasura.Backends.Postgres.Translate.Select.JoinTree
|
||||||
|
, Hasura.Backends.Postgres.Translate.Select.OrderBy
|
||||||
|
, Hasura.Backends.Postgres.Translate.Select.Process
|
||||||
|
, Hasura.Backends.Postgres.Translate.Select.Query
|
||||||
|
, Hasura.Backends.Postgres.Translate.Select.Streaming
|
||||||
, Hasura.Backends.Postgres.Translate.Types
|
, Hasura.Backends.Postgres.Translate.Types
|
||||||
, Hasura.Backends.Postgres.Translate.Update
|
, Hasura.Backends.Postgres.Translate.Update
|
||||||
, Hasura.Backends.Postgres.Types.BoolExp
|
, Hasura.Backends.Postgres.Types.BoolExp
|
||||||
|
@ -41,7 +41,7 @@ import Hasura.Backends.Postgres.Translate.Select (PostgresAnnotatedFieldJSON)
|
|||||||
import Hasura.Backends.Postgres.Translate.Select qualified as DS
|
import Hasura.Backends.Postgres.Translate.Select qualified as DS
|
||||||
import Hasura.Backends.Postgres.Types.Update
|
import Hasura.Backends.Postgres.Types.Update
|
||||||
import Hasura.Base.Error (QErr)
|
import Hasura.Base.Error (QErr)
|
||||||
import Hasura.EncJSON (EncJSON, encJFromJValue)
|
import Hasura.EncJSON (EncJSON, encJFromBS, encJFromJValue)
|
||||||
import Hasura.GraphQL.Execute.Backend
|
import Hasura.GraphQL.Execute.Backend
|
||||||
( BackendExecute (..),
|
( BackendExecute (..),
|
||||||
DBStepInfo (..),
|
DBStepInfo (..),
|
||||||
@ -393,7 +393,18 @@ mkCurPlanTx userInfo ps@(PreparedSql q prepMap) =
|
|||||||
-- WARNING: this quietly assumes the intmap keys are contiguous
|
-- WARNING: this quietly assumes the intmap keys are contiguous
|
||||||
prepArgs = fst <$> IntMap.elems args
|
prepArgs = fst <$> IntMap.elems args
|
||||||
in (,Just ps) $
|
in (,Just ps) $
|
||||||
Tracing.trace "Postgres" $ liftTx $ DS.asSingleRowJsonResp q prepArgs
|
Tracing.trace "Postgres" $ liftTx $ asSingleRowJsonResp q prepArgs
|
||||||
|
|
||||||
|
-- | This function is generally used on the result of 'selectQuerySQL',
|
||||||
|
-- 'selectAggregateQuerySQL' or 'connectionSelectSQL' to run said query and get
|
||||||
|
-- back the resulting JSON.
|
||||||
|
asSingleRowJsonResp ::
|
||||||
|
Q.Query ->
|
||||||
|
[Q.PrepArg] ->
|
||||||
|
Q.TxE QErr EncJSON
|
||||||
|
asSingleRowJsonResp query args =
|
||||||
|
encJFromBS . runIdentity . Q.getRow
|
||||||
|
<$> Q.rawQE dmlTxErrorHandler query args True
|
||||||
|
|
||||||
-- convert a query from an intermediate representation to... another
|
-- convert a query from an intermediate representation to... another
|
||||||
irToRootFieldPlan ::
|
irToRootFieldPlan ::
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,68 @@
|
|||||||
|
-- | This module defines the top-level translation functions pertaining to
|
||||||
|
-- queries that use aggregation (i.e., /not/ so-called "simple" selects) into
|
||||||
|
-- Postgres AST.
|
||||||
|
module Hasura.Backends.Postgres.Translate.Select.Aggregate
|
||||||
|
( mkAggregateSelect,
|
||||||
|
selectAggregateQuerySQL,
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Control.Monad.Writer.Strict (runWriter)
|
||||||
|
import Database.PG.Query (Query, fromBuilder)
|
||||||
|
import Hasura.Backends.Postgres.SQL.DML (BoolExp (BELit), Select)
|
||||||
|
import Hasura.Backends.Postgres.SQL.IdentifierUniqueness
|
||||||
|
( prefixNumToAliases,
|
||||||
|
)
|
||||||
|
import Hasura.Backends.Postgres.SQL.Types
|
||||||
|
( IsIdentifier (toIdentifier),
|
||||||
|
)
|
||||||
|
import Hasura.Backends.Postgres.Translate.Select.AnnotatedFieldJSON
|
||||||
|
import Hasura.Backends.Postgres.Translate.Select.GenerateSelect (generateSQLSelectFromArrayNode)
|
||||||
|
import Hasura.Backends.Postgres.Translate.Select.Process (processAnnAggregateSelect)
|
||||||
|
import Hasura.Backends.Postgres.Translate.Types
|
||||||
|
( MultiRowSelectNode (MultiRowSelectNode),
|
||||||
|
SelectNode (SelectNode),
|
||||||
|
SourcePrefixes (SourcePrefixes),
|
||||||
|
)
|
||||||
|
import Hasura.Prelude
|
||||||
|
import Hasura.RQL.IR.Select
|
||||||
|
( AnnAggregateSelect,
|
||||||
|
AnnSelectG (_asnStrfyNum),
|
||||||
|
)
|
||||||
|
import Hasura.RQL.Types.Backend (Backend)
|
||||||
|
import Hasura.RQL.Types.Common (FieldName (FieldName))
|
||||||
|
import Hasura.SQL.Backend (BackendType (Postgres))
|
||||||
|
import Hasura.SQL.Types (ToSQL (toSQL))
|
||||||
|
|
||||||
|
-- | Translates IR to Postgres queries for aggregated SELECTs.
|
||||||
|
--
|
||||||
|
-- See 'mkAggregateSelect' for the Postgres AST.
|
||||||
|
selectAggregateQuerySQL ::
|
||||||
|
forall pgKind.
|
||||||
|
(Backend ('Postgres pgKind), PostgresAnnotatedFieldJSON pgKind) =>
|
||||||
|
AnnAggregateSelect ('Postgres pgKind) ->
|
||||||
|
Query
|
||||||
|
selectAggregateQuerySQL =
|
||||||
|
fromBuilder . toSQL . mkAggregateSelect
|
||||||
|
|
||||||
|
mkAggregateSelect ::
|
||||||
|
forall pgKind.
|
||||||
|
( Backend ('Postgres pgKind),
|
||||||
|
PostgresAnnotatedFieldJSON pgKind
|
||||||
|
) =>
|
||||||
|
AnnAggregateSelect ('Postgres pgKind) ->
|
||||||
|
Select
|
||||||
|
mkAggregateSelect annAggSel =
|
||||||
|
let ((selectSource, nodeExtractors, topExtractor), joinTree) =
|
||||||
|
runWriter $
|
||||||
|
flip runReaderT strfyNum $
|
||||||
|
processAnnAggregateSelect sourcePrefixes rootFieldName annAggSel
|
||||||
|
selectNode = SelectNode nodeExtractors joinTree
|
||||||
|
arrayNode = MultiRowSelectNode [topExtractor] selectNode
|
||||||
|
in prefixNumToAliases $
|
||||||
|
generateSQLSelectFromArrayNode selectSource arrayNode $ BELit True
|
||||||
|
where
|
||||||
|
strfyNum = _asnStrfyNum annAggSel
|
||||||
|
rootFieldName = FieldName "root"
|
||||||
|
rootIdentifier = toIdentifier rootFieldName
|
||||||
|
sourcePrefixes = SourcePrefixes rootIdentifier rootIdentifier
|
@ -0,0 +1,107 @@
|
|||||||
|
{-# OPTIONS_GHC -Wno-missing-export-lists #-}
|
||||||
|
|
||||||
|
-- | Stuff gutted from Translate.Select
|
||||||
|
module Hasura.Backends.Postgres.Translate.Select.Aliases where
|
||||||
|
|
||||||
|
import Control.Monad.Writer.Strict
|
||||||
|
import Data.HashMap.Strict qualified as HM
|
||||||
|
import Data.Text qualified as T
|
||||||
|
import Data.Text.Extended
|
||||||
|
import Hasura.Backends.Postgres.SQL.DML qualified as S
|
||||||
|
import Hasura.Backends.Postgres.SQL.Types
|
||||||
|
import Hasura.Backends.Postgres.Translate.Types
|
||||||
|
import Hasura.Prelude
|
||||||
|
import Hasura.RQL.IR.Select
|
||||||
|
import Hasura.RQL.Types
|
||||||
|
|
||||||
|
-- | Generate alias for order by extractors
|
||||||
|
mkAnnOrderByAlias ::
|
||||||
|
Identifier -> FieldName -> SimilarArrayFields -> AnnotatedOrderByElement ('Postgres pgKind) v -> S.Alias
|
||||||
|
mkAnnOrderByAlias pfx parAls similarFields = \case
|
||||||
|
AOCColumn pgColumnInfo ->
|
||||||
|
let pgColumn = ciColumn pgColumnInfo
|
||||||
|
obColAls = mkBaseTableColumnAlias pfx pgColumn
|
||||||
|
in S.Alias obColAls
|
||||||
|
-- "pfx.or.relname"."pfx.ob.or.relname.rest" AS "pfx.ob.or.relname.rest"
|
||||||
|
AOCObjectRelation relInfo _ rest ->
|
||||||
|
let rn = riName relInfo
|
||||||
|
relPfx = mkObjectRelationTableAlias pfx rn
|
||||||
|
ordByFldName = mkOrderByFieldName rn
|
||||||
|
nesAls = mkAnnOrderByAlias relPfx ordByFldName mempty rest
|
||||||
|
in nesAls
|
||||||
|
AOCArrayAggregation relInfo _ aggOrderBy ->
|
||||||
|
let rn = riName relInfo
|
||||||
|
arrPfx =
|
||||||
|
mkArrayRelationSourcePrefix pfx parAls similarFields $
|
||||||
|
mkOrderByFieldName rn
|
||||||
|
obAls = arrPfx <> Identifier "." <> toIdentifier (mkAggregateOrderByAlias aggOrderBy)
|
||||||
|
in S.Alias obAls
|
||||||
|
AOCComputedField cfOrderBy ->
|
||||||
|
let fieldName = fromComputedField $ _cfobName cfOrderBy
|
||||||
|
in case _cfobOrderByElement cfOrderBy of
|
||||||
|
CFOBEScalar _ -> S.Alias $ mkComputedFieldTableAlias pfx fieldName
|
||||||
|
CFOBETableAggregation _ _ aggOrderBy ->
|
||||||
|
let cfPfx = mkComputedFieldTableAlias pfx fieldName
|
||||||
|
obAls = cfPfx <> Identifier "." <> toIdentifier (mkAggregateOrderByAlias aggOrderBy)
|
||||||
|
in S.Alias obAls
|
||||||
|
|
||||||
|
-- array relationships are not grouped, so have to be prefixed by
|
||||||
|
-- parent's alias
|
||||||
|
mkUniqArrayRelationAlias :: FieldName -> [FieldName] -> Identifier
|
||||||
|
mkUniqArrayRelationAlias parAls flds =
|
||||||
|
let sortedFields = sort flds
|
||||||
|
in Identifier $
|
||||||
|
getFieldNameTxt parAls <> "."
|
||||||
|
<> T.intercalate "." (map getFieldNameTxt sortedFields)
|
||||||
|
|
||||||
|
mkArrayRelationTableAlias :: Identifier -> FieldName -> [FieldName] -> Identifier
|
||||||
|
mkArrayRelationTableAlias pfx parAls flds =
|
||||||
|
pfx <> Identifier ".ar." <> uniqArrRelAls
|
||||||
|
where
|
||||||
|
uniqArrRelAls = mkUniqArrayRelationAlias parAls flds
|
||||||
|
|
||||||
|
mkObjectRelationTableAlias :: Identifier -> RelName -> Identifier
|
||||||
|
mkObjectRelationTableAlias pfx relName =
|
||||||
|
pfx <> Identifier ".or." <> toIdentifier relName
|
||||||
|
|
||||||
|
mkComputedFieldTableAlias :: Identifier -> FieldName -> Identifier
|
||||||
|
mkComputedFieldTableAlias pfx fldAls =
|
||||||
|
pfx <> Identifier ".cf." <> toIdentifier fldAls
|
||||||
|
|
||||||
|
mkBaseTableAlias :: Identifier -> Identifier
|
||||||
|
mkBaseTableAlias pfx =
|
||||||
|
pfx <> Identifier ".base"
|
||||||
|
|
||||||
|
mkBaseTableColumnAlias :: Identifier -> PGCol -> Identifier
|
||||||
|
mkBaseTableColumnAlias pfx pgColumn =
|
||||||
|
pfx <> Identifier ".pg." <> toIdentifier pgColumn
|
||||||
|
|
||||||
|
mkAggregateOrderByAlias :: AnnotatedAggregateOrderBy ('Postgres pgKind) -> S.Alias
|
||||||
|
mkAggregateOrderByAlias =
|
||||||
|
(S.Alias . Identifier) . \case
|
||||||
|
AAOCount -> "count"
|
||||||
|
AAOOp opText col -> opText <> "." <> getPGColTxt (ciColumn col)
|
||||||
|
|
||||||
|
mkOrderByFieldName :: ToTxt a => a -> FieldName
|
||||||
|
mkOrderByFieldName name =
|
||||||
|
FieldName $ toTxt name <> "." <> "order_by"
|
||||||
|
|
||||||
|
mkArrayRelationSourcePrefix ::
|
||||||
|
Identifier ->
|
||||||
|
FieldName ->
|
||||||
|
HM.HashMap FieldName [FieldName] ->
|
||||||
|
FieldName ->
|
||||||
|
Identifier
|
||||||
|
mkArrayRelationSourcePrefix parentSourcePrefix parentFieldName similarFieldsMap fieldName =
|
||||||
|
mkArrayRelationTableAlias parentSourcePrefix parentFieldName $
|
||||||
|
HM.lookupDefault [fieldName] fieldName similarFieldsMap
|
||||||
|
|
||||||
|
mkArrayRelationAlias ::
|
||||||
|
FieldName ->
|
||||||
|
HM.HashMap FieldName [FieldName] ->
|
||||||
|
FieldName ->
|
||||||
|
S.Alias
|
||||||
|
mkArrayRelationAlias parentFieldName similarFieldsMap fieldName =
|
||||||
|
S.Alias $
|
||||||
|
mkUniqArrayRelationAlias parentFieldName $
|
||||||
|
HM.lookupDefault [fieldName] fieldName similarFieldsMap
|
@ -0,0 +1,49 @@
|
|||||||
|
-- | This module defines the type class 'PostgresAnnotatedFieldJSON'.
|
||||||
|
module Hasura.Backends.Postgres.Translate.Select.AnnotatedFieldJSON
|
||||||
|
( PostgresAnnotatedFieldJSON (..),
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Data.Text qualified as T
|
||||||
|
import Hasura.Backends.Postgres.SQL.DML qualified as S
|
||||||
|
import Hasura.Backends.Postgres.Translate.Select.Aux1 (withJsonBuildObj)
|
||||||
|
import Hasura.Prelude
|
||||||
|
import Hasura.RQL.Types.Common (FieldName (getFieldNameTxt))
|
||||||
|
import Hasura.SQL.Backend (PostgresKind (..))
|
||||||
|
|
||||||
|
class PostgresAnnotatedFieldJSON (pgKind :: PostgresKind) where
|
||||||
|
annRowToJson :: FieldName -> [(FieldName, S.SQLExp)] -> (S.Alias, S.SQLExp)
|
||||||
|
|
||||||
|
instance PostgresAnnotatedFieldJSON 'Vanilla where
|
||||||
|
annRowToJson fieldAlias fieldExps =
|
||||||
|
-- postgres ignores anything beyond 63 chars for an iden
|
||||||
|
-- in this case, we'll need to use json_build_object function
|
||||||
|
-- json_build_object is slower than row_to_json hence it is only
|
||||||
|
-- used when needed
|
||||||
|
if any ((> 63) . T.length . getFieldNameTxt . fst) fieldExps
|
||||||
|
then withJsonBuildObj fieldAlias $ concatMap toJsonBuildObjectExps fieldExps
|
||||||
|
else withRowToJSON fieldAlias $ map toRowToJsonExtr fieldExps
|
||||||
|
where
|
||||||
|
toJsonBuildObjectExps (fieldName, fieldExp) =
|
||||||
|
[S.SELit $ getFieldNameTxt fieldName, fieldExp]
|
||||||
|
|
||||||
|
toRowToJsonExtr (fieldName, fieldExp) =
|
||||||
|
S.Extractor fieldExp $ Just $ S.toAlias fieldName
|
||||||
|
|
||||||
|
-- uses row_to_json to build a json object
|
||||||
|
withRowToJSON ::
|
||||||
|
FieldName -> [S.Extractor] -> (S.Alias, S.SQLExp)
|
||||||
|
withRowToJSON parAls extrs =
|
||||||
|
(S.toAlias parAls, jsonRow)
|
||||||
|
where
|
||||||
|
jsonRow = S.applyRowToJson extrs
|
||||||
|
|
||||||
|
instance PostgresAnnotatedFieldJSON 'Citus where
|
||||||
|
annRowToJson fieldAlias fieldExps =
|
||||||
|
-- Due to the restrictions Citus imposes on joins between tables of various
|
||||||
|
-- distribution types we cannot use row_to_json and have to only rely on
|
||||||
|
-- json_build_object.
|
||||||
|
withJsonBuildObj fieldAlias $ concatMap toJsonBuildObjectExps fieldExps
|
||||||
|
where
|
||||||
|
toJsonBuildObjectExps (fieldName, fieldExp) =
|
||||||
|
[S.SELit $ getFieldNameTxt fieldName, fieldExp]
|
103
server/src-lib/Hasura/Backends/Postgres/Translate/Select/Aux1.hs
Normal file
103
server/src-lib/Hasura/Backends/Postgres/Translate/Select/Aux1.hs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
{-# OPTIONS_GHC -Wno-missing-export-lists #-}
|
||||||
|
|
||||||
|
-- | Stuff gutted from Translate.Select.
|
||||||
|
module Hasura.Backends.Postgres.Translate.Select.Aux1 where
|
||||||
|
|
||||||
|
import Hasura.Backends.Postgres.SQL.DML qualified as S
|
||||||
|
import Hasura.Backends.Postgres.SQL.Types (Identifier (..), QualifiedFunction, qualifiedObjectToText, toIdentifier)
|
||||||
|
import Hasura.Backends.Postgres.Translate.Select.Aliases
|
||||||
|
import Hasura.Prelude
|
||||||
|
import Hasura.RQL.IR
|
||||||
|
import Hasura.RQL.Types (BackendType (Postgres))
|
||||||
|
import Hasura.RQL.Types.Common (FieldName)
|
||||||
|
|
||||||
|
-- | First element extractor expression from given record set
|
||||||
|
-- For example:- To get first "id" column from given row set,
|
||||||
|
-- the function generates the SQL expression AS `(array_agg("id"))[1]`
|
||||||
|
mkFirstElementExp :: S.SQLExp -> S.SQLExp
|
||||||
|
mkFirstElementExp expIdentifier =
|
||||||
|
-- For Example
|
||||||
|
S.SEArrayIndex (S.SEFnApp "array_agg" [expIdentifier] Nothing) (S.intToSQLExp 1)
|
||||||
|
|
||||||
|
-- | Last element extractor expression from given record set.
|
||||||
|
-- For example:- To get first "id" column from given row set,
|
||||||
|
-- the function generates the SQL expression AS `(array_agg("id"))[array_length(array_agg("id"), 1)]`
|
||||||
|
mkLastElementExp :: S.SQLExp -> S.SQLExp
|
||||||
|
mkLastElementExp expIdentifier =
|
||||||
|
let arrayExp = S.SEFnApp "array_agg" [expIdentifier] Nothing
|
||||||
|
in S.SEArrayIndex arrayExp $
|
||||||
|
S.SEFnApp "array_length" [arrayExp, S.intToSQLExp 1] Nothing
|
||||||
|
|
||||||
|
cursorIdentifier :: Identifier
|
||||||
|
cursorIdentifier = Identifier "__cursor"
|
||||||
|
|
||||||
|
startCursorIdentifier :: Identifier
|
||||||
|
startCursorIdentifier = Identifier "__start_cursor"
|
||||||
|
|
||||||
|
endCursorIdentifier :: Identifier
|
||||||
|
endCursorIdentifier = Identifier "__end_cursor"
|
||||||
|
|
||||||
|
hasPreviousPageIdentifier :: Identifier
|
||||||
|
hasPreviousPageIdentifier = Identifier "__has_previous_page"
|
||||||
|
|
||||||
|
hasNextPageIdentifier :: Identifier
|
||||||
|
hasNextPageIdentifier = Identifier "__has_next_page"
|
||||||
|
|
||||||
|
pageInfoSelectAliasIdentifier :: Identifier
|
||||||
|
pageInfoSelectAliasIdentifier = Identifier "__page_info"
|
||||||
|
|
||||||
|
cursorsSelectAliasIdentifier :: Identifier
|
||||||
|
cursorsSelectAliasIdentifier = Identifier "__cursors_select"
|
||||||
|
|
||||||
|
encodeBase64 :: S.SQLExp -> S.SQLExp
|
||||||
|
encodeBase64 =
|
||||||
|
removeNewline . bytesToBase64Text . convertToBytes
|
||||||
|
where
|
||||||
|
convertToBytes e =
|
||||||
|
S.SEFnApp "convert_to" [e, S.SELit "UTF8"] Nothing
|
||||||
|
bytesToBase64Text e =
|
||||||
|
S.SEFnApp "encode" [e, S.SELit "base64"] Nothing
|
||||||
|
removeNewline e =
|
||||||
|
S.SEFnApp "regexp_replace" [e, S.SELit "\\n", S.SELit "", S.SELit "g"] Nothing
|
||||||
|
|
||||||
|
fromTableRowArgs ::
|
||||||
|
Identifier -> FunctionArgsExpTableRow S.SQLExp -> S.FunctionArgs
|
||||||
|
fromTableRowArgs prefix = toFunctionArgs . fmap toSQLExp
|
||||||
|
where
|
||||||
|
toFunctionArgs (FunctionArgsExp positional named) =
|
||||||
|
S.FunctionArgs positional named
|
||||||
|
toSQLExp =
|
||||||
|
onArgumentExp
|
||||||
|
(S.SERowIdentifier alias)
|
||||||
|
(S.mkQIdenExp alias . Identifier)
|
||||||
|
alias = mkBaseTableAlias prefix
|
||||||
|
|
||||||
|
selectFromToFromItem :: Identifier -> SelectFrom ('Postgres pgKind) -> S.FromItem
|
||||||
|
selectFromToFromItem pfx = \case
|
||||||
|
FromTable tn -> S.FISimple tn Nothing
|
||||||
|
FromIdentifier i -> S.FIIdentifier $ toIdentifier i
|
||||||
|
FromFunction qf args defListM ->
|
||||||
|
S.FIFunc $
|
||||||
|
S.FunctionExp qf (fromTableRowArgs pfx args) $
|
||||||
|
Just $ S.mkFunctionAlias (functionToIdentifier qf) defListM
|
||||||
|
|
||||||
|
-- | Converts a function name to an 'Identifier'.
|
||||||
|
--
|
||||||
|
-- If the schema name is public, it will just use its name, otherwise it will
|
||||||
|
-- prefix it by the schema name.
|
||||||
|
functionToIdentifier :: QualifiedFunction -> Identifier
|
||||||
|
functionToIdentifier = Identifier . qualifiedObjectToText
|
||||||
|
|
||||||
|
-- uses json_build_object to build a json object
|
||||||
|
withJsonBuildObj ::
|
||||||
|
FieldName -> [S.SQLExp] -> (S.Alias, S.SQLExp)
|
||||||
|
withJsonBuildObj parAls exps =
|
||||||
|
(S.toAlias parAls, jsonRow)
|
||||||
|
where
|
||||||
|
jsonRow = S.applyJsonBuildObj exps
|
||||||
|
|
||||||
|
-- | Forces aggregation
|
||||||
|
withForceAggregation :: S.TypeAnn -> S.SQLExp -> S.SQLExp
|
||||||
|
withForceAggregation tyAnn e =
|
||||||
|
-- bool_or to force aggregation
|
||||||
|
S.SEFnApp "coalesce" [e, S.SETyAnn (S.SEUnsafe "bool_or('true')") tyAnn] Nothing
|
@ -0,0 +1,67 @@
|
|||||||
|
-- | This module defines the top-level translation functions pertaining to
|
||||||
|
-- translating Connection (i.e. Relay) queries into Postgres AST.
|
||||||
|
module Hasura.Backends.Postgres.Translate.Select.Connection
|
||||||
|
( connectionSelectQuerySQL,
|
||||||
|
mkConnectionSelect,
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Control.Monad.Writer (runWriter)
|
||||||
|
import Database.PG.Query (Query, fromBuilder)
|
||||||
|
import Hasura.Backends.Postgres.SQL.DML qualified as S
|
||||||
|
import Hasura.Backends.Postgres.SQL.IdentifierUniqueness (prefixNumToAliasesSelectWith)
|
||||||
|
import Hasura.Backends.Postgres.SQL.Types
|
||||||
|
import Hasura.Backends.Postgres.Translate.Select.AnnotatedFieldJSON
|
||||||
|
import Hasura.Backends.Postgres.Translate.Select.GenerateSelect (connectionToSelectWith)
|
||||||
|
import Hasura.Backends.Postgres.Translate.Select.Process (processConnectionSelect)
|
||||||
|
import Hasura.Backends.Postgres.Translate.Types
|
||||||
|
import Hasura.Prelude
|
||||||
|
import Hasura.RQL.IR.Select
|
||||||
|
( AnnSelectG (_asnStrfyNum),
|
||||||
|
ConnectionSelect (_csSelect),
|
||||||
|
)
|
||||||
|
import Hasura.RQL.Types.Backend (Backend)
|
||||||
|
import Hasura.RQL.Types.Common (FieldName (FieldName))
|
||||||
|
import Hasura.SQL.Backend (BackendType (Postgres))
|
||||||
|
import Hasura.SQL.Types (ToSQL (toSQL))
|
||||||
|
|
||||||
|
-- | Translates IR to Postgres queries for "connection" queries (used for Relay).
|
||||||
|
--
|
||||||
|
-- See 'mkConnectionSelect' for the Postgres AST.
|
||||||
|
connectionSelectQuerySQL ::
|
||||||
|
forall pgKind.
|
||||||
|
( Backend ('Postgres pgKind),
|
||||||
|
PostgresAnnotatedFieldJSON pgKind
|
||||||
|
) =>
|
||||||
|
ConnectionSelect ('Postgres pgKind) Void S.SQLExp ->
|
||||||
|
Query
|
||||||
|
connectionSelectQuerySQL =
|
||||||
|
fromBuilder . toSQL . mkConnectionSelect
|
||||||
|
|
||||||
|
mkConnectionSelect ::
|
||||||
|
forall pgKind.
|
||||||
|
( Backend ('Postgres pgKind),
|
||||||
|
PostgresAnnotatedFieldJSON pgKind
|
||||||
|
) =>
|
||||||
|
ConnectionSelect ('Postgres pgKind) Void S.SQLExp ->
|
||||||
|
S.SelectWithG S.Select
|
||||||
|
mkConnectionSelect connectionSelect =
|
||||||
|
let ((connectionSource, topExtractor, nodeExtractors), joinTree) =
|
||||||
|
runWriter $
|
||||||
|
flip runReaderT strfyNum $
|
||||||
|
processConnectionSelect
|
||||||
|
sourcePrefixes
|
||||||
|
rootFieldName
|
||||||
|
(S.Alias rootIdentifier)
|
||||||
|
mempty
|
||||||
|
connectionSelect
|
||||||
|
selectNode =
|
||||||
|
MultiRowSelectNode [topExtractor] $
|
||||||
|
SelectNode nodeExtractors joinTree
|
||||||
|
in prefixNumToAliasesSelectWith $
|
||||||
|
connectionToSelectWith (S.Alias rootIdentifier) connectionSource selectNode
|
||||||
|
where
|
||||||
|
strfyNum = _asnStrfyNum $ _csSelect connectionSelect
|
||||||
|
rootFieldName = FieldName "root"
|
||||||
|
rootIdentifier = toIdentifier rootFieldName
|
||||||
|
sourcePrefixes = SourcePrefixes rootIdentifier rootIdentifier
|
@ -0,0 +1,146 @@
|
|||||||
|
{-# OPTIONS_GHC -Wno-missing-export-lists #-}
|
||||||
|
|
||||||
|
-- | Stuff gutted from Translate.Select
|
||||||
|
module Hasura.Backends.Postgres.Translate.Select.Extractor where
|
||||||
|
|
||||||
|
import Control.Monad.Writer.Strict
|
||||||
|
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.Translate.Select.Aliases
|
||||||
|
import Hasura.Backends.Postgres.Translate.Types (PermissionLimitSubQuery (..))
|
||||||
|
import Hasura.Prelude
|
||||||
|
import Hasura.RQL.IR.Select
|
||||||
|
import Hasura.RQL.Types
|
||||||
|
|
||||||
|
aggregateFieldsToExtractorExps ::
|
||||||
|
Identifier -> AggregateFields ('Postgres pgKind) -> [(S.Alias, S.SQLExp)]
|
||||||
|
aggregateFieldsToExtractorExps sourcePrefix aggregateFields =
|
||||||
|
flip concatMap aggregateFields $ \(_, field) ->
|
||||||
|
case field of
|
||||||
|
AFCount cty -> case cty of
|
||||||
|
S.CTStar -> []
|
||||||
|
S.CTSimple cols -> colsToExps cols
|
||||||
|
S.CTDistinct cols -> colsToExps cols
|
||||||
|
AFOp aggOp -> aggOpToExps aggOp
|
||||||
|
AFExp _ -> []
|
||||||
|
where
|
||||||
|
colsToExps = fmap mkColExp
|
||||||
|
|
||||||
|
aggOpToExps = mapMaybe colToMaybeExp . _aoFields
|
||||||
|
colToMaybeExp = \case
|
||||||
|
(_, CFCol col _) -> Just $ mkColExp col
|
||||||
|
_ -> Nothing
|
||||||
|
|
||||||
|
mkColExp c =
|
||||||
|
let qualCol = S.mkQIdenExp (mkBaseTableAlias sourcePrefix) (toIdentifier c)
|
||||||
|
colAls = toIdentifier c
|
||||||
|
in (S.Alias colAls, qualCol)
|
||||||
|
|
||||||
|
mkAggregateOrderByExtractorAndFields ::
|
||||||
|
forall pgKind.
|
||||||
|
Backend ('Postgres pgKind) =>
|
||||||
|
AnnotatedAggregateOrderBy ('Postgres pgKind) ->
|
||||||
|
(S.Extractor, AggregateFields ('Postgres pgKind))
|
||||||
|
mkAggregateOrderByExtractorAndFields annAggOrderBy =
|
||||||
|
case annAggOrderBy of
|
||||||
|
AAOCount ->
|
||||||
|
( S.Extractor S.countStar alias,
|
||||||
|
[(FieldName "count", AFCount S.CTStar)]
|
||||||
|
)
|
||||||
|
AAOOp opText pgColumnInfo ->
|
||||||
|
let pgColumn = ciColumn pgColumnInfo
|
||||||
|
pgType = ciType pgColumnInfo
|
||||||
|
in ( S.Extractor (S.SEFnApp opText [S.SEIdentifier $ toIdentifier pgColumn] Nothing) alias,
|
||||||
|
[ ( FieldName opText,
|
||||||
|
AFOp $
|
||||||
|
AggregateOp
|
||||||
|
opText
|
||||||
|
[ ( fromCol @('Postgres pgKind) pgColumn,
|
||||||
|
CFCol pgColumn pgType
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
where
|
||||||
|
alias = Just $ mkAggregateOrderByAlias annAggOrderBy
|
||||||
|
|
||||||
|
withJsonAggExtr ::
|
||||||
|
PermissionLimitSubQuery -> Maybe S.OrderByExp -> S.Alias -> S.SQLExp
|
||||||
|
withJsonAggExtr permLimitSubQuery ordBy alias =
|
||||||
|
-- if select has aggregations then use subquery to apply permission limit
|
||||||
|
case permLimitSubQuery of
|
||||||
|
PLSQRequired permLimit -> withPermLimit permLimit
|
||||||
|
PLSQNotRequired -> simpleJsonAgg
|
||||||
|
where
|
||||||
|
simpleJsonAgg = mkSimpleJsonAgg rowIdenExp ordBy
|
||||||
|
rowIdenExp = S.SEIdentifier $ S.getAlias alias
|
||||||
|
subSelAls = Identifier "sub_query"
|
||||||
|
unnestTable = Identifier "unnest_table"
|
||||||
|
|
||||||
|
mkSimpleJsonAgg rowExp ob =
|
||||||
|
let jsonAggExp = S.SEFnApp "json_agg" [rowExp] ob
|
||||||
|
in S.SEFnApp "coalesce" [jsonAggExp, S.SELit "[]"] Nothing
|
||||||
|
|
||||||
|
withPermLimit limit =
|
||||||
|
let subSelect = mkSubSelect limit
|
||||||
|
rowIdentifier = S.mkQIdenExp subSelAls alias
|
||||||
|
extr = S.Extractor (mkSimpleJsonAgg rowIdentifier newOrderBy) Nothing
|
||||||
|
fromExp =
|
||||||
|
S.FromExp $
|
||||||
|
pure $
|
||||||
|
S.mkSelFromItem subSelect $ S.Alias subSelAls
|
||||||
|
in S.SESelect $
|
||||||
|
S.mkSelect
|
||||||
|
{ S.selExtr = pure extr,
|
||||||
|
S.selFrom = Just fromExp
|
||||||
|
}
|
||||||
|
|
||||||
|
mkSubSelect limit =
|
||||||
|
let jsonRowExtr =
|
||||||
|
flip S.Extractor (Just alias) $
|
||||||
|
S.mkQIdenExp unnestTable alias
|
||||||
|
obExtrs = flip map newOBAliases $ \a ->
|
||||||
|
S.Extractor (S.mkQIdenExp unnestTable a) $ Just $ S.Alias a
|
||||||
|
in S.mkSelect
|
||||||
|
{ S.selExtr = jsonRowExtr : obExtrs,
|
||||||
|
S.selFrom = Just $ S.FromExp $ pure unnestFromItem,
|
||||||
|
S.selLimit = Just $ S.LimitExp $ S.intToSQLExp limit,
|
||||||
|
S.selOrderBy = newOrderBy
|
||||||
|
}
|
||||||
|
|
||||||
|
unnestFromItem =
|
||||||
|
let arrayAggItems = flip map (rowIdenExp : obCols) $
|
||||||
|
\s -> S.SEFnApp "array_agg" [s] Nothing
|
||||||
|
in S.FIUnnest arrayAggItems (S.Alias unnestTable) $
|
||||||
|
rowIdenExp : map S.SEIdentifier newOBAliases
|
||||||
|
|
||||||
|
newOrderBy = S.OrderByExp <$> NE.nonEmpty newOBItems
|
||||||
|
|
||||||
|
(newOBItems, obCols, newOBAliases) = maybe ([], [], []) transformOrderBy ordBy
|
||||||
|
transformOrderBy (S.OrderByExp l) = unzip3 $
|
||||||
|
flip map (zip (toList l) [1 ..]) $ \(obItem, i :: Int) ->
|
||||||
|
let iden = Identifier $ "ob_col_" <> tshow i
|
||||||
|
in ( obItem {S.oColumn = S.SEIdentifier iden},
|
||||||
|
S.oColumn obItem,
|
||||||
|
iden
|
||||||
|
)
|
||||||
|
|
||||||
|
asSingleRowExtr :: S.Alias -> S.SQLExp
|
||||||
|
asSingleRowExtr col =
|
||||||
|
S.SEFnApp "coalesce" [jsonAgg, S.SELit "null"] Nothing
|
||||||
|
where
|
||||||
|
jsonAgg =
|
||||||
|
S.SEOpApp
|
||||||
|
(S.SQLOp "->")
|
||||||
|
[ S.SEFnApp "json_agg" [S.SEIdentifier $ toIdentifier col] Nothing,
|
||||||
|
S.SEUnsafe "0"
|
||||||
|
]
|
||||||
|
|
||||||
|
asJsonAggExtr ::
|
||||||
|
JsonAggSelect -> S.Alias -> PermissionLimitSubQuery -> Maybe S.OrderByExp -> S.Extractor
|
||||||
|
asJsonAggExtr jsonAggSelect als permLimitSubQuery ordByExpM =
|
||||||
|
flip S.Extractor (Just als) $ case jsonAggSelect of
|
||||||
|
JASMultipleRows -> withJsonAggExtr permLimitSubQuery ordByExpM als
|
||||||
|
JASSingleObject -> asSingleRowExtr als
|
@ -0,0 +1,288 @@
|
|||||||
|
-- | 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.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.Translate.Select.Aliases (mkBaseTableAlias)
|
||||||
|
import Hasura.Backends.Postgres.Translate.Select.Aux1
|
||||||
|
( cursorIdentifier,
|
||||||
|
cursorsSelectAliasIdentifier,
|
||||||
|
endCursorIdentifier,
|
||||||
|
hasNextPageIdentifier,
|
||||||
|
hasPreviousPageIdentifier,
|
||||||
|
mkFirstElementExp,
|
||||||
|
mkLastElementExp,
|
||||||
|
pageInfoSelectAliasIdentifier,
|
||||||
|
startCursorIdentifier,
|
||||||
|
)
|
||||||
|
import Hasura.Backends.Postgres.Translate.Types
|
||||||
|
import Hasura.Prelude
|
||||||
|
import Hasura.RQL.IR.Select (ConnectionSlice (SliceFirst, SliceLast))
|
||||||
|
|
||||||
|
generateSQLSelect ::
|
||||||
|
-- | Pre join condition
|
||||||
|
S.BoolExp ->
|
||||||
|
SelectSource ->
|
||||||
|
SelectNode ->
|
||||||
|
S.Select
|
||||||
|
generateSQLSelect joinCondition selectSource selectNode =
|
||||||
|
S.mkSelect
|
||||||
|
{ S.selExtr = [S.Extractor e $ Just a | (a, e) <- HM.toList extractors],
|
||||||
|
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
|
||||||
|
}
|
||||||
|
baseSelectAlias = S.Alias $ mkBaseTableAlias sourcePrefix
|
||||||
|
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.Alias $ _ossPrefix objectSelectSource
|
||||||
|
source = objectSelectSourceToSelectSource objectSelectSource
|
||||||
|
select = generateSQLSelect (mkJoinCond baseSelectAlias colMapping) source node
|
||||||
|
in S.mkLateralFromItem select alias
|
||||||
|
|
||||||
|
arrayRelationToFromItem ::
|
||||||
|
(ArrayRelationSource, MultiRowSelectNode) -> S.FromItem
|
||||||
|
arrayRelationToFromItem (arrayRelationSource, arraySelectNode) =
|
||||||
|
let ArrayRelationSource _ colMapping source = arrayRelationSource
|
||||||
|
alias = S.Alias $ _ssPrefix source
|
||||||
|
select =
|
||||||
|
generateSQLSelectFromArrayNode source arraySelectNode $
|
||||||
|
mkJoinCond baseSelectAlias colMapping
|
||||||
|
in S.mkLateralFromItem select alias
|
||||||
|
|
||||||
|
arrayConnectionToFromItem ::
|
||||||
|
(ArrayConnectionSource, MultiRowSelectNode) -> S.FromItem
|
||||||
|
arrayConnectionToFromItem (arrayConnectionSource, arraySelectNode) =
|
||||||
|
let selectWith = connectionToSelectWith baseSelectAlias arrayConnectionSource arraySelectNode
|
||||||
|
alias = S.Alias $ _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.Alias $ _ssPrefix source
|
||||||
|
select =
|
||||||
|
S.mkSelect
|
||||||
|
{ S.selExtr = _mrsnTopExtractors node,
|
||||||
|
S.selFrom = Just $ S.FromExp [S.mkSelFromItem internalSelect alias]
|
||||||
|
}
|
||||||
|
in S.mkLateralFromItem select alias
|
||||||
|
|
||||||
|
generateSQLSelectFromArrayNode ::
|
||||||
|
SelectSource ->
|
||||||
|
MultiRowSelectNode ->
|
||||||
|
S.BoolExp ->
|
||||||
|
S.Select
|
||||||
|
generateSQLSelectFromArrayNode selectSource arraySelectNode joinCondition =
|
||||||
|
S.mkSelect
|
||||||
|
{ S.selExtr = topExtractors,
|
||||||
|
S.selFrom = Just $ S.FromExp [selectFrom]
|
||||||
|
}
|
||||||
|
where
|
||||||
|
MultiRowSelectNode topExtractors selectNode = arraySelectNode
|
||||||
|
selectFrom =
|
||||||
|
S.mkSelFromItem
|
||||||
|
(generateSQLSelect joinCondition selectSource selectNode)
|
||||||
|
$ S.Alias $ _ssPrefix selectSource
|
||||||
|
|
||||||
|
mkJoinCond :: S.Alias -> 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.Alias ->
|
||||||
|
ArrayConnectionSource ->
|
||||||
|
MultiRowSelectNode ->
|
||||||
|
S.SelectWithG S.Select
|
||||||
|
connectionToSelectWith baseSelectAlias 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
|
||||||
|
baseSelectIdentifier = Identifier "__base_select"
|
||||||
|
splitSelectIdentifier = Identifier "__split_select"
|
||||||
|
sliceSelectIdentifier = Identifier "__slice_select"
|
||||||
|
finalSelectIdentifier = Identifier "__final_select"
|
||||||
|
|
||||||
|
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 baseSelectAlias columnMapping
|
||||||
|
baseSelectFrom =
|
||||||
|
S.mkSelFromItem
|
||||||
|
(generateSQLSelect joinCond selectSource selectNode)
|
||||||
|
$ S.Alias $ _ssPrefix selectSource
|
||||||
|
select =
|
||||||
|
S.mkSelect
|
||||||
|
{ S.selExtr =
|
||||||
|
[ S.selectStar,
|
||||||
|
S.Extractor rowNumberExp $ Just $ S.Alias rowNumberIdentifier
|
||||||
|
],
|
||||||
|
S.selFrom = Just $ S.FromExp [baseSelectFrom]
|
||||||
|
}
|
||||||
|
in (S.Alias baseSelectIdentifier, 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 (S.Alias splitSelectIdentifier, 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 $ S.Alias sliceSelectIdentifier
|
||||||
|
in S.mkSelect
|
||||||
|
{ S.selExtr = [S.selectStar],
|
||||||
|
S.selFrom = Just $ S.FromExp [sliceLastSelectFrom],
|
||||||
|
S.selOrderBy = Just $ mkRowNumberOrderBy S.OTAsc
|
||||||
|
}
|
||||||
|
in (S.Alias sliceSelectIdentifier, select) : fromFinalSelect sliceSelectIdentifier
|
||||||
|
|
||||||
|
fromFinalSelect prevSelect =
|
||||||
|
let select = mkStarSelect prevSelect
|
||||||
|
in (S.Alias finalSelectIdentifier, select) : fromCursorSelection
|
||||||
|
|
||||||
|
fromCursorSelection =
|
||||||
|
let extrs =
|
||||||
|
[ S.Extractor startCursorExp $ Just $ S.Alias startCursorIdentifier,
|
||||||
|
S.Extractor endCursorExp $ Just $ S.Alias endCursorIdentifier,
|
||||||
|
S.Extractor startRowNumberExp $ Just $ S.Alias startRowNumberIdentifier,
|
||||||
|
S.Extractor endRowNumberExp $ Just $ S.Alias endRowNumberIdentifier
|
||||||
|
]
|
||||||
|
select =
|
||||||
|
S.mkSelect
|
||||||
|
{ S.selExtr = extrs,
|
||||||
|
S.selFrom = Just $ S.FromExp [S.FIIdentifier finalSelectIdentifier]
|
||||||
|
}
|
||||||
|
in (S.Alias cursorsSelectAliasIdentifier, 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.Alias hasPreviousPageIdentifier,
|
||||||
|
S.Extractor hasNextPage $ Just $ S.Alias hasNextPageIdentifier
|
||||||
|
]
|
||||||
|
}
|
||||||
|
in pure (S.Alias pageInfoSelectAliasIdentifier, select)
|
@ -0,0 +1,106 @@
|
|||||||
|
{-# OPTIONS_GHC -Wno-missing-export-lists #-}
|
||||||
|
|
||||||
|
-- | Stuff gutted from Translate.Select
|
||||||
|
module Hasura.Backends.Postgres.Translate.Select.JoinTree where
|
||||||
|
|
||||||
|
import Control.Monad.Writer.Strict
|
||||||
|
import Data.HashMap.Strict qualified as HM
|
||||||
|
import Hasura.Backends.Postgres.SQL.DML qualified as S
|
||||||
|
import Hasura.Backends.Postgres.Translate.Types
|
||||||
|
import Hasura.Prelude
|
||||||
|
|
||||||
|
-- | This is the lowest level function which deals with @MonadWriter JoinTree@, whose
|
||||||
|
-- purpose is to essentially create the selection tree across relationships.
|
||||||
|
--
|
||||||
|
-- Each type of relationship uses a different kind of update function; see
|
||||||
|
-- 'withWriteObjectRelation', 'withWriteArrayRelation', 'withWriteArrayConnection',
|
||||||
|
-- and 'withWriteComputedFieldTableSet'.
|
||||||
|
--
|
||||||
|
-- See the definition of 'JoinTree' for details before diving further
|
||||||
|
-- (particularly its components and Monoid instance).
|
||||||
|
withWriteJoinTree ::
|
||||||
|
(MonadWriter JoinTree m) =>
|
||||||
|
(JoinTree -> b -> JoinTree) ->
|
||||||
|
m (a, b) ->
|
||||||
|
m a
|
||||||
|
withWriteJoinTree joinTreeUpdater action =
|
||||||
|
pass $ do
|
||||||
|
(out, result) <- action
|
||||||
|
let fromJoinTree joinTree =
|
||||||
|
joinTreeUpdater joinTree result
|
||||||
|
pure (out, fromJoinTree)
|
||||||
|
|
||||||
|
withWriteObjectRelation ::
|
||||||
|
(MonadWriter JoinTree m) =>
|
||||||
|
m
|
||||||
|
( ObjectRelationSource,
|
||||||
|
HM.HashMap S.Alias S.SQLExp,
|
||||||
|
a
|
||||||
|
) ->
|
||||||
|
m a
|
||||||
|
withWriteObjectRelation action =
|
||||||
|
withWriteJoinTree updateJoinTree $ do
|
||||||
|
(source, nodeExtractors, out) <- action
|
||||||
|
pure (out, (source, nodeExtractors))
|
||||||
|
where
|
||||||
|
updateJoinTree joinTree (source, nodeExtractors) =
|
||||||
|
let selectNode = SelectNode nodeExtractors joinTree
|
||||||
|
in mempty {_jtObjectRelations = HM.singleton source selectNode}
|
||||||
|
|
||||||
|
withWriteArrayRelation ::
|
||||||
|
(MonadWriter JoinTree m) =>
|
||||||
|
m
|
||||||
|
( ArrayRelationSource,
|
||||||
|
S.Extractor,
|
||||||
|
HM.HashMap S.Alias S.SQLExp,
|
||||||
|
a
|
||||||
|
) ->
|
||||||
|
m a
|
||||||
|
withWriteArrayRelation action =
|
||||||
|
withWriteJoinTree updateJoinTree $ do
|
||||||
|
(source, topExtractor, nodeExtractors, out) <- action
|
||||||
|
pure (out, (source, topExtractor, nodeExtractors))
|
||||||
|
where
|
||||||
|
updateJoinTree joinTree (source, topExtractor, nodeExtractors) =
|
||||||
|
let arraySelectNode =
|
||||||
|
MultiRowSelectNode [topExtractor] $
|
||||||
|
SelectNode nodeExtractors joinTree
|
||||||
|
in mempty {_jtArrayRelations = HM.singleton source arraySelectNode}
|
||||||
|
|
||||||
|
withWriteArrayConnection ::
|
||||||
|
(MonadWriter JoinTree m) =>
|
||||||
|
m
|
||||||
|
( ArrayConnectionSource,
|
||||||
|
S.Extractor,
|
||||||
|
HM.HashMap S.Alias S.SQLExp,
|
||||||
|
a
|
||||||
|
) ->
|
||||||
|
m a
|
||||||
|
withWriteArrayConnection action =
|
||||||
|
withWriteJoinTree updateJoinTree $ do
|
||||||
|
(source, topExtractor, nodeExtractors, out) <- action
|
||||||
|
pure (out, (source, topExtractor, nodeExtractors))
|
||||||
|
where
|
||||||
|
updateJoinTree joinTree (source, topExtractor, nodeExtractors) =
|
||||||
|
let arraySelectNode =
|
||||||
|
MultiRowSelectNode [topExtractor] $
|
||||||
|
SelectNode nodeExtractors joinTree
|
||||||
|
in mempty {_jtArrayConnections = HM.singleton source arraySelectNode}
|
||||||
|
|
||||||
|
withWriteComputedFieldTableSet ::
|
||||||
|
(MonadWriter JoinTree m) =>
|
||||||
|
m
|
||||||
|
( ComputedFieldTableSetSource,
|
||||||
|
S.Extractor,
|
||||||
|
HM.HashMap S.Alias S.SQLExp,
|
||||||
|
a
|
||||||
|
) ->
|
||||||
|
m a
|
||||||
|
withWriteComputedFieldTableSet action =
|
||||||
|
withWriteJoinTree updateJoinTree $ do
|
||||||
|
(source, topExtractor, nodeExtractors, out) <- action
|
||||||
|
pure (out, (source, topExtractor, nodeExtractors))
|
||||||
|
where
|
||||||
|
updateJoinTree joinTree (source, topExtractor, nodeExtractors) =
|
||||||
|
let selectNode = MultiRowSelectNode [topExtractor] $ SelectNode nodeExtractors joinTree
|
||||||
|
in mempty {_jtComputedFieldTableSets = HM.singleton source selectNode}
|
@ -0,0 +1,274 @@
|
|||||||
|
-- | This module relates to the processing of Order-By clauses.
|
||||||
|
module Hasura.Backends.Postgres.Translate.Select.OrderBy (processOrderByItems) where
|
||||||
|
|
||||||
|
import Control.Lens ((^?))
|
||||||
|
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
|
||||||
|
( Identifier,
|
||||||
|
IsIdentifier (toIdentifier),
|
||||||
|
PGCol (..),
|
||||||
|
)
|
||||||
|
import Hasura.Backends.Postgres.Translate.BoolExp (toSQLBoolExp)
|
||||||
|
import Hasura.Backends.Postgres.Translate.Select.Aliases
|
||||||
|
( mkAggregateOrderByAlias,
|
||||||
|
mkAnnOrderByAlias,
|
||||||
|
mkArrayRelationAlias,
|
||||||
|
mkArrayRelationSourcePrefix,
|
||||||
|
mkBaseTableAlias,
|
||||||
|
mkBaseTableColumnAlias,
|
||||||
|
mkComputedFieldTableAlias,
|
||||||
|
mkObjectRelationTableAlias,
|
||||||
|
mkOrderByFieldName,
|
||||||
|
)
|
||||||
|
import Hasura.Backends.Postgres.Translate.Select.Aux1
|
||||||
|
( fromTableRowArgs,
|
||||||
|
functionToIdentifier,
|
||||||
|
selectFromToFromItem,
|
||||||
|
)
|
||||||
|
import Hasura.Backends.Postgres.Translate.Select.Extractor
|
||||||
|
( aggregateFieldsToExtractorExps,
|
||||||
|
mkAggregateOrderByExtractorAndFields,
|
||||||
|
)
|
||||||
|
import Hasura.Backends.Postgres.Translate.Select.JoinTree
|
||||||
|
( withWriteArrayRelation,
|
||||||
|
withWriteComputedFieldTableSet,
|
||||||
|
withWriteObjectRelation,
|
||||||
|
)
|
||||||
|
import Hasura.Backends.Postgres.Translate.Types
|
||||||
|
import Hasura.Prelude
|
||||||
|
import Hasura.RQL.IR.OrderBy
|
||||||
|
( OrderByItemG (OrderByItemG, obiColumn),
|
||||||
|
)
|
||||||
|
import Hasura.RQL.IR.Select
|
||||||
|
import Hasura.RQL.Types
|
||||||
|
|
||||||
|
{- Note [Optimizing queries using limit/offset]
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Refer to the issue https://github.com/hasura/graphql-engine/issues/5745
|
||||||
|
|
||||||
|
Before this change, limit/offset/distinct_on is applied at outer selection
|
||||||
|
node along with order by clause. This greatly reduces query performance if
|
||||||
|
our base selection table contains many rows and relationships are selected
|
||||||
|
which joins remote tables. We need to optimize application of limit wrt to
|
||||||
|
order by input.
|
||||||
|
|
||||||
|
If "Order by" is not present:
|
||||||
|
Apply limit/offset/distinct on at the base table selection
|
||||||
|
Else if "Order by" contains only columns:
|
||||||
|
Apply limit/offset/distinct_on at the base table selection along with order by
|
||||||
|
Otherwise:
|
||||||
|
Apply limit/offset/distinct_on at the node selection along with order by
|
||||||
|
-}
|
||||||
|
|
||||||
|
processOrderByItems ::
|
||||||
|
forall pgKind m.
|
||||||
|
( MonadReader StringifyNumbers m,
|
||||||
|
MonadWriter JoinTree m,
|
||||||
|
Backend ('Postgres pgKind)
|
||||||
|
) =>
|
||||||
|
Identifier ->
|
||||||
|
FieldName ->
|
||||||
|
SimilarArrayFields ->
|
||||||
|
Maybe (NE.NonEmpty PGCol) ->
|
||||||
|
Maybe (NE.NonEmpty (AnnotatedOrderByItem ('Postgres pgKind))) ->
|
||||||
|
m
|
||||||
|
( [(S.Alias, S.SQLExp)], -- Order by Extractors
|
||||||
|
SelectSorting,
|
||||||
|
Maybe S.SQLExp -- The cursor expression
|
||||||
|
)
|
||||||
|
processOrderByItems sourcePrefix' fieldAlias' similarArrayFields distOnCols = \case
|
||||||
|
Nothing -> pure ([], NoSorting $ applyDistinctOnAtBase <$> distOnCols, Nothing)
|
||||||
|
Just orderByItems -> do
|
||||||
|
orderByItemExps <- forM orderByItems processAnnOrderByItem
|
||||||
|
let (sorting, distinctOnExtractors) = generateSorting orderByItemExps
|
||||||
|
orderByExtractors = concat $ toList $ map snd . toList <$> orderByItemExps
|
||||||
|
cursor = mkCursorExp $ toList orderByItemExps
|
||||||
|
pure (orderByExtractors <> distinctOnExtractors, sorting, Just cursor)
|
||||||
|
where
|
||||||
|
processAnnOrderByItem ::
|
||||||
|
AnnotatedOrderByItem ('Postgres pgKind) ->
|
||||||
|
m (OrderByItemG ('Postgres pgKind) (AnnotatedOrderByElement ('Postgres pgKind) (SQLExpression ('Postgres pgKind)), (S.Alias, SQLExpression ('Postgres pgKind))))
|
||||||
|
processAnnOrderByItem orderByItem =
|
||||||
|
forM orderByItem $ \ordByCol ->
|
||||||
|
(ordByCol,)
|
||||||
|
<$> processAnnotatedOrderByElement sourcePrefix' fieldAlias' ordByCol
|
||||||
|
|
||||||
|
processAnnotatedOrderByElement ::
|
||||||
|
Identifier -> FieldName -> AnnotatedOrderByElement ('Postgres pgKind) S.SQLExp -> m (S.Alias, (SQLExpression ('Postgres pgKind)))
|
||||||
|
processAnnotatedOrderByElement sourcePrefix fieldAlias annObCol = do
|
||||||
|
let ordByAlias = mkAnnOrderByAlias sourcePrefix fieldAlias similarArrayFields annObCol
|
||||||
|
(ordByAlias,) <$> case annObCol of
|
||||||
|
AOCColumn pgColInfo ->
|
||||||
|
pure $
|
||||||
|
S.mkQIdenExp (mkBaseTableAlias sourcePrefix) $ toIdentifier $ ciColumn pgColInfo
|
||||||
|
AOCObjectRelation relInfo relFilter rest -> withWriteObjectRelation $ do
|
||||||
|
let RelInfo relName _ colMapping relTable _ _ = relInfo
|
||||||
|
relSourcePrefix = mkObjectRelationTableAlias sourcePrefix relName
|
||||||
|
fieldName = mkOrderByFieldName relName
|
||||||
|
(relOrderByAlias, relOrdByExp) <-
|
||||||
|
processAnnotatedOrderByElement relSourcePrefix fieldName rest
|
||||||
|
let selectSource =
|
||||||
|
ObjectSelectSource
|
||||||
|
relSourcePrefix
|
||||||
|
(S.FISimple relTable Nothing)
|
||||||
|
(toSQLBoolExp (S.QualTable relTable) relFilter)
|
||||||
|
relSource = ObjectRelationSource relName colMapping selectSource
|
||||||
|
pure
|
||||||
|
( relSource,
|
||||||
|
HM.singleton relOrderByAlias relOrdByExp,
|
||||||
|
S.mkQIdenExp relSourcePrefix relOrderByAlias
|
||||||
|
)
|
||||||
|
AOCArrayAggregation relInfo relFilter aggOrderBy -> withWriteArrayRelation $ do
|
||||||
|
let RelInfo relName _ colMapping relTable _ _ = relInfo
|
||||||
|
fieldName = mkOrderByFieldName relName
|
||||||
|
relSourcePrefix =
|
||||||
|
mkArrayRelationSourcePrefix
|
||||||
|
sourcePrefix
|
||||||
|
fieldAlias
|
||||||
|
similarArrayFields
|
||||||
|
fieldName
|
||||||
|
relAlias = mkArrayRelationAlias fieldAlias similarArrayFields fieldName
|
||||||
|
(topExtractor, fields) = mkAggregateOrderByExtractorAndFields aggOrderBy
|
||||||
|
selectSource =
|
||||||
|
SelectSource
|
||||||
|
relSourcePrefix
|
||||||
|
(S.FISimple relTable Nothing)
|
||||||
|
(toSQLBoolExp (S.QualTable relTable) relFilter)
|
||||||
|
noSortingAndSlicing
|
||||||
|
relSource = ArrayRelationSource relAlias colMapping selectSource
|
||||||
|
pure
|
||||||
|
( relSource,
|
||||||
|
topExtractor,
|
||||||
|
HM.fromList $ aggregateFieldsToExtractorExps relSourcePrefix fields,
|
||||||
|
S.mkQIdenExp relSourcePrefix (mkAggregateOrderByAlias aggOrderBy)
|
||||||
|
)
|
||||||
|
AOCComputedField ComputedFieldOrderBy {..} ->
|
||||||
|
case _cfobOrderByElement of
|
||||||
|
CFOBEScalar _ -> do
|
||||||
|
let functionArgs = fromTableRowArgs sourcePrefix _cfobFunctionArgsExp
|
||||||
|
functionExp = S.FunctionExp _cfobFunction functionArgs Nothing
|
||||||
|
pure $ S.SEFunction functionExp
|
||||||
|
CFOBETableAggregation _ tableFilter aggOrderBy -> withWriteComputedFieldTableSet $ do
|
||||||
|
let fieldName = mkOrderByFieldName _cfobName
|
||||||
|
computedFieldSourcePrefix = mkComputedFieldTableAlias sourcePrefix fieldName
|
||||||
|
(topExtractor, fields) = mkAggregateOrderByExtractorAndFields aggOrderBy
|
||||||
|
fromItem =
|
||||||
|
selectFromToFromItem sourcePrefix $
|
||||||
|
FromFunction _cfobFunction _cfobFunctionArgsExp Nothing
|
||||||
|
functionQual = S.QualifiedIdentifier (functionToIdentifier _cfobFunction) Nothing
|
||||||
|
selectSource =
|
||||||
|
SelectSource
|
||||||
|
computedFieldSourcePrefix
|
||||||
|
fromItem
|
||||||
|
(toSQLBoolExp functionQual tableFilter)
|
||||||
|
noSortingAndSlicing
|
||||||
|
source = ComputedFieldTableSetSource fieldName selectSource
|
||||||
|
pure
|
||||||
|
( source,
|
||||||
|
topExtractor,
|
||||||
|
HM.fromList $ aggregateFieldsToExtractorExps computedFieldSourcePrefix fields,
|
||||||
|
S.mkQIdenExp computedFieldSourcePrefix (mkAggregateOrderByAlias aggOrderBy)
|
||||||
|
)
|
||||||
|
|
||||||
|
generateSorting ::
|
||||||
|
NE.NonEmpty (OrderByItemG ('Postgres pgKind) (AnnotatedOrderByElement ('Postgres pgKind) (SQLExpression ('Postgres pgKind)), (S.Alias, SQLExpression ('Postgres pgKind)))) ->
|
||||||
|
( SelectSorting,
|
||||||
|
[(S.Alias, SQLExpression ('Postgres pgKind))] -- 'distinct on' column extractors
|
||||||
|
)
|
||||||
|
generateSorting orderByExps@(firstOrderBy NE.:| restOrderBys) =
|
||||||
|
case fst $ obiColumn firstOrderBy of
|
||||||
|
AOCColumn columnInfo ->
|
||||||
|
-- If rest order by expressions are all columns then apply order by clause at base selection.
|
||||||
|
if all (isJust . getColumnOrderBy . obiColumn) restOrderBys
|
||||||
|
then -- Collect column order by expressions from the rest.
|
||||||
|
|
||||||
|
let restColumnOrderBys = mapMaybe (sequenceA . (getColumnOrderBy <$>)) restOrderBys
|
||||||
|
firstColumnOrderBy = firstOrderBy {obiColumn = columnInfo}
|
||||||
|
in sortAtNodeAndBase $ firstColumnOrderBy NE.:| restColumnOrderBys
|
||||||
|
else -- Else rest order by expressions contain atleast one non-column order by.
|
||||||
|
-- So, apply order by clause at node selection.
|
||||||
|
sortOnlyAtNode
|
||||||
|
_ ->
|
||||||
|
-- First order by expression is non-column. So, apply order by clause at node selection.
|
||||||
|
sortOnlyAtNode
|
||||||
|
where
|
||||||
|
getColumnOrderBy = (^? _AOCColumn) . fst
|
||||||
|
|
||||||
|
(nodeOrderBy, nodeDistinctOn, nodeDistinctOnExtractors) =
|
||||||
|
let toOrderByExp orderByItemExp =
|
||||||
|
let OrderByItemG obTyM expAlias obNullsM = fst . snd <$> orderByItemExp
|
||||||
|
in S.OrderByItem (S.SEIdentifier $ toIdentifier expAlias) obTyM obNullsM
|
||||||
|
orderByExp = S.OrderByExp $ toOrderByExp <$> orderByExps
|
||||||
|
(maybeDistOn, distOnExtrs) = NE.unzip $ applyDistinctOnAtNode sourcePrefix' <$> distOnCols
|
||||||
|
in (orderByExp, maybeDistOn, fromMaybe [] distOnExtrs)
|
||||||
|
|
||||||
|
sortOnlyAtNode =
|
||||||
|
(Sorting $ ASorting (nodeOrderBy, nodeDistinctOn) Nothing, nodeDistinctOnExtractors)
|
||||||
|
|
||||||
|
sortAtNodeAndBase baseColumnOrderBys =
|
||||||
|
let mkBaseOrderByItem (OrderByItemG orderByType columnInfo nullsOrder) =
|
||||||
|
S.OrderByItem
|
||||||
|
(S.SEIdentifier $ toIdentifier $ ciColumn columnInfo)
|
||||||
|
orderByType
|
||||||
|
nullsOrder
|
||||||
|
baseOrderByExp = S.OrderByExp $ mkBaseOrderByItem <$> baseColumnOrderBys
|
||||||
|
baseDistOnExp = applyDistinctOnAtBase <$> distOnCols
|
||||||
|
sorting = Sorting $ ASorting (nodeOrderBy, nodeDistinctOn) $ Just (baseOrderByExp, baseDistOnExp)
|
||||||
|
in (sorting, nodeDistinctOnExtractors)
|
||||||
|
|
||||||
|
mkCursorExp ::
|
||||||
|
[OrderByItemG ('Postgres pgKind) (AnnotatedOrderByElement ('Postgres pgKind) (SQLExpression ('Postgres pgKind)), (S.Alias, SQLExpression ('Postgres pgKind)))] ->
|
||||||
|
S.SQLExp
|
||||||
|
mkCursorExp orderByItemExps =
|
||||||
|
S.applyJsonBuildObj $
|
||||||
|
flip concatMap orderByItemExps $
|
||||||
|
\orderByItemExp ->
|
||||||
|
let OrderByItemG _ (annObCol, (_, valExp)) _ = orderByItemExp
|
||||||
|
in annObColToJSONField valExp annObCol
|
||||||
|
where
|
||||||
|
mkAggOrderByValExp valExp = \case
|
||||||
|
AAOCount -> [S.SELit "count", valExp]
|
||||||
|
AAOOp opText colInfo ->
|
||||||
|
[ S.SELit opText,
|
||||||
|
S.applyJsonBuildObj [S.SELit $ getPGColTxt $ ciColumn colInfo, valExp]
|
||||||
|
]
|
||||||
|
|
||||||
|
annObColToJSONField valExp = \case
|
||||||
|
AOCColumn pgCol -> [S.SELit $ getPGColTxt $ ciColumn pgCol, valExp]
|
||||||
|
AOCObjectRelation relInfo _ obCol ->
|
||||||
|
[ S.SELit $ relNameToTxt $ riName relInfo,
|
||||||
|
S.applyJsonBuildObj $ annObColToJSONField valExp obCol
|
||||||
|
]
|
||||||
|
AOCArrayAggregation relInfo _ aggOrderBy ->
|
||||||
|
[ S.SELit $ relNameToTxt (riName relInfo) <> "_aggregate",
|
||||||
|
S.applyJsonBuildObj $ mkAggOrderByValExp valExp aggOrderBy
|
||||||
|
]
|
||||||
|
AOCComputedField cfOrderBy ->
|
||||||
|
let fieldNameText = computedFieldNameToText $ _cfobName cfOrderBy
|
||||||
|
in case _cfobOrderByElement cfOrderBy of
|
||||||
|
CFOBEScalar _ -> [S.SELit fieldNameText, valExp]
|
||||||
|
CFOBETableAggregation _ _ aggOrderBy ->
|
||||||
|
[ S.SELit $ fieldNameText <> "_aggregate",
|
||||||
|
S.applyJsonBuildObj $ mkAggOrderByValExp valExp aggOrderBy
|
||||||
|
]
|
||||||
|
|
||||||
|
applyDistinctOnAtBase ::
|
||||||
|
NE.NonEmpty PGCol -> S.DistinctExpr
|
||||||
|
applyDistinctOnAtBase =
|
||||||
|
S.DistinctOn . map (S.SEIdentifier . toIdentifier) . toList
|
||||||
|
|
||||||
|
applyDistinctOnAtNode ::
|
||||||
|
Identifier ->
|
||||||
|
NE.NonEmpty PGCol ->
|
||||||
|
( S.DistinctExpr,
|
||||||
|
[(S.Alias, S.SQLExp)] -- additional column extractors
|
||||||
|
)
|
||||||
|
applyDistinctOnAtNode pfx neCols = (distOnExp, colExtrs)
|
||||||
|
where
|
||||||
|
cols = toList neCols
|
||||||
|
distOnExp = S.DistinctOn $ map (S.SEIdentifier . toIdentifier . mkQColAls) cols
|
||||||
|
mkQCol c = S.mkQIdenExp (mkBaseTableAlias pfx) $ toIdentifier c
|
||||||
|
mkQColAls = S.Alias . mkBaseTableColumnAlias pfx
|
||||||
|
colExtrs = flip map cols $ mkQColAls &&& mkQCol
|
@ -0,0 +1,696 @@
|
|||||||
|
-- | This module defines functions that translate from the Postgres IR into
|
||||||
|
-- Postgres SQL AST.
|
||||||
|
--
|
||||||
|
-- NOTE: These functions 'processAnnAggregateSelect', 'processAnnSimpleSelect',
|
||||||
|
-- 'processConnectionSelect', are all mutually recursive.
|
||||||
|
module Hasura.Backends.Postgres.Translate.Select.Process
|
||||||
|
( processAnnAggregateSelect,
|
||||||
|
processAnnSimpleSelect,
|
||||||
|
processConnectionSelect,
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Data.HashMap.Strict qualified as HM
|
||||||
|
import Data.List.NonEmpty qualified as NE
|
||||||
|
import Data.Text.Extended (ToTxt (toTxt))
|
||||||
|
import Hasura.Backends.Postgres.SQL.DML qualified as S
|
||||||
|
import Hasura.Backends.Postgres.SQL.Types
|
||||||
|
( Identifier,
|
||||||
|
IsIdentifier (toIdentifier),
|
||||||
|
PGCol (..),
|
||||||
|
QualifiedObject (QualifiedObject),
|
||||||
|
QualifiedTable,
|
||||||
|
SchemaName (getSchemaTxt),
|
||||||
|
)
|
||||||
|
import Hasura.Backends.Postgres.Translate.BoolExp (toSQLBoolExp)
|
||||||
|
import Hasura.Backends.Postgres.Translate.Column (toJSONableExp)
|
||||||
|
import Hasura.Backends.Postgres.Translate.Select.Aliases
|
||||||
|
( mkAnnOrderByAlias,
|
||||||
|
mkArrayRelationAlias,
|
||||||
|
mkArrayRelationSourcePrefix,
|
||||||
|
mkBaseTableAlias,
|
||||||
|
mkBaseTableColumnAlias,
|
||||||
|
mkComputedFieldTableAlias,
|
||||||
|
mkObjectRelationTableAlias,
|
||||||
|
mkOrderByFieldName,
|
||||||
|
)
|
||||||
|
import Hasura.Backends.Postgres.Translate.Select.AnnotatedFieldJSON
|
||||||
|
import Hasura.Backends.Postgres.Translate.Select.Aux1
|
||||||
|
( cursorIdentifier,
|
||||||
|
cursorsSelectAliasIdentifier,
|
||||||
|
encodeBase64,
|
||||||
|
endCursorIdentifier,
|
||||||
|
fromTableRowArgs,
|
||||||
|
functionToIdentifier,
|
||||||
|
hasNextPageIdentifier,
|
||||||
|
hasPreviousPageIdentifier,
|
||||||
|
pageInfoSelectAliasIdentifier,
|
||||||
|
selectFromToFromItem,
|
||||||
|
startCursorIdentifier,
|
||||||
|
withForceAggregation,
|
||||||
|
)
|
||||||
|
import Hasura.Backends.Postgres.Translate.Select.Extractor
|
||||||
|
( aggregateFieldsToExtractorExps,
|
||||||
|
asJsonAggExtr,
|
||||||
|
withJsonAggExtr,
|
||||||
|
)
|
||||||
|
import Hasura.Backends.Postgres.Translate.Select.JoinTree
|
||||||
|
( withWriteArrayConnection,
|
||||||
|
withWriteArrayRelation,
|
||||||
|
withWriteComputedFieldTableSet,
|
||||||
|
withWriteObjectRelation,
|
||||||
|
)
|
||||||
|
import Hasura.Backends.Postgres.Translate.Select.OrderBy (processOrderByItems)
|
||||||
|
import Hasura.Backends.Postgres.Translate.Types
|
||||||
|
import Hasura.GraphQL.Schema.Common (currentNodeIdVersion, nodeIdVersionInt)
|
||||||
|
import Hasura.Prelude
|
||||||
|
import Hasura.RQL.IR.OrderBy (OrderByItemG (OrderByItemG, obiColumn))
|
||||||
|
import Hasura.RQL.IR.Select
|
||||||
|
import Hasura.RQL.Types
|
||||||
|
|
||||||
|
processSelectParams ::
|
||||||
|
forall pgKind m.
|
||||||
|
( MonadReader StringifyNumbers m,
|
||||||
|
MonadWriter JoinTree m,
|
||||||
|
Backend ('Postgres pgKind)
|
||||||
|
) =>
|
||||||
|
SourcePrefixes ->
|
||||||
|
FieldName ->
|
||||||
|
SimilarArrayFields ->
|
||||||
|
SelectFrom ('Postgres pgKind) ->
|
||||||
|
PermissionLimitSubQuery ->
|
||||||
|
TablePerm ('Postgres pgKind) ->
|
||||||
|
SelectArgs ('Postgres pgKind) ->
|
||||||
|
m
|
||||||
|
( SelectSource,
|
||||||
|
[(S.Alias, S.SQLExp)],
|
||||||
|
Maybe S.SQLExp -- Order by cursor
|
||||||
|
)
|
||||||
|
processSelectParams
|
||||||
|
sourcePrefixes
|
||||||
|
fieldAlias
|
||||||
|
similarArrFields
|
||||||
|
selectFrom
|
||||||
|
permLimitSubQ
|
||||||
|
tablePermissions
|
||||||
|
tableArgs = do
|
||||||
|
(additionalExtrs, selectSorting, cursorExp) <-
|
||||||
|
processOrderByItems thisSourcePrefix fieldAlias similarArrFields distM orderByM
|
||||||
|
let fromItem = selectFromToFromItem (_pfBase sourcePrefixes) selectFrom
|
||||||
|
finalWhere =
|
||||||
|
toSQLBoolExp (selectFromToQual selectFrom) $
|
||||||
|
maybe permFilter (andAnnBoolExps permFilter) whereM
|
||||||
|
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
|
||||||
|
(Just inp, Just perm) -> Just if inp < perm then inp else perm
|
||||||
|
|
||||||
|
-- You should be able to retrieve this information
|
||||||
|
-- from the FromItem generated with selectFromToFromItem
|
||||||
|
-- however given from S.FromItem is modelled, it is not
|
||||||
|
-- possible currently.
|
||||||
|
--
|
||||||
|
-- More precisely, 'selectFromToFromItem' is injective but not surjective, so
|
||||||
|
-- any S.FromItem -> S.Qual function would have to be partial.
|
||||||
|
selectFromToQual :: SelectFrom ('Postgres pgKind) -> S.Qual
|
||||||
|
selectFromToQual = \case
|
||||||
|
FromTable table -> S.QualTable table
|
||||||
|
FromIdentifier i -> S.QualifiedIdentifier (toIdentifier i) Nothing
|
||||||
|
FromFunction qf _ _ -> S.QualifiedIdentifier (functionToIdentifier qf) Nothing
|
||||||
|
|
||||||
|
processAnnAggregateSelect ::
|
||||||
|
forall pgKind m.
|
||||||
|
( MonadReader StringifyNumbers m,
|
||||||
|
MonadWriter JoinTree m,
|
||||||
|
Backend ('Postgres pgKind),
|
||||||
|
PostgresAnnotatedFieldJSON pgKind
|
||||||
|
) =>
|
||||||
|
SourcePrefixes ->
|
||||||
|
FieldName ->
|
||||||
|
AnnAggregateSelect ('Postgres pgKind) ->
|
||||||
|
m
|
||||||
|
( SelectSource,
|
||||||
|
HM.HashMap S.Alias S.SQLExp,
|
||||||
|
S.Extractor
|
||||||
|
)
|
||||||
|
processAnnAggregateSelect sourcePrefixes fieldAlias annAggSel = do
|
||||||
|
(selectSource, orderByAndDistinctExtrs, _) <-
|
||||||
|
processSelectParams
|
||||||
|
sourcePrefixes
|
||||||
|
fieldAlias
|
||||||
|
similarArrayFields
|
||||||
|
tableFrom
|
||||||
|
permLimitSubQuery
|
||||||
|
tablePermissions
|
||||||
|
tableArgs
|
||||||
|
let thisSourcePrefix = _pfThis sourcePrefixes
|
||||||
|
processedFields <- forM aggSelFields $ \(fieldName, field) ->
|
||||||
|
(fieldName,)
|
||||||
|
<$> case field of
|
||||||
|
TAFAgg aggFields ->
|
||||||
|
pure
|
||||||
|
( aggregateFieldsToExtractorExps thisSourcePrefix aggFields,
|
||||||
|
aggregateFieldToExp aggFields strfyNum
|
||||||
|
)
|
||||||
|
TAFNodes _ annFields -> do
|
||||||
|
annFieldExtr <- processAnnFields thisSourcePrefix fieldName similarArrayFields annFields
|
||||||
|
pure
|
||||||
|
( [annFieldExtr],
|
||||||
|
withJsonAggExtr permLimitSubQuery (orderByForJsonAgg selectSource) $
|
||||||
|
S.Alias $ toIdentifier fieldName
|
||||||
|
)
|
||||||
|
TAFExp e ->
|
||||||
|
pure
|
||||||
|
( [],
|
||||||
|
withForceAggregation S.textTypeAnn $ S.SELit e
|
||||||
|
)
|
||||||
|
|
||||||
|
let topLevelExtractor =
|
||||||
|
flip S.Extractor (Just $ S.Alias $ toIdentifier fieldAlias) $
|
||||||
|
S.applyJsonBuildObj $
|
||||||
|
flip concatMap (map (second snd) processedFields) $
|
||||||
|
\(FieldName fieldText, fieldExp) -> [S.SELit fieldText, fieldExp]
|
||||||
|
nodeExtractors =
|
||||||
|
HM.fromList $
|
||||||
|
concatMap (fst . snd) processedFields <> orderByAndDistinctExtrs
|
||||||
|
|
||||||
|
pure (selectSource, nodeExtractors, topLevelExtractor)
|
||||||
|
where
|
||||||
|
AnnSelectG aggSelFields tableFrom tablePermissions tableArgs strfyNum = annAggSel
|
||||||
|
permLimit = _tpLimit tablePermissions
|
||||||
|
orderBy = _saOrderBy tableArgs
|
||||||
|
permLimitSubQuery = mkPermissionLimitSubQuery permLimit aggSelFields orderBy
|
||||||
|
similarArrayFields = HM.unions $
|
||||||
|
flip map (map snd aggSelFields) $ \case
|
||||||
|
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
|
||||||
|
hasAggregateField = flip any (map snd aggFields) $
|
||||||
|
\case
|
||||||
|
TAFAgg _ -> True
|
||||||
|
_ -> False
|
||||||
|
|
||||||
|
hasAggOrderBy = case orderBys of
|
||||||
|
Nothing -> False
|
||||||
|
Just l -> flip any (concatMap toList $ toList l) $
|
||||||
|
\case
|
||||||
|
AOCArrayAggregation {} -> True
|
||||||
|
_ -> False
|
||||||
|
|
||||||
|
processAnnFields ::
|
||||||
|
forall pgKind m.
|
||||||
|
( MonadReader StringifyNumbers m,
|
||||||
|
MonadWriter JoinTree m,
|
||||||
|
Backend ('Postgres pgKind),
|
||||||
|
PostgresAnnotatedFieldJSON pgKind
|
||||||
|
) =>
|
||||||
|
Identifier ->
|
||||||
|
FieldName ->
|
||||||
|
SimilarArrayFields ->
|
||||||
|
AnnFields ('Postgres pgKind) ->
|
||||||
|
m (S.Alias, S.SQLExp)
|
||||||
|
processAnnFields sourcePrefix fieldAlias similarArrFields annFields = do
|
||||||
|
fieldExps <- forM annFields $ \(fieldName, field) ->
|
||||||
|
(fieldName,)
|
||||||
|
<$> case field of
|
||||||
|
AFExpression t -> pure $ S.SELit t
|
||||||
|
AFNodeId _ tn pKeys -> pure $ mkNodeId tn pKeys
|
||||||
|
AFColumn c -> toSQLCol c
|
||||||
|
AFObjectRelation objSel -> withWriteObjectRelation $ do
|
||||||
|
let AnnRelationSelectG relName relMapping annObjSel = objSel
|
||||||
|
AnnObjectSelectG objAnnFields tableFrom tableFilter = annObjSel
|
||||||
|
objRelSourcePrefix = mkObjectRelationTableAlias sourcePrefix relName
|
||||||
|
sourcePrefixes = mkSourcePrefixes objRelSourcePrefix
|
||||||
|
annFieldsExtr <- processAnnFields (_pfThis sourcePrefixes) fieldName HM.empty objAnnFields
|
||||||
|
let selectSource =
|
||||||
|
ObjectSelectSource
|
||||||
|
(_pfThis sourcePrefixes)
|
||||||
|
(S.FISimple tableFrom Nothing)
|
||||||
|
(toSQLBoolExp (S.QualTable tableFrom) tableFilter)
|
||||||
|
objRelSource = ObjectRelationSource relName relMapping selectSource
|
||||||
|
pure
|
||||||
|
( objRelSource,
|
||||||
|
HM.fromList [annFieldsExtr],
|
||||||
|
S.mkQIdenExp objRelSourcePrefix fieldName
|
||||||
|
)
|
||||||
|
AFArrayRelation arrSel -> do
|
||||||
|
let arrRelSourcePrefix = mkArrayRelationSourcePrefix sourcePrefix fieldAlias similarArrFields fieldName
|
||||||
|
arrRelAlias = mkArrayRelationAlias fieldAlias similarArrFields fieldName
|
||||||
|
processArrayRelation (mkSourcePrefixes arrRelSourcePrefix) fieldName arrRelAlias arrSel
|
||||||
|
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 =
|
||||||
|
S.simplifyBoolExp $
|
||||||
|
toSQLBoolExp (S.QualifiedIdentifier baseTableIdentifier Nothing) $
|
||||||
|
_accColCaseBoolExpField <$> caseBoolExp
|
||||||
|
in pure $ S.SECond boolExp computedFieldSQLExp S.SENull
|
||||||
|
AFComputedField _ _ (CFSTable selectTy sel) -> withWriteComputedFieldTableSet $ do
|
||||||
|
let computedFieldSourcePrefix =
|
||||||
|
mkComputedFieldTableAlias sourcePrefix fieldName
|
||||||
|
(selectSource, nodeExtractors) <-
|
||||||
|
processAnnSimpleSelect
|
||||||
|
(mkSourcePrefixes computedFieldSourcePrefix)
|
||||||
|
fieldName
|
||||||
|
PLSQNotRequired
|
||||||
|
sel
|
||||||
|
let computedFieldTableSetSource = ComputedFieldTableSetSource fieldName selectSource
|
||||||
|
extractor =
|
||||||
|
asJsonAggExtr selectTy (S.toAlias fieldName) PLSQNotRequired $
|
||||||
|
orderByForJsonAgg selectSource
|
||||||
|
pure
|
||||||
|
( computedFieldTableSetSource,
|
||||||
|
extractor,
|
||||||
|
nodeExtractors,
|
||||||
|
S.mkQIdenExp computedFieldSourcePrefix fieldName
|
||||||
|
)
|
||||||
|
|
||||||
|
pure $ annRowToJson @pgKind fieldAlias fieldExps
|
||||||
|
where
|
||||||
|
mkSourcePrefixes newPrefix = SourcePrefixes newPrefix sourcePrefix
|
||||||
|
|
||||||
|
baseTableIdentifier = mkBaseTableAlias sourcePrefix
|
||||||
|
|
||||||
|
toSQLCol :: AnnColumnField ('Postgres pgKind) S.SQLExp -> m S.SQLExp
|
||||||
|
toSQLCol (AnnColumnField col typ asText colOpM caseBoolExpMaybe) = do
|
||||||
|
strfyNum <- ask
|
||||||
|
let sqlExpression =
|
||||||
|
withColumnOp colOpM $
|
||||||
|
S.mkQIdenExp baseTableIdentifier col
|
||||||
|
finalSQLExpression =
|
||||||
|
-- Check out [SQL generation for inherited role]
|
||||||
|
case caseBoolExpMaybe of
|
||||||
|
Nothing -> sqlExpression
|
||||||
|
Just caseBoolExp ->
|
||||||
|
let boolExp =
|
||||||
|
S.simplifyBoolExp $
|
||||||
|
toSQLBoolExp (S.QualifiedIdentifier baseTableIdentifier Nothing) $
|
||||||
|
_accColCaseBoolExpField <$> caseBoolExp
|
||||||
|
in S.SECond boolExp sqlExpression S.SENull
|
||||||
|
pure $ toJSONableExp strfyNum typ asText finalSQLExpression
|
||||||
|
|
||||||
|
fromScalarComputedField :: ComputedFieldScalarSelect ('Postgres pgKind) S.SQLExp -> m S.SQLExp
|
||||||
|
fromScalarComputedField computedFieldScalar = do
|
||||||
|
strfyNum <- ask
|
||||||
|
pure $
|
||||||
|
toJSONableExp strfyNum (ColumnScalar ty) False $
|
||||||
|
withColumnOp colOpM $
|
||||||
|
S.SEFunction $ S.FunctionExp fn (fromTableRowArgs sourcePrefix args) Nothing
|
||||||
|
where
|
||||||
|
ComputedFieldScalarSelect fn args ty colOpM = computedFieldScalar
|
||||||
|
|
||||||
|
withColumnOp :: Maybe (ColumnOp ('Postgres pgKind)) -> S.SQLExp -> S.SQLExp
|
||||||
|
withColumnOp colOpM sqlExp = case colOpM of
|
||||||
|
Nothing -> sqlExp
|
||||||
|
Just (ColumnOp opText cExp) -> S.mkSQLOpExp opText sqlExp cExp
|
||||||
|
|
||||||
|
mkNodeId :: QualifiedTable -> PrimaryKeyColumns ('Postgres pgKind) -> S.SQLExp
|
||||||
|
mkNodeId (QualifiedObject tableSchema tableName) pkeyColumns =
|
||||||
|
let columnInfoToSQLExp pgColumnInfo =
|
||||||
|
toJSONableExp LeaveNumbersAlone (ciType pgColumnInfo) False $
|
||||||
|
S.mkQIdenExp (mkBaseTableAlias sourcePrefix) $ ciColumn pgColumnInfo
|
||||||
|
in -- See Note [Relay Node id].
|
||||||
|
encodeBase64 $
|
||||||
|
flip S.SETyAnn S.textTypeAnn $
|
||||||
|
S.applyJsonBuildArray $
|
||||||
|
[ S.intToSQLExp $ nodeIdVersionInt currentNodeIdVersion,
|
||||||
|
S.SELit (getSchemaTxt tableSchema),
|
||||||
|
S.SELit (toTxt tableName)
|
||||||
|
]
|
||||||
|
<> map columnInfoToSQLExp (toList pkeyColumns)
|
||||||
|
|
||||||
|
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 =
|
||||||
|
HM.fromList $
|
||||||
|
flip map allTuples $
|
||||||
|
\(relNameAndArgs, fieldName) -> (fieldName, getSimilarFields relNameAndArgs)
|
||||||
|
where
|
||||||
|
getSimilarFields relNameAndArgs = map snd $ filter ((== relNameAndArgs) . fst) allTuples
|
||||||
|
allTuples = arrayRelationTuples <> aggOrderByRelationTuples
|
||||||
|
arrayRelationTuples =
|
||||||
|
let arrayFields = mapMaybe getAnnArr annFields
|
||||||
|
in flip map arrayFields $
|
||||||
|
\(f, relSel) -> (getArrayRelNameAndSelectArgs relSel, f)
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
in map mkItem $
|
||||||
|
maybe
|
||||||
|
[]
|
||||||
|
(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.
|
||||||
|
( MonadReader StringifyNumbers m,
|
||||||
|
MonadWriter JoinTree m,
|
||||||
|
Backend ('Postgres pgKind),
|
||||||
|
PostgresAnnotatedFieldJSON pgKind
|
||||||
|
) =>
|
||||||
|
SourcePrefixes ->
|
||||||
|
FieldName ->
|
||||||
|
S.Alias ->
|
||||||
|
ArraySelect ('Postgres pgKind) ->
|
||||||
|
m ()
|
||||||
|
processArrayRelation sourcePrefixes fieldAlias relAlias arrSel =
|
||||||
|
case arrSel of
|
||||||
|
ASSimple annArrRel -> withWriteArrayRelation $ do
|
||||||
|
let AnnRelationSelectG _ colMapping sel = annArrRel
|
||||||
|
permLimitSubQuery =
|
||||||
|
maybe PLSQNotRequired PLSQRequired $ _tpLimit $ _asnPerm sel
|
||||||
|
(source, nodeExtractors) <-
|
||||||
|
processAnnSimpleSelect sourcePrefixes fieldAlias permLimitSubQuery sel
|
||||||
|
let topExtr =
|
||||||
|
asJsonAggExtr
|
||||||
|
JASMultipleRows
|
||||||
|
(S.toAlias fieldAlias)
|
||||||
|
permLimitSubQuery
|
||||||
|
$ orderByForJsonAgg source
|
||||||
|
pure
|
||||||
|
( ArrayRelationSource relAlias colMapping source,
|
||||||
|
topExtr,
|
||||||
|
nodeExtractors,
|
||||||
|
()
|
||||||
|
)
|
||||||
|
ASAggregate aggSel -> withWriteArrayRelation $ do
|
||||||
|
let AnnRelationSelectG _ colMapping sel = aggSel
|
||||||
|
(source, nodeExtractors, topExtr) <-
|
||||||
|
processAnnAggregateSelect sourcePrefixes fieldAlias sel
|
||||||
|
pure
|
||||||
|
( ArrayRelationSource relAlias colMapping source,
|
||||||
|
topExtr,
|
||||||
|
nodeExtractors,
|
||||||
|
()
|
||||||
|
)
|
||||||
|
ASConnection connSel -> withWriteArrayConnection $ do
|
||||||
|
let AnnRelationSelectG _ colMapping sel = connSel
|
||||||
|
(source, topExtractor, nodeExtractors) <-
|
||||||
|
processConnectionSelect sourcePrefixes fieldAlias relAlias colMapping sel
|
||||||
|
pure
|
||||||
|
( source,
|
||||||
|
topExtractor,
|
||||||
|
nodeExtractors,
|
||||||
|
()
|
||||||
|
)
|
||||||
|
|
||||||
|
aggregateFieldToExp :: AggregateFields ('Postgres pgKind) -> StringifyNumbers -> S.SQLExp
|
||||||
|
aggregateFieldToExp aggFlds strfyNum = jsonRow
|
||||||
|
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
|
||||||
|
|
||||||
|
colFldsToExtr opText (FieldName t, CFCol col ty) =
|
||||||
|
[ S.SELit t,
|
||||||
|
toJSONableExp strfyNum ty False $
|
||||||
|
S.SEFnApp opText [S.SEIdentifier $ toIdentifier col] Nothing
|
||||||
|
]
|
||||||
|
colFldsToExtr _ (FieldName t, CFExp e) =
|
||||||
|
[S.SELit t, S.SELit e]
|
||||||
|
|
||||||
|
processAnnSimpleSelect ::
|
||||||
|
forall pgKind m.
|
||||||
|
( MonadReader StringifyNumbers m,
|
||||||
|
MonadWriter JoinTree m,
|
||||||
|
Backend ('Postgres pgKind),
|
||||||
|
PostgresAnnotatedFieldJSON pgKind
|
||||||
|
) =>
|
||||||
|
SourcePrefixes ->
|
||||||
|
FieldName ->
|
||||||
|
PermissionLimitSubQuery ->
|
||||||
|
AnnSimpleSelect ('Postgres pgKind) ->
|
||||||
|
m
|
||||||
|
( SelectSource,
|
||||||
|
HM.HashMap S.Alias S.SQLExp
|
||||||
|
)
|
||||||
|
processAnnSimpleSelect sourcePrefixes fieldAlias permLimitSubQuery annSimpleSel = do
|
||||||
|
(selectSource, orderByAndDistinctExtrs, _) <-
|
||||||
|
processSelectParams
|
||||||
|
sourcePrefixes
|
||||||
|
fieldAlias
|
||||||
|
similarArrayFields
|
||||||
|
tableFrom
|
||||||
|
permLimitSubQuery
|
||||||
|
tablePermissions
|
||||||
|
tableArgs
|
||||||
|
annFieldsExtr <- processAnnFields (_pfThis sourcePrefixes) fieldAlias similarArrayFields annSelFields
|
||||||
|
let allExtractors = HM.fromList $ annFieldsExtr : orderByAndDistinctExtrs
|
||||||
|
pure (selectSource, allExtractors)
|
||||||
|
where
|
||||||
|
AnnSelectG annSelFields tableFrom tablePermissions tableArgs _ = annSimpleSel
|
||||||
|
similarArrayFields =
|
||||||
|
mkSimilarArrayFields annSelFields $ _saOrderBy tableArgs
|
||||||
|
|
||||||
|
processConnectionSelect ::
|
||||||
|
forall pgKind m.
|
||||||
|
( MonadReader StringifyNumbers m,
|
||||||
|
MonadWriter JoinTree m,
|
||||||
|
Backend ('Postgres pgKind),
|
||||||
|
PostgresAnnotatedFieldJSON pgKind
|
||||||
|
) =>
|
||||||
|
SourcePrefixes ->
|
||||||
|
FieldName ->
|
||||||
|
S.Alias ->
|
||||||
|
HM.HashMap PGCol PGCol ->
|
||||||
|
ConnectionSelect ('Postgres pgKind) Void S.SQLExp ->
|
||||||
|
m
|
||||||
|
( ArrayConnectionSource,
|
||||||
|
S.Extractor,
|
||||||
|
HM.HashMap S.Alias S.SQLExp
|
||||||
|
)
|
||||||
|
processConnectionSelect sourcePrefixes fieldAlias relAlias colMapping connectionSelect = do
|
||||||
|
(selectSource, orderByAndDistinctExtrs, maybeOrderByCursor) <-
|
||||||
|
processSelectParams
|
||||||
|
sourcePrefixes
|
||||||
|
fieldAlias
|
||||||
|
similarArrayFields
|
||||||
|
selectFrom
|
||||||
|
permLimitSubQuery
|
||||||
|
tablePermissions
|
||||||
|
tableArgs
|
||||||
|
|
||||||
|
let mkCursorExtractor = (S.Alias cursorIdentifier,) . (`S.SETyAnn` S.textTypeAnn)
|
||||||
|
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
|
||||||
|
let topExtractor = S.Extractor topExtractorExp $ Just $ S.Alias fieldIdentifier
|
||||||
|
allExtractors = HM.fromList $ cursorExtractors <> exps <> orderByAndDistinctExtrs
|
||||||
|
arrayConnectionSource =
|
||||||
|
ArrayConnectionSource
|
||||||
|
relAlias
|
||||||
|
colMapping
|
||||||
|
(mkSplitBoolExp <$> maybeSplit)
|
||||||
|
maybeSlice
|
||||||
|
selectSource
|
||||||
|
pure
|
||||||
|
( arrayConnectionSource,
|
||||||
|
topExtractor,
|
||||||
|
allExtractors
|
||||||
|
)
|
||||||
|
where
|
||||||
|
ConnectionSelect _ primaryKeyColumns maybeSplit maybeSlice select = connectionSelect
|
||||||
|
AnnSelectG fields selectFrom tablePermissions tableArgs _ = select
|
||||||
|
fieldIdentifier = toIdentifier fieldAlias
|
||||||
|
thisPrefix = _pfThis sourcePrefixes
|
||||||
|
permLimitSubQuery = PLSQNotRequired
|
||||||
|
|
||||||
|
primaryKeyColumnsObjectExp =
|
||||||
|
S.applyJsonBuildObj $
|
||||||
|
flip concatMap (toList primaryKeyColumns) $
|
||||||
|
\pgColumnInfo ->
|
||||||
|
[ S.SELit $ getPGColTxt $ ciColumn pgColumnInfo,
|
||||||
|
toJSONableExp LeaveNumbersAlone (ciType pgColumnInfo) False $
|
||||||
|
S.mkQIdenExp (mkBaseTableAlias thisPrefix) $ ciColumn pgColumnInfo
|
||||||
|
]
|
||||||
|
|
||||||
|
primaryKeyColumnExtractors =
|
||||||
|
flip map (toList primaryKeyColumns) $
|
||||||
|
\pgColumnInfo ->
|
||||||
|
let pgColumn = ciColumn pgColumnInfo
|
||||||
|
in ( S.Alias $ mkBaseTableColumnAlias thisPrefix pgColumn,
|
||||||
|
S.mkQIdenExp (mkBaseTableAlias thisPrefix) pgColumn
|
||||||
|
)
|
||||||
|
|
||||||
|
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 =
|
||||||
|
mkAnnOrderByAlias thisPrefix fieldAlias similarArrayFields $
|
||||||
|
obiColumn orderByItem
|
||||||
|
in S.BECompare S.SEQ (S.SEIdentifier $ toIdentifier obAlias) v
|
||||||
|
|
||||||
|
similarArrayFields = HM.unions $
|
||||||
|
flip map (map snd fields) $ \case
|
||||||
|
ConnectionTypename {} -> mempty
|
||||||
|
ConnectionPageInfo {} -> mempty
|
||||||
|
ConnectionEdges edges -> HM.unions $
|
||||||
|
flip map (map snd edges) $ \case
|
||||||
|
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 ::
|
||||||
|
forall n.
|
||||||
|
( MonadReader StringifyNumbers n,
|
||||||
|
MonadWriter JoinTree n,
|
||||||
|
MonadState [(S.Alias, S.SQLExp)] n
|
||||||
|
) =>
|
||||||
|
SelectSource ->
|
||||||
|
n S.SQLExp
|
||||||
|
processFields selectSource =
|
||||||
|
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
|
||||||
|
annFieldsExtrExp <- processAnnFields thisPrefix edgeFieldName similarArrayFields annFields
|
||||||
|
modify' (<> [annFieldsExtrExp])
|
||||||
|
pure $ S.SEIdentifier edgeFieldIdentifier
|
||||||
|
|
||||||
|
processPageInfoFields infoFields =
|
||||||
|
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
|
||||||
|
where
|
||||||
|
mkSingleFieldSelect field fromIdentifier =
|
||||||
|
S.SESelect
|
||||||
|
S.mkSelect
|
||||||
|
{ S.selExtr = [S.Extractor field Nothing],
|
||||||
|
S.selFrom = Just $ S.FromExp [S.FIIdentifier fromIdentifier]
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
-- | This module defines the top-level translation functions pertaining to
|
||||||
|
-- queries that are not aggregation queries, i.e. so-called "simple" selects
|
||||||
|
-- into Postgres AST.
|
||||||
|
module Hasura.Backends.Postgres.Translate.Select.Query
|
||||||
|
( mkSQLSelect,
|
||||||
|
selectQuerySQL,
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Control.Monad.Writer.Strict (runWriter)
|
||||||
|
import Database.PG.Query (Query, fromBuilder)
|
||||||
|
import Hasura.Backends.Postgres.SQL.DML qualified as S
|
||||||
|
import Hasura.Backends.Postgres.SQL.IdentifierUniqueness (prefixNumToAliases)
|
||||||
|
import Hasura.Backends.Postgres.SQL.Types (IsIdentifier (toIdentifier))
|
||||||
|
import Hasura.Backends.Postgres.Translate.Select.AnnotatedFieldJSON
|
||||||
|
import Hasura.Backends.Postgres.Translate.Select.Extractor (asJsonAggExtr)
|
||||||
|
import Hasura.Backends.Postgres.Translate.Select.GenerateSelect (generateSQLSelectFromArrayNode)
|
||||||
|
import Hasura.Backends.Postgres.Translate.Select.Process (processAnnSimpleSelect)
|
||||||
|
import Hasura.Backends.Postgres.Translate.Types
|
||||||
|
import Hasura.Prelude
|
||||||
|
import Hasura.RQL.IR.Select
|
||||||
|
( AnnSelectG (_asnStrfyNum),
|
||||||
|
AnnSimpleSelect,
|
||||||
|
)
|
||||||
|
import Hasura.RQL.Types.Backend (Backend)
|
||||||
|
import Hasura.RQL.Types.Common
|
||||||
|
( FieldName (FieldName),
|
||||||
|
JsonAggSelect,
|
||||||
|
)
|
||||||
|
import Hasura.SQL.Backend (BackendType (Postgres))
|
||||||
|
import Hasura.SQL.Types (ToSQL (toSQL))
|
||||||
|
|
||||||
|
mkSQLSelect ::
|
||||||
|
forall pgKind.
|
||||||
|
( Backend ('Postgres pgKind),
|
||||||
|
PostgresAnnotatedFieldJSON pgKind
|
||||||
|
) =>
|
||||||
|
JsonAggSelect ->
|
||||||
|
AnnSimpleSelect ('Postgres pgKind) ->
|
||||||
|
S.Select
|
||||||
|
mkSQLSelect jsonAggSelect annSel =
|
||||||
|
let permLimitSubQuery = PLSQNotRequired
|
||||||
|
((selectSource, nodeExtractors), joinTree) =
|
||||||
|
runWriter $
|
||||||
|
flip runReaderT strfyNum $
|
||||||
|
processAnnSimpleSelect sourcePrefixes rootFldName permLimitSubQuery annSel
|
||||||
|
selectNode = SelectNode nodeExtractors joinTree
|
||||||
|
topExtractor =
|
||||||
|
asJsonAggExtr jsonAggSelect rootFldAls permLimitSubQuery $
|
||||||
|
orderByForJsonAgg selectSource
|
||||||
|
arrayNode = MultiRowSelectNode [topExtractor] selectNode
|
||||||
|
in prefixNumToAliases $
|
||||||
|
generateSQLSelectFromArrayNode selectSource arrayNode $ S.BELit True
|
||||||
|
where
|
||||||
|
strfyNum = _asnStrfyNum annSel
|
||||||
|
rootFldIdentifier = toIdentifier rootFldName
|
||||||
|
sourcePrefixes = SourcePrefixes rootFldIdentifier rootFldIdentifier
|
||||||
|
rootFldName = FieldName "root"
|
||||||
|
rootFldAls = S.Alias $ toIdentifier rootFldName
|
||||||
|
|
||||||
|
-- | Translates IR to Postgres queries for simple SELECTs (select queries that
|
||||||
|
-- are not aggregations, including subscriptions).
|
||||||
|
--
|
||||||
|
-- See 'mkSQLSelect' for the Postgres AST.
|
||||||
|
selectQuerySQL ::
|
||||||
|
forall pgKind.
|
||||||
|
(Backend ('Postgres pgKind), PostgresAnnotatedFieldJSON pgKind) =>
|
||||||
|
JsonAggSelect ->
|
||||||
|
AnnSimpleSelect ('Postgres pgKind) ->
|
||||||
|
Query
|
||||||
|
selectQuerySQL jsonAggSelect sel =
|
||||||
|
fromBuilder $ toSQL $ mkSQLSelect jsonAggSelect sel
|
@ -0,0 +1,141 @@
|
|||||||
|
-- | This module defines the top-level translation functions pertaining to
|
||||||
|
-- streaming selects into Postgres AST.
|
||||||
|
module Hasura.Backends.Postgres.Translate.Select.Streaming
|
||||||
|
( mkStreamSQLSelect,
|
||||||
|
selectStreamQuerySQL,
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Control.Monad.Writer.Strict (runWriter)
|
||||||
|
import Database.PG.Query (Query, fromBuilder)
|
||||||
|
import Hasura.Backends.Postgres.SQL.DML qualified as S
|
||||||
|
import Hasura.Backends.Postgres.SQL.IdentifierUniqueness (prefixNumToAliases)
|
||||||
|
import Hasura.Backends.Postgres.SQL.Types
|
||||||
|
import Hasura.Backends.Postgres.SQL.Value (withConstructorFn)
|
||||||
|
import Hasura.Backends.Postgres.Translate.Select.Aliases (mkBaseTableColumnAlias)
|
||||||
|
import Hasura.Backends.Postgres.Translate.Select.AnnotatedFieldJSON
|
||||||
|
import Hasura.Backends.Postgres.Translate.Select.Extractor (asJsonAggExtr)
|
||||||
|
import Hasura.Backends.Postgres.Translate.Select.GenerateSelect (generateSQLSelectFromArrayNode)
|
||||||
|
import Hasura.Backends.Postgres.Translate.Select.Process (processAnnSimpleSelect)
|
||||||
|
import Hasura.Backends.Postgres.Translate.Types
|
||||||
|
( MultiRowSelectNode (MultiRowSelectNode),
|
||||||
|
PermissionLimitSubQuery (PLSQNotRequired),
|
||||||
|
SelectNode (SelectNode),
|
||||||
|
SourcePrefixes (SourcePrefixes),
|
||||||
|
orderByForJsonAgg,
|
||||||
|
)
|
||||||
|
import Hasura.Backends.Postgres.Types.Column (unsafePGColumnToBackend)
|
||||||
|
import Hasura.Prelude
|
||||||
|
import Hasura.RQL.IR.BoolExp
|
||||||
|
( AnnBoolExpFld (AVColumn),
|
||||||
|
GBoolExp (BoolFld),
|
||||||
|
OpExpG (AGT, ALT),
|
||||||
|
andAnnBoolExps,
|
||||||
|
)
|
||||||
|
import Hasura.RQL.IR.OrderBy (OrderByItemG (OrderByItemG))
|
||||||
|
import Hasura.RQL.IR.Select
|
||||||
|
import Hasura.RQL.Types.Backend (Backend)
|
||||||
|
import Hasura.RQL.Types.Column
|
||||||
|
( ColumnInfo (ciColumn),
|
||||||
|
ColumnValue (cvType),
|
||||||
|
)
|
||||||
|
import Hasura.RQL.Types.Common
|
||||||
|
( FieldName (FieldName),
|
||||||
|
JsonAggSelect (JASMultipleRows),
|
||||||
|
)
|
||||||
|
import Hasura.RQL.Types.Subscription
|
||||||
|
( CursorOrdering (CODescending),
|
||||||
|
)
|
||||||
|
import Hasura.SQL.Backend (BackendType (Postgres))
|
||||||
|
import Hasura.SQL.Types
|
||||||
|
( CollectableType (CollectableTypeArray, CollectableTypeScalar),
|
||||||
|
ToSQL (toSQL),
|
||||||
|
)
|
||||||
|
|
||||||
|
selectStreamQuerySQL ::
|
||||||
|
forall pgKind.
|
||||||
|
(Backend ('Postgres pgKind), PostgresAnnotatedFieldJSON pgKind) =>
|
||||||
|
AnnSimpleStreamSelect ('Postgres pgKind) ->
|
||||||
|
Query
|
||||||
|
selectStreamQuerySQL sel =
|
||||||
|
fromBuilder $ toSQL $ mkStreamSQLSelect sel
|
||||||
|
|
||||||
|
mkStreamSQLSelect ::
|
||||||
|
forall pgKind.
|
||||||
|
( Backend ('Postgres pgKind),
|
||||||
|
PostgresAnnotatedFieldJSON pgKind
|
||||||
|
) =>
|
||||||
|
AnnSimpleStreamSelect ('Postgres pgKind) ->
|
||||||
|
S.Select
|
||||||
|
mkStreamSQLSelect (AnnSelectStreamG () fields from perm args strfyNum) =
|
||||||
|
let cursorArg = _ssaCursorArg args
|
||||||
|
cursorColInfo = _sciColInfo cursorArg
|
||||||
|
annOrderbyCol = AOCColumn cursorColInfo
|
||||||
|
basicOrderType =
|
||||||
|
bool S.OTAsc S.OTDesc $ _sciOrdering cursorArg == CODescending
|
||||||
|
orderByItems =
|
||||||
|
nonEmpty $ pure $ OrderByItemG (Just basicOrderType) annOrderbyCol Nothing
|
||||||
|
cursorBoolExp =
|
||||||
|
let orderByOpExp = bool ALT AGT $ basicOrderType == S.OTAsc
|
||||||
|
sqlExp =
|
||||||
|
fromResVars
|
||||||
|
(CollectableTypeScalar $ unsafePGColumnToBackend $ cvType (_sciInitialValue cursorArg))
|
||||||
|
["cursor", getPGColTxt $ ciColumn cursorColInfo]
|
||||||
|
in BoolFld $ AVColumn cursorColInfo [(orderByOpExp sqlExp)]
|
||||||
|
|
||||||
|
selectArgs =
|
||||||
|
noSelectArgs
|
||||||
|
{ _saWhere =
|
||||||
|
Just $ maybe cursorBoolExp (andAnnBoolExps cursorBoolExp) $ _ssaWhere args,
|
||||||
|
_saOrderBy = orderByItems,
|
||||||
|
_saLimit = Just $ _ssaBatchSize args
|
||||||
|
}
|
||||||
|
sqlSelect = AnnSelectG fields from perm selectArgs strfyNum
|
||||||
|
permLimitSubQuery = PLSQNotRequired
|
||||||
|
((selectSource, nodeExtractors), joinTree) =
|
||||||
|
runWriter $
|
||||||
|
flip runReaderT strfyNum $
|
||||||
|
processAnnSimpleSelect sourcePrefixes rootFldName permLimitSubQuery sqlSelect
|
||||||
|
selectNode = SelectNode nodeExtractors joinTree
|
||||||
|
topExtractor =
|
||||||
|
asJsonAggExtr JASMultipleRows rootFldAls permLimitSubQuery $
|
||||||
|
orderByForJsonAgg selectSource
|
||||||
|
cursorLatestValueExp :: S.SQLExp =
|
||||||
|
let pgColumn = ciColumn cursorColInfo
|
||||||
|
mkMaxOrMinSQLExp maxOrMin col =
|
||||||
|
S.SEFnApp maxOrMin [S.SEIdentifier col] Nothing
|
||||||
|
maxOrMinTxt = bool "MIN" "MAX" $ basicOrderType == S.OTAsc
|
||||||
|
-- text encoding the cursor value while it's fetched from the DB, because
|
||||||
|
-- we can then directly reuse this value, otherwise if this were json encoded
|
||||||
|
-- then we'd have to parse the value and then convert it into a text encoded value
|
||||||
|
colExp =
|
||||||
|
[ S.SELit (getPGColTxt pgColumn),
|
||||||
|
S.SETyAnn
|
||||||
|
(mkMaxOrMinSQLExp maxOrMinTxt $ mkBaseTableColumnAlias rootFldIdentifier pgColumn)
|
||||||
|
S.textTypeAnn
|
||||||
|
]
|
||||||
|
in -- SELECT json_build_object ('col1', MAX(col1) :: text)
|
||||||
|
|
||||||
|
S.SEFnApp "json_build_object" colExp Nothing
|
||||||
|
cursorLatestValueExtractor = S.Extractor cursorLatestValueExp (Just $ S.Alias $ Identifier "cursor")
|
||||||
|
arrayNode = MultiRowSelectNode [topExtractor, cursorLatestValueExtractor] selectNode
|
||||||
|
in prefixNumToAliases $
|
||||||
|
generateSQLSelectFromArrayNode selectSource arrayNode $ S.BELit True
|
||||||
|
where
|
||||||
|
rootFldIdentifier = toIdentifier rootFldName
|
||||||
|
sourcePrefixes = SourcePrefixes rootFldIdentifier rootFldIdentifier
|
||||||
|
rootFldName = FieldName "root"
|
||||||
|
rootFldAls = S.Alias $ toIdentifier rootFldName
|
||||||
|
|
||||||
|
-- TODO: these functions also exist in `resolveMultiplexedValue`, de-duplicate these!
|
||||||
|
fromResVars pgType jPath =
|
||||||
|
addTypeAnnotation pgType $
|
||||||
|
S.SEOpApp
|
||||||
|
(S.SQLOp "#>>")
|
||||||
|
[ S.SEQIdentifier $ S.QIdentifier (S.QualifiedIdentifier (Identifier "_subs") Nothing) (Identifier "result_vars"),
|
||||||
|
S.SEArray $ map S.SELit jPath
|
||||||
|
]
|
||||||
|
addTypeAnnotation pgType =
|
||||||
|
flip S.SETyAnn (S.mkTypeAnn pgType) . case pgType of
|
||||||
|
CollectableTypeScalar scalarType -> withConstructorFn scalarType
|
||||||
|
CollectableTypeArray _ -> id
|
@ -20,6 +20,7 @@ module Hasura.Backends.Postgres.Translate.Types
|
|||||||
SelectSource (SelectSource, _ssPrefix),
|
SelectSource (SelectSource, _ssPrefix),
|
||||||
SortingAndSlicing (SortingAndSlicing),
|
SortingAndSlicing (SortingAndSlicing),
|
||||||
SourcePrefixes (..),
|
SourcePrefixes (..),
|
||||||
|
SimilarArrayFields,
|
||||||
applySortingAndSlicing,
|
applySortingAndSlicing,
|
||||||
noSortingAndSlicing,
|
noSortingAndSlicing,
|
||||||
objectSelectSourceToSelectSource,
|
objectSelectSourceToSelectSource,
|
||||||
@ -243,3 +244,5 @@ data PermissionLimitSubQuery
|
|||||||
PLSQRequired !Int
|
PLSQRequired !Int
|
||||||
| PLSQNotRequired
|
| PLSQNotRequired
|
||||||
deriving (Show, Eq)
|
deriving (Show, Eq)
|
||||||
|
|
||||||
|
type SimilarArrayFields = HM.HashMap FieldName [FieldName]
|
||||||
|
@ -40,7 +40,6 @@ import Hasura.Backends.Postgres.Execute.Prepare
|
|||||||
import Hasura.Backends.Postgres.SQL.DML qualified as S
|
import Hasura.Backends.Postgres.SQL.DML qualified as S
|
||||||
import Hasura.Backends.Postgres.SQL.Types
|
import Hasura.Backends.Postgres.SQL.Types
|
||||||
import Hasura.Backends.Postgres.SQL.Value (PGScalarValue (..))
|
import Hasura.Backends.Postgres.SQL.Value (PGScalarValue (..))
|
||||||
import Hasura.Backends.Postgres.Translate.Select (asSingleRowJsonResp)
|
|
||||||
import Hasura.Backends.Postgres.Translate.Select qualified as RS
|
import Hasura.Backends.Postgres.Translate.Select qualified as RS
|
||||||
import Hasura.Base.Error
|
import Hasura.Base.Error
|
||||||
import Hasura.EncJSON
|
import Hasura.EncJSON
|
||||||
@ -56,6 +55,7 @@ import Hasura.Prelude
|
|||||||
import Hasura.RQL.DDL.Headers
|
import Hasura.RQL.DDL.Headers
|
||||||
import Hasura.RQL.DDL.Webhook.Transform
|
import Hasura.RQL.DDL.Webhook.Transform
|
||||||
import Hasura.RQL.DDL.Webhook.Transform.Class (mkReqTransformCtx)
|
import Hasura.RQL.DDL.Webhook.Transform.Class (mkReqTransformCtx)
|
||||||
|
import Hasura.RQL.DML.Internal (dmlTxErrorHandler)
|
||||||
import Hasura.RQL.IR.Action qualified as RA
|
import Hasura.RQL.IR.Action qualified as RA
|
||||||
import Hasura.RQL.IR.Select qualified as RS
|
import Hasura.RQL.IR.Select qualified as RS
|
||||||
import Hasura.RQL.Types
|
import Hasura.RQL.Types
|
||||||
@ -108,6 +108,17 @@ runActionExecution userInfo aep =
|
|||||||
liftEitherM $ runExceptT $ runTx (_pscExecCtx srcConfig) Q.ReadOnly $ liftTx $ asSingleRowJsonResp querySQL []
|
liftEitherM $ runExceptT $ runTx (_pscExecCtx srcConfig) Q.ReadOnly $ liftTx $ asSingleRowJsonResp querySQL []
|
||||||
AEPAsyncMutation actionId -> pure $ (,Nothing) $ encJFromJValue $ actionIdToText actionId
|
AEPAsyncMutation actionId -> pure $ (,Nothing) $ encJFromJValue $ actionIdToText actionId
|
||||||
|
|
||||||
|
-- | This function is generally used on the result of 'selectQuerySQL',
|
||||||
|
-- 'selectAggregateQuerySQL' or 'connectionSelectSQL' to run said query and get
|
||||||
|
-- back the resulting JSON.
|
||||||
|
asSingleRowJsonResp ::
|
||||||
|
Q.Query ->
|
||||||
|
[Q.PrepArg] ->
|
||||||
|
Q.TxE QErr EncJSON
|
||||||
|
asSingleRowJsonResp query args =
|
||||||
|
encJFromBS . runIdentity . Q.getRow
|
||||||
|
<$> Q.rawQE dmlTxErrorHandler query args True
|
||||||
|
|
||||||
-- | Synchronously execute webhook handler and resolve response to action "output"
|
-- | Synchronously execute webhook handler and resolve response to action "output"
|
||||||
resolveActionExecution ::
|
resolveActionExecution ::
|
||||||
Env.Environment ->
|
Env.Environment ->
|
||||||
|
Loading…
Reference in New Issue
Block a user