2020-10-29 19:58:13 +03:00
|
|
|
module Hasura.Backends.Postgres.Translate.Select
|
|
|
|
( selectQuerySQL
|
|
|
|
, selectAggregateQuerySQL
|
|
|
|
, connectionSelectQuerySQL
|
|
|
|
, asSingleRowJsonResp
|
|
|
|
, mkSQLSelect
|
2020-06-08 15:13:01 +03:00
|
|
|
, mkAggregateSelect
|
|
|
|
, mkConnectionSelect
|
2021-05-21 05:46:58 +03:00
|
|
|
, PostgresAnnotatedFieldJSON
|
2020-10-29 19:58:13 +03:00
|
|
|
) where
|
2018-10-31 15:51:20 +03:00
|
|
|
|
2020-08-27 19:36:39 +03:00
|
|
|
import Hasura.Prelude
|
2018-10-31 15:51:20 +03:00
|
|
|
|
2020-11-02 14:50:40 +03:00
|
|
|
import qualified Data.HashMap.Strict as HM
|
|
|
|
import qualified Data.List.NonEmpty as NE
|
|
|
|
import qualified Data.Text as T
|
|
|
|
import qualified Database.PG.Query as Q
|
2018-10-31 15:51:20 +03:00
|
|
|
|
2020-11-02 14:50:40 +03:00
|
|
|
import Control.Lens hiding (op)
|
2020-08-27 19:36:39 +03:00
|
|
|
import Control.Monad.Writer.Strict
|
2020-10-21 19:35:06 +03:00
|
|
|
import Data.Text.Extended
|
2020-08-27 19:36:39 +03:00
|
|
|
|
2020-11-02 14:50:40 +03:00
|
|
|
import qualified Hasura.Backends.Postgres.SQL.DML as S
|
2020-08-27 19:36:39 +03:00
|
|
|
|
2020-10-27 16:53:49 +03:00
|
|
|
import Hasura.Backends.Postgres.SQL.Rewrite
|
|
|
|
import Hasura.Backends.Postgres.SQL.Types
|
2020-11-02 14:50:40 +03:00
|
|
|
import Hasura.Backends.Postgres.Translate.BoolExp
|
2020-11-25 17:18:58 +03:00
|
|
|
import Hasura.Backends.Postgres.Translate.Types
|
2021-05-11 18:18:31 +03:00
|
|
|
import Hasura.Base.Error
|
2020-10-29 19:58:13 +03:00
|
|
|
import Hasura.EncJSON
|
2021-02-03 19:17:20 +03:00
|
|
|
import Hasura.GraphQL.Schema.Common (currentNodeIdVersion, nodeIdVersionInt)
|
2018-10-31 15:51:20 +03:00
|
|
|
import Hasura.RQL.DML.Internal
|
2020-11-12 12:25:48 +03:00
|
|
|
import Hasura.RQL.IR.OrderBy
|
2020-10-29 19:58:13 +03:00
|
|
|
import Hasura.RQL.IR.Select
|
2020-11-25 17:18:58 +03:00
|
|
|
import Hasura.RQL.Types hiding (Identifier)
|
2020-10-29 19:58:13 +03:00
|
|
|
import Hasura.SQL.Types
|
|
|
|
|
2021-04-22 00:44:37 +03:00
|
|
|
selectQuerySQL
|
|
|
|
:: forall pgKind
|
2021-05-21 05:46:58 +03:00
|
|
|
. (Backend ('Postgres pgKind), PostgresAnnotatedFieldJSON pgKind)
|
2021-04-22 00:44:37 +03:00
|
|
|
=> JsonAggSelect
|
|
|
|
-> AnnSimpleSel ('Postgres pgKind)
|
|
|
|
-> Q.Query
|
2020-10-29 19:58:13 +03:00
|
|
|
selectQuerySQL jsonAggSelect sel =
|
|
|
|
Q.fromBuilder $ toSQL $ mkSQLSelect jsonAggSelect sel
|
|
|
|
|
2021-04-22 00:44:37 +03:00
|
|
|
selectAggregateQuerySQL
|
|
|
|
:: forall pgKind
|
2021-05-21 05:46:58 +03:00
|
|
|
. (Backend ('Postgres pgKind), PostgresAnnotatedFieldJSON pgKind)
|
2021-04-22 00:44:37 +03:00
|
|
|
=> AnnAggregateSelect ('Postgres pgKind)
|
|
|
|
-> Q.Query
|
2020-10-29 19:58:13 +03:00
|
|
|
selectAggregateQuerySQL =
|
|
|
|
Q.fromBuilder . toSQL . mkAggregateSelect
|
|
|
|
|
2021-04-22 00:44:37 +03:00
|
|
|
connectionSelectQuerySQL
|
|
|
|
:: forall pgKind
|
2021-05-21 05:46:58 +03:00
|
|
|
. ( Backend ('Postgres pgKind)
|
|
|
|
, PostgresAnnotatedFieldJSON pgKind
|
|
|
|
)
|
server: IR for DB-DB joins
### Description
This PR adds the required IR for DB to DB joins, based on @paf31 and @0x777 's `feature/db-to-db` branch.
To do so, it also refactors the IR to introduce a new type parameter, `r`, which is used to recursively constructs the `v` parameter of remote QueryDBs. When collecting remote joins, we replace `r` with `Const Void`, indicating at the type level that there cannot be any leftover remote join.
Furthermore, this PR refactors IR.Select for readability, moves some code from IR.Root to IR.Select to avoid having to deal with circular dependencies, and makes it compile by adding `error` in all new cases in the execution pipeline.
The diff doesn't make it clear, but most of Select.hs is actually unchanged. Declarations have just been reordered by topic, in the following order:
- type declarations
- instance declarations
- type aliases
- constructor functions
- traverse functions
https://github.com/hasura/graphql-engine-mono/pull/1580
Co-authored-by: Phil Freeman <630306+paf31@users.noreply.github.com>
GitOrigin-RevId: bbdcb4119cec8bb3fc32f1294f91b8dea0728721
2021-06-18 02:12:11 +03:00
|
|
|
=> ConnectionSelect ('Postgres pgKind) (Const Void) S.SQLExp
|
2021-04-22 00:44:37 +03:00
|
|
|
-> Q.Query
|
2020-10-29 19:58:13 +03:00
|
|
|
connectionSelectQuerySQL =
|
|
|
|
Q.fromBuilder . toSQL . mkConnectionSelect
|
|
|
|
|
2021-04-22 00:44:37 +03:00
|
|
|
asSingleRowJsonResp
|
|
|
|
:: Q.Query
|
|
|
|
-> [Q.PrepArg]
|
|
|
|
-> Q.TxE QErr EncJSON
|
2020-10-29 19:58:13 +03:00
|
|
|
asSingleRowJsonResp query args =
|
|
|
|
encJFromBS . runIdentity . Q.getRow
|
|
|
|
<$> Q.rawQE dmlTxErrorHandler query args True
|
2018-10-31 15:51:20 +03:00
|
|
|
|
|
|
|
|
|
|
|
-- Conversion of SelectQ happens in 2 Stages.
|
|
|
|
-- Stage 1 : Convert input query into an annotated AST
|
|
|
|
-- Stage 2 : Convert annotated AST to SQL Select
|
|
|
|
|
2020-10-27 13:34:31 +03:00
|
|
|
functionToIdentifier :: QualifiedFunction -> Identifier
|
|
|
|
functionToIdentifier = Identifier . qualifiedObjectToText
|
2018-11-16 15:40:23 +03:00
|
|
|
|
2021-04-22 00:44:37 +03:00
|
|
|
selectFromToFromItem :: Identifier -> SelectFrom ('Postgres pgKind) -> S.FromItem
|
2020-06-08 15:13:01 +03:00
|
|
|
selectFromToFromItem pfx = \case
|
2019-10-18 11:29:47 +03:00
|
|
|
FromTable tn -> S.FISimple tn Nothing
|
2020-10-27 13:34:31 +03:00
|
|
|
FromIdentifier i -> S.FIIdentifier i
|
2020-02-13 20:38:23 +03:00
|
|
|
FromFunction qf args defListM ->
|
2019-10-18 11:29:47 +03:00
|
|
|
S.FIFunc $ S.FunctionExp qf (fromTableRowArgs pfx args) $
|
2020-10-27 13:34:31 +03:00
|
|
|
Just $ S.mkFunctionAlias (functionToIdentifier qf) defListM
|
2019-10-18 11:29:47 +03:00
|
|
|
|
2020-02-13 20:38:23 +03:00
|
|
|
-- This function shouldn't be present ideally
|
|
|
|
-- You should be able to retrieve this information
|
2020-06-08 15:13:01 +03:00
|
|
|
-- from the FromItem generated with selectFromToFromItem
|
2020-02-13 20:38:23 +03:00
|
|
|
-- however given from S.FromItem is modelled, it is not
|
|
|
|
-- possible currently
|
2021-04-22 00:44:37 +03:00
|
|
|
selectFromToQual :: SelectFrom ('Postgres pgKind) -> S.Qual
|
2020-06-08 15:13:01 +03:00
|
|
|
selectFromToQual = \case
|
2021-07-07 14:58:37 +03:00
|
|
|
FromTable table -> S.QualTable table
|
2020-10-27 13:34:31 +03:00
|
|
|
FromIdentifier i -> S.QualifiedIdentifier i Nothing
|
|
|
|
FromFunction qf _ _ -> S.QualifiedIdentifier (functionToIdentifier qf) Nothing
|
2018-11-16 15:40:23 +03:00
|
|
|
|
2021-04-22 00:44:37 +03:00
|
|
|
aggregateFieldToExp :: AggregateFields ('Postgres pgKind) -> Bool -> S.SQLExp
|
2021-01-18 16:51:36 +03:00
|
|
|
aggregateFieldToExp aggFlds strfyNum = jsonRow
|
2018-10-31 15:51:20 +03:00
|
|
|
where
|
|
|
|
jsonRow = S.applyJsonBuildObj (concatMap aggToFlds aggFlds)
|
|
|
|
withAls fldName sqlExp = [S.SELit fldName, sqlExp]
|
2018-12-12 15:58:39 +03:00
|
|
|
aggToFlds (FieldName t, fld) = withAls t $ case fld of
|
2018-11-14 15:59:59 +03:00
|
|
|
AFCount cty -> S.SECount cty
|
|
|
|
AFOp aggOp -> aggOpToObj aggOp
|
|
|
|
AFExp e -> S.SELit e
|
|
|
|
|
2020-06-08 15:13:01 +03:00
|
|
|
aggOpToObj (AggregateOp opText flds) =
|
|
|
|
S.applyJsonBuildObj $ concatMap (colFldsToExtr opText) flds
|
2018-10-31 15:51:20 +03:00
|
|
|
|
2021-01-18 16:51:36 +03:00
|
|
|
colFldsToExtr opText (FieldName t, CFCol col ty) =
|
2018-10-31 15:51:20 +03:00
|
|
|
[ S.SELit t
|
2021-01-18 16:51:36 +03:00
|
|
|
, toJSONableExp strfyNum ty False
|
|
|
|
$ S.SEFnApp opText [S.SEIdentifier $ toIdentifier col] Nothing
|
2018-10-31 15:51:20 +03:00
|
|
|
]
|
2020-10-22 23:42:27 +03:00
|
|
|
colFldsToExtr _ (FieldName t, CFExp e) =
|
2018-10-31 15:51:20 +03:00
|
|
|
[ S.SELit t , S.SELit e]
|
|
|
|
|
2019-04-26 11:19:59 +03:00
|
|
|
asSingleRowExtr :: S.Alias -> S.SQLExp
|
|
|
|
asSingleRowExtr col =
|
|
|
|
S.SEFnApp "coalesce" [jsonAgg, S.SELit "null"] Nothing
|
2018-10-31 15:51:20 +03:00
|
|
|
where
|
|
|
|
jsonAgg = S.SEOpApp (S.SQLOp "->")
|
2020-10-27 13:34:31 +03:00
|
|
|
[ S.SEFnApp "json_agg" [S.SEIdentifier $ toIdentifier col] Nothing
|
2018-10-31 15:51:20 +03:00
|
|
|
, S.SEUnsafe "0"
|
|
|
|
]
|
|
|
|
|
2019-08-01 08:09:52 +03:00
|
|
|
withJsonAggExtr
|
2020-06-08 15:13:01 +03:00
|
|
|
:: PermissionLimitSubQuery -> Maybe S.OrderByExp -> S.Alias -> S.SQLExp
|
|
|
|
withJsonAggExtr permLimitSubQuery ordBy alias =
|
2019-08-01 08:09:52 +03:00
|
|
|
-- if select has aggregations then use subquery to apply permission limit
|
2020-06-08 15:13:01 +03:00
|
|
|
case permLimitSubQuery of
|
|
|
|
PLSQRequired permLimit -> withPermLimit permLimit
|
|
|
|
PLSQNotRequired -> simpleJsonAgg
|
2018-10-31 15:51:20 +03:00
|
|
|
where
|
2019-08-01 08:09:52 +03:00
|
|
|
simpleJsonAgg = mkSimpleJsonAgg rowIdenExp ordBy
|
2020-10-27 13:34:31 +03:00
|
|
|
rowIdenExp = S.SEIdentifier $ S.getAlias alias
|
|
|
|
subSelAls = Identifier "sub_query"
|
|
|
|
unnestTable = Identifier "unnest_table"
|
2019-04-26 11:19:59 +03:00
|
|
|
|
|
|
|
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
|
2020-10-27 13:34:31 +03:00
|
|
|
rowIdentifier = S.mkQIdenExp subSelAls alias
|
|
|
|
extr = S.Extractor (mkSimpleJsonAgg rowIdentifier newOrderBy) Nothing
|
2019-04-26 11:19:59 +03:00
|
|
|
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
|
2020-06-08 15:13:01 +03:00
|
|
|
, S.selOrderBy = newOrderBy
|
2019-04-26 11:19:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
unnestFromItem =
|
|
|
|
let arrayAggItems = flip map (rowIdenExp : obCols) $
|
|
|
|
\s -> S.SEFnApp "array_agg" [s] Nothing
|
|
|
|
in S.FIUnnest arrayAggItems (S.Alias unnestTable) $
|
2020-10-27 13:34:31 +03:00
|
|
|
rowIdenExp : map S.SEIdentifier newOBAliases
|
2019-04-26 11:19:59 +03:00
|
|
|
|
2020-06-08 15:13:01 +03:00
|
|
|
newOrderBy = S.OrderByExp <$> NE.nonEmpty newOBItems
|
2019-04-26 11:19:59 +03:00
|
|
|
|
2020-06-08 15:13:01 +03:00
|
|
|
(newOBItems, obCols, newOBAliases) = maybe ([], [], []) transformOrderBy ordBy
|
|
|
|
transformOrderBy (S.OrderByExp l) = unzip3 $
|
|
|
|
flip map (zip (toList l) [1..]) $ \(obItem, i::Int) ->
|
2020-12-17 14:37:16 +03:00
|
|
|
let iden = Identifier $ "ob_col_" <> tshow i
|
2020-10-27 13:34:31 +03:00
|
|
|
in ( obItem{S.oColumn = S.SEIdentifier iden}
|
2019-04-26 11:19:59 +03:00
|
|
|
, S.oColumn obItem
|
|
|
|
, iden
|
|
|
|
)
|
|
|
|
|
|
|
|
asJsonAggExtr
|
2020-06-08 15:13:01 +03:00
|
|
|
:: JsonAggSelect -> S.Alias -> PermissionLimitSubQuery -> Maybe S.OrderByExp -> S.Extractor
|
|
|
|
asJsonAggExtr jsonAggSelect als permLimitSubQuery ordByExpM =
|
2020-02-13 20:38:23 +03:00
|
|
|
flip S.Extractor (Just als) $ case jsonAggSelect of
|
2020-06-08 15:13:01 +03:00
|
|
|
JASMultipleRows -> withJsonAggExtr permLimitSubQuery ordByExpM als
|
2020-02-13 20:38:23 +03:00
|
|
|
JASSingleObject -> asSingleRowExtr als
|
2018-10-31 15:51:20 +03:00
|
|
|
|
|
|
|
-- array relationships are not grouped, so have to be prefixed by
|
|
|
|
-- parent's alias
|
2020-10-27 13:34:31 +03:00
|
|
|
mkUniqArrayRelationAlias :: FieldName -> [FieldName] -> Identifier
|
2020-06-08 15:13:01 +03:00
|
|
|
mkUniqArrayRelationAlias parAls flds =
|
|
|
|
let sortedFields = sort flds
|
2020-10-27 13:34:31 +03:00
|
|
|
in Identifier $
|
2020-06-08 15:13:01 +03:00
|
|
|
getFieldNameTxt parAls <> "."
|
|
|
|
<> T.intercalate "." (map getFieldNameTxt sortedFields)
|
|
|
|
|
2020-10-27 13:34:31 +03:00
|
|
|
mkArrayRelationTableAlias :: Identifier -> FieldName -> [FieldName] -> Identifier
|
2020-06-08 15:13:01 +03:00
|
|
|
mkArrayRelationTableAlias pfx parAls flds =
|
2020-10-27 13:34:31 +03:00
|
|
|
pfx <> Identifier ".ar." <> uniqArrRelAls
|
2018-10-31 15:51:20 +03:00
|
|
|
where
|
2020-06-08 15:13:01 +03:00
|
|
|
uniqArrRelAls = mkUniqArrayRelationAlias parAls flds
|
2018-10-31 15:51:20 +03:00
|
|
|
|
2020-10-27 13:34:31 +03:00
|
|
|
mkObjectRelationTableAlias :: Identifier -> RelName -> Identifier
|
2020-06-08 15:13:01 +03:00
|
|
|
mkObjectRelationTableAlias pfx relName =
|
2020-10-27 13:34:31 +03:00
|
|
|
pfx <> Identifier ".or." <> toIdentifier relName
|
2018-10-31 15:51:20 +03:00
|
|
|
|
2020-10-27 13:34:31 +03:00
|
|
|
mkComputedFieldTableAlias :: Identifier -> FieldName -> Identifier
|
2020-06-08 15:13:01 +03:00
|
|
|
mkComputedFieldTableAlias pfx fldAls =
|
2020-10-27 13:34:31 +03:00
|
|
|
pfx <> Identifier ".cf." <> toIdentifier fldAls
|
2019-10-18 11:29:47 +03:00
|
|
|
|
2020-10-27 13:34:31 +03:00
|
|
|
mkBaseTableAlias :: Identifier -> Identifier
|
2020-06-08 15:13:01 +03:00
|
|
|
mkBaseTableAlias pfx =
|
2020-10-27 13:34:31 +03:00
|
|
|
pfx <> Identifier ".base"
|
2018-10-31 15:51:20 +03:00
|
|
|
|
2020-10-27 13:34:31 +03:00
|
|
|
mkBaseTableColumnAlias :: Identifier -> PGCol -> Identifier
|
2020-06-08 15:13:01 +03:00
|
|
|
mkBaseTableColumnAlias pfx pgColumn =
|
2020-10-27 13:34:31 +03:00
|
|
|
pfx <> Identifier ".pg." <> toIdentifier pgColumn
|
2018-10-31 15:51:20 +03:00
|
|
|
|
2019-10-05 07:00:53 +03:00
|
|
|
mkOrderByFieldName :: RelName -> FieldName
|
|
|
|
mkOrderByFieldName relName =
|
|
|
|
FieldName $ relNameToTxt relName <> "." <> "order_by"
|
2018-12-12 15:58:39 +03:00
|
|
|
|
2021-04-22 00:44:37 +03:00
|
|
|
mkAggregateOrderByAlias :: AnnAggregateOrderBy ('Postgres pgKind) -> S.Alias
|
2020-10-27 13:34:31 +03:00
|
|
|
mkAggregateOrderByAlias = (S.Alias . Identifier) . \case
|
2020-10-21 19:35:06 +03:00
|
|
|
AAOCount -> "count"
|
2020-06-08 15:13:01 +03:00
|
|
|
AAOOp opText col -> opText <> "." <> getPGColTxt (pgiColumn col)
|
|
|
|
|
|
|
|
mkArrayRelationSourcePrefix
|
2020-10-27 13:34:31 +03:00
|
|
|
:: Identifier
|
2020-06-08 15:13:01 +03:00
|
|
|
-> FieldName
|
|
|
|
-> HM.HashMap FieldName [FieldName]
|
|
|
|
-> FieldName
|
2020-10-27 13:34:31 +03:00
|
|
|
-> Identifier
|
2020-06-08 15:13:01 +03:00
|
|
|
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
|
|
|
|
|
2019-10-18 11:29:47 +03:00
|
|
|
fromTableRowArgs
|
2021-04-22 00:44:37 +03:00
|
|
|
:: Identifier -> FunctionArgsExpTableRow ('Postgres pgKind) S.SQLExp -> S.FunctionArgs
|
2019-10-18 11:29:47 +03:00
|
|
|
fromTableRowArgs pfx = toFunctionArgs . fmap toSQLExp
|
|
|
|
where
|
|
|
|
toFunctionArgs (FunctionArgsExp positional named) =
|
|
|
|
S.FunctionArgs positional named
|
2020-10-27 13:34:31 +03:00
|
|
|
toSQLExp (AETableRow Nothing) = S.SERowIdentifier $ mkBaseTableAlias pfx
|
2020-06-08 15:13:01 +03:00
|
|
|
toSQLExp (AETableRow (Just acc)) = S.mkQIdenExp (mkBaseTableAlias pfx) acc
|
2020-04-27 18:07:03 +03:00
|
|
|
toSQLExp (AESession s) = s
|
2020-02-13 20:38:23 +03:00
|
|
|
toSQLExp (AEInput s) = s
|
2019-10-18 11:29:47 +03:00
|
|
|
|
2018-10-31 15:51:20 +03:00
|
|
|
-- 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
|
|
|
|
|
|
|
|
-- 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
|
|
|
|
|
2020-06-08 15:13:01 +03:00
|
|
|
-- | 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
|
|
|
|
|
|
|
|
mkAggregateOrderByExtractorAndFields
|
2021-04-22 00:44:37 +03:00
|
|
|
:: forall pgKind
|
|
|
|
. Backend ('Postgres pgKind)
|
|
|
|
=> AnnAggregateOrderBy ('Postgres pgKind)
|
|
|
|
-> (S.Extractor, AggregateFields ('Postgres pgKind))
|
2020-06-08 15:13:01 +03:00
|
|
|
mkAggregateOrderByExtractorAndFields annAggOrderBy =
|
|
|
|
case annAggOrderBy of
|
|
|
|
AAOCount ->
|
|
|
|
( S.Extractor S.countStar alias
|
|
|
|
, [(FieldName "count", AFCount S.CTStar)]
|
|
|
|
)
|
|
|
|
AAOOp opText pgColumnInfo ->
|
|
|
|
let pgColumn = pgiColumn pgColumnInfo
|
2021-01-18 16:51:36 +03:00
|
|
|
pgType = pgiType pgColumnInfo
|
2020-10-27 13:34:31 +03:00
|
|
|
in ( S.Extractor (S.SEFnApp opText [S.SEIdentifier $ toIdentifier pgColumn] Nothing) alias
|
2021-01-18 16:51:36 +03:00
|
|
|
, [ ( FieldName opText
|
|
|
|
, AFOp $ AggregateOp opText
|
2021-04-22 00:44:37 +03:00
|
|
|
[ ( fromCol @('Postgres pgKind) pgColumn
|
2021-01-18 16:51:36 +03:00
|
|
|
, CFCol pgColumn pgType
|
|
|
|
)
|
|
|
|
]
|
|
|
|
)
|
|
|
|
]
|
2020-06-08 15:13:01 +03:00
|
|
|
)
|
2018-12-12 15:58:39 +03:00
|
|
|
where
|
2020-06-08 15:13:01 +03:00
|
|
|
alias = Just $ mkAggregateOrderByAlias annAggOrderBy
|
|
|
|
|
|
|
|
mkAnnOrderByAlias
|
2021-04-22 00:44:37 +03:00
|
|
|
:: Identifier -> FieldName -> SimilarArrayFields -> AnnOrderByElementG ('Postgres pgKind) v -> S.Alias
|
2020-06-08 15:13:01 +03:00
|
|
|
mkAnnOrderByAlias pfx parAls similarFields = \case
|
|
|
|
AOCColumn pgColumnInfo ->
|
|
|
|
let pgColumn = pgiColumn pgColumnInfo
|
|
|
|
obColAls = mkBaseTableColumnAlias pfx pgColumn
|
|
|
|
in S.Alias obColAls
|
2018-10-31 15:51:20 +03:00
|
|
|
-- "pfx.or.relname"."pfx.ob.or.relname.rest" AS "pfx.ob.or.relname.rest"
|
2020-06-08 15:13:01 +03:00
|
|
|
AOCObjectRelation relInfo _ rest ->
|
|
|
|
let rn = riName relInfo
|
|
|
|
relPfx = mkObjectRelationTableAlias pfx rn
|
2019-10-05 07:00:53 +03:00
|
|
|
ordByFldName = mkOrderByFieldName rn
|
2020-06-08 15:13:01 +03:00
|
|
|
nesAls = mkAnnOrderByAlias relPfx ordByFldName mempty rest
|
|
|
|
in nesAls
|
|
|
|
AOCArrayAggregation relInfo _ aggOrderBy ->
|
|
|
|
let rn = riName relInfo
|
|
|
|
arrPfx = mkArrayRelationSourcePrefix pfx parAls similarFields $
|
|
|
|
mkOrderByFieldName rn
|
2020-10-27 13:34:31 +03:00
|
|
|
obAls = arrPfx <> Identifier "." <> toIdentifier (mkAggregateOrderByAlias aggOrderBy)
|
2020-06-08 15:13:01 +03:00
|
|
|
in S.Alias obAls
|
|
|
|
|
|
|
|
processDistinctOnColumns
|
2020-10-27 13:34:31 +03:00
|
|
|
:: Identifier
|
2018-11-23 04:53:56 +03:00
|
|
|
-> NE.NonEmpty PGCol
|
|
|
|
-> ( S.DistinctExpr
|
2020-06-08 15:13:01 +03:00
|
|
|
, [(S.Alias, S.SQLExp)] -- additional column extractors
|
2018-11-23 04:53:56 +03:00
|
|
|
)
|
2020-06-08 15:13:01 +03:00
|
|
|
processDistinctOnColumns pfx neCols = (distOnExp, colExtrs)
|
2018-11-23 04:53:56 +03:00
|
|
|
where
|
|
|
|
cols = toList neCols
|
2020-10-27 13:34:31 +03:00
|
|
|
distOnExp = S.DistinctOn $ map (S.SEIdentifier . toIdentifier . mkQColAls) cols
|
|
|
|
mkQCol c = S.mkQIdenExp (mkBaseTableAlias pfx) $ toIdentifier c
|
2020-06-08 15:13:01 +03:00
|
|
|
mkQColAls = S.Alias . mkBaseTableColumnAlias pfx
|
2018-11-23 04:53:56 +03:00
|
|
|
colExtrs = flip map cols $ mkQColAls &&& mkQCol
|
|
|
|
|
2020-06-08 15:13:01 +03:00
|
|
|
type SimilarArrayFields = HM.HashMap FieldName [FieldName]
|
2018-11-23 04:53:56 +03:00
|
|
|
|
2020-06-08 15:13:01 +03:00
|
|
|
mkSimilarArrayFields
|
2021-04-22 00:44:37 +03:00
|
|
|
:: forall pgKind v
|
|
|
|
. (Backend ('Postgres pgKind), Eq v)
|
server: IR for DB-DB joins
### Description
This PR adds the required IR for DB to DB joins, based on @paf31 and @0x777 's `feature/db-to-db` branch.
To do so, it also refactors the IR to introduce a new type parameter, `r`, which is used to recursively constructs the `v` parameter of remote QueryDBs. When collecting remote joins, we replace `r` with `Const Void`, indicating at the type level that there cannot be any leftover remote join.
Furthermore, this PR refactors IR.Select for readability, moves some code from IR.Root to IR.Select to avoid having to deal with circular dependencies, and makes it compile by adding `error` in all new cases in the execution pipeline.
The diff doesn't make it clear, but most of Select.hs is actually unchanged. Declarations have just been reordered by topic, in the following order:
- type declarations
- instance declarations
- type aliases
- constructor functions
- traverse functions
https://github.com/hasura/graphql-engine-mono/pull/1580
Co-authored-by: Phil Freeman <630306+paf31@users.noreply.github.com>
GitOrigin-RevId: bbdcb4119cec8bb3fc32f1294f91b8dea0728721
2021-06-18 02:12:11 +03:00
|
|
|
=> AnnFieldsG ('Postgres pgKind) (Const Void) v
|
2021-04-22 00:44:37 +03:00
|
|
|
-> Maybe (NE.NonEmpty (AnnOrderByItemG ('Postgres pgKind) v))
|
2020-06-08 15:13:01 +03:00
|
|
|
-> SimilarArrayFields
|
|
|
|
mkSimilarArrayFields annFields maybeOrderBys =
|
|
|
|
HM.fromList $ flip map allTuples $
|
|
|
|
\(relNameAndArgs, fieldName) -> (fieldName, getSimilarFields relNameAndArgs)
|
2018-10-31 15:51:20 +03:00
|
|
|
where
|
2020-06-08 15:13:01 +03:00
|
|
|
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)
|
|
|
|
|
|
|
|
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
|
|
|
|
|
server: IR for DB-DB joins
### Description
This PR adds the required IR for DB to DB joins, based on @paf31 and @0x777 's `feature/db-to-db` branch.
To do so, it also refactors the IR to introduce a new type parameter, `r`, which is used to recursively constructs the `v` parameter of remote QueryDBs. When collecting remote joins, we replace `r` with `Const Void`, indicating at the type level that there cannot be any leftover remote join.
Furthermore, this PR refactors IR.Select for readability, moves some code from IR.Root to IR.Select to avoid having to deal with circular dependencies, and makes it compile by adding `error` in all new cases in the execution pipeline.
The diff doesn't make it clear, but most of Select.hs is actually unchanged. Declarations have just been reordered by topic, in the following order:
- type declarations
- instance declarations
- type aliases
- constructor functions
- traverse functions
https://github.com/hasura/graphql-engine-mono/pull/1580
Co-authored-by: Phil Freeman <630306+paf31@users.noreply.github.com>
GitOrigin-RevId: bbdcb4119cec8bb3fc32f1294f91b8dea0728721
2021-06-18 02:12:11 +03:00
|
|
|
getArrayRelNameAndSelectArgs
|
|
|
|
:: ArraySelectG ('Postgres pgKind) r v
|
|
|
|
-> (RelName, SelectArgsG ('Postgres pgKind) v)
|
2020-06-08 15:13:01 +03:00
|
|
|
getArrayRelNameAndSelectArgs = \case
|
2020-10-21 19:35:06 +03:00
|
|
|
ASSimple r -> (aarRelationshipName r, _asnArgs $ aarAnnSelect r)
|
|
|
|
ASAggregate r -> (aarRelationshipName r, _asnArgs $ aarAnnSelect r)
|
2020-06-08 15:13:01 +03:00
|
|
|
ASConnection r -> (aarRelationshipName r, _asnArgs $ _csSelect $ aarAnnSelect r)
|
|
|
|
|
server: IR for DB-DB joins
### Description
This PR adds the required IR for DB to DB joins, based on @paf31 and @0x777 's `feature/db-to-db` branch.
To do so, it also refactors the IR to introduce a new type parameter, `r`, which is used to recursively constructs the `v` parameter of remote QueryDBs. When collecting remote joins, we replace `r` with `Const Void`, indicating at the type level that there cannot be any leftover remote join.
Furthermore, this PR refactors IR.Select for readability, moves some code from IR.Root to IR.Select to avoid having to deal with circular dependencies, and makes it compile by adding `error` in all new cases in the execution pipeline.
The diff doesn't make it clear, but most of Select.hs is actually unchanged. Declarations have just been reordered by topic, in the following order:
- type declarations
- instance declarations
- type aliases
- constructor functions
- traverse functions
https://github.com/hasura/graphql-engine-mono/pull/1580
Co-authored-by: Phil Freeman <630306+paf31@users.noreply.github.com>
GitOrigin-RevId: bbdcb4119cec8bb3fc32f1294f91b8dea0728721
2021-06-18 02:12:11 +03:00
|
|
|
getAnnArr
|
|
|
|
:: (a, AnnFieldG ('Postgres pgKind) r v)
|
|
|
|
-> Maybe (a, ArraySelectG ('Postgres pgKind) r v)
|
2020-06-08 15:13:01 +03:00
|
|
|
getAnnArr (f, annFld) = case annFld of
|
|
|
|
AFArrayRelation (ASConnection _) -> Nothing
|
|
|
|
AFArrayRelation ar -> Just (f, ar)
|
|
|
|
_ -> Nothing
|
|
|
|
|
|
|
|
|
|
|
|
withWriteJoinTree
|
2021-03-25 20:50:08 +03:00
|
|
|
:: (MonadWriter JoinTree m)
|
|
|
|
=> (JoinTree -> b -> JoinTree)
|
2020-06-08 15:13:01 +03:00
|
|
|
-> m (a, b)
|
|
|
|
-> m a
|
|
|
|
withWriteJoinTree joinTreeUpdater action =
|
|
|
|
pass $ do
|
|
|
|
(out, result) <- action
|
|
|
|
let fromJoinTree joinTree =
|
|
|
|
joinTreeUpdater joinTree result
|
|
|
|
pure (out, fromJoinTree)
|
|
|
|
|
|
|
|
withWriteObjectRelation
|
2021-03-25 20:50:08 +03:00
|
|
|
:: (MonadWriter JoinTree m)
|
|
|
|
=> m ( ObjectRelationSource
|
2020-06-08 15:13:01 +03:00
|
|
|
, HM.HashMap S.Alias S.SQLExp
|
|
|
|
, a
|
|
|
|
)
|
|
|
|
-> m a
|
|
|
|
withWriteObjectRelation action =
|
|
|
|
withWriteJoinTree updateJoinTree $ do
|
|
|
|
(source, nodeExtractors, out) <- action
|
|
|
|
pure (out, (source, nodeExtractors))
|
2018-10-31 15:51:20 +03:00
|
|
|
where
|
2020-06-08 15:13:01 +03:00
|
|
|
updateJoinTree joinTree (source, nodeExtractors) =
|
|
|
|
let selectNode = SelectNode nodeExtractors joinTree
|
|
|
|
in mempty{_jtObjectRelations = HM.singleton source selectNode}
|
|
|
|
|
|
|
|
withWriteArrayRelation
|
2021-03-25 20:50:08 +03:00
|
|
|
:: (MonadWriter JoinTree m)
|
|
|
|
=> m ( ArrayRelationSource
|
2020-06-08 15:13:01 +03:00
|
|
|
, 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))
|
2019-08-01 08:09:52 +03:00
|
|
|
where
|
2020-06-08 15:13:01 +03:00
|
|
|
updateJoinTree joinTree (source, topExtractor, nodeExtractors) =
|
|
|
|
let arraySelectNode = ArraySelectNode [topExtractor] $
|
|
|
|
SelectNode nodeExtractors joinTree
|
|
|
|
in mempty{_jtArrayRelations = HM.singleton source arraySelectNode}
|
|
|
|
|
|
|
|
withWriteArrayConnection
|
2021-03-25 20:50:08 +03:00
|
|
|
:: (MonadWriter JoinTree m)
|
|
|
|
=> m ( ArrayConnectionSource
|
2020-06-08 15:13:01 +03:00
|
|
|
, 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))
|
2018-12-12 15:58:39 +03:00
|
|
|
where
|
2020-06-08 15:13:01 +03:00
|
|
|
updateJoinTree joinTree (source, topExtractor, nodeExtractors) =
|
|
|
|
let arraySelectNode = ArraySelectNode [topExtractor] $
|
|
|
|
SelectNode nodeExtractors joinTree
|
|
|
|
in mempty{_jtArrayConnections = HM.singleton source arraySelectNode}
|
|
|
|
|
|
|
|
withWriteComputedFieldTableSet
|
2021-03-25 20:50:08 +03:00
|
|
|
:: (MonadWriter JoinTree m)
|
|
|
|
=> m ( ComputedFieldTableSetSource
|
2020-06-08 15:13:01 +03:00
|
|
|
, HM.HashMap S.Alias S.SQLExp
|
|
|
|
, a
|
|
|
|
)
|
|
|
|
-> m a
|
|
|
|
withWriteComputedFieldTableSet action =
|
|
|
|
withWriteJoinTree updateJoinTree $ do
|
|
|
|
(source, nodeExtractors, out) <- action
|
|
|
|
pure (out, (source, nodeExtractors))
|
2018-12-12 15:58:39 +03:00
|
|
|
where
|
2020-06-08 15:13:01 +03:00
|
|
|
updateJoinTree joinTree (source, nodeExtractors) =
|
|
|
|
let selectNode = SelectNode nodeExtractors joinTree
|
|
|
|
in mempty{_jtComputedFieldTableSets = HM.singleton source selectNode}
|
2018-12-12 15:58:39 +03:00
|
|
|
|
|
|
|
|
2020-06-08 15:13:01 +03:00
|
|
|
processAnnSimpleSelect
|
2021-04-22 00:44:37 +03:00
|
|
|
:: forall pgKind m
|
|
|
|
. ( MonadReader Bool m
|
|
|
|
, MonadWriter JoinTree m
|
|
|
|
, Backend ('Postgres pgKind)
|
2021-05-21 05:46:58 +03:00
|
|
|
, PostgresAnnotatedFieldJSON pgKind
|
2021-04-22 00:44:37 +03:00
|
|
|
)
|
2020-06-08 15:13:01 +03:00
|
|
|
=> SourcePrefixes
|
|
|
|
-> FieldName
|
|
|
|
-> PermissionLimitSubQuery
|
2021-04-22 00:44:37 +03:00
|
|
|
-> AnnSimpleSel ('Postgres pgKind)
|
2021-03-25 20:50:08 +03:00
|
|
|
-> m ( SelectSource
|
2020-06-08 15:13:01 +03:00
|
|
|
, 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)
|
2018-12-12 15:58:39 +03:00
|
|
|
where
|
2020-06-08 15:13:01 +03:00
|
|
|
AnnSelectG annSelFields tableFrom tablePermissions tableArgs _ = annSimpleSel
|
|
|
|
similarArrayFields =
|
|
|
|
mkSimilarArrayFields annSelFields $ _saOrderBy tableArgs
|
|
|
|
|
|
|
|
processAnnAggregateSelect
|
2021-04-22 00:44:37 +03:00
|
|
|
:: forall pgKind m
|
|
|
|
. ( MonadReader Bool m
|
|
|
|
, MonadWriter JoinTree m
|
|
|
|
, Backend ('Postgres pgKind)
|
2021-05-21 05:46:58 +03:00
|
|
|
, PostgresAnnotatedFieldJSON pgKind
|
2021-04-22 00:44:37 +03:00
|
|
|
)
|
2020-06-08 15:13:01 +03:00
|
|
|
=> SourcePrefixes
|
|
|
|
-> FieldName
|
2021-04-22 00:44:37 +03:00
|
|
|
-> AnnAggregateSelect ('Postgres pgKind)
|
2021-03-25 20:50:08 +03:00
|
|
|
-> m ( SelectSource
|
2020-06-08 15:13:01 +03:00
|
|
|
, 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
|
2021-01-18 16:51:36 +03:00
|
|
|
, aggregateFieldToExp aggFields strfyNum
|
2020-06-08 15:13:01 +03:00
|
|
|
)
|
2021-02-03 19:17:20 +03:00
|
|
|
TAFNodes _ annFields -> do
|
2020-06-08 15:13:01 +03:00
|
|
|
annFieldExtr <- processAnnFields thisSourcePrefix fieldName similarArrayFields annFields
|
|
|
|
pure ( [annFieldExtr]
|
|
|
|
, withJsonAggExtr permLimitSubQuery (_ssOrderBy selectSource) $
|
2020-10-27 13:34:31 +03:00
|
|
|
S.Alias $ toIdentifier fieldName
|
2020-06-08 15:13:01 +03:00
|
|
|
)
|
|
|
|
TAFExp e ->
|
|
|
|
pure ( []
|
|
|
|
, withForceAggregation S.textTypeAnn $ S.SELit e
|
|
|
|
)
|
2018-12-12 15:58:39 +03:00
|
|
|
|
2020-06-08 15:13:01 +03:00
|
|
|
let topLevelExtractor =
|
2020-10-27 13:34:31 +03:00
|
|
|
flip S.Extractor (Just $ S.Alias $ toIdentifier fieldAlias) $
|
2020-06-08 15:13:01 +03:00
|
|
|
S.applyJsonBuildObj $ flip concatMap (map (second snd) processedFields) $
|
|
|
|
\(FieldName fieldText, fieldExp) -> [S.SELit fieldText, fieldExp]
|
|
|
|
nodeExtractors = HM.fromList $
|
|
|
|
concatMap (fst . snd) processedFields <> orderByAndDistinctExtrs
|
2018-12-12 15:58:39 +03:00
|
|
|
|
2020-06-08 15:13:01 +03:00
|
|
|
pure (selectSource, nodeExtractors, topLevelExtractor)
|
|
|
|
where
|
2021-01-18 16:51:36 +03:00
|
|
|
AnnSelectG aggSelFields tableFrom tablePermissions tableArgs strfyNum = annAggSel
|
2020-06-08 15:13:01 +03:00
|
|
|
permLimit = _tpLimit tablePermissions
|
|
|
|
orderBy = _saOrderBy tableArgs
|
|
|
|
permLimitSubQuery = mkPermissionLimitSubQuery permLimit aggSelFields orderBy
|
|
|
|
similarArrayFields = HM.unions $
|
|
|
|
flip map (map snd aggSelFields) $ \case
|
|
|
|
TAFAgg _ -> mempty
|
2021-02-03 19:17:20 +03:00
|
|
|
TAFNodes _ annFlds ->
|
2020-06-08 15:13:01 +03:00
|
|
|
mkSimilarArrayFields annFlds orderBy
|
|
|
|
TAFExp _ -> mempty
|
|
|
|
|
|
|
|
mkPermissionLimitSubQuery
|
|
|
|
:: Maybe Int
|
2021-04-22 00:44:37 +03:00
|
|
|
-> TableAggregateFields ('Postgres pgKind)
|
|
|
|
-> Maybe (NE.NonEmpty (AnnOrderByItem ('Postgres pgKind)))
|
2020-06-08 15:13:01 +03:00
|
|
|
-> 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
|
2020-10-21 19:35:06 +03:00
|
|
|
_ -> False
|
2020-06-08 15:13:01 +03:00
|
|
|
|
|
|
|
processArrayRelation
|
2021-04-22 00:44:37 +03:00
|
|
|
:: forall pgKind m
|
|
|
|
. ( MonadReader Bool m
|
|
|
|
, MonadWriter JoinTree m
|
|
|
|
, Backend ('Postgres pgKind)
|
2021-05-21 05:46:58 +03:00
|
|
|
, PostgresAnnotatedFieldJSON pgKind
|
2021-04-22 00:44:37 +03:00
|
|
|
)
|
2020-06-08 15:13:01 +03:00
|
|
|
=> SourcePrefixes
|
2019-08-01 08:09:52 +03:00
|
|
|
-> FieldName
|
2020-06-08 15:13:01 +03:00
|
|
|
-> S.Alias
|
2021-04-22 00:44:37 +03:00
|
|
|
-> ArraySelect ('Postgres pgKind)
|
2020-06-08 15:13:01 +03:00
|
|
|
-> 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 $ _ssOrderBy 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
|
|
|
|
, ()
|
|
|
|
)
|
|
|
|
|
|
|
|
processSelectParams
|
2021-04-22 00:44:37 +03:00
|
|
|
:: forall pgKind m
|
|
|
|
. ( MonadReader Bool m
|
|
|
|
, MonadWriter JoinTree m
|
|
|
|
, Backend ('Postgres pgKind)
|
|
|
|
)
|
2020-06-08 15:13:01 +03:00
|
|
|
=> SourcePrefixes
|
|
|
|
-> FieldName
|
|
|
|
-> SimilarArrayFields
|
2021-04-22 00:44:37 +03:00
|
|
|
-> SelectFrom ('Postgres pgKind)
|
2020-06-08 15:13:01 +03:00
|
|
|
-> PermissionLimitSubQuery
|
2021-04-22 00:44:37 +03:00
|
|
|
-> TablePerm ('Postgres pgKind)
|
|
|
|
-> SelectArgs ('Postgres pgKind)
|
2021-03-25 20:50:08 +03:00
|
|
|
-> m ( SelectSource
|
2020-06-08 15:13:01 +03:00
|
|
|
, [(S.Alias, S.SQLExp)]
|
|
|
|
, Maybe S.SQLExp -- Order by cursor
|
|
|
|
)
|
|
|
|
processSelectParams sourcePrefixes fieldAlias similarArrFields selectFrom
|
|
|
|
permLimitSubQ tablePermissions tableArgs = do
|
|
|
|
maybeOrderBy <- mapM
|
|
|
|
(processOrderByItems thisSourcePrefix fieldAlias similarArrFields)
|
|
|
|
orderByM
|
|
|
|
let fromItem = selectFromToFromItem (_pfBase sourcePrefixes) selectFrom
|
|
|
|
(maybeDistinct, distinctExtrs) =
|
2021-06-15 18:53:20 +03:00
|
|
|
maybe (Nothing, []) (first Just) $ processDistinctOnColumns thisSourcePrefix <$> distM
|
2020-06-08 15:13:01 +03:00
|
|
|
finalWhere = toSQLBoolExp (selectFromToQual selectFrom) $
|
|
|
|
maybe permFilter (andAnnBoolExps permFilter) whereM
|
|
|
|
selectSource = SelectSource thisSourcePrefix fromItem maybeDistinct finalWhere
|
|
|
|
((^. _2) <$> maybeOrderBy) finalLimit offsetM
|
|
|
|
orderByExtrs = maybe [] (^. _1) maybeOrderBy
|
|
|
|
pure ( selectSource
|
|
|
|
, orderByExtrs <> distinctExtrs
|
|
|
|
, (^. _3) <$> maybeOrderBy
|
|
|
|
)
|
2018-10-31 15:51:20 +03:00
|
|
|
where
|
2020-06-08 15:13:01 +03:00
|
|
|
thisSourcePrefix = _pfThis sourcePrefixes
|
|
|
|
SelectArgs whereM orderByM inpLimitM offsetM distM = tableArgs
|
|
|
|
TablePerm permFilter permLimit = tablePermissions
|
|
|
|
finalLimit =
|
|
|
|
-- if sub query is required, then only use input limit
|
2019-08-01 08:09:52 +03:00
|
|
|
-- because permission limit is being applied in subquery
|
|
|
|
-- else compare input and permission limits
|
2020-06-08 15:13:01 +03:00
|
|
|
case permLimitSubQ of
|
|
|
|
PLSQRequired _ -> inpLimitM
|
|
|
|
PLSQNotRequired -> compareLimits
|
2019-08-01 08:09:52 +03:00
|
|
|
|
2020-06-08 15:13:01 +03:00
|
|
|
compareLimits =
|
2019-08-01 08:09:52 +03:00
|
|
|
case (inpLimitM, permLimit) of
|
|
|
|
(inpLim, Nothing) -> inpLim
|
|
|
|
(Nothing, permLim) -> permLim
|
|
|
|
(Just inp, Just perm) -> Just $ if inp < perm then inp else perm
|
|
|
|
|
2020-06-08 15:13:01 +03:00
|
|
|
processOrderByItems
|
2021-04-22 00:44:37 +03:00
|
|
|
:: forall pgKind m
|
|
|
|
. ( MonadReader Bool m
|
|
|
|
, MonadWriter JoinTree m
|
|
|
|
, Backend ('Postgres pgKind)
|
|
|
|
)
|
2020-10-27 13:34:31 +03:00
|
|
|
=> Identifier
|
2020-06-08 15:13:01 +03:00
|
|
|
-> FieldName
|
|
|
|
-> SimilarArrayFields
|
2021-04-22 00:44:37 +03:00
|
|
|
-> NE.NonEmpty (AnnOrderByItem ('Postgres pgKind))
|
2020-06-08 15:13:01 +03:00
|
|
|
-> m ( [(S.Alias, S.SQLExp)] -- Order by Extractors
|
|
|
|
, S.OrderByExp
|
|
|
|
, S.SQLExp -- The cursor expression
|
|
|
|
)
|
|
|
|
processOrderByItems sourcePrefix' fieldAlias' similarArrayFields orderByItems = do
|
|
|
|
orderByItemExps <- forM orderByItems processAnnOrderByItem
|
|
|
|
let orderByExp = S.OrderByExp $ toOrderByExp <$> orderByItemExps
|
|
|
|
orderByExtractors = concat $ toList $ map snd . toList <$> orderByItemExps
|
|
|
|
cursor = mkCursorExp $ toList orderByItemExps
|
|
|
|
pure (orderByExtractors, orderByExp, cursor)
|
|
|
|
where
|
2021-04-22 00:44:37 +03:00
|
|
|
processAnnOrderByItem :: AnnOrderByItem ('Postgres pgKind) -> m (OrderByItemExp ('Postgres pgKind))
|
2020-06-08 15:13:01 +03:00
|
|
|
processAnnOrderByItem orderByItem =
|
|
|
|
forM orderByItem $ \ordByCol -> (ordByCol,) <$>
|
|
|
|
processAnnOrderByElement sourcePrefix' fieldAlias' ordByCol
|
|
|
|
|
|
|
|
processAnnOrderByElement
|
2021-04-22 00:44:37 +03:00
|
|
|
:: Identifier -> FieldName -> AnnOrderByElement ('Postgres pgKind) S.SQLExp -> m (S.Alias, S.SQLExp)
|
2020-06-08 15:13:01 +03:00
|
|
|
processAnnOrderByElement sourcePrefix fieldAlias annObCol = do
|
|
|
|
let ordByAlias = mkAnnOrderByAlias sourcePrefix fieldAlias similarArrayFields annObCol
|
|
|
|
(ordByAlias, ) <$> case annObCol of
|
|
|
|
AOCColumn pgColInfo -> pure $
|
2020-10-27 13:34:31 +03:00
|
|
|
S.mkQIdenExp (mkBaseTableAlias sourcePrefix) $ toIdentifier $ pgiColumn pgColInfo
|
2020-06-08 15:13:01 +03:00
|
|
|
|
|
|
|
AOCObjectRelation relInfo relFilter rest -> withWriteObjectRelation $ do
|
2021-03-03 16:02:00 +03:00
|
|
|
let RelInfo relName _ colMapping relTable _ _ _ = relInfo
|
2020-06-08 15:13:01 +03:00
|
|
|
relSourcePrefix = mkObjectRelationTableAlias sourcePrefix relName
|
|
|
|
fieldName = mkOrderByFieldName relName
|
|
|
|
(relOrderByAlias, relOrdByExp) <-
|
|
|
|
processAnnOrderByElement relSourcePrefix fieldName rest
|
2020-06-25 06:33:37 +03:00
|
|
|
let selectSource = ObjectSelectSource relSourcePrefix
|
|
|
|
(S.FISimple relTable Nothing)
|
2020-06-08 15:13:01 +03:00
|
|
|
(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
|
2021-03-03 16:02:00 +03:00
|
|
|
let RelInfo relName _ colMapping relTable _ _ _ = relInfo
|
2020-06-08 15:13:01 +03:00
|
|
|
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) Nothing
|
|
|
|
(toSQLBoolExp (S.QualTable relTable) relFilter)
|
|
|
|
Nothing Nothing Nothing
|
|
|
|
relSource = ArrayRelationSource relAlias colMapping selectSource
|
|
|
|
pure ( relSource
|
|
|
|
, topExtractor
|
|
|
|
, HM.fromList $ aggregateFieldsToExtractorExps relSourcePrefix fields
|
|
|
|
, S.mkQIdenExp relSourcePrefix (mkAggregateOrderByAlias aggOrderBy)
|
|
|
|
)
|
|
|
|
|
2021-04-22 00:44:37 +03:00
|
|
|
toOrderByExp :: OrderByItemExp ('Postgres pgKind) -> S.OrderByItem
|
2020-06-08 15:13:01 +03:00
|
|
|
toOrderByExp orderByItemExp =
|
|
|
|
let OrderByItemG obTyM expAlias obNullsM = fst . snd <$> orderByItemExp
|
2020-11-12 12:25:48 +03:00
|
|
|
in S.OrderByItem (S.SEIdentifier $ toIdentifier expAlias) obTyM obNullsM
|
2020-06-08 15:13:01 +03:00
|
|
|
|
2021-04-22 00:44:37 +03:00
|
|
|
mkCursorExp :: [OrderByItemExp ('Postgres pgKind)] -> S.SQLExp
|
2020-06-08 15:13:01 +03:00
|
|
|
mkCursorExp orderByItemExps =
|
|
|
|
S.applyJsonBuildObj $ flip concatMap orderByItemExps $
|
|
|
|
\orderByItemExp ->
|
|
|
|
let OrderByItemG _ (annObCol, (_, valExp)) _ = orderByItemExp
|
|
|
|
in annObColToJSONField valExp annObCol
|
|
|
|
where
|
|
|
|
annObColToJSONField valExp = \case
|
|
|
|
AOCColumn pgCol -> [S.SELit $ getPGColTxt $ pgiColumn 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 $
|
|
|
|
case aggOrderBy of
|
|
|
|
AAOCount -> [S.SELit "count", valExp]
|
|
|
|
AAOOp opText colInfo ->
|
|
|
|
[ S.SELit opText
|
|
|
|
, S.applyJsonBuildObj [S.SELit $ getPGColTxt $ pgiColumn colInfo, valExp]
|
|
|
|
]
|
|
|
|
]
|
|
|
|
|
|
|
|
aggregateFieldsToExtractorExps
|
2021-04-22 00:44:37 +03:00
|
|
|
:: Identifier -> AggregateFields ('Postgres pgKind) -> [(S.Alias, S.SQLExp)]
|
2020-06-08 15:13:01 +03:00
|
|
|
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
|
2021-01-18 16:51:36 +03:00
|
|
|
colsToExps = fmap mkColExp
|
|
|
|
|
|
|
|
aggOpToExps = mapMaybe colToMaybeExp . _aoFields
|
|
|
|
colToMaybeExp = \case
|
|
|
|
(_, CFCol col _) -> Just $ mkColExp col
|
|
|
|
_ -> Nothing
|
2018-10-31 15:51:20 +03:00
|
|
|
|
2021-01-18 16:51:36 +03:00
|
|
|
mkColExp c =
|
2020-10-27 13:34:31 +03:00
|
|
|
let qualCol = S.mkQIdenExp (mkBaseTableAlias sourcePrefix) (toIdentifier c)
|
|
|
|
colAls = toIdentifier c
|
2021-01-18 16:51:36 +03:00
|
|
|
in (S.Alias colAls, qualCol)
|
2018-10-31 15:51:20 +03:00
|
|
|
|
[Preview] Inherited roles for postgres read queries
fixes #3868
docker image - `hasura/graphql-engine:inherited-roles-preview-48b73a2de`
Note:
To be able to use the inherited roles feature, the graphql-engine should be started with the env variable `HASURA_GRAPHQL_EXPERIMENTAL_FEATURES` set to `inherited_roles`.
Introduction
------------
This PR implements the idea of multiple roles as presented in this [paper](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/FGALanguageICDE07.pdf). The multiple roles feature in this PR can be used via inherited roles. An inherited role is a role which can be created by combining multiple singular roles. For example, if there are two roles `author` and `editor` configured in the graphql-engine, then we can create a inherited role with the name of `combined_author_editor` role which will combine the select permissions of the `author` and `editor` roles and then make GraphQL queries using the `combined_author_editor`.
How are select permissions of different roles are combined?
------------------------------------------------------------
A select permission includes 5 things:
1. Columns accessible to the role
2. Row selection filter
3. Limit
4. Allow aggregation
5. Scalar computed fields accessible to the role
Suppose there are two roles, `role1` gives access to the `address` column with row filter `P1` and `role2` gives access to both the `address` and the `phone` column with row filter `P2` and we create a new role `combined_roles` which combines `role1` and `role2`.
Let's say the following GraphQL query is queried with the `combined_roles` role.
```graphql
query {
employees {
address
phone
}
}
```
This will translate to the following SQL query:
```sql
select
(case when (P1 or P2) then address else null end) as address,
(case when P2 then phone else null end) as phone
from employee
where (P1 or P2)
```
The other parameters of the select permission will be combined in the following manner:
1. Limit - Minimum of the limits will be the limit of the inherited role
2. Allow aggregations - If any of the role allows aggregation, then the inherited role will allow aggregation
3. Scalar computed fields - same as table column fields, as in the above example
APIs for inherited roles:
----------------------
1. `add_inherited_role`
`add_inherited_role` is the [metadata API](https://hasura.io/docs/1.0/graphql/core/api-reference/index.html#schema-metadata-api) to create a new inherited role. It accepts two arguments
`role_name`: the name of the inherited role to be added (String)
`role_set`: list of roles that need to be combined (Array of Strings)
Example:
```json
{
"type": "add_inherited_role",
"args": {
"role_name":"combined_user",
"role_set":[
"user",
"user1"
]
}
}
```
After adding the inherited role, the inherited role can be used like single roles like earlier
Note:
An inherited role can only be created with non-inherited/singular roles.
2. `drop_inherited_role`
The `drop_inherited_role` API accepts the name of the inherited role and drops it from the metadata. It accepts a single argument:
`role_name`: name of the inherited role to be dropped
Example:
```json
{
"type": "drop_inherited_role",
"args": {
"role_name":"combined_user"
}
}
```
Metadata
---------
The derived roles metadata will be included under the `experimental_features` key while exporting the metadata.
```json
{
"experimental_features": {
"derived_roles": [
{
"role_name": "manager_is_employee_too",
"role_set": [
"employee",
"manager"
]
}
]
}
}
```
Scope
------
Only postgres queries and subscriptions are supported in this PR.
Important points:
-----------------
1. All columns exposed to an inherited role will be marked as `nullable`, this is done so that cell value nullification can be done.
TODOs
-------
- [ ] Tests
- [ ] Test a GraphQL query running with a inherited role without enabling inherited roles in experimental features
- [] Tests for aggregate queries, limit, computed fields, functions, subscriptions (?)
- [ ] Introspection test with a inherited role (nullability changes in a inherited role)
- [ ] Docs
- [ ] Changelog
Co-authored-by: Vamshi Surabhi <6562944+0x777@users.noreply.github.com>
GitOrigin-RevId: 3b8ee1e11f5ceca80fe294f8c074d42fbccfec63
2021-03-08 14:14:13 +03:00
|
|
|
{- Note: [SQL generation for inherited roles]
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
When a query is executed by an inherited role, each column may contain a predicate
|
2021-04-22 00:44:37 +03:00
|
|
|
(AnnColumnCaseBoolExp ('Postgres pgKind) SQLExp) along with it. The predicate is then
|
[Preview] Inherited roles for postgres read queries
fixes #3868
docker image - `hasura/graphql-engine:inherited-roles-preview-48b73a2de`
Note:
To be able to use the inherited roles feature, the graphql-engine should be started with the env variable `HASURA_GRAPHQL_EXPERIMENTAL_FEATURES` set to `inherited_roles`.
Introduction
------------
This PR implements the idea of multiple roles as presented in this [paper](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/FGALanguageICDE07.pdf). The multiple roles feature in this PR can be used via inherited roles. An inherited role is a role which can be created by combining multiple singular roles. For example, if there are two roles `author` and `editor` configured in the graphql-engine, then we can create a inherited role with the name of `combined_author_editor` role which will combine the select permissions of the `author` and `editor` roles and then make GraphQL queries using the `combined_author_editor`.
How are select permissions of different roles are combined?
------------------------------------------------------------
A select permission includes 5 things:
1. Columns accessible to the role
2. Row selection filter
3. Limit
4. Allow aggregation
5. Scalar computed fields accessible to the role
Suppose there are two roles, `role1` gives access to the `address` column with row filter `P1` and `role2` gives access to both the `address` and the `phone` column with row filter `P2` and we create a new role `combined_roles` which combines `role1` and `role2`.
Let's say the following GraphQL query is queried with the `combined_roles` role.
```graphql
query {
employees {
address
phone
}
}
```
This will translate to the following SQL query:
```sql
select
(case when (P1 or P2) then address else null end) as address,
(case when P2 then phone else null end) as phone
from employee
where (P1 or P2)
```
The other parameters of the select permission will be combined in the following manner:
1. Limit - Minimum of the limits will be the limit of the inherited role
2. Allow aggregations - If any of the role allows aggregation, then the inherited role will allow aggregation
3. Scalar computed fields - same as table column fields, as in the above example
APIs for inherited roles:
----------------------
1. `add_inherited_role`
`add_inherited_role` is the [metadata API](https://hasura.io/docs/1.0/graphql/core/api-reference/index.html#schema-metadata-api) to create a new inherited role. It accepts two arguments
`role_name`: the name of the inherited role to be added (String)
`role_set`: list of roles that need to be combined (Array of Strings)
Example:
```json
{
"type": "add_inherited_role",
"args": {
"role_name":"combined_user",
"role_set":[
"user",
"user1"
]
}
}
```
After adding the inherited role, the inherited role can be used like single roles like earlier
Note:
An inherited role can only be created with non-inherited/singular roles.
2. `drop_inherited_role`
The `drop_inherited_role` API accepts the name of the inherited role and drops it from the metadata. It accepts a single argument:
`role_name`: name of the inherited role to be dropped
Example:
```json
{
"type": "drop_inherited_role",
"args": {
"role_name":"combined_user"
}
}
```
Metadata
---------
The derived roles metadata will be included under the `experimental_features` key while exporting the metadata.
```json
{
"experimental_features": {
"derived_roles": [
{
"role_name": "manager_is_employee_too",
"role_set": [
"employee",
"manager"
]
}
]
}
}
```
Scope
------
Only postgres queries and subscriptions are supported in this PR.
Important points:
-----------------
1. All columns exposed to an inherited role will be marked as `nullable`, this is done so that cell value nullification can be done.
TODOs
-------
- [ ] Tests
- [ ] Test a GraphQL query running with a inherited role without enabling inherited roles in experimental features
- [] Tests for aggregate queries, limit, computed fields, functions, subscriptions (?)
- [ ] Introspection test with a inherited role (nullability changes in a inherited role)
- [ ] Docs
- [ ] Changelog
Co-authored-by: Vamshi Surabhi <6562944+0x777@users.noreply.github.com>
GitOrigin-RevId: 3b8ee1e11f5ceca80fe294f8c074d42fbccfec63
2021-03-08 14:14:13 +03:00
|
|
|
converted to a BoolExp, which will be used to check if the said column should
|
|
|
|
be nullified. For example,
|
|
|
|
|
|
|
|
Suppose there are two roles, role1 gives access only to the `addr` column with
|
|
|
|
row filter P1 and role2 gives access to both addr and phone column with row
|
|
|
|
filter P2. The `OR`ing of the predicates will have already been done while
|
|
|
|
the schema has been generated. The SQL generated will look like this:
|
|
|
|
|
|
|
|
select
|
|
|
|
(case when (P1 or P2) then addr else null end) as addr,
|
|
|
|
(case when P2 then phone else null end) as phone
|
|
|
|
from employee
|
|
|
|
where (P1 or P2)
|
|
|
|
|
|
|
|
-}
|
|
|
|
|
2021-05-21 05:46:58 +03:00
|
|
|
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
|
|
|
|
|
|
|
|
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]
|
|
|
|
|
2020-06-08 15:13:01 +03:00
|
|
|
processAnnFields
|
2021-04-22 00:44:37 +03:00
|
|
|
:: forall pgKind m
|
[Preview] Inherited roles for postgres read queries
fixes #3868
docker image - `hasura/graphql-engine:inherited-roles-preview-48b73a2de`
Note:
To be able to use the inherited roles feature, the graphql-engine should be started with the env variable `HASURA_GRAPHQL_EXPERIMENTAL_FEATURES` set to `inherited_roles`.
Introduction
------------
This PR implements the idea of multiple roles as presented in this [paper](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/FGALanguageICDE07.pdf). The multiple roles feature in this PR can be used via inherited roles. An inherited role is a role which can be created by combining multiple singular roles. For example, if there are two roles `author` and `editor` configured in the graphql-engine, then we can create a inherited role with the name of `combined_author_editor` role which will combine the select permissions of the `author` and `editor` roles and then make GraphQL queries using the `combined_author_editor`.
How are select permissions of different roles are combined?
------------------------------------------------------------
A select permission includes 5 things:
1. Columns accessible to the role
2. Row selection filter
3. Limit
4. Allow aggregation
5. Scalar computed fields accessible to the role
Suppose there are two roles, `role1` gives access to the `address` column with row filter `P1` and `role2` gives access to both the `address` and the `phone` column with row filter `P2` and we create a new role `combined_roles` which combines `role1` and `role2`.
Let's say the following GraphQL query is queried with the `combined_roles` role.
```graphql
query {
employees {
address
phone
}
}
```
This will translate to the following SQL query:
```sql
select
(case when (P1 or P2) then address else null end) as address,
(case when P2 then phone else null end) as phone
from employee
where (P1 or P2)
```
The other parameters of the select permission will be combined in the following manner:
1. Limit - Minimum of the limits will be the limit of the inherited role
2. Allow aggregations - If any of the role allows aggregation, then the inherited role will allow aggregation
3. Scalar computed fields - same as table column fields, as in the above example
APIs for inherited roles:
----------------------
1. `add_inherited_role`
`add_inherited_role` is the [metadata API](https://hasura.io/docs/1.0/graphql/core/api-reference/index.html#schema-metadata-api) to create a new inherited role. It accepts two arguments
`role_name`: the name of the inherited role to be added (String)
`role_set`: list of roles that need to be combined (Array of Strings)
Example:
```json
{
"type": "add_inherited_role",
"args": {
"role_name":"combined_user",
"role_set":[
"user",
"user1"
]
}
}
```
After adding the inherited role, the inherited role can be used like single roles like earlier
Note:
An inherited role can only be created with non-inherited/singular roles.
2. `drop_inherited_role`
The `drop_inherited_role` API accepts the name of the inherited role and drops it from the metadata. It accepts a single argument:
`role_name`: name of the inherited role to be dropped
Example:
```json
{
"type": "drop_inherited_role",
"args": {
"role_name":"combined_user"
}
}
```
Metadata
---------
The derived roles metadata will be included under the `experimental_features` key while exporting the metadata.
```json
{
"experimental_features": {
"derived_roles": [
{
"role_name": "manager_is_employee_too",
"role_set": [
"employee",
"manager"
]
}
]
}
}
```
Scope
------
Only postgres queries and subscriptions are supported in this PR.
Important points:
-----------------
1. All columns exposed to an inherited role will be marked as `nullable`, this is done so that cell value nullification can be done.
TODOs
-------
- [ ] Tests
- [ ] Test a GraphQL query running with a inherited role without enabling inherited roles in experimental features
- [] Tests for aggregate queries, limit, computed fields, functions, subscriptions (?)
- [ ] Introspection test with a inherited role (nullability changes in a inherited role)
- [ ] Docs
- [ ] Changelog
Co-authored-by: Vamshi Surabhi <6562944+0x777@users.noreply.github.com>
GitOrigin-RevId: 3b8ee1e11f5ceca80fe294f8c074d42fbccfec63
2021-03-08 14:14:13 +03:00
|
|
|
. ( MonadReader Bool m
|
2021-03-25 20:50:08 +03:00
|
|
|
, MonadWriter JoinTree m
|
2021-04-22 00:44:37 +03:00
|
|
|
, Backend ('Postgres pgKind)
|
2021-05-21 05:46:58 +03:00
|
|
|
, PostgresAnnotatedFieldJSON pgKind
|
[Preview] Inherited roles for postgres read queries
fixes #3868
docker image - `hasura/graphql-engine:inherited-roles-preview-48b73a2de`
Note:
To be able to use the inherited roles feature, the graphql-engine should be started with the env variable `HASURA_GRAPHQL_EXPERIMENTAL_FEATURES` set to `inherited_roles`.
Introduction
------------
This PR implements the idea of multiple roles as presented in this [paper](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/FGALanguageICDE07.pdf). The multiple roles feature in this PR can be used via inherited roles. An inherited role is a role which can be created by combining multiple singular roles. For example, if there are two roles `author` and `editor` configured in the graphql-engine, then we can create a inherited role with the name of `combined_author_editor` role which will combine the select permissions of the `author` and `editor` roles and then make GraphQL queries using the `combined_author_editor`.
How are select permissions of different roles are combined?
------------------------------------------------------------
A select permission includes 5 things:
1. Columns accessible to the role
2. Row selection filter
3. Limit
4. Allow aggregation
5. Scalar computed fields accessible to the role
Suppose there are two roles, `role1` gives access to the `address` column with row filter `P1` and `role2` gives access to both the `address` and the `phone` column with row filter `P2` and we create a new role `combined_roles` which combines `role1` and `role2`.
Let's say the following GraphQL query is queried with the `combined_roles` role.
```graphql
query {
employees {
address
phone
}
}
```
This will translate to the following SQL query:
```sql
select
(case when (P1 or P2) then address else null end) as address,
(case when P2 then phone else null end) as phone
from employee
where (P1 or P2)
```
The other parameters of the select permission will be combined in the following manner:
1. Limit - Minimum of the limits will be the limit of the inherited role
2. Allow aggregations - If any of the role allows aggregation, then the inherited role will allow aggregation
3. Scalar computed fields - same as table column fields, as in the above example
APIs for inherited roles:
----------------------
1. `add_inherited_role`
`add_inherited_role` is the [metadata API](https://hasura.io/docs/1.0/graphql/core/api-reference/index.html#schema-metadata-api) to create a new inherited role. It accepts two arguments
`role_name`: the name of the inherited role to be added (String)
`role_set`: list of roles that need to be combined (Array of Strings)
Example:
```json
{
"type": "add_inherited_role",
"args": {
"role_name":"combined_user",
"role_set":[
"user",
"user1"
]
}
}
```
After adding the inherited role, the inherited role can be used like single roles like earlier
Note:
An inherited role can only be created with non-inherited/singular roles.
2. `drop_inherited_role`
The `drop_inherited_role` API accepts the name of the inherited role and drops it from the metadata. It accepts a single argument:
`role_name`: name of the inherited role to be dropped
Example:
```json
{
"type": "drop_inherited_role",
"args": {
"role_name":"combined_user"
}
}
```
Metadata
---------
The derived roles metadata will be included under the `experimental_features` key while exporting the metadata.
```json
{
"experimental_features": {
"derived_roles": [
{
"role_name": "manager_is_employee_too",
"role_set": [
"employee",
"manager"
]
}
]
}
}
```
Scope
------
Only postgres queries and subscriptions are supported in this PR.
Important points:
-----------------
1. All columns exposed to an inherited role will be marked as `nullable`, this is done so that cell value nullification can be done.
TODOs
-------
- [ ] Tests
- [ ] Test a GraphQL query running with a inherited role without enabling inherited roles in experimental features
- [] Tests for aggregate queries, limit, computed fields, functions, subscriptions (?)
- [ ] Introspection test with a inherited role (nullability changes in a inherited role)
- [ ] Docs
- [ ] Changelog
Co-authored-by: Vamshi Surabhi <6562944+0x777@users.noreply.github.com>
GitOrigin-RevId: 3b8ee1e11f5ceca80fe294f8c074d42fbccfec63
2021-03-08 14:14:13 +03:00
|
|
|
)
|
2020-10-27 13:34:31 +03:00
|
|
|
=> Identifier
|
2020-06-08 15:13:01 +03:00
|
|
|
-> FieldName
|
|
|
|
-> SimilarArrayFields
|
2021-04-22 00:44:37 +03:00
|
|
|
-> AnnFields ('Postgres pgKind)
|
2020-06-08 15:13:01 +03:00
|
|
|
-> 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
|
|
|
|
|
2021-02-03 19:17:20 +03:00
|
|
|
AFNodeId _ tn pKeys -> pure $ mkNodeId tn pKeys
|
2020-06-08 15:13:01 +03:00
|
|
|
|
|
|
|
AFColumn c -> toSQLCol c
|
|
|
|
|
2021-06-11 06:26:50 +03:00
|
|
|
-- this will be gone once the code which collects remote joins from the IR
|
|
|
|
-- emits a modified IR where remote relationships can't be reached
|
|
|
|
AFRemote _ -> pure $ S.SELit "null: remote field selected"
|
2020-06-08 15:13:01 +03:00
|
|
|
|
|
|
|
AFObjectRelation objSel -> withWriteObjectRelation $ do
|
2020-06-25 06:33:37 +03:00
|
|
|
let AnnRelationSelectG relName relMapping annObjSel = objSel
|
|
|
|
AnnObjectSelectG objAnnFields tableFrom tableFilter = annObjSel
|
2020-06-08 15:13:01 +03:00
|
|
|
objRelSourcePrefix = mkObjectRelationTableAlias sourcePrefix relName
|
2020-06-25 06:33:37 +03:00
|
|
|
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
|
2020-06-08 15:13:01 +03:00
|
|
|
pure ( objRelSource
|
2020-06-25 06:33:37 +03:00
|
|
|
, HM.fromList [annFieldsExtr]
|
2020-06-08 15:13:01 +03:00
|
|
|
, S.mkQIdenExp objRelSourcePrefix fieldName
|
|
|
|
)
|
2018-10-31 15:51:20 +03:00
|
|
|
|
2020-06-08 15:13:01 +03:00
|
|
|
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
|
|
|
|
|
[Preview] Inherited roles for postgres read queries
fixes #3868
docker image - `hasura/graphql-engine:inherited-roles-preview-48b73a2de`
Note:
To be able to use the inherited roles feature, the graphql-engine should be started with the env variable `HASURA_GRAPHQL_EXPERIMENTAL_FEATURES` set to `inherited_roles`.
Introduction
------------
This PR implements the idea of multiple roles as presented in this [paper](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/FGALanguageICDE07.pdf). The multiple roles feature in this PR can be used via inherited roles. An inherited role is a role which can be created by combining multiple singular roles. For example, if there are two roles `author` and `editor` configured in the graphql-engine, then we can create a inherited role with the name of `combined_author_editor` role which will combine the select permissions of the `author` and `editor` roles and then make GraphQL queries using the `combined_author_editor`.
How are select permissions of different roles are combined?
------------------------------------------------------------
A select permission includes 5 things:
1. Columns accessible to the role
2. Row selection filter
3. Limit
4. Allow aggregation
5. Scalar computed fields accessible to the role
Suppose there are two roles, `role1` gives access to the `address` column with row filter `P1` and `role2` gives access to both the `address` and the `phone` column with row filter `P2` and we create a new role `combined_roles` which combines `role1` and `role2`.
Let's say the following GraphQL query is queried with the `combined_roles` role.
```graphql
query {
employees {
address
phone
}
}
```
This will translate to the following SQL query:
```sql
select
(case when (P1 or P2) then address else null end) as address,
(case when P2 then phone else null end) as phone
from employee
where (P1 or P2)
```
The other parameters of the select permission will be combined in the following manner:
1. Limit - Minimum of the limits will be the limit of the inherited role
2. Allow aggregations - If any of the role allows aggregation, then the inherited role will allow aggregation
3. Scalar computed fields - same as table column fields, as in the above example
APIs for inherited roles:
----------------------
1. `add_inherited_role`
`add_inherited_role` is the [metadata API](https://hasura.io/docs/1.0/graphql/core/api-reference/index.html#schema-metadata-api) to create a new inherited role. It accepts two arguments
`role_name`: the name of the inherited role to be added (String)
`role_set`: list of roles that need to be combined (Array of Strings)
Example:
```json
{
"type": "add_inherited_role",
"args": {
"role_name":"combined_user",
"role_set":[
"user",
"user1"
]
}
}
```
After adding the inherited role, the inherited role can be used like single roles like earlier
Note:
An inherited role can only be created with non-inherited/singular roles.
2. `drop_inherited_role`
The `drop_inherited_role` API accepts the name of the inherited role and drops it from the metadata. It accepts a single argument:
`role_name`: name of the inherited role to be dropped
Example:
```json
{
"type": "drop_inherited_role",
"args": {
"role_name":"combined_user"
}
}
```
Metadata
---------
The derived roles metadata will be included under the `experimental_features` key while exporting the metadata.
```json
{
"experimental_features": {
"derived_roles": [
{
"role_name": "manager_is_employee_too",
"role_set": [
"employee",
"manager"
]
}
]
}
}
```
Scope
------
Only postgres queries and subscriptions are supported in this PR.
Important points:
-----------------
1. All columns exposed to an inherited role will be marked as `nullable`, this is done so that cell value nullification can be done.
TODOs
-------
- [ ] Tests
- [ ] Test a GraphQL query running with a inherited role without enabling inherited roles in experimental features
- [] Tests for aggregate queries, limit, computed fields, functions, subscriptions (?)
- [ ] Introspection test with a inherited role (nullability changes in a inherited role)
- [ ] Docs
- [ ] Changelog
Co-authored-by: Vamshi Surabhi <6562944+0x777@users.noreply.github.com>
GitOrigin-RevId: 3b8ee1e11f5ceca80fe294f8c074d42fbccfec63
2021-03-08 14:14:13 +03:00
|
|
|
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
|
2020-06-08 15:13:01 +03:00
|
|
|
|
2021-02-03 19:17:20 +03:00
|
|
|
AFComputedField _ (CFSTable selectTy sel) -> withWriteComputedFieldTableSet $ do
|
2020-06-08 15:13:01 +03:00
|
|
|
let computedFieldSourcePrefix =
|
|
|
|
mkComputedFieldTableAlias sourcePrefix fieldName
|
|
|
|
(selectSource, nodeExtractors) <-
|
|
|
|
processAnnSimpleSelect (mkSourcePrefixes computedFieldSourcePrefix)
|
|
|
|
fieldName PLSQNotRequired sel
|
|
|
|
let computedFieldTableSetSource =
|
|
|
|
ComputedFieldTableSetSource fieldName selectTy selectSource
|
|
|
|
pure ( computedFieldTableSetSource
|
|
|
|
, nodeExtractors
|
|
|
|
, S.mkQIdenExp computedFieldSourcePrefix fieldName
|
|
|
|
)
|
2018-12-12 15:58:39 +03:00
|
|
|
|
server: IR for DB-DB joins
### Description
This PR adds the required IR for DB to DB joins, based on @paf31 and @0x777 's `feature/db-to-db` branch.
To do so, it also refactors the IR to introduce a new type parameter, `r`, which is used to recursively constructs the `v` parameter of remote QueryDBs. When collecting remote joins, we replace `r` with `Const Void`, indicating at the type level that there cannot be any leftover remote join.
Furthermore, this PR refactors IR.Select for readability, moves some code from IR.Root to IR.Select to avoid having to deal with circular dependencies, and makes it compile by adding `error` in all new cases in the execution pipeline.
The diff doesn't make it clear, but most of Select.hs is actually unchanged. Declarations have just been reordered by topic, in the following order:
- type declarations
- instance declarations
- type aliases
- constructor functions
- traverse functions
https://github.com/hasura/graphql-engine-mono/pull/1580
Co-authored-by: Phil Freeman <630306+paf31@users.noreply.github.com>
GitOrigin-RevId: bbdcb4119cec8bb3fc32f1294f91b8dea0728721
2021-06-18 02:12:11 +03:00
|
|
|
-- TODO: implement this
|
|
|
|
AFDBRemote _ -> error "FIXME"
|
|
|
|
|
|
|
|
|
2021-05-21 05:46:58 +03:00
|
|
|
pure $ annRowToJson @pgKind fieldAlias fieldExps
|
|
|
|
|
2020-06-08 15:13:01 +03:00
|
|
|
where
|
|
|
|
mkSourcePrefixes newPrefix = SourcePrefixes newPrefix sourcePrefix
|
|
|
|
|
[Preview] Inherited roles for postgres read queries
fixes #3868
docker image - `hasura/graphql-engine:inherited-roles-preview-48b73a2de`
Note:
To be able to use the inherited roles feature, the graphql-engine should be started with the env variable `HASURA_GRAPHQL_EXPERIMENTAL_FEATURES` set to `inherited_roles`.
Introduction
------------
This PR implements the idea of multiple roles as presented in this [paper](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/FGALanguageICDE07.pdf). The multiple roles feature in this PR can be used via inherited roles. An inherited role is a role which can be created by combining multiple singular roles. For example, if there are two roles `author` and `editor` configured in the graphql-engine, then we can create a inherited role with the name of `combined_author_editor` role which will combine the select permissions of the `author` and `editor` roles and then make GraphQL queries using the `combined_author_editor`.
How are select permissions of different roles are combined?
------------------------------------------------------------
A select permission includes 5 things:
1. Columns accessible to the role
2. Row selection filter
3. Limit
4. Allow aggregation
5. Scalar computed fields accessible to the role
Suppose there are two roles, `role1` gives access to the `address` column with row filter `P1` and `role2` gives access to both the `address` and the `phone` column with row filter `P2` and we create a new role `combined_roles` which combines `role1` and `role2`.
Let's say the following GraphQL query is queried with the `combined_roles` role.
```graphql
query {
employees {
address
phone
}
}
```
This will translate to the following SQL query:
```sql
select
(case when (P1 or P2) then address else null end) as address,
(case when P2 then phone else null end) as phone
from employee
where (P1 or P2)
```
The other parameters of the select permission will be combined in the following manner:
1. Limit - Minimum of the limits will be the limit of the inherited role
2. Allow aggregations - If any of the role allows aggregation, then the inherited role will allow aggregation
3. Scalar computed fields - same as table column fields, as in the above example
APIs for inherited roles:
----------------------
1. `add_inherited_role`
`add_inherited_role` is the [metadata API](https://hasura.io/docs/1.0/graphql/core/api-reference/index.html#schema-metadata-api) to create a new inherited role. It accepts two arguments
`role_name`: the name of the inherited role to be added (String)
`role_set`: list of roles that need to be combined (Array of Strings)
Example:
```json
{
"type": "add_inherited_role",
"args": {
"role_name":"combined_user",
"role_set":[
"user",
"user1"
]
}
}
```
After adding the inherited role, the inherited role can be used like single roles like earlier
Note:
An inherited role can only be created with non-inherited/singular roles.
2. `drop_inherited_role`
The `drop_inherited_role` API accepts the name of the inherited role and drops it from the metadata. It accepts a single argument:
`role_name`: name of the inherited role to be dropped
Example:
```json
{
"type": "drop_inherited_role",
"args": {
"role_name":"combined_user"
}
}
```
Metadata
---------
The derived roles metadata will be included under the `experimental_features` key while exporting the metadata.
```json
{
"experimental_features": {
"derived_roles": [
{
"role_name": "manager_is_employee_too",
"role_set": [
"employee",
"manager"
]
}
]
}
}
```
Scope
------
Only postgres queries and subscriptions are supported in this PR.
Important points:
-----------------
1. All columns exposed to an inherited role will be marked as `nullable`, this is done so that cell value nullification can be done.
TODOs
-------
- [ ] Tests
- [ ] Test a GraphQL query running with a inherited role without enabling inherited roles in experimental features
- [] Tests for aggregate queries, limit, computed fields, functions, subscriptions (?)
- [ ] Introspection test with a inherited role (nullability changes in a inherited role)
- [ ] Docs
- [ ] Changelog
Co-authored-by: Vamshi Surabhi <6562944+0x777@users.noreply.github.com>
GitOrigin-RevId: 3b8ee1e11f5ceca80fe294f8c074d42fbccfec63
2021-03-08 14:14:13 +03:00
|
|
|
baseTableIdentifier = mkBaseTableAlias sourcePrefix
|
|
|
|
|
2021-04-22 00:44:37 +03:00
|
|
|
toSQLCol :: AnnColumnField ('Postgres pgKind) S.SQLExp -> m S.SQLExp
|
[Preview] Inherited roles for postgres read queries
fixes #3868
docker image - `hasura/graphql-engine:inherited-roles-preview-48b73a2de`
Note:
To be able to use the inherited roles feature, the graphql-engine should be started with the env variable `HASURA_GRAPHQL_EXPERIMENTAL_FEATURES` set to `inherited_roles`.
Introduction
------------
This PR implements the idea of multiple roles as presented in this [paper](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/FGALanguageICDE07.pdf). The multiple roles feature in this PR can be used via inherited roles. An inherited role is a role which can be created by combining multiple singular roles. For example, if there are two roles `author` and `editor` configured in the graphql-engine, then we can create a inherited role with the name of `combined_author_editor` role which will combine the select permissions of the `author` and `editor` roles and then make GraphQL queries using the `combined_author_editor`.
How are select permissions of different roles are combined?
------------------------------------------------------------
A select permission includes 5 things:
1. Columns accessible to the role
2. Row selection filter
3. Limit
4. Allow aggregation
5. Scalar computed fields accessible to the role
Suppose there are two roles, `role1` gives access to the `address` column with row filter `P1` and `role2` gives access to both the `address` and the `phone` column with row filter `P2` and we create a new role `combined_roles` which combines `role1` and `role2`.
Let's say the following GraphQL query is queried with the `combined_roles` role.
```graphql
query {
employees {
address
phone
}
}
```
This will translate to the following SQL query:
```sql
select
(case when (P1 or P2) then address else null end) as address,
(case when P2 then phone else null end) as phone
from employee
where (P1 or P2)
```
The other parameters of the select permission will be combined in the following manner:
1. Limit - Minimum of the limits will be the limit of the inherited role
2. Allow aggregations - If any of the role allows aggregation, then the inherited role will allow aggregation
3. Scalar computed fields - same as table column fields, as in the above example
APIs for inherited roles:
----------------------
1. `add_inherited_role`
`add_inherited_role` is the [metadata API](https://hasura.io/docs/1.0/graphql/core/api-reference/index.html#schema-metadata-api) to create a new inherited role. It accepts two arguments
`role_name`: the name of the inherited role to be added (String)
`role_set`: list of roles that need to be combined (Array of Strings)
Example:
```json
{
"type": "add_inherited_role",
"args": {
"role_name":"combined_user",
"role_set":[
"user",
"user1"
]
}
}
```
After adding the inherited role, the inherited role can be used like single roles like earlier
Note:
An inherited role can only be created with non-inherited/singular roles.
2. `drop_inherited_role`
The `drop_inherited_role` API accepts the name of the inherited role and drops it from the metadata. It accepts a single argument:
`role_name`: name of the inherited role to be dropped
Example:
```json
{
"type": "drop_inherited_role",
"args": {
"role_name":"combined_user"
}
}
```
Metadata
---------
The derived roles metadata will be included under the `experimental_features` key while exporting the metadata.
```json
{
"experimental_features": {
"derived_roles": [
{
"role_name": "manager_is_employee_too",
"role_set": [
"employee",
"manager"
]
}
]
}
}
```
Scope
------
Only postgres queries and subscriptions are supported in this PR.
Important points:
-----------------
1. All columns exposed to an inherited role will be marked as `nullable`, this is done so that cell value nullification can be done.
TODOs
-------
- [ ] Tests
- [ ] Test a GraphQL query running with a inherited role without enabling inherited roles in experimental features
- [] Tests for aggregate queries, limit, computed fields, functions, subscriptions (?)
- [ ] Introspection test with a inherited role (nullability changes in a inherited role)
- [ ] Docs
- [ ] Changelog
Co-authored-by: Vamshi Surabhi <6562944+0x777@users.noreply.github.com>
GitOrigin-RevId: 3b8ee1e11f5ceca80fe294f8c074d42fbccfec63
2021-03-08 14:14:13 +03:00
|
|
|
toSQLCol (AnnColumnField col asText colOpM caseBoolExpMaybe) = do
|
2020-06-08 15:13:01 +03:00
|
|
|
strfyNum <- ask
|
[Preview] Inherited roles for postgres read queries
fixes #3868
docker image - `hasura/graphql-engine:inherited-roles-preview-48b73a2de`
Note:
To be able to use the inherited roles feature, the graphql-engine should be started with the env variable `HASURA_GRAPHQL_EXPERIMENTAL_FEATURES` set to `inherited_roles`.
Introduction
------------
This PR implements the idea of multiple roles as presented in this [paper](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/FGALanguageICDE07.pdf). The multiple roles feature in this PR can be used via inherited roles. An inherited role is a role which can be created by combining multiple singular roles. For example, if there are two roles `author` and `editor` configured in the graphql-engine, then we can create a inherited role with the name of `combined_author_editor` role which will combine the select permissions of the `author` and `editor` roles and then make GraphQL queries using the `combined_author_editor`.
How are select permissions of different roles are combined?
------------------------------------------------------------
A select permission includes 5 things:
1. Columns accessible to the role
2. Row selection filter
3. Limit
4. Allow aggregation
5. Scalar computed fields accessible to the role
Suppose there are two roles, `role1` gives access to the `address` column with row filter `P1` and `role2` gives access to both the `address` and the `phone` column with row filter `P2` and we create a new role `combined_roles` which combines `role1` and `role2`.
Let's say the following GraphQL query is queried with the `combined_roles` role.
```graphql
query {
employees {
address
phone
}
}
```
This will translate to the following SQL query:
```sql
select
(case when (P1 or P2) then address else null end) as address,
(case when P2 then phone else null end) as phone
from employee
where (P1 or P2)
```
The other parameters of the select permission will be combined in the following manner:
1. Limit - Minimum of the limits will be the limit of the inherited role
2. Allow aggregations - If any of the role allows aggregation, then the inherited role will allow aggregation
3. Scalar computed fields - same as table column fields, as in the above example
APIs for inherited roles:
----------------------
1. `add_inherited_role`
`add_inherited_role` is the [metadata API](https://hasura.io/docs/1.0/graphql/core/api-reference/index.html#schema-metadata-api) to create a new inherited role. It accepts two arguments
`role_name`: the name of the inherited role to be added (String)
`role_set`: list of roles that need to be combined (Array of Strings)
Example:
```json
{
"type": "add_inherited_role",
"args": {
"role_name":"combined_user",
"role_set":[
"user",
"user1"
]
}
}
```
After adding the inherited role, the inherited role can be used like single roles like earlier
Note:
An inherited role can only be created with non-inherited/singular roles.
2. `drop_inherited_role`
The `drop_inherited_role` API accepts the name of the inherited role and drops it from the metadata. It accepts a single argument:
`role_name`: name of the inherited role to be dropped
Example:
```json
{
"type": "drop_inherited_role",
"args": {
"role_name":"combined_user"
}
}
```
Metadata
---------
The derived roles metadata will be included under the `experimental_features` key while exporting the metadata.
```json
{
"experimental_features": {
"derived_roles": [
{
"role_name": "manager_is_employee_too",
"role_set": [
"employee",
"manager"
]
}
]
}
}
```
Scope
------
Only postgres queries and subscriptions are supported in this PR.
Important points:
-----------------
1. All columns exposed to an inherited role will be marked as `nullable`, this is done so that cell value nullification can be done.
TODOs
-------
- [ ] Tests
- [ ] Test a GraphQL query running with a inherited role without enabling inherited roles in experimental features
- [] Tests for aggregate queries, limit, computed fields, functions, subscriptions (?)
- [ ] Introspection test with a inherited role (nullability changes in a inherited role)
- [ ] Docs
- [ ] Changelog
Co-authored-by: Vamshi Surabhi <6562944+0x777@users.noreply.github.com>
GitOrigin-RevId: 3b8ee1e11f5ceca80fe294f8c074d42fbccfec63
2021-03-08 14:14:13 +03:00
|
|
|
let sqlExpression =
|
|
|
|
withColumnOp colOpM $
|
|
|
|
S.mkQIdenExp baseTableIdentifier $ pgiColumn 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 (pgiType col) asText finalSQLExpression
|
2020-06-08 15:13:01 +03:00
|
|
|
|
2021-04-22 00:44:37 +03:00
|
|
|
fromScalarComputedField :: ComputedFieldScalarSelect ('Postgres pgKind) S.SQLExp -> m S.SQLExp
|
2020-06-08 15:13:01 +03:00
|
|
|
fromScalarComputedField computedFieldScalar = do
|
|
|
|
strfyNum <- ask
|
2020-11-25 17:18:58 +03:00
|
|
|
pure $ toJSONableExp strfyNum (ColumnScalar ty) False $ withColumnOp colOpM $
|
2020-06-08 15:13:01 +03:00
|
|
|
S.SEFunction $ S.FunctionExp fn (fromTableRowArgs sourcePrefix args) Nothing
|
|
|
|
where
|
|
|
|
ComputedFieldScalarSelect fn args ty colOpM = computedFieldScalar
|
|
|
|
|
2021-04-22 00:44:37 +03:00
|
|
|
withColumnOp :: Maybe (ColumnOp ('Postgres pgKind)) -> S.SQLExp -> S.SQLExp
|
2020-06-08 15:13:01 +03:00
|
|
|
withColumnOp colOpM sqlExp = case colOpM of
|
|
|
|
Nothing -> sqlExp
|
|
|
|
Just (ColumnOp opText cExp) -> S.mkSQLOpExp opText sqlExp cExp
|
|
|
|
|
2021-04-22 00:44:37 +03:00
|
|
|
mkNodeId :: QualifiedTable -> PrimaryKeyColumns ('Postgres pgKind) -> S.SQLExp
|
2020-06-08 15:13:01 +03:00
|
|
|
mkNodeId (QualifiedObject tableSchema tableName) pkeyColumns =
|
2020-06-16 17:25:49 +03:00
|
|
|
let columnInfoToSQLExp pgColumnInfo =
|
|
|
|
toJSONableExp False (pgiType pgColumnInfo) False $
|
|
|
|
S.mkQIdenExp (mkBaseTableAlias sourcePrefix) $ pgiColumn pgColumnInfo
|
|
|
|
|
|
|
|
-- See Note [Relay Node id].
|
|
|
|
in encodeBase64 $ flip S.SETyAnn S.textTypeAnn $ S.applyJsonBuildArray $
|
|
|
|
[ S.intToSQLExp $ nodeIdVersionInt currentNodeIdVersion
|
|
|
|
, S.SELit (getSchemaTxt tableSchema)
|
|
|
|
, S.SELit (toTxt tableName)
|
|
|
|
] <> map columnInfoToSQLExp (toList pkeyColumns)
|
2018-10-31 15:51:20 +03:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2019-12-13 00:46:33 +03:00
|
|
|
mkJoinCond :: S.Alias -> HashMap PGCol PGCol -> S.BoolExp
|
2019-10-18 11:29:47 +03:00
|
|
|
mkJoinCond baseTablepfx colMapn =
|
2019-12-13 00:46:33 +03:00
|
|
|
foldl' (S.BEBin S.AndOp) (S.BELit True) $ flip map (HM.toList colMapn) $ \(lCol, rCol) ->
|
2019-10-18 11:29:47 +03:00
|
|
|
S.BECompare S.SEQ (S.mkQIdenExp baseTablepfx lCol) (S.mkSIdenExp rCol)
|
2018-10-31 15:51:20 +03:00
|
|
|
|
2020-06-08 15:13:01 +03:00
|
|
|
generateSQLSelect
|
|
|
|
:: S.BoolExp -- ^ Pre join condition
|
2021-03-25 20:50:08 +03:00
|
|
|
-> SelectSource
|
|
|
|
-> SelectNode
|
2020-06-08 15:13:01 +03:00
|
|
|
-> S.Select
|
|
|
|
generateSQLSelect joinCondition selectSource selectNode =
|
2018-10-31 15:51:20 +03:00
|
|
|
S.mkSelect
|
2020-06-08 15:13:01 +03:00
|
|
|
{ S.selExtr = [S.Extractor e $ Just a | (a, e) <- HM.toList extractors]
|
|
|
|
, S.selFrom = Just $ S.FromExp [joinedFrom]
|
|
|
|
, S.selOrderBy = maybeOrderby
|
|
|
|
, S.selLimit = S.LimitExp . S.intToSQLExp <$> maybeLimit
|
2021-06-10 19:13:20 +03:00
|
|
|
, S.selOffset = S.OffsetExp . S.int64ToSQLExp <$> maybeOffset
|
2020-06-08 15:13:01 +03:00
|
|
|
, S.selDistinct = maybeDistinct
|
2018-10-31 15:51:20 +03:00
|
|
|
}
|
|
|
|
where
|
2020-06-08 15:13:01 +03:00
|
|
|
SelectSource sourcePrefix fromItem maybeDistinct whereExp
|
|
|
|
maybeOrderby maybeLimit maybeOffset = selectSource
|
|
|
|
SelectNode extractors joinTree = selectNode
|
|
|
|
JoinTree objectRelations arrayRelations arrayConnections computedFields = joinTree
|
|
|
|
-- this is the table which is aliased as "sourcePrefix.base"
|
|
|
|
baseSelect = S.mkSelect
|
2020-01-16 07:53:28 +03:00
|
|
|
{ S.selExtr = [S.Extractor (S.SEStar Nothing) Nothing]
|
2018-10-31 15:51:20 +03:00
|
|
|
, S.selFrom = Just $ S.FromExp [fromItem]
|
2020-06-08 15:13:01 +03:00
|
|
|
, S.selWhere = Just $ injectJoinCond joinCondition whereExp
|
2018-10-31 15:51:20 +03:00
|
|
|
}
|
2020-06-08 15:13:01 +03:00
|
|
|
baseSelectAlias = S.Alias $ mkBaseTableAlias sourcePrefix
|
|
|
|
baseFromItem = S.mkSelFromItem baseSelect baseSelectAlias
|
2018-10-31 15:51:20 +03:00
|
|
|
|
|
|
|
-- 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 $
|
2020-06-08 15:13:01 +03:00
|
|
|
map objectRelationToFromItem (HM.toList objectRelations) <>
|
|
|
|
map arrayRelationToFromItem (HM.toList arrayRelations) <>
|
|
|
|
map arrayConnectionToFromItem (HM.toList arrayConnections) <>
|
|
|
|
map computedFieldToFromItem (HM.toList computedFields)
|
|
|
|
|
|
|
|
|
|
|
|
objectRelationToFromItem
|
2021-03-25 20:50:08 +03:00
|
|
|
:: (ObjectRelationSource, SelectNode) -> S.FromItem
|
2020-06-08 15:13:01 +03:00
|
|
|
objectRelationToFromItem (objectRelationSource, node) =
|
2020-06-25 06:33:37 +03:00
|
|
|
let ObjectRelationSource _ colMapping objectSelectSource = objectRelationSource
|
|
|
|
alias = S.Alias $ _ossPrefix objectSelectSource
|
|
|
|
source = objectSelectSourceToSelectSource objectSelectSource
|
2020-06-08 15:13:01 +03:00
|
|
|
select = generateSQLSelect (mkJoinCond baseSelectAlias colMapping) source node
|
|
|
|
in S.mkLateralFromItem select alias
|
|
|
|
|
|
|
|
arrayRelationToFromItem
|
2021-03-25 20:50:08 +03:00
|
|
|
:: (ArrayRelationSource, ArraySelectNode) -> S.FromItem
|
2020-06-08 15:13:01 +03:00
|
|
|
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
|
2021-03-25 20:50:08 +03:00
|
|
|
:: (ArrayConnectionSource, ArraySelectNode) -> S.FromItem
|
2020-06-08 15:13:01 +03:00
|
|
|
arrayConnectionToFromItem (arrayConnectionSource, arraySelectNode) =
|
|
|
|
let selectWith = connectionToSelectWith baseSelectAlias arrayConnectionSource arraySelectNode
|
|
|
|
alias = S.Alias $ _ssPrefix $ _acsSource arrayConnectionSource
|
|
|
|
in S.FISelectWith (S.Lateral True) selectWith alias
|
|
|
|
|
|
|
|
computedFieldToFromItem
|
2021-03-25 20:50:08 +03:00
|
|
|
:: (ComputedFieldTableSetSource, SelectNode) -> S.FromItem
|
2020-06-08 15:13:01 +03:00
|
|
|
computedFieldToFromItem (computedFieldTableSource, node) =
|
|
|
|
let ComputedFieldTableSetSource fieldName selectTy source = computedFieldTableSource
|
|
|
|
internalSelect = generateSQLSelect (S.BELit True) source node
|
|
|
|
extractor = asJsonAggExtr selectTy (S.toAlias fieldName) PLSQNotRequired $
|
|
|
|
_ssOrderBy source
|
|
|
|
alias = S.Alias $ _ssPrefix source
|
|
|
|
select = S.mkSelect
|
|
|
|
{ S.selExtr = [extractor]
|
|
|
|
, S.selFrom = Just $ S.FromExp [S.mkSelFromItem internalSelect alias]
|
2019-10-18 11:29:47 +03:00
|
|
|
}
|
2020-06-08 15:13:01 +03:00
|
|
|
in S.mkLateralFromItem select alias
|
|
|
|
|
|
|
|
generateSQLSelectFromArrayNode
|
2021-03-25 20:50:08 +03:00
|
|
|
:: SelectSource
|
|
|
|
-> ArraySelectNode
|
2020-06-08 15:13:01 +03:00
|
|
|
-> S.BoolExp
|
|
|
|
-> S.Select
|
|
|
|
generateSQLSelectFromArrayNode selectSource arraySelectNode joinCondition =
|
|
|
|
S.mkSelect
|
|
|
|
{ S.selExtr = topExtractors
|
|
|
|
, S.selFrom = Just $ S.FromExp [selectFrom]
|
|
|
|
}
|
|
|
|
where
|
|
|
|
ArraySelectNode topExtractors selectNode = arraySelectNode
|
|
|
|
selectFrom = S.mkSelFromItem
|
|
|
|
(generateSQLSelect joinCondition selectSource selectNode) $
|
|
|
|
S.Alias $ _ssPrefix selectSource
|
|
|
|
|
2021-04-22 00:44:37 +03:00
|
|
|
mkAggregateSelect
|
|
|
|
:: forall pgKind
|
2021-05-21 05:46:58 +03:00
|
|
|
. ( Backend ('Postgres pgKind)
|
|
|
|
, PostgresAnnotatedFieldJSON pgKind
|
|
|
|
)
|
2021-04-22 00:44:37 +03:00
|
|
|
=> AnnAggregateSelect ('Postgres pgKind)
|
|
|
|
-> S.Select
|
2020-06-08 15:13:01 +03:00
|
|
|
mkAggregateSelect annAggSel =
|
|
|
|
let ((selectSource, nodeExtractors, topExtractor), joinTree) =
|
|
|
|
runWriter $ flip runReaderT strfyNum $
|
|
|
|
processAnnAggregateSelect sourcePrefixes rootFieldName annAggSel
|
|
|
|
selectNode = SelectNode nodeExtractors joinTree
|
|
|
|
arrayNode = ArraySelectNode [topExtractor] selectNode
|
|
|
|
in prefixNumToAliases $
|
|
|
|
generateSQLSelectFromArrayNode selectSource arrayNode $ S.BELit True
|
2018-12-12 15:58:39 +03:00
|
|
|
where
|
2020-06-08 15:13:01 +03:00
|
|
|
strfyNum = _asnStrfyNum annAggSel
|
|
|
|
rootFieldName = FieldName "root"
|
2020-10-27 13:34:31 +03:00
|
|
|
rootIdentifier = toIdentifier rootFieldName
|
|
|
|
sourcePrefixes = SourcePrefixes rootIdentifier rootIdentifier
|
2018-12-12 15:58:39 +03:00
|
|
|
|
2021-04-22 00:44:37 +03:00
|
|
|
mkSQLSelect
|
|
|
|
:: forall pgKind
|
2021-05-21 05:46:58 +03:00
|
|
|
. ( Backend ('Postgres pgKind)
|
|
|
|
, PostgresAnnotatedFieldJSON pgKind
|
|
|
|
)
|
2021-04-22 00:44:37 +03:00
|
|
|
=> JsonAggSelect
|
|
|
|
-> AnnSimpleSel ('Postgres pgKind)
|
|
|
|
-> S.Select
|
2020-02-13 20:38:23 +03:00
|
|
|
mkSQLSelect jsonAggSelect annSel =
|
2020-06-08 15:13:01 +03:00
|
|
|
let permLimitSubQuery = PLSQNotRequired
|
|
|
|
((selectSource, nodeExtractors), joinTree) =
|
|
|
|
runWriter $ flip runReaderT strfyNum $
|
|
|
|
processAnnSimpleSelect sourcePrefixes rootFldName permLimitSubQuery annSel
|
|
|
|
selectNode = SelectNode nodeExtractors joinTree
|
|
|
|
topExtractor = asJsonAggExtr jsonAggSelect rootFldAls permLimitSubQuery
|
|
|
|
$ _ssOrderBy selectSource
|
|
|
|
arrayNode = ArraySelectNode [topExtractor] selectNode
|
|
|
|
in prefixNumToAliases $
|
|
|
|
generateSQLSelectFromArrayNode selectSource arrayNode $ S.BELit True
|
2018-12-12 15:58:39 +03:00
|
|
|
where
|
2020-06-08 15:13:01 +03:00
|
|
|
strfyNum = _asnStrfyNum annSel
|
2020-10-27 13:34:31 +03:00
|
|
|
rootFldIdentifier = toIdentifier rootFldName
|
|
|
|
sourcePrefixes = SourcePrefixes rootFldIdentifier rootFldIdentifier
|
2018-12-12 15:58:39 +03:00
|
|
|
rootFldName = FieldName "root"
|
2020-10-27 13:34:31 +03:00
|
|
|
rootFldAls = S.Alias $ toIdentifier rootFldName
|
2020-06-08 15:13:01 +03:00
|
|
|
|
2021-04-22 00:44:37 +03:00
|
|
|
mkConnectionSelect
|
|
|
|
:: forall pgKind
|
2021-05-21 05:46:58 +03:00
|
|
|
. ( Backend ('Postgres pgKind)
|
|
|
|
, PostgresAnnotatedFieldJSON pgKind
|
|
|
|
)
|
server: IR for DB-DB joins
### Description
This PR adds the required IR for DB to DB joins, based on @paf31 and @0x777 's `feature/db-to-db` branch.
To do so, it also refactors the IR to introduce a new type parameter, `r`, which is used to recursively constructs the `v` parameter of remote QueryDBs. When collecting remote joins, we replace `r` with `Const Void`, indicating at the type level that there cannot be any leftover remote join.
Furthermore, this PR refactors IR.Select for readability, moves some code from IR.Root to IR.Select to avoid having to deal with circular dependencies, and makes it compile by adding `error` in all new cases in the execution pipeline.
The diff doesn't make it clear, but most of Select.hs is actually unchanged. Declarations have just been reordered by topic, in the following order:
- type declarations
- instance declarations
- type aliases
- constructor functions
- traverse functions
https://github.com/hasura/graphql-engine-mono/pull/1580
Co-authored-by: Phil Freeman <630306+paf31@users.noreply.github.com>
GitOrigin-RevId: bbdcb4119cec8bb3fc32f1294f91b8dea0728721
2021-06-18 02:12:11 +03:00
|
|
|
=> ConnectionSelect ('Postgres pgKind) (Const Void) S.SQLExp
|
2021-04-22 00:44:37 +03:00
|
|
|
-> S.SelectWithG S.Select
|
2020-06-08 15:13:01 +03:00
|
|
|
mkConnectionSelect connectionSelect =
|
|
|
|
let ((connectionSource, topExtractor, nodeExtractors), joinTree) =
|
|
|
|
runWriter $ flip runReaderT strfyNum $
|
|
|
|
processConnectionSelect sourcePrefixes rootFieldName
|
2020-10-27 13:34:31 +03:00
|
|
|
(S.Alias rootIdentifier) mempty connectionSelect
|
2020-06-08 15:13:01 +03:00
|
|
|
selectNode = ArraySelectNode [topExtractor] $
|
|
|
|
SelectNode nodeExtractors joinTree
|
|
|
|
in prefixNumToAliasesSelectWith $
|
2020-10-27 13:34:31 +03:00
|
|
|
connectionToSelectWith (S.Alias rootIdentifier) connectionSource selectNode
|
2020-06-08 15:13:01 +03:00
|
|
|
where
|
|
|
|
strfyNum = _asnStrfyNum $ _csSelect connectionSelect
|
|
|
|
rootFieldName = FieldName "root"
|
2020-10-27 13:34:31 +03:00
|
|
|
rootIdentifier = toIdentifier rootFieldName
|
|
|
|
sourcePrefixes = SourcePrefixes rootIdentifier rootIdentifier
|
2020-06-08 15:13:01 +03:00
|
|
|
|
|
|
|
-- | 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
|
2020-10-27 13:34:31 +03:00
|
|
|
mkFirstElementExp expIdentifier =
|
2020-06-08 15:13:01 +03:00
|
|
|
-- For Example
|
2020-10-27 13:34:31 +03:00
|
|
|
S.SEArrayIndex (S.SEFnApp "array_agg" [expIdentifier] Nothing) (S.intToSQLExp 1)
|
2020-06-08 15:13:01 +03:00
|
|
|
|
|
|
|
-- | 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
|
2020-10-27 13:34:31 +03:00
|
|
|
mkLastElementExp expIdentifier =
|
|
|
|
let arrayExp = S.SEFnApp "array_agg" [expIdentifier] Nothing
|
2020-06-08 15:13:01 +03:00
|
|
|
in S.SEArrayIndex arrayExp $
|
|
|
|
S.SEFnApp "array_length" [arrayExp, S.intToSQLExp 1] Nothing
|
|
|
|
|
2020-10-27 13:34:31 +03:00
|
|
|
cursorIdentifier :: Identifier
|
|
|
|
cursorIdentifier = Identifier "__cursor"
|
2020-06-08 15:13:01 +03:00
|
|
|
|
2020-10-27 13:34:31 +03:00
|
|
|
startCursorIdentifier :: Identifier
|
|
|
|
startCursorIdentifier = Identifier "__start_cursor"
|
2020-06-08 15:13:01 +03:00
|
|
|
|
2020-10-27 13:34:31 +03:00
|
|
|
endCursorIdentifier :: Identifier
|
|
|
|
endCursorIdentifier = Identifier "__end_cursor"
|
2020-06-08 15:13:01 +03:00
|
|
|
|
2020-10-27 13:34:31 +03:00
|
|
|
hasPreviousPageIdentifier :: Identifier
|
|
|
|
hasPreviousPageIdentifier = Identifier "__has_previous_page"
|
2020-06-08 15:13:01 +03:00
|
|
|
|
2020-10-27 13:34:31 +03:00
|
|
|
hasNextPageIdentifier :: Identifier
|
|
|
|
hasNextPageIdentifier = Identifier "__has_next_page"
|
2020-06-08 15:13:01 +03:00
|
|
|
|
2020-10-27 13:34:31 +03:00
|
|
|
pageInfoSelectAliasIdentifier :: Identifier
|
|
|
|
pageInfoSelectAliasIdentifier = Identifier "__page_info"
|
2020-06-08 15:13:01 +03:00
|
|
|
|
2020-10-27 13:34:31 +03:00
|
|
|
cursorsSelectAliasIdentifier :: Identifier
|
|
|
|
cursorsSelectAliasIdentifier = Identifier "__cursors_select"
|
2020-06-08 15:13:01 +03:00
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
processConnectionSelect
|
2021-05-21 05:46:58 +03:00
|
|
|
:: forall pgKind m
|
|
|
|
. ( MonadReader Bool m
|
2021-03-25 20:50:08 +03:00
|
|
|
, MonadWriter JoinTree m
|
2021-04-22 00:44:37 +03:00
|
|
|
, Backend ('Postgres pgKind)
|
2021-05-21 05:46:58 +03:00
|
|
|
, PostgresAnnotatedFieldJSON pgKind
|
2020-06-08 15:13:01 +03:00
|
|
|
)
|
|
|
|
=> SourcePrefixes
|
|
|
|
-> FieldName
|
|
|
|
-> S.Alias
|
|
|
|
-> HM.HashMap PGCol PGCol
|
server: IR for DB-DB joins
### Description
This PR adds the required IR for DB to DB joins, based on @paf31 and @0x777 's `feature/db-to-db` branch.
To do so, it also refactors the IR to introduce a new type parameter, `r`, which is used to recursively constructs the `v` parameter of remote QueryDBs. When collecting remote joins, we replace `r` with `Const Void`, indicating at the type level that there cannot be any leftover remote join.
Furthermore, this PR refactors IR.Select for readability, moves some code from IR.Root to IR.Select to avoid having to deal with circular dependencies, and makes it compile by adding `error` in all new cases in the execution pipeline.
The diff doesn't make it clear, but most of Select.hs is actually unchanged. Declarations have just been reordered by topic, in the following order:
- type declarations
- instance declarations
- type aliases
- constructor functions
- traverse functions
https://github.com/hasura/graphql-engine-mono/pull/1580
Co-authored-by: Phil Freeman <630306+paf31@users.noreply.github.com>
GitOrigin-RevId: bbdcb4119cec8bb3fc32f1294f91b8dea0728721
2021-06-18 02:12:11 +03:00
|
|
|
-> ConnectionSelect ('Postgres pgKind) (Const Void) S.SQLExp
|
2021-03-25 20:50:08 +03:00
|
|
|
-> m ( ArrayConnectionSource
|
2020-06-08 15:13:01 +03:00
|
|
|
, 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
|
|
|
|
|
2020-10-27 13:34:31 +03:00
|
|
|
let mkCursorExtractor = (S.Alias cursorIdentifier,) . (`S.SETyAnn` S.textTypeAnn)
|
2020-06-08 15:13:01 +03:00
|
|
|
cursorExtractors = case maybeOrderByCursor of
|
|
|
|
Just orderByCursor -> [mkCursorExtractor orderByCursor]
|
|
|
|
Nothing ->
|
|
|
|
-- Extract primary key columns from base select along with cursor expression.
|
|
|
|
-- Those columns are required to perform connection split via a WHERE clause.
|
2020-06-16 17:25:49 +03:00
|
|
|
mkCursorExtractor primaryKeyColumnsObjectExp : primaryKeyColumnExtractors
|
2020-06-08 15:13:01 +03:00
|
|
|
orderByExp = _ssOrderBy selectSource
|
|
|
|
(topExtractorExp, exps) <- flip runStateT [] $ processFields orderByExp
|
2020-10-27 13:34:31 +03:00
|
|
|
let topExtractor = S.Extractor topExtractorExp $ Just $ S.Alias fieldIdentifier
|
2020-06-08 15:13:01 +03:00
|
|
|
allExtractors = HM.fromList $ cursorExtractors <> exps <> orderByAndDistinctExtrs
|
|
|
|
arrayConnectionSource = ArrayConnectionSource relAlias colMapping
|
|
|
|
(mkSplitBoolExp <$> maybeSplit) maybeSlice selectSource
|
|
|
|
pure ( arrayConnectionSource
|
|
|
|
, topExtractor
|
|
|
|
, allExtractors
|
|
|
|
)
|
|
|
|
where
|
2021-02-03 19:17:20 +03:00
|
|
|
ConnectionSelect _ primaryKeyColumns maybeSplit maybeSlice select = connectionSelect
|
2020-06-08 15:13:01 +03:00
|
|
|
AnnSelectG fields selectFrom tablePermissions tableArgs _ = select
|
2020-10-27 13:34:31 +03:00
|
|
|
fieldIdentifier = toIdentifier fieldAlias
|
2020-06-08 15:13:01 +03:00
|
|
|
thisPrefix = _pfThis sourcePrefixes
|
|
|
|
permLimitSubQuery = PLSQNotRequired
|
|
|
|
|
2020-06-16 17:25:49 +03:00
|
|
|
primaryKeyColumnsObjectExp =
|
|
|
|
S.applyJsonBuildObj $ flip concatMap (toList primaryKeyColumns) $
|
|
|
|
\pgColumnInfo ->
|
|
|
|
[ S.SELit $ getPGColTxt $ pgiColumn pgColumnInfo
|
|
|
|
, toJSONableExp False (pgiType pgColumnInfo) False $
|
|
|
|
S.mkQIdenExp (mkBaseTableAlias thisPrefix) $ pgiColumn pgColumnInfo
|
|
|
|
]
|
|
|
|
|
2020-06-08 15:13:01 +03:00
|
|
|
primaryKeyColumnExtractors =
|
|
|
|
flip map (toList primaryKeyColumns) $
|
|
|
|
\pgColumnInfo ->
|
|
|
|
let pgColumn = pgiColumn 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
|
2020-11-12 12:25:48 +03:00
|
|
|
obTy = fromMaybe S.OTAsc obTyM
|
2020-06-08 15:13:01 +03:00
|
|
|
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
|
2020-10-27 13:34:31 +03:00
|
|
|
in S.BECompare compareOp (S.SEIdentifier $ toIdentifier obAlias) v
|
2020-06-08 15:13:01 +03:00
|
|
|
|
|
|
|
mkEqualityCompareExp (ConnectionSplit _ v orderByItem) =
|
|
|
|
let obAlias = mkAnnOrderByAlias thisPrefix fieldAlias similarArrayFields $
|
|
|
|
obiColumn orderByItem
|
2020-10-27 13:34:31 +03:00
|
|
|
in S.BECompare S.SEQ (S.SEIdentifier $ toIdentifier obAlias) v
|
2020-06-08 15:13:01 +03:00
|
|
|
|
|
|
|
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
|
2021-05-21 05:46:58 +03:00
|
|
|
:: forall n
|
|
|
|
. ( MonadReader Bool n
|
|
|
|
, MonadWriter JoinTree n
|
|
|
|
, MonadState [(S.Alias, S.SQLExp)] n
|
2020-06-08 15:13:01 +03:00
|
|
|
)
|
2021-05-21 05:46:58 +03:00
|
|
|
=> Maybe S.OrderByExp -> n S.SQLExp
|
2020-06-08 15:13:01 +03:00
|
|
|
processFields orderByExp =
|
|
|
|
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 orderByExp . S.applyJsonBuildObj . concat) $ forM edges $
|
|
|
|
\(FieldName edgeText, edge) -> (S.SELit edgeText:) . pure <$>
|
|
|
|
case edge of
|
|
|
|
EdgeTypename t -> pure $ S.SELit t
|
2020-10-27 13:34:31 +03:00
|
|
|
EdgeCursor -> pure $ encodeBase64 $ S.SEIdentifier (toIdentifier cursorIdentifier)
|
2020-06-08 15:13:01 +03:00
|
|
|
EdgeNode annFields -> do
|
|
|
|
let edgeFieldName = FieldName $
|
|
|
|
getFieldNameTxt fieldAlias <> "." <> fieldText <> "." <> edgeText
|
2020-10-27 13:34:31 +03:00
|
|
|
edgeFieldIdentifier = toIdentifier edgeFieldName
|
2020-06-08 15:13:01 +03:00
|
|
|
annFieldsExtrExp <- processAnnFields thisPrefix edgeFieldName similarArrayFields annFields
|
|
|
|
modify' (<> [annFieldsExtrExp])
|
2020-10-27 13:34:31 +03:00
|
|
|
pure $ S.SEIdentifier edgeFieldIdentifier
|
2020-06-08 15:13:01 +03:00
|
|
|
|
|
|
|
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 $
|
2020-10-27 13:34:31 +03:00
|
|
|
mkSingleFieldSelect (S.SEIdentifier hasNextPageIdentifier) pageInfoSelectAliasIdentifier
|
2020-06-08 15:13:01 +03:00
|
|
|
PageInfoHasPreviousPage -> withForceAggregation S.boolTypeAnn $
|
2020-10-27 13:34:31 +03:00
|
|
|
mkSingleFieldSelect (S.SEIdentifier hasPreviousPageIdentifier) pageInfoSelectAliasIdentifier
|
2020-06-08 15:13:01 +03:00
|
|
|
PageInfoStartCursor -> withForceAggregation S.textTypeAnn $
|
2020-10-27 13:34:31 +03:00
|
|
|
encodeBase64 $ mkSingleFieldSelect (S.SEIdentifier startCursorIdentifier) cursorsSelectAliasIdentifier
|
2020-06-08 15:13:01 +03:00
|
|
|
PageInfoEndCursor -> withForceAggregation S.textTypeAnn $
|
2020-10-27 13:34:31 +03:00
|
|
|
encodeBase64 $ mkSingleFieldSelect (S.SEIdentifier endCursorIdentifier) cursorsSelectAliasIdentifier
|
2020-06-08 15:13:01 +03:00
|
|
|
where
|
2020-10-27 13:34:31 +03:00
|
|
|
mkSingleFieldSelect field fromIdentifier = S.SESelect
|
2020-06-08 15:13:01 +03:00
|
|
|
S.mkSelect { S.selExtr = [S.Extractor field Nothing]
|
2020-10-27 13:34:31 +03:00
|
|
|
, S.selFrom = Just $ S.FromExp [S.FIIdentifier fromIdentifier]
|
2020-06-08 15:13:01 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
connectionToSelectWith
|
|
|
|
:: S.Alias
|
2021-03-25 20:50:08 +03:00
|
|
|
-> ArrayConnectionSource
|
|
|
|
-> ArraySelectNode
|
2020-06-08 15:13:01 +03:00
|
|
|
-> S.SelectWithG S.Select
|
|
|
|
connectionToSelectWith baseSelectAlias arrayConnectionSource arraySelectNode =
|
|
|
|
let extractionSelect = S.mkSelect
|
|
|
|
{ S.selExtr = topExtractors
|
2020-10-27 13:34:31 +03:00
|
|
|
, S.selFrom = Just $ S.FromExp [S.FIIdentifier finalSelectIdentifier]
|
2020-06-08 15:13:01 +03:00
|
|
|
}
|
|
|
|
in S.SelectWith fromBaseSelections extractionSelect
|
|
|
|
where
|
|
|
|
ArrayConnectionSource _ columnMapping maybeSplit maybeSlice selectSource =
|
|
|
|
arrayConnectionSource
|
|
|
|
ArraySelectNode topExtractors selectNode = arraySelectNode
|
2020-10-27 13:34:31 +03:00
|
|
|
baseSelectIdentifier = Identifier "__base_select"
|
|
|
|
splitSelectIdentifier = Identifier "__split_select"
|
|
|
|
sliceSelectIdentifier = Identifier "__slice_select"
|
|
|
|
finalSelectIdentifier = Identifier "__final_select"
|
2020-06-08 15:13:01 +03:00
|
|
|
|
2020-10-27 13:34:31 +03:00
|
|
|
rowNumberIdentifier = Identifier "__row_number"
|
2020-06-08 15:13:01 +03:00
|
|
|
rowNumberExp = S.SEUnsafe "(row_number() over (partition by 1))"
|
2020-10-27 13:34:31 +03:00
|
|
|
startRowNumberIdentifier = Identifier "__start_row_number"
|
|
|
|
endRowNumberIdentifier = Identifier "__end_row_number"
|
2020-06-08 15:13:01 +03:00
|
|
|
|
2020-10-27 13:34:31 +03:00
|
|
|
startCursorExp = mkFirstElementExp $ S.SEIdentifier cursorIdentifier
|
|
|
|
endCursorExp = mkLastElementExp $ S.SEIdentifier cursorIdentifier
|
2020-06-08 15:13:01 +03:00
|
|
|
|
2020-10-27 13:34:31 +03:00
|
|
|
startRowNumberExp = mkFirstElementExp $ S.SEIdentifier rowNumberIdentifier
|
|
|
|
endRowNumberExp = mkLastElementExp $ S.SEIdentifier rowNumberIdentifier
|
2020-06-08 15:13:01 +03:00
|
|
|
|
|
|
|
fromBaseSelections =
|
|
|
|
let joinCond = mkJoinCond baseSelectAlias columnMapping
|
|
|
|
baseSelectFrom = S.mkSelFromItem
|
|
|
|
(generateSQLSelect joinCond selectSource selectNode)
|
|
|
|
$ S.Alias $ _ssPrefix selectSource
|
|
|
|
select =
|
|
|
|
S.mkSelect { S.selExtr = [ S.selectStar
|
2020-10-27 13:34:31 +03:00
|
|
|
, S.Extractor rowNumberExp $ Just $ S.Alias rowNumberIdentifier
|
2020-06-08 15:13:01 +03:00
|
|
|
]
|
|
|
|
, S.selFrom = Just $ S.FromExp [baseSelectFrom]
|
|
|
|
}
|
2020-10-27 13:34:31 +03:00
|
|
|
in (S.Alias baseSelectIdentifier, select):fromSplitSelection
|
2020-06-08 15:13:01 +03:00
|
|
|
|
2020-10-27 13:34:31 +03:00
|
|
|
mkStarSelect fromIdentifier =
|
2020-06-08 15:13:01 +03:00
|
|
|
S.mkSelect { S.selExtr = [S.selectStar]
|
2020-10-27 13:34:31 +03:00
|
|
|
, S.selFrom = Just $ S.FromExp [S.FIIdentifier fromIdentifier]
|
2020-06-08 15:13:01 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
fromSplitSelection = case maybeSplit of
|
2020-10-27 13:34:31 +03:00
|
|
|
Nothing -> fromSliceSelection baseSelectIdentifier
|
2020-06-08 15:13:01 +03:00
|
|
|
Just splitBool ->
|
|
|
|
let select =
|
2020-10-27 13:34:31 +03:00
|
|
|
(mkStarSelect baseSelectIdentifier){S.selWhere = Just $ S.WhereFrag splitBool}
|
|
|
|
in (S.Alias splitSelectIdentifier, select):fromSliceSelection splitSelectIdentifier
|
2020-06-08 15:13:01 +03:00
|
|
|
|
|
|
|
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 =
|
2020-10-27 13:34:31 +03:00
|
|
|
S.OrderByItem (S.SEIdentifier rowNumberIdentifier) (Just obType) Nothing
|
2020-06-08 15:13:01 +03:00
|
|
|
in S.OrderByExp $ orderByItem NE.:| []
|
|
|
|
|
|
|
|
sliceLastSelect = (mkStarSelect prevSelect)
|
|
|
|
{ S.selLimit = (Just . S.LimitExp . S.intToSQLExp) limit
|
|
|
|
, S.selOrderBy = Just $ mkRowNumberOrderBy S.OTDesc
|
|
|
|
}
|
|
|
|
sliceLastSelectFrom =
|
2020-10-27 13:34:31 +03:00
|
|
|
S.mkSelFromItem sliceLastSelect $ S.Alias sliceSelectIdentifier
|
2020-06-08 15:13:01 +03:00
|
|
|
in S.mkSelect { S.selExtr = [S.selectStar]
|
|
|
|
, S.selFrom = Just $ S.FromExp [sliceLastSelectFrom]
|
|
|
|
, S.selOrderBy = Just $ mkRowNumberOrderBy S.OTAsc
|
|
|
|
}
|
2020-10-27 13:34:31 +03:00
|
|
|
in (S.Alias sliceSelectIdentifier, select):fromFinalSelect sliceSelectIdentifier
|
2020-06-08 15:13:01 +03:00
|
|
|
|
|
|
|
fromFinalSelect prevSelect =
|
|
|
|
let select = mkStarSelect prevSelect
|
2020-10-27 13:34:31 +03:00
|
|
|
in (S.Alias finalSelectIdentifier, select):fromCursorSelection
|
2020-06-08 15:13:01 +03:00
|
|
|
|
|
|
|
fromCursorSelection =
|
2020-10-27 13:34:31 +03:00
|
|
|
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
|
2020-06-08 15:13:01 +03:00
|
|
|
]
|
|
|
|
select =
|
|
|
|
S.mkSelect { S.selExtr = extrs
|
2020-10-27 13:34:31 +03:00
|
|
|
, S.selFrom = Just $ S.FromExp [S.FIIdentifier finalSelectIdentifier]
|
2020-06-08 15:13:01 +03:00
|
|
|
}
|
2020-10-27 13:34:31 +03:00
|
|
|
in (S.Alias cursorsSelectAliasIdentifier, select):fromPageInfoSelection
|
2020-06-08 15:13:01 +03:00
|
|
|
|
|
|
|
fromPageInfoSelection =
|
|
|
|
let hasPrevPage = S.SEBool $
|
2020-10-27 13:34:31 +03:00
|
|
|
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]
|
2020-06-08 15:13:01 +03:00
|
|
|
}
|
|
|
|
hasNextPage = S.SEBool $
|
2020-10-27 13:34:31 +03:00
|
|
|
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]
|
2020-06-08 15:13:01 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
select =
|
2020-10-27 13:34:31 +03:00
|
|
|
S.mkSelect { S.selExtr = [ S.Extractor hasPrevPage $ Just $ S.Alias hasPreviousPageIdentifier
|
|
|
|
, S.Extractor hasNextPage $ Just $ S.Alias hasNextPageIdentifier
|
2020-06-08 15:13:01 +03:00
|
|
|
]
|
|
|
|
}
|
2020-10-27 13:34:31 +03:00
|
|
|
in pure (S.Alias pageInfoSelectAliasIdentifier, select)
|