Skip to content

Commit

Permalink
Order by nested fields
Browse files Browse the repository at this point in the history
PR-URL: hasura/graphql-engine-mono#10082
GitOrigin-RevId: 9c53d7ce3635f442451e04f70f0975ed79a3f4f7
  • Loading branch information
dmoverton authored and hasura-bot committed Aug 14, 2023
1 parent cd324f7 commit 7a9e1f6
Show file tree
Hide file tree
Showing 24 changed files with 91 additions and 84 deletions.
2 changes: 1 addition & 1 deletion dc-agents/dc-api-types/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@hasura/dc-api-types",
"version": "0.36.0",
"version": "0.37.0",
"description": "Hasura GraphQL Engine Data Connector Agent API types",
"author": "Hasura (https://github.com/hasura/graphql-engine)",
"license": "Apache-2.0",
Expand Down
13 changes: 12 additions & 1 deletion dc-agents/dc-api-types/src/agent.openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -2263,7 +2263,18 @@
"OrderByColumn": {
"properties": {
"column": {
"type": "string"
"additionalProperties": true,
"anyOf": [
{
"type": "string"
},
{
"items": {
"type": "string"
},
"type": "array"
}
]
},
"redaction_expression": {
"$ref": "#/components/schemas/RedactionExpressionName"
Expand Down
2 changes: 1 addition & 1 deletion dc-agents/dc-api-types/src/models/OrderByColumn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import type { RedactionExpressionName } from './RedactionExpressionName';

export type OrderByColumn = {
column: string;
column: (string | Array<string>);
redaction_expression?: RedactionExpressionName;
type: 'column';
};
Expand Down
10 changes: 5 additions & 5 deletions dc-agents/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions dc-agents/reference/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dc-agents/reference/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
},
"dependencies": {
"@fastify/cors": "^8.1.0",
"@hasura/dc-api-types": "0.36.0",
"@hasura/dc-api-types": "0.37.0",
"fastify": "^4.13.0",
"mathjs": "^11.0.0",
"pino-pretty": "^8.0.0",
Expand Down
19 changes: 10 additions & 9 deletions dc-agents/reference/src/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,10 @@ const getUnaryComparisonOperatorEvaluator = (operator: UnaryComparisonOperator):
};
};

const getComparisonColumnSelector = (comparisonColumn: ComparisonColumn): string => {
if (typeof comparisonColumn.name === "string")
return comparisonColumn.name;
return comparisonColumn.name[0];
const getColumnSelector = (columnSelector: string | Array<string>): string => {
if (typeof columnSelector === "string")
return columnSelector;
return columnSelector[0];
}

const prettyPrintComparisonColumn = (comparisonColumn: ComparisonColumn): string => {
Expand Down Expand Up @@ -257,9 +257,10 @@ const buildQueryForPathedOrderByElement = (orderByElement: OrderByElement, order
if (relationshipName === undefined) {
switch (orderByElement.target.type) {
case "column":
const columnSelector = getColumnSelector(orderByElement.target.column);
return {
fields: {
[orderByElement.target.column]: { type: "column", column: orderByElement.target.column, column_type: "unknown" } // Unknown column type here is a hack because we don't actually know what the column type is and we don't care
[columnSelector]: { type: "column", column: columnSelector, column_type: "unknown" } // Unknown column type here is a hack because we don't actually know what the column type is and we don't care
}
};
case "single_column_aggregate":
Expand Down Expand Up @@ -309,7 +310,7 @@ const extractResultFromOrderByElementQueryResponse = (orderByElement: OrderByEle
if (rows.length > 1)
throw new Error(`Unexpected number of rows (${rows.length}) returned by order by element query`);

const fieldValue = rows.length === 1 ? rows[0][orderByElement.target.column] : null;
const fieldValue = rows.length === 1 ? rows[0][getColumnSelector(orderByElement.target.column)] : null;
if (fieldValue !== null && typeof fieldValue === "object")
throw new Error("Column order by target path did not end in a column field value");

Expand Down Expand Up @@ -346,7 +347,7 @@ const makeGetOrderByElementValue = (
if (relationshipName === undefined) {
if (orderByElement.target.type !== "column")
throw new Error(`Cannot perform an order by target of type ${orderByElement.target.type} on the current table. Only column-typed targets are supported.`)
return applyRedaction(row, orderByElement.target.redaction_expression, coerceUndefinedToNull(row[orderByElement.target.column]));
return applyRedaction(row, orderByElement.target.redaction_expression, coerceUndefinedToNull(row[getColumnSelector(orderByElement.target.column)]));
} else {
const relationship = findRelationship(relationshipName);
const orderByRelation = orderByRelations[relationshipName];
Expand Down Expand Up @@ -522,12 +523,12 @@ const addRelationshipFilterToQuery = (row: Record<string, RawScalarValue>, relat
const makeGetComparisonColumnValue = (parentQueryRowChain: Record<string, RawScalarValue>[], applyRedaction: ApplyRedaction) => (comparisonColumn: ComparisonColumn, row: Record<string, RawScalarValue>): RawScalarValue => {
const path = comparisonColumn.path ?? [];
if (path.length === 0) {
return applyRedaction(row, comparisonColumn.redaction_expression, coerceUndefinedToNull(row[getComparisonColumnSelector(comparisonColumn)]));
return applyRedaction(row, comparisonColumn.redaction_expression, coerceUndefinedToNull(row[getColumnSelector(comparisonColumn.name)]));
} else if (path.length === 1 && path[0] === "$") {
const queryRow = parentQueryRowChain.length === 0
? row
: parentQueryRowChain[0];
return coerceUndefinedToNull(queryRow[getComparisonColumnSelector(comparisonColumn)]);
return coerceUndefinedToNull(queryRow[getColumnSelector(comparisonColumn.name)]);
} else {
throw new Error(`Unsupported path on ComparisonColumn: ${prettyPrintComparisonColumn(comparisonColumn)}`);
}
Expand Down
4 changes: 2 additions & 2 deletions dc-agents/sqlite/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dc-agents/sqlite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
},
"dependencies": {
"@fastify/cors": "^8.1.0",
"@hasura/dc-api-types": "0.36.0",
"@hasura/dc-api-types": "0.37.0",
"fastify-metrics": "^9.2.1",
"fastify": "^4.13.0",
"nanoid": "^3.3.4",
Expand Down
12 changes: 6 additions & 6 deletions dc-agents/sqlite/src/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ function generateComparisonColumnFragment(comparisonColumn: ComparisonColumn, qu
const path = comparisonColumn.path ?? [];
const queryTablePrefix = queryTableAlias ? `${queryTableAlias}.` : '';
const currentTablePrefix = currentTableAlias ? `${currentTableAlias}.` : '';
const selector = getComparisonColumnSelector(comparisonColumn);
const selector = getColumnSelector(comparisonColumn.name);
if (path.length === 0) {
return `${currentTablePrefix}${escapeIdentifier(selector)}`
} else if (path.length === 1 && path[0] === "$") {
Expand Down Expand Up @@ -610,7 +610,7 @@ function generateOrderByAggregateTargetJoinInfo(

function getOrderByTargetAlias(orderByTarget: OrderByTarget): string {
switch (orderByTarget.type) {
case "column": return escapeIdentifier(orderByTarget.column);
case "column": return escapeIdentifier(getColumnSelector(orderByTarget.column));
case "star_count_aggregate": return escapeIdentifier("__star_count__");
case "single_column_aggregate": return escapeIdentifier(`__${orderByTarget.function}_${orderByTarget.column}__`);
default:
Expand Down Expand Up @@ -883,8 +883,8 @@ type AnalysisEntry = {
detail: string
}

const getComparisonColumnSelector = (comparisonColumn: ComparisonColumn): string => {
if (typeof comparisonColumn.name === "string")
return comparisonColumn.name;
return comparisonColumn.name[0];
const getColumnSelector = (columnSelector: string | Array<string>): string => {
if (typeof columnSelector === "string")
return columnSelector;
return columnSelector[0];
}
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ tests = describe "Order By Tests" $ do
& API.qLimit
?~ 3
& API.qOrderBy
?~ API.OrderBy mempty (API.OrderByElement [] (API.OrderByColumn (API.ColumnName "AlbumId") Nothing) API.Ascending :| [])
?~ API.OrderBy mempty (API.OrderByElement [] (API.OrderByColumn (API.mkColumnSelector $ API.ColumnName "AlbumId") Nothing) API.Ascending :| [])
)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -338,8 +338,8 @@ tests = describe "Object Relationships Tests" $ do
]
)
( NE.fromList
[ API.OrderByElement [API.RelationshipName "Album", API.RelationshipName "Artist"] (API.OrderByColumn (API.ColumnName "Name") Nothing) API.Descending,
API.OrderByElement [] (API.OrderByColumn (API.ColumnName "Name") Nothing) API.Ascending
[ API.OrderByElement [API.RelationshipName "Album", API.RelationshipName "Artist"] (API.OrderByColumn (API.mkColumnSelector $ API.ColumnName "Name") Nothing) API.Descending,
API.OrderByElement [] (API.OrderByColumn (API.mkColumnSelector $ API.ColumnName "Name") Nothing) API.Ascending
]
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import Data.List.NonEmpty (NonEmpty)
import Data.OpenApi (ToSchema)
import GHC.Generics (Generic)
import Hasura.Backends.DataConnector.API.V0.Aggregate qualified as API.V0
import Hasura.Backends.DataConnector.API.V0.Column qualified as API.V0
import Hasura.Backends.DataConnector.API.V0.Expression qualified as API.V0
import Hasura.Backends.DataConnector.API.V0.Relationships qualified as API.V0
import Prelude
Expand Down Expand Up @@ -84,7 +83,7 @@ instance HasCodec OrderByElement where
<*> requiredField "order_direction" "The direction of ordering to apply" .= _obeOrderDirection

data OrderByTarget
= OrderByColumn API.V0.ColumnName (Maybe API.V0.RedactionExpressionName)
= OrderByColumn API.V0.ColumnSelector (Maybe API.V0.RedactionExpressionName)
| OrderByStarCountAggregate
| OrderBySingleColumnAggregate API.V0.SingleColumnAggregate
deriving stock (Eq, Generic, Ord, Show)
Expand Down
2 changes: 1 addition & 1 deletion server/lib/dc-api/test/Test/Data.hs
Original file line number Diff line number Diff line change
Expand Up @@ -797,7 +797,7 @@ scalarValueComparison value valueType = API.ScalarValueComparison $ API.ScalarVa

orderByColumn :: [API.RelationshipName] -> API.ColumnName -> API.OrderDirection -> API.OrderByElement
orderByColumn targetPath columnName orderDirection =
API.OrderByElement targetPath (API.OrderByColumn columnName Nothing) orderDirection
API.OrderByElement targetPath (API.OrderByColumn (API.mkColumnSelector columnName) Nothing) orderDirection

insertAutoIncPk :: Text -> Integer -> [HashMap API.FieldName API.FieldValue] -> [HashMap API.FieldName API.FieldValue]
insertAutoIncPk pkFieldName startingPkId rows =
Expand Down
4 changes: 4 additions & 0 deletions server/src-lib/Hasura/Backends/DataConnector/Plan/Common.hs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ module Hasura.Backends.DataConnector.Plan.Common
mkRelationshipName,
mapFieldNameHashMap,
encodeAssocListAsObject,
ColumnStack,
emptyColumnStack,
pushColumn,
toColumnSelector,
)
where

Expand Down
11 changes: 7 additions & 4 deletions server/src-lib/Hasura/Backends/DataConnector/Plan/QueryPlan.hs
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ translateOrderBy ::
translateOrderBy sourceName orderByItems = do
orderByElementsAndRelations <- for orderByItems \OrderByItemG {..} -> do
let orderDirection = maybe API.Ascending Witch.from obiType
translateOrderByElement sourceName orderDirection [] obiColumn
translateOrderByElement sourceName orderDirection [] emptyColumnStack obiColumn
relations <- mergeOrderByRelations $ snd <$> orderByElementsAndRelations
pure
API.OrderBy
Expand All @@ -262,22 +262,25 @@ translateOrderByElement ::
API.TargetName ->
API.OrderDirection ->
[API.RelationshipName] ->
ColumnStack ->
AnnotatedOrderByElement 'DataConnector (UnpreparedValue 'DataConnector) ->
m (API.OrderByElement, HashMap API.RelationshipName API.OrderByRelation)
translateOrderByElement sourceName orderDirection targetReversePath = \case
translateOrderByElement sourceName orderDirection targetReversePath columnStack = \case
AOCColumn ColumnInfo {..} redactionExp -> do
redactionExpName <- recordRedactionExpression sourceName redactionExp
pure
( API.OrderByElement
{ _obeTargetPath = reverse targetReversePath,
_obeTarget = API.OrderByColumn (Witch.from ciColumn) redactionExpName,
_obeTarget = API.OrderByColumn (toColumnSelector columnStack ciColumn) redactionExpName,
_obeOrderDirection = orderDirection
},
mempty
)
AOCNestedObject NestedObjectInfo {..} nestedOrderBy ->
translateOrderByElement sourceName orderDirection targetReversePath (pushColumn columnStack _noiColumn) nestedOrderBy
AOCObjectRelation relationshipInfo filterExp orderByElement -> do
(relationshipName, API.Relationship {..}) <- recordTableRelationshipFromRelInfo sourceName relationshipInfo
(translatedOrderByElement, subOrderByRelations) <- translateOrderByElement (API.TNTable _rTargetTable) orderDirection (relationshipName : targetReversePath) orderByElement
(translatedOrderByElement, subOrderByRelations) <- translateOrderByElement (API.TNTable _rTargetTable) orderDirection (relationshipName : targetReversePath) columnStack orderByElement

targetTableWhereExp <- translateBoolExpToExpression (API.TNTable _rTargetTable) filterExp
let orderByRelations = HashMap.fromList [(relationshipName, API.OrderByRelation targetTableWhereExp subOrderByRelations)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ processOrderByItems sourcePrefix' selectSourceQual fieldAlias' similarArrayField
S.applyJsonBuildObj [S.SELit $ getPGColTxt $ ciColumn colInfo, valExp]
]

annObColToJSONField :: S.SQLExp -> AnnotatedOrderByElement ('Postgres pgKind) v -> [S.SQLExp]
annObColToJSONField valExp = \case
AOCColumn pgCol _redactionExp -> [S.SELit $ getPGColTxt $ ciColumn pgCol, valExp]
AOCObjectRelation relInfo _ obCol ->
Expand Down
32 changes: 19 additions & 13 deletions server/src-lib/Hasura/GraphQL/Schema/OrderBy.hs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import Hasura.GraphQL.Schema.Parser qualified as P
import Hasura.GraphQL.Schema.Table
import Hasura.GraphQL.Schema.Typename
import Hasura.LogicalModel.Cache (LogicalModelInfo (..))
import Hasura.LogicalModel.Common (columnsFromFields, getSelPermInfoForLogicalModel, toFieldInfo)
import Hasura.LogicalModel.Common (getSelPermInfoForLogicalModel, logicalModelFieldsToFieldInfo)
import Hasura.LogicalModel.Types (LogicalModelName (..))
import Hasura.Name qualified as Name
import Hasura.Prelude
Expand Down Expand Up @@ -81,16 +81,14 @@ logicalModelOrderByExp logicalModel = do
roleName <- retrieve scRole
let name = getLogicalModelName (_lmiName logicalModel)
selectPermissions = getSelPermInfoForLogicalModel roleName logicalModel
case toFieldInfo (columnsFromFields $ _lmiFields logicalModel) of
Nothing -> throw500 $ "Error creating fields for logical model " <> tshow name
Just tableFields -> do
let description =
G.Description
$ "Ordering options when selecting data from "
<> name
<<> "."
memoizeKey = name
orderByExpInternal (C.fromCustomName name) description selectPermissions tableFields memoizeKey
fieldInfos = HashMap.elems $ logicalModelFieldsToFieldInfo $ _lmiFields logicalModel
description =
G.Description
$ "Ordering options when selecting data from "
<> name
<<> "."
memoizeKey = name
orderByExpInternal (C.fromCustomName name) description selectPermissions fieldInfos memoizeKey

-- | Corresponds to an object type for an order by.
--
Expand Down Expand Up @@ -137,8 +135,16 @@ orderByExpInternal gqlName description selectPermissions tableFields memoizeKey
& P.fieldOptional fieldName Nothing
<&> (fmap (pure . mkOrderByItemG @b (IR.AOCColumn columnInfo redactionExp)) . join)
& pure
FIColumn (SCIObjectColumn _) -> empty -- TODO(dmoverton)
FIColumn (SCIArrayColumn _) -> empty -- TODO(dmoverton)
FIColumn (SCIObjectColumn nestedObjectInfo@NestedObjectInfo {..}) -> do
let !fieldName = _noiName
logicalModelInfo <-
HashMap.lookup _noiType (_siLogicalModels sourceInfo)
`onNothing` throw500 ("Logical model " <> _noiType <<> " not found in source " <>> (_siName sourceInfo))
nestedParser <- lift $ logicalModelOrderByExp @b @r @m @n logicalModelInfo
pure $ do
nestedOrderBy <- P.fieldOptional fieldName Nothing nestedParser
pure $ fmap (map $ fmap $ IR.AOCNestedObject nestedObjectInfo) nestedOrderBy
FIColumn (SCIArrayColumn _) -> empty
FIRelationship relationshipInfo -> do
case riTarget relationshipInfo of
RelTargetNativeQuery _ -> error "mkField RelTargetNativeQuery"
Expand Down
Loading

0 comments on commit 7a9e1f6

Please sign in to comment.