diff --git a/CHANGELOG.md b/CHANGELOG.md index 0651f32839e..886593a704e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Next release (Add entries below in the order of server, console, cli, docs, others) +- server: support computed fields in query 'order_by' (close #7103) - server: log warning if there are errors while executing clean up actions after "drop source" (previously it would throw an error) - server: Fixed a bug where MSSQL and BigQuery would ignore environment variables set from the console - server: Fixing bug in ReplaceMetadata parser - Moving from Alternative to committed-choice. diff --git a/server/src-lib/Hasura/Backends/BigQuery/FromIr.hs b/server/src-lib/Hasura/Backends/BigQuery/FromIr.hs index 479de0a0ee1..1aaefca1ecd 100644 --- a/server/src-lib/Hasura/Backends/BigQuery/FromIr.hs +++ b/server/src-lib/Hasura/Backends/BigQuery/FromIr.hs @@ -403,7 +403,7 @@ fromSelectArgsG selectArgsG = do Nothing -> pure Proxy Just {} -> refute (pure DistinctIsn'tSupported) (argsOrderBy, joins) <- - runWriterT (traverse fromAnnOrderByItemG (maybe [] toList orders)) + runWriterT (traverse fromAnnotatedOrderByItemG (maybe [] toList orders)) -- Any object-relation joins that we generated, we record their -- generated names into a mapping. let argsExistingJoins = @@ -424,10 +424,10 @@ fromSelectArgsG selectArgsG = do -- | Produce a valid ORDER BY construct, telling about any joins -- needed on the side. -fromAnnOrderByItemG :: - Ir.AnnOrderByItemG 'BigQuery Expression -> WriterT (Seq UnfurledJoin) (ReaderT EntityAlias FromIr) OrderBy -fromAnnOrderByItemG Ir.OrderByItemG {obiType, obiColumn, obiNulls} = do - orderByFieldName <- unfurlAnnOrderByElement obiColumn +fromAnnotatedOrderByItemG :: + Ir.AnnotatedOrderByItemG 'BigQuery Expression -> WriterT (Seq UnfurledJoin) (ReaderT EntityAlias FromIr) OrderBy +fromAnnotatedOrderByItemG Ir.OrderByItemG {obiType, obiColumn, obiNulls} = do + orderByFieldName <- unfurlAnnotatedOrderByElement obiColumn let morderByOrder = obiType let orderByNullsOrder = @@ -439,9 +439,9 @@ fromAnnOrderByItemG Ir.OrderByItemG {obiType, obiColumn, obiNulls} = do -- | Unfurl the nested set of object relations (tell'd in the writer) -- that are terminated by field name (Ir.AOCColumn and -- Ir.AOCArrayAggregation). -unfurlAnnOrderByElement :: - Ir.AnnOrderByElement 'BigQuery Expression -> WriterT (Seq UnfurledJoin) (ReaderT EntityAlias FromIr) FieldName -unfurlAnnOrderByElement = +unfurlAnnotatedOrderByElement :: + Ir.AnnotatedOrderByElement 'BigQuery Expression -> WriterT (Seq UnfurledJoin) (ReaderT EntityAlias FromIr) FieldName +unfurlAnnotatedOrderByElement = \case Ir.AOCColumn pgColumnInfo -> lift (fromPGColumnInfo pgColumnInfo) Ir.AOCObjectRelation Rql.RelInfo {riMapping = mapping, riRTable = tableName} annBoolExp annOrderByElementG -> do @@ -477,7 +477,7 @@ unfurlAnnOrderByElement = } , unfurledObjectTableAlias = Just (tableName, joinAliasEntity) }) - local (const joinAliasEntity) (unfurlAnnOrderByElement annOrderByElementG) + local (const joinAliasEntity) (unfurlAnnotatedOrderByElement annOrderByElementG) Ir.AOCArrayAggregation Rql.RelInfo {riMapping = mapping, riRTable = tableName} annBoolExp annAggregateOrderBy -> do selectFrom <- lift (lift (fromQualifiedTable tableName)) let alias = aggFieldName diff --git a/server/src-lib/Hasura/Backends/MSSQL/FromIr.hs b/server/src-lib/Hasura/Backends/MSSQL/FromIr.hs index 7cf4695a971..632bf881481 100644 --- a/server/src-lib/Hasura/Backends/MSSQL/FromIr.hs +++ b/server/src-lib/Hasura/Backends/MSSQL/FromIr.hs @@ -358,7 +358,7 @@ fromSelectArgsG selectArgsG = do -- you can just drop the Proxy wrapper. let argsDistinct = Proxy (argsOrderBy, joins) <- - runWriterT (traverse fromAnnOrderByItemG (maybe [] toList orders)) + runWriterT (traverse fromAnnotatedOrderByItemG (maybe [] toList orders)) -- Any object-relation joins that we generated, we record their -- generated names into a mapping. let argsExistingJoins = @@ -378,11 +378,11 @@ fromSelectArgsG selectArgsG = do -- | Produce a valid ORDER BY construct, telling about any joins -- needed on the side. -fromAnnOrderByItemG - :: IR.AnnOrderByItemG 'MSSQL Expression +fromAnnotatedOrderByItemG + :: IR.AnnotatedOrderByItemG 'MSSQL Expression -> WriterT (Seq UnfurledJoin) (ReaderT EntityAlias FromIr) OrderBy -fromAnnOrderByItemG IR.OrderByItemG {obiType, obiColumn = obiColumn, obiNulls} = do - (orderByFieldName, orderByType) <- unfurlAnnOrderByElement obiColumn +fromAnnotatedOrderByItemG IR.OrderByItemG {obiType, obiColumn = obiColumn, obiNulls} = do + (orderByFieldName, orderByType) <- unfurlAnnotatedOrderByElement obiColumn let orderByNullsOrder = fromMaybe NullsAnyOrder obiNulls orderByOrder = fromMaybe AscOrder obiType pure OrderBy {..} @@ -390,10 +390,10 @@ fromAnnOrderByItemG IR.OrderByItemG {obiType, obiColumn = obiColumn, obiNulls} = -- | Unfurl the nested set of object relations (tell'd in the writer) -- that are terminated by field name (IR.AOCColumn and -- IR.AOCArrayAggregation). -unfurlAnnOrderByElement - :: IR.AnnOrderByElement 'MSSQL Expression +unfurlAnnotatedOrderByElement + :: IR.AnnotatedOrderByElement 'MSSQL Expression -> WriterT (Seq UnfurledJoin) (ReaderT EntityAlias FromIr) (FieldName, Maybe TSQL.ScalarType) -unfurlAnnOrderByElement = +unfurlAnnotatedOrderByElement = \case IR.AOCColumn pgColumnInfo -> do fieldName <- lift (fromPGColumnInfo pgColumnInfo) @@ -444,7 +444,7 @@ unfurlAnnOrderByElement = }) local (const (EntityAlias joinAliasEntity)) - (unfurlAnnOrderByElement annOrderByElementG) + (unfurlAnnotatedOrderByElement annOrderByElementG) IR.AOCArrayAggregation IR.RelInfo {riMapping = mapping, riRTable = tableName} annBoolExp annAggregateOrderBy -> do selectFrom <- lift (lift (fromQualifiedTable tableName)) let alias = aggFieldName diff --git a/server/src-lib/Hasura/Backends/Postgres/Translate/Select.hs b/server/src-lib/Hasura/Backends/Postgres/Translate/Select.hs index aa9c30f1ec3..a635c4bdfe3 100644 --- a/server/src-lib/Hasura/Backends/Postgres/Translate/Select.hs +++ b/server/src-lib/Hasura/Backends/Postgres/Translate/Select.hs @@ -220,11 +220,11 @@ mkBaseTableColumnAlias :: Identifier -> PGCol -> Identifier mkBaseTableColumnAlias pfx pgColumn = pfx <> Identifier ".pg." <> toIdentifier pgColumn -mkOrderByFieldName :: RelName -> FieldName -mkOrderByFieldName relName = - FieldName $ relNameToTxt relName <> "." <> "order_by" +mkOrderByFieldName :: ToTxt a => a -> FieldName +mkOrderByFieldName name = + FieldName $ toTxt name <> "." <> "order_by" -mkAggregateOrderByAlias :: AnnAggregateOrderBy ('Postgres pgKind) -> S.Alias +mkAggregateOrderByAlias :: AnnotatedAggregateOrderBy ('Postgres pgKind) -> S.Alias mkAggregateOrderByAlias = (S.Alias . Identifier) . \case AAOCount -> "count" AAOOp opText col -> opText <> "." <> getPGColTxt (pgiColumn col) @@ -284,7 +284,7 @@ withForceAggregation tyAnn e = mkAggregateOrderByExtractorAndFields :: forall pgKind . Backend ('Postgres pgKind) - => AnnAggregateOrderBy ('Postgres pgKind) + => AnnotatedAggregateOrderBy ('Postgres pgKind) -> (S.Extractor, AggregateFields ('Postgres pgKind)) mkAggregateOrderByExtractorAndFields annAggOrderBy = case annAggOrderBy of @@ -309,7 +309,7 @@ mkAggregateOrderByExtractorAndFields annAggOrderBy = alias = Just $ mkAggregateOrderByAlias annAggOrderBy mkAnnOrderByAlias - :: Identifier -> FieldName -> SimilarArrayFields -> AnnOrderByElementG ('Postgres pgKind) v -> S.Alias + :: Identifier -> FieldName -> SimilarArrayFields -> AnnotatedOrderByElement ('Postgres pgKind) v -> S.Alias mkAnnOrderByAlias pfx parAls similarFields = \case AOCColumn pgColumnInfo -> let pgColumn = pgiColumn pgColumnInfo @@ -328,6 +328,14 @@ mkAnnOrderByAlias pfx parAls similarFields = \case mkOrderByFieldName rn obAls = arrPfx <> Identifier "." <> toIdentifier (mkAggregateOrderByAlias aggOrderBy) in S.Alias obAls + AOCComputedField cfOrderBy -> + let fieldName = fromComputedField $ _cfobName cfOrderBy + in case _cfobOrderByElement cfOrderBy of + CFOBEScalar _ -> S.Alias $ mkComputedFieldTableAlias pfx fieldName + CFOBETableAggregation _ _ aggOrderBy -> + let cfPfx = mkComputedFieldTableAlias pfx fieldName + obAls = cfPfx <> Identifier "." <> toIdentifier (mkAggregateOrderByAlias aggOrderBy) + in S.Alias obAls processDistinctOnColumns :: Identifier @@ -349,7 +357,7 @@ mkSimilarArrayFields :: forall pgKind v . (Backend ('Postgres pgKind), Eq v) => AnnFieldsG ('Postgres pgKind) (Const Void) v - -> Maybe (NE.NonEmpty (AnnOrderByItemG ('Postgres pgKind) v)) + -> Maybe (NE.NonEmpty (AnnotatedOrderByItemG ('Postgres pgKind) v)) -> SimilarArrayFields mkSimilarArrayFields annFields maybeOrderBys = HM.fromList $ flip map allTuples $ @@ -432,7 +440,7 @@ withWriteArrayRelation action = pure (out, (source, topExtractor, nodeExtractors)) where updateJoinTree joinTree (source, topExtractor, nodeExtractors) = - let arraySelectNode = ArraySelectNode [topExtractor] $ + let arraySelectNode = MultiRowSelectNode [topExtractor] $ SelectNode nodeExtractors joinTree in mempty{_jtArrayRelations = HM.singleton source arraySelectNode} @@ -450,24 +458,25 @@ withWriteArrayConnection action = pure (out, (source, topExtractor, nodeExtractors)) where updateJoinTree joinTree (source, topExtractor, nodeExtractors) = - let arraySelectNode = ArraySelectNode [topExtractor] $ + let arraySelectNode = MultiRowSelectNode [topExtractor] $ SelectNode nodeExtractors joinTree in mempty{_jtArrayConnections = HM.singleton source arraySelectNode} withWriteComputedFieldTableSet :: (MonadWriter JoinTree m) => m ( ComputedFieldTableSetSource + , S.Extractor , HM.HashMap S.Alias S.SQLExp , a ) -> m a withWriteComputedFieldTableSet action = withWriteJoinTree updateJoinTree $ do - (source, nodeExtractors, out) <- action - pure (out, (source, nodeExtractors)) + (source, topExtractor, nodeExtractors, out) <- action + pure (out, (source, topExtractor, nodeExtractors)) where - updateJoinTree joinTree (source, nodeExtractors) = - let selectNode = SelectNode nodeExtractors joinTree + updateJoinTree joinTree (source, topExtractor, nodeExtractors) = + let selectNode = MultiRowSelectNode [topExtractor] $ SelectNode nodeExtractors joinTree in mempty{_jtComputedFieldTableSets = HM.singleton source selectNode} @@ -557,7 +566,7 @@ processAnnAggregateSelect sourcePrefixes fieldAlias annAggSel = do mkPermissionLimitSubQuery :: Maybe Int -> TableAggregateFields ('Postgres pgKind) - -> Maybe (NE.NonEmpty (AnnOrderByItem ('Postgres pgKind))) + -> Maybe (NE.NonEmpty (AnnotatedOrderByItem ('Postgres pgKind))) -> PermissionLimitSubQuery mkPermissionLimitSubQuery permLimit aggFields orderBys = case permLimit of @@ -685,7 +694,7 @@ processOrderByItems => Identifier -> FieldName -> SimilarArrayFields - -> NE.NonEmpty (AnnOrderByItem ('Postgres pgKind)) + -> NE.NonEmpty (AnnotatedOrderByItem ('Postgres pgKind)) -> m ( [(S.Alias, S.SQLExp)] -- Order by Extractors , S.OrderByExp , S.SQLExp -- The cursor expression @@ -698,15 +707,15 @@ processOrderByItems sourcePrefix' fieldAlias' similarArrayFields orderByItems = pure (orderByExtractors, orderByExp, cursor) where processAnnOrderByItem - :: AnnOrderByItem ('Postgres pgKind) - -> m (OrderByItemG ('Postgres pgKind) (AnnOrderByElement ('Postgres pgKind) (SQLExpression ('Postgres pgKind)), (S.Alias, SQLExpression ('Postgres pgKind)))) + :: AnnotatedOrderByItem ('Postgres pgKind) + -> m (OrderByItemG ('Postgres pgKind) (AnnotatedOrderByElement ('Postgres pgKind) (SQLExpression ('Postgres pgKind)), (S.Alias, SQLExpression ('Postgres pgKind)))) processAnnOrderByItem orderByItem = forM orderByItem $ \ordByCol -> (ordByCol,) <$> - processAnnOrderByElement sourcePrefix' fieldAlias' ordByCol + processAnnotatedOrderByElement sourcePrefix' fieldAlias' ordByCol - processAnnOrderByElement - :: Identifier -> FieldName -> AnnOrderByElement ('Postgres pgKind) S.SQLExp -> m (S.Alias, S.SQLExp) - processAnnOrderByElement sourcePrefix fieldAlias annObCol = do + processAnnotatedOrderByElement + :: Identifier -> FieldName -> AnnotatedOrderByElement ('Postgres pgKind) S.SQLExp -> m (S.Alias, S.SQLExp) + processAnnotatedOrderByElement sourcePrefix fieldAlias annObCol = do let ordByAlias = mkAnnOrderByAlias sourcePrefix fieldAlias similarArrayFields annObCol (ordByAlias, ) <$> case annObCol of AOCColumn pgColInfo -> pure $ @@ -717,7 +726,7 @@ processOrderByItems sourcePrefix' fieldAlias' similarArrayFields orderByItems = relSourcePrefix = mkObjectRelationTableAlias sourcePrefix relName fieldName = mkOrderByFieldName relName (relOrderByAlias, relOrdByExp) <- - processAnnOrderByElement relSourcePrefix fieldName rest + processAnnotatedOrderByElement relSourcePrefix fieldName rest let selectSource = ObjectSelectSource relSourcePrefix (S.FISimple relTable Nothing) (toSQLBoolExp (S.QualTable relTable) relFilter) @@ -745,15 +754,38 @@ processOrderByItems sourcePrefix' fieldAlias' similarArrayFields orderByItems = , S.mkQIdenExp relSourcePrefix (mkAggregateOrderByAlias aggOrderBy) ) + AOCComputedField ComputedFieldOrderBy{..} -> + case _cfobOrderByElement of + CFOBEScalar _ -> do + let functionArgs = fromTableRowArgs sourcePrefix _cfobFunctionArgsExp + functionExp = S.FunctionExp _cfobFunction functionArgs Nothing + pure $ S.SEFunction functionExp + CFOBETableAggregation _ tableFilter aggOrderBy -> withWriteComputedFieldTableSet $ do + let fieldName = mkOrderByFieldName _cfobName + computedFieldSourcePrefix = mkComputedFieldTableAlias sourcePrefix fieldName + (topExtractor, fields) = mkAggregateOrderByExtractorAndFields aggOrderBy + fromItem = selectFromToFromItem sourcePrefix $ + FromFunction _cfobFunction _cfobFunctionArgsExp Nothing + functionQual = S.QualifiedIdentifier (functionToIdentifier _cfobFunction) Nothing + selectSource = SelectSource computedFieldSourcePrefix fromItem Nothing + (toSQLBoolExp functionQual tableFilter) + Nothing Nothing Nothing + source = ComputedFieldTableSetSource fieldName selectSource + pure ( source + , topExtractor + , HM.fromList $ aggregateFieldsToExtractorExps computedFieldSourcePrefix fields + , S.mkQIdenExp computedFieldSourcePrefix (mkAggregateOrderByAlias aggOrderBy) + ) + toOrderByExp - :: OrderByItemG ('Postgres pgKind) (AnnOrderByElement ('Postgres pgKind) (SQLExpression ('Postgres pgKind)), (S.Alias, SQLExpression ('Postgres pgKind))) + :: OrderByItemG ('Postgres pgKind) (AnnotatedOrderByElement ('Postgres pgKind) (SQLExpression ('Postgres pgKind)), (S.Alias, SQLExpression ('Postgres pgKind))) -> S.OrderByItem toOrderByExp orderByItemExp = let OrderByItemG obTyM expAlias obNullsM = fst . snd <$> orderByItemExp in S.OrderByItem (S.SEIdentifier $ toIdentifier expAlias) obTyM obNullsM mkCursorExp - :: [OrderByItemG ('Postgres pgKind) (AnnOrderByElement ('Postgres pgKind) (SQLExpression ('Postgres pgKind)), (S.Alias, SQLExpression ('Postgres pgKind)))] + :: [OrderByItemG ('Postgres pgKind) (AnnotatedOrderByElement ('Postgres pgKind) (SQLExpression ('Postgres pgKind)), (S.Alias, SQLExpression ('Postgres pgKind)))] -> S.SQLExp mkCursorExp orderByItemExps = S.applyJsonBuildObj $ flip concatMap orderByItemExps $ @@ -761,6 +793,13 @@ processOrderByItems sourcePrefix' fieldAlias' similarArrayFields orderByItems = let OrderByItemG _ (annObCol, (_, valExp)) _ = orderByItemExp in annObColToJSONField valExp annObCol where + mkAggOrderByValExp valExp = \case + AAOCount -> [S.SELit "count", valExp] + AAOOp opText colInfo -> + [ S.SELit opText + , S.applyJsonBuildObj [S.SELit $ getPGColTxt $ pgiColumn colInfo, valExp] + ] + annObColToJSONField valExp = \case AOCColumn pgCol -> [S.SELit $ getPGColTxt $ pgiColumn pgCol, valExp] AOCObjectRelation relInfo _ obCol -> @@ -769,14 +808,16 @@ processOrderByItems sourcePrefix' fieldAlias' similarArrayFields orderByItems = ] 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] - ] + , S.applyJsonBuildObj $ mkAggOrderByValExp valExp aggOrderBy ] + AOCComputedField cfOrderBy -> + let fieldNameText = computedFieldNameToText $ _cfobName cfOrderBy + in case _cfobOrderByElement cfOrderBy of + CFOBEScalar _ -> [S.SELit fieldNameText, valExp] + CFOBETableAggregation _ _ aggOrderBy -> + [ S.SELit $ fieldNameText <> "_aggregate" + , S.applyJsonBuildObj $ mkAggOrderByValExp valExp aggOrderBy + ] aggregateFieldsToExtractorExps :: Identifier -> AggregateFields ('Postgres pgKind) -> [(S.Alias, S.SQLExp)] @@ -917,9 +958,11 @@ processAnnFields sourcePrefix fieldAlias similarArrFields annFields = do (selectSource, nodeExtractors) <- processAnnSimpleSelect (mkSourcePrefixes computedFieldSourcePrefix) fieldName PLSQNotRequired sel - let computedFieldTableSetSource = - ComputedFieldTableSetSource fieldName selectTy selectSource + let computedFieldTableSetSource = ComputedFieldTableSetSource fieldName selectSource + extractor = asJsonAggExtr selectTy (S.toAlias fieldName) PLSQNotRequired $ + _ssOrderBy selectSource pure ( computedFieldTableSetSource + , extractor , nodeExtractors , S.mkQIdenExp computedFieldSourcePrefix fieldName ) @@ -1027,7 +1070,6 @@ generateSQLSelect joinCondition selectSource selectNode = map arrayConnectionToFromItem (HM.toList arrayConnections) <> map computedFieldToFromItem (HM.toList computedFields) - objectRelationToFromItem :: (ObjectRelationSource, SelectNode) -> S.FromItem objectRelationToFromItem (objectRelationSource, node) = @@ -1038,7 +1080,7 @@ generateSQLSelect joinCondition selectSource selectNode = in S.mkLateralFromItem select alias arrayRelationToFromItem - :: (ArrayRelationSource, ArraySelectNode) -> S.FromItem + :: (ArrayRelationSource, MultiRowSelectNode) -> S.FromItem arrayRelationToFromItem (arrayRelationSource, arraySelectNode) = let ArrayRelationSource _ colMapping source = arrayRelationSource alias = S.Alias $ _ssPrefix source @@ -1047,29 +1089,27 @@ generateSQLSelect joinCondition selectSource selectNode = in S.mkLateralFromItem select alias arrayConnectionToFromItem - :: (ArrayConnectionSource, ArraySelectNode) -> S.FromItem + :: (ArrayConnectionSource, MultiRowSelectNode) -> S.FromItem arrayConnectionToFromItem (arrayConnectionSource, arraySelectNode) = let selectWith = connectionToSelectWith baseSelectAlias arrayConnectionSource arraySelectNode alias = S.Alias $ _ssPrefix $ _acsSource arrayConnectionSource in S.FISelectWith (S.Lateral True) selectWith alias computedFieldToFromItem - :: (ComputedFieldTableSetSource, SelectNode) -> S.FromItem + :: (ComputedFieldTableSetSource, MultiRowSelectNode) -> S.FromItem 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 + let ComputedFieldTableSetSource _ source = computedFieldTableSource + internalSelect = generateSQLSelect (S.BELit True) source $ _mrsnSelectNode node alias = S.Alias $ _ssPrefix source select = S.mkSelect - { S.selExtr = [extractor] + { S.selExtr = _mrsnTopExtractors node , S.selFrom = Just $ S.FromExp [S.mkSelFromItem internalSelect alias] } in S.mkLateralFromItem select alias generateSQLSelectFromArrayNode :: SelectSource - -> ArraySelectNode + -> MultiRowSelectNode -> S.BoolExp -> S.Select generateSQLSelectFromArrayNode selectSource arraySelectNode joinCondition = @@ -1078,7 +1118,7 @@ generateSQLSelectFromArrayNode selectSource arraySelectNode joinCondition = , S.selFrom = Just $ S.FromExp [selectFrom] } where - ArraySelectNode topExtractors selectNode = arraySelectNode + MultiRowSelectNode topExtractors selectNode = arraySelectNode selectFrom = S.mkSelFromItem (generateSQLSelect joinCondition selectSource selectNode) $ S.Alias $ _ssPrefix selectSource @@ -1095,7 +1135,7 @@ mkAggregateSelect annAggSel = runWriter $ flip runReaderT strfyNum $ processAnnAggregateSelect sourcePrefixes rootFieldName annAggSel selectNode = SelectNode nodeExtractors joinTree - arrayNode = ArraySelectNode [topExtractor] selectNode + arrayNode = MultiRowSelectNode [topExtractor] selectNode in prefixNumToAliases $ generateSQLSelectFromArrayNode selectSource arrayNode $ S.BELit True where @@ -1120,7 +1160,7 @@ mkSQLSelect jsonAggSelect annSel = selectNode = SelectNode nodeExtractors joinTree topExtractor = asJsonAggExtr jsonAggSelect rootFldAls permLimitSubQuery $ _ssOrderBy selectSource - arrayNode = ArraySelectNode [topExtractor] selectNode + arrayNode = MultiRowSelectNode [topExtractor] selectNode in prefixNumToAliases $ generateSQLSelectFromArrayNode selectSource arrayNode $ S.BELit True where @@ -1142,7 +1182,7 @@ mkConnectionSelect connectionSelect = runWriter $ flip runReaderT strfyNum $ processConnectionSelect sourcePrefixes rootFieldName (S.Alias rootIdentifier) mempty connectionSelect - selectNode = ArraySelectNode [topExtractor] $ + selectNode = MultiRowSelectNode [topExtractor] $ SelectNode nodeExtractors joinTree in prefixNumToAliasesSelectWith $ connectionToSelectWith (S.Alias rootIdentifier) connectionSource selectNode @@ -1350,7 +1390,7 @@ processConnectionSelect sourcePrefixes fieldAlias relAlias colMapping connection connectionToSelectWith :: S.Alias -> ArrayConnectionSource - -> ArraySelectNode + -> MultiRowSelectNode -> S.SelectWithG S.Select connectionToSelectWith baseSelectAlias arrayConnectionSource arraySelectNode = let extractionSelect = S.mkSelect @@ -1361,7 +1401,7 @@ connectionToSelectWith baseSelectAlias arrayConnectionSource arraySelectNode = where ArrayConnectionSource _ columnMapping maybeSplit maybeSlice selectSource = arrayConnectionSource - ArraySelectNode topExtractors selectNode = arraySelectNode + MultiRowSelectNode topExtractors selectNode = arraySelectNode baseSelectIdentifier = Identifier "__base_select" splitSelectIdentifier = Identifier "__split_select" sliceSelectIdentifier = Identifier "__slice_select" diff --git a/server/src-lib/Hasura/Backends/Postgres/Translate/Types.hs b/server/src-lib/Hasura/Backends/Postgres/Translate/Types.hs index d5e68faad59..b22003ba701 100644 --- a/server/src-lib/Hasura/Backends/Postgres/Translate/Types.hs +++ b/server/src-lib/Hasura/Backends/Postgres/Translate/Types.hs @@ -79,20 +79,19 @@ data ArrayRelationSource instance Hashable ArrayRelationSource deriving instance Eq ArrayRelationSource -data ArraySelectNode - = ArraySelectNode - { _asnTopExtractors :: ![PG.Extractor] - , _asnSelectNode :: !SelectNode +data MultiRowSelectNode + = MultiRowSelectNode + { _mrsnTopExtractors :: ![PG.Extractor] + , _mrsnSelectNode :: !SelectNode } -instance Semigroup ArraySelectNode where - ArraySelectNode lTopExtrs lSelNode <> ArraySelectNode rTopExtrs rSelNode = - ArraySelectNode (lTopExtrs <> rTopExtrs) (lSelNode <> rSelNode) +instance Semigroup MultiRowSelectNode where + MultiRowSelectNode lTopExtrs lSelNode <> MultiRowSelectNode rTopExtrs rSelNode = + MultiRowSelectNode (lTopExtrs <> rTopExtrs) (lSelNode <> rSelNode) data ComputedFieldTableSetSource = ComputedFieldTableSetSource { _cftssFieldName :: !FieldName - , _cftssSelectType :: !JsonAggSelect , _cftssSelectSource :: !SelectSource } deriving (Generic) instance Hashable ComputedFieldTableSetSource @@ -114,9 +113,9 @@ instance Hashable ArrayConnectionSource data JoinTree = JoinTree { _jtObjectRelations :: !(HM.HashMap ObjectRelationSource SelectNode) - , _jtArrayRelations :: !(HM.HashMap ArrayRelationSource ArraySelectNode) - , _jtArrayConnections :: !(HM.HashMap ArrayConnectionSource ArraySelectNode) - , _jtComputedFieldTableSets :: !(HM.HashMap ComputedFieldTableSetSource SelectNode) + , _jtArrayRelations :: !(HM.HashMap ArrayRelationSource MultiRowSelectNode) + , _jtArrayConnections :: !(HM.HashMap ArrayConnectionSource MultiRowSelectNode) + , _jtComputedFieldTableSets :: !(HM.HashMap ComputedFieldTableSetSource MultiRowSelectNode) } instance Semigroup JoinTree where diff --git a/server/src-lib/Hasura/GraphQL/Execute/RemoteJoin/Collect.hs b/server/src-lib/Hasura/GraphQL/Execute/RemoteJoin/Collect.hs index 1ef7eac5cb5..e6aab2a67d8 100644 --- a/server/src-lib/Hasura/GraphQL/Execute/RemoteJoin/Collect.hs +++ b/server/src-lib/Hasura/GraphQL/Execute/RemoteJoin/Collect.hs @@ -322,19 +322,10 @@ transformAnnFields path fields = do mkScalarComputedFieldSelect :: ScalarComputedField b -> (AnnFieldG b (Const Void) (UnpreparedValue b)) mkScalarComputedFieldSelect ScalarComputedField{..} = let functionArgs = flip FunctionArgsExp mempty - $ functionArgsWithTableRowAndSession _scfTableArgument _scfSessionArgument + $ functionArgsWithTableRowAndSession UVSession _scfTableArgument _scfSessionArgument fieldSelect = flip CFSScalar Nothing $ ComputedFieldScalarSelect _scfFunction functionArgs _scfType Nothing in AFComputedField _scfXField _scfName fieldSelect - where - functionArgsWithTableRowAndSession - :: FunctionTableArgument - -> Maybe FunctionSessionArgument - -> [ArgumentExp b (UnpreparedValue b)] - functionArgsWithTableRowAndSession _ Nothing = [AETableRow Nothing] -- No session argument - functionArgsWithTableRowAndSession (FTAFirst) _ = [AETableRow Nothing, AESession UVSession] - functionArgsWithTableRowAndSession (FTANamed _ 0) _ = [AETableRow Nothing, AESession UVSession] -- Index is 0 implies table argument is first - functionArgsWithTableRowAndSession _ _ = [AESession UVSession, AETableRow Nothing] mapToNonEmpty :: RemoteJoinMap -> Maybe RemoteJoins diff --git a/server/src-lib/Hasura/GraphQL/Schema/OrderBy.hs b/server/src-lib/Hasura/GraphQL/Schema/OrderBy.hs index 83960e3e7aa..2dac9ff2da8 100644 --- a/server/src-lib/Hasura/GraphQL/Schema/OrderBy.hs +++ b/server/src-lib/Hasura/GraphQL/Schema/OrderBy.hs @@ -36,7 +36,7 @@ orderByExp => SourceName -> TableInfo b -> SelPermInfo b - -> m (Parser 'Input n [IR.AnnOrderByItemG b (UnpreparedValue b)]) + -> m (Parser 'Input n [IR.AnnotatedOrderByItemG b (UnpreparedValue b)]) orderByExp sourceName tableInfo selectPermissions = memoizeOn 'orderByExp (sourceName, tableInfoName tableInfo) $ do tableGQLName <- getTableGQLName tableInfo let name = tableGQLName <> $$(G.litName "_order_by") @@ -48,18 +48,19 @@ orderByExp sourceName tableInfo selectPermissions = memoizeOn 'orderByExp (sourc where mkField :: FieldInfo b - -> m (Maybe (InputFieldsParser n (Maybe [IR.AnnOrderByItemG b (UnpreparedValue b)]))) + -> m (Maybe (InputFieldsParser n (Maybe [IR.AnnotatedOrderByItemG b (UnpreparedValue b)]))) mkField fieldInfo = runMaybeT $ case fieldInfo of FIColumn columnInfo -> do let fieldName = pgiName columnInfo pure $ P.fieldOptional fieldName Nothing (orderByOperator @b) <&> fmap (pure . mkOrderByItemG @b (IR.AOCColumn columnInfo)) . join + FIRelationship relationshipInfo -> do remoteTableInfo <- askTableInfo @b sourceName $ riRTable relationshipInfo fieldName <- hoistMaybe $ G.mkName $ relNameToTxt $ riName relationshipInfo perms <- MaybeT $ tableSelectPermissions remoteTableInfo - let newPerms = (fmap . fmap) partialSQLExpToUnpreparedValue $ spiFilter perms + let newPerms = fmap partialSQLExpToUnpreparedValue <$> spiFilter perms case riType relationshipInfo of ObjRel -> do otherTableParser <- lift $ orderByExp sourceName remoteTableInfo perms @@ -72,7 +73,33 @@ orderByExp sourceName tableInfo selectPermissions = memoizeOn 'orderByExp (sourc pure $ do aggregationOrderBy <- join <$> P.fieldOptional aggregateFieldName Nothing (P.nullable aggregationParser) pure $ fmap (map $ fmap $ IR.AOCArrayAggregation relationshipInfo newPerms) aggregationOrderBy - FIComputedField _ -> empty + + FIComputedField ComputedFieldInfo{..} -> do + let ComputedFieldFunction{..} = _cfiFunction + mkComputedFieldOrderBy = + let functionArgs = flip IR.FunctionArgsExp mempty + $ IR.functionArgsWithTableRowAndSession P.UVSession _cffTableArgument _cffSessionArgument + in IR.ComputedFieldOrderBy _cfiXComputedFieldInfo _cfiName _cffName functionArgs + fieldName <- hoistMaybe $ G.mkName $ toTxt _cfiName + guard $ _cffInputArgs == mempty -- No input arguments other than table row and session argument + case _cfiReturnType of + CFRScalar scalarType -> do + let computedFieldOrderBy = mkComputedFieldOrderBy $ IR.CFOBEScalar scalarType + pure $ P.fieldOptional fieldName Nothing (orderByOperator @b) + <&> fmap (pure . mkOrderByItemG @b (IR.AOCComputedField computedFieldOrderBy)) . join + CFRSetofTable table -> do + let aggregateFieldName = fieldName <> $$(G.litName "_aggregate") + tableInfo' <- askTableInfo @b sourceName table + perms <- MaybeT $ tableSelectPermissions tableInfo' + let newPerms = fmap partialSQLExpToUnpreparedValue <$> spiFilter perms + aggregationParser <- lift $ orderByAggregation sourceName tableInfo' perms + pure $ do + aggregationOrderBy <- join <$> P.fieldOptional aggregateFieldName Nothing (P.nullable aggregationParser) + pure $ fmap (map $ fmap $ IR.AOCComputedField + . mkComputedFieldOrderBy + . IR.CFOBETableAggregation table newPerms + ) aggregationOrderBy + FIRemoteRelationship _ -> empty @@ -85,7 +112,7 @@ orderByAggregation => SourceName -> TableInfo b -> SelPermInfo b - -> m (Parser 'Input n [IR.OrderByItemG b (IR.AnnAggregateOrderBy b)]) + -> m (Parser 'Input n [IR.OrderByItemG b (IR.AnnotatedAggregateOrderBy b)]) orderByAggregation sourceName tableInfo selectPermissions = memoizeOn 'orderByAggregation (sourceName, tableName) do -- WIP NOTE -- there is heavy duplication between this and Select.tableAggregationFields @@ -125,7 +152,7 @@ orderByAggregation sourceName tableInfo selectPermissions = memoizeOn 'orderByAg :: G.Name -> G.Name -> InputFieldsParser n [(ColumnInfo b, (BasicOrderType b, NullsOrderType b))] - -> InputFieldsParser n (Maybe [IR.OrderByItemG b (IR.AnnAggregateOrderBy b)]) + -> InputFieldsParser n (Maybe [IR.OrderByItemG b (IR.AnnotatedAggregateOrderBy b)]) parseOperator operator tableGQLName columns = let opText = G.unName operator objectName = tableGQLName <> $$(G.litName "_") <> operator <> $$(G.litName "_order_by") diff --git a/server/src-lib/Hasura/GraphQL/Schema/Select.hs b/server/src-lib/Hasura/GraphQL/Schema/Select.hs index 39f1c4d3ea4..39e25d027ae 100644 --- a/server/src-lib/Hasura/GraphQL/Schema/Select.hs +++ b/server/src-lib/Hasura/GraphQL/Schema/Select.hs @@ -625,7 +625,7 @@ tableOrderByArg => SourceName -> TableInfo b -> SelPermInfo b - -> m (InputFieldsParser n (Maybe (NonEmpty (IR.AnnOrderByItemG b (UnpreparedValue b))))) + -> m (InputFieldsParser n (Maybe (NonEmpty (IR.AnnotatedOrderByItemG b (UnpreparedValue b))))) tableOrderByArg sourceName tableInfo selectPermissions = do orderByParser <- orderByExp sourceName tableInfo selectPermissions pure $ do @@ -738,7 +738,7 @@ tableConnectionArgs pkeyColumns sourceName tableInfo selectPermissions = do where base64Text = base64Decode <$> P.string - appendPrimaryKeyOrderBy :: NonEmpty (IR.AnnOrderByItemG b v) -> NonEmpty (IR.AnnOrderByItemG b v) + appendPrimaryKeyOrderBy :: NonEmpty (IR.AnnotatedOrderByItemG b v) -> NonEmpty (IR.AnnotatedOrderByItemG b v) appendPrimaryKeyOrderBy orderBys@(h NE.:| t) = let orderByColumnNames = orderBys ^.. traverse . to IR.obiColumn . IR._AOCColumn . to pgiColumn @@ -748,7 +748,7 @@ tableConnectionArgs pkeyColumns sourceName tableInfo selectPermissions = do in h NE.:| (t <> pkeyOrderBys) parseConnectionSplit - :: Maybe (NonEmpty (IR.AnnOrderByItemG b (UnpreparedValue b))) + :: Maybe (NonEmpty (IR.AnnotatedOrderByItemG b (UnpreparedValue b))) -> IR.ConnectionSplitKind -> BL.ByteString -> n (NonEmpty (IR.ConnectionSplit b (UnpreparedValue b))) @@ -774,11 +774,15 @@ tableConnectionArgs pkeyColumns sourceName tableInfo selectPermissions = do pgValue <- liftQErr $ parseScalarValueColumnType columnType orderByItemValue let unresolvedValue = UVParameter Nothing $ ColumnValue columnType pgValue pure $ IR.ConnectionSplit splitKind unresolvedValue $ - IR.OrderByItemG orderType (() <$ annObCol) nullsOrder + IR.OrderByItemG orderType annObCol nullsOrder where throwInvalidCursor = parseError "the \"after\" or \"before\" cursor is invalid" liftQErr = either (parseError . qeError) pure . runExcept + mkAggregateOrderByPath = \case + IR.AAOCount -> [J.Key "count"] + IR.AAOOp t col -> [J.Key t, J.Key $ toTxt $ pgiColumn col] + getPathFromOrderBy = \case IR.AOCColumn pgColInfo -> let pathElement = J.Key $ toTxt $ pgiColumn pgColInfo @@ -788,15 +792,24 @@ tableConnectionArgs pkeyColumns sourceName tableInfo selectPermissions = do in pathElement : getPathFromOrderBy obCol IR.AOCArrayAggregation relInfo _ aggOb -> let fieldName = J.Key $ relNameToTxt (riName relInfo) <> "_aggregate" - in fieldName : case aggOb of - IR.AAOCount -> [J.Key "count"] - IR.AAOOp t col -> [J.Key t, J.Key $ toTxt $ pgiColumn col] + in fieldName : mkAggregateOrderByPath aggOb + IR.AOCComputedField cfob -> + let fieldNameText = computedFieldNameToText $ IR._cfobName cfob + in case IR._cfobOrderByElement cfob of + IR.CFOBEScalar _ -> [J.Key fieldNameText] + IR.CFOBETableAggregation _ _ aggOb -> + J.Key (fieldNameText <> "_aggregate") : mkAggregateOrderByPath aggOb getOrderByColumnType = \case IR.AOCColumn pgColInfo -> pgiType pgColInfo IR.AOCObjectRelation _ _ obCol -> getOrderByColumnType obCol - IR.AOCArrayAggregation _ _ aggOb -> - case aggOb of + IR.AOCArrayAggregation _ _ aggOb -> aggregateOrderByColumnType aggOb + IR.AOCComputedField cfob -> + case IR._cfobOrderByElement cfob of + IR.CFOBEScalar scalarType -> ColumnScalar scalarType + IR.CFOBETableAggregation _ _ aggOb -> aggregateOrderByColumnType aggOb + where + aggregateOrderByColumnType = \case IR.AAOCount -> ColumnScalar (aggregateOrderByCountType @b) IR.AAOOp _ colInfo -> pgiType colInfo @@ -1311,7 +1324,7 @@ functionArgs functionTrackedAs (toList -> inputArgs) = do tablePermissionsInfo :: Backend b => SelPermInfo b -> TablePerms b tablePermissionsInfo selectPermissions = IR.TablePerm - { IR._tpFilter = (fmap . fmap) partialSQLExpToUnpreparedValue $ spiFilter selectPermissions + { IR._tpFilter = fmap partialSQLExpToUnpreparedValue <$> spiFilter selectPermissions , IR._tpLimit = spiLimit selectPermissions } diff --git a/server/src-lib/Hasura/RQL/DML/Select.hs b/server/src-lib/Hasura/RQL/DML/Select.hs index b5bdd22834f..e754fdcd4cd 100644 --- a/server/src-lib/Hasura/RQL/DML/Select.hs +++ b/server/src-lib/Hasura/RQL/DML/Select.hs @@ -47,7 +47,7 @@ convSelCol -> SelCol -> m [ExtCol ('Postgres 'Vanilla)] convSelCol _ _ (SCExtSimple cn) = - return [ECSimple cn] + pure [ECSimple cn] convSelCol fieldInfoMap _ (SCExtRel rn malias selQ) = do -- Point to the name key let pgWhenRelErr = "only relationships can be expanded" @@ -56,7 +56,7 @@ convSelCol fieldInfoMap _ (SCExtRel rn malias selQ) = do let (RelInfo _ _ _ relTab _ _ _) = relInfo (rfim, rspi) <- fetchRelDet rn relTab resolvedSelQ <- resolveStar rfim rspi selQ - return [ECRel rn malias resolvedSelQ] + pure [ECRel rn malias resolvedSelQ] convSelCol fieldInfoMap spi (SCStar wildcard) = convWildcard fieldInfoMap spi wildcard @@ -68,7 +68,7 @@ convWildcard -> m [ExtCol ('Postgres 'Vanilla)] convWildcard fieldInfoMap selPermInfo wildcard = case wildcard of - Star -> return simpleCols + Star -> pure simpleCols (StarDot wc) -> (simpleCols ++) <$> (catMaybes <$> relExtCols wc) where cols = spiCols selPermInfo @@ -85,7 +85,7 @@ convWildcard fieldInfoMap selPermInfo wildcard = forM mRelSelPerm $ \relSelPermInfo -> do rExtCols <- convWildcard (_tciFieldInfoMap $ _tiCoreInfo relTabInfo) relSelPermInfo wc - return $ ECRel relName Nothing $ + pure $ ECRel relName Nothing $ SelectG rExtCols Nothing Nothing Nothing Nothing relExtCols wc = mapM (mkRelCol wc) relColInfos @@ -99,13 +99,13 @@ resolveStar resolveStar fim selPermInfo (SelectG selCols mWh mOb mLt mOf) = do procOverrides <- fmap (concat . catMaybes) $ withPathK "columns" $ indexedForM selCols $ \selCol -> case selCol of - (SCStar _) -> return Nothing + (SCStar _) -> pure Nothing _ -> Just <$> convSelCol fim selPermInfo selCol everything <- case wildcards of - [] -> return [] + [] -> pure [] _ -> convWildcard fim selPermInfo $ maximum wildcards let extCols = unionBy equals procOverrides everything - return $ SelectG extCols mWh mOb mLt mOf + pure $ SelectG extCols mWh mOb mLt mOf where wildcards = lefts $ map mkEither selCols @@ -121,7 +121,7 @@ convOrderByElem => SessVarBldr ('Postgres 'Vanilla) m -> (FieldInfoMap (FieldInfo ('Postgres 'Vanilla)), SelPermInfo ('Postgres 'Vanilla)) -> OrderByCol - -> m (AnnOrderByElement ('Postgres 'Vanilla) S.SQLExp) + -> m (AnnotatedOrderByElement ('Postgres 'Vanilla) S.SQLExp) convOrderByElem sessVarBldr (flds, spi) = \case OCPG fldName -> do fldInfo <- askFieldInfo flds fldName @@ -134,7 +134,7 @@ convOrderByElem sessVarBldr (flds, spi) = \case [ fldName <<> " has type 'geometry'" , " and cannot be used in order_by" ] - else return $ AOCColumn colInfo + else pure $ AOCColumn colInfo FIRelationship _ -> throw400 UnexpectedPayload $ mconcat [ fldName <<> " is a" , " relationship and should be expanded" @@ -165,8 +165,7 @@ convOrderByElem sessVarBldr (flds, spi) = \case ] (relFim, relSelPermInfo) <- fetchRelDet (riName relInfo) (riRTable relInfo) resolvedSelFltr <- convAnnBoolExpPartialSQL sessVarBldr $ spiFilter relSelPermInfo - AOCObjectRelation relInfo resolvedSelFltr <$> - convOrderByElem sessVarBldr (relFim, relSelPermInfo) rest + AOCObjectRelation relInfo resolvedSelFltr <$> convOrderByElem sessVarBldr (relFim, relSelPermInfo) rest FIRemoteRelationship {} -> throw400 UnexpectedPayload (mconcat [ fldName <<> " is a remote field" ]) @@ -195,11 +194,11 @@ convSelectQ table fieldInfoMap selPermInfo selQ sessVarBldr prepValBldr = do (colInfo, caseBoolExpMaybe) <- convExtSimple fieldInfoMap selPermInfo pgCol resolvedCaseBoolExp <- traverse (convAnnColumnCaseBoolExpPartialSQL sessVarBldr) caseBoolExpMaybe - return (fromCol @('Postgres 'Vanilla) pgCol, mkAnnColumnField colInfo resolvedCaseBoolExp Nothing) + pure (fromCol @('Postgres 'Vanilla) pgCol, mkAnnColumnField colInfo resolvedCaseBoolExp Nothing) (ECRel relName mAlias relSelQ) -> do annRel <- convExtRel fieldInfoMap relName mAlias relSelQ sessVarBldr prepValBldr - return ( fromRel $ fromMaybe relName mAlias + pure ( fromRel $ fromMaybe relName mAlias , either AFObjectRelation AFArrayRelation annRel ) @@ -221,7 +220,7 @@ convSelectQ table fieldInfoMap selPermInfo selQ sessVarBldr prepValBldr = do tabArgs = SelectArgs wClause annOrdByM mQueryLimit (fromIntegral <$> mQueryOffset) Nothing strfyNum <- stringifyNum . _sccSQLGenCtx <$> askServerConfigCtx - return $ AnnSelectG annFlds tabFrom tabPerm tabArgs strfyNum + pure $ AnnSelectG annFlds tabFrom tabPerm tabArgs strfyNum where mQueryOffset = sqOffset selQ @@ -264,10 +263,10 @@ convExtRel fieldInfoMap relName mAlias selQ sessVarBldr prepValBldr = do case relTy of ObjRel -> do when misused $ throw400 UnexpectedPayload objRelMisuseMsg - return $ Left $ AnnRelationSelectG (fromMaybe relName mAlias) colMapping $ + pure $ Left $ AnnRelationSelectG (fromMaybe relName mAlias) colMapping $ AnnObjectSelectG (_asnFields annSel) relTab $ _tpFilter $ _asnPerm annSel ArrRel -> - return $ Right $ ASSimple $ AnnRelationSelectG (fromMaybe relName mAlias) + pure $ Right $ ASSimple $ AnnRelationSelectG (fromMaybe relName mAlias) colMapping annSel where pgWhenRelErr = "only relationships can be expanded" diff --git a/server/src-lib/Hasura/RQL/IR/Select.hs b/server/src-lib/Hasura/RQL/IR/Select.hs index 0836e6b1f76..408677868ae 100644 --- a/server/src-lib/Hasura/RQL/IR/Select.hs +++ b/server/src-lib/Hasura/RQL/IR/Select.hs @@ -96,9 +96,9 @@ data ConnectionSplit (b :: BackendType) v = ConnectionSplit { _csKind :: !ConnectionSplitKind , _csValue :: !v - , _csOrderBy :: !(OrderByItemG b (AnnOrderByElementG b ())) + , _csOrderBy :: !(OrderByItemG b (AnnotatedOrderByElement b v)) } deriving (Functor, Generic, Foldable, Traversable) -instance (Backend b, Hashable (ColumnInfo b), Hashable v) => Hashable (ConnectionSplit b v) +instance (Backend b, Hashable (ColumnInfo b), Hashable v, Hashable (BooleanOperators b v)) => Hashable (ConnectionSplit b v) data ConnectionSlice = SliceFirst !Int @@ -133,7 +133,7 @@ type SelectFrom b = SelectFromG b (SQLExpression b) data SelectArgsG (b :: BackendType) v = SelectArgs { _saWhere :: !(Maybe (AnnBoolExp b v)) - , _saOrderBy :: !(Maybe (NE.NonEmpty (AnnOrderByItemG b v))) + , _saOrderBy :: !(Maybe (NE.NonEmpty (AnnotatedOrderByItemG b v))) , _saLimit :: !(Maybe Int) , _saOffset :: !(Maybe Int64) , _saDistinct :: !(Maybe (NE.NonEmpty (Column b))) @@ -159,24 +159,51 @@ noSelectArgs = SelectArgs Nothing Nothing Nothing Nothing Nothing -- Order by argument -data AnnOrderByElementG (b :: BackendType) v - = AOCColumn !(ColumnInfo b) - | AOCObjectRelation !(RelInfo b) !v !(AnnOrderByElementG b v) - | AOCArrayAggregation !(RelInfo b) !v !(AnnAggregateOrderBy b) +-- | The order by element for a computed field based on its return type +data ComputedFieldOrderByElement (b :: BackendType) v + = CFOBEScalar !(ScalarType b) + -- ^ Sort by the scalar computed field + | CFOBETableAggregation !(TableName b) + !(AnnBoolExp b v) -- ^ Permission filter of the retuning table + !(AnnotatedAggregateOrderBy b) + -- ^ Sort by aggregation fields of table rows returned by computed field deriving (Generic, Functor, Foldable, Traversable) -deriving instance (Backend b, Eq v) => Eq (AnnOrderByElementG b v) -instance (Backend b, Hashable v) => Hashable (AnnOrderByElementG b v) +deriving instance (Backend b, Eq v, Eq (BooleanOperators b v)) => Eq (ComputedFieldOrderByElement b v) +instance (Backend b, Hashable v, Hashable (BooleanOperators b v)) => Hashable (ComputedFieldOrderByElement b v) -data AnnAggregateOrderBy (b :: BackendType) +data ComputedFieldOrderBy (b :: BackendType) v + = ComputedFieldOrderBy + { _cfobXField :: !(XComputedField b) + , _cfobName :: !ComputedFieldName + , _cfobFunction :: !(FunctionName b) + , _cfobFunctionArgsExp :: !(FunctionArgsExpTableRow b v) + , _cfobOrderByElement :: !(ComputedFieldOrderByElement b v) + } deriving (Generic, Functor, Foldable, Traversable) +deriving instance (Backend b, Eq v, Eq (BooleanOperators b v)) => Eq (ComputedFieldOrderBy b v) +instance (Backend b, Hashable v, Hashable (BooleanOperators b v)) => Hashable (ComputedFieldOrderBy b v) + +data AnnotatedOrderByElement (b :: BackendType) v + = AOCColumn !(ColumnInfo b) + | AOCObjectRelation !(RelInfo b) + !(AnnBoolExp b v) -- ^ Permission filter of the remote table to which the relationship is defined + !(AnnotatedOrderByElement b v) + | AOCArrayAggregation !(RelInfo b) + !(AnnBoolExp b v) -- ^ Permission filter of the remote table to which the relationship is defined + !(AnnotatedAggregateOrderBy b) + | AOCComputedField !(ComputedFieldOrderBy b v) + deriving (Generic, Functor, Foldable, Traversable) +deriving instance (Backend b, Eq v, Eq (BooleanOperators b v)) => Eq (AnnotatedOrderByElement b v) +instance (Backend b, Hashable v, Hashable (BooleanOperators b v)) => Hashable (AnnotatedOrderByElement b v) + +data AnnotatedAggregateOrderBy (b :: BackendType) = AAOCount | AAOOp !Text !(ColumnInfo b) deriving (Generic) -deriving instance (Backend b) => Eq (AnnAggregateOrderBy b) -instance (Backend b) => Hashable (AnnAggregateOrderBy b) +deriving instance (Backend b) => Eq (AnnotatedAggregateOrderBy b) +instance (Backend b) => Hashable (AnnotatedAggregateOrderBy b) -type AnnOrderByElement b v = AnnOrderByElementG b (AnnBoolExp b v) -type AnnOrderByItemG b v = OrderByItemG b (AnnOrderByElement b v) -type AnnOrderByItem b = AnnOrderByItemG b (SQLExpression b) +type AnnotatedOrderByItemG b v = OrderByItemG b (AnnotatedOrderByElement b v) +type AnnotatedOrderByItem b = AnnotatedOrderByItemG b (SQLExpression b) -- Fields @@ -466,6 +493,17 @@ type FunctionArgExp b = FunctionArgsExpG (SQLExpression b) emptyFunctionArgsExp :: FunctionArgsExpG a emptyFunctionArgsExp = FunctionArgsExp [] HM.empty +functionArgsWithTableRowAndSession + :: v + -> FunctionTableArgument + -> Maybe FunctionSessionArgument + -> [ArgumentExp b v] +functionArgsWithTableRowAndSession _ _ Nothing = [AETableRow Nothing] -- No session argument +functionArgsWithTableRowAndSession sess (FTAFirst) _ = [AETableRow Nothing, AESession sess] +functionArgsWithTableRowAndSession sess (FTANamed _ 0) _ = [AETableRow Nothing, AESession sess] -- Index is 0 implies table argument is first +functionArgsWithTableRowAndSession sess _ _ = [AESession sess, AETableRow Nothing] + + -- | If argument positional index is less than or equal to length of -- 'positional' arguments then insert the value in 'positional' arguments else -- insert the value with argument name in 'named' arguments @@ -488,4 +526,4 @@ insertFunctionArg argName idx value (FunctionArgsExp positional named) = $(makeLenses ''AnnSelectG) $(makePrisms ''AnnFieldG) -$(makePrisms ''AnnOrderByElementG) +$(makePrisms ''AnnotatedOrderByElement) diff --git a/server/tests-py/queries/graphql_query/order_by/Track_order_by_size.yaml b/server/tests-py/queries/graphql_query/order_by/Track_order_by_size.yaml new file mode 100644 index 00000000000..45ae6957753 --- /dev/null +++ b/server/tests-py/queries/graphql_query/order_by/Track_order_by_size.yaml @@ -0,0 +1,21 @@ +description: Fetch tracks order by their size which is a computed field +url: /v1/graphql +status: 200 +query: + query: | + query { + Track(order_by: {size: desc}, limit: 2){ + name + bytes + size + } + } +response: + data: + Track: + - name: Restless + bytes: 9836284 + size: 9 + - name: Mistress + bytes: 7521946 + size: 7 diff --git a/server/tests-py/queries/graphql_query/order_by/author_order_by_get_articles_aggregate.yaml b/server/tests-py/queries/graphql_query/order_by/author_order_by_get_articles_aggregate.yaml new file mode 100644 index 00000000000..83a771a6edf --- /dev/null +++ b/server/tests-py/queries/graphql_query/order_by/author_order_by_get_articles_aggregate.yaml @@ -0,0 +1,31 @@ +description: Fetch author with order by using computed field aggregate returns set of articles with a search query via session variable +url: /v1/graphql +status: 200 +headers: + X-Hasura-Role: admin + X-Hasura-Search: '1' +query: + query: | + query{ + author(order_by: {get_articles_aggregate: {count: desc}}){ + id + name + get_articles{ + id + title + content + } + } + } +response: + data: + author: + - id: 1 + name: Author 1 + get_articles: + - id: 1 + title: Article 1 + content: Sample article content 1 + - id: 2 + name: Author 2 + get_articles: [] diff --git a/server/tests-py/queries/graphql_query/order_by/setup.yaml b/server/tests-py/queries/graphql_query/order_by/setup.yaml index a58ba049480..ed21a78d2f2 100644 --- a/server/tests-py/queries/graphql_query/order_by/setup.yaml +++ b/server/tests-py/queries/graphql_query/order_by/setup.yaml @@ -11,11 +11,13 @@ args: phone INTEGER, address TEXT ); + create table author( id serial primary key, name text unique, contact_id INTEGER REFERENCES contact(id) ); + CREATE TABLE article ( id SERIAL PRIMARY KEY, title TEXT, @@ -24,14 +26,27 @@ args: is_published BOOLEAN, published_on TIMESTAMP ); + + CREATE FUNCTION get_articles(hasura_session json, author_row author) + RETURNS SETOF article AS $$ + SELECT * + FROM article + WHERE author_id = author_row.id AND + ( title ilike ('%' || (hasura_session ->> 'x-hasura-search') || '%') + OR content ilike ('%' || (hasura_session ->> 'x-hasura-search') || '%') + ) + $$ LANGUAGE SQL STABLE; + insert into contact (phone) values (1234567890), (1234567891); + insert into author (name, contact_id) values ('Author 1', 2), ('Author 2', 1); + insert into article (title,content,author_id,is_published) values ( @@ -56,6 +71,7 @@ args: album_id INTEGER NOT NULL PRIMARY KEY, title TEXT NOT NULL ); + create table "Track" ( track_id INTEGER NOT NULL PRIMARY KEY, name TEXT NOT NULL, @@ -63,8 +79,10 @@ args: milliseconds INTEGER NOT NULL, bytes INTEGER NOT NULL ); + insert into "Album" (album_id, title) values ( 1, 'Big Ones' ), (2, 'Face Lift'); + insert into "Track" (track_id, name, album_id, milliseconds, bytes) values @@ -75,16 +93,24 @@ args: ( 5, 'Random', 2, 1094732, 6032547) , ( 6, 'Good One', 2, 346208, 6732987) , ( 7, 'Mistress', 2, 420985, 7521946); + + CREATE FUNCTION track_size_mb(track_row "Track") + RETURNS INTEGER AS $$ + SELECT (track_row.bytes / 1048576) + $$ LANGUAGE SQL STABLE; + CREATE table "Tag" ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, album_id INTEGER NOT NULL REFERENCES "Album"(album_id) ); + INSERT INTO "Tag" (name, album_id) VALUES ( 'Rock', 1), ( 'Folk', 1), ( 'Hip Hop', 2); + CREATE TABLE employee ( id SERIAL PRIMARY KEY, name TEXT NOT NULL UNIQUE, @@ -109,6 +135,16 @@ args: schema: public name: article +# Computed field to author table +- type: add_computed_field + args: + table: author + name: get_articles + definition: + function: get_articles + table_argument: author_row + session_argument: hasura_session + #Object relationship - type: create_object_relationship args: @@ -161,6 +197,14 @@ args: table: Track column: album_id +# Create computed field for Track table +- type: add_computed_field + args: + table: Track + name: size + definition: + function: track_size_mb + # Insert values in Album and Track - type: track_table args: diff --git a/server/tests-py/queries/graphql_query/order_by/teardown.yaml b/server/tests-py/queries/graphql_query/order_by/teardown.yaml index 7b7f8210ea8..f88fdb4f705 100644 --- a/server/tests-py/queries/graphql_query/order_by/teardown.yaml +++ b/server/tests-py/queries/graphql_query/order_by/teardown.yaml @@ -3,9 +3,11 @@ args: - type: run_sql args: sql: | + DROP FUNCTION get_articles(json, author); DROP TABLE article; DROP TABLE author; DROP TABLE contact; + DROP FUNCTION track_size_mb("Track"); DROP TABLE "Track"; DROP TABLE "Tag"; DROP TABLE "Album"; diff --git a/server/tests-py/test_graphql_queries.py b/server/tests-py/test_graphql_queries.py index fac043be704..0c733640f18 100644 --- a/server/tests-py/test_graphql_queries.py +++ b/server/tests-py/test_graphql_queries.py @@ -894,6 +894,12 @@ class TestGraphQLQueryOrderBy: def test_album_order_by_tracks_tags(self, hge_ctx, transport): check_query_f(hge_ctx, self.dir() + '/album_order_by_tracks_tags.yaml', transport) + def test_Track_order_by_size(self, hge_ctx, transport): + check_query_f(hge_ctx, self.dir() + '/Track_order_by_size.yaml', transport) + + def test_author_order_by_get_articles_aggregate(self, hge_ctx, transport): + check_query_f(hge_ctx, self.dir() + '/author_order_by_get_articles_aggregate.yaml', transport) + @classmethod def dir(cls): return 'queries/graphql_query/order_by'