Fix "limit" from permissions being incorrectly applied to aggregates in Data Connectors

[GDC-1064]: https://hasurahq.atlassian.net/browse/GDC-1064?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/8345
GitOrigin-RevId: 9ef91944ba6408e1030a47de58f3271145433ee2
This commit is contained in:
Daniel Chambers 2023-03-20 15:00:34 +11:00 committed by hasura-bot
parent 6e1ebd6a4b
commit e9c697aaa9
30 changed files with 1415 additions and 1341 deletions

View File

@ -473,7 +473,125 @@ The rows returned by the query must be put into the `rows` property array in the
#### Pagination
If the GraphQL query contains pagination information, then the `limit` and `offset` fields may be set to integer values, indicating the number of rows to return, and the index of the first row to return, respectively.
There are three properties that are used to control pagination of queried data:
* `aggregates_limit`: The maximum number of rows to consider in aggregations calculated and returned in the `aggregrates` property. `aggregates_limit` does not influence the rows returned in the `rows` property. It will only be used if there are aggregates in the query.
* `limit`: The maximum number of rows to return from a query in the `rows` property. `limit` does not influence the rows considered by aggregations.
* `offset`: The index of the first row to return. This affects the rows returned, and also the rows considered by aggregations.
`limit` and `aggregates_limit` are set when the user uses a `limit` parameter in their GraphQL query. This restricts the dataset considered when returning rows as well as calculating aggregates.
HGE also has a row limit setting in a table's select permissions. This row limit will be used in the `limit` property if it is specified or if it is smaller than the limit specified in the GraphQL query itself.
To illustrate the difference between `limit` and `aggregates_limit`, consider this GraphQL query, where a row limit of `2` has been placed on the Artist table in its select permissions.
```graphql
query ArtistsQuery {
Artist_aggregate {
aggregate {
count
}
nodes {
Name
}
}
}
```
This would produce the following agent query request JSON:
```json
{
"table": ["Artist"],
"table_relationships": [],
"query": {
"aggregates_limit": null,
"limit": 2,
"offset": null,
"aggregates": {
"aggregate_count": {
"type": "star_count"
}
},
"fields": {
"nodes_Name": {
"type": "column",
"column": "Name",
"column_type": "string"
}
}
}
}
```
The expected response to that query request would be the following JSON. Note that the row count has counted all rows (since `aggregates_limit` was null), but query has only returned the maximum number of rows as specified by the `limit` property: `2`.
```json
{
"aggregates": {
"aggregate_count": 275
},
"rows":[
{ "nodes_Name": "AC/DC" },
{ "nodes_Name": "Accept" }
]
}
```
By comparison, if we added a limit to our GraphQL query:
```graphql
query ArtistsQuery {
Artist_aggregate(limit: 5) {
aggregate {
count
}
nodes {
Name
}
}
}
```
This would produce the following agent query request JSON:
```json
{
"table": ["Artist"],
"table_relationships": [],
"query": {
"aggregates_limit": 5,
"limit": 2,
"offset": null,
"aggregates": {
"aggregate_count": {
"type": "star_count"
}
},
"fields": {
"nodes_Name": {
"type": "column",
"column": "Name",
"column_type": "string"
}
}
}
}
```
We would expect the following result:
```json
{
"aggregates": {
"aggregate_count": 5
},
"rows":[
{ "nodes_Name": "AC/DC" },
{ "nodes_Name": "Accept" }
]
}
```
Note that the row count aggregation has been limited to `5` because `aggregates_limit` was `5`, and the rows returned were limited by the value of `limit`: `2`.
#### Filters

View File

@ -1,6 +1,6 @@
{
"name": "@hasura/dc-api-types",
"version": "0.26.0",
"version": "0.27.0",
"description": "Hasura GraphQL Engine Data Connector Agent API types",
"author": "Hasura (https://github.com/hasura/graphql-engine)",
"license": "Apache-2.0",

View File

@ -1305,6 +1305,13 @@
"nullable": true,
"type": "object"
},
"aggregates_limit": {
"description": "Optionally limit the maximum number of rows considered while applying aggregations. This limit does not apply to returned rows.",
"maximum": 9223372036854776000,
"minimum": -9223372036854776000,
"nullable": true,
"type": "number"
},
"fields": {
"additionalProperties": {
"$ref": "#/components/schemas/Field"
@ -1314,14 +1321,14 @@
"type": "object"
},
"limit": {
"description": "Optionally limit to N results",
"description": "Optionally limit the maximum number of returned rows. This limit does not apply to records considered while apply aggregations.",
"maximum": 9223372036854776000,
"minimum": -9223372036854776000,
"nullable": true,
"type": "number"
},
"offset": {
"description": "Optionally offset from the Nth result",
"description": "Optionally offset from the Nth result. This applies to both row and aggregation results.",
"maximum": 9223372036854776000,
"minimum": -9223372036854776000,
"nullable": true,

View File

@ -12,16 +12,20 @@ export type Query = {
* Aggregate fields of the query
*/
aggregates?: Record<string, Aggregate> | null;
/**
* Optionally limit the maximum number of rows considered while applying aggregations. This limit does not apply to returned rows.
*/
aggregates_limit?: number | null;
/**
* Fields of the query
*/
fields?: Record<string, Field> | null;
/**
* Optionally limit to N results
* Optionally limit the maximum number of returned rows. This limit does not apply to records considered while apply aggregations.
*/
limit?: number | null;
/**
* Optionally offset from the Nth result
* Optionally offset from the Nth result. This applies to both row and aggregation results.
*/
offset?: number | null;
order_by?: OrderBy;

View File

@ -24,7 +24,7 @@
},
"dc-api-types": {
"name": "@hasura/dc-api-types",
"version": "0.26.0",
"version": "0.27.0",
"license": "Apache-2.0",
"devDependencies": {
"@tsconfig/node16": "^1.0.3",
@ -2227,7 +2227,7 @@
"license": "Apache-2.0",
"dependencies": {
"@fastify/cors": "^8.1.0",
"@hasura/dc-api-types": "0.26.0",
"@hasura/dc-api-types": "0.27.0",
"fastify": "^4.13.0",
"mathjs": "^11.0.0",
"pino-pretty": "^8.0.0",
@ -2547,7 +2547,7 @@
"license": "Apache-2.0",
"dependencies": {
"@fastify/cors": "^8.1.0",
"@hasura/dc-api-types": "0.26.0",
"@hasura/dc-api-types": "0.27.0",
"fastify": "^4.13.0",
"fastify-metrics": "^9.2.1",
"nanoid": "^3.3.4",
@ -2868,7 +2868,7 @@
"version": "file:reference",
"requires": {
"@fastify/cors": "^8.1.0",
"@hasura/dc-api-types": "0.26.0",
"@hasura/dc-api-types": "0.27.0",
"@tsconfig/node16": "^1.0.3",
"@types/node": "^16.11.49",
"@types/xml2js": "^0.4.11",
@ -3080,7 +3080,7 @@
"version": "file:sqlite",
"requires": {
"@fastify/cors": "^8.1.0",
"@hasura/dc-api-types": "0.26.0",
"@hasura/dc-api-types": "0.27.0",
"@tsconfig/node16": "^1.0.3",
"@types/node": "^16.11.49",
"@types/sqlite3": "^3.1.8",

View File

@ -10,7 +10,7 @@
"license": "Apache-2.0",
"dependencies": {
"@fastify/cors": "^8.1.0",
"@hasura/dc-api-types": "0.26.0",
"@hasura/dc-api-types": "0.27.0",
"fastify": "^4.13.0",
"mathjs": "^11.0.0",
"pino-pretty": "^8.0.0",
@ -52,7 +52,7 @@
"integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ=="
},
"node_modules/@hasura/dc-api-types": {
"version": "0.26.0",
"version": "0.27.0",
"license": "Apache-2.0",
"devDependencies": {
"@tsconfig/node16": "^1.0.3",

View File

@ -22,7 +22,7 @@
},
"dependencies": {
"@fastify/cors": "^8.1.0",
"@hasura/dc-api-types": "0.26.0",
"@hasura/dc-api-types": "0.27.0",
"fastify": "^4.13.0",
"mathjs": "^11.0.0",
"pino-pretty": "^8.0.0",

View File

@ -578,12 +578,22 @@ export const queryData = (getTable: (tableName: TableName) => Record<string, Raw
const filteredRows = filterIterable(rows, makeFilterPredicate(query.where ?? null, getComparisonColumnValue, performExistsSubquery));
const sortedRows = query.order_by ? sortRows(Array.from(filteredRows), query.order_by, getOrderByElementValue) : filteredRows;
const paginatedRows = Array.from(paginateRows(sortedRows, query.offset ?? null, query.limit ?? null));
// Get the smallest set of rows required _for both_ row results and aggregation result
const aggregatesLimit = query.aggregates_limit ?? null;
const rowLimit = query.limit ?? null;
const largestLimit = aggregatesLimit !== null && rowLimit !== null ? Math.max(aggregatesLimit, rowLimit) : null;
const largestPageOfRows = Array.from(paginateRows(sortedRows, query.offset ?? null, largestLimit));
// Limit the set of input rows to appropriate size for row results and aggregation results
const paginatedRows = rowLimit != null ? largestPageOfRows.slice(0, rowLimit) : largestPageOfRows;
const paginatedRowsForAggregation = aggregatesLimit != null ? largestPageOfRows.slice(0, aggregatesLimit) : largestPageOfRows;
const projectedRows = query.fields
? paginatedRows.map(projectRow(query.fields, findRelationship, performNewQuery))
: null;
const calculatedAggregates = query.aggregates
? calculateAggregates(paginatedRows, query.aggregates)
? calculateAggregates(paginatedRowsForAggregation, query.aggregates)
: null;
return {
aggregates: calculatedAggregates,

View File

@ -10,7 +10,7 @@
"license": "Apache-2.0",
"dependencies": {
"@fastify/cors": "^8.1.0",
"@hasura/dc-api-types": "0.26.0",
"@hasura/dc-api-types": "0.27.0",
"fastify": "^4.13.0",
"fastify-metrics": "^9.2.1",
"nanoid": "^3.3.4",
@ -57,7 +57,7 @@
"integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ=="
},
"node_modules/@hasura/dc-api-types": {
"version": "0.26.0",
"version": "0.27.0",
"license": "Apache-2.0",
"devDependencies": {
"@tsconfig/node16": "^1.0.3",

View File

@ -22,7 +22,7 @@
},
"dependencies": {
"@fastify/cors": "^8.1.0",
"@hasura/dc-api-types": "0.26.0",
"@hasura/dc-api-types": "0.27.0",
"fastify-metrics": "^9.2.1",
"fastify": "^4.13.0",
"nanoid": "^3.3.4",

View File

@ -23,9 +23,6 @@ import {
OrderByRelation,
OrderByElement,
OrderByTarget,
Query,
ColumnFieldValue,
NullColumnFieldValue,
ScalarValue,
} from "@hasura/dc-api-types";
import { customAlphabet } from "nanoid";
@ -335,12 +332,13 @@ function table_query(
fields: Fields | null,
aggregates: Aggregates | null,
wWhere: Expression | null,
aggregatesLimit: number | null,
wLimit: number | null,
wOffset: number | null,
wOrder: OrderBy | null,
): string {
const tableAlias = generateTableAlias(tableName);
const aggregateSelect = aggregates ? [aggregates_query(ts, tableName, joinInfo, aggregates, wWhere, wLimit, wOffset, wOrder)] : [];
const aggregateSelect = aggregates ? [aggregates_query(ts, tableName, joinInfo, aggregates, wWhere, aggregatesLimit, wOffset, wOrder)] : [];
// The use of the JSON function inside JSON_GROUP_ARRAY is necessary from SQLite 3.39.0 due to breaking changes in
// SQLite. See https://sqlite.org/forum/forumpost/e3b101fb3234272b for more details. This approach still works fine
// for older versions too.
@ -374,10 +372,10 @@ function relationship(ts: TableRelationships[], r: Relationship, field: Relation
// We force a limit of 1 for object relationships in case the user has configured a manual
// "object" relationship that accidentally actually is an array relationship
const limit =
const [limit, aggregatesLimit] =
r.relationship_type === "object"
? 1
: coerceUndefinedToNull(field.query.limit);
? [1, 1]
: [coerceUndefinedToNull(field.query.limit), coerceUndefinedToNull(field.query.aggregates_limit)];
return tag("relationship", table_query(
ts,
@ -386,6 +384,7 @@ function relationship(ts: TableRelationships[], r: Relationship, field: Relation
coerceUndefinedToNull(field.query.fields),
coerceUndefinedToNull(field.query.aggregates),
coerceUndefinedToNull(field.query.where),
aggregatesLimit,
limit,
coerceUndefinedToNull(field.query.offset),
coerceUndefinedToNull(field.query.order_by),
@ -657,6 +656,7 @@ function query(request: QueryRequest): string {
coerceUndefinedToNull(request.query.fields),
coerceUndefinedToNull(request.query.aggregates),
coerceUndefinedToNull(request.query.where),
coerceUndefinedToNull(request.query.aggregates_limit),
coerceUndefinedToNull(request.query.limit),
coerceUndefinedToNull(request.query.offset),
coerceUndefinedToNull(request.query.order_by),
@ -740,6 +740,7 @@ function foreach_query(foreachIds: Record<string, ScalarValue>[], request: Query
null,
null,
null,
null,
);
return tag('foreach_query', `WITH ${escapeTableName(foreachTableName)} AS (${foreachIdsTableValue}) SELECT ${tableSubquery} AS data`);
}

View File

@ -119,6 +119,7 @@ library
Test.DataConnector.MockAgent.OrderBySpec
Test.DataConnector.MockAgent.QueryRelationshipsSpec
Test.DataConnector.MockAgent.RemoteRelationshipsSpec
Test.DataConnector.MockAgent.TestHelpers
Test.DataConnector.MockAgent.TransformedConfigurationSpec
Test.DataConnector.MockAgent.UpdateMutationsSpec
Test.DataConnector.QuerySpec

View File

@ -4,6 +4,7 @@ module Test.DataConnector.MockAgent.AggregateQuerySpec (spec) where
--------------------------------------------------------------------------------
import Control.Lens ((.~), (?~))
import Data.Aeson qualified as Aeson
import Data.HashMap.Strict qualified as HashMap
import Data.List.NonEmpty qualified as NE
@ -18,6 +19,7 @@ import Harness.Yaml (shouldBeYaml)
import Hasura.Backends.DataConnector.API qualified as API
import Hasura.Prelude
import Language.GraphQL.Draft.Syntax.QQ qualified as G
import Test.DataConnector.MockAgent.TestHelpers
import Test.Hspec (SpecWith, describe, shouldBe)
--------------------------------------------------------------------------------
@ -108,14 +110,14 @@ tests _opts = describe "Aggregate Query Tests" $ do
}
|]
let queryResponse =
rowsResponse
[ [ (API.FieldName "ArtistIds_Id", API.mkColumnFieldValue $ Aeson.Number 1),
(API.FieldName "ArtistNames_Name", API.mkColumnFieldValue $ Aeson.String "AC/DC"),
( API.FieldName "nodes_Albums",
mkRowsQueryResponse
[ [ ("ArtistIds_Id", API.mkColumnFieldValue $ Aeson.Number 1),
("ArtistNames_Name", API.mkColumnFieldValue $ Aeson.String "AC/DC"),
( "nodes_Albums",
API.mkRelationshipFieldValue $
rowsResponse
[ [(API.FieldName "nodes_Title", API.mkColumnFieldValue $ Aeson.String "For Those About To Rock We Salute You")],
[(API.FieldName "nodes_Title", API.mkColumnFieldValue $ Aeson.String "Let There Be Rock")]
mkRowsQueryResponse
[ [("nodes_Title", API.mkColumnFieldValue $ Aeson.String "For Those About To Rock We Salute You")],
[("nodes_Title", API.mkColumnFieldValue $ Aeson.String "Let There Be Rock")]
]
)
]
@ -142,57 +144,40 @@ tests _opts = describe "Aggregate Query Tests" $ do
_mrrRecordedRequest
`shouldBe` Just
( Query $
API.QueryRequest
{ _qrTable = API.TableName ("Artist" :| []),
_qrTableRelationships =
[ API.TableRelationships
{ _trSourceTable = API.TableName ("Artist" :| []),
_trRelationships =
HashMap.fromList
[ ( API.RelationshipName "Albums",
API.Relationship
{ _rTargetTable = API.TableName ("Album" :| []),
_rRelationshipType = API.ArrayRelationship,
_rColumnMapping = HashMap.fromList [(API.ColumnName "ArtistId", API.ColumnName "ArtistId")]
}
)
]
}
],
_qrQuery =
API.Query
{ _qFields =
Just $
HashMap.fromList
[ (API.FieldName "ArtistIds_Id", API.ColumnField (API.ColumnName "ArtistId") (API.ScalarType "number")),
(API.FieldName "ArtistNames_Name", API.ColumnField (API.ColumnName "Name") (API.ScalarType "string")),
( API.FieldName "nodes_Albums",
API.RelField
( API.RelationshipField
(API.RelationshipName "Albums")
API.Query
{ _qFields =
Just $
HashMap.fromList
[ (API.FieldName "nodes_Title", API.ColumnField (API.ColumnName "Title") (API.ScalarType "string"))
],
_qAggregates = Nothing,
_qLimit = Nothing,
_qOffset = Nothing,
_qWhere = Nothing,
_qOrderBy = Nothing
}
)
)
],
_qAggregates = Nothing,
_qLimit = Just 1,
_qOffset = Nothing,
_qWhere = Nothing,
_qOrderBy = Nothing
},
_qrForeach = Nothing
}
mkQueryRequest
(mkTableName "Artist")
( emptyQuery
& API.qFields
?~ mkFieldsMap
[ ("ArtistIds_Id", API.ColumnField (API.ColumnName "ArtistId") (API.ScalarType "number")),
("ArtistNames_Name", API.ColumnField (API.ColumnName "Name") (API.ScalarType "string")),
( "nodes_Albums",
API.RelField
( API.RelationshipField
(API.RelationshipName "Albums")
( emptyQuery
& API.qFields ?~ mkFieldsMap [("nodes_Title", API.ColumnField (API.ColumnName "Title") (API.ScalarType "string"))]
)
)
)
]
& API.qLimit ?~ 1
)
& API.qrTableRelationships
.~ [ API.TableRelationships
{ _trSourceTable = mkTableName "Artist",
_trRelationships =
HashMap.fromList
[ ( API.RelationshipName "Albums",
API.Relationship
{ _rTargetTable = mkTableName "Album",
_rRelationshipType = API.ArrayRelationship,
_rColumnMapping = HashMap.fromList [(API.ColumnName "ArtistId", API.ColumnName "ArtistId")]
}
)
]
}
]
)
mockAgentGraphqlTest "works with multiple aggregate fields and through array relations" $ \_testEnv performGraphqlRequest -> do
@ -224,28 +209,28 @@ tests _opts = describe "Aggregate Query Tests" $ do
}
|]
let aggregates =
[ (API.FieldName "counts_count", Aeson.Number 2),
(API.FieldName "counts_uniqueBillingCountries", Aeson.Number 2),
(API.FieldName "ids_minimum_Id", Aeson.Number 1),
(API.FieldName "ids_max_InvoiceId", Aeson.Number 2)
[ ("counts_count", Aeson.Number 2),
("counts_uniqueBillingCountries", Aeson.Number 2),
("ids_minimum_Id", Aeson.Number 1),
("ids_max_InvoiceId", Aeson.Number 2)
]
rows =
[ [ ( API.FieldName "nodes_Lines",
[ [ ( "nodes_Lines",
API.mkRelationshipFieldValue $
aggregatesResponse
[ (API.FieldName "aggregate_count", Aeson.Number 2)
mkAggregatesQueryResponse
[ ("aggregate_count", Aeson.Number 2)
]
)
],
[ ( API.FieldName "nodes_Lines",
[ ( "nodes_Lines",
API.mkRelationshipFieldValue $
aggregatesResponse
[ (API.FieldName "aggregate_count", Aeson.Number 4)
mkAggregatesQueryResponse
[ ("aggregate_count", Aeson.Number 4)
]
)
]
]
let mockConfig = Mock.chinookMock & mockQueryResponse (aggregatesAndRowsResponse aggregates rows)
let mockConfig = Mock.chinookMock & mockQueryResponse (mkQueryResponse rows aggregates)
MockRequestResults {..} <- performGraphqlRequest mockConfig headers graphqlRequest
@ -273,72 +258,47 @@ tests _opts = describe "Aggregate Query Tests" $ do
_mrrRecordedRequest
`shouldBe` Just
( Query $
API.QueryRequest
{ _qrTable = API.TableName ("Invoice" :| []),
_qrTableRelationships =
[ API.TableRelationships
{ _trSourceTable = API.TableName ("Invoice" :| []),
_trRelationships =
HashMap.fromList
[ ( API.RelationshipName "InvoiceLines",
API.Relationship
{ _rTargetTable = API.TableName ("InvoiceLine" :| []),
_rRelationshipType = API.ArrayRelationship,
_rColumnMapping = HashMap.fromList [(API.ColumnName "InvoiceId", API.ColumnName "InvoiceId")]
}
)
]
}
],
_qrQuery =
API.Query
{ _qFields =
Just $
HashMap.fromList
[ ( API.FieldName "nodes_Lines",
API.RelField
( API.RelationshipField
(API.RelationshipName "InvoiceLines")
API.Query
{ _qFields = Nothing,
_qAggregates =
Just $
HashMap.fromList
[(API.FieldName "aggregate_count", API.StarCount)],
_qLimit = Nothing,
_qOffset = Nothing,
_qWhere = Nothing,
_qOrderBy = Nothing
}
)
)
],
_qAggregates =
Just $
HashMap.fromList
[ (API.FieldName "counts_count", API.StarCount),
(API.FieldName "counts_uniqueBillingCountries", API.ColumnCount (API.ColumnCountAggregate (API.ColumnName "BillingCountry") True)),
(API.FieldName "ids_minimum_Id", API.SingleColumn (singleColumnAggregateMin (API.ColumnName "InvoiceId") (API.ScalarType "number"))),
(API.FieldName "ids_max_InvoiceId", API.SingleColumn (singleColumnAggregateMax (API.ColumnName "InvoiceId") (API.ScalarType "number")))
],
_qLimit = Just 2,
_qOffset = Nothing,
_qWhere = Nothing,
_qOrderBy = Nothing
},
_qrForeach = Nothing
}
mkQueryRequest
(mkTableName "Invoice")
( emptyQuery
& API.qFields
?~ mkFieldsMap
[ ( "nodes_Lines",
API.RelField
( API.RelationshipField
(API.RelationshipName "InvoiceLines")
( emptyQuery & API.qAggregates ?~ mkFieldsMap [("aggregate_count", API.StarCount)]
)
)
)
]
& API.qAggregates
?~ mkFieldsMap
[ ("counts_count", API.StarCount),
("counts_uniqueBillingCountries", API.ColumnCount (API.ColumnCountAggregate (API.ColumnName "BillingCountry") True)),
("ids_minimum_Id", API.SingleColumn (singleColumnAggregateMin (API.ColumnName "InvoiceId") (API.ScalarType "number"))),
("ids_max_InvoiceId", API.SingleColumn (singleColumnAggregateMax (API.ColumnName "InvoiceId") (API.ScalarType "number")))
]
& API.qLimit ?~ 2
& API.qAggregatesLimit ?~ 2
)
& API.qrTableRelationships
.~ [ API.TableRelationships
{ _trSourceTable = mkTableName "Invoice",
_trRelationships =
HashMap.fromList
[ ( API.RelationshipName "InvoiceLines",
API.Relationship
{ _rTargetTable = mkTableName "InvoiceLine",
_rRelationshipType = API.ArrayRelationship,
_rColumnMapping = HashMap.fromList [(API.ColumnName "InvoiceId", API.ColumnName "InvoiceId")]
}
)
]
}
]
)
rowsResponse :: [[(API.FieldName, API.FieldValue)]] -> API.QueryResponse
rowsResponse rows = API.QueryResponse (Just $ HashMap.fromList <$> rows) Nothing
aggregatesResponse :: [(API.FieldName, Aeson.Value)] -> API.QueryResponse
aggregatesResponse aggregates = API.QueryResponse Nothing (Just $ HashMap.fromList aggregates)
aggregatesAndRowsResponse :: [(API.FieldName, Aeson.Value)] -> [[(API.FieldName, API.FieldValue)]] -> API.QueryResponse
aggregatesAndRowsResponse aggregates rows = API.QueryResponse (Just $ HashMap.fromList <$> rows) (Just $ HashMap.fromList aggregates)
singleColumnAggregateMax :: API.ColumnName -> API.ScalarType -> API.SingleColumnAggregate
singleColumnAggregateMax = API.SingleColumnAggregate $ API.SingleColumnAggregateFunction [G.name|max|]

View File

@ -6,9 +6,9 @@ module Test.DataConnector.MockAgent.BasicQuerySpec (spec) where
--------------------------------------------------------------------------------
import Control.Lens ((?~))
import Data.Aeson qualified as Aeson
import Data.ByteString (ByteString)
import Data.HashMap.Strict qualified as HashMap
import Data.List.NonEmpty qualified as NE
import Harness.Backend.DataConnector.Mock (AgentRequest (..), MockRequestResults (..), mockAgentGraphqlTest, mockQueryResponse)
import Harness.Backend.DataConnector.Mock qualified as Mock
@ -20,6 +20,7 @@ import Harness.TestEnvironment (GlobalTestEnvironment, TestEnvironment)
import Harness.Yaml (shouldBeYaml)
import Hasura.Backends.DataConnector.API qualified as API
import Hasura.Prelude
import Test.DataConnector.MockAgent.TestHelpers
import Test.Hspec (SpecWith, describe, shouldBe)
--------------------------------------------------------------------------------
@ -73,19 +74,19 @@ sourceMetadata =
configuration:
custom_root_fields:
select: artists
select_by_pk: artists_by_pk
column_config:
ArtistId:
custom_name: id
Name:
custom_name: name
array_relationships:
- name: albums
using:
manual_configuration:
remote_table: [Album]
column_mapping:
ArtistId: ArtistId
select_permissions:
- role: *testRoleName
permission:
columns:
- ArtistId
- Name
limit: 3
filter: {}
- table: [Employee]
- table: [Customer]
select_permissions:
@ -118,9 +119,9 @@ tests _opts = describe "Basic Tests" $ do
}
|]
let queryResponse =
rowsResponse
[ [ (API.FieldName "id", API.mkColumnFieldValue $ Aeson.Number 1),
(API.FieldName "title", API.mkColumnFieldValue $ Aeson.String "For Those About To Rock We Salute You")
mkRowsQueryResponse
[ [ ("id", API.mkColumnFieldValue $ Aeson.Number 1),
("title", API.mkColumnFieldValue $ Aeson.String "For Those About To Rock We Salute You")
]
]
let mockConfig = Mock.chinookMock & mockQueryResponse queryResponse
@ -138,25 +139,70 @@ tests _opts = describe "Basic Tests" $ do
_mrrRecordedRequest
`shouldBe` Just
( Query $
API.QueryRequest
{ _qrTable = API.TableName ("Album" :| []),
_qrTableRelationships = [],
_qrQuery =
API.Query
{ _qFields =
Just $
HashMap.fromList
[ (API.FieldName "id", API.ColumnField (API.ColumnName "AlbumId") (API.ScalarType "number")),
(API.FieldName "title", API.ColumnField (API.ColumnName "Title") (API.ScalarType "string"))
],
_qAggregates = Nothing,
_qLimit = Just 1,
_qOffset = Nothing,
_qWhere = Nothing,
_qOrderBy = Nothing
},
_qrForeach = Nothing
mkQueryRequest
(mkTableName "Album")
( emptyQuery
& API.qFields
?~ mkFieldsMap
[ ("id", API.ColumnField (API.ColumnName "AlbumId") (API.ScalarType "number")),
("title", API.ColumnField (API.ColumnName "Title") (API.ScalarType "string"))
]
& API.qLimit ?~ 1
)
)
mockAgentGraphqlTest "permissions-based row limits are applied" $ \_testEnv performGraphqlRequest -> do
let headers = [("X-Hasura-Role", testRoleName)]
let graphqlRequest =
[graphql|
query getArtists {
artists(limit: 5) {
id
name
}
}
|]
let queryResponse =
mkRowsQueryResponse
[ [ ("id", API.mkColumnFieldValue $ Aeson.Number 1),
("name", API.mkColumnFieldValue $ Aeson.String "AC/DC")
],
[ ("id", API.mkColumnFieldValue $ Aeson.Number 2),
("name", API.mkColumnFieldValue $ Aeson.String "Accept")
],
[ ("id", API.mkColumnFieldValue $ Aeson.Number 3),
("name", API.mkColumnFieldValue $ Aeson.String "Aerosmith")
]
]
let mockConfig = Mock.chinookMock & mockQueryResponse queryResponse
MockRequestResults {..} <- performGraphqlRequest mockConfig headers graphqlRequest
_mrrResponse
`shouldBeYaml` [yaml|
data:
artists:
- id: 1
name: AC/DC
- id: 2
name: Accept
- id: 3
name: Aerosmith
|]
_mrrRecordedRequest
`shouldBe` Just
( Query $
mkQueryRequest
(mkTableName "Artist")
( emptyQuery
& API.qFields
?~ mkFieldsMap
[ ("id", API.ColumnField (API.ColumnName "ArtistId") $ API.ScalarType "number"),
("name", API.ColumnField (API.ColumnName "Name") $ API.ScalarType "string")
]
& API.qLimit ?~ 3 -- The permissions limit is smaller than the query limit, so it is used
)
)
mockAgentGraphqlTest "works with an exists-based permissions filter" $ \_testEnv performGraphqlRequest -> do
@ -173,12 +219,12 @@ tests _opts = describe "Basic Tests" $ do
}
|]
let queryResponse =
rowsResponse
[ [ (API.FieldName "CustomerId", API.mkColumnFieldValue $ Aeson.Number 1)
mkRowsQueryResponse
[ [ ("CustomerId", API.mkColumnFieldValue $ Aeson.Number 1)
],
[ (API.FieldName "CustomerId", API.mkColumnFieldValue $ Aeson.Number 2)
[ ("CustomerId", API.mkColumnFieldValue $ Aeson.Number 2)
],
[ (API.FieldName "CustomerId", API.mkColumnFieldValue $ Aeson.Number 3)
[ ("CustomerId", API.mkColumnFieldValue $ Aeson.Number 3)
]
]
let mockConfig = Mock.chinookMock & mockQueryResponse queryResponse
@ -197,31 +243,20 @@ tests _opts = describe "Basic Tests" $ do
_mrrRecordedRequest
`shouldBe` Just
( Query $
API.QueryRequest
{ _qrTable = API.TableName ("Customer" :| []),
_qrTableRelationships = [],
_qrQuery =
API.Query
{ _qFields =
Just $
HashMap.fromList
[ (API.FieldName "CustomerId", API.ColumnField (API.ColumnName "CustomerId") $ API.ScalarType "number")
],
_qAggregates = Nothing,
_qLimit = Nothing,
_qOffset = Nothing,
_qWhere =
Just $
API.Exists (API.UnrelatedTable $ API.TableName ("Employee" :| [])) $
API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.ColumnName "EmployeeId") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (Aeson.Number 1) (API.ScalarType "number")),
_qOrderBy = Nothing
},
_qrForeach = Nothing
}
mkQueryRequest
(mkTableName "Customer")
( emptyQuery
& API.qFields
?~ mkFieldsMap
[ ("CustomerId", API.ColumnField (API.ColumnName "CustomerId") $ API.ScalarType "number")
]
& API.qWhere
?~ API.Exists
(API.UnrelatedTable $ mkTableName "Employee")
( API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.ColumnName "EmployeeId") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (Aeson.Number 1) (API.ScalarType "number"))
)
)
)
rowsResponse :: [[(API.FieldName, API.FieldValue)]] -> API.QueryResponse
rowsResponse rows = API.QueryResponse (Just $ HashMap.fromList <$> rows) Nothing

View File

@ -6,8 +6,8 @@ module Test.DataConnector.MockAgent.CustomScalarsSpec (spec) where
--------------------------------------------------------------------------------
import Control.Lens ((?~))
import Data.Aeson qualified as Aeson
import Data.HashMap.Strict qualified as HashMap
import Data.List.NonEmpty qualified as NE
import Harness.Backend.DataConnector.Mock (AgentRequest (..), MockRequestResults (..), mockAgentGraphqlTest, mockQueryResponse)
import Harness.Backend.DataConnector.Mock qualified as Mock
@ -21,6 +21,7 @@ import Hasura.Backends.DataConnector.API (ColumnName (..), ScalarType (..), Scal
import Hasura.Backends.DataConnector.API qualified as API
import Hasura.Backends.DataConnector.API.V0.Expression
import Hasura.Prelude
import Test.DataConnector.MockAgent.TestHelpers
import Test.Hspec (SpecWith, describe, shouldBe)
--------------------------------------------------------------------------------
@ -67,7 +68,7 @@ tests _opts = describe "Custom scalar parsing tests" $ do
}
}
|]
let mockConfig = Mock.chinookMock & mockQueryResponse (rowsResponse customScalarsTable)
let mockConfig = Mock.chinookMock & mockQueryResponse (mkRowsQueryResponse customScalarsTable)
MockRequestResults {..} <- performGraphqlRequest mockConfig headers graphqlRequest
@ -86,29 +87,20 @@ tests _opts = describe "Custom scalar parsing tests" $ do
_mrrRecordedRequest
`shouldBe` Just
( Query $
API.QueryRequest
{ _qrTable = API.TableName ("MyCustomScalarsTable" :| []),
_qrTableRelationships = [],
_qrQuery =
API.Query
{ _qFields =
Just $
HashMap.fromList
[ (API.FieldName "MyIntColumn", API.ColumnField (API.ColumnName "MyIntColumn") $ API.ScalarType "MyInt"),
(API.FieldName "MyFloatColumn", API.ColumnField (API.ColumnName "MyFloatColumn") $ API.ScalarType "MyFloat"),
(API.FieldName "MyStringColumn", API.ColumnField (API.ColumnName "MyStringColumn") $ API.ScalarType "MyString"),
(API.FieldName "MyBooleanColumn", API.ColumnField (API.ColumnName "MyBooleanColumn") $ API.ScalarType "MyBoolean"),
(API.FieldName "MyIDColumn", API.ColumnField (API.ColumnName "MyIDColumn") $ API.ScalarType "MyID"),
(API.FieldName "MyAnythingColumn", API.ColumnField (API.ColumnName "MyAnythingColumn") $ API.ScalarType "MyAnything")
],
_qAggregates = Nothing,
_qLimit = Just 1,
_qOffset = Nothing,
_qWhere = Nothing,
_qOrderBy = Nothing
},
_qrForeach = Nothing
}
mkQueryRequest
(mkTableName "MyCustomScalarsTable")
( emptyQuery
& API.qFields
?~ mkFieldsMap
[ ("MyIntColumn", API.ColumnField (API.ColumnName "MyIntColumn") $ API.ScalarType "MyInt"),
("MyFloatColumn", API.ColumnField (API.ColumnName "MyFloatColumn") $ API.ScalarType "MyFloat"),
("MyStringColumn", API.ColumnField (API.ColumnName "MyStringColumn") $ API.ScalarType "MyString"),
("MyBooleanColumn", API.ColumnField (API.ColumnName "MyBooleanColumn") $ API.ScalarType "MyBoolean"),
("MyIDColumn", API.ColumnField (API.ColumnName "MyIDColumn") $ API.ScalarType "MyID"),
("MyAnythingColumn", API.ColumnField (API.ColumnName "MyAnythingColumn") $ API.ScalarType "MyAnything")
]
& API.qLimit ?~ 1
)
)
mockAgentGraphqlTest "parses scalar literals in where queries" $ \_testEnv performGraphqlRequest -> do
@ -133,7 +125,7 @@ tests _opts = describe "Custom scalar parsing tests" $ do
}
}
|]
let mockConfig = Mock.chinookMock & mockQueryResponse (rowsResponse customScalarsTable)
let mockConfig = Mock.chinookMock & mockQueryResponse (mkRowsQueryResponse customScalarsTable)
MockRequestResults {..} <- performGraphqlRequest mockConfig headers graphqlRequest
@ -152,56 +144,47 @@ tests _opts = describe "Custom scalar parsing tests" $ do
_mrrRecordedRequest
`shouldBe` Just
( Query $
API.QueryRequest
{ _qrTable = API.TableName ("MyCustomScalarsTable" :| []),
_qrTableRelationships = [],
_qrQuery =
API.Query
{ _qFields =
Just $
HashMap.fromList
[ (API.FieldName "MyIntColumn", API.ColumnField (API.ColumnName "MyIntColumn") $ API.ScalarType "MyInt"),
(API.FieldName "MyFloatColumn", API.ColumnField (API.ColumnName "MyFloatColumn") $ API.ScalarType "MyFloat"),
(API.FieldName "MyStringColumn", API.ColumnField (API.ColumnName "MyStringColumn") $ API.ScalarType "MyString"),
(API.FieldName "MyBooleanColumn", API.ColumnField (API.ColumnName "MyBooleanColumn") $ API.ScalarType "MyBoolean"),
(API.FieldName "MyIDColumn", API.ColumnField (API.ColumnName "MyIDColumn") $ API.ScalarType "MyID"),
(API.FieldName "MyAnythingColumn", API.ColumnField (API.ColumnName "MyAnythingColumn") $ API.ScalarType "MyAnything")
],
_qAggregates = Nothing,
_qLimit = Just 1,
_qOffset = Nothing,
_qWhere =
Just $
And
[ ApplyBinaryComparisonOperator
Equal
(ComparisonColumn {_ccPath = CurrentTable, _ccName = ColumnName {unColumnName = "MyBooleanColumn"}, _ccColumnType = ScalarType "MyBoolean"})
(ScalarValueComparison $ ScalarValue (Aeson.Bool True) (ScalarType "MyBoolean")),
ApplyBinaryComparisonOperator
Equal
(ComparisonColumn {_ccPath = CurrentTable, _ccName = ColumnName {unColumnName = "MyFloatColumn"}, _ccColumnType = ScalarType "MyFloat"})
(ScalarValueComparison $ ScalarValue (Aeson.Number 3.14) (ScalarType "MyFloat")),
ApplyBinaryComparisonOperator
Equal
(ComparisonColumn {_ccPath = CurrentTable, _ccName = ColumnName {unColumnName = "MyStringColumn"}, _ccColumnType = ScalarType "MyString"})
(ScalarValueComparison $ ScalarValue (Aeson.String "foo") (ScalarType "MyString")),
ApplyBinaryComparisonOperator
Equal
(ComparisonColumn {_ccPath = CurrentTable, _ccName = ColumnName {unColumnName = "MyIDColumn"}, _ccColumnType = ScalarType "MyID"})
(ScalarValueComparison $ ScalarValue (Aeson.String "x") (ScalarType "MyID")),
ApplyBinaryComparisonOperator
Equal
(ComparisonColumn {_ccPath = CurrentTable, _ccName = ColumnName {unColumnName = "MyIntColumn"}, _ccColumnType = ScalarType "MyInt"})
(ScalarValueComparison $ ScalarValue (Aeson.Number 42.0) (ScalarType "MyInt")),
ApplyBinaryComparisonOperator
Equal
(ComparisonColumn {_ccPath = CurrentTable, _ccName = ColumnName {unColumnName = "MyAnythingColumn"}, _ccColumnType = ScalarType "MyAnything"})
(ScalarValueComparison $ ScalarValue (Aeson.Object mempty) (ScalarType "MyAnything"))
],
_qOrderBy = Nothing
},
_qrForeach = Nothing
}
mkQueryRequest
(mkTableName "MyCustomScalarsTable")
( emptyQuery
& API.qFields
?~ mkFieldsMap
[ ("MyIntColumn", API.ColumnField (API.ColumnName "MyIntColumn") $ API.ScalarType "MyInt"),
("MyFloatColumn", API.ColumnField (API.ColumnName "MyFloatColumn") $ API.ScalarType "MyFloat"),
("MyStringColumn", API.ColumnField (API.ColumnName "MyStringColumn") $ API.ScalarType "MyString"),
("MyBooleanColumn", API.ColumnField (API.ColumnName "MyBooleanColumn") $ API.ScalarType "MyBoolean"),
("MyIDColumn", API.ColumnField (API.ColumnName "MyIDColumn") $ API.ScalarType "MyID"),
("MyAnythingColumn", API.ColumnField (API.ColumnName "MyAnythingColumn") $ API.ScalarType "MyAnything")
]
& API.qLimit ?~ 1
& API.qWhere
?~ And
[ ApplyBinaryComparisonOperator
Equal
(ComparisonColumn CurrentTable (ColumnName "MyBooleanColumn") (ScalarType "MyBoolean"))
(ScalarValueComparison $ ScalarValue (Aeson.Bool True) (ScalarType "MyBoolean")),
ApplyBinaryComparisonOperator
Equal
(ComparisonColumn CurrentTable (ColumnName "MyFloatColumn") (ScalarType "MyFloat"))
(ScalarValueComparison $ ScalarValue (Aeson.Number 3.14) (ScalarType "MyFloat")),
ApplyBinaryComparisonOperator
Equal
(ComparisonColumn CurrentTable (ColumnName "MyStringColumn") (ScalarType "MyString"))
(ScalarValueComparison $ ScalarValue (Aeson.String "foo") (ScalarType "MyString")),
ApplyBinaryComparisonOperator
Equal
(ComparisonColumn CurrentTable (ColumnName "MyIDColumn") (ScalarType "MyID"))
(ScalarValueComparison $ ScalarValue (Aeson.String "x") (ScalarType "MyID")),
ApplyBinaryComparisonOperator
Equal
(ComparisonColumn CurrentTable (ColumnName "MyIntColumn") (ScalarType "MyInt"))
(ScalarValueComparison $ ScalarValue (Aeson.Number 42.0) (ScalarType "MyInt")),
ApplyBinaryComparisonOperator
Equal
(ComparisonColumn CurrentTable (ColumnName "MyAnythingColumn") (ScalarType "MyAnything"))
(ScalarValueComparison $ ScalarValue (Aeson.Object mempty) (ScalarType "MyAnything"))
]
)
)
mockAgentGraphqlTest "fails parsing float when expecting int" $ \_testEnv performGraphqlRequest -> do
@ -216,7 +199,7 @@ tests _opts = describe "Custom scalar parsing tests" $ do
}
}
|]
let mockConfig = Mock.chinookMock & mockQueryResponse (rowsResponse myIntTable)
let mockConfig = Mock.chinookMock & mockQueryResponse (mkRowsQueryResponse myIntTable)
MockRequestResults {..} <- performGraphqlRequest mockConfig headers graphqlRequest
@ -243,7 +226,7 @@ tests _opts = describe "Custom scalar parsing tests" $ do
}
}
|]
let mockConfig = Mock.chinookMock & mockQueryResponse (rowsResponse myIntTable)
let mockConfig = Mock.chinookMock & mockQueryResponse (mkRowsQueryResponse myIntTable)
MockRequestResults {..} <- performGraphqlRequest mockConfig headers graphqlRequest
@ -270,7 +253,7 @@ tests _opts = describe "Custom scalar parsing tests" $ do
}
}
|]
let mockConfig = Mock.chinookMock & mockQueryResponse (rowsResponse myIntTable)
let mockConfig = Mock.chinookMock & mockQueryResponse (mkRowsQueryResponse myIntTable)
MockRequestResults {..} <- performGraphqlRequest mockConfig headers graphqlRequest
@ -297,7 +280,7 @@ tests _opts = describe "Custom scalar parsing tests" $ do
}
}
|]
let mockConfig = Mock.chinookMock & mockQueryResponse (rowsResponse myIntTable)
let mockConfig = Mock.chinookMock & mockQueryResponse (mkRowsQueryResponse myIntTable)
MockRequestResults {..} <- performGraphqlRequest mockConfig headers graphqlRequest
@ -320,7 +303,7 @@ tests _opts = describe "Custom scalar parsing tests" $ do
}
}
|]
let mockConfig = Mock.chinookMock & mockQueryResponse (rowsResponse myIntTable)
let mockConfig = Mock.chinookMock & mockQueryResponse (mkRowsQueryResponse myIntTable)
MockRequestResults {..} <- performGraphqlRequest mockConfig headers graphqlRequest
@ -347,7 +330,7 @@ tests _opts = describe "Custom scalar parsing tests" $ do
}
}
|]
let mockConfig = Mock.chinookMock & mockQueryResponse (rowsResponse myIntTable)
let mockConfig = Mock.chinookMock & mockQueryResponse (mkRowsQueryResponse myIntTable)
MockRequestResults {..} <- performGraphqlRequest mockConfig headers graphqlRequest
@ -374,7 +357,7 @@ tests _opts = describe "Custom scalar parsing tests" $ do
}
}
|]
let mockConfig = Mock.chinookMock & mockQueryResponse (rowsResponse myIntTable)
let mockConfig = Mock.chinookMock & mockQueryResponse (mkRowsQueryResponse myIntTable)
MockRequestResults {..} <- performGraphqlRequest mockConfig headers graphqlRequest
@ -401,7 +384,7 @@ tests _opts = describe "Custom scalar parsing tests" $ do
}
}
|]
let mockConfig = Mock.chinookMock & mockQueryResponse (rowsResponse myIntTable)
let mockConfig = Mock.chinookMock & mockQueryResponse (mkRowsQueryResponse myIntTable)
MockRequestResults {..} <- performGraphqlRequest mockConfig headers graphqlRequest
@ -428,7 +411,7 @@ tests _opts = describe "Custom scalar parsing tests" $ do
}
}
|]
let mockConfig = Mock.chinookMock & mockQueryResponse (rowsResponse myIntTable)
let mockConfig = Mock.chinookMock & mockQueryResponse (mkRowsQueryResponse myIntTable)
MockRequestResults {..} <- performGraphqlRequest mockConfig headers graphqlRequest
@ -455,7 +438,7 @@ tests _opts = describe "Custom scalar parsing tests" $ do
}
}
|]
let mockConfig = Mock.chinookMock & mockQueryResponse (rowsResponse myIntTable)
let mockConfig = Mock.chinookMock & mockQueryResponse (mkRowsQueryResponse myIntTable)
MockRequestResults {..} <- performGraphqlRequest mockConfig headers graphqlRequest
@ -482,7 +465,7 @@ tests _opts = describe "Custom scalar parsing tests" $ do
}
}
|]
let mockConfig = Mock.chinookMock & mockQueryResponse (rowsResponse myIntTable)
let mockConfig = Mock.chinookMock & mockQueryResponse (mkRowsQueryResponse myIntTable)
MockRequestResults {..} <- performGraphqlRequest mockConfig headers graphqlRequest
@ -509,7 +492,7 @@ tests _opts = describe "Custom scalar parsing tests" $ do
}
}
|]
let mockConfig = Mock.chinookMock & mockQueryResponse (rowsResponse myIntTable)
let mockConfig = Mock.chinookMock & mockQueryResponse (mkRowsQueryResponse myIntTable)
MockRequestResults {..} <- performGraphqlRequest mockConfig headers graphqlRequest
@ -536,7 +519,7 @@ tests _opts = describe "Custom scalar parsing tests" $ do
}
}
|]
let mockConfig = Mock.chinookMock & mockQueryResponse (rowsResponse myIntTable)
let mockConfig = Mock.chinookMock & mockQueryResponse (mkRowsQueryResponse myIntTable)
MockRequestResults {..} <- performGraphqlRequest mockConfig headers graphqlRequest
@ -559,7 +542,7 @@ tests _opts = describe "Custom scalar parsing tests" $ do
}
}
|]
let mockConfig = Mock.chinookMock & mockQueryResponse (rowsResponse myIntTable)
let mockConfig = Mock.chinookMock & mockQueryResponse (mkRowsQueryResponse myIntTable)
MockRequestResults {..} <- performGraphqlRequest mockConfig headers graphqlRequest
@ -586,7 +569,7 @@ tests _opts = describe "Custom scalar parsing tests" $ do
}
}
|]
let mockConfig = Mock.chinookMock & mockQueryResponse (rowsResponse myIntTable)
let mockConfig = Mock.chinookMock & mockQueryResponse (mkRowsQueryResponse myIntTable)
MockRequestResults {..} <- performGraphqlRequest mockConfig headers graphqlRequest
@ -613,7 +596,7 @@ tests _opts = describe "Custom scalar parsing tests" $ do
}
}
|]
let mockConfig = Mock.chinookMock & mockQueryResponse (rowsResponse myIntTable)
let mockConfig = Mock.chinookMock & mockQueryResponse (mkRowsQueryResponse myIntTable)
MockRequestResults {..} <- performGraphqlRequest mockConfig headers graphqlRequest
@ -636,7 +619,7 @@ tests _opts = describe "Custom scalar parsing tests" $ do
}
}
|]
let mockConfig = Mock.chinookMock & mockQueryResponse (rowsResponse myIntTable)
let mockConfig = Mock.chinookMock & mockQueryResponse (mkRowsQueryResponse myIntTable)
MockRequestResults {..} <- performGraphqlRequest mockConfig headers graphqlRequest
@ -659,7 +642,7 @@ tests _opts = describe "Custom scalar parsing tests" $ do
}
}
|]
let mockConfig = Mock.chinookMock & mockQueryResponse (rowsResponse myIntTable)
let mockConfig = Mock.chinookMock & mockQueryResponse (mkRowsQueryResponse myIntTable)
MockRequestResults {..} <- performGraphqlRequest mockConfig headers graphqlRequest
@ -682,7 +665,7 @@ tests _opts = describe "Custom scalar parsing tests" $ do
}
}
|]
let mockConfig = Mock.chinookMock & mockQueryResponse (rowsResponse myIntTable)
let mockConfig = Mock.chinookMock & mockQueryResponse (mkRowsQueryResponse myIntTable)
MockRequestResults {..} <- performGraphqlRequest mockConfig headers graphqlRequest
@ -705,7 +688,7 @@ tests _opts = describe "Custom scalar parsing tests" $ do
}
}
|]
let mockConfig = Mock.chinookMock & mockQueryResponse (rowsResponse myIntTable)
let mockConfig = Mock.chinookMock & mockQueryResponse (mkRowsQueryResponse myIntTable)
MockRequestResults {..} <- performGraphqlRequest mockConfig headers graphqlRequest
@ -717,18 +700,15 @@ tests _opts = describe "Custom scalar parsing tests" $ do
|]
where
customScalarsTable =
[ [ (API.FieldName "MyIntColumn", API.mkColumnFieldValue $ Aeson.Number 42),
(API.FieldName "MyFloatColumn", API.mkColumnFieldValue $ Aeson.Number 3.14),
(API.FieldName "MyStringColumn", API.mkColumnFieldValue $ Aeson.String "foo"),
(API.FieldName "MyBooleanColumn", API.mkColumnFieldValue $ Aeson.Bool True),
(API.FieldName "MyIDColumn", API.mkColumnFieldValue $ Aeson.String "x"),
(API.FieldName "MyAnythingColumn", API.mkColumnFieldValue $ Aeson.Object mempty)
[ [ ("MyIntColumn", API.mkColumnFieldValue $ Aeson.Number 42),
("MyFloatColumn", API.mkColumnFieldValue $ Aeson.Number 3.14),
("MyStringColumn", API.mkColumnFieldValue $ Aeson.String "foo"),
("MyBooleanColumn", API.mkColumnFieldValue $ Aeson.Bool True),
("MyIDColumn", API.mkColumnFieldValue $ Aeson.String "x"),
("MyAnythingColumn", API.mkColumnFieldValue $ Aeson.Object mempty)
]
]
myIntTable =
[ [ (API.FieldName "MyIntColumn", API.mkColumnFieldValue $ Aeson.Number 42)
[ [ ("MyIntColumn", API.mkColumnFieldValue $ Aeson.Number 42)
]
]
rowsResponse :: [[(API.FieldName, API.FieldValue)]] -> API.QueryResponse
rowsResponse rows = API.QueryResponse (Just $ HashMap.fromList <$> rows) Nothing

View File

@ -3,6 +3,7 @@ module Test.DataConnector.MockAgent.DeleteMutationsSpec
)
where
import Control.Lens ((.~), (?~))
import Data.Aeson qualified as Aeson
import Data.ByteString (ByteString)
import Data.HashMap.Strict qualified as HashMap
@ -17,6 +18,7 @@ import Harness.TestEnvironment (GlobalTestEnvironment, TestEnvironment)
import Harness.Yaml
import Hasura.Backends.DataConnector.API qualified as API
import Hasura.Prelude
import Test.DataConnector.MockAgent.TestHelpers
import Test.Hspec
--------------------------------------------------------------------------------
@ -107,38 +109,38 @@ tests _opts = do
{ API._morAffectedRows = 3,
API._morReturning =
Just
[ HashMap.fromList
[ (API.FieldName "deletedRows_AlbumId", API.mkColumnFieldValue $ Aeson.Number 112),
(API.FieldName "deletedRows_Title", API.mkColumnFieldValue $ Aeson.String "The Number of The Beast"),
( API.FieldName "deletedRows_Artist",
[ mkFieldsMap
[ ("deletedRows_AlbumId", API.mkColumnFieldValue $ Aeson.Number 112),
("deletedRows_Title", API.mkColumnFieldValue $ Aeson.String "The Number of The Beast"),
( "deletedRows_Artist",
API.mkRelationshipFieldValue $
rowsResponse
[ [ (API.FieldName "ArtistId", API.mkColumnFieldValue $ Aeson.Number 90),
(API.FieldName "Name", API.mkColumnFieldValue $ Aeson.String "Iron Maiden")
mkRowsQueryResponse
[ [ ("ArtistId", API.mkColumnFieldValue $ Aeson.Number 90),
("Name", API.mkColumnFieldValue $ Aeson.String "Iron Maiden")
]
]
)
],
HashMap.fromList
[ (API.FieldName "deletedRows_AlbumId", API.mkColumnFieldValue $ Aeson.Number 113),
(API.FieldName "deletedRows_Title", API.mkColumnFieldValue $ Aeson.String "The X Factor"),
( API.FieldName "deletedRows_Artist",
mkFieldsMap
[ ("deletedRows_AlbumId", API.mkColumnFieldValue $ Aeson.Number 113),
("deletedRows_Title", API.mkColumnFieldValue $ Aeson.String "The X Factor"),
( "deletedRows_Artist",
API.mkRelationshipFieldValue $
rowsResponse
[ [ (API.FieldName "ArtistId", API.mkColumnFieldValue $ Aeson.Number 90),
(API.FieldName "Name", API.mkColumnFieldValue $ Aeson.String "Iron Maiden")
mkRowsQueryResponse
[ [ ("ArtistId", API.mkColumnFieldValue $ Aeson.Number 90),
("Name", API.mkColumnFieldValue $ Aeson.String "Iron Maiden")
]
]
)
],
HashMap.fromList
[ (API.FieldName "deletedRows_AlbumId", API.mkColumnFieldValue $ Aeson.Number 114),
(API.FieldName "deletedRows_Title", API.mkColumnFieldValue $ Aeson.String "Virtual XI"),
( API.FieldName "deletedRows_Artist",
mkFieldsMap
[ ("deletedRows_AlbumId", API.mkColumnFieldValue $ Aeson.Number 114),
("deletedRows_Title", API.mkColumnFieldValue $ Aeson.String "Virtual XI"),
( "deletedRows_Artist",
API.mkRelationshipFieldValue $
rowsResponse
[ [ (API.FieldName "ArtistId", API.mkColumnFieldValue $ Aeson.Number 90),
(API.FieldName "Name", API.mkColumnFieldValue $ Aeson.String "Iron Maiden")
mkRowsQueryResponse
[ [ ("ArtistId", API.mkColumnFieldValue $ Aeson.Number 90),
("Name", API.mkColumnFieldValue $ Aeson.String "Iron Maiden")
]
]
)
@ -174,66 +176,58 @@ tests _opts = do
|]
let expectedRequest =
API.MutationRequest
{ API._mrTableRelationships =
[ API.TableRelationships
{ API._trSourceTable = API.TableName ("Album" :| []),
API._trRelationships =
HashMap.fromList
[ ( API.RelationshipName "Artist",
API.Relationship
{ API._rTargetTable = API.TableName ("Artist" :| []),
API._rRelationshipType = API.ObjectRelationship,
API._rColumnMapping = HashMap.fromList [(API.ColumnName "ArtistId", API.ColumnName "ArtistId")]
}
)
]
}
],
API._mrInsertSchema = [],
API._mrOperations =
[ API.DeleteOperation $
API.DeleteMutationOperation
{ API._dmoTable = API.TableName ("Album" :| []),
API._dmoWhere =
Just $
API.And
[ API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.ColumnName "ArtistId") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (Aeson.Number 90) (API.ScalarType "number")),
API.ApplyBinaryComparisonOperator
API.GreaterThan
(API.ComparisonColumn API.CurrentTable (API.ColumnName "AlbumId") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (Aeson.Number 111) (API.ScalarType "number"))
],
API._dmoReturningFields =
HashMap.fromList
[ (API.FieldName "deletedRows_AlbumId", API.ColumnField (API.ColumnName "AlbumId") (API.ScalarType "number")),
(API.FieldName "deletedRows_Title", API.ColumnField (API.ColumnName "Title") (API.ScalarType "string")),
( API.FieldName "deletedRows_Artist",
API.RelField
( API.RelationshipField
(API.RelationshipName "Artist")
API.Query
{ _qFields =
Just $
HashMap.fromList
[ (API.FieldName "ArtistId", API.ColumnField (API.ColumnName "ArtistId") $ API.ScalarType "number"),
(API.FieldName "Name", API.ColumnField (API.ColumnName "Name") $ API.ScalarType "string")
],
_qAggregates = Nothing,
_qLimit = Nothing,
_qOffset = Nothing,
_qWhere = Nothing,
_qOrderBy = Nothing
}
)
)
]
}
]
}
emptyMutationRequest
& API.mrTableRelationships
.~ [ API.TableRelationships
{ API._trSourceTable = mkTableName "Album",
API._trRelationships =
HashMap.fromList
[ ( API.RelationshipName "Artist",
API.Relationship
{ API._rTargetTable = mkTableName "Artist",
API._rRelationshipType = API.ObjectRelationship,
API._rColumnMapping = HashMap.fromList [(API.ColumnName "ArtistId", API.ColumnName "ArtistId")]
}
)
]
}
]
& API.mrOperations
.~ [ API.DeleteOperation $
API.DeleteMutationOperation
{ API._dmoTable = mkTableName "Album",
API._dmoWhere =
Just $
API.And
[ API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.ColumnName "ArtistId") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (Aeson.Number 90) (API.ScalarType "number")),
API.ApplyBinaryComparisonOperator
API.GreaterThan
(API.ComparisonColumn API.CurrentTable (API.ColumnName "AlbumId") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (Aeson.Number 111) (API.ScalarType "number"))
],
API._dmoReturningFields =
mkFieldsMap
[ ("deletedRows_AlbumId", API.ColumnField (API.ColumnName "AlbumId") (API.ScalarType "number")),
("deletedRows_Title", API.ColumnField (API.ColumnName "Title") (API.ScalarType "string")),
( "deletedRows_Artist",
API.RelField
( API.RelationshipField
(API.RelationshipName "Artist")
( emptyQuery
& API.qFields
?~ mkFieldsMap
[ ("ArtistId", API.ColumnField (API.ColumnName "ArtistId") $ API.ScalarType "number"),
("Name", API.ColumnField (API.ColumnName "Name") $ API.ScalarType "string")
]
)
)
)
]
}
]
_mrrRecordedRequest `shouldBe` Just (Mutation expectedRequest)
mockAgentGraphqlTest "delete row by pk with delete permissions" $ \_testEnv performGraphqlRequest -> do
@ -257,14 +251,14 @@ tests _opts = do
{ API._morAffectedRows = 1,
API._morReturning =
Just
[ HashMap.fromList
[ (API.FieldName "AlbumId", API.mkColumnFieldValue $ Aeson.Number 112),
(API.FieldName "Title", API.mkColumnFieldValue $ Aeson.String "The Number of The Beast"),
( API.FieldName "Artist",
[ mkFieldsMap
[ ("AlbumId", API.mkColumnFieldValue $ Aeson.Number 112),
("Title", API.mkColumnFieldValue $ Aeson.String "The Number of The Beast"),
( "Artist",
API.mkRelationshipFieldValue $
rowsResponse
[ [ (API.FieldName "ArtistId", API.mkColumnFieldValue $ Aeson.Number 90),
(API.FieldName "Name", API.mkColumnFieldValue $ Aeson.String "Iron Maiden")
mkRowsQueryResponse
[ [ ("ArtistId", API.mkColumnFieldValue $ Aeson.Number 90),
("Name", API.mkColumnFieldValue $ Aeson.String "Iron Maiden")
]
]
)
@ -288,67 +282,56 @@ tests _opts = do
|]
let expectedRequest =
API.MutationRequest
{ API._mrTableRelationships =
[ API.TableRelationships
{ API._trSourceTable = API.TableName ("Album" :| []),
API._trRelationships =
HashMap.fromList
[ ( API.RelationshipName "Artist",
API.Relationship
{ API._rTargetTable = API.TableName ("Artist" :| []),
API._rRelationshipType = API.ObjectRelationship,
API._rColumnMapping = HashMap.fromList [(API.ColumnName "ArtistId", API.ColumnName "ArtistId")]
}
)
]
}
],
API._mrInsertSchema = [],
API._mrOperations =
[ API.DeleteOperation $
API.DeleteMutationOperation
{ API._dmoTable = API.TableName ("Album" :| []),
API._dmoWhere =
Just $
API.And
[ API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.ColumnName "ArtistId") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (Aeson.Number 90) (API.ScalarType "number")),
API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.ColumnName "AlbumId") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (Aeson.Number 112) (API.ScalarType "number"))
],
API._dmoReturningFields =
HashMap.fromList
[ (API.FieldName "AlbumId", API.ColumnField (API.ColumnName "AlbumId") (API.ScalarType "number")),
(API.FieldName "Title", API.ColumnField (API.ColumnName "Title") (API.ScalarType "string")),
( API.FieldName "Artist",
API.RelField
( API.RelationshipField
(API.RelationshipName "Artist")
API.Query
{ _qFields =
Just $
HashMap.fromList
[ (API.FieldName "ArtistId", API.ColumnField (API.ColumnName "ArtistId") $ API.ScalarType "number"),
(API.FieldName "Name", API.ColumnField (API.ColumnName "Name") $ API.ScalarType "string")
],
_qAggregates = Nothing,
_qLimit = Nothing,
_qOffset = Nothing,
_qWhere = Nothing,
_qOrderBy = Nothing
}
)
)
]
}
]
}
emptyMutationRequest
& API.mrTableRelationships
.~ [ API.TableRelationships
{ API._trSourceTable = mkTableName "Album",
API._trRelationships =
HashMap.fromList
[ ( API.RelationshipName "Artist",
API.Relationship
{ API._rTargetTable = mkTableName "Artist",
API._rRelationshipType = API.ObjectRelationship,
API._rColumnMapping = HashMap.fromList [(API.ColumnName "ArtistId", API.ColumnName "ArtistId")]
}
)
]
}
]
& API.mrOperations
.~ [ API.DeleteOperation $
API.DeleteMutationOperation
{ API._dmoTable = mkTableName "Album",
API._dmoWhere =
Just $
API.And
[ API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.ColumnName "ArtistId") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (Aeson.Number 90) (API.ScalarType "number")),
API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.ColumnName "AlbumId") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (Aeson.Number 112) (API.ScalarType "number"))
],
API._dmoReturningFields =
mkFieldsMap
[ ("AlbumId", API.ColumnField (API.ColumnName "AlbumId") (API.ScalarType "number")),
("Title", API.ColumnField (API.ColumnName "Title") (API.ScalarType "string")),
( "Artist",
API.RelField
( API.RelationshipField
(API.RelationshipName "Artist")
( emptyQuery
& API.qFields
?~ mkFieldsMap
[ ("ArtistId", API.ColumnField (API.ColumnName "ArtistId") $ API.ScalarType "number"),
("Name", API.ColumnField (API.ColumnName "Name") $ API.ScalarType "string")
]
)
)
)
]
}
]
_mrrRecordedRequest `shouldBe` Just (Mutation expectedRequest)
rowsResponse :: [[(API.FieldName, API.FieldValue)]] -> API.QueryResponse
rowsResponse rows = API.QueryResponse (Just $ HashMap.fromList <$> rows) Nothing

View File

@ -6,8 +6,8 @@ module Test.DataConnector.MockAgent.ErrorSpec (spec) where
--------------------------------------------------------------------------------
import Control.Lens ((?~))
import Data.Aeson qualified as Aeson
import Data.HashMap.Strict qualified as HashMap
import Data.List.NonEmpty qualified as NE
import Harness.Backend.DataConnector.Mock (AgentRequest (..), MockRequestResults (..), mockAgentGraphqlTest)
import Harness.Backend.DataConnector.Mock qualified as Mock
@ -19,6 +19,7 @@ import Harness.TestEnvironment (GlobalTestEnvironment, TestEnvironment)
import Harness.Yaml (shouldBeYaml)
import Hasura.Backends.DataConnector.API qualified as API
import Hasura.Prelude
import Test.DataConnector.MockAgent.TestHelpers
import Test.Hspec (SpecWith, describe, shouldBe)
--------------------------------------------------------------------------------
@ -92,23 +93,14 @@ tests _opts = describe "Error Protocol Tests" $ do
_mrrRecordedRequest
`shouldBe` Just
( Query $
API.QueryRequest
{ _qrTable = API.TableName ("Album" :| []),
_qrTableRelationships = [],
_qrQuery =
API.Query
{ _qFields =
Just $
HashMap.fromList
[ (API.FieldName "id", API.ColumnField (API.ColumnName "AlbumId") $ API.ScalarType "number"),
(API.FieldName "title", API.ColumnField (API.ColumnName "Title") $ API.ScalarType "string")
],
_qAggregates = Nothing,
_qLimit = Just 1,
_qOffset = Nothing,
_qWhere = Nothing,
_qOrderBy = Nothing
},
_qrForeach = Nothing
}
mkQueryRequest
(mkTableName "Album")
( emptyQuery
& API.qFields
?~ mkFieldsMap
[ ("id", API.ColumnField (API.ColumnName "AlbumId") $ API.ScalarType "number"),
("title", API.ColumnField (API.ColumnName "Title") $ API.ScalarType "string")
]
& API.qLimit ?~ 1
)
)

View File

@ -3,6 +3,7 @@ module Test.DataConnector.MockAgent.InsertMutationsSpec
)
where
import Control.Lens ((.~), (?~))
import Data.Aeson qualified as Aeson
import Data.ByteString (ByteString)
import Data.HashMap.Strict qualified as HashMap
@ -17,6 +18,7 @@ import Harness.TestEnvironment (GlobalTestEnvironment, TestEnvironment)
import Harness.Yaml
import Hasura.Backends.DataConnector.API qualified as API
import Hasura.Prelude
import Test.DataConnector.MockAgent.TestHelpers
import Test.Hspec
--------------------------------------------------------------------------------
@ -116,26 +118,26 @@ tests _opts = do
{ API._morAffectedRows = 2,
API._morReturning =
Just
[ HashMap.fromList
[ (API.FieldName "insertedRows_AlbumId", API.mkColumnFieldValue $ Aeson.Number 9001),
(API.FieldName "insertedRows_Title", API.mkColumnFieldValue $ Aeson.String "Super Mega Rock"),
( API.FieldName "insertedRows_Artist",
[ mkFieldsMap
[ ("insertedRows_AlbumId", API.mkColumnFieldValue $ Aeson.Number 9001),
("insertedRows_Title", API.mkColumnFieldValue $ Aeson.String "Super Mega Rock"),
( "insertedRows_Artist",
API.mkRelationshipFieldValue $
rowsResponse
[ [ (API.FieldName "ArtistId", API.mkColumnFieldValue $ Aeson.Number 2),
(API.FieldName "Name", API.mkColumnFieldValue $ Aeson.String "Accept")
mkRowsQueryResponse
[ [ ("ArtistId", API.mkColumnFieldValue $ Aeson.Number 2),
("Name", API.mkColumnFieldValue $ Aeson.String "Accept")
]
]
)
],
HashMap.fromList
[ (API.FieldName "insertedRows_AlbumId", API.mkColumnFieldValue $ Aeson.Number 9002),
(API.FieldName "insertedRows_Title", API.mkColumnFieldValue $ Aeson.String "Accept This"),
( API.FieldName "insertedRows_Artist",
mkFieldsMap
[ ("insertedRows_AlbumId", API.mkColumnFieldValue $ Aeson.Number 9002),
("insertedRows_Title", API.mkColumnFieldValue $ Aeson.String "Accept This"),
( "insertedRows_Artist",
API.mkRelationshipFieldValue $
rowsResponse
[ [ (API.FieldName "ArtistId", API.mkColumnFieldValue $ Aeson.Number 2),
(API.FieldName "Name", API.mkColumnFieldValue $ Aeson.String "Accept")
mkRowsQueryResponse
[ [ ("ArtistId", API.mkColumnFieldValue $ Aeson.Number 2),
("Name", API.mkColumnFieldValue $ Aeson.String "Accept")
]
]
)
@ -166,85 +168,75 @@ tests _opts = do
|]
let expectedRequest =
API.MutationRequest
{ API._mrTableRelationships =
[ API.TableRelationships
{ API._trSourceTable = API.TableName ("Album" :| []),
API._trRelationships =
HashMap.fromList
[ ( API.RelationshipName "Artist",
API.Relationship
{ API._rTargetTable = API.TableName ("Artist" :| []),
API._rRelationshipType = API.ObjectRelationship,
API._rColumnMapping = HashMap.fromList [(API.ColumnName "ArtistId", API.ColumnName "ArtistId")]
}
)
]
}
],
API._mrInsertSchema =
[ API.TableInsertSchema
{ API._tisTable = API.TableName ("Album" :| []),
API._tisFields =
HashMap.fromList
[ (API.FieldName "AlbumId", API.ColumnInsert $ API.ColumnInsertSchema (API.ColumnName "AlbumId") (API.ScalarType "number")),
(API.FieldName "ArtistId", API.ColumnInsert $ API.ColumnInsertSchema (API.ColumnName "ArtistId") (API.ScalarType "number")),
(API.FieldName "Title", API.ColumnInsert $ API.ColumnInsertSchema (API.ColumnName "Title") (API.ScalarType "string"))
]
}
],
API._mrOperations =
[ API.InsertOperation $
API.InsertMutationOperation
{ API._imoTable = API.TableName ("Album" :| []),
API._imoRows =
[ API.RowObject $
HashMap.fromList
[ (API.FieldName "AlbumId", API.mkColumnInsertFieldValue $ Aeson.Number 9001),
(API.FieldName "ArtistId", API.mkColumnInsertFieldValue $ Aeson.Number 2),
(API.FieldName "Title", API.mkColumnInsertFieldValue $ Aeson.String "Super Mega Rock")
],
API.RowObject $
HashMap.fromList
[ (API.FieldName "AlbumId", API.mkColumnInsertFieldValue $ Aeson.Number 9002),
(API.FieldName "ArtistId", API.mkColumnInsertFieldValue $ Aeson.Number 2),
(API.FieldName "Title", API.mkColumnInsertFieldValue $ Aeson.String "Accept This")
]
],
API._imoPostInsertCheck =
Just $
API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.ColumnName "ArtistId") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (Aeson.Number 2) (API.ScalarType "number")),
API._imoReturningFields =
HashMap.fromList
[ (API.FieldName "insertedRows_AlbumId", API.ColumnField (API.ColumnName "AlbumId") (API.ScalarType "number")),
(API.FieldName "insertedRows_Title", API.ColumnField (API.ColumnName "Title") (API.ScalarType "string")),
( API.FieldName "insertedRows_Artist",
API.RelField
( API.RelationshipField
(API.RelationshipName "Artist")
API.Query
{ _qFields =
Just $
HashMap.fromList
[ (API.FieldName "ArtistId", API.ColumnField (API.ColumnName "ArtistId") $ API.ScalarType "number"),
(API.FieldName "Name", API.ColumnField (API.ColumnName "Name") $ API.ScalarType "string")
],
_qAggregates = Nothing,
_qLimit = Nothing,
_qOffset = Nothing,
_qWhere = Nothing,
_qOrderBy = Nothing
}
)
)
]
}
]
}
emptyMutationRequest
& API.mrTableRelationships
.~ [ API.TableRelationships
{ API._trSourceTable = mkTableName "Album",
API._trRelationships =
HashMap.fromList
[ ( API.RelationshipName "Artist",
API.Relationship
{ API._rTargetTable = mkTableName "Artist",
API._rRelationshipType = API.ObjectRelationship,
API._rColumnMapping = HashMap.fromList [(API.ColumnName "ArtistId", API.ColumnName "ArtistId")]
}
)
]
}
]
& API.mrInsertSchema
.~ [ API.TableInsertSchema
{ API._tisTable = mkTableName "Album",
API._tisFields =
mkFieldsMap
[ ("AlbumId", API.ColumnInsert $ API.ColumnInsertSchema (API.ColumnName "AlbumId") (API.ScalarType "number")),
("ArtistId", API.ColumnInsert $ API.ColumnInsertSchema (API.ColumnName "ArtistId") (API.ScalarType "number")),
("Title", API.ColumnInsert $ API.ColumnInsertSchema (API.ColumnName "Title") (API.ScalarType "string"))
]
}
]
& API.mrOperations
.~ [ API.InsertOperation $
API.InsertMutationOperation
{ API._imoTable = mkTableName "Album",
API._imoRows =
[ API.RowObject $
mkFieldsMap
[ ("AlbumId", API.mkColumnInsertFieldValue $ Aeson.Number 9001),
("ArtistId", API.mkColumnInsertFieldValue $ Aeson.Number 2),
("Title", API.mkColumnInsertFieldValue $ Aeson.String "Super Mega Rock")
],
API.RowObject $
mkFieldsMap
[ ("AlbumId", API.mkColumnInsertFieldValue $ Aeson.Number 9002),
("ArtistId", API.mkColumnInsertFieldValue $ Aeson.Number 2),
("Title", API.mkColumnInsertFieldValue $ Aeson.String "Accept This")
]
],
API._imoPostInsertCheck =
Just $
API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.ColumnName "ArtistId") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (Aeson.Number 2) (API.ScalarType "number")),
API._imoReturningFields =
mkFieldsMap
[ ("insertedRows_AlbumId", API.ColumnField (API.ColumnName "AlbumId") (API.ScalarType "number")),
("insertedRows_Title", API.ColumnField (API.ColumnName "Title") (API.ScalarType "string")),
( "insertedRows_Artist",
API.RelField
( API.RelationshipField
(API.RelationshipName "Artist")
( emptyQuery
& API.qFields
?~ mkFieldsMap
[ ("ArtistId", API.ColumnField (API.ColumnName "ArtistId") $ API.ScalarType "number"),
("Name", API.ColumnField (API.ColumnName "Name") $ API.ScalarType "string")
]
)
)
)
]
}
]
_mrrRecordedRequest `shouldBe` Just (Mutation expectedRequest)
rowsResponse :: [[(API.FieldName, API.FieldValue)]] -> API.QueryResponse
rowsResponse rows = API.QueryResponse (Just $ HashMap.fromList <$> rows) Nothing

View File

@ -5,6 +5,7 @@ module Test.DataConnector.MockAgent.OrderBySpec (spec) where
--------------------------------------------------------------------------------
import Control.Lens ((.~), (?~))
import Data.Aeson qualified as Aeson
import Data.HashMap.Strict qualified as HashMap
import Data.List.NonEmpty qualified as NE
@ -19,6 +20,7 @@ import Harness.Yaml (shouldBeYaml)
import Hasura.Backends.DataConnector.API qualified as API
import Hasura.Prelude
import Language.GraphQL.Draft.Syntax.QQ qualified as G
import Test.DataConnector.MockAgent.TestHelpers
import Test.Hspec (SpecWith, describe, shouldBe)
--------------------------------------------------------------------------------
@ -111,25 +113,17 @@ tests _opts = describe "Order By Tests" $ do
_mrrRecordedRequest
`shouldBe` Just
( Query $
API.QueryRequest
{ _qrTable = API.TableName ("Album" :| []),
_qrTableRelationships = [],
_qrQuery =
API.Query
{ _qFields =
Just $
HashMap.fromList
[ (API.FieldName "AlbumId", API.ColumnField (API.ColumnName "AlbumId") $ API.ScalarType "number"),
(API.FieldName "Title", API.ColumnField (API.ColumnName "Title") $ API.ScalarType "string")
],
_qAggregates = Nothing,
_qLimit = Just 3,
_qOffset = Nothing,
_qWhere = Nothing,
_qOrderBy = Just (API.OrderBy mempty (API.OrderByElement [] (API.OrderByColumn (API.ColumnName "AlbumId")) API.Ascending :| []))
},
_qrForeach = Nothing
}
mkQueryRequest
(mkTableName "Album")
( emptyQuery
& API.qFields
?~ mkFieldsMap
[ ("AlbumId", API.ColumnField (API.ColumnName "AlbumId") $ API.ScalarType "number"),
("Title", API.ColumnField (API.ColumnName "Title") $ API.ScalarType "string")
]
& API.qLimit ?~ 3
& API.qOrderBy ?~ API.OrderBy mempty (API.OrderByElement [] (API.OrderByColumn (API.ColumnName "AlbumId")) API.Ascending :| [])
)
)
mockAgentGraphqlTest "can order by aggregates" $ \_testEnv performGraphqlRequest -> do
@ -162,59 +156,48 @@ tests _opts = describe "Order By Tests" $ do
_mrrRecordedRequest
`shouldBe` Just
( Query $
API.QueryRequest
{ _qrTable = API.TableName ("Artist" :| []),
_qrTableRelationships =
[ API.TableRelationships
{ _trSourceTable = API.TableName ("Artist" :| []),
_trRelationships =
HashMap.fromList
[ ( API.RelationshipName "Albums",
API.Relationship
{ _rTargetTable = API.TableName ("Album" :| []),
_rRelationshipType = API.ArrayRelationship,
_rColumnMapping = HashMap.fromList [(API.ColumnName "ArtistId", API.ColumnName "ArtistId")]
}
mkQueryRequest
(mkTableName "Artist")
( emptyQuery
& API.qFields ?~ mkFieldsMap [("Name", API.ColumnField (API.ColumnName "Name") (API.ScalarType "string"))]
& API.qLimit ?~ 2
& API.qOrderBy
?~ API.OrderBy
( HashMap.fromList
[ ( API.RelationshipName "Albums",
API.OrderByRelation Nothing mempty
)
]
)
( NE.fromList
[ API.OrderByElement [API.RelationshipName "Albums"] API.OrderByStarCountAggregate API.Ascending,
API.OrderByElement
[API.RelationshipName "Albums"]
( API.OrderBySingleColumnAggregate $
API.SingleColumnAggregate
(API.SingleColumnAggregateFunction [G.name|max|])
(API.ColumnName "AlbumId")
(API.ScalarType "number")
)
]
}
],
_qrQuery =
API.Query
{ _qFields =
Just $
HashMap.fromList
[ (API.FieldName "Name", API.ColumnField (API.ColumnName "Name") (API.ScalarType "string"))
],
_qAggregates = Nothing,
_qLimit = Just 2,
_qOffset = Nothing,
_qWhere = Nothing,
_qOrderBy =
Just $
API.OrderBy
( HashMap.fromList
[ ( API.RelationshipName "Albums",
API.OrderByRelation Nothing mempty
)
]
)
( NE.fromList
[ API.OrderByElement [API.RelationshipName "Albums"] API.OrderByStarCountAggregate API.Ascending,
API.OrderByElement
[API.RelationshipName "Albums"]
( API.OrderBySingleColumnAggregate $
API.SingleColumnAggregate
(API.SingleColumnAggregateFunction [G.name|max|])
(API.ColumnName "AlbumId")
(API.ScalarType "number")
)
API.Ascending
]
)
},
_qrForeach = Nothing
}
API.Ascending
]
)
)
& API.qrTableRelationships
.~ [ API.TableRelationships
{ _trSourceTable = mkTableName "Artist",
_trRelationships =
HashMap.fromList
[ ( API.RelationshipName "Albums",
API.Relationship
{ _rTargetTable = mkTableName "Album",
_rRelationshipType = API.ArrayRelationship,
_rColumnMapping = HashMap.fromList [(API.ColumnName "ArtistId", API.ColumnName "ArtistId")]
}
)
]
}
]
)
rowsResponse :: [[(API.FieldName, API.FieldValue)]] -> API.QueryResponse

View File

@ -5,6 +5,7 @@ module Test.DataConnector.MockAgent.QueryRelationshipsSpec (spec) where
--------------------------------------------------------------------------------
import Control.Lens ((.~), (?~))
import Data.Aeson qualified as Aeson
import Data.ByteString (ByteString)
import Data.HashMap.Strict qualified as HashMap
@ -19,6 +20,7 @@ import Harness.TestEnvironment (GlobalTestEnvironment, TestEnvironment)
import Harness.Yaml (shouldBeYaml)
import Hasura.Backends.DataConnector.API qualified as API
import Hasura.Prelude
import Test.DataConnector.MockAgent.TestHelpers
import Test.Hspec (SpecWith, describe, shouldBe)
--------------------------------------------------------------------------------
@ -145,18 +147,18 @@ tests _opts = describe "Object Relationships Tests" $ do
}
|]
let queryResponse =
rowsResponse
[ [ (API.FieldName "Name", API.mkColumnFieldValue $ Aeson.String "For Those About To Rock (We Salute You)"),
( API.FieldName "Genre",
mkRowsQueryResponse
[ [ ("Name", API.mkColumnFieldValue $ Aeson.String "For Those About To Rock (We Salute You)"),
( "Genre",
API.mkRelationshipFieldValue $
rowsResponse
[ [(API.FieldName "Name", API.mkColumnFieldValue $ Aeson.String "Rock")]
mkRowsQueryResponse
[ [("Name", API.mkColumnFieldValue $ Aeson.String "Rock")]
]
),
( API.FieldName "MediaType",
( "MediaType",
API.mkRelationshipFieldValue $
rowsResponse
[ [(API.FieldName "Name", API.mkColumnFieldValue $ Aeson.String "MPEG audio file")]
mkRowsQueryResponse
[ [("Name", API.mkColumnFieldValue $ Aeson.String "MPEG audio file")]
]
)
]
@ -179,83 +181,53 @@ tests _opts = describe "Object Relationships Tests" $ do
_mrrRecordedRequest
`shouldBe` Just
( Query $
API.QueryRequest
{ _qrTable = API.TableName ("Track" :| []),
_qrTableRelationships =
[ API.TableRelationships
{ _trSourceTable = API.TableName ("Track" :| []),
_trRelationships =
HashMap.fromList
[ ( API.RelationshipName "Genre",
API.Relationship
{ _rTargetTable = API.TableName ("Genre" :| []),
_rRelationshipType = API.ObjectRelationship,
_rColumnMapping = HashMap.fromList [(API.ColumnName "GenreId", API.ColumnName "GenreId")]
}
),
( API.RelationshipName "MediaType",
API.Relationship
{ _rTargetTable = API.TableName ("MediaType" :| []),
_rRelationshipType = API.ObjectRelationship,
_rColumnMapping =
HashMap.fromList
[(API.ColumnName "MediaTypeId", API.ColumnName "MediaTypeId")]
}
)
]
}
],
_qrQuery =
API.Query
{ _qFields =
Just $
HashMap.fromList
[ (API.FieldName "Name", API.ColumnField (API.ColumnName "Name") $ API.ScalarType "string"),
( API.FieldName "Genre",
API.RelField
( API.RelationshipField
(API.RelationshipName "Genre")
API.Query
{ _qFields =
Just $
HashMap.fromList
[ (API.FieldName "Name", API.ColumnField (API.ColumnName "Name") $ API.ScalarType "string")
],
_qAggregates = Nothing,
_qLimit = Nothing,
_qOffset = Nothing,
_qWhere = Nothing,
_qOrderBy = Nothing
}
)
),
( API.FieldName "MediaType",
API.RelField
( API.RelationshipField
(API.RelationshipName "MediaType")
API.Query
{ _qFields =
Just $
HashMap.fromList
[ (API.FieldName "Name", API.ColumnField (API.ColumnName "Name") $ API.ScalarType "string")
],
_qAggregates = Nothing,
_qLimit = Nothing,
_qOffset = Nothing,
_qWhere = Nothing,
_qOrderBy = Nothing
}
)
)
],
_qAggregates = Nothing,
_qLimit = Just 1,
_qOffset = Nothing,
_qWhere = Nothing,
_qOrderBy = Nothing
},
_qrForeach = Nothing
}
mkQueryRequest
(mkTableName "Track")
( emptyQuery
& API.qFields
?~ mkFieldsMap
[ ("Name", API.ColumnField (API.ColumnName "Name") $ API.ScalarType "string"),
( "Genre",
API.RelField
( API.RelationshipField
(API.RelationshipName "Genre")
(emptyQuery & API.qFields ?~ mkFieldsMap [("Name", API.ColumnField (API.ColumnName "Name") $ API.ScalarType "string")])
)
),
( "MediaType",
API.RelField
( API.RelationshipField
(API.RelationshipName "MediaType")
(emptyQuery & API.qFields ?~ mkFieldsMap [("Name", API.ColumnField (API.ColumnName "Name") $ API.ScalarType "string")])
)
)
]
& API.qLimit ?~ 1
)
& API.qrTableRelationships
.~ [ API.TableRelationships
{ _trSourceTable = mkTableName "Track",
_trRelationships =
HashMap.fromList
[ ( API.RelationshipName "Genre",
API.Relationship
{ _rTargetTable = mkTableName "Genre",
_rRelationshipType = API.ObjectRelationship,
_rColumnMapping = HashMap.fromList [(API.ColumnName "GenreId", API.ColumnName "GenreId")]
}
),
( API.RelationshipName "MediaType",
API.Relationship
{ _rTargetTable = mkTableName "MediaType",
_rRelationshipType = API.ObjectRelationship,
_rColumnMapping =
HashMap.fromList
[(API.ColumnName "MediaTypeId", API.ColumnName "MediaTypeId")]
}
)
]
}
]
)
mockAgentGraphqlTest "works with an order by that navigates relationships" $ \_testEnv performGraphqlRequest -> do
@ -274,19 +246,19 @@ tests _opts = describe "Object Relationships Tests" $ do
}
|]
let queryResponse =
rowsResponse
[ [ ( API.FieldName "Album",
mkRowsQueryResponse
[ [ ( "Album",
API.mkRelationshipFieldValue $
rowsResponse
[ [ ( API.FieldName "Artist",
mkRowsQueryResponse
[ [ ( "Artist",
API.mkRelationshipFieldValue $
rowsResponse
[[(API.FieldName "Name", API.mkColumnFieldValue $ Aeson.String "Zeca Pagodinho")]]
mkRowsQueryResponse
[[("Name", API.mkColumnFieldValue $ Aeson.String "Zeca Pagodinho")]]
)
]
]
),
(API.FieldName "Name", API.mkColumnFieldValue $ Aeson.String "Camarão que Dorme e Onda Leva")
("Name", API.mkColumnFieldValue $ Aeson.String "Camarão que Dorme e Onda Leva")
]
]
let mockConfig = Mock.chinookMock & mockQueryResponse queryResponse
@ -306,108 +278,83 @@ tests _opts = describe "Object Relationships Tests" $ do
_mrrRecordedRequest
`shouldBe` Just
( Query $
API.QueryRequest
{ _qrTable = API.TableName ("Track" :| []),
_qrTableRelationships =
[ API.TableRelationships
{ _trSourceTable = API.TableName ("Track" :| []),
_trRelationships =
HashMap.fromList
[ ( API.RelationshipName "Album",
API.Relationship
{ _rTargetTable = API.TableName ("Album" :| []),
_rRelationshipType = API.ObjectRelationship,
_rColumnMapping = HashMap.fromList [(API.ColumnName "AlbumId", API.ColumnName "AlbumId")]
}
)
]
},
API.TableRelationships
{ _trSourceTable = API.TableName ("Album" :| []),
_trRelationships =
HashMap.fromList
[ ( API.RelationshipName "Artist",
API.Relationship
{ _rTargetTable = API.TableName ("Artist" :| []),
_rRelationshipType = API.ObjectRelationship,
_rColumnMapping = HashMap.fromList [(API.ColumnName "ArtistId", API.ColumnName "ArtistId")]
}
)
]
}
],
_qrQuery =
API.Query
{ _qFields =
Just $
HashMap.fromList
[ (API.FieldName "Name", API.ColumnField (API.ColumnName "Name") $ API.ScalarType "string"),
( API.FieldName "Album",
API.RelField
( API.RelationshipField
(API.RelationshipName "Album")
API.Query
{ _qFields =
Just $
HashMap.fromList
[ ( API.FieldName "Artist",
API.RelField
( API.RelationshipField
(API.RelationshipName "Artist")
API.Query
{ _qFields =
Just $
HashMap.fromList
[ (API.FieldName "Name", API.ColumnField (API.ColumnName "Name") $ API.ScalarType "string")
],
_qAggregates = Nothing,
_qLimit = Nothing,
_qOffset = Nothing,
_qWhere = Nothing,
_qOrderBy = Nothing
}
)
)
],
_qAggregates = Nothing,
_qLimit = Nothing,
_qOffset = Nothing,
_qWhere = Nothing,
_qOrderBy = Nothing
}
)
)
],
_qAggregates = Nothing,
_qLimit = Just 1,
_qOffset = Nothing,
_qWhere = Nothing,
_qOrderBy =
Just $
API.OrderBy
( HashMap.fromList
[ ( API.RelationshipName "Album",
API.OrderByRelation
Nothing
( HashMap.fromList
[ ( API.RelationshipName "Artist",
API.OrderByRelation
Nothing
mempty
)
]
mkQueryRequest
(mkTableName "Track")
( emptyQuery
& API.qFields
?~ mkFieldsMap
[ ("Name", API.ColumnField (API.ColumnName "Name") $ API.ScalarType "string"),
( "Album",
API.RelField
( API.RelationshipField
(API.RelationshipName "Album")
( emptyQuery
& API.qFields
?~ mkFieldsMap
[ ( "Artist",
API.RelField
( API.RelationshipField
(API.RelationshipName "Artist")
(emptyQuery & API.qFields ?~ mkFieldsMap [("Name", API.ColumnField (API.ColumnName "Name") (API.ScalarType "string"))])
)
)
]
)
)
)
]
& API.qLimit ?~ 1
& API.qOrderBy
?~ API.OrderBy
( HashMap.fromList
[ ( API.RelationshipName "Album",
API.OrderByRelation
Nothing
( HashMap.fromList
[ ( API.RelationshipName "Artist",
API.OrderByRelation
Nothing
mempty
)
)
]
]
)
)
( NE.fromList
[ API.OrderByElement [API.RelationshipName "Album", API.RelationshipName "Artist"] (API.OrderByColumn (API.ColumnName "Name")) API.Descending,
API.OrderByElement [] (API.OrderByColumn (API.ColumnName "Name")) API.Ascending
]
)
},
_qrForeach = Nothing
}
]
)
( NE.fromList
[ API.OrderByElement [API.RelationshipName "Album", API.RelationshipName "Artist"] (API.OrderByColumn (API.ColumnName "Name")) API.Descending,
API.OrderByElement [] (API.OrderByColumn (API.ColumnName "Name")) API.Ascending
]
)
)
& API.qrTableRelationships
.~ [ API.TableRelationships
{ _trSourceTable = mkTableName "Track",
_trRelationships =
HashMap.fromList
[ ( API.RelationshipName "Album",
API.Relationship
{ _rTargetTable = mkTableName "Album",
_rRelationshipType = API.ObjectRelationship,
_rColumnMapping = HashMap.fromList [(API.ColumnName "AlbumId", API.ColumnName "AlbumId")]
}
)
]
},
API.TableRelationships
{ _trSourceTable = mkTableName "Album",
_trRelationships =
HashMap.fromList
[ ( API.RelationshipName "Artist",
API.Relationship
{ _rTargetTable = mkTableName "Artist",
_rRelationshipType = API.ObjectRelationship,
_rColumnMapping = HashMap.fromList [(API.ColumnName "ArtistId", API.ColumnName "ArtistId")]
}
)
]
}
]
)
mockAgentGraphqlTest "works with an order by that navigates a relationship with table permissions" $ \_testEnv performGraphqlRequest -> do
@ -421,8 +368,8 @@ tests _opts = describe "Object Relationships Tests" $ do
}
|]
let queryResponse =
rowsResponse
[ [ (API.FieldName "EmployeeId", API.mkColumnFieldValue $ Aeson.Number 3)
mkRowsQueryResponse
[ [ ("EmployeeId", API.mkColumnFieldValue $ Aeson.Number 3)
]
]
let mockConfig = Mock.chinookMock & mockQueryResponse queryResponse
@ -439,75 +386,63 @@ tests _opts = describe "Object Relationships Tests" $ do
_mrrRecordedRequest
`shouldBe` Just
( Query $
API.QueryRequest
{ _qrTable = API.TableName ("Employee" :| []),
_qrTableRelationships =
[ API.TableRelationships
{ _trSourceTable = API.TableName ("Customer" :| []),
_trRelationships =
HashMap.fromList
[ ( API.RelationshipName "SupportRep",
API.Relationship
{ _rTargetTable = API.TableName ("Employee" :| []),
_rRelationshipType = API.ObjectRelationship,
_rColumnMapping = HashMap.fromList [(API.ColumnName "SupportRepId", API.ColumnName "EmployeeId")]
}
)
]
},
API.TableRelationships
{ _trSourceTable = API.TableName ("Employee" :| []),
_trRelationships =
HashMap.fromList
[ ( API.RelationshipName "SupportRepForCustomers",
API.Relationship
{ _rTargetTable = API.TableName ("Customer" :| []),
_rRelationshipType = API.ArrayRelationship,
_rColumnMapping = HashMap.fromList [(API.ColumnName "EmployeeId", API.ColumnName "SupportRepId")]
}
)
]
}
],
_qrQuery =
API.Query
{ _qFields =
Just $
HashMap.fromList
[ (API.FieldName "EmployeeId", API.ColumnField (API.ColumnName "EmployeeId") $ API.ScalarType "number")
],
_qAggregates = Nothing,
_qLimit = Just 1,
_qOffset = Nothing,
_qWhere =
Just $
API.Exists (API.RelatedTable $ API.RelationshipName "SupportRepForCustomers") $
API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.ColumnName "Country") $ API.ScalarType "string")
(API.AnotherColumnComparison (API.ComparisonColumn API.QueryTable (API.ColumnName "Country") $ API.ScalarType "string")),
_qOrderBy =
Just $
API.OrderBy
( HashMap.fromList
[ ( API.RelationshipName "SupportRepForCustomers",
API.OrderByRelation
( Just $
API.Exists (API.RelatedTable $ API.RelationshipName "SupportRep") $
API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.ColumnName "Country") $ API.ScalarType "string")
(API.AnotherColumnComparison (API.ComparisonColumn API.QueryTable (API.ColumnName "Country") $ API.ScalarType "string"))
)
mempty
)
]
mkQueryRequest
(mkTableName "Employee")
( emptyQuery
& API.qFields ?~ mkFieldsMap [("EmployeeId", API.ColumnField (API.ColumnName "EmployeeId") $ API.ScalarType "number")]
& API.qLimit ?~ 1
& API.qWhere
?~ API.Exists
(API.RelatedTable $ API.RelationshipName "SupportRepForCustomers")
( API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.ColumnName "Country") $ API.ScalarType "string")
(API.AnotherColumnComparison (API.ComparisonColumn API.QueryTable (API.ColumnName "Country") $ API.ScalarType "string"))
)
& API.qOrderBy
?~ API.OrderBy
( HashMap.fromList
[ ( API.RelationshipName "SupportRepForCustomers",
API.OrderByRelation
( Just $
API.Exists (API.RelatedTable $ API.RelationshipName "SupportRep") $
API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.ColumnName "Country") $ API.ScalarType "string")
(API.AnotherColumnComparison (API.ComparisonColumn API.QueryTable (API.ColumnName "Country") $ API.ScalarType "string"))
)
mempty
)
(API.OrderByElement [API.RelationshipName "SupportRepForCustomers"] API.OrderByStarCountAggregate API.Descending :| [])
},
_qrForeach = Nothing
}
]
)
(API.OrderByElement [API.RelationshipName "SupportRepForCustomers"] API.OrderByStarCountAggregate API.Descending :| [])
)
& API.qrTableRelationships
.~ [ API.TableRelationships
{ _trSourceTable = mkTableName "Customer",
_trRelationships =
HashMap.fromList
[ ( API.RelationshipName "SupportRep",
API.Relationship
{ _rTargetTable = mkTableName "Employee",
_rRelationshipType = API.ObjectRelationship,
_rColumnMapping = HashMap.fromList [(API.ColumnName "SupportRepId", API.ColumnName "EmployeeId")]
}
)
]
},
API.TableRelationships
{ _trSourceTable = mkTableName "Employee",
_trRelationships =
HashMap.fromList
[ ( API.RelationshipName "SupportRepForCustomers",
API.Relationship
{ _rTargetTable = mkTableName "Customer",
_rRelationshipType = API.ArrayRelationship,
_rColumnMapping = HashMap.fromList [(API.ColumnName "EmployeeId", API.ColumnName "SupportRepId")]
}
)
]
}
]
)
rowsResponse :: [[(API.FieldName, API.FieldValue)]] -> API.QueryResponse
rowsResponse rows = API.QueryResponse (Just $ HashMap.fromList <$> rows) Nothing

View File

@ -5,7 +5,7 @@ module Test.DataConnector.MockAgent.RemoteRelationshipsSpec (spec) where
--------------------------------------------------------------------------------
import Control.Lens ((.~), _Just)
import Control.Lens ((.~), (?~), _Just)
import Data.Aeson qualified as Aeson
import Data.HashMap.Strict qualified as HashMap
import Data.List.NonEmpty qualified as NE
@ -24,6 +24,7 @@ import Harness.TestEnvironment (GlobalTestEnvironment, TestEnvironment)
import Harness.Yaml (shouldBeYaml, shouldReturnYaml)
import Hasura.Backends.DataConnector.API qualified as API
import Hasura.Prelude
import Test.DataConnector.MockAgent.TestHelpers
import Test.Hspec (HasCallStack, SpecWith, describe, it, shouldBe)
--------------------------------------------------------------------------------
@ -211,27 +212,27 @@ tests _opts = do
}
|]
let queryResponse =
mkRowsResponse
[ [ ( API.FieldName "query",
mkRowsQueryResponse
[ [ ( "query",
API.mkRelationshipFieldValue $
mkRowsResponse
[ [ (API.FieldName "AlbumId", API.mkColumnFieldValue $ Aeson.Number 1),
(API.FieldName "Title", API.mkColumnFieldValue $ Aeson.String "For Those About To Rock We Salute You")
mkRowsQueryResponse
[ [ ("AlbumId", API.mkColumnFieldValue $ Aeson.Number 1),
("Title", API.mkColumnFieldValue $ Aeson.String "For Those About To Rock We Salute You")
],
[ (API.FieldName "AlbumId", API.mkColumnFieldValue $ Aeson.Number 4),
(API.FieldName "Title", API.mkColumnFieldValue $ Aeson.String "Let There Be Rock")
[ ("AlbumId", API.mkColumnFieldValue $ Aeson.Number 4),
("Title", API.mkColumnFieldValue $ Aeson.String "Let There Be Rock")
]
]
)
],
[ ( API.FieldName "query",
[ ( "query",
API.mkRelationshipFieldValue $
mkRowsResponse
[ [ (API.FieldName "AlbumId", API.mkColumnFieldValue $ Aeson.Number 2),
(API.FieldName "Title", API.mkColumnFieldValue $ Aeson.String "Balls to the Wall")
mkRowsQueryResponse
[ [ ("AlbumId", API.mkColumnFieldValue $ Aeson.Number 2),
("Title", API.mkColumnFieldValue $ Aeson.String "Balls to the Wall")
],
[ (API.FieldName "AlbumId", API.mkColumnFieldValue $ Aeson.Number 3),
(API.FieldName "Title", API.mkColumnFieldValue $ Aeson.String "Restless and Wild")
[ ("AlbumId", API.mkColumnFieldValue $ Aeson.Number 3),
("Title", API.mkColumnFieldValue $ Aeson.String "Restless and Wild")
]
]
)
@ -264,30 +265,20 @@ tests _opts = do
_mrrRecordedRequest
`shouldBe` Just
( Query $
API.QueryRequest
{ _qrTable = API.TableName ("Album" :| []),
_qrTableRelationships = [],
_qrQuery =
API.Query
{ _qFields =
Just $
HashMap.fromList
[ (API.FieldName "AlbumId", API.ColumnField (API.ColumnName "AlbumId") $ API.ScalarType "number"),
(API.FieldName "Title", API.ColumnField (API.ColumnName "Title") $ API.ScalarType "string")
],
_qAggregates = Nothing,
_qLimit = Nothing,
_qOffset = Nothing,
_qWhere = Nothing,
_qOrderBy = Nothing
},
_qrForeach =
Just $
NonEmpty.fromList
[ HashMap.fromList [(API.ColumnName "ArtistId", API.ScalarValue (Aeson.Number 1) (API.ScalarType "number"))],
HashMap.fromList [(API.ColumnName "ArtistId", API.ScalarValue (Aeson.Number 2) (API.ScalarType "number"))]
mkQueryRequest
(mkTableName "Album")
( emptyQuery
& API.qFields
?~ mkFieldsMap
[ ("AlbumId", API.ColumnField (API.ColumnName "AlbumId") $ API.ScalarType "number"),
("Title", API.ColumnField (API.ColumnName "Title") $ API.ScalarType "string")
]
}
)
& API.qrForeach
?~ NonEmpty.fromList
[ HashMap.fromList [(API.ColumnName "ArtistId", API.ScalarValue (Aeson.Number 1) (API.ScalarType "number"))],
HashMap.fromList [(API.ColumnName "ArtistId", API.ScalarValue (Aeson.Number 2) (API.ScalarType "number"))]
]
)
mockAgentGraphqlTest "can act as the target of a remote object relationship" $ \testEnv performGraphqlRequest -> do
@ -307,30 +298,30 @@ tests _opts = do
}
|]
let queryResponse =
mkRowsResponse
[ [ ( API.FieldName "query",
mkRowsQueryResponse
[ [ ( "query",
API.mkRelationshipFieldValue $
mkRowsResponse
[ [ (API.FieldName "AlbumId", API.mkColumnFieldValue $ Aeson.Number 3),
(API.FieldName "Title", API.mkColumnFieldValue $ Aeson.String "Restless and Wild")
mkRowsQueryResponse
[ [ ("AlbumId", API.mkColumnFieldValue $ Aeson.Number 3),
("Title", API.mkColumnFieldValue $ Aeson.String "Restless and Wild")
]
]
)
],
[ ( API.FieldName "query",
[ ( "query",
API.mkRelationshipFieldValue $
mkRowsResponse
[ [ (API.FieldName "AlbumId", API.mkColumnFieldValue $ Aeson.Number 1),
(API.FieldName "Title", API.mkColumnFieldValue $ Aeson.String "For Those About To Rock We Salute You")
mkRowsQueryResponse
[ [ ("AlbumId", API.mkColumnFieldValue $ Aeson.Number 1),
("Title", API.mkColumnFieldValue $ Aeson.String "For Those About To Rock We Salute You")
]
]
)
],
[ ( API.FieldName "query",
[ ( "query",
API.mkRelationshipFieldValue $
mkRowsResponse
[ [ (API.FieldName "AlbumId", API.mkColumnFieldValue $ Aeson.Number 4),
(API.FieldName "Title", API.mkColumnFieldValue $ Aeson.String "Let There Be Rock")
mkRowsQueryResponse
[ [ ("AlbumId", API.mkColumnFieldValue $ Aeson.Number 4),
("Title", API.mkColumnFieldValue $ Aeson.String "Let There Be Rock")
]
]
)
@ -364,31 +355,21 @@ tests _opts = do
_mrrRecordedRequest
`shouldBe` Just
( Query $
API.QueryRequest
{ _qrTable = API.TableName ("Album" :| []),
_qrTableRelationships = [],
_qrQuery =
API.Query
{ _qFields =
Just $
HashMap.fromList
[ (API.FieldName "AlbumId", API.ColumnField (API.ColumnName "AlbumId") $ API.ScalarType "number"),
(API.FieldName "Title", API.ColumnField (API.ColumnName "Title") $ API.ScalarType "string")
],
_qAggregates = Nothing,
_qLimit = Nothing,
_qOffset = Nothing,
_qWhere = Nothing,
_qOrderBy = Nothing
},
_qrForeach =
Just $
NonEmpty.fromList
[ HashMap.fromList [(API.ColumnName "AlbumId", API.ScalarValue (Aeson.Number 3) (API.ScalarType "number"))],
HashMap.fromList [(API.ColumnName "AlbumId", API.ScalarValue (Aeson.Number 1) (API.ScalarType "number"))],
HashMap.fromList [(API.ColumnName "AlbumId", API.ScalarValue (Aeson.Number 4) (API.ScalarType "number"))]
mkQueryRequest
(mkTableName "Album")
( emptyQuery
& API.qFields
?~ mkFieldsMap
[ ("AlbumId", API.ColumnField (API.ColumnName "AlbumId") $ API.ScalarType "number"),
("Title", API.ColumnField (API.ColumnName "Title") $ API.ScalarType "string")
]
}
)
& API.qrForeach
?~ NonEmpty.fromList
[ HashMap.fromList [(API.ColumnName "AlbumId", API.ScalarValue (Aeson.Number 3) (API.ScalarType "number"))],
HashMap.fromList [(API.ColumnName "AlbumId", API.ScalarValue (Aeson.Number 1) (API.ScalarType "number"))],
HashMap.fromList [(API.ColumnName "AlbumId", API.ScalarValue (Aeson.Number 4) (API.ScalarType "number"))]
]
)
mockAgentGraphqlTest "can act as the target of an aggregation over a remote array relationship" $ \testEnv performGraphqlRequest -> do
@ -413,32 +394,32 @@ tests _opts = do
}
|]
let queryResponse =
mkRowsResponse
[ [ ( API.FieldName "query",
mkRowsQueryResponse
[ [ ( "query",
API.mkRelationshipFieldValue $
mkQueryResponse
[ [ (API.FieldName "nodes_AlbumId", API.mkColumnFieldValue $ Aeson.Number 1),
(API.FieldName "nodes_Title", API.mkColumnFieldValue $ Aeson.String "For Those About To Rock We Salute You")
[ [ ("nodes_AlbumId", API.mkColumnFieldValue $ Aeson.Number 1),
("nodes_Title", API.mkColumnFieldValue $ Aeson.String "For Those About To Rock We Salute You")
],
[ (API.FieldName "nodes_AlbumId", API.mkColumnFieldValue $ Aeson.Number 4),
(API.FieldName "nodes_Title", API.mkColumnFieldValue $ Aeson.String "Let There Be Rock")
[ ("nodes_AlbumId", API.mkColumnFieldValue $ Aeson.Number 4),
("nodes_Title", API.mkColumnFieldValue $ Aeson.String "Let There Be Rock")
]
]
[ (API.FieldName "aggregate_count", Aeson.Number 2)
[ ("aggregate_count", Aeson.Number 2)
]
)
],
[ ( API.FieldName "query",
[ ( "query",
API.mkRelationshipFieldValue $
mkQueryResponse
[ [ (API.FieldName "nodes_AlbumId", API.mkColumnFieldValue $ Aeson.Number 2),
(API.FieldName "nodes_Title", API.mkColumnFieldValue $ Aeson.String "Balls to the Wall")
[ [ ("nodes_AlbumId", API.mkColumnFieldValue $ Aeson.Number 2),
("nodes_Title", API.mkColumnFieldValue $ Aeson.String "Balls to the Wall")
],
[ (API.FieldName "nodes_AlbumId", API.mkColumnFieldValue $ Aeson.Number 3),
(API.FieldName "nodes_Title", API.mkColumnFieldValue $ Aeson.String "Restless and Wild")
[ ("nodes_AlbumId", API.mkColumnFieldValue $ Aeson.Number 3),
("nodes_Title", API.mkColumnFieldValue $ Aeson.String "Restless and Wild")
]
]
[ (API.FieldName "aggregate_count", Aeson.Number 2)
[ ("aggregate_count", Aeson.Number 2)
]
)
]
@ -476,41 +457,23 @@ tests _opts = do
_mrrRecordedRequest
`shouldBe` Just
( Query $
API.QueryRequest
{ _qrTable = API.TableName ("Album" :| []),
_qrTableRelationships = [],
_qrQuery =
API.Query
{ _qFields =
Just $
HashMap.fromList
[ (API.FieldName "nodes_AlbumId", API.ColumnField (API.ColumnName "AlbumId") $ API.ScalarType "number"),
(API.FieldName "nodes_Title", API.ColumnField (API.ColumnName "Title") $ API.ScalarType "string")
],
_qAggregates =
Just $
HashMap.fromList
[(API.FieldName "aggregate_count", API.StarCount)],
_qLimit = Nothing,
_qOffset = Nothing,
_qWhere = Nothing,
_qOrderBy = Nothing
},
_qrForeach =
Just $
NonEmpty.fromList
[ HashMap.fromList [(API.ColumnName "ArtistId", API.ScalarValue (Aeson.Number 1) (API.ScalarType "number"))],
HashMap.fromList [(API.ColumnName "ArtistId", API.ScalarValue (Aeson.Number 2) (API.ScalarType "number"))]
mkQueryRequest
(mkTableName "Album")
( emptyQuery
& API.qFields
?~ mkFieldsMap
[ ("nodes_AlbumId", API.ColumnField (API.ColumnName "AlbumId") $ API.ScalarType "number"),
("nodes_Title", API.ColumnField (API.ColumnName "Title") $ API.ScalarType "string")
]
}
& API.qAggregates ?~ mkFieldsMap [("aggregate_count", API.StarCount)]
)
& API.qrForeach
?~ NonEmpty.fromList
[ HashMap.fromList [(API.ColumnName "ArtistId", API.ScalarValue (Aeson.Number 1) (API.ScalarType "number"))],
HashMap.fromList [(API.ColumnName "ArtistId", API.ScalarValue (Aeson.Number 2) (API.ScalarType "number"))]
]
)
mkRowsResponse :: [[(API.FieldName, API.FieldValue)]] -> API.QueryResponse
mkRowsResponse rows = API.QueryResponse (Just $ HashMap.fromList <$> rows) Nothing
mkQueryResponse :: [[(API.FieldName, API.FieldValue)]] -> [(API.FieldName, Aeson.Value)] -> API.QueryResponse
mkQueryResponse rows aggregates = API.QueryResponse (Just $ HashMap.fromList <$> rows) (Just $ HashMap.fromList aggregates)
errorTests :: Fixture.Options -> SpecWith (TestEnvironment, Mock.MockAgentEnvironment)
errorTests opts = do
it "creating a remote relationship returns an error when it is unsupported by the target" $ \(testEnv, _) -> do

View File

@ -0,0 +1,40 @@
module Test.DataConnector.MockAgent.TestHelpers
( mkTableName,
mkQueryRequest,
emptyQuery,
emptyMutationRequest,
mkRowsQueryResponse,
mkAggregatesQueryResponse,
mkQueryResponse,
mkFieldsMap,
)
where
import Data.Aeson qualified as Aeson
import Data.HashMap.Strict qualified as HashMap
import Hasura.Backends.DataConnector.API qualified as API
import Hasura.Prelude
mkTableName :: Text -> API.TableName
mkTableName name = API.TableName (name :| [])
mkQueryRequest :: API.TableName -> API.Query -> API.QueryRequest
mkQueryRequest tableName query = API.QueryRequest tableName [] query Nothing
emptyQuery :: API.Query
emptyQuery = API.Query Nothing Nothing Nothing Nothing Nothing Nothing Nothing
emptyMutationRequest :: API.MutationRequest
emptyMutationRequest = API.MutationRequest [] [] []
mkRowsQueryResponse :: [[(Text, API.FieldValue)]] -> API.QueryResponse
mkRowsQueryResponse rows = API.QueryResponse (Just $ mkFieldsMap <$> rows) Nothing
mkAggregatesQueryResponse :: [(Text, Aeson.Value)] -> API.QueryResponse
mkAggregatesQueryResponse aggregates = API.QueryResponse Nothing (Just $ mkFieldsMap aggregates)
mkQueryResponse :: [[(Text, API.FieldValue)]] -> [(Text, Aeson.Value)] -> API.QueryResponse
mkQueryResponse rows aggregates = API.QueryResponse (Just $ mkFieldsMap <$> rows) (Just $ mkFieldsMap aggregates)
mkFieldsMap :: [(Text, v)] -> HashMap API.FieldName v
mkFieldsMap = HashMap.fromList . fmap (first API.FieldName)

View File

@ -5,8 +5,8 @@ module Test.DataConnector.MockAgent.TransformedConfigurationSpec (spec) where
--------------------------------------------------------------------------------
import Control.Lens ((?~))
import Data.Aeson qualified as Aeson
import Data.HashMap.Strict qualified as HashMap
import Data.List.NonEmpty qualified as NE
import Harness.Backend.DataConnector.Mock (AgentRequest (..), MockRequestResults (..), mockAgentGraphqlTest, mockQueryResponse)
import Harness.Backend.DataConnector.Mock qualified as Mock
@ -18,6 +18,7 @@ import Harness.TestEnvironment (GlobalTestEnvironment, TestEnvironment)
import Harness.Yaml (shouldBeYaml)
import Hasura.Backends.DataConnector.API qualified as API
import Hasura.Prelude
import Test.DataConnector.MockAgent.TestHelpers
import Test.Hspec (SpecWith, describe, shouldBe)
--------------------------------------------------------------------------------
@ -113,9 +114,9 @@ tests _opts = describe "Transformed Configuration Tests" $ do
}
|]
let queryResponse =
rowsResponse
[ [ (API.FieldName "id", API.mkColumnFieldValue $ Aeson.Number 1),
(API.FieldName "title", API.mkColumnFieldValue $ Aeson.String "For Those About To Rock We Salute You")
mkRowsQueryResponse
[ [ ("id", API.mkColumnFieldValue $ Aeson.Number 1),
("title", API.mkColumnFieldValue $ Aeson.String "For Those About To Rock We Salute You")
]
]
let mockConfig = Mock.chinookMock & mockQueryResponse queryResponse
@ -133,25 +134,16 @@ tests _opts = describe "Transformed Configuration Tests" $ do
_mrrRecordedRequest
`shouldBe` Just
( Query $
API.QueryRequest
{ _qrTable = API.TableName ("Album" :| []),
_qrTableRelationships = [],
_qrQuery =
API.Query
{ _qFields =
Just $
HashMap.fromList
[ (API.FieldName "id", API.ColumnField (API.ColumnName "AlbumId") $ API.ScalarType "number"),
(API.FieldName "title", API.ColumnField (API.ColumnName "Title") $ API.ScalarType "string")
],
_qAggregates = Nothing,
_qLimit = Just 1,
_qOffset = Nothing,
_qWhere = Nothing,
_qOrderBy = Nothing
},
_qrForeach = Nothing
}
mkQueryRequest
(mkTableName "Album")
( emptyQuery
& API.qFields
?~ mkFieldsMap
[ ("id", API.ColumnField (API.ColumnName "AlbumId") $ API.ScalarType "number"),
("title", API.ColumnField (API.ColumnName "Title") $ API.ScalarType "string")
]
& API.qLimit ?~ 1
)
)
Aeson.toJSON _mrrRecordedRequestConfig
@ -161,6 +153,3 @@ tests _opts = describe "Transformed Configuration Tests" $ do
env: "bar env default"
session: "foo session default"
|]
rowsResponse :: [[(API.FieldName, API.FieldValue)]] -> API.QueryResponse
rowsResponse rows = API.QueryResponse (Just $ HashMap.fromList <$> rows) Nothing

View File

@ -3,6 +3,7 @@ module Test.DataConnector.MockAgent.UpdateMutationsSpec
)
where
import Control.Lens ((.~), (?~))
import Data.Aeson qualified as Aeson
import Data.ByteString (ByteString)
import Data.HashMap.Strict qualified as HashMap
@ -18,6 +19,7 @@ import Harness.Yaml
import Hasura.Backends.DataConnector.API qualified as API
import Hasura.Prelude
import Language.GraphQL.Draft.Syntax.QQ qualified as G
import Test.DataConnector.MockAgent.TestHelpers
import Test.Hspec
--------------------------------------------------------------------------------
@ -127,35 +129,35 @@ tests _opts = do
{ API._morAffectedRows = 3,
API._morReturning =
Just
[ HashMap.fromList
[ (API.FieldName "updatedRows_TrackId", API.mkColumnFieldValue $ Aeson.Number 3),
(API.FieldName "updatedRows_Name", API.mkColumnFieldValue $ Aeson.String "Another Name"),
( API.FieldName "updatedRows_Genre",
[ mkFieldsMap
[ ("updatedRows_TrackId", API.mkColumnFieldValue $ Aeson.Number 3),
("updatedRows_Name", API.mkColumnFieldValue $ Aeson.String "Another Name"),
( "updatedRows_Genre",
API.mkRelationshipFieldValue $
rowsResponse
[ [ (API.FieldName "Name", API.mkColumnFieldValue $ Aeson.String "Rock")
mkRowsQueryResponse
[ [ ("Name", API.mkColumnFieldValue $ Aeson.String "Rock")
]
]
)
],
HashMap.fromList
[ (API.FieldName "updatedRows_TrackId", API.mkColumnFieldValue $ Aeson.Number 4),
(API.FieldName "updatedRows_Name", API.mkColumnFieldValue $ Aeson.String "Another Name"),
( API.FieldName "updatedRows_Genre",
mkFieldsMap
[ ("updatedRows_TrackId", API.mkColumnFieldValue $ Aeson.Number 4),
("updatedRows_Name", API.mkColumnFieldValue $ Aeson.String "Another Name"),
( "updatedRows_Genre",
API.mkRelationshipFieldValue $
rowsResponse
[ [ (API.FieldName "Name", API.mkColumnFieldValue $ Aeson.String "Rock")
mkRowsQueryResponse
[ [ ("Name", API.mkColumnFieldValue $ Aeson.String "Rock")
]
]
)
],
HashMap.fromList
[ (API.FieldName "updatedRows_TrackId", API.mkColumnFieldValue $ Aeson.Number 5),
(API.FieldName "updatedRows_Name", API.mkColumnFieldValue $ Aeson.String "Another Name"),
( API.FieldName "updatedRows_Genre",
mkFieldsMap
[ ("updatedRows_TrackId", API.mkColumnFieldValue $ Aeson.Number 5),
("updatedRows_Name", API.mkColumnFieldValue $ Aeson.String "Another Name"),
( "updatedRows_Genre",
API.mkRelationshipFieldValue $
rowsResponse
[ [ (API.FieldName "Name", API.mkColumnFieldValue $ Aeson.String "Rock")
mkRowsQueryResponse
[ [ ("Name", API.mkColumnFieldValue $ Aeson.String "Rock")
]
]
)
@ -188,91 +190,78 @@ tests _opts = do
|]
let expectedRequest =
API.MutationRequest
{ API._mrTableRelationships =
[ API.TableRelationships
{ API._trSourceTable = API.TableName ("Track" :| []),
API._trRelationships =
HashMap.fromList
[ ( API.RelationshipName "Genre",
API.Relationship
{ API._rTargetTable = API.TableName ("Genre" :| []),
API._rRelationshipType = API.ObjectRelationship,
API._rColumnMapping = HashMap.fromList [(API.ColumnName "GenreId", API.ColumnName "GenreId")]
}
)
]
}
],
API._mrInsertSchema = [],
API._mrOperations =
[ API.UpdateOperation $
API.UpdateMutationOperation
{ API._umoTable = API.TableName ("Track" :| []),
API._umoUpdates =
[ API.SetColumn $
API.RowColumnOperatorValue
{ API._rcovColumn = API.ColumnName "Name",
API._rcovValue = Aeson.String "Another Name",
API._rcovValueType = API.ScalarType "string"
},
API.CustomUpdateColumnOperator (API.UpdateColumnOperatorName [G.name|inc|]) $
API.RowColumnOperatorValue
{ API._rcovColumn = API.ColumnName "Milliseconds",
API._rcovValue = Aeson.Number 1000,
API._rcovValueType = API.ScalarType "number"
},
API.SetColumn $
API.RowColumnOperatorValue
{ API._rcovColumn = API.ColumnName "AlbumId",
API._rcovValue = Aeson.Number 3,
API._rcovValueType = API.ScalarType "number"
}
],
API._umoWhere =
Just $
API.And
[ API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.ColumnName "AlbumId") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (Aeson.Number 3) (API.ScalarType "number")),
API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.ColumnName "GenreId") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (Aeson.Number 1) (API.ScalarType "number"))
],
API._umoPostUpdateCheck =
Just $
API.ApplyBinaryComparisonOperator
API.GreaterThan
(API.ComparisonColumn API.CurrentTable (API.ColumnName "UnitPrice") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (Aeson.Number 0) (API.ScalarType "number")),
API._umoReturningFields =
HashMap.fromList
[ (API.FieldName "updatedRows_TrackId", API.ColumnField (API.ColumnName "TrackId") (API.ScalarType "number")),
(API.FieldName "updatedRows_Name", API.ColumnField (API.ColumnName "Name") (API.ScalarType "string")),
( API.FieldName "updatedRows_Genre",
API.RelField
( API.RelationshipField
(API.RelationshipName "Genre")
API.Query
{ _qFields =
Just $
HashMap.fromList
[ (API.FieldName "Name", API.ColumnField (API.ColumnName "Name") $ API.ScalarType "string")
],
_qAggregates = Nothing,
_qLimit = Nothing,
_qOffset = Nothing,
_qWhere = Nothing,
_qOrderBy = Nothing
}
)
)
]
}
]
}
emptyMutationRequest
& API.mrTableRelationships
.~ [ API.TableRelationships
{ API._trSourceTable = mkTableName "Track",
API._trRelationships =
HashMap.fromList
[ ( API.RelationshipName "Genre",
API.Relationship
{ API._rTargetTable = mkTableName "Genre",
API._rRelationshipType = API.ObjectRelationship,
API._rColumnMapping = HashMap.fromList [(API.ColumnName "GenreId", API.ColumnName "GenreId")]
}
)
]
}
]
& API.mrOperations
.~ [ API.UpdateOperation $
API.UpdateMutationOperation
{ API._umoTable = mkTableName "Track",
API._umoUpdates =
[ API.SetColumn $
API.RowColumnOperatorValue
{ API._rcovColumn = API.ColumnName "Name",
API._rcovValue = Aeson.String "Another Name",
API._rcovValueType = API.ScalarType "string"
},
API.CustomUpdateColumnOperator (API.UpdateColumnOperatorName [G.name|inc|]) $
API.RowColumnOperatorValue
{ API._rcovColumn = API.ColumnName "Milliseconds",
API._rcovValue = Aeson.Number 1000,
API._rcovValueType = API.ScalarType "number"
},
API.SetColumn $
API.RowColumnOperatorValue
{ API._rcovColumn = API.ColumnName "AlbumId",
API._rcovValue = Aeson.Number 3,
API._rcovValueType = API.ScalarType "number"
}
],
API._umoWhere =
Just $
API.And
[ API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.ColumnName "AlbumId") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (Aeson.Number 3) (API.ScalarType "number")),
API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.ColumnName "GenreId") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (Aeson.Number 1) (API.ScalarType "number"))
],
API._umoPostUpdateCheck =
Just $
API.ApplyBinaryComparisonOperator
API.GreaterThan
(API.ComparisonColumn API.CurrentTable (API.ColumnName "UnitPrice") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (Aeson.Number 0) (API.ScalarType "number")),
API._umoReturningFields =
mkFieldsMap
[ ("updatedRows_TrackId", API.ColumnField (API.ColumnName "TrackId") (API.ScalarType "number")),
("updatedRows_Name", API.ColumnField (API.ColumnName "Name") (API.ScalarType "string")),
( "updatedRows_Genre",
API.RelField
( API.RelationshipField
(API.RelationshipName "Genre")
(emptyQuery & API.qFields ?~ mkFieldsMap [("Name", API.ColumnField (API.ColumnName "Name") $ API.ScalarType "string")])
)
)
]
}
]
_mrrRecordedRequest `shouldBe` Just (Mutation expectedRequest)
mockAgentGraphqlTest "update_many rows with update permissions" $ \_testEnv performGraphqlRequest -> do
@ -301,13 +290,13 @@ tests _opts = do
{ API._morAffectedRows = 1,
API._morReturning =
Just
[ HashMap.fromList
[ (API.FieldName "updatedRows_TrackId", API.mkColumnFieldValue $ Aeson.Number 3),
(API.FieldName "updatedRows_Name", API.mkColumnFieldValue $ Aeson.String "Another Name"),
( API.FieldName "updatedRows_Genre",
[ mkFieldsMap
[ ("updatedRows_TrackId", API.mkColumnFieldValue $ Aeson.Number 3),
("updatedRows_Name", API.mkColumnFieldValue $ Aeson.String "Another Name"),
( "updatedRows_Genre",
API.mkRelationshipFieldValue $
rowsResponse
[ [ (API.FieldName "Name", API.mkColumnFieldValue $ Aeson.String "Rock")
mkRowsQueryResponse
[ [ ("Name", API.mkColumnFieldValue $ Aeson.String "Rock")
]
]
)
@ -318,24 +307,24 @@ tests _opts = do
{ API._morAffectedRows = 2,
API._morReturning =
Just
[ HashMap.fromList
[ (API.FieldName "updatedRows_TrackId", API.mkColumnFieldValue $ Aeson.Number 4),
(API.FieldName "updatedRows_Name", API.mkColumnFieldValue $ Aeson.String "Better Name"),
( API.FieldName "updatedRows_Genre",
[ mkFieldsMap
[ ("updatedRows_TrackId", API.mkColumnFieldValue $ Aeson.Number 4),
("updatedRows_Name", API.mkColumnFieldValue $ Aeson.String "Better Name"),
( "updatedRows_Genre",
API.mkRelationshipFieldValue $
rowsResponse
[ [ (API.FieldName "Name", API.mkColumnFieldValue $ Aeson.String "Rock")
mkRowsQueryResponse
[ [ ("Name", API.mkColumnFieldValue $ Aeson.String "Rock")
]
]
)
],
HashMap.fromList
[ (API.FieldName "updatedRows_TrackId", API.mkColumnFieldValue $ Aeson.Number 5),
(API.FieldName "updatedRows_Name", API.mkColumnFieldValue $ Aeson.String "Better Name"),
( API.FieldName "updatedRows_Genre",
mkFieldsMap
[ ("updatedRows_TrackId", API.mkColumnFieldValue $ Aeson.Number 5),
("updatedRows_Name", API.mkColumnFieldValue $ Aeson.String "Better Name"),
( "updatedRows_Genre",
API.mkRelationshipFieldValue $
rowsResponse
[ [ (API.FieldName "Name", API.mkColumnFieldValue $ Aeson.String "Rock")
mkRowsQueryResponse
[ [ ("Name", API.mkColumnFieldValue $ Aeson.String "Rock")
]
]
)
@ -376,126 +365,110 @@ tests _opts = do
(API.ComparisonColumn API.CurrentTable (API.ColumnName "UnitPrice") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (Aeson.Number 0) (API.ScalarType "number"))
let sharedReturning =
HashMap.fromList
[ (API.FieldName "updatedRows_TrackId", API.ColumnField (API.ColumnName "TrackId") (API.ScalarType "number")),
(API.FieldName "updatedRows_Name", API.ColumnField (API.ColumnName "Name") (API.ScalarType "string")),
( API.FieldName "updatedRows_Genre",
mkFieldsMap
[ ("updatedRows_TrackId", API.ColumnField (API.ColumnName "TrackId") (API.ScalarType "number")),
("updatedRows_Name", API.ColumnField (API.ColumnName "Name") (API.ScalarType "string")),
( "updatedRows_Genre",
API.RelField
( API.RelationshipField
(API.RelationshipName "Genre")
API.Query
{ _qFields =
Just $
HashMap.fromList
[ (API.FieldName "Name", API.ColumnField (API.ColumnName "Name") $ API.ScalarType "string")
],
_qAggregates = Nothing,
_qLimit = Nothing,
_qOffset = Nothing,
_qWhere = Nothing,
_qOrderBy = Nothing
}
(emptyQuery & API.qFields ?~ mkFieldsMap [("Name", API.ColumnField (API.ColumnName "Name") $ API.ScalarType "string")])
)
)
]
let expectedRequest =
API.MutationRequest
{ API._mrTableRelationships =
[ API.TableRelationships
{ API._trSourceTable = API.TableName ("Track" :| []),
API._trRelationships =
HashMap.fromList
[ ( API.RelationshipName "Genre",
API.Relationship
{ API._rTargetTable = API.TableName ("Genre" :| []),
API._rRelationshipType = API.ObjectRelationship,
API._rColumnMapping = HashMap.fromList [(API.ColumnName "GenreId", API.ColumnName "GenreId")]
}
)
]
}
],
API._mrInsertSchema = [],
API._mrOperations =
[ API.UpdateOperation $
API.UpdateMutationOperation
{ API._umoTable = API.TableName ("Track" :| []),
API._umoUpdates =
[ API.SetColumn $
API.RowColumnOperatorValue
{ API._rcovColumn = API.ColumnName "Name",
API._rcovValue = Aeson.String "Another Name",
API._rcovValueType = API.ScalarType "string"
},
API.CustomUpdateColumnOperator (API.UpdateColumnOperatorName [G.name|inc|]) $
API.RowColumnOperatorValue
{ API._rcovColumn = API.ColumnName "Milliseconds",
API._rcovValue = Aeson.Number 1000,
API._rcovValueType = API.ScalarType "number"
},
API.SetColumn $
API.RowColumnOperatorValue
{ API._rcovColumn = API.ColumnName "AlbumId",
API._rcovValue = Aeson.Number 3,
API._rcovValueType = API.ScalarType "number"
}
],
API._umoWhere =
Just $
API.And
[ API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.ColumnName "AlbumId") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (Aeson.Number 3) (API.ScalarType "number")),
API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.ColumnName "TrackId") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (Aeson.Number 3) (API.ScalarType "number"))
],
API._umoPostUpdateCheck = sharedPostUpdateCheck,
API._umoReturningFields = sharedReturning
},
API.UpdateOperation $
API.UpdateMutationOperation
{ API._umoTable = API.TableName ("Track" :| []),
API._umoUpdates =
[ API.SetColumn $
API.RowColumnOperatorValue
{ API._rcovColumn = API.ColumnName "Name",
API._rcovValue = Aeson.String "Better Name",
API._rcovValueType = API.ScalarType "string"
},
API.CustomUpdateColumnOperator (API.UpdateColumnOperatorName [G.name|inc|]) $
API.RowColumnOperatorValue
{ API._rcovColumn = API.ColumnName "UnitPrice",
API._rcovValue = Aeson.Number 1,
API._rcovValueType = API.ScalarType "number"
},
API.SetColumn $
API.RowColumnOperatorValue
{ API._rcovColumn = API.ColumnName "AlbumId",
API._rcovValue = Aeson.Number 3,
API._rcovValueType = API.ScalarType "number"
}
],
API._umoWhere =
Just $
API.And
[ API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.ColumnName "AlbumId") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (Aeson.Number 3) (API.ScalarType "number")),
API.ApplyBinaryComparisonOperator
API.GreaterThan
(API.ComparisonColumn API.CurrentTable (API.ColumnName "TrackId") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (Aeson.Number 3) (API.ScalarType "number"))
],
API._umoPostUpdateCheck = sharedPostUpdateCheck,
API._umoReturningFields = sharedReturning
}
]
}
emptyMutationRequest
& API.mrTableRelationships
.~ [ API.TableRelationships
{ API._trSourceTable = mkTableName "Track",
API._trRelationships =
HashMap.fromList
[ ( API.RelationshipName "Genre",
API.Relationship
{ API._rTargetTable = mkTableName "Genre",
API._rRelationshipType = API.ObjectRelationship,
API._rColumnMapping = HashMap.fromList [(API.ColumnName "GenreId", API.ColumnName "GenreId")]
}
)
]
}
]
& API.mrOperations
.~ [ API.UpdateOperation $
API.UpdateMutationOperation
{ API._umoTable = mkTableName "Track",
API._umoUpdates =
[ API.SetColumn $
API.RowColumnOperatorValue
{ API._rcovColumn = API.ColumnName "Name",
API._rcovValue = Aeson.String "Another Name",
API._rcovValueType = API.ScalarType "string"
},
API.CustomUpdateColumnOperator (API.UpdateColumnOperatorName [G.name|inc|]) $
API.RowColumnOperatorValue
{ API._rcovColumn = API.ColumnName "Milliseconds",
API._rcovValue = Aeson.Number 1000,
API._rcovValueType = API.ScalarType "number"
},
API.SetColumn $
API.RowColumnOperatorValue
{ API._rcovColumn = API.ColumnName "AlbumId",
API._rcovValue = Aeson.Number 3,
API._rcovValueType = API.ScalarType "number"
}
],
API._umoWhere =
Just $
API.And
[ API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.ColumnName "AlbumId") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (Aeson.Number 3) (API.ScalarType "number")),
API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.ColumnName "TrackId") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (Aeson.Number 3) (API.ScalarType "number"))
],
API._umoPostUpdateCheck = sharedPostUpdateCheck,
API._umoReturningFields = sharedReturning
},
API.UpdateOperation $
API.UpdateMutationOperation
{ API._umoTable = mkTableName "Track",
API._umoUpdates =
[ API.SetColumn $
API.RowColumnOperatorValue
{ API._rcovColumn = API.ColumnName "Name",
API._rcovValue = Aeson.String "Better Name",
API._rcovValueType = API.ScalarType "string"
},
API.CustomUpdateColumnOperator (API.UpdateColumnOperatorName [G.name|inc|]) $
API.RowColumnOperatorValue
{ API._rcovColumn = API.ColumnName "UnitPrice",
API._rcovValue = Aeson.Number 1,
API._rcovValueType = API.ScalarType "number"
},
API.SetColumn $
API.RowColumnOperatorValue
{ API._rcovColumn = API.ColumnName "AlbumId",
API._rcovValue = Aeson.Number 3,
API._rcovValueType = API.ScalarType "number"
}
],
API._umoWhere =
Just $
API.And
[ API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.ColumnName "AlbumId") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (Aeson.Number 3) (API.ScalarType "number")),
API.ApplyBinaryComparisonOperator
API.GreaterThan
(API.ComparisonColumn API.CurrentTable (API.ColumnName "TrackId") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (Aeson.Number 3) (API.ScalarType "number"))
],
API._umoPostUpdateCheck = sharedPostUpdateCheck,
API._umoReturningFields = sharedReturning
}
]
_mrrRecordedRequest `shouldBe` Just (Mutation expectedRequest)
rowsResponse :: [[(API.FieldName, API.FieldValue)]] -> API.QueryResponse
rowsResponse rows = API.QueryResponse (Just $ HashMap.fromList <$> rows) Nothing

View File

@ -11,6 +11,7 @@ module Hasura.Backends.DataConnector.API.V0.Query
Query (..),
qFields,
qAggregates,
qAggregatesLimit,
qLimit,
qOffset,
qWhere,
@ -92,9 +93,14 @@ data Query = Query
_qFields :: Maybe (HashMap FieldName Field),
-- | Map of aggregate field name to Aggregate definition
_qAggregates :: Maybe (HashMap FieldName API.V0.Aggregate),
-- | Optionally limit to N results.
-- | Optionally limit the maximum number of rows considered while applying
-- aggregations. This limit does not apply to returned rows.
_qAggregatesLimit :: Maybe Int,
-- | Optionally limit the maximum number of returned rows. This limit does not
-- apply to records considered while apply aggregations.
_qLimit :: Maybe Int,
-- | Optionally offset from the Nth result.
-- | Optionally offset from the Nth result. This applies to both row
-- and aggregation results.
_qOffset :: Maybe Int,
-- | Optionally constrain the results to satisfy some predicate.
_qWhere :: Maybe API.V0.Expression,
@ -112,9 +118,11 @@ instance HasCodec Query where
.= _qFields
<*> optionalFieldOrNull "aggregates" "Aggregate fields of the query"
.= _qAggregates
<*> optionalFieldOrNull "limit" "Optionally limit to N results"
<*> optionalFieldOrNull "aggregates_limit" "Optionally limit the maximum number of rows considered while applying aggregations. This limit does not apply to returned rows."
.= _qAggregatesLimit
<*> optionalFieldOrNull "limit" "Optionally limit the maximum number of returned rows. This limit does not apply to records considered while apply aggregations."
.= _qLimit
<*> optionalFieldOrNull "offset" "Optionally offset from the Nth result"
<*> optionalFieldOrNull "offset" "Optionally offset from the Nth result. This applies to both row and aggregation results."
.= _qOffset
<*> optionalFieldOrNull "where" "Optionally constrain the results to satisfy some predicate"
.= _qWhere

View File

@ -598,7 +598,7 @@ findColumnScalarType API.SchemaResponse {..} tableName columnName =
columnInfo = find (\API.ColumnInfo {..} -> _ciName == columnName) =<< API._tiColumns <$> tableInfo
emptyQuery :: API.Query
emptyQuery = API.Query Nothing Nothing Nothing Nothing Nothing Nothing
emptyQuery = API.Query Nothing Nothing Nothing Nothing Nothing Nothing Nothing
emptyMutationRequest :: API.MutationRequest
emptyMutationRequest = API.MutationRequest mempty mempty mempty

View File

@ -52,12 +52,24 @@ spec TestData {..} relationshipCapabilities = describe "Aggregate Queries" $ do
it "counts all rows, after applying pagination" $ do
let offset = 400
let limit = 20
let aggregatesLimit = 20
let aggregates = Data.mkFieldsMap [("count_all", StarCount)]
let queryRequest = invoicesQueryRequest aggregates & qrQuery %~ (qLimit ?~ limit >>> qOffset ?~ offset)
let queryRequest = invoicesQueryRequest aggregates & qrQuery %~ (qAggregatesLimit ?~ aggregatesLimit >>> qOffset ?~ offset)
response <- queryGuarded queryRequest
let invoiceCount = length . take limit $ drop offset _tdInvoicesRows
let invoiceCount = length . take aggregatesLimit $ drop offset _tdInvoicesRows
let expectedAggregates = Data.mkFieldsMap [("count_all", Number $ fromIntegral invoiceCount)]
Data.responseAggregates response `jsonShouldBe` expectedAggregates
Data.responseRows response `rowsShouldBe` []
it "limit does not limit the count aggregation" $ do
let limit = 20
let aggregates = Data.mkFieldsMap [("count_all", StarCount)]
let queryRequest = invoicesQueryRequest aggregates & qrQuery . qLimit ?~ limit
response <- queryGuarded queryRequest
let invoiceCount = length _tdInvoicesRows
let expectedAggregates = Data.mkFieldsMap [("count_all", Number $ fromIntegral invoiceCount)]
Data.responseAggregates response `jsonShouldBe` expectedAggregates
@ -76,16 +88,16 @@ spec TestData {..} relationshipCapabilities = describe "Aggregate Queries" $ do
Data.responseRows response `rowsShouldBe` []
it "can count all rows with non-null values in a column, after applying pagination and filtering" $ do
let limit = 50
let aggregatesLimit = 50
let where' = ApplyBinaryComparisonOperator GreaterThanOrEqual (_tdCurrentComparisonColumn "InvoiceId" invoiceIdScalarType) (Data.scalarValueComparison (Number 380) invoiceIdScalarType)
let aggregates = Data.mkFieldsMap [("count_cols", ColumnCount $ ColumnCountAggregate (_tdColumnName "BillingState") False)]
let queryRequest = invoicesQueryRequest aggregates & qrQuery %~ (qLimit ?~ limit >>> qWhere ?~ where')
let queryRequest = invoicesQueryRequest aggregates & qrQuery %~ (qAggregatesLimit ?~ aggregatesLimit >>> qWhere ?~ where')
response <- queryGuarded queryRequest
let invoiceCount =
_tdInvoicesRows
& filter ((^? Data.field "InvoiceId" . Data._ColumnFieldNumber) >>> (>= Just 380))
& take limit
& take aggregatesLimit
& mapMaybe ((^? Data.field "BillingState" . Data._ColumnFieldString))
& length
@ -106,18 +118,18 @@ spec TestData {..} relationshipCapabilities = describe "Aggregate Queries" $ do
Data.responseRows response `rowsShouldBe` []
it "can count all rows with distinct non-null values in a column, after applying pagination and filtering" $ do
let limit = 20
let aggregatesLimit = 20
let where' = ApplyBinaryComparisonOperator GreaterThanOrEqual (_tdCurrentComparisonColumn "InvoiceId" invoiceIdScalarType) (Data.scalarValueComparison (Number 380) invoiceIdScalarType)
-- It is important to add an explicit order by for this query as different database engines will order implicitly resulting in incorrect results
let orderBy = OrderBy mempty $ _tdOrderByColumn [] "InvoiceId" Ascending :| []
let aggregates = Data.mkFieldsMap [("count_cols", ColumnCount $ ColumnCountAggregate (_tdColumnName "BillingState") True)]
let queryRequest = invoicesQueryRequest aggregates & qrQuery %~ (qLimit ?~ limit >>> qWhere ?~ where' >>> qOrderBy ?~ orderBy)
let queryRequest = invoicesQueryRequest aggregates & qrQuery %~ (qAggregatesLimit ?~ aggregatesLimit >>> qWhere ?~ where' >>> qOrderBy ?~ orderBy)
response <- queryGuarded queryRequest
let billingStateCount =
_tdInvoicesRows
& filter ((^? Data.field "InvoiceId" . Data._ColumnFieldNumber) >>> (>= Just 380))
& take limit
& take aggregatesLimit
& mapMaybe ((^? Data.field "BillingState" . Data._ColumnFieldString))
& HashSet.fromList
& length
@ -127,6 +139,22 @@ spec TestData {..} relationshipCapabilities = describe "Aggregate Queries" $ do
Data.responseAggregates response `jsonShouldBe` expectedAggregates
Data.responseRows response `rowsShouldBe` []
it "limit does not limit the column count aggregation" $ do
let limit = 50
let aggregates = Data.mkFieldsMap [("count_cols", ColumnCount $ ColumnCountAggregate (_tdColumnName "BillingState") False)]
let queryRequest = invoicesQueryRequest aggregates & qrQuery . qLimit ?~ limit
response <- queryGuarded queryRequest
let invoiceCount =
_tdInvoicesRows
& mapMaybe ((^? Data.field "BillingState" . Data._ColumnFieldString))
& length
let expectedAggregates = Data.mkFieldsMap [("count_cols", Number $ fromIntegral invoiceCount)]
Data.responseAggregates response `jsonShouldBe` expectedAggregates
Data.responseRows response `rowsShouldBe` []
describe "Single Column Function" $ do
it "can get the max total from all rows" $ do
let aggregates = Data.mkFieldsMap [("max", singleColumnAggregateMax (_tdColumnName "Total") invoiceTotalScalarType)]
@ -140,18 +168,18 @@ spec TestData {..} relationshipCapabilities = describe "Aggregate Queries" $ do
Data.responseRows response `rowsShouldBe` []
it "can get the max total from all rows, after applying pagination, filtering and ordering" $ do
let limit = 20
let aggregatesLimit = 20
let where' = ApplyBinaryComparisonOperator Equal (_tdCurrentComparisonColumn "BillingCountry" billingCountryScalarType) (Data.scalarValueComparison (String "USA") billingCountryScalarType)
let orderBy = OrderBy mempty $ _tdOrderByColumn [] "BillingPostalCode" Descending :| [_tdOrderByColumn [] "InvoiceId" Ascending]
let aggregates = Data.mkFieldsMap [("max", singleColumnAggregateMax (_tdColumnName "Total") invoiceTotalScalarType)]
let queryRequest = invoicesQueryRequest aggregates & qrQuery %~ (qLimit ?~ limit >>> qWhere ?~ where' >>> qOrderBy ?~ orderBy)
let queryRequest = invoicesQueryRequest aggregates & qrQuery %~ (qAggregatesLimit ?~ aggregatesLimit >>> qWhere ?~ where' >>> qOrderBy ?~ orderBy)
response <- queryGuarded queryRequest
let maxTotal =
_tdInvoicesRows
& filter ((^? Data.field "BillingCountry" . Data._ColumnFieldString) >>> (== Just "USA"))
& sortOn (Down . (^? Data.field "BillingPostalCode"))
& take limit
& take aggregatesLimit
& mapMaybe ((^? Data.field "Total" . Data._ColumnFieldNumber))
& maximum
@ -190,6 +218,22 @@ spec TestData {..} relationshipCapabilities = describe "Aggregate Queries" $ do
Data.responseAggregates response `jsonShouldBe` expectedAggregates
Data.responseRows response `rowsShouldBe` []
it "limit does not limit the single column function aggregation" $ do
let limit = 20
let aggregates = Data.mkFieldsMap [("max", singleColumnAggregateMax (_tdColumnName "Total") invoiceTotalScalarType)]
let queryRequest = invoicesQueryRequest aggregates & qrQuery . qLimit ?~ limit
response <- queryGuarded queryRequest
let maxTotal =
_tdInvoicesRows
& mapMaybe ((^? Data.field "Total" . Data._ColumnFieldNumber))
& maximum
let expectedAggregates = Data.mkFieldsMap [("max", Number maxTotal)]
Data.responseAggregates response `jsonShouldBe` expectedAggregates
Data.responseRows response `rowsShouldBe` []
describe "Multiple Aggregates and Returning Rows" $ do
it "can get the max total from all rows, the count and the distinct count, simultaneously" $ do
let aggregates =
@ -246,7 +290,7 @@ spec TestData {..} relationshipCapabilities = describe "Aggregate Queries" $ do
let where' = ApplyBinaryComparisonOperator Equal (_tdCurrentComparisonColumn "BillingCountry" billingCountryScalarType) (Data.scalarValueComparison (String "Canada") billingCountryScalarType)
let orderBy = OrderBy mempty $ _tdOrderByColumn [] "BillingAddress" Ascending :| [_tdOrderByColumn [] "InvoiceId" Ascending]
let aggregates = Data.mkFieldsMap [("min", singleColumnAggregateMin (_tdColumnName "Total") invoiceTotalScalarType)]
let queryRequest = invoicesQueryRequest aggregates & qrQuery %~ (qFields ?~ fields >>> qLimit ?~ limit >>> qWhere ?~ where' >>> qOrderBy ?~ orderBy)
let queryRequest = invoicesQueryRequest aggregates & qrQuery %~ (qFields ?~ fields >>> qLimit ?~ limit >>> qAggregatesLimit ?~ limit >>> qWhere ?~ where' >>> qOrderBy ?~ orderBy)
response <- queryGuarded queryRequest
let invoiceRows =
@ -266,6 +310,56 @@ spec TestData {..} relationshipCapabilities = describe "Aggregate Queries" $ do
Data.responseRows response `rowsShouldBe` expectedRows
Data.responseAggregates response `jsonShouldBe` expectedAggregates
it "limit limits the number of returned rows but not the rows considered by the aggregate function" $ do
let limit = 20
let fields = Data.mkFieldsMap [("InvoiceId", _tdColumnField _tdInvoicesTableName "InvoiceId")]
let orderBy = OrderBy mempty $ _tdOrderByColumn [] "InvoiceId" Ascending :| []
let aggregates = Data.mkFieldsMap [("count_all", StarCount)]
let queryRequest = invoicesQueryRequest aggregates & qrQuery %~ (qFields ?~ fields >>> qLimit ?~ limit >>> qOrderBy ?~ orderBy)
response <- queryGuarded queryRequest
let invoiceCount = length _tdInvoicesRows
let expectedAggregates = Data.mkFieldsMap [("count_all", Number $ fromIntegral invoiceCount)]
let expectedRows = take limit $ Data.filterColumnsByQueryFields (_qrQuery queryRequest) <$> _tdInvoicesRows
Data.responseAggregates response `jsonShouldBe` expectedAggregates
Data.responseRows response `rowsShouldBe` expectedRows
it "aggregates limit is applied separately to row limit" $ do
let aggregatesLimit = 30
let limit = 20
let fields =
Data.mkFieldsMap
[ ("InvoiceId", _tdColumnField _tdInvoicesTableName "InvoiceId"),
("BillingCountry", _tdColumnField _tdInvoicesTableName "BillingCountry")
]
let where' = ApplyBinaryComparisonOperator Equal (_tdCurrentComparisonColumn "BillingCountry" billingCountryScalarType) (Data.scalarValueComparison (String "Canada") billingCountryScalarType)
let orderBy = OrderBy mempty $ _tdOrderByColumn [] "BillingAddress" Ascending :| [_tdOrderByColumn [] "InvoiceId" Ascending]
let aggregates = Data.mkFieldsMap [("min", singleColumnAggregateMin (_tdColumnName "Total") invoiceTotalScalarType)]
let queryRequest = invoicesQueryRequest aggregates & qrQuery %~ (qFields ?~ fields >>> qAggregatesLimit ?~ aggregatesLimit >>> qLimit ?~ limit >>> qWhere ?~ where' >>> qOrderBy ?~ orderBy)
response <- queryGuarded queryRequest
let aggregateLimitedInvoiceRows =
_tdInvoicesRows
& filter ((^? Data.field "BillingCountry" . Data._ColumnFieldString) >>> (== Just "Canada"))
& sortOn (^? Data.field "BillingAddress")
& take aggregatesLimit
-- Limit is smaller than aggregatesLimit, so we can just take from the aggregateLimitedInvoiceRows
let invoiceRows = take limit aggregateLimitedInvoiceRows
let maxTotal =
aggregateLimitedInvoiceRows
& take limit
& mapMaybe ((^? Data.field "Total" . Data._ColumnFieldNumber))
& aggregate (Number . minimum)
let expectedAggregates = Data.mkFieldsMap [("min", maxTotal)]
let expectedRows = Data.filterColumnsByQueryFields (_qrQuery queryRequest) <$> invoiceRows
Data.responseRows response `rowsShouldBe` expectedRows
Data.responseAggregates response `jsonShouldBe` expectedAggregates
when (isJust relationshipCapabilities) $
describe "Aggregates via Relationships" $ do
it "can query aggregates via an array relationship" $ do
@ -387,8 +481,8 @@ spec TestData {..} relationshipCapabilities = describe "Aggregate Queries" $ do
describe "Aggregates over ordered and paginated tables" $ do
it "orders by a column" $ do
let offset = 2
let limit = 5
let orderBy = OrderBy mempty $ _tdOrderByColumn [] "Title" Descending :| []
let aggregatesLimit = 5
let orderBy = OrderBy mempty $ _tdOrderByColumn [] "Title" Ascending :| []
let aggregates = Data.mkFieldsMap [("max", singleColumnAggregateMax (_tdColumnName "Title") albumTitleScalarType)]
let queryRequest =
albumsQueryRequest
@ -396,15 +490,15 @@ spec TestData {..} relationshipCapabilities = describe "Aggregate Queries" $ do
%~ ( qAggregates ?~ aggregates
>>> qOrderBy ?~ orderBy
>>> qOffset ?~ offset
>>> qLimit ?~ limit
>>> qAggregatesLimit ?~ aggregatesLimit
)
response <- queryGuarded queryRequest
let names =
_tdAlbumsRows
& sortOn (Down . (^? Data.field "Title"))
& sortOn ((^? Data.field "Title"))
& drop offset
& take limit
& take aggregatesLimit
& mapMaybe (^? Data.field "Title" . Data._ColumnFieldString)
let expectedAggregates = Data.mkFieldsMap [("max", aggregate (String . maximum) names)]
@ -415,7 +509,7 @@ spec TestData {..} relationshipCapabilities = describe "Aggregate Queries" $ do
when (isJust relationshipCapabilities) . describe "involving related tables in the ordering" $ do
it "orders by a column" $ do
let offset = 10
let limit = 50
let aggregatesLimit = 50
let orderByRelations = HashMap.fromList [(_tdArtistRelationshipName, OrderByRelation Nothing mempty)]
let orderBy =
OrderBy orderByRelations $
@ -431,7 +525,7 @@ spec TestData {..} relationshipCapabilities = describe "Aggregate Queries" $ do
%~ ( qAggregates ?~ aggregates
>>> qOrderBy ?~ orderBy
>>> qOffset ?~ offset
>>> qLimit ?~ limit
>>> qAggregatesLimit ?~ aggregatesLimit
)
response <- queryGuarded queryRequest
@ -442,7 +536,7 @@ spec TestData {..} relationshipCapabilities = describe "Aggregate Queries" $ do
_tdAlbumsRows
& sortOn (\album -> getRelatedArtist album ^? _Just . Data.field "Name")
& drop offset
& take limit
& take aggregatesLimit
& mapMaybe (^? Data.field "Title" . Data._ColumnFieldString)
let expectedAggregates = Data.mkFieldsMap [("max", aggregate (String . maximum) names)]
@ -452,7 +546,7 @@ spec TestData {..} relationshipCapabilities = describe "Aggregate Queries" $ do
it "orders by an aggregate" $ do
let offset = 15
let limit = 10
let aggregatesLimit = 10
let orderByRelations = HashMap.fromList [(_tdTracksRelationshipName, OrderByRelation Nothing mempty)]
let orderBy =
OrderBy orderByRelations $
@ -468,7 +562,7 @@ spec TestData {..} relationshipCapabilities = describe "Aggregate Queries" $ do
%~ ( qAggregates ?~ aggregates
>>> qOrderBy ?~ orderBy
>>> qOffset ?~ offset
>>> qLimit ?~ limit
>>> qAggregatesLimit ?~ aggregatesLimit
)
response <- queryGuarded queryRequest
@ -480,7 +574,7 @@ spec TestData {..} relationshipCapabilities = describe "Aggregate Queries" $ do
_tdAlbumsRows
& sortOn (\album -> (Down $ getRelatedTracksCount album, Down $ album ^? Data.field "Title" . Data._ColumnFieldString))
& drop offset
& take limit
& take aggregatesLimit
& mapMaybe (^? Data.field "Title" . Data._ColumnFieldString)
let expectedAggregates = Data.mkFieldsMap [("max", aggregate (String . maximum) names)]
@ -549,7 +643,7 @@ spec TestData {..} relationshipCapabilities = describe "Aggregate Queries" $ do
tracksAggregates = Data.mkFieldsMap [("aggregate_count", StarCount)]
tracksWhere = ApplyBinaryComparisonOperator LessThan (_tdCurrentComparisonColumn "Milliseconds" millisecondsScalarType) (Data.scalarValueComparison (Number 300000) millisecondsScalarType)
tracksOrderBy = OrderBy mempty $ _tdOrderByColumn [] "Name" Descending :| []
tracksSubquery = Query (Just tracksFields) (Just tracksAggregates) Nothing Nothing (Just tracksWhere) (Just tracksOrderBy)
tracksSubquery = Query (Just tracksFields) (Just tracksAggregates) Nothing Nothing Nothing (Just tracksWhere) (Just tracksOrderBy)
albumsFields =
Data.mkFieldsMap
[ ("nodes_Title", _tdColumnField _tdAlbumsTableName "Title"),
@ -568,7 +662,7 @@ spec TestData {..} relationshipCapabilities = describe "Aggregate Queries" $ do
ApplyBinaryComparisonOperator LessThan (_tdCurrentComparisonColumn "Name" artistNameScalarType) (Data.scalarValueComparison (String "B") artistNameScalarType)
]
artistOrderBy = OrderBy mempty $ _tdOrderByColumn [] "Name" Descending :| []
artistQuery = Query (Just artistFields) Nothing (Just 3) (Just 1) (Just artistWhere) (Just artistOrderBy)
artistQuery = Query (Just artistFields) Nothing Nothing (Just 3) (Just 1) (Just artistWhere) (Just artistOrderBy)
in QueryRequest
_tdArtistsTableName
[ Data.onlyKeepRelationships [_tdAlbumsRelationshipName] _tdArtistsTableRelationships,

View File

@ -146,6 +146,7 @@ translateAnnSelect sessionVariables translateFieldsAndAggregates tableName selec
API.Query
{ _qFields = mapFieldNameHashMap <$> _faaFields,
_qAggregates = mapFieldNameHashMap <$> _faaAggregates,
_qAggregatesLimit = _saLimit (_asnArgs selectG) <* _faaAggregates, -- Only include the aggregates limit if we actually have aggregrates
_qLimit =
fmap getMin $
foldMap
@ -310,6 +311,7 @@ translateAnnField sessionVariables sourceTableName = \case
{ _qFields = Just $ mapFieldNameHashMap fields,
_qAggregates = mempty,
_qWhere = whereClause,
_qAggregatesLimit = Nothing,
_qLimit = Nothing,
_qOffset = Nothing,
_qOrderBy = Nothing

View File

@ -89,6 +89,7 @@ mkRemoteRelationshipPlan sessionVariables _sourceConfig joinIds joinIdsSchema ar
API.Query
{ _qFields = Just $ mapFieldNameHashMap fields,
_qAggregates = Nothing,
_qAggregatesLimit = Nothing,
_qLimit = Nothing,
_qOffset = Nothing,
_qWhere = whereClause,

View File

@ -41,7 +41,7 @@ spec = do
}
|]
describe "RelationshipField" $ do
let query = Query (Just mempty) Nothing Nothing Nothing Nothing Nothing
let query = Query (Just mempty) Nothing Nothing Nothing Nothing Nothing Nothing
testToFromJSONToSchema
(RelField $ RelationshipField (RelationshipName "a_relationship") query)
[aesonQQ|
@ -57,6 +57,7 @@ spec = do
Query
{ _qFields = Just $ HashMap.fromList [(FieldName "my_field_alias", ColumnField (ColumnName "my_field_name") (ScalarType "string"))],
_qAggregates = Just $ HashMap.fromList [(FieldName "my_aggregate", StarCount)],
_qAggregatesLimit = Just 5,
_qLimit = Just 10,
_qOffset = Just 20,
_qWhere = Just $ And [],
@ -67,6 +68,7 @@ spec = do
[aesonQQ|
{ "fields": {"my_field_alias": {"type": "column", "column": "my_field_name", "column_type": "string"}},
"aggregates": { "my_aggregate": { "type": "star_count" } },
"aggregates_limit": 5,
"limit": 10,
"offset": 20,
"where": {"type": "and", "expressions": []},
@ -91,7 +93,7 @@ spec = do
QueryRequest
{ _qrTable = TableName ["my_table"],
_qrTableRelationships = [],
_qrQuery = Query (Just mempty) Nothing Nothing Nothing Nothing Nothing,
_qrQuery = Query (Just mempty) Nothing Nothing Nothing Nothing Nothing Nothing,
_qrForeach = Just (HashMap.fromList [(ColumnName "my_id", ScalarValue (J.Number 666) (ScalarType "number"))] :| [])
}
testToFromJSONToSchema
@ -181,6 +183,7 @@ genQuery =
<*> Gen.maybe (genFieldMap genAggregate)
<*> Gen.maybe (Gen.int defaultRange)
<*> Gen.maybe (Gen.int defaultRange)
<*> Gen.maybe (Gen.int defaultRange)
<*> Gen.maybe genExpression
<*> Gen.maybe genOrderBy