Add inherited roles data redaction support to the Data Connector API

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

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/10023
GitOrigin-RevId: d6947dd89dc59fce3f68b786192c59e731826c60
This commit is contained in:
Daniel Chambers 2023-08-03 16:09:14 +10:00 committed by hasura-bot
parent 8c6f823f7b
commit 68f7d6e9a4
75 changed files with 1547 additions and 539 deletions

View File

@ -129,7 +129,8 @@ The `GET /capabilities` endpoint is used by `graphql-engine` to discover the cap
{
"capabilities": {
"queries": {
"foreach": {}
"foreach": {},
"redaction": {}
},
"data_schema": {
"supports_primary_keys": true,
@ -183,6 +184,8 @@ The `config_schema` property contains an [OpenAPI 3 Schema](https://swagger.io/s
#### Query capabilities
The agent can declare whether or not it supports ["foreach queries"](#foreach-queries) by including a `foreach` property with an empty object assigned to it. Foreach query support is optional, but is required if the agent is to be used as the target of remote relationships in HGE.
The agent can also declare whether or not it supports ["data redaction"](#data-redaction) by including a `redaction` property with an empty object assigned to it. Data redaction support is optional, but is required if a user configures HGE with inherited roles with different column selection permissions for the same table in the inherited role's role set.
#### Data schema capabilities
The agent can declare whether or not it supports primary keys or foreign keys by setting the `supports_primary_keys` and `supports_foreign_keys` properties under the `data_schema` object on capabilities. If it does not declare support, it is expected that it will not return any such primary/foreign keys in the schema it exposes on the `/schema` endpoint.
@ -1564,8 +1567,7 @@ The `order_by` field can either be null, which means no particular ordering is r
"target_path": [],
"target": {
"type": "column",
"column": "last_name",
"column_type": "string"
"column": "last_name"
},
"order_direction": "asc"
},
@ -1573,8 +1575,7 @@ The `order_by` field can either be null, which means no particular ordering is r
"target_path": [],
"target": {
"type": "column",
"column": "first_name",
"column_type": "string"
"column": "first_name"
},
"order_direction": "desc"
}
@ -1824,6 +1825,369 @@ It is important to point out that a `LATERAL` join is necessary instead of regul
The artificial `Index` column is inserted into the foreach rowset to ensure that the ordering of the results matches the original ordering of the foreach array in the query request.
#### Data Redaction
In HGE, it is possible to create inherited roles; these produce the union of the access rights of the roles in the inherited role's role set. Roles in HGE can grant or deny access to columns on tables and can filter the rows accessible on the table. Naturally, different roles composed together into an inherited role may combine different filters and column access rights for the same table.
Consider the following `Test` table:
| Id | ColumnA | ColumnB | ColumnC |
|-------|---------|---------|---------|
| **1** | **A1** | **B1** | C1 |
| **2** | **A2** | **B2** | **C2** |
| **3** | A3 | **B3** | **C3** |
and the following roles defined for this `Test` table:
* `RoleA`:
* Column Select Permissions: `Id`, `ColumnA`, `ColumnB`
* Row Filter: `{ Id: { _in: [1,2] } }`
* `RoleB`:
* Column Select Permissions: `Id`, `ColumnB`, `ColumnC`
* Row Filter: `{ Id: { _in: [2,3] } }`
* `ComboRole`: An inherited role, composed of `RoleA` and `RoleB`
In this scenario, `ComboRole` grants access to all the data that has been **bolded** in the above table. Note that the `A3` is inaccessible because `RoleA` does not grant access to that row, `RoleB` does, but `RoleB` does not grant access to `ColumnA`. Similarly, `C1` is inaccessible because `RoleA` grants access to the row, but not to `ColumnC`, and `RoleB` does not grant access to the row.
If a user using the `ComboRole` role was to query this `Test` table, we want the access rights as defined above to be enforced, so we'd expect the following data to be returned:
| Id | ColumnA | ColumnB | ColumnC |
|----|---------|---------|---------|
| 1 | A1 | B1 | null |
| 2 | A2 | B2 | C2 |
| 3 | null | B3 | C3 |
Data redaction is the process by which data is "nulled out" (ie. redacted) by the agent when it should be inaccessible to a user, as it has been above. It is only necessary when different roles in one inherited roles have differing column select permissions. When all the roles have the same column select permissions, the user is prevented from querying inaccessible columns in the first place. Redaction is only necessary when they have partial access to the column, such as in the above example scenario.
To support data redaction, the agent must declare the `redaction` capability under the `queries` capability.
```json
{
"queries": {
"redaction": {}
}
}
```
Once this is declared, all query requests that require redaction will contain a `redaction_expressions` property that will contain named redaction expressions per table/function. Then, in every place in the query that requires redaction, a `redaction_expression` property will be defined that refers to the particular named expression that needs to be used.
A redaction expression is an `Expression`, same as what is used for the `where` query property used in [filters](#filters). If specified, the expression needs to be evaluated per row, and if it evaluates to false, the associated column value must be replaced with null.
##### Redaction in Fields
The first, most obvious, place redaction is applied is against columns requested in a query's `fields`.
Here's an example query, querying all the columns of the above example `Test` table using the `ComboRole`:
```jsonc
{
"table": ["Test"],
"table_relationships": [],
// Redaction expressions are defined per table/function
"redaction_expressions": [
{
"target": {
"type": "table",
"table": ["Test"] // These expressions are defined for the Test table
},
"expressions": {
// Redaction expressions are named (names are only unique within a table/function)
"RedactionExp0": {
"type": "binary_arr_op",
"operator": "in",
"column": { "name": "Id", "column_type": "number" },
"values": [1,2],
"value_type": "number"
},
"RedactionExp1": {
"type": "binary_arr_op",
"operator": "in",
"column": { "name": "Id", "column_type": "number" },
"values": [2,3],
"value_type": "number"
}
}
}
],
"query": {
"fields": {
"Id": {
"type": "column",
"column": "Id",
"column_type": "number"
},
"ColumnA": {
"type": "column",
"column": "ColumnA",
"column_type": "string",
// This column must be redacted using the Test table's RedactionExp0 expression
// If this expression evaluates to false for a row, this field must return null for that row
"redaction_expression": "RedactionExp0"
},
"ColumnB": {
"type": "column",
"column": "ColumnB",
"column_type": "string"
},
"ColumnC": {
"type": "column",
"column": "ColumnC",
"column_type": "string",
// This column must be redacted using the Test table's RedactionExp1 expression
// If this expression evaluates to false for a row, this field must return null for that row
"redaction_expression": "RedactionExp1"
}
},
// The row filters from ComboRole are pushed down into the where filter predicate
"where": {
"type": "or",
"expressions": [
{
"type": "binary_arr_op",
"operator": "in",
"column": { "name": "Id", "column_type": "number" },
"values": [1,2],
"value_type": "number"
},
{
"type": "binary_arr_op",
"operator": "in",
"column": { "name": "Id", "column_type": "number" },
"values": [2,3],
"value_type": "number"
}
]
}
}
}
```
This query might be translated into SQL that is similar to this:
```sql
SELECT
"Id",
CASE
WHEN "Id" IN (1,2) THEN "ColumnA"
ELSE NULL
END AS "ColumnA",
"ColumnB",
CASE
WHEN "Id" IN (2,3) THEN "ColumnC"
ELSE NULL
END AS "ColumnC"
FROM "Test"
WHERE "Id" IN (1,2) OR "Id" IN (2,3)
```
##### Redaction in Aggregates
Another place redaction can be applied is in aggregates. When aggregations are calculated across a set of rows, the columns being aggregated may need to be redacted _before_ being aggregated over.
For example, here's an aggregation query:
```jsonc
{
"table": ["Test"],
"table_relationships": [],
// Redaction expressions are defined per table/function
"redaction_expressions": [
{
"target": {
"type": "table",
"table": ["Test"] // These expressions are defined for the Test table
},
"expressions": {
// Redaction expressions are named (names are only unique within a table/function)
"RedactionExp0": {
"type": "binary_arr_op",
"operator": "in",
"column": { "name": "Id", "column_type": "number" },
"values": [1,2],
"value_type": "number"
},
"RedactionExp1": {
"type": "binary_arr_op",
"operator": "in",
"column": { "name": "Id", "column_type": "number" },
"values": [2,3],
"value_type": "number"
}
}
}
],
"query": {
"aggregates": {
// Both "single_column" and "column_count" aggregations can have redaction expressions
"aggregate_max_ColumnA": {
"type": "single_column",
"function": "max",
"column": "ColumnA",
"redaction_expression": "RedactionExp0",
"result_type": "string"
},
"aggregate_count_ColumnC": {
"type": "column_count",
"column": "ColumnC",
"redaction_expression": "RedactionExp1",
"distinct": false
}
}
}
}
```
This query might be translated into SQL that is similar to this:
```sql
SELECT
MAX(
CASE
WHEN "Id" IN (1,2) THEN "ColumnA"
ELSE NULL
END
) AS "aggregate_max_ColumnA",
COUNT(
CASE
WHEN "Id" IN (2,3) THEN "ColumnC"
ELSE NULL
END
) AS "aggregate_count_ColumnC"
FROM "Test"
```
##### Redaction in Filtering
When comparing a column to something during filtering, data redaction can require that the column be redacted before the comparison is performed.
For example, here's a query that uses redaction inside the filter expression in `where`:
```jsonc
{
"table": ["Test"],
"table_relationships": [],
// Redaction expressions are defined per table/function
"redaction_expressions": [
{
"target": {
"type": "table",
"table": ["Test"] // These expressions are defined for the Test table
},
"expressions": {
// Redaction expressions are named (names are only unique within a table/function)
"RedactionExp0": {
"type": "binary_arr_op",
"operator": "in",
"column": { "name": "Id", "column_type": "number" },
"values": [1,2],
"value_type": "number"
}
}
}
],
"query": {
"fields": {
"Id": {
"type": "column",
"column": "Id",
"column_type": "number"
}
},
"where": {
"type": "binary_op",
"operator": "equals",
// ALl column comparisons in "binary_op", "binary_arr_op", and "unary_op"
// can potentially have a redaction expression
"column": {
"name": "ColumnA",
"column_type": "string",
"redaction_expression": "RedactionExp0"
},
"value": {
"type": "scalar",
"value": "A1",
"value_type": "string"
}
}
}
}
```
This query might be translated into SQL that is similar to this:
```sql
SELECT "Id"
FROM "Test"
WHERE
(CASE
WHEN "Id" IN (1,2) THEN "ColumnA"
ELSE NULL
END) = "A1"
```
##### Redaction in Ordering
Data redaction also needs to be applied when ordering upon a column; namely the ordering should be performed across the redacted values.
For example, here's a query that uses redaction inside the `order_by`:
```jsonc
{
"table": ["Test"],
"table_relationships": [],
// Redaction expressions are defined per table/function
"redaction_expressions": [
{
"target": {
"type": "table",
"table": ["Test"] // These expressions are defined for the Test table
},
"expressions": {
// Redaction expressions are named (names are only unique within a table/function)
"RedactionExp0": {
"type": "binary_arr_op",
"operator": "in",
"column": { "name": "Id", "column_type": "number" },
"values": [1,2],
"value_type": "number"
}
}
}
],
"query": {
"fields": {
"Id": {
"type": "column",
"column": "Id",
"column_type": "number"
}
},
"order_by": {
"relations": {},
"elements": [
{
"target_path": [],
// Both "column" and "single_column_aggregate"-typed ordering targets can have
// redaction expressions applied to them
"target": {
"type": "column",
"column": "ColumnA",
"redaction_expression": "RedactionExp0"
},
"order_direction": "asc"
}
]
}
}
}
```
This query might be translated into SQL that is similar to this:
```sql
SELECT "Id"
FROM "Test"
ORDER BY
(CASE
WHEN "Id" IN (1,2) THEN "ColumnA"
ELSE NULL
END) ASC
```
#### Type Definitions
The `QueryRequest` TypeScript type in the [reference implementation](./reference/src/types/index.ts) describes the valid request body payloads which may be passed to the `POST /query` endpoint. The response body structure is captured by the `QueryResponse` type.
@ -2458,7 +2822,7 @@ Schema:
```json
{
"tables": [
"tables": [
{
"name": [
"Artist"

View File

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

View File

@ -568,10 +568,14 @@
"type": "object"
},
"ForeachCapabilities": {},
"RedactionCapabilities": {},
"QueryCapabilities": {
"properties": {
"foreach": {
"$ref": "#/components/schemas/ForeachCapabilities"
},
"redaction": {
"$ref": "#/components/schemas/RedactionCapabilities"
}
},
"type": "object"
@ -1710,28 +1714,56 @@
}
]
},
"Field": {
"TNFunction": {
"properties": {
"function": {
"$ref": "#/components/schemas/FunctionName"
},
"type": {
"enum": [
"function"
],
"type": "string"
}
},
"required": [
"function",
"type"
],
"type": "object"
},
"TNTable": {
"properties": {
"table": {
"$ref": "#/components/schemas/TableName"
},
"type": {
"enum": [
"table"
],
"type": "string"
}
},
"required": [
"table",
"type"
],
"type": "object"
},
"TargetName": {
"discriminator": {
"mapping": {
"array": "NestedArrayField",
"column": "ColumnField",
"object": "NestedObjField",
"relationship": "RelationshipField"
"function": "TNFunction",
"table": "TNTable"
},
"propertyName": "type"
},
"oneOf": [
{
"$ref": "#/components/schemas/NestedArrayField"
"$ref": "#/components/schemas/TNFunction"
},
{
"$ref": "#/components/schemas/NestedObjField"
},
{
"$ref": "#/components/schemas/ColumnField"
},
{
"$ref": "#/components/schemas/RelationshipField"
"$ref": "#/components/schemas/TNTable"
}
]
},
@ -1753,6 +1785,9 @@
}
]
},
"RedactionExpressionName": {
"type": "string"
},
"ComparisonColumn": {
"properties": {
"column_type": {
@ -1780,6 +1815,9 @@
"type": "string"
},
"type": "array"
},
"redaction_expression": {
"$ref": "#/components/schemas/RedactionExpressionName"
}
},
"required": [
@ -2126,6 +2164,50 @@
],
"type": "object"
},
"TargetRedactionExpressions": {
"properties": {
"expressions": {
"additionalProperties": {
"$ref": "#/components/schemas/Expression"
},
"description": "The named redaction expressions associated with the target",
"type": "object"
},
"target": {
"$ref": "#/components/schemas/TargetName"
}
},
"required": [
"target",
"expressions"
],
"type": "object"
},
"Field": {
"discriminator": {
"mapping": {
"array": "NestedArrayField",
"column": "ColumnField",
"object": "NestedObjField",
"relationship": "RelationshipField"
},
"propertyName": "type"
},
"oneOf": [
{
"$ref": "#/components/schemas/NestedArrayField"
},
{
"$ref": "#/components/schemas/NestedObjField"
},
{
"$ref": "#/components/schemas/ColumnField"
},
{
"$ref": "#/components/schemas/RelationshipField"
}
]
},
"OrderByRelation": {
"properties": {
"subrelations": {
@ -2157,6 +2239,9 @@
"function": {
"$ref": "#/components/schemas/SingleColumnAggregateFunction"
},
"redaction_expression": {
"$ref": "#/components/schemas/RedactionExpressionName"
},
"result_type": {
"$ref": "#/components/schemas/ScalarType"
},
@ -2180,6 +2265,9 @@
"column": {
"type": "string"
},
"redaction_expression": {
"$ref": "#/components/schemas/RedactionExpressionName"
},
"type": {
"enum": [
"column"
@ -2398,6 +2486,9 @@
"column_type": {
"$ref": "#/components/schemas/ScalarType"
},
"redaction_expression": {
"$ref": "#/components/schemas/RedactionExpressionName"
},
"type": {
"enum": [
"column"
@ -2444,6 +2535,9 @@
"function": {
"$ref": "#/components/schemas/SingleColumnAggregateFunction"
},
"redaction_expression": {
"$ref": "#/components/schemas/RedactionExpressionName"
},
"result_type": {
"$ref": "#/components/schemas/ScalarType"
},
@ -2472,6 +2566,9 @@
"description": "Whether or not only distinct items should be counted",
"type": "boolean"
},
"redaction_expression": {
"$ref": "#/components/schemas/RedactionExpressionName"
},
"type": {
"enum": [
"column_count"
@ -2537,6 +2634,14 @@
"query": {
"$ref": "#/components/schemas/Query"
},
"redaction_expressions": {
"default": [],
"description": "Expressions that can be referenced by the query to redact fields/columns",
"items": {
"$ref": "#/components/schemas/TargetRedactionExpressions"
},
"type": "array"
},
"relationships": {
"description": "The relationships between entities involved in the entire query request",
"items": {
@ -2590,6 +2695,14 @@
"query": {
"$ref": "#/components/schemas/Query"
},
"redaction_expressions": {
"default": [],
"description": "Expressions that can be referenced by the query to redact fields/columns",
"items": {
"$ref": "#/components/schemas/TargetRedactionExpressions"
},
"type": "array"
},
"table": {
"$ref": "#/components/schemas/TableName"
},
@ -2702,6 +2815,14 @@
},
"type": "array"
},
"redaction_expressions": {
"default": [],
"description": "Expressions that can be referenced by the query to redact fields/columns",
"items": {
"$ref": "#/components/schemas/TargetRedactionExpressions"
},
"type": "array"
},
"table_relationships": {
"description": "The relationships between tables involved in the entire mutation request",
"items": {

View File

@ -109,6 +109,8 @@ export type { QueryResponse } from './models/QueryResponse';
export type { RawCapabilities } from './models/RawCapabilities';
export type { RawRequest } from './models/RawRequest';
export type { RawResponse } from './models/RawResponse';
export type { RedactionCapabilities } from './models/RedactionCapabilities';
export type { RedactionExpressionName } from './models/RedactionExpressionName';
export type { RelatedTable } from './models/RelatedTable';
export type { Relationship } from './models/Relationship';
export type { RelationshipCapabilities } from './models/RelationshipCapabilities';
@ -137,6 +139,10 @@ export type { TableName } from './models/TableName';
export type { TableRelationships } from './models/TableRelationships';
export type { TableRequest } from './models/TableRequest';
export type { TableType } from './models/TableType';
export type { TargetName } from './models/TargetName';
export type { TargetRedactionExpressions } from './models/TargetRedactionExpressions';
export type { TNFunction } from './models/TNFunction';
export type { TNTable } from './models/TNTable';
export type { UnaryComparisonOperator } from './models/UnaryComparisonOperator';
export type { UniqueIdentifierGenerationStrategy } from './models/UniqueIdentifierGenerationStrategy';
export type { UnrelatedTable } from './models/UnrelatedTable';

View File

@ -2,6 +2,8 @@
/* tslint:disable */
/* eslint-disable */
import type { RedactionExpressionName } from './RedactionExpressionName';
export type ColumnCountAggregate = {
/**
* The column to apply the count aggregate function to
@ -11,6 +13,7 @@ export type ColumnCountAggregate = {
* Whether or not only distinct items should be counted
*/
distinct: boolean;
redaction_expression?: RedactionExpressionName;
type: 'column_count';
};

View File

@ -2,11 +2,13 @@
/* tslint:disable */
/* eslint-disable */
import type { RedactionExpressionName } from './RedactionExpressionName';
import type { ScalarType } from './ScalarType';
export type ColumnField = {
column: string;
column_type: ScalarType;
redaction_expression?: RedactionExpressionName;
type: 'column';
};

View File

@ -2,6 +2,7 @@
/* tslint:disable */
/* eslint-disable */
import type { RedactionExpressionName } from './RedactionExpressionName';
import type { ScalarType } from './ScalarType';
export type ComparisonColumn = {
@ -14,5 +15,6 @@ export type ComparisonColumn = {
* The path to the table that contains the specified column. Missing or empty array means the current table. ["$"] means the query table. No other values are supported at this time.
*/
path?: Array<string>;
redaction_expression?: RedactionExpressionName;
};

View File

@ -6,6 +6,7 @@ import type { FunctionName } from './FunctionName';
import type { FunctionRequestArgument } from './FunctionRequestArgument';
import type { Query } from './Query';
import type { Relationships } from './Relationships';
import type { TargetRedactionExpressions } from './TargetRedactionExpressions';
export type FunctionRequest = {
function: FunctionName;
@ -14,6 +15,10 @@ export type FunctionRequest = {
*/
function_arguments?: Array<FunctionRequestArgument>;
query: Query;
/**
* Expressions that can be referenced by the query to redact fields/columns
*/
redaction_expressions?: Array<TargetRedactionExpressions>;
/**
* The relationships between entities involved in the entire query request
*/

View File

@ -5,6 +5,7 @@
import type { MutationOperation } from './MutationOperation';
import type { TableInsertSchema } from './TableInsertSchema';
import type { TableRelationships } from './TableRelationships';
import type { TargetRedactionExpressions } from './TargetRedactionExpressions';
export type MutationRequest = {
/**
@ -15,6 +16,10 @@ export type MutationRequest = {
* The mutation operations to perform
*/
operations: Array<MutationOperation>;
/**
* Expressions that can be referenced by the query to redact fields/columns
*/
redaction_expressions?: Array<TargetRedactionExpressions>;
/**
* The relationships between tables involved in the entire mutation request
*/

View File

@ -2,8 +2,11 @@
/* tslint:disable */
/* eslint-disable */
import type { RedactionExpressionName } from './RedactionExpressionName';
export type OrderByColumn = {
column: string;
redaction_expression?: RedactionExpressionName;
type: 'column';
};

View File

@ -2,6 +2,7 @@
/* tslint:disable */
/* eslint-disable */
import type { RedactionExpressionName } from './RedactionExpressionName';
import type { ScalarType } from './ScalarType';
import type { SingleColumnAggregateFunction } from './SingleColumnAggregateFunction';
@ -11,6 +12,7 @@ export type OrderBySingleColumnAggregate = {
*/
column: string;
function: SingleColumnAggregateFunction;
redaction_expression?: RedactionExpressionName;
result_type: ScalarType;
type: 'single_column_aggregate';
};

View File

@ -3,8 +3,10 @@
/* eslint-disable */
import type { ForeachCapabilities } from './ForeachCapabilities';
import type { RedactionCapabilities } from './RedactionCapabilities';
export type QueryCapabilities = {
foreach?: ForeachCapabilities;
redaction?: RedactionCapabilities;
};

View File

@ -0,0 +1,7 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type RedactionCapabilities = {
};

View File

@ -0,0 +1,5 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type RedactionExpressionName = string;

View File

@ -2,6 +2,7 @@
/* tslint:disable */
/* eslint-disable */
import type { RedactionExpressionName } from './RedactionExpressionName';
import type { ScalarType } from './ScalarType';
import type { SingleColumnAggregateFunction } from './SingleColumnAggregateFunction';
@ -11,6 +12,7 @@ export type SingleColumnAggregate = {
*/
column: string;
function: SingleColumnAggregateFunction;
redaction_expression?: RedactionExpressionName;
result_type: ScalarType;
type: 'single_column';
};

View File

@ -0,0 +1,11 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { FunctionName } from './FunctionName';
export type TNFunction = {
function: FunctionName;
type: 'function';
};

View File

@ -0,0 +1,11 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { TableName } from './TableName';
export type TNTable = {
table: TableName;
type: 'table';
};

View File

@ -6,6 +6,7 @@ import type { Query } from './Query';
import type { Relationships } from './Relationships';
import type { ScalarValue } from './ScalarValue';
import type { TableName } from './TableName';
import type { TargetRedactionExpressions } from './TargetRedactionExpressions';
export type TableRequest = {
/**
@ -13,6 +14,10 @@ export type TableRequest = {
*/
foreach?: Array<Record<string, ScalarValue>> | null;
query: Query;
/**
* Expressions that can be referenced by the query to redact fields/columns
*/
redaction_expressions?: Array<TargetRedactionExpressions>;
table: TableName;
/**
* The relationships between tables involved in the entire query request

View File

@ -0,0 +1,9 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { TNFunction } from './TNFunction';
import type { TNTable } from './TNTable';
export type TargetName = (TNFunction | TNTable);

View File

@ -0,0 +1,15 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { Expression } from './Expression';
import type { TargetName } from './TargetName';
export type TargetRedactionExpressions = {
/**
* The named redaction expressions associated with the target
*/
expressions: Record<string, Expression>;
target: TargetName;
};

View File

@ -24,7 +24,7 @@
},
"dc-api-types": {
"name": "@hasura/dc-api-types",
"version": "0.35.0",
"version": "0.36.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.35.0",
"@hasura/dc-api-types": "0.36.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.35.0",
"@hasura/dc-api-types": "0.36.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.35.0",
"@hasura/dc-api-types": "0.36.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.35.0",
"@hasura/dc-api-types": "0.36.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.35.0",
"@hasura/dc-api-types": "0.36.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.35.0",
"version": "0.36.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.35.0",
"@hasura/dc-api-types": "0.36.0",
"fastify": "^4.13.0",
"mathjs": "^11.0.0",
"pino-pretty": "^8.0.0",

View File

@ -10,7 +10,7 @@
"license": "Apache-2.0",
"dependencies": {
"@fastify/cors": "^8.1.0",
"@hasura/dc-api-types": "0.35.0",
"@hasura/dc-api-types": "0.36.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.35.0",
"version": "0.36.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.35.0",
"@hasura/dc-api-types": "0.36.0",
"fastify-metrics": "^9.2.1",
"fastify": "^4.13.0",
"nanoid": "^3.3.4",

View File

@ -1185,6 +1185,7 @@ test-suite graphql-engine-tests
Hasura.Backends.DataConnector.API.V0.ColumnSpec
Hasura.Backends.DataConnector.API.V0.ConfigSchemaSpec
Hasura.Backends.DataConnector.API.V0.ExpressionSpec
Hasura.Backends.DataConnector.API.V0.FunctionSpec
Hasura.Backends.DataConnector.API.V0.MutationsSpec
Hasura.Backends.DataConnector.API.V0.OrderBySpec
Hasura.Backends.DataConnector.API.V0.QuerySpec
@ -1192,6 +1193,7 @@ test-suite graphql-engine-tests
Hasura.Backends.DataConnector.API.V0.ScalarSpec
Hasura.Backends.DataConnector.API.V0.SchemaSpec
Hasura.Backends.DataConnector.API.V0.TableSpec
Hasura.Backends.DataConnector.API.V0.TargetSpec
Hasura.Backends.Postgres.Connection.VersionCheckSpec
Hasura.Backends.Postgres.Execute.PrepareSpec
Hasura.Backends.Postgres.NativeQueries.NativeQueriesSpec

View File

@ -150,15 +150,15 @@ tests = describe "Aggregate Query Tests" $ do
( emptyQuery
& API.qFields
?~ mkFieldsMap
[ ("ArtistIds_Id", API.ColumnField (API.ColumnName "ArtistId") (API.ScalarType "number")),
("ArtistNames_Name", API.ColumnField (API.ColumnName "Name") (API.ScalarType "string")),
[ ("ArtistIds_Id", API.ColumnField (API.ColumnName "ArtistId") (API.ScalarType "number") Nothing),
("ArtistNames_Name", API.ColumnField (API.ColumnName "Name") (API.ScalarType "string") Nothing),
( "nodes_Albums",
API.RelField
( API.RelationshipField
(API.RelationshipName "Albums")
( emptyQuery
& API.qFields
?~ mkFieldsMap [("nodes_Title", API.ColumnField (API.ColumnName "Title") (API.ScalarType "string"))]
?~ mkFieldsMap [("nodes_Title", API.ColumnField (API.ColumnName "Title") (API.ScalarType "string") Nothing)]
)
)
)
@ -280,7 +280,7 @@ tests = describe "Aggregate Query Tests" $ do
& API.qAggregates
?~ mkFieldsMap
[ ("counts_count", API.StarCount),
("counts_uniqueBillingCountries", API.ColumnCount (API.ColumnCountAggregate (API.ColumnName "BillingCountry") True)),
("counts_uniqueBillingCountries", API.ColumnCount (API.ColumnCountAggregate (API.ColumnName "BillingCountry") Nothing 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")))
]
@ -309,7 +309,7 @@ tests = describe "Aggregate Query Tests" $ do
)
singleColumnAggregateMax :: API.ColumnName -> API.ScalarType -> API.SingleColumnAggregate
singleColumnAggregateMax = API.SingleColumnAggregate $ API.SingleColumnAggregateFunction [G.name|max|]
singleColumnAggregateMax columnName scalarType = API.SingleColumnAggregate (API.SingleColumnAggregateFunction [G.name|max|]) columnName Nothing scalarType
singleColumnAggregateMin :: API.ColumnName -> API.ScalarType -> API.SingleColumnAggregate
singleColumnAggregateMin = API.SingleColumnAggregate $ API.SingleColumnAggregateFunction [G.name|min|]
singleColumnAggregateMin columnName scalarType = API.SingleColumnAggregate (API.SingleColumnAggregateFunction [G.name|min|]) columnName Nothing scalarType

View File

@ -144,8 +144,8 @@ tests = describe "Basic Tests" $ do
( emptyQuery
& API.qFields
?~ mkFieldsMap
[ ("id", API.ColumnField (API.ColumnName "AlbumId") (API.ScalarType "number")),
("title", API.ColumnField (API.ColumnName "Title") (API.ScalarType "string"))
[ ("id", API.ColumnField (API.ColumnName "AlbumId") (API.ScalarType "number") Nothing),
("title", API.ColumnField (API.ColumnName "Title") (API.ScalarType "string") Nothing)
]
& API.qLimit
?~ 1
@ -188,8 +188,8 @@ tests = describe "Basic Tests" $ do
( emptyQuery
& API.qFields
?~ mkFieldsMap
[ ("id", API.ColumnField (API.ColumnName "AlbumId") (API.ScalarType "number")),
("title", API.ColumnField (API.ColumnName "Title") (API.ScalarType "string"))
[ ("id", API.ColumnField (API.ColumnName "AlbumId") (API.ScalarType "number") Nothing),
("title", API.ColumnField (API.ColumnName "Title") (API.ScalarType "string") Nothing)
]
& API.qLimit
?~ 1
@ -243,8 +243,8 @@ tests = describe "Basic Tests" $ do
( emptyQuery
& API.qFields
?~ mkFieldsMap
[ ("id", API.ColumnField (API.ColumnName "ArtistId") $ API.ScalarType "number"),
("name", API.ColumnField (API.ColumnName "Name") $ API.ScalarType "string")
[ ("id", API.ColumnField (API.ColumnName "ArtistId") (API.ScalarType "number") Nothing),
("name", API.ColumnField (API.ColumnName "Name") (API.ScalarType "string") Nothing)
]
& API.qLimit
?~ 3 -- The permissions limit is smaller than the query limit, so it is used
@ -294,14 +294,14 @@ tests = describe "Basic Tests" $ do
( emptyQuery
& API.qFields
?~ mkFieldsMap
[ ("CustomerId", API.ColumnField (API.ColumnName "CustomerId") $ API.ScalarType "number")
[ ("CustomerId", API.ColumnField (API.ColumnName "CustomerId") (API.ScalarType "number") Nothing)
]
& API.qWhere
?~ API.Exists
(API.UnrelatedTable $ mkTableName "Employee")
( API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "EmployeeId") $ API.ScalarType "number")
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "EmployeeId") (API.ScalarType "number") Nothing)
(API.ScalarValueComparison $ API.ScalarValue (J.Number 1) (API.ScalarType "number"))
)
)

View File

@ -93,12 +93,12 @@ tests = describe "Custom scalar parsing tests" $ do
( 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")
[ ("MyIntColumn", API.ColumnField (API.ColumnName "MyIntColumn") (API.ScalarType "MyInt") Nothing),
("MyFloatColumn", API.ColumnField (API.ColumnName "MyFloatColumn") (API.ScalarType "MyFloat") Nothing),
("MyStringColumn", API.ColumnField (API.ColumnName "MyStringColumn") (API.ScalarType "MyString") Nothing),
("MyBooleanColumn", API.ColumnField (API.ColumnName "MyBooleanColumn") (API.ScalarType "MyBoolean") Nothing),
("MyIDColumn", API.ColumnField (API.ColumnName "MyIDColumn") (API.ScalarType "MyID") Nothing),
("MyAnythingColumn", API.ColumnField (API.ColumnName "MyAnythingColumn") (API.ScalarType "MyAnything") Nothing)
]
& API.qLimit
?~ 1
@ -151,12 +151,12 @@ tests = describe "Custom scalar parsing tests" $ do
( 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")
[ ("MyIntColumn", API.ColumnField (API.ColumnName "MyIntColumn") (API.ScalarType "MyInt") Nothing),
("MyFloatColumn", API.ColumnField (API.ColumnName "MyFloatColumn") (API.ScalarType "MyFloat") Nothing),
("MyStringColumn", API.ColumnField (API.ColumnName "MyStringColumn") (API.ScalarType "MyString") Nothing),
("MyBooleanColumn", API.ColumnField (API.ColumnName "MyBooleanColumn") (API.ScalarType "MyBoolean") Nothing),
("MyIDColumn", API.ColumnField (API.ColumnName "MyIDColumn") (API.ScalarType "MyID") Nothing),
("MyAnythingColumn", API.ColumnField (API.ColumnName "MyAnythingColumn") (API.ScalarType "MyAnything") Nothing)
]
& API.qLimit
?~ 1
@ -165,27 +165,27 @@ tests = describe "Custom scalar parsing tests" $ do
( Set.fromList
[ ApplyBinaryComparisonOperator
Equal
(ComparisonColumn CurrentTable (mkColumnSelector $ ColumnName "MyBooleanColumn") (ScalarType "MyBoolean"))
(ComparisonColumn CurrentTable (mkColumnSelector $ ColumnName "MyBooleanColumn") (ScalarType "MyBoolean") Nothing)
(ScalarValueComparison $ ScalarValue (J.Bool True) (ScalarType "MyBoolean")),
ApplyBinaryComparisonOperator
Equal
(ComparisonColumn CurrentTable (mkColumnSelector $ ColumnName "MyFloatColumn") (ScalarType "MyFloat"))
(ComparisonColumn CurrentTable (mkColumnSelector $ ColumnName "MyFloatColumn") (ScalarType "MyFloat") Nothing)
(ScalarValueComparison $ ScalarValue (J.Number 3.14) (ScalarType "MyFloat")),
ApplyBinaryComparisonOperator
Equal
(ComparisonColumn CurrentTable (mkColumnSelector $ ColumnName "MyStringColumn") (ScalarType "MyString"))
(ComparisonColumn CurrentTable (mkColumnSelector $ ColumnName "MyStringColumn") (ScalarType "MyString") Nothing)
(ScalarValueComparison $ ScalarValue (J.String "foo") (ScalarType "MyString")),
ApplyBinaryComparisonOperator
Equal
(ComparisonColumn CurrentTable (mkColumnSelector $ ColumnName "MyIDColumn") (ScalarType "MyID"))
(ComparisonColumn CurrentTable (mkColumnSelector $ ColumnName "MyIDColumn") (ScalarType "MyID") Nothing)
(ScalarValueComparison $ ScalarValue (J.String "x") (ScalarType "MyID")),
ApplyBinaryComparisonOperator
Equal
(ComparisonColumn CurrentTable (mkColumnSelector $ ColumnName "MyIntColumn") (ScalarType "MyInt"))
(ComparisonColumn CurrentTable (mkColumnSelector $ ColumnName "MyIntColumn") (ScalarType "MyInt") Nothing)
(ScalarValueComparison $ ScalarValue (J.Number 42.0) (ScalarType "MyInt")),
ApplyBinaryComparisonOperator
Equal
(ComparisonColumn CurrentTable (mkColumnSelector $ ColumnName "MyAnythingColumn") (ScalarType "MyAnything"))
(ComparisonColumn CurrentTable (mkColumnSelector $ ColumnName "MyAnythingColumn") (ScalarType "MyAnything") Nothing)
(ScalarValueComparison $ ScalarValue (J.Object mempty) (ScalarType "MyAnything"))
]
)

View File

@ -204,17 +204,17 @@ tests = do
$ Set.fromList
[ API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "ArtistId") $ API.ScalarType "number")
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "ArtistId") (API.ScalarType "number") Nothing)
(API.ScalarValueComparison $ API.ScalarValue (J.Number 90) (API.ScalarType "number")),
API.ApplyBinaryComparisonOperator
API.GreaterThan
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "AlbumId") $ API.ScalarType "number")
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "AlbumId") (API.ScalarType "number") Nothing)
(API.ScalarValueComparison $ API.ScalarValue (J.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_AlbumId", API.ColumnField (API.ColumnName "AlbumId") (API.ScalarType "number") Nothing),
("deletedRows_Title", API.ColumnField (API.ColumnName "Title") (API.ScalarType "string") Nothing),
( "deletedRows_Artist",
API.RelField
( API.RelationshipField
@ -222,8 +222,8 @@ tests = do
( emptyQuery
& API.qFields
?~ mkFieldsMap
[ ("ArtistId", API.ColumnField (API.ColumnName "ArtistId") $ API.ScalarType "number"),
("Name", API.ColumnField (API.ColumnName "Name") $ API.ScalarType "string")
[ ("ArtistId", API.ColumnField (API.ColumnName "ArtistId") (API.ScalarType "number") Nothing),
("Name", API.ColumnField (API.ColumnName "Name") (API.ScalarType "string") Nothing)
]
)
)
@ -312,17 +312,17 @@ tests = do
$ Set.fromList
[ API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "ArtistId") $ API.ScalarType "number")
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "ArtistId") (API.ScalarType "number") Nothing)
(API.ScalarValueComparison $ API.ScalarValue (J.Number 90) (API.ScalarType "number")),
API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "AlbumId") $ API.ScalarType "number")
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "AlbumId") (API.ScalarType "number") Nothing)
(API.ScalarValueComparison $ API.ScalarValue (J.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")),
[ ("AlbumId", API.ColumnField (API.ColumnName "AlbumId") (API.ScalarType "number") Nothing),
("Title", API.ColumnField (API.ColumnName "Title") (API.ScalarType "string") Nothing),
( "Artist",
API.RelField
( API.RelationshipField
@ -330,8 +330,8 @@ tests = do
( emptyQuery
& API.qFields
?~ mkFieldsMap
[ ("ArtistId", API.ColumnField (API.ColumnName "ArtistId") $ API.ScalarType "number"),
("Name", API.ColumnField (API.ColumnName "Name") $ API.ScalarType "string")
[ ("ArtistId", API.ColumnField (API.ColumnName "ArtistId") (API.ScalarType "number") Nothing),
("Name", API.ColumnField (API.ColumnName "Name") (API.ScalarType "string") Nothing)
]
)
)

View File

@ -100,8 +100,8 @@ tests = describe "Error Protocol Tests" $ do
( emptyQuery
& API.qFields
?~ mkFieldsMap
[ ("id", API.ColumnField (API.ColumnName "AlbumId") $ API.ScalarType "number"),
("title", API.ColumnField (API.ColumnName "Title") $ API.ScalarType "string")
[ ("id", API.ColumnField (API.ColumnName "AlbumId") (API.ScalarType "number") Nothing),
("title", API.ColumnField (API.ColumnName "Title") (API.ScalarType "string") Nothing)
]
& API.qLimit
?~ 1

View File

@ -221,12 +221,12 @@ tests = do
Just
$ API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "ArtistId") $ API.ScalarType "number")
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "ArtistId") (API.ScalarType "number") Nothing)
(API.ScalarValueComparison $ API.ScalarValue (J.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_AlbumId", API.ColumnField (API.ColumnName "AlbumId") (API.ScalarType "number") Nothing),
("insertedRows_Title", API.ColumnField (API.ColumnName "Title") (API.ScalarType "string") Nothing),
( "insertedRows_Artist",
API.RelField
( API.RelationshipField
@ -234,8 +234,8 @@ tests = do
( emptyQuery
& API.qFields
?~ mkFieldsMap
[ ("ArtistId", API.ColumnField (API.ColumnName "ArtistId") $ API.ScalarType "number"),
("Name", API.ColumnField (API.ColumnName "Name") $ API.ScalarType "string")
[ ("ArtistId", API.ColumnField (API.ColumnName "ArtistId") (API.ScalarType "number") Nothing),
("Name", API.ColumnField (API.ColumnName "Name") (API.ScalarType "string") Nothing)
]
)
)

View File

@ -119,13 +119,13 @@ tests = describe "Order By Tests" $ do
( emptyQuery
& API.qFields
?~ mkFieldsMap
[ ("AlbumId", API.ColumnField (API.ColumnName "AlbumId") $ API.ScalarType "number"),
("Title", API.ColumnField (API.ColumnName "Title") $ API.ScalarType "string")
[ ("AlbumId", API.ColumnField (API.ColumnName "AlbumId") (API.ScalarType "number") Nothing),
("Title", API.ColumnField (API.ColumnName "Title") (API.ScalarType "string") Nothing)
]
& API.qLimit
?~ 3
& API.qOrderBy
?~ API.OrderBy mempty (API.OrderByElement [] (API.OrderByColumn (API.ColumnName "AlbumId")) API.Ascending :| [])
?~ API.OrderBy mempty (API.OrderByElement [] (API.OrderByColumn (API.ColumnName "AlbumId") Nothing) API.Ascending :| [])
)
)
@ -163,7 +163,7 @@ tests = describe "Order By Tests" $ do
(mkTableName "Artist")
( emptyQuery
& API.qFields
?~ mkFieldsMap [("Name", API.ColumnField (API.ColumnName "Name") (API.ScalarType "string"))]
?~ mkFieldsMap [("Name", API.ColumnField (API.ColumnName "Name") (API.ScalarType "string") Nothing)]
& API.qLimit
?~ 2
& API.qOrderBy
@ -182,6 +182,7 @@ tests = describe "Order By Tests" $ do
$ API.SingleColumnAggregate
(API.SingleColumnAggregateFunction [G.name|max|])
(API.ColumnName "AlbumId")
Nothing
(API.ScalarType "number")
)
API.Ascending

View File

@ -198,19 +198,19 @@ tests = describe "Object Relationships Tests" $ do
( emptyQuery
& API.qFields
?~ mkFieldsMap
[ ("Name", API.ColumnField (API.ColumnName "Name") $ API.ScalarType "string"),
[ ("Name", API.ColumnField (API.ColumnName "Name") (API.ScalarType "string") Nothing),
( "Genre",
API.RelField
( API.RelationshipField
(API.RelationshipName "Genre")
(emptyQuery & API.qFields ?~ mkFieldsMap [("Name", API.ColumnField (API.ColumnName "Name") $ API.ScalarType "string")])
(emptyQuery & API.qFields ?~ mkFieldsMap [("Name", API.ColumnField (API.ColumnName "Name") (API.ScalarType "string") Nothing)])
)
),
( "MediaType",
API.RelField
( API.RelationshipField
(API.RelationshipName "MediaType")
(emptyQuery & API.qFields ?~ mkFieldsMap [("Name", API.ColumnField (API.ColumnName "Name") $ API.ScalarType "string")])
(emptyQuery & API.qFields ?~ mkFieldsMap [("Name", API.ColumnField (API.ColumnName "Name") (API.ScalarType "string") Nothing)])
)
)
]
@ -298,7 +298,7 @@ tests = describe "Object Relationships Tests" $ do
( emptyQuery
& API.qFields
?~ mkFieldsMap
[ ("Name", API.ColumnField (API.ColumnName "Name") $ API.ScalarType "string"),
[ ("Name", API.ColumnField (API.ColumnName "Name") (API.ScalarType "string") Nothing),
( "Album",
API.RelField
( API.RelationshipField
@ -310,7 +310,7 @@ tests = describe "Object Relationships Tests" $ do
API.RelField
( API.RelationshipField
(API.RelationshipName "Artist")
(emptyQuery & API.qFields ?~ mkFieldsMap [("Name", API.ColumnField (API.ColumnName "Name") (API.ScalarType "string"))])
(emptyQuery & API.qFields ?~ mkFieldsMap [("Name", API.ColumnField (API.ColumnName "Name") (API.ScalarType "string") Nothing)])
)
)
]
@ -338,8 +338,8 @@ tests = describe "Object Relationships Tests" $ do
]
)
( NE.fromList
[ API.OrderByElement [API.RelationshipName "Album", API.RelationshipName "Artist"] (API.OrderByColumn (API.ColumnName "Name")) API.Descending,
API.OrderByElement [] (API.OrderByColumn (API.ColumnName "Name")) API.Ascending
[ API.OrderByElement [API.RelationshipName "Album", API.RelationshipName "Artist"] (API.OrderByColumn (API.ColumnName "Name") Nothing) API.Descending,
API.OrderByElement [] (API.OrderByColumn (API.ColumnName "Name") Nothing) API.Ascending
]
)
)
@ -409,7 +409,7 @@ tests = describe "Object Relationships Tests" $ do
(mkTableName "Employee")
( emptyQuery
& API.qFields
?~ mkFieldsMap [("EmployeeId", API.ColumnField (API.ColumnName "EmployeeId") $ API.ScalarType "number")]
?~ mkFieldsMap [("EmployeeId", API.ColumnField (API.ColumnName "EmployeeId") (API.ScalarType "number") Nothing)]
& API.qLimit
?~ 1
& API.qWhere
@ -417,8 +417,8 @@ tests = describe "Object Relationships Tests" $ do
(API.RelatedTable $ API.RelationshipName "SupportRepForCustomers")
( API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "Country") $ API.ScalarType "string")
(API.AnotherColumnComparison (API.ComparisonColumn API.QueryTable (API.mkColumnSelector $ API.ColumnName "Country") $ API.ScalarType "string"))
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "Country") (API.ScalarType "string") Nothing)
(API.AnotherColumnComparison (API.ComparisonColumn API.QueryTable (API.mkColumnSelector $ API.ColumnName "Country") (API.ScalarType "string") Nothing))
)
& API.qOrderBy
?~ API.OrderBy
@ -429,8 +429,8 @@ tests = describe "Object Relationships Tests" $ do
$ API.Exists (API.RelatedTable $ API.RelationshipName "SupportRep")
$ API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "Country") $ API.ScalarType "string")
(API.AnotherColumnComparison (API.ComparisonColumn API.QueryTable (API.mkColumnSelector $ API.ColumnName "Country") $ API.ScalarType "string"))
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "Country") (API.ScalarType "string") Nothing)
(API.AnotherColumnComparison (API.ComparisonColumn API.QueryTable (API.mkColumnSelector $ API.ColumnName "Country") (API.ScalarType "string") Nothing))
)
mempty
)

View File

@ -270,8 +270,8 @@ tests = do
( emptyQuery
& API.qFields
?~ mkFieldsMap
[ ("AlbumId", API.ColumnField (API.ColumnName "AlbumId") $ API.ScalarType "number"),
("Title", API.ColumnField (API.ColumnName "Title") $ API.ScalarType "string")
[ ("AlbumId", API.ColumnField (API.ColumnName "AlbumId") (API.ScalarType "number") Nothing),
("Title", API.ColumnField (API.ColumnName "Title") (API.ScalarType "string") Nothing)
]
)
& API._QRTable
@ -361,8 +361,8 @@ tests = do
( emptyQuery
& API.qFields
?~ mkFieldsMap
[ ("AlbumId", API.ColumnField (API.ColumnName "AlbumId") $ API.ScalarType "number"),
("Title", API.ColumnField (API.ColumnName "Title") $ API.ScalarType "string")
[ ("AlbumId", API.ColumnField (API.ColumnName "AlbumId") (API.ScalarType "number") Nothing),
("Title", API.ColumnField (API.ColumnName "Title") (API.ScalarType "string") Nothing)
]
)
& API._QRTable
@ -464,8 +464,8 @@ tests = do
( emptyQuery
& API.qFields
?~ mkFieldsMap
[ ("nodes_AlbumId", API.ColumnField (API.ColumnName "AlbumId") $ API.ScalarType "number"),
("nodes_Title", API.ColumnField (API.ColumnName "Title") $ API.ScalarType "string")
[ ("nodes_AlbumId", API.ColumnField (API.ColumnName "AlbumId") (API.ScalarType "number") Nothing),
("nodes_Title", API.ColumnField (API.ColumnName "Title") (API.ScalarType "string") Nothing)
]
& API.qAggregates
?~ mkFieldsMap [("aggregate_count", API.StarCount)]

View File

@ -19,13 +19,13 @@ mkTableName :: Text -> API.TableName
mkTableName name = API.TableName (name :| [])
mkTableRequest :: API.TableName -> API.Query -> API.QueryRequest
mkTableRequest tableName query = API.QRTable $ API.TableRequest tableName mempty query Nothing
mkTableRequest tableName query = API.QRTable $ API.TableRequest tableName mempty mempty query Nothing
emptyQuery :: API.Query
emptyQuery = API.Query Nothing Nothing Nothing Nothing Nothing Nothing Nothing
emptyMutationRequest :: API.MutationRequest
emptyMutationRequest = API.MutationRequest mempty mempty []
emptyMutationRequest = API.MutationRequest mempty mempty mempty []
mkRowsQueryResponse :: [[(Text, API.FieldValue)]] -> API.QueryResponse
mkRowsQueryResponse rows = API.QueryResponse (Just $ mkFieldsMap <$> rows) Nothing

View File

@ -139,8 +139,8 @@ tests = describe "Transformed Configuration Tests" $ do
( emptyQuery
& API.qFields
?~ mkFieldsMap
[ ("id", API.ColumnField (API.ColumnName "AlbumId") $ API.ScalarType "number"),
("title", API.ColumnField (API.ColumnName "Title") $ API.ScalarType "string")
[ ("id", API.ColumnField (API.ColumnName "AlbumId") (API.ScalarType "number") Nothing),
("title", API.ColumnField (API.ColumnName "Title") (API.ScalarType "string") Nothing)
]
& API.qLimit
?~ 1

View File

@ -239,28 +239,28 @@ tests = do
$ Set.fromList
[ API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "AlbumId") $ API.ScalarType "number")
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "AlbumId") (API.ScalarType "number") Nothing)
(API.ScalarValueComparison $ API.ScalarValue (J.Number 3) (API.ScalarType "number")),
API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "GenreId") $ API.ScalarType "number")
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "GenreId") (API.ScalarType "number") Nothing)
(API.ScalarValueComparison $ API.ScalarValue (J.Number 1) (API.ScalarType "number"))
],
API._umoPostUpdateCheck =
Just
$ API.ApplyBinaryComparisonOperator
API.GreaterThan
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "UnitPrice") $ API.ScalarType "number")
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "UnitPrice") (API.ScalarType "number") Nothing)
(API.ScalarValueComparison $ API.ScalarValue (J.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_TrackId", API.ColumnField (API.ColumnName "TrackId") (API.ScalarType "number") Nothing),
("updatedRows_Name", API.ColumnField (API.ColumnName "Name") (API.ScalarType "string") Nothing),
( "updatedRows_Genre",
API.RelField
( API.RelationshipField
(API.RelationshipName "Genre")
(emptyQuery & API.qFields ?~ mkFieldsMap [("Name", API.ColumnField (API.ColumnName "Name") $ API.ScalarType "string")])
(emptyQuery & API.qFields ?~ mkFieldsMap [("Name", API.ColumnField (API.ColumnName "Name") (API.ScalarType "string") Nothing)])
)
)
]
@ -366,17 +366,17 @@ tests = do
Just
$ API.ApplyBinaryComparisonOperator
API.GreaterThan
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "UnitPrice") $ API.ScalarType "number")
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "UnitPrice") (API.ScalarType "number") Nothing)
(API.ScalarValueComparison $ API.ScalarValue (J.Number 0) (API.ScalarType "number"))
let sharedReturning =
mkFieldsMap
[ ("updatedRows_TrackId", API.ColumnField (API.ColumnName "TrackId") (API.ScalarType "number")),
("updatedRows_Name", API.ColumnField (API.ColumnName "Name") (API.ScalarType "string")),
[ ("updatedRows_TrackId", API.ColumnField (API.ColumnName "TrackId") (API.ScalarType "number") Nothing),
("updatedRows_Name", API.ColumnField (API.ColumnName "Name") (API.ScalarType "string") Nothing),
( "updatedRows_Genre",
API.RelField
( API.RelationshipField
(API.RelationshipName "Genre")
(emptyQuery & API.qFields ?~ mkFieldsMap [("Name", API.ColumnField (API.ColumnName "Name") $ API.ScalarType "string")])
(emptyQuery & API.qFields ?~ mkFieldsMap [("Name", API.ColumnField (API.ColumnName "Name") (API.ScalarType "string") Nothing)])
)
)
]
@ -429,11 +429,11 @@ tests = do
$ Set.fromList
[ API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "AlbumId") $ API.ScalarType "number")
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "AlbumId") (API.ScalarType "number") Nothing)
(API.ScalarValueComparison $ API.ScalarValue (J.Number 3) (API.ScalarType "number")),
API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "TrackId") $ API.ScalarType "number")
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "TrackId") (API.ScalarType "number") Nothing)
(API.ScalarValueComparison $ API.ScalarValue (J.Number 3) (API.ScalarType "number"))
],
API._umoPostUpdateCheck = sharedPostUpdateCheck,
@ -469,11 +469,11 @@ tests = do
$ Set.fromList
[ API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "AlbumId") $ API.ScalarType "number")
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "AlbumId") (API.ScalarType "number") Nothing)
(API.ScalarValueComparison $ API.ScalarValue (J.Number 3) (API.ScalarType "number")),
API.ApplyBinaryComparisonOperator
API.GreaterThan
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "TrackId") $ API.ScalarType "number")
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "TrackId") (API.ScalarType "number") Nothing)
(API.ScalarValueComparison $ API.ScalarValue (J.Number 3) (API.ScalarType "number"))
],
API._umoPostUpdateCheck = sharedPostUpdateCheck,

View File

@ -100,6 +100,7 @@ library
Hasura.Backends.DataConnector.API.V0.Scalar
Hasura.Backends.DataConnector.API.V0.Schema
Hasura.Backends.DataConnector.API.V0.Table
Hasura.Backends.DataConnector.API.V0.Target
Hasura.Backends.DataConnector.API.V0.Dataset
other-modules:
Hasura.Backends.DataConnector.API.V0.Name

View File

@ -15,6 +15,7 @@ module Hasura.Backends.DataConnector.API.V0
module Scalar,
module Schema,
module Table,
module Target,
module Dataset,
)
where
@ -36,3 +37,4 @@ import Hasura.Backends.DataConnector.API.V0.Relationships as Relationships
import Hasura.Backends.DataConnector.API.V0.Scalar as Scalar
import Hasura.Backends.DataConnector.API.V0.Schema as Schema
import Hasura.Backends.DataConnector.API.V0.Table as Table
import Hasura.Backends.DataConnector.API.V0.Target as Target

View File

@ -16,6 +16,7 @@ import Data.HashMap.Strict qualified as HashMap
import Data.OpenApi (ToSchema)
import GHC.Generics (Generic)
import Hasura.Backends.DataConnector.API.V0.Column qualified as API.V0
import Hasura.Backends.DataConnector.API.V0.Expression qualified as API.V0
import Hasura.Backends.DataConnector.API.V0.Name (nameCodec)
import Hasura.Backends.DataConnector.API.V0.Scalar qualified as API.V0
import Language.GraphQL.Draft.Syntax qualified as GQL
@ -24,6 +25,7 @@ import Prelude
data SingleColumnAggregate = SingleColumnAggregate
{ _scaFunction :: SingleColumnAggregateFunction,
_scaColumn :: API.V0.ColumnName,
_scaRedactionExpression :: Maybe API.V0.RedactionExpressionName,
_scaResultType :: API.V0.ScalarType
}
deriving stock (Eq, Ord, Show, Generic)
@ -33,6 +35,7 @@ singleColumnAggregateObjectCodec =
SingleColumnAggregate
<$> requiredField "function" "The aggregation function" .= _scaFunction
<*> requiredField "column" "The column to apply the aggregation function to" .= _scaColumn
<*> optionalFieldOrNull "redaction_expression" "If present, the name of the redaction expression to evaluate. If the expression is false, the column value must be nulled out before being aggregated." .= _scaRedactionExpression
<*> requiredField "result_type" "The scalar type of the result of the aggregate operation" .= _scaResultType
newtype SingleColumnAggregateFunction = SingleColumnAggregateFunction {unSingleColumnAggregateFunction :: GQL.Name}
@ -47,6 +50,7 @@ instance HasCodec SingleColumnAggregateFunction where
data ColumnCountAggregate = ColumnCountAggregate
{ _ccaColumn :: API.V0.ColumnName,
_ccaRedactionExpression :: Maybe API.V0.RedactionExpressionName,
_ccaDistinct :: Bool
}
deriving stock (Eq, Ord, Show, Generic, Data)
@ -55,6 +59,7 @@ columnCountAggregateObjectCodec :: JSONObjectCodec ColumnCountAggregate
columnCountAggregateObjectCodec =
ColumnCountAggregate
<$> requiredField "column" "The column to apply the count aggregate function to" .= _ccaColumn
<*> optionalFieldOrNull "redaction_expression" "If present, the name of the redaction expression to evaluate. If the expression is false, the column value must be nulled out before being aggregated." .= _ccaRedactionExpression
<*> requiredField "distinct" "Whether or not only distinct items should be counted" .= _ccaDistinct
data Aggregate

View File

@ -32,7 +32,9 @@ module Hasura.Backends.DataConnector.API.V0.Capabilities
ColumnNullability (..),
QueryCapabilities (..),
qcForeach,
qcRedaction,
ForeachCapabilities (..),
RedactionCapabilities (..),
MutationCapabilities (..),
InsertCapabilities (..),
UpdateCapabilities (..),
@ -173,7 +175,8 @@ instance HasCodec ColumnNullability where
]
data QueryCapabilities = QueryCapabilities
{ _qcForeach :: Maybe ForeachCapabilities
{ _qcForeach :: Maybe ForeachCapabilities,
_qcRedaction :: Maybe RedactionCapabilities
}
deriving stock (Eq, Ord, Show, Generic, Data)
deriving anyclass (NFData, Hashable)
@ -184,6 +187,7 @@ instance HasCodec QueryCapabilities where
object "QueryCapabilities" $
QueryCapabilities
<$> optionalField "foreach" "Whether or not the agent supports foreach queries, which are used to enable remote joins to the agent" .= _qcForeach
<*> optionalField "redaction" "Whether or not the agent supports redaction expressions in the query" .= _qcRedaction
data ForeachCapabilities = ForeachCapabilities {}
deriving stock (Eq, Ord, Show, Generic, Data)
@ -194,6 +198,15 @@ instance HasCodec ForeachCapabilities where
codec =
object "ForeachCapabilities" $ pure ForeachCapabilities
data RedactionCapabilities = RedactionCapabilities {}
deriving stock (Eq, Ord, Show, Generic, Data)
deriving anyclass (NFData, Hashable)
deriving (FromJSON, ToJSON, ToSchema) via Autodocodec RedactionCapabilities
instance HasCodec RedactionCapabilities where
codec =
object "RedactionCapabilities" $ pure RedactionCapabilities
data MutationCapabilities = MutationCapabilities
{ _mcInsertCapabilities :: Maybe InsertCapabilities,
_mcUpdateCapabilities :: Maybe UpdateCapabilities,

View File

@ -12,6 +12,9 @@ module Hasura.Backends.DataConnector.API.V0.Expression
ColumnSelector (..),
mkColumnSelector,
ComparisonValue (..),
TargetRedactionExpressions (..),
RedactionExpressionName (..),
RedactionExpression (..),
)
where
@ -19,8 +22,9 @@ import Autodocodec.Extended
import Autodocodec.OpenAPI ()
import Control.DeepSeq (NFData)
import Control.Lens ((^.), _1, _2, _3, _4)
import Data.Aeson (FromJSON, ToJSON, Value)
import Data.Aeson (FromJSON, FromJSONKey, ToJSON, ToJSONKey, Value)
import Data.Data (Data)
import Data.HashMap.Strict (HashMap)
import Data.HashMap.Strict qualified as HashMap
import Data.Hashable (Hashable)
import Data.List.NonEmpty (NonEmpty (..))
@ -34,6 +38,7 @@ import Hasura.Backends.DataConnector.API.V0.Column qualified as API.V0
import Hasura.Backends.DataConnector.API.V0.Relationships qualified as API.V0
import Hasura.Backends.DataConnector.API.V0.Scalar qualified as API.V0
import Hasura.Backends.DataConnector.API.V0.Table qualified as API.V0
import Hasura.Backends.DataConnector.API.V0.Target qualified as API.V0
import Prelude
--------------------------------------------------------------------------------
@ -250,7 +255,10 @@ data ComparisonColumn = ComparisonColumn
-- | The name of the column
_ccName :: ColumnSelector,
-- | The scalar type of the column
_ccColumnType :: API.V0.ScalarType
_ccColumnType :: API.V0.ScalarType,
-- | If present, the name of the redaction expression to evaluate.
-- If the expression is false, the column value must be nulled out before being compared to.
_ccRedactionExpression :: Maybe RedactionExpressionName
}
deriving stock (Eq, Ord, Show, Generic)
deriving (FromJSON, ToJSON, ToSchema) via Autodocodec ComparisonColumn
@ -263,6 +271,7 @@ instance HasCodec ComparisonColumn where
<$> optionalFieldWithOmittedDefault "path" CurrentTable "The path to the table that contains the specified column. Missing or empty array means the current table. [\"$\"] means the query table. No other values are supported at this time." .= _ccPath
<*> requiredField "name" "The name of the column" .= _ccName
<*> requiredField "column_type" "The scalar type of the column" .= _ccColumnType
<*> optionalFieldOrNull "redaction_expression" "If present, the name of the redaction expression to evaluate. If the expression is false, the column value must be nulled out before being compared to." .= _ccRedactionExpression
-- | Describes what table a column is located on. This may either be the "current" table
-- (which would be query table, or the table specified by the closest ancestor 'Exists'
@ -332,3 +341,35 @@ instance HasCodec ComparisonValue where
[ ("column", ("AnotherColumnComparison", mapToDecoder AnotherColumnComparison columnCodec)),
("scalar", ("ScalarValueComparison", mapToDecoder ScalarValueComparison objectCodec))
]
data TargetRedactionExpressions = TargetRedactionExpressions
{ _treTarget :: API.V0.TargetName,
_treExpressions :: HashMap RedactionExpressionName RedactionExpression
}
deriving stock (Eq, Ord, Show, Generic)
deriving anyclass (NFData, Hashable)
deriving (FromJSON, ToJSON, ToSchema) via Autodocodec TargetRedactionExpressions
instance HasCodec TargetRedactionExpressions where
codec =
object "TargetRedactionExpressions" $
TargetRedactionExpressions
<$> requiredField "target" "The target entity with whom the redaction expressions are to be used with" .= _treTarget
<*> requiredField "expressions" "The named redaction expressions associated with the target" .= _treExpressions
newtype RedactionExpressionName = RedactionExpressionName {unRedactionExpressionName :: Text}
deriving stock (Eq, Ord, Show, Generic, Data)
deriving anyclass (NFData, Hashable)
deriving newtype (ToJSONKey, FromJSONKey)
deriving (FromJSON, ToJSON, ToSchema) via Autodocodec RedactionExpressionName
instance HasCodec RedactionExpressionName where
codec = named "RedactionExpressionName" $ dimapCodec RedactionExpressionName unRedactionExpressionName textCodec
newtype RedactionExpression = RedactionExpression {unRedactionExpression :: Expression}
deriving stock (Eq, Ord, Show, Generic)
deriving anyclass (NFData, Hashable)
deriving (FromJSON, ToJSON, ToSchema) via Autodocodec RedactionExpression
instance HasCodec RedactionExpression where
codec = dimapCodec RedactionExpression unRedactionExpression codec

View File

@ -5,6 +5,7 @@
module Hasura.Backends.DataConnector.API.V0.Mutations
( MutationRequest (..),
mrTableRelationships,
mrRedactionExpressions,
mrInsertSchema,
mrOperations,
TableInsertSchema (..),
@ -82,6 +83,7 @@ import Prelude
-- TODO: Does this need to be enhanced ala. QueryRequest to support FunctionRequests?
data MutationRequest = MutationRequest
{ _mrTableRelationships :: Set API.V0.TableRelationships,
_mrRedactionExpressions :: Set API.V0.TargetRedactionExpressions,
_mrInsertSchema :: Set TableInsertSchema,
_mrOperations :: [MutationOperation]
}
@ -95,6 +97,8 @@ instance HasCodec MutationRequest where
MutationRequest
<$> requiredField "table_relationships" "The relationships between tables involved in the entire mutation request"
.= _mrTableRelationships
<*> optionalFieldWithOmittedDefault "redaction_expressions" mempty "Expressions that can be referenced by the query to redact fields/columns"
.= _mrRedactionExpressions
<*> requiredField "insert_schema" "The schema by which to interpret row data specified in any insert operations in this request"
.= _mrInsertSchema
<*> requiredField "operations" "The mutation operations to perform"

View File

@ -76,7 +76,7 @@ instance HasCodec OrderByElement where
<*> requiredField "order_direction" "The direction of ordering to apply" .= _obeOrderDirection
data OrderByTarget
= OrderByColumn API.V0.ColumnName
= OrderByColumn API.V0.ColumnName (Maybe API.V0.RedactionExpressionName)
| OrderByStarCountAggregate
| OrderBySingleColumnAggregate API.V0.SingleColumnAggregate
deriving stock (Eq, Generic, Ord, Show)
@ -87,16 +87,19 @@ instance HasCodec OrderByTarget where
object "OrderByTarget" $
discriminatedUnionCodec "type" enc dec
where
columnCodec = requiredField' "column"
columnCodec =
(,)
<$> requiredField' "column" .= fst
<*> optionalFieldOrNull "redaction_expression" "If present, the name of the redaction expression to evaluate. If the expression is false, the column value must be nulled out before being ordered over." .= snd
starAggregateCodec = pureCodec ()
singleColumnAggregateCodec = API.V0.singleColumnAggregateObjectCodec
enc = \case
OrderByColumn c -> ("column", mapToEncoder c columnCodec)
OrderByColumn c r -> ("column", mapToEncoder (c, r) columnCodec)
OrderByStarCountAggregate -> ("star_count_aggregate", mapToEncoder () starAggregateCodec)
OrderBySingleColumnAggregate agg -> ("single_column_aggregate", mapToEncoder agg singleColumnAggregateCodec)
dec =
HashMap.fromList
[ ("column", ("OrderByColumn", mapToDecoder OrderByColumn columnCodec)),
[ ("column", ("OrderByColumn", mapToDecoder (uncurry OrderByColumn) columnCodec)),
("star_count_aggregate", ("OrderByStarCountAggregate", mapToDecoder (const OrderByStarCountAggregate) starAggregateCodec)),
("single_column_aggregate", ("OrderBySingleColumnAggregate", mapToDecoder OrderBySingleColumnAggregate singleColumnAggregateCodec))
]

View File

@ -17,12 +17,14 @@ module Hasura.Backends.DataConnector.API.V0.Query
qrForeach,
trTable,
trRelationships,
trRedactionExpressions,
trQuery,
trForeach,
frFunction,
frFunctionArguments,
frQuery,
frRelationships,
frRedactionExpressions,
FieldName (..),
Query (..),
qFields,
@ -72,6 +74,7 @@ import Data.OpenApi (ToSchema)
import Data.Set (Set)
import Data.Text (Text)
import Data.Text qualified as T
import Data.Tuple.Extra (fst3, snd3, thd3, uncurry3)
import GHC.Generics (Generic)
import GHC.Show (appPrec, appPrec1)
import Hasura.Backends.DataConnector.API.V0.Aggregate qualified as API.V0
@ -125,13 +128,14 @@ instance HasCodec QueryRequest where
("function", ("FunctionRequest", mapToDecoder QRFunction objectCodec))
]
pattern TableQueryRequest :: API.V0.TableName -> Set API.V0.Relationships -> Query -> Maybe (NonEmpty (HashMap API.V0.ColumnName API.V0.ScalarValue)) -> QueryRequest
pattern TableQueryRequest table relationships query foreach = QRTable (TableRequest table relationships query foreach)
pattern TableQueryRequest :: API.V0.TableName -> Set API.V0.Relationships -> Set API.V0.TargetRedactionExpressions -> Query -> Maybe (NonEmpty (HashMap API.V0.ColumnName API.V0.ScalarValue)) -> QueryRequest
pattern TableQueryRequest table relationships redactionExps query foreach = QRTable (TableRequest table relationships redactionExps query foreach)
-- | A serializable request to retrieve strutured data from tables.
data TableRequest = TableRequest
{ _trTable :: API.V0.TableName,
_trRelationships :: Set API.V0.Relationships,
_trRedactionExpressions :: Set API.V0.TargetRedactionExpressions,
_trQuery :: Query,
_trForeach :: Maybe (NonEmpty (HashMap API.V0.ColumnName API.V0.ScalarValue))
}
@ -146,19 +150,22 @@ instance HasObjectCodec TableRequest where
-- NOTE: This can't be done immediately as it would break compatibility in agents.
<*> requiredField "table_relationships" "The relationships between tables involved in the entire query request"
.= _trRelationships
<*> optionalFieldWithOmittedDefault "redaction_expressions" mempty "Expressions that can be referenced by the query to redact fields/columns"
.= _trRedactionExpressions
<*> requiredField "query" "The details of the query against the table"
.= _trQuery
<*> optionalFieldOrNull "foreach" "If present, a list of columns and values for the columns that the query must be repeated for, applying the column values as a filter for each query."
.= _trForeach
pattern FunctionQueryRequest :: API.V0.FunctionName -> [FunctionArgument] -> Set API.V0.Relationships -> Query -> QueryRequest
pattern FunctionQueryRequest function args relationships query = QRFunction (FunctionRequest function args relationships query)
pattern FunctionQueryRequest :: API.V0.FunctionName -> [FunctionArgument] -> Set API.V0.Relationships -> Set API.V0.TargetRedactionExpressions -> Query -> QueryRequest
pattern FunctionQueryRequest function args relationships redactionExps query = QRFunction (FunctionRequest function args relationships redactionExps query)
-- | A serializable request to compute strutured data from a function.
data FunctionRequest = FunctionRequest
{ _frFunction :: API.V0.FunctionName,
_frFunctionArguments :: [FunctionArgument],
_frRelationships :: Set API.V0.Relationships,
_frRedactionExpressions :: Set API.V0.TargetRedactionExpressions,
_frQuery :: Query
}
deriving stock (Eq, Ord, Show, Generic)
@ -217,6 +224,8 @@ instance HasObjectCodec FunctionRequest where
.= _frFunctionArguments
<*> requiredField "relationships" "The relationships between entities involved in the entire query request"
.= _frRelationships
<*> optionalFieldWithOmittedDefault "redaction_expressions" mempty "Expressions that can be referenced by the query to redact fields/columns"
.= _frRedactionExpressions
<*> requiredField "query" "The details of the query against the table"
.= _frQuery
@ -313,7 +322,7 @@ arrayFieldObjectCodec =
-- 3. an "object", which indicates that the field contains a nested object
-- 4. an "array", which indicates that the field contains a nested array
data Field
= ColumnField API.V0.ColumnName API.V0.ScalarType
= ColumnField API.V0.ColumnName API.V0.ScalarType (Maybe API.V0.RedactionExpressionName)
| RelField RelationshipField
| NestedObjField API.V0.ColumnName Query
| NestedArrayField ArrayField
@ -327,11 +336,12 @@ instance HasCodec Field where
discriminatedUnionCodec "type" enc dec
where
columnCodec =
(,)
(,,)
<$> requiredField' "column"
.= fst
.= fst3
<*> requiredField' "column_type"
.= snd
.= snd3
<*> optionalFieldOrNull "redaction_expression" "If present, the name of the redaction expression to evaluate. If the expression is false, the column value must be nulled out." .= thd3
nestedObjCodec =
(,)
<$> requiredField' "column"
@ -339,13 +349,13 @@ instance HasCodec Field where
<*> requiredField' "query"
.= snd
enc = \case
ColumnField columnName scalarType -> ("column", mapToEncoder (columnName, scalarType) columnCodec)
ColumnField columnName scalarType redactionExpName -> ("column", mapToEncoder (columnName, scalarType, redactionExpName) columnCodec)
RelField relField -> ("relationship", mapToEncoder relField relationshipFieldObjectCodec)
NestedObjField columnName nestedObjQuery -> ("object", mapToEncoder (columnName, nestedObjQuery) nestedObjCodec)
NestedArrayField arrayField -> ("array", mapToEncoder arrayField arrayFieldObjectCodec)
dec =
HashMap.fromList
[ ("column", ("ColumnField", mapToDecoder (uncurry ColumnField) columnCodec)),
[ ("column", ("ColumnField", mapToDecoder (uncurry3 ColumnField) columnCodec)),
("relationship", ("RelationshipField", mapToDecoder RelField relationshipFieldObjectCodec)),
("object", ("NestedObjField", mapToDecoder (uncurry NestedObjField) nestedObjCodec)),
("array", ("NestedArrayField", mapToDecoder NestedArrayField arrayFieldObjectCodec))

View File

@ -0,0 +1,48 @@
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE TemplateHaskell #-}
module Hasura.Backends.DataConnector.API.V0.Target
( TargetName (..),
)
where
import Autodocodec.Extended
import Autodocodec.OpenAPI ()
import Control.DeepSeq (NFData)
import Control.Lens.TH (makePrisms)
import Data.Aeson (FromJSON, ToJSON)
import Data.Data (Data)
import Data.HashMap.Strict qualified as HashMap
import Data.Hashable (Hashable)
import Data.OpenApi (ToSchema)
import GHC.Generics (Generic)
import Hasura.Backends.DataConnector.API.V0.Function qualified as API.V0
import Hasura.Backends.DataConnector.API.V0.Table qualified as API.V0
import Prelude
data TargetName
= TNTable API.V0.TableName
| TNFunction API.V0.FunctionName
deriving stock (Eq, Ord, Show, Generic, Data)
deriving anyclass (NFData, Hashable)
deriving (FromJSON, ToJSON, ToSchema) via Autodocodec TargetName
instance HasCodec TargetName where
codec = object "TargetName" $ discriminatedUnionCodec "type" enc dec
where
tableKey = "table"
functionKey = "function"
tnTableObjectCodec = requiredField "table" "The name of the table to query"
tnFunctionObjectCodec = requiredField "function" "The name of the function"
enc = \case
(TNTable t) -> (tableKey, mapToEncoder t tnTableObjectCodec)
(TNFunction f) -> (functionKey, mapToEncoder f tnFunctionObjectCodec)
dec =
HashMap.fromList
[ (tableKey, ("TNTable", mapToDecoder TNTable tnTableObjectCodec)),
(functionKey, ("TNFunction", mapToDecoder TNFunction tnFunctionObjectCodec))
]
$(makePrisms ''TargetName)

View File

@ -517,8 +517,8 @@ mkTestData schemaResponse testConfig =
_tdColumnInsertSchema = columnInsertSchema schemaResponse testConfig,
_tdRowColumnOperatorValue = rowColumnOperatorValue schemaResponse testConfig,
_tdFindColumnScalarType = \tableName name -> findColumnScalarType schemaResponse tableName (formatColumnName testConfig $ API.ColumnName name),
_tdQueryComparisonColumn = API.ComparisonColumn API.QueryTable . API.mkColumnSelector . formatColumnName testConfig . API.ColumnName,
_tdCurrentComparisonColumn = API.ComparisonColumn API.CurrentTable . API.mkColumnSelector . formatColumnName testConfig . API.ColumnName,
_tdQueryComparisonColumn = \name scalarType -> API.ComparisonColumn API.QueryTable (API.mkColumnSelector . formatColumnName testConfig $ API.ColumnName name) scalarType Nothing,
_tdCurrentComparisonColumn = \name scalarType -> API.ComparisonColumn API.CurrentTable (API.mkColumnSelector . formatColumnName testConfig $ API.ColumnName name) scalarType Nothing,
_tdOrderByColumn = \targetPath name -> orderByColumn targetPath (formatColumnName testConfig $ API.ColumnName name)
}
where
@ -561,7 +561,7 @@ mkEdgeCasesTestData testConfig schemaResponse =
_ectdColumnField = columnField schemaResponse testConfig,
_ectdMkDefaultTableInsertSchema = mkDefaultTableInsertSchema schemaResponse testConfig edgeCasesSchemaTables,
_ectdRowColumnOperatorValue = rowColumnOperatorValue schemaResponse testConfig,
_ectdCurrentComparisonColumn = API.ComparisonColumn API.CurrentTable . API.mkColumnSelector . formatColumnName testConfig . API.ColumnName
_ectdCurrentComparisonColumn = \name scalarType -> API.ComparisonColumn API.CurrentTable (API.mkColumnSelector . formatColumnName testConfig $ API.ColumnName name) scalarType Nothing
}
where
tableExists :: API.TableName -> Bool
@ -636,7 +636,7 @@ formatColumnName TestConfig {..} = API.ColumnName . applyNameCasing _tcColumnNam
columnField :: API.SchemaResponse -> TestConfig -> API.TableName -> Text -> API.Field
columnField schemaResponse testConfig tableName columnName =
API.ColumnField columnName' scalarType
API.ColumnField columnName' scalarType Nothing
where
columnName' = formatColumnName testConfig $ API.ColumnName columnName
scalarType = findColumnScalarType schemaResponse tableName columnName'
@ -706,7 +706,7 @@ emptyQuery :: API.Query
emptyQuery = API.Query Nothing Nothing Nothing Nothing Nothing Nothing Nothing
emptyMutationRequest :: API.MutationRequest
emptyMutationRequest = API.MutationRequest mempty mempty mempty
emptyMutationRequest = API.MutationRequest mempty mempty mempty mempty
sortBy :: (Ixed m, Ord (IxValue m)) => Index m -> [m] -> [m]
sortBy propName = sortOn (^? ix propName)
@ -784,7 +784,7 @@ scalarValueComparison value valueType = API.ScalarValueComparison $ API.ScalarVa
orderByColumn :: [API.RelationshipName] -> API.ColumnName -> API.OrderDirection -> API.OrderByElement
orderByColumn targetPath columnName orderDirection =
API.OrderByElement targetPath (API.OrderByColumn columnName) orderDirection
API.OrderByElement targetPath (API.OrderByColumn columnName Nothing) orderDirection
insertAutoIncPk :: Text -> Integer -> [HashMap API.FieldName API.FieldValue] -> [HashMap API.FieldName API.FieldValue]
insertAutoIncPk pkFieldName startingPkId rows =

View File

@ -27,6 +27,6 @@ spec TestData {..} = describe "Error Protocol" do
(CustomBinaryComparisonOperator "FOOBAR")
(_tdCurrentComparisonColumn "ArtistId" artistIdScalarType)
(Data.scalarValueComparison (Number 1) $ artistIdScalarType)
in TableQueryRequest _tdArtistsTableName mempty query Nothing
in TableQueryRequest _tdArtistsTableName mempty mempty query Nothing
artistIdScalarType = _tdFindColumnScalarType _tdArtistsTableName "ArtistId"

View File

@ -28,4 +28,4 @@ spec TestData {..} _ = do
artistsQueryRequest =
let fields = Data.mkFieldsMap [("ArtistId", _tdColumnField _tdArtistsTableName "ArtistId"), ("Name", _tdColumnField _tdArtistsTableName "Name")]
query = Data.emptyQuery & qFields ?~ fields
in TableQueryRequest _tdArtistsTableName mempty query Nothing
in TableQueryRequest _tdArtistsTableName mempty mempty query Nothing

View File

@ -453,7 +453,7 @@ spec TestData {..} edgeCasesTestData Capabilities {..} = describe "Delete Mutati
invoiceLinesQueryRequest :: QueryRequest
invoiceLinesQueryRequest =
let query = Data.emptyQuery & qFields ?~ invoiceLinesFields & qOrderBy ?~ OrderBy mempty (_tdOrderByColumn [] "InvoiceId" Ascending :| [])
in TableQueryRequest _tdInvoiceLinesTableName mempty query Nothing
in TableQueryRequest _tdInvoiceLinesTableName mempty mempty query Nothing
invoiceIdScalarType = _tdFindColumnScalarType _tdInvoiceLinesTableName "InvoiceId"
invoiceLineIdScalarType = _tdFindColumnScalarType _tdInvoiceLinesTableName "InvoiceLineId"

View File

@ -717,7 +717,7 @@ spec TestData {..} edgeCasesTestData Capabilities {..} = describe "Insert Mutati
Data.emptyQuery
& qFields ?~ mkFieldsFromExpectedData _tdArtistsTableName (expectedInsertedArtists artistsStartingId)
& qWhere ?~ ApplyBinaryArrayComparisonOperator In (_tdCurrentComparisonColumn "ArtistId" artistIdScalarType) (J.Number . fromInteger <$> artistIds) artistIdScalarType
in TableQueryRequest _tdArtistsTableName mempty query Nothing
in TableQueryRequest _tdArtistsTableName mempty mempty query Nothing
albumsQueryRequest :: [Integer] -> QueryRequest
albumsQueryRequest albumIds =
@ -725,7 +725,7 @@ spec TestData {..} edgeCasesTestData Capabilities {..} = describe "Insert Mutati
Data.emptyQuery
& qFields ?~ mkFieldsFromExpectedData _tdAlbumsTableName (expectedInsertedAcdcAlbums albumsStartingId)
& qWhere ?~ ApplyBinaryArrayComparisonOperator In (_tdCurrentComparisonColumn "AlbumId" albumIdScalarType) (J.Number . fromInteger <$> albumIds) albumIdScalarType
in TableQueryRequest _tdAlbumsTableName mempty query Nothing
in TableQueryRequest _tdAlbumsTableName mempty mempty query Nothing
employeesQueryRequest :: [Integer] -> QueryRequest
employeesQueryRequest employeeIds =
@ -733,7 +733,7 @@ spec TestData {..} edgeCasesTestData Capabilities {..} = describe "Insert Mutati
Data.emptyQuery
& qFields ?~ mkFieldsFromExpectedData _tdEmployeesTableName (expectedInsertedEmployees employeesStartingId)
& qWhere ?~ ApplyBinaryArrayComparisonOperator In (_tdCurrentComparisonColumn "EmployeeId" albumIdScalarType) (J.Number . fromInteger <$> employeeIds) employeeIdScalarType
in TableQueryRequest _tdEmployeesTableName mempty query Nothing
in TableQueryRequest _tdEmployeesTableName mempty mempty query Nothing
artistsInsertSchema :: TableInsertSchema
artistsInsertSchema = _tdMkDefaultTableInsertSchema _tdArtistsTableName

View File

@ -719,7 +719,7 @@ spec TestData {..} edgeCasesTestData Capabilities {..} = describe "Update Mutati
artistsQueryRequest :: Expression -> QueryRequest
artistsQueryRequest whereExp =
let query = Data.emptyQuery & qFields ?~ artistsFields & qWhere ?~ whereExp
in TableQueryRequest _tdArtistsTableName mempty query Nothing
in TableQueryRequest _tdArtistsTableName mempty mempty query Nothing
invoiceLinesFields :: HashMap FieldName Field
invoiceLinesFields =
@ -734,7 +734,7 @@ spec TestData {..} edgeCasesTestData Capabilities {..} = describe "Update Mutati
invoiceLinesQueryRequest :: Expression -> QueryRequest
invoiceLinesQueryRequest whereExp =
let query = Data.emptyQuery & qFields ?~ invoiceLinesFields & qWhere ?~ whereExp
in TableQueryRequest _tdInvoiceLinesTableName mempty query Nothing
in TableQueryRequest _tdInvoiceLinesTableName mempty mempty query Nothing
incOperator :: UpdateColumnOperatorName
incOperator = UpdateColumnOperatorName $ [G.name|inc|]

View File

@ -79,7 +79,7 @@ spec TestData {..} relationshipCapabilities = describe "Aggregate Queries" $ do
describe "Column Count" $ do
it "counts all rows with non-null columns" $ do
let aggregates = Data.mkFieldsMap [("count_cols", ColumnCount $ ColumnCountAggregate (_tdColumnName "BillingState") False)]
let aggregates = Data.mkFieldsMap [("count_cols", ColumnCount $ ColumnCountAggregate (_tdColumnName "BillingState") Nothing False)]
let queryRequest = invoicesQueryRequest aggregates
response <- queryGuarded queryRequest
@ -92,7 +92,7 @@ spec TestData {..} relationshipCapabilities = describe "Aggregate Queries" $ do
it "can count all rows with non-null values in a column, after applying pagination and filtering" $ do
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 aggregates = Data.mkFieldsMap [("count_cols", ColumnCount $ ColumnCountAggregate (_tdColumnName "BillingState") Nothing False)]
let queryRequest = invoicesQueryRequest aggregates & qrQuery %~ (qAggregatesLimit ?~ aggregatesLimit >>> qWhere ?~ where')
response <- queryGuarded queryRequest
@ -109,7 +109,7 @@ spec TestData {..} relationshipCapabilities = describe "Aggregate Queries" $ do
Data.responseRows response `rowsShouldBe` []
it "can count all rows with distinct non-null values in a column" $ do
let aggregates = Data.mkFieldsMap [("count_cols", ColumnCount $ ColumnCountAggregate (_tdColumnName "BillingState") True)]
let aggregates = Data.mkFieldsMap [("count_cols", ColumnCount $ ColumnCountAggregate (_tdColumnName "BillingState") Nothing True)]
let queryRequest = invoicesQueryRequest aggregates
response <- queryGuarded queryRequest
@ -124,7 +124,7 @@ spec TestData {..} relationshipCapabilities = describe "Aggregate Queries" $ do
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 aggregates = Data.mkFieldsMap [("count_cols", ColumnCount $ ColumnCountAggregate (_tdColumnName "BillingState") Nothing True)]
let queryRequest = invoicesQueryRequest aggregates & qrQuery %~ (qAggregatesLimit ?~ aggregatesLimit >>> qWhere ?~ where' >>> qOrderBy ?~ orderBy)
response <- queryGuarded queryRequest
@ -143,7 +143,7 @@ spec TestData {..} relationshipCapabilities = describe "Aggregate Queries" $ do
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 aggregates = Data.mkFieldsMap [("count_cols", ColumnCount $ ColumnCountAggregate (_tdColumnName "BillingState") Nothing False)]
let queryRequest = invoicesQueryRequest aggregates & qrQuery . qLimit ?~ limit
response <- queryGuarded queryRequest
@ -241,7 +241,7 @@ spec TestData {..} relationshipCapabilities = describe "Aggregate Queries" $ do
let aggregates =
Data.mkFieldsMap
[ ("count", StarCount),
("distinctBillingStates", ColumnCount $ ColumnCountAggregate (_tdColumnName "BillingState") True),
("distinctBillingStates", ColumnCount $ ColumnCountAggregate (_tdColumnName "BillingState") Nothing True),
("maxTotal", singleColumnAggregateMax (_tdColumnName "Total") invoiceTotalScalarType)
]
let queryRequest = invoicesQueryRequest aggregates
@ -597,7 +597,7 @@ spec TestData {..} relationshipCapabilities = describe "Aggregate Queries" $ do
artistOrderBy = OrderBy mempty $ _tdOrderByColumn [] "ArtistId" Ascending :| []
artistQuery = Data.emptyQuery & qFields ?~ artistFields & qOrderBy ?~ artistOrderBy
artistsTableRelationships = Data.onlyKeepRelationships [_tdAlbumsRelationshipName] _tdArtistsTableRelationships
in QRTable $ TableRequest _tdArtistsTableName (Set.fromList [API.RTable artistsTableRelationships]) artistQuery Nothing
in QRTable $ TableRequest _tdArtistsTableName (Set.fromList [API.RTable artistsTableRelationships]) mempty artistQuery Nothing
-- This query is basically what would be generated by this complex HGE GraphQL query
-- @
@ -673,35 +673,36 @@ spec TestData {..} relationshipCapabilities = describe "Aggregate Queries" $ do
API.RTable $ Data.onlyKeepRelationships [_tdInvoiceLinesRelationshipName, _tdMediaTypeRelationshipName] _tdTracksTableRelationships
]
)
mempty
artistQuery
Nothing
artistsQueryRequest :: HashMap FieldName Aggregate -> QueryRequest
artistsQueryRequest aggregates =
let query = Data.emptyQuery & qAggregates ?~ aggregates
in TableQueryRequest _tdArtistsTableName mempty query Nothing
in TableQueryRequest _tdArtistsTableName mempty mempty query Nothing
invoicesQueryRequest :: HashMap FieldName Aggregate -> QueryRequest
invoicesQueryRequest aggregates =
let query = Data.emptyQuery & qAggregates ?~ aggregates
in TableQueryRequest _tdInvoicesTableName mempty query Nothing
in TableQueryRequest _tdInvoicesTableName mempty mempty query Nothing
albumsQueryRequest :: QueryRequest
albumsQueryRequest =
TableQueryRequest _tdAlbumsTableName mempty Data.emptyQuery Nothing
TableQueryRequest _tdAlbumsTableName mempty mempty Data.emptyQuery Nothing
aggregate :: (NonEmpty a -> Value) -> [a] -> Value
aggregate aggFn values =
maybe Null aggFn $ NonEmpty.nonEmpty values
singleColumnAggregateMax :: ColumnName -> ScalarType -> Aggregate
singleColumnAggregateMax columnName resultType = SingleColumn $ SingleColumnAggregate (SingleColumnAggregateFunction [G.name|max|]) columnName resultType
singleColumnAggregateMax columnName resultType = SingleColumn $ SingleColumnAggregate (SingleColumnAggregateFunction [G.name|max|]) columnName Nothing resultType
singleColumnAggregateMin :: ColumnName -> ScalarType -> Aggregate
singleColumnAggregateMin columnName resultType = SingleColumn $ SingleColumnAggregate (SingleColumnAggregateFunction [G.name|min|]) columnName resultType
singleColumnAggregateMin columnName resultType = SingleColumn $ SingleColumnAggregate (SingleColumnAggregateFunction [G.name|min|]) columnName Nothing resultType
singleColumnAggregateSum :: ColumnName -> ScalarType -> Aggregate
singleColumnAggregateSum columnName resultType = SingleColumn $ SingleColumnAggregate (SingleColumnAggregateFunction [G.name|sum|]) columnName resultType
singleColumnAggregateSum columnName resultType = SingleColumn $ SingleColumnAggregate (SingleColumnAggregateFunction [G.name|sum|]) columnName Nothing resultType
billingCityScalarType = _tdFindColumnScalarType _tdInvoicesTableName "BillingCity"
billingCountryScalarType = _tdFindColumnScalarType _tdInvoicesTableName "BillingCountry"

View File

@ -74,10 +74,10 @@ spec TestData {..} = describe "Basic Queries" $ do
artistsQueryRequest =
let fields = Data.mkFieldsMap [("ArtistId", _tdColumnField _tdArtistsTableName "ArtistId"), ("Name", _tdColumnField _tdArtistsTableName "Name")]
query = Data.emptyQuery & qFields ?~ fields
in TableQueryRequest _tdArtistsTableName mempty query Nothing
in TableQueryRequest _tdArtistsTableName mempty mempty query Nothing
albumsQueryRequest :: QueryRequest
albumsQueryRequest =
let fields = Data.mkFieldsMap [("AlbumId", _tdColumnField _tdAlbumsTableName "AlbumId"), ("ArtistId", _tdColumnField _tdAlbumsTableName "ArtistId"), ("Title", _tdColumnField _tdAlbumsTableName "Title")]
query = Data.emptyQuery & qFields ?~ fields
in TableQueryRequest _tdAlbumsTableName mempty query Nothing
in TableQueryRequest _tdAlbumsTableName mempty mempty query Nothing

View File

@ -42,12 +42,12 @@ spec TestData {..} (ScalarTypesCapabilities scalarTypesCapabilities) = describe
let queryRequest =
let fields = Data.mkFieldsMap [(unColumnName columnName, _tdColumnField tableName (unColumnName columnName))]
query' = Data.emptyQuery & qFields ?~ fields
in TableQueryRequest tableName mempty query' Nothing
in TableQueryRequest tableName mempty mempty query' Nothing
where' =
ApplyBinaryComparisonOperator
(CustomBinaryComparisonOperator (unName operatorName))
(_tdCurrentComparisonColumn (unColumnName columnName) columnType)
(AnotherColumnComparison $ ComparisonColumn CurrentTable (mkColumnSelector argColumnName) argType)
(AnotherColumnComparison $ ComparisonColumn CurrentTable (mkColumnSelector argColumnName) argType Nothing)
query =
queryRequest
& qrQuery . qWhere ?~ where'

View File

@ -313,13 +313,13 @@ spec TestData {..} comparisonCapabilities = describe "Filtering in Queries" $ do
artistsQueryRequest =
let fields = Data.mkFieldsMap [("ArtistId", _tdColumnField _tdArtistsTableName "ArtistId"), ("Name", _tdColumnField _tdArtistsTableName "Name")]
query = Data.emptyQuery & qFields ?~ fields
in TableQueryRequest _tdArtistsTableName mempty query Nothing
in TableQueryRequest _tdArtistsTableName mempty mempty query Nothing
albumsQueryRequest :: QueryRequest
albumsQueryRequest =
let fields = Data.mkFieldsMap [("AlbumId", _tdColumnField _tdAlbumsTableName "AlbumId"), ("ArtistId", _tdColumnField _tdAlbumsTableName "ArtistId"), ("Title", _tdColumnField _tdAlbumsTableName "Title")]
query = Data.emptyQuery & qFields ?~ fields
in TableQueryRequest _tdAlbumsTableName mempty query Nothing
in TableQueryRequest _tdAlbumsTableName mempty mempty query Nothing
albumIdScalarType = _tdFindColumnScalarType _tdAlbumsTableName "AlbumId"
albumTitleScalarType = _tdFindColumnScalarType _tdAlbumsTableName "Title"

View File

@ -261,13 +261,13 @@ spec TestData {..} Capabilities {..} = describe "Foreach Queries" $ do
albumsQueryRequest =
let fields = Data.mkFieldsMap [("AlbumId", _tdColumnField _tdAlbumsTableName "AlbumId"), ("ArtistId", _tdColumnField _tdAlbumsTableName "ArtistId"), ("Title", _tdColumnField _tdAlbumsTableName "Title")]
query = Data.emptyQuery & qFields ?~ fields
in TableQueryRequest _tdAlbumsTableName mempty query Nothing
in TableQueryRequest _tdAlbumsTableName mempty mempty query Nothing
playlistTracksQueryRequest :: QueryRequest
playlistTracksQueryRequest =
let fields = Data.mkFieldsMap [("PlaylistId", _tdColumnField _tdPlaylistTracksTableName "PlaylistId"), ("TrackId", _tdColumnField _tdPlaylistTracksTableName "TrackId")]
query = Data.emptyQuery & qFields ?~ fields
in TableQueryRequest _tdPlaylistTracksTableName mempty query Nothing
in TableQueryRequest _tdPlaylistTracksTableName mempty mempty query Nothing
mkForeachIds :: TableName -> [(Text, J.Value)] -> HashMap ColumnName ScalarValue
mkForeachIds tableName =

View File

@ -409,13 +409,13 @@ spec TestData {..} Capabilities {..} = describe "Order By in Queries" $ do
albumsQueryRequest :: QueryRequest
albumsQueryRequest =
TableQueryRequest _tdAlbumsTableName mempty albumsQuery Nothing
TableQueryRequest _tdAlbumsTableName mempty mempty albumsQuery Nothing
artistsQueryRequest :: QueryRequest
artistsQueryRequest =
let fields = Data.mkFieldsMap [("ArtistId", _tdColumnField _tdArtistsTableName "ArtistId"), ("Name", _tdColumnField _tdArtistsTableName "Name")]
query = Data.emptyQuery & qFields ?~ fields
in TableQueryRequest _tdArtistsTableName mempty query Nothing
in TableQueryRequest _tdArtistsTableName mempty mempty query Nothing
tracksQuery :: Query
tracksQuery =
@ -424,16 +424,16 @@ spec TestData {..} Capabilities {..} = describe "Order By in Queries" $ do
tracksQueryRequest :: QueryRequest
tracksQueryRequest =
TableQueryRequest _tdTracksTableName mempty tracksQuery Nothing
TableQueryRequest _tdTracksTableName mempty mempty tracksQuery Nothing
invoicesQueryRequest :: QueryRequest
invoicesQueryRequest =
let fields = Data.mkFieldsMap [("InvoiceId", _tdColumnField _tdInvoicesTableName "InvoiceId"), ("BillingState", _tdColumnField _tdInvoicesTableName "BillingState")]
query = Data.emptyQuery & qFields ?~ fields
in TableQueryRequest _tdInvoicesTableName mempty query Nothing
in TableQueryRequest _tdInvoicesTableName mempty mempty query Nothing
orderBySingleColumnAggregateMax :: ColumnName -> ScalarType -> OrderByTarget
orderBySingleColumnAggregateMax columnName resultType = OrderBySingleColumnAggregate $ SingleColumnAggregate (SingleColumnAggregateFunction [G.name|max|]) columnName resultType
orderBySingleColumnAggregateMax columnName resultType = OrderBySingleColumnAggregate $ SingleColumnAggregate (SingleColumnAggregateFunction [G.name|max|]) columnName Nothing resultType
albumTitleScalarType = _tdFindColumnScalarType _tdAlbumsTableName "Title"
artistNameScalarType = _tdFindColumnScalarType _tdArtistsTableName "Name"

View File

@ -247,7 +247,7 @@ spec TestData {..} subqueryComparisonCapabilities = describe "Relationship Queri
("Artist", RelField $ RelationshipField _tdArtistRelationshipName artistsSubquery)
]
query = albumsQuery & qFields ?~ fields
in TableQueryRequest _tdAlbumsTableName (Set.fromList [API.RTable $ Data.onlyKeepRelationships [_tdArtistRelationshipName] _tdAlbumsTableRelationships]) query Nothing
in TableQueryRequest _tdAlbumsTableName (Set.fromList [API.RTable $ Data.onlyKeepRelationships [_tdArtistRelationshipName] _tdAlbumsTableRelationships]) mempty query Nothing
artistsWithAlbumsQuery :: (Query -> Query) -> QueryRequest
artistsWithAlbumsQuery modifySubquery =
@ -261,7 +261,7 @@ spec TestData {..} subqueryComparisonCapabilities = describe "Relationship Queri
("Albums", RelField $ RelationshipField _tdAlbumsRelationshipName albumsSubquery)
]
query = artistsQuery & qFields ?~ fields
in TableQueryRequest _tdArtistsTableName (Set.fromList [API.RTable $ Data.onlyKeepRelationships [_tdAlbumsRelationshipName] _tdArtistsTableRelationships]) query Nothing
in TableQueryRequest _tdArtistsTableName (Set.fromList [API.RTable $ Data.onlyKeepRelationships [_tdAlbumsRelationshipName] _tdArtistsTableRelationships]) mempty query Nothing
employeesWithCustomersQuery :: (Query -> Query) -> QueryRequest
employeesWithCustomersQuery modifySubquery =
@ -273,7 +273,7 @@ spec TestData {..} subqueryComparisonCapabilities = describe "Relationship Queri
[ ("SupportRepForCustomers", RelField $ RelationshipField _tdSupportRepForCustomersRelationshipName customersSubquery)
]
query = employeesQuery & qFields ?~ fields
in TableQueryRequest _tdEmployeesTableName (Set.fromList [API.RTable $ Data.onlyKeepRelationships [_tdSupportRepForCustomersRelationshipName] _tdEmployeesTableRelationships]) query Nothing
in TableQueryRequest _tdEmployeesTableName (Set.fromList [API.RTable $ Data.onlyKeepRelationships [_tdSupportRepForCustomersRelationshipName] _tdEmployeesTableRelationships]) mempty query Nothing
customersWithSupportRepQuery :: (Query -> Query) -> QueryRequest
customersWithSupportRepQuery modifySubquery =
@ -284,7 +284,7 @@ spec TestData {..} subqueryComparisonCapabilities = describe "Relationship Queri
[ ("SupportRep", RelField $ RelationshipField _tdSupportRepRelationshipName supportRepSubquery)
]
query = customersQuery & qFields ?~ fields
in TableQueryRequest _tdCustomersTableName (Set.fromList [API.RTable $ Data.onlyKeepRelationships [_tdSupportRepRelationshipName] _tdCustomersTableRelationships]) query Nothing
in TableQueryRequest _tdCustomersTableName (Set.fromList [API.RTable $ Data.onlyKeepRelationships [_tdSupportRepRelationshipName] _tdCustomersTableRelationships]) mempty query Nothing
artistsQuery :: Query
artistsQuery =

View File

@ -60,7 +60,7 @@ spec testConfig API.Capabilities {} = describe "supports functions" $ preloadAge
k = "take" :: Text.Text
v = API.ScalarValue (Number (fromIntegral fibonacciRows)) (API.ScalarType "number")
args = [NamedArgument k (API.ScalarArgumentValue v)]
in QRFunction $ FunctionRequest _ftdFibonacciFunctionName args mempty query'
in FunctionQueryRequest _ftdFibonacciFunctionName args mempty mempty query'
testData@FunctionsTestData {..} = mkFunctionsTestData preloadedSchema testConfig
query = fibonacciRequest testData
@ -96,7 +96,7 @@ spec testConfig API.Capabilities {} = describe "supports functions" $ preloadAge
relationships = Set.singleton (API.RFunction authorRelationship)
v = API.ScalarValue (String "x") (API.ScalarType "string")
args = [NamedArgument "query" (API.ScalarArgumentValue v)]
in QRFunction $ FunctionRequest _ftdSearchArticlesFunctionName args relationships query'
in FunctionQueryRequest _ftdSearchArticlesFunctionName args relationships mempty query'
testData = mkFunctionsTestData preloadedSchema testConfig
query = articlesRequest testData
@ -130,7 +130,7 @@ spec testConfig API.Capabilities {} = describe "supports functions" $ preloadAge
whereClause =
API.ApplyBinaryComparisonOperator
API.LessThan
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "id") (API.ScalarType "number"))
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "id") (API.ScalarType "number") Nothing)
(API.ScalarValueComparison (API.ScalarValue (Number 10) (API.ScalarType "number")))
query' = Data.emptyQuery & qFields ?~ fields & qWhere ?~ whereClause & qLimit ?~ 2
authorRelationship =
@ -144,7 +144,7 @@ spec testConfig API.Capabilities {} = describe "supports functions" $ preloadAge
relationships = Set.singleton (API.RFunction authorRelationship)
v = API.ScalarValue (String "y") (API.ScalarType "string")
args = [NamedArgument "query" (API.ScalarArgumentValue v)]
in QRFunction $ FunctionRequest _ftdSearchArticlesFunctionName args relationships query'
in FunctionQueryRequest _ftdSearchArticlesFunctionName args relationships mempty query'
testData = mkFunctionsTestData preloadedSchema testConfig
query = articlesRequest testData

View File

@ -60,7 +60,8 @@ capabilities =
API._cQueries =
Just
API.QueryCapabilities
{ _qcForeach = Just API.ForeachCapabilities
{ _qcForeach = Just API.ForeachCapabilities,
_qcRedaction = Just API.RedactionCapabilities
},
API._cMutations =
Just

View File

@ -53,7 +53,7 @@ instance BackendExecute 'DataConnector where
type ExecutionMonad 'DataConnector = AgentClientT
mkDBQueryPlan UserInfo {..} sourceName sourceConfig ir _headers _gName = do
queryPlan@Plan {..} <- flip runReaderT sourceConfig $ Plan.mkQueryPlan _uiSession ir
queryPlan@Plan {..} <- flip runReaderT (API._cScalarTypes $ _scCapabilities sourceConfig, _uiSession) $ Plan.mkQueryPlan ir
transformedSourceConfig <- transformSourceConfig sourceConfig (Just _uiSession)
pure
DBStepInfo
@ -65,7 +65,7 @@ instance BackendExecute 'DataConnector where
}
mkDBQueryExplain fieldName UserInfo {..} sourceName sourceConfig ir _headers _gName = do
queryPlan@Plan {..} <- flip runReaderT sourceConfig $ Plan.mkQueryPlan _uiSession ir
queryPlan@Plan {..} <- flip runReaderT (API._cScalarTypes $ _scCapabilities sourceConfig, _uiSession) $ Plan.mkQueryPlan ir
transformedSourceConfig <- transformSourceConfig sourceConfig (Just _uiSession)
pure
$ mkAnyBackend @'DataConnector
@ -78,7 +78,7 @@ instance BackendExecute 'DataConnector where
}
mkDBMutationPlan _env _manager _logger UserInfo {..} _stringifyNum sourceName sourceConfig mutationDB _headers _gName _maybeSelSetArgs = do
mutationPlan@Plan {..} <- flip runReaderT sourceConfig $ Plan.mkMutationPlan _uiSession mutationDB
mutationPlan@Plan {..} <- flip runReaderT (API._cScalarTypes $ _scCapabilities sourceConfig, _uiSession) $ Plan.mkMutationPlan mutationDB
transformedSourceConfig <- transformSourceConfig sourceConfig (Just _uiSession)
pure
DBStepInfo
@ -96,7 +96,7 @@ instance BackendExecute 'DataConnector where
throw400 NotSupported "mkLiveQuerySubscriptionPlan: not implemented for the Data Connector backend."
mkDBRemoteRelationshipPlan UserInfo {..} sourceName sourceConfig joinIds joinIdsSchema argumentIdFieldName (resultFieldName, ir) _ _ _ = do
remoteRelationshipPlan@Plan {..} <- flip runReaderT sourceConfig $ Plan.mkRemoteRelationshipPlan _uiSession sourceConfig joinIds joinIdsSchema argumentIdFieldName resultFieldName ir
remoteRelationshipPlan@Plan {..} <- flip runReaderT (API._cScalarTypes $ _scCapabilities sourceConfig, _uiSession) $ Plan.mkRemoteRelationshipPlan sourceConfig joinIds joinIdsSchema argumentIdFieldName resultFieldName ir
transformedSourceConfig <- transformSourceConfig sourceConfig (Just _uiSession)
pure
DBStepInfo

View File

@ -1,4 +1,3 @@
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DerivingStrategies #-}
-- | This module contains Data Connector request/response planning code and utility
@ -11,15 +10,19 @@
-- for example 'Hasura.Backends.DataConnector.Plan.QueryPlan.mkQueryPlan`.
module Hasura.Backends.DataConnector.Plan.Common
( Plan (..),
writeOutput,
replaceOutput,
TableRelationships (..),
TableRelationshipsKey (..),
recordTableRelationship,
recordTableRelationshipFromRelInfo,
FieldPrefix,
noPrefix,
prefixWith,
applyPrefix,
Cardinality (..),
recordTableRelationship,
recordTableRelationshipFromRelInfo,
RedactionExpressionState (..),
recordRedactionExpression,
translateRedactionExpressions,
prepareLiteral,
translateBoolExpToExpression,
mkRelationshipName,
@ -28,7 +31,6 @@ module Hasura.Backends.DataConnector.Plan.Common
)
where
import Control.Monad.Trans.Writer.CPS qualified as CPS
import Data.Aeson qualified as J
import Data.Aeson.Encoding qualified as JE
import Data.Aeson.Types qualified as J
@ -42,6 +44,7 @@ import Data.Set qualified as Set
import Data.Text qualified as T
import Data.Text.Encoding qualified as TE
import Data.Text.Extended (toTxt, (<<>), (<>>))
import Data.Tuple (swap)
import Hasura.Backends.DataConnector.API qualified as API
import Hasura.Backends.DataConnector.Adapter.Backend
import Hasura.Backends.DataConnector.Adapter.Types
@ -70,18 +73,30 @@ data Plan request response = Plan
--------------------------------------------------------------------------------
-- | Key datatype for TableRelationships to avoid having an Either directly as the key,
-- and make extending the types of relationships easier in future.
data TableRelationshipsKey
= FunctionNameKey API.FunctionName
| TableNameKey API.TableName
deriving stock (Eq, Show, Generic)
deriving anyclass (Hashable)
-- | Writes some output to state, like one might do if one was using a Writer monad.
-- The output is combined with the existing output using '<>' from 'Semigroup'
writeOutput :: (Semigroup output, MonadState state m, Has output state) => output -> m ()
writeOutput x = modify $ modifier (<> x)
-- | Replaces some output in the state with a new version of the output. Also, a value
-- can be returned from the replacement function.
--
-- This is useful if you need to inspect the existing state, make a decision, and update it
-- based on that decision. The result of the decision can be returned from the transformation
-- as your 'a' value.
replaceOutput :: (MonadState state m, Has output state) => (output -> (output, a)) -> m a
replaceOutput replace = do
output <- gets getter
let (newOutput, retval) = replace output
modify (modifier (const newOutput))
pure retval
--------------------------------------------------------------------------------
-- | A monoidal data structure used to record Table Relationships encountered during request
-- translation. Used with 'recordTableRelationship'.
newtype TableRelationships = TableRelationships
{unTableRelationships :: HashMap TableRelationshipsKey (HashMap API.RelationshipName API.Relationship)}
{unTableRelationships :: HashMap API.TargetName (HashMap API.RelationshipName API.Relationship)}
deriving stock (Eq, Show)
instance Semigroup TableRelationships where
@ -93,26 +108,23 @@ instance Monoid TableRelationships where
-- | Records a table relationship encountered during request translation into the output of the current
-- 'CPS.WriterT'
recordTableRelationship ::
( Has TableRelationships writerOutput,
Monoid writerOutput,
MonadError QErr m
( MonadState state m,
Has TableRelationships state
) =>
TableRelationshipsKey ->
API.TargetName ->
API.RelationshipName ->
API.Relationship ->
CPS.WriterT writerOutput m ()
m ()
recordTableRelationship sourceName relationshipName relationship =
let newRelationship = TableRelationships $ HashMap.singleton sourceName (HashMap.singleton relationshipName relationship)
in CPS.tell $ modifier (const newRelationship) mempty
writeOutput $ TableRelationships $ HashMap.singleton sourceName (HashMap.singleton relationshipName relationship)
recordTableRelationshipFromRelInfo ::
( Has TableRelationships writerOutput,
Monoid writerOutput,
MonadError QErr m
( MonadState state m,
Has TableRelationships state
) =>
TableRelationshipsKey ->
API.TargetName ->
RelInfo 'DataConnector ->
CPS.WriterT writerOutput m (API.RelationshipName, API.Relationship)
m (API.RelationshipName, API.Relationship)
recordTableRelationshipFromRelInfo sourceTableName RelInfo {..} = do
let relationshipName = mkRelationshipName riName
let relationshipType = case riType of
@ -135,6 +147,55 @@ recordTableRelationshipFromRelInfo sourceTableName RelInfo {..} = do
--------------------------------------------------------------------------------
-- | Collects encountered redaction expressions on a per table/function basis.
-- Expressions are deduplicated and assigned a unique name (within that table/function)
-- that is then used to reference the expression inside the query.
newtype RedactionExpressionState = RedactionExpressionState
{unRedactionExpressionState :: HashMap API.TargetName (HashMap API.RedactionExpression API.RedactionExpressionName)}
deriving stock (Eq, Show)
recordRedactionExpression ::
( MonadState state m,
Has TableRelationships state,
Has RedactionExpressionState state,
MonadError QErr m,
MonadReader r m,
Has API.ScalarTypesCapabilities r,
Has SessionVariables r
) =>
API.TargetName ->
AnnRedactionExp 'DataConnector (UnpreparedValue 'DataConnector) ->
m (Maybe API.RedactionExpressionName)
recordRedactionExpression target = \case
NoRedaction -> pure Nothing
RedactIfFalse boolExp -> runMaybeT $ do
expression <- MaybeT $ fmap API.RedactionExpression <$> translateBoolExpToExpression target boolExp
replaceOutput $ \existingState@(RedactionExpressionState recordedExps) ->
let targetRecordedExps = fromMaybe mempty $ HashMap.lookup target recordedExps
in case HashMap.lookup expression targetRecordedExps of
Just existingName -> (existingState, existingName)
Nothing ->
-- A unique name is generated by counting up from zero as redaction expressions are added
-- by using the size of the HashMap they are placed into
let newName = API.RedactionExpressionName $ "RedactionExp" <> tshow (HashMap.size targetRecordedExps)
newTargetRecordedExps = HashMap.insert expression newName targetRecordedExps
newState = RedactionExpressionState $ HashMap.insert target newTargetRecordedExps recordedExps
in (newState, newName)
translateRedactionExpressions :: RedactionExpressionState -> Set API.TargetRedactionExpressions
translateRedactionExpressions (RedactionExpressionState redactionsByTarget) =
redactionsByTarget
& HashMap.toList
<&> ( \(targetKey, redactionExps) ->
API.TargetRedactionExpressions
{ _treTarget = targetKey,
_treExpressions = HashMap.fromList $ swap <$> HashMap.toList redactionExps
}
)
& Set.fromList
--------------------------------------------------------------------------------
-- | Represents a potential prefix that can be applied to a field name, useful for
-- namespacing field names that may be otherwise duplicated.
newtype FieldPrefix = FieldPrefix (Maybe FieldName)
@ -166,15 +227,19 @@ data Cardinality
--------------------------------------------------------------------------------
prepareLiteral ::
(MonadError QErr m, MonadReader r m, Has API.ScalarTypesCapabilities r) =>
SessionVariables ->
( MonadError QErr m,
MonadReader r m,
Has API.ScalarTypesCapabilities r,
Has SessionVariables r
) =>
UnpreparedValue 'DataConnector ->
m Literal
prepareLiteral sessionVariables = \case
prepareLiteral = \case
UVLiteral literal -> pure $ literal
UVParameter _ e -> pure (ValueLiteral (columnTypeToScalarType $ cvType e) (cvValue e))
UVSession -> throw400 NotSupported "prepareLiteral: UVSession"
UVSessionVar sessionVarType sessionVar -> do
sessionVariables <- asks getter
textValue <-
getSessionVariableValue sessionVar sessionVariables
`onNothing` throw400 NotSupported ("prepareLiteral: session var not found: " <>> sessionVar)
@ -237,54 +302,54 @@ toColumnSelector (ColumnStack stack) columnName =
--------------------------------------------------------------------------------
translateBoolExpToExpression ::
( Has TableRelationships writerOutput,
Monoid writerOutput,
( MonadState state m,
Has TableRelationships state,
Has RedactionExpressionState state,
MonadError QErr m,
MonadReader r m,
Has API.ScalarTypesCapabilities r
Has API.ScalarTypesCapabilities r,
Has SessionVariables r
) =>
SessionVariables ->
TableRelationshipsKey ->
API.TargetName ->
AnnBoolExp 'DataConnector (UnpreparedValue 'DataConnector) ->
CPS.WriterT writerOutput m (Maybe API.Expression)
translateBoolExpToExpression sessionVariables sourceName boolExp = do
removeAlwaysTrueExpression <$> translateBoolExp sessionVariables sourceName emptyColumnStack boolExp
m (Maybe API.Expression)
translateBoolExpToExpression sourceName boolExp = do
removeAlwaysTrueExpression <$> translateBoolExp sourceName emptyColumnStack boolExp
translateBoolExp ::
( Has TableRelationships writerOutput,
Monoid writerOutput,
( MonadState state m,
Has TableRelationships state,
Has RedactionExpressionState state,
MonadError QErr m,
MonadReader r m,
Has API.ScalarTypesCapabilities r
Has API.ScalarTypesCapabilities r,
Has SessionVariables r
) =>
SessionVariables ->
TableRelationshipsKey ->
API.TargetName ->
ColumnStack ->
AnnBoolExp 'DataConnector (UnpreparedValue 'DataConnector) ->
CPS.WriterT writerOutput m API.Expression
translateBoolExp sessionVariables sourceName columnStack = \case
m API.Expression
translateBoolExp sourceName columnStack = \case
BoolAnd xs ->
mkIfZeroOrMany API.And . mapMaybe removeAlwaysTrueExpression <$> traverse (translateBoolExp' sourceName columnStack) xs
mkIfZeroOrMany API.And . mapMaybe removeAlwaysTrueExpression <$> traverse (translateBoolExp sourceName columnStack) xs
BoolOr xs ->
mkIfZeroOrMany API.Or . mapMaybe removeAlwaysFalseExpression <$> traverse (translateBoolExp' sourceName columnStack) xs
mkIfZeroOrMany API.Or . mapMaybe removeAlwaysFalseExpression <$> traverse (translateBoolExp sourceName columnStack) xs
BoolNot x ->
API.Not <$> (translateBoolExp' sourceName columnStack) x
BoolField (AVColumn c _redactionExp opExps) -> do
-- TODO(redactionExp): Deal with the redaction expression
API.Not <$> (translateBoolExp sourceName columnStack) x
BoolField (AVColumn c redactionExp opExps) -> do
let columnSelector = toColumnSelector columnStack $ ciColumn c
lift $ mkIfZeroOrMany API.And <$> traverse (translateOp sessionVariables columnSelector (Witch.from . columnTypeToScalarType $ ciType c)) opExps
redactionExpName <- recordRedactionExpression sourceName redactionExp
mkIfZeroOrMany API.And <$> traverse (translateOp columnSelector (Witch.from . columnTypeToScalarType $ ciType c) redactionExpName) opExps
BoolField (AVNestedObject NestedObjectInfo {..} nestedExp) ->
translateBoolExp' sourceName (pushColumn columnStack _noiColumn) nestedExp
translateBoolExp sourceName (pushColumn columnStack _noiColumn) nestedExp
BoolField (AVRelationship relationshipInfo (RelationshipFilters {rfTargetTablePermissions, rfFilter})) -> do
(relationshipName, API.Relationship {..}) <- recordTableRelationshipFromRelInfo sourceName relationshipInfo
-- TODO: How does this function keep track of the root table?
API.Exists (API.RelatedTable relationshipName) <$> translateBoolExp' (TableNameKey _rTargetTable) emptyColumnStack (BoolAnd [rfTargetTablePermissions, rfFilter])
API.Exists (API.RelatedTable relationshipName) <$> translateBoolExp (API.TNTable _rTargetTable) emptyColumnStack (BoolAnd [rfTargetTablePermissions, rfFilter])
BoolExists GExists {..} ->
let tableName = Witch.from _geTable
in API.Exists (API.UnrelatedTable tableName) <$> translateBoolExp' (TableNameKey tableName) emptyColumnStack _geWhere
in API.Exists (API.UnrelatedTable tableName) <$> translateBoolExp (API.TNTable tableName) emptyColumnStack _geWhere
where
translateBoolExp' = translateBoolExp sessionVariables
-- Makes an 'API.Expression' like 'API.And' if there is zero or many input expressions otherwise
-- just returns the singleton expression. This helps remove redundant 'API.And' etcs from the expression.
mkIfZeroOrMany :: (Set API.Expression -> API.Expression) -> [API.Expression] -> API.Expression
@ -305,14 +370,18 @@ removeAlwaysFalseExpression = \case
other -> Just other
translateOp ::
(MonadError QErr m, MonadReader r m, Has API.ScalarTypesCapabilities r) =>
SessionVariables ->
( MonadError QErr m,
MonadReader r m,
Has API.ScalarTypesCapabilities r,
Has SessionVariables r
) =>
API.ColumnSelector ->
API.ScalarType ->
Maybe API.RedactionExpressionName ->
OpExpG 'DataConnector (UnpreparedValue 'DataConnector) ->
m API.Expression
translateOp sessionVariables columnName columnType opExp = do
preparedOpExp <- traverse (prepareLiteral sessionVariables) $ opExp
translateOp columnName columnType redactionExpName opExp = do
preparedOpExp <- traverse prepareLiteral $ opExp
case preparedOpExp of
AEQ _ (ValueLiteral scalarType value) ->
pure $ mkApplyBinaryComparisonOperatorToScalar API.Equal value scalarType
@ -370,7 +439,7 @@ translateOp sessionVariables columnName columnType opExp = do
pure $ API.ApplyBinaryArrayComparisonOperator (API.CustomBinaryArrayComparisonOperator _cboName) currentComparisonColumn array (Witch.from scalarType)
where
currentComparisonColumn :: API.ComparisonColumn
currentComparisonColumn = API.ComparisonColumn API.CurrentTable columnName columnType
currentComparisonColumn = API.ComparisonColumn API.CurrentTable columnName columnType redactionExpName
mkApplyBinaryComparisonOperatorToAnotherColumn :: API.BinaryComparisonOperator -> RootOrCurrentColumn 'DataConnector -> API.Expression
mkApplyBinaryComparisonOperatorToAnotherColumn operator (RootOrCurrentColumn rootOrCurrent otherColumnName) =
@ -379,7 +448,7 @@ translateOp sessionVariables columnName columnType opExp = do
IsCurrent -> API.CurrentTable
otherColumnSelector = API.mkColumnSelector $ Witch.from otherColumnName
in -- TODO(dmoverton): allow otherColumnName to refer to nested object fields.
API.ApplyBinaryComparisonOperator operator currentComparisonColumn (API.AnotherColumnComparison $ API.ComparisonColumn columnPath otherColumnSelector columnType)
API.ApplyBinaryComparisonOperator operator currentComparisonColumn (API.AnotherColumnComparison $ API.ComparisonColumn columnPath otherColumnSelector columnType Nothing)
inOperator :: Literal -> API.Expression
inOperator literal =

View File

@ -3,10 +3,9 @@ module Hasura.Backends.DataConnector.Plan.MutationPlan
)
where
import Control.Monad.Trans.Writer.CPS qualified as CPS
import Data.Aeson qualified as J
import Data.Aeson.Encoding qualified as JE
import Data.Has (Has, modifier)
import Data.Has (Has)
import Data.HashMap.Strict qualified as HashMap
import Data.Semigroup.Foldable (toNonEmpty)
import Data.Set (Set)
@ -62,36 +61,41 @@ instance Semigroup TableInsertSchema where
}
recordTableInsertSchema ::
( Has TableInsertSchemas writerOutput,
Monoid writerOutput,
MonadError QErr m
( Has TableInsertSchemas state,
MonadState state m
) =>
API.TableName ->
TableInsertSchema ->
CPS.WriterT writerOutput m ()
m ()
recordTableInsertSchema tableName tableInsertSchema =
let newTableSchema = TableInsertSchemas $ HashMap.singleton tableName tableInsertSchema
in CPS.tell $ modifier (const newTableSchema) mempty
writeOutput . TableInsertSchemas $ HashMap.singleton tableName tableInsertSchema
--------------------------------------------------------------------------------
mkMutationPlan ::
(MonadError QErr m, MonadReader r m, Has API.ScalarTypesCapabilities r) =>
SessionVariables ->
( MonadError QErr m,
MonadReader r m,
Has API.ScalarTypesCapabilities r,
Has SessionVariables r
) =>
MutationDB 'DataConnector Void (UnpreparedValue 'DataConnector) ->
m (Plan API.MutationRequest API.MutationResponse)
mkMutationPlan sessionVariables mutationDB = do
request <- translateMutationDB sessionVariables mutationDB
mkMutationPlan mutationDB = do
request <- translateMutationDB mutationDB
pure $ Plan request (reshapeResponseToMutationGqlShape mutationDB)
translateMutationDB ::
(MonadError QErr m, MonadReader r m, Has API.ScalarTypesCapabilities r) =>
SessionVariables ->
( MonadError QErr m,
MonadReader r m,
Has API.ScalarTypesCapabilities r,
Has SessionVariables r
) =>
MutationDB 'DataConnector Void (UnpreparedValue 'DataConnector) ->
m API.MutationRequest
translateMutationDB sessionVariables = \case
translateMutationDB = \case
MDBInsert insert -> do
(insertOperation, (tableRelationships, tableInsertSchemas)) <- CPS.runWriterT $ translateInsert sessionVariables insert
(insertOperation, (tableRelationships, redactionExpressionState, tableInsertSchemas)) <-
flip runStateT (mempty, RedactionExpressionState mempty, mempty) $ translateInsert insert
let apiTableInsertSchema =
unTableInsertSchemas tableInsertSchemas
& HashMap.toList
@ -100,11 +104,13 @@ translateMutationDB sessionVariables = \case
pure
$ API.MutationRequest
{ _mrTableRelationships = apiTableRelationships,
_mrRedactionExpressions = translateRedactionExpressions redactionExpressionState,
_mrInsertSchema = Set.fromList apiTableInsertSchema,
_mrOperations = [API.InsertOperation insertOperation]
}
MDBUpdate update -> do
(updateOperations, tableRelationships) <- CPS.runWriterT $ translateUpdate sessionVariables update
(updateOperations, (tableRelationships, redactionExpressionState)) <-
flip runStateT (mempty, RedactionExpressionState mempty) $ translateUpdate update
let apiTableRelationships =
Set.fromList
$ uncurry API.TableRelationships
@ -112,11 +118,13 @@ translateMutationDB sessionVariables = \case
pure
$ API.MutationRequest
{ _mrTableRelationships = apiTableRelationships,
_mrRedactionExpressions = translateRedactionExpressions redactionExpressionState,
_mrInsertSchema = mempty,
_mrOperations = API.UpdateOperation <$> updateOperations
}
MDBDelete delete -> do
(deleteOperation, tableRelationships) <- CPS.runWriterT $ translateDelete sessionVariables delete
(deleteOperation, (tableRelationships, redactionExpressionState)) <-
flip runStateT (mempty, RedactionExpressionState mempty) $ translateDelete delete
let apiTableRelationships =
Set.fromList
$ uncurry API.TableRelationships
@ -124,26 +132,34 @@ translateMutationDB sessionVariables = \case
pure
$ API.MutationRequest
{ _mrTableRelationships = apiTableRelationships,
_mrRedactionExpressions = translateRedactionExpressions redactionExpressionState,
_mrInsertSchema = mempty,
_mrOperations = [API.DeleteOperation deleteOperation]
}
MDBFunction _returnsSet _select ->
throw400 NotSupported "translateMutationDB: function mutations not implemented for the Data Connector backend."
eitherKey :: (TableRelationshipsKey, c) -> Either (API.FunctionName, c) (API.TableName, c)
eitherKey (FunctionNameKey f, x) = Left (f, x)
eitherKey (TableNameKey t, x) = Right (t, x)
eitherKey :: (API.TargetName, c) -> Either (API.FunctionName, c) (API.TableName, c)
eitherKey (API.TNFunction f, x) = Left (f, x)
eitherKey (API.TNTable t, x) = Right (t, x)
translateInsert ::
(MonadError QErr m, MonadReader r m, Has API.ScalarTypesCapabilities r) =>
SessionVariables ->
( MonadState state m,
Has TableRelationships state,
Has RedactionExpressionState state,
Has TableInsertSchemas state,
MonadError QErr m,
MonadReader r m,
Has API.ScalarTypesCapabilities r,
Has SessionVariables r
) =>
AnnotatedInsert 'DataConnector Void (UnpreparedValue 'DataConnector) ->
CPS.WriterT (TableRelationships, TableInsertSchemas) m API.InsertMutationOperation
translateInsert sessionVariables AnnotatedInsert {_aiData = AnnotatedInsertData {..}, ..} = do
m API.InsertMutationOperation
translateInsert AnnotatedInsert {_aiData = AnnotatedInsertData {..}, ..} = do
captureTableInsertSchema tableName _aiTableColumns _aiPrimaryKey _aiExtraTableMetadata
rows <- lift $ traverse (translateInsertRow sessionVariables tableName _aiTableColumns _aiPresetValues) _aiInsertObject
postInsertCheck <- translateBoolExpToExpression sessionVariables (TableNameKey tableName) insertCheckCondition
returningFields <- translateMutationOutputToReturningFields sessionVariables tableName _aiOutput
rows <- traverse (translateInsertRow tableName _aiTableColumns _aiPresetValues) _aiInsertObject
postInsertCheck <- translateBoolExpToExpression (API.TNTable tableName) insertCheckCondition
returningFields <- translateMutationOutputToReturningFields tableName _aiOutput
pure
$ API.InsertMutationOperation
{ API._imoTable = tableName,
@ -157,15 +173,14 @@ translateInsert sessionVariables AnnotatedInsert {_aiData = AnnotatedInsertData
(insertCheckCondition, _updateCheckCondition) = _aiCheckCondition
captureTableInsertSchema ::
( Has TableInsertSchemas writerOutput,
Monoid writerOutput,
MonadError QErr m
( MonadState state m,
Has TableInsertSchemas state
) =>
API.TableName ->
[ColumnInfo 'DataConnector] ->
Maybe (NESeq ColumnName) ->
ExtraTableMetadata ->
CPS.WriterT writerOutput m ()
m ()
captureTableInsertSchema tableName tableColumns primaryKey ExtraTableMetadata {..} = do
let fieldSchemas =
tableColumns
@ -183,20 +198,23 @@ captureTableInsertSchema tableName tableColumns primaryKey ExtraTableMetadata {.
recordTableInsertSchema tableName $ TableInsertSchema primaryKey' fieldSchemas
translateInsertRow ::
(MonadError QErr m, MonadReader r m, Has API.ScalarTypesCapabilities r) =>
SessionVariables ->
( MonadError QErr m,
MonadReader r m,
Has API.ScalarTypesCapabilities r,
Has SessionVariables r
) =>
API.TableName ->
[ColumnInfo 'DataConnector] ->
HashMap ColumnName (UnpreparedValue 'DataConnector) ->
AnnotatedInsertRow 'DataConnector (UnpreparedValue 'DataConnector) ->
m API.RowObject
translateInsertRow sessionVariables tableName tableColumns defaultColumnValues insertRow = do
translateInsertRow tableName tableColumns defaultColumnValues insertRow = do
columnSchemasAndValues <- forM (HashMap.toList columnUnpreparedValues) $ \(columnName, columnValue) -> do
fieldName <-
case find (\ColumnInfo {..} -> ciColumn == columnName) tableColumns of
Just ColumnInfo {..} -> pure . API.FieldName $ G.unName ciName
Nothing -> throw500 $ "Can't find column " <> toTxt columnName <> " in table schema for " <> API.tableNameToText tableName
preparedLiteral <- prepareLiteral sessionVariables columnValue
preparedLiteral <- prepareLiteral columnValue
value <-
case preparedLiteral of
@ -223,26 +241,38 @@ translateInsertRow sessionVariables tableName tableColumns defaultColumnValues i
& HashMap.fromList
translateUpdate ::
(MonadError QErr m, MonadReader r m, Has API.ScalarTypesCapabilities r) =>
SessionVariables ->
( MonadState state m,
Has TableRelationships state,
Has RedactionExpressionState state,
MonadError QErr m,
MonadReader r m,
Has API.ScalarTypesCapabilities r,
Has SessionVariables r
) =>
AnnotatedUpdateG 'DataConnector Void (UnpreparedValue 'DataConnector) ->
CPS.WriterT TableRelationships m [API.UpdateMutationOperation]
translateUpdate sessionVariables annUpdate@AnnotatedUpdateG {..} = do
m [API.UpdateMutationOperation]
translateUpdate annUpdate@AnnotatedUpdateG {..} = do
case _auUpdateVariant of
SingleBatch batch -> (: []) <$> translateUpdateBatch sessionVariables annUpdate batch
MultipleBatches batches -> traverse (translateUpdateBatch sessionVariables annUpdate) batches
SingleBatch batch -> (: []) <$> translateUpdateBatch annUpdate batch
MultipleBatches batches -> traverse (translateUpdateBatch annUpdate) batches
translateUpdateBatch ::
(MonadError QErr m, MonadReader r m, Has API.ScalarTypesCapabilities r) =>
SessionVariables ->
( MonadState state m,
Has TableRelationships state,
Has RedactionExpressionState state,
MonadError QErr m,
MonadReader r m,
Has API.ScalarTypesCapabilities r,
Has SessionVariables r
) =>
AnnotatedUpdateG 'DataConnector Void (UnpreparedValue 'DataConnector) ->
UpdateBatch 'DataConnector UpdateOperator (UnpreparedValue 'DataConnector) ->
CPS.WriterT TableRelationships m API.UpdateMutationOperation
translateUpdateBatch sessionVariables AnnotatedUpdateG {..} UpdateBatch {..} = do
updates <- lift $ translateUpdateOperations sessionVariables _ubOperations
whereExp <- translateBoolExpToExpression sessionVariables (TableNameKey tableName) (BoolAnd [_auUpdatePermissions, _ubWhere])
postUpdateCheck <- translateBoolExpToExpression sessionVariables (TableNameKey tableName) _auCheck
returningFields <- translateMutationOutputToReturningFields sessionVariables tableName _auOutput
m API.UpdateMutationOperation
translateUpdateBatch AnnotatedUpdateG {..} UpdateBatch {..} = do
updates <- translateUpdateOperations _ubOperations
whereExp <- translateBoolExpToExpression (API.TNTable tableName) (BoolAnd [_auUpdatePermissions, _ubWhere])
postUpdateCheck <- translateBoolExpToExpression (API.TNTable tableName) _auCheck
returningFields <- translateMutationOutputToReturningFields tableName _auOutput
pure
$ API.UpdateMutationOperation
@ -257,11 +287,14 @@ translateUpdateBatch sessionVariables AnnotatedUpdateG {..} UpdateBatch {..} = d
translateUpdateOperations ::
forall m r.
(MonadError QErr m, MonadReader r m, Has API.ScalarTypesCapabilities r) =>
SessionVariables ->
( MonadError QErr m,
MonadReader r m,
Has API.ScalarTypesCapabilities r,
Has SessionVariables r
) =>
HashMap ColumnName (UpdateOperator (UnpreparedValue 'DataConnector)) ->
m (Set API.RowUpdate)
translateUpdateOperations sessionVariables columnUpdates =
translateUpdateOperations columnUpdates =
fmap Set.fromList . forM (HashMap.toList columnUpdates) $ \(columnName, updateOperator) -> do
let (mkRowUpdate, value) =
case updateOperator of
@ -273,19 +306,25 @@ translateUpdateOperations sessionVariables columnUpdates =
where
prepareAndExtractLiteralValue :: UnpreparedValue 'DataConnector -> m (ScalarType, J.Value)
prepareAndExtractLiteralValue unpreparedValue = do
preparedLiteral <- prepareLiteral sessionVariables unpreparedValue
preparedLiteral <- prepareLiteral unpreparedValue
case preparedLiteral of
ValueLiteral scalarType value -> pure (scalarType, value)
ArrayLiteral _scalarType _values -> throw400 NotSupported "translateUpdateOperations: Array literals are not supported as column update values"
translateDelete ::
(MonadError QErr m, MonadReader r m, Has API.ScalarTypesCapabilities r) =>
SessionVariables ->
( MonadState state m,
Has TableRelationships state,
Has RedactionExpressionState state,
MonadError QErr m,
MonadReader r m,
Has API.ScalarTypesCapabilities r,
Has SessionVariables r
) =>
AnnDelG 'DataConnector Void (UnpreparedValue 'DataConnector) ->
CPS.WriterT TableRelationships m API.DeleteMutationOperation
translateDelete sessionVariables AnnDel {..} = do
whereExp <- translateBoolExpToExpression sessionVariables (TableNameKey tableName) (BoolAnd [permissionFilter, whereClause])
returningFields <- translateMutationOutputToReturningFields sessionVariables tableName _adOutput
m API.DeleteMutationOperation
translateDelete AnnDel {..} = do
whereExp <- translateBoolExpToExpression (API.TNTable tableName) (BoolAnd [permissionFilter, whereClause])
returningFields <- translateMutationOutputToReturningFields tableName _adOutput
pure
$ API.DeleteMutationOperation
{ API._dmoTable = tableName,
@ -297,35 +336,37 @@ translateDelete sessionVariables AnnDel {..} = do
(permissionFilter, whereClause) = _adWhere
translateMutationOutputToReturningFields ::
( MonadError QErr m,
Has TableRelationships writerOutput,
Monoid writerOutput,
( MonadState state m,
Has TableRelationships state,
Has RedactionExpressionState state,
MonadError QErr m,
MonadReader r m,
Has API.ScalarTypesCapabilities r
Has API.ScalarTypesCapabilities r,
Has SessionVariables r
) =>
SessionVariables ->
API.TableName ->
MutationOutputG 'DataConnector Void (UnpreparedValue 'DataConnector) ->
CPS.WriterT writerOutput m (HashMap FieldName API.Field)
translateMutationOutputToReturningFields sessionVariables tableName = \case
m (HashMap FieldName API.Field)
translateMutationOutputToReturningFields tableName = \case
MOutSinglerowObject annFields ->
translateAnnFields sessionVariables noPrefix (TableNameKey tableName) annFields
translateAnnFields noPrefix (API.TNTable tableName) annFields
MOutMultirowFields mutFields ->
HashMap.unions <$> traverse (uncurry $ translateMutField sessionVariables tableName) mutFields
HashMap.unions <$> traverse (uncurry $ translateMutField tableName) mutFields
translateMutField ::
( MonadError QErr m,
Has TableRelationships writerOutput,
Monoid writerOutput,
( MonadState state m,
Has TableRelationships state,
Has RedactionExpressionState state,
MonadError QErr m,
MonadReader r m,
Has API.ScalarTypesCapabilities r
Has API.ScalarTypesCapabilities r,
Has SessionVariables r
) =>
SessionVariables ->
API.TableName ->
FieldName ->
MutFldG 'DataConnector Void (UnpreparedValue 'DataConnector) ->
CPS.WriterT writerOutput m (HashMap FieldName API.Field)
translateMutField sessionVariables tableName fieldName = \case
m (HashMap FieldName API.Field)
translateMutField tableName fieldName = \case
MCount ->
-- All mutation operations in a request return their affected rows count.
-- The count can just be added to the response JSON during agent response reshaping
@ -336,7 +377,7 @@ translateMutField sessionVariables tableName fieldName = \case
-- to us
pure mempty
MRet annFields ->
translateAnnFields sessionVariables (prefixWith fieldName) (TableNameKey tableName) annFields
translateAnnFields (prefixWith fieldName) (API.TNTable tableName) annFields
--------------------------------------------------------------------------------

View File

@ -13,11 +13,10 @@ where
--------------------------------------------------------------------------------
import Control.Monad.Trans.Writer.CPS qualified as CPS
import Data.Aeson qualified as J
import Data.Aeson.Encoding qualified as JE
import Data.Bifunctor (Bifunctor (bimap))
import Data.Has (Has)
import Data.Has
import Data.HashMap.Strict qualified as HashMap
import Data.List.NonEmpty qualified as NE
import Data.Semigroup (Min (..))
@ -63,11 +62,14 @@ instance Monoid FieldsAndAggregates where
-- | Map a 'QueryDB 'DataConnector' term into a 'Plan'
mkQueryPlan ::
forall m r.
(MonadError QErr m, MonadReader r m, Has API.ScalarTypesCapabilities r) =>
SessionVariables ->
( MonadError QErr m,
MonadReader r m,
Has API.ScalarTypesCapabilities r,
Has SessionVariables r
) =>
QueryDB 'DataConnector Void (UnpreparedValue 'DataConnector) ->
m (Plan API.QueryRequest API.QueryResponse)
mkQueryPlan sessionVariables ir = do
mkQueryPlan ir = do
queryRequest <- translateQueryDB ir
pure $ Plan queryRequest (reshapeResponseToQueryShape ir)
where
@ -76,78 +78,103 @@ mkQueryPlan sessionVariables ir = do
m API.QueryRequest
translateQueryDB =
\case
QDBMultipleRows simpleSelect -> translateAnnSimpleSelectToQueryRequest sessionVariables simpleSelect
QDBSingleRow simpleSelect -> translateAnnSimpleSelectToQueryRequest sessionVariables simpleSelect
QDBAggregation aggregateSelect -> translateAnnAggregateSelectToQueryRequest sessionVariables aggregateSelect
QDBMultipleRows simpleSelect -> translateAnnSimpleSelectToQueryRequest simpleSelect
QDBSingleRow simpleSelect -> translateAnnSimpleSelectToQueryRequest simpleSelect
QDBAggregation aggregateSelect -> translateAnnAggregateSelectToQueryRequest aggregateSelect
translateAnnSimpleSelectToQueryRequest ::
forall m r.
(MonadError QErr m, MonadReader r m, Has API.ScalarTypesCapabilities r) =>
SessionVariables ->
( MonadError QErr m,
MonadReader r m,
Has API.ScalarTypesCapabilities r,
Has SessionVariables r
) =>
AnnSimpleSelectG 'DataConnector Void (UnpreparedValue 'DataConnector) ->
m API.QueryRequest
translateAnnSimpleSelectToQueryRequest sessionVariables simpleSelect =
translateAnnSelectToQueryRequest sessionVariables (translateAnnFieldsWithNoAggregates sessionVariables noPrefix) simpleSelect
translateAnnSimpleSelectToQueryRequest simpleSelect =
translateAnnSelectToQueryRequest (translateAnnFieldsWithNoAggregates noPrefix) simpleSelect
translateAnnAggregateSelectToQueryRequest ::
forall m r.
(MonadError QErr m, MonadReader r m, Has API.ScalarTypesCapabilities r) =>
SessionVariables ->
( MonadError QErr m,
MonadReader r m,
Has API.ScalarTypesCapabilities r,
Has SessionVariables r
) =>
AnnAggregateSelectG 'DataConnector Void (UnpreparedValue 'DataConnector) ->
m API.QueryRequest
translateAnnAggregateSelectToQueryRequest sessionVariables aggregateSelect =
translateAnnSelectToQueryRequest sessionVariables (translateTableAggregateFields sessionVariables) aggregateSelect
translateAnnAggregateSelectToQueryRequest aggregateSelect =
translateAnnSelectToQueryRequest translateTableAggregateFields aggregateSelect
translateAnnSelectToQueryRequest ::
forall m r fieldType.
(MonadError QErr m, MonadReader r m, Has API.ScalarTypesCapabilities r) =>
SessionVariables ->
(TableRelationshipsKey -> Fields (fieldType (UnpreparedValue 'DataConnector)) -> CPS.WriterT TableRelationships m FieldsAndAggregates) ->
( MonadError QErr m,
MonadReader r m,
Has API.ScalarTypesCapabilities r,
Has SessionVariables r
) =>
( forall state m2.
( MonadState state m2,
Has TableRelationships state,
Has RedactionExpressionState state,
MonadError QErr m2,
MonadReader r m2,
Has API.ScalarTypesCapabilities r,
Has SessionVariables r
) =>
API.TargetName ->
Fields (fieldType (UnpreparedValue 'DataConnector)) ->
m2 FieldsAndAggregates
) ->
AnnSelectG 'DataConnector fieldType (UnpreparedValue 'DataConnector) ->
m API.QueryRequest
translateAnnSelectToQueryRequest sessionVariables translateFieldsAndAggregates selectG = do
translateAnnSelectToQueryRequest translateFieldsAndAggregates selectG = do
case _asnFrom selectG of
FromIdentifier _ -> throw400 NotSupported "AnnSelectG: FromIdentifier not supported"
FromNativeQuery {} -> throw400 NotSupported "AnnSelectG: FromNativeQuery not supported"
FromStoredProcedure {} -> throw400 NotSupported "AnnSelectG: FromStoredProcedure not supported"
FromTable tableName -> do
(query, TableRelationships tableRelationships) <-
CPS.runWriterT (translateAnnSelect sessionVariables translateFieldsAndAggregates (TableNameKey (Witch.into tableName)) selectG)
(query, (TableRelationships tableRelationships, redactionExpressionState)) <-
flip runStateT (mempty, RedactionExpressionState mempty) $ translateAnnSelect translateFieldsAndAggregates (API.TNTable (Witch.into tableName)) selectG
let relationships = mkRelationships <$> HashMap.toList tableRelationships
pure
$ API.QRTable
API.TableRequest
{ _trTable = Witch.into tableName,
_trRelationships = Set.fromList relationships,
_trRedactionExpressions = translateRedactionExpressions redactionExpressionState,
_trQuery = query,
_trForeach = Nothing
}
FromFunction fn@(FunctionName functionName) argsExp _dListM -> do
args <- mkArgs sessionVariables argsExp fn
(query, TableRelationships tableRelationships) <-
CPS.runWriterT (translateAnnSelect sessionVariables translateFieldsAndAggregates (FunctionNameKey (Witch.into functionName)) selectG)
args <- mkArgs argsExp fn
(query, (redactionExpressionState, TableRelationships tableRelationships)) <-
flip runStateT (RedactionExpressionState mempty, mempty) $ translateAnnSelect translateFieldsAndAggregates (API.TNFunction (Witch.into functionName)) selectG
let relationships = mkRelationships <$> HashMap.toList tableRelationships
pure
$ API.QRFunction
API.FunctionRequest
{ _frFunction = Witch.into functionName,
_frRelationships = Set.fromList relationships,
_frRedactionExpressions = translateRedactionExpressions redactionExpressionState,
_frQuery = query,
_frFunctionArguments = args
}
mkRelationships :: (TableRelationshipsKey, (HashMap API.RelationshipName API.Relationship)) -> API.Relationships
mkRelationships (FunctionNameKey functionName, relationships) = API.RFunction (API.FunctionRelationships functionName relationships)
mkRelationships (TableNameKey tableName, relationships) = API.RTable (API.TableRelationships tableName relationships)
mkRelationships :: (API.TargetName, (HashMap API.RelationshipName API.Relationship)) -> API.Relationships
mkRelationships (API.TNFunction functionName, relationships) = API.RFunction (API.FunctionRelationships functionName relationships)
mkRelationships (API.TNTable tableName, relationships) = API.RTable (API.TableRelationships tableName relationships)
mkArgs ::
( MonadError QErr m
forall r m.
( MonadError QErr m,
MonadReader r m,
Has SessionVariables r
) =>
SessionVariables ->
Function.FunctionArgsExpG (ArgumentExp (UnpreparedValue 'DataConnector)) ->
FunctionName ->
m [API.FunctionArgument]
mkArgs sessionVariables (Function.FunctionArgsExp ps ns) functionName = do
mkArgs (Function.FunctionArgsExp ps ns) functionName = do
unless (null ps) $ throw400 NotSupported $ "Positional arguments not supported in function " <> toTxt functionName
getNamed
where
@ -158,28 +185,31 @@ mkArgs sessionVariables (Function.FunctionArgsExp ps ns) functionName = do
UVLiteral _ -> throw400 NotSupported "Literal not supported in Data Connector function args."
UVSessionVar _ _ -> throw400 NotSupported "SessionVar not supported in Data Connector function args."
UVParameter _ (ColumnValue t v) -> pure (API.ScalarValue v (coerce (toTxt t)))
UVSession -> pure (API.ScalarValue (J.toJSON sessionVariables) (API.ScalarType "json"))
UVSession -> do
(sessionVariables :: SessionVariables) <- asks getter
pure (API.ScalarValue (J.toJSON sessionVariables) (API.ScalarType "json"))
translateAnnSelect ::
( Has TableRelationships writerOutput,
Monoid writerOutput,
( MonadState state m,
Has TableRelationships state,
Has RedactionExpressionState state,
MonadError QErr m,
MonadReader r m,
Has API.ScalarTypesCapabilities r
Has API.ScalarTypesCapabilities r,
Has SessionVariables r
) =>
SessionVariables ->
(TableRelationshipsKey -> Fields (fieldType (UnpreparedValue 'DataConnector)) -> CPS.WriterT writerOutput m FieldsAndAggregates) ->
TableRelationshipsKey ->
(API.TargetName -> Fields (fieldType (UnpreparedValue 'DataConnector)) -> m FieldsAndAggregates) ->
API.TargetName ->
AnnSelectG 'DataConnector fieldType (UnpreparedValue 'DataConnector) ->
CPS.WriterT writerOutput m API.Query
translateAnnSelect sessionVariables translateFieldsAndAggregates entityName selectG = do
m API.Query
translateAnnSelect translateFieldsAndAggregates entityName selectG = do
FieldsAndAggregates {..} <- translateFieldsAndAggregates entityName (_asnFields selectG)
let whereClauseWithPermissions =
case _saWhere (_asnArgs selectG) of
Just expr -> BoolAnd [expr, _tpFilter (_asnPerm selectG)]
Nothing -> _tpFilter (_asnPerm selectG)
whereClause <- translateBoolExpToExpression sessionVariables entityName whereClauseWithPermissions
orderBy <- traverse (translateOrderBy sessionVariables entityName) (_saOrderBy $ _asnArgs selectG)
whereClause <- translateBoolExpToExpression entityName whereClauseWithPermissions
orderBy <- traverse (translateOrderBy entityName) (_saOrderBy $ _asnArgs selectG)
pure
API.Query
{ _qFields = mapFieldNameHashMap <$> _faaFields,
@ -198,21 +228,22 @@ translateAnnSelect sessionVariables translateFieldsAndAggregates entityName sele
}
translateOrderBy ::
( Has TableRelationships writerOutput,
Monoid writerOutput,
( MonadState state m,
Has TableRelationships state,
Has RedactionExpressionState state,
MonadError QErr m,
MonadReader r m,
Has API.ScalarTypesCapabilities r
Has API.ScalarTypesCapabilities r,
Has SessionVariables r
) =>
SessionVariables ->
TableRelationshipsKey ->
API.TargetName ->
NE.NonEmpty (AnnotatedOrderByItemG 'DataConnector (UnpreparedValue 'DataConnector)) ->
CPS.WriterT writerOutput m API.OrderBy
translateOrderBy sessionVariables sourceName orderByItems = do
m API.OrderBy
translateOrderBy sourceName orderByItems = do
orderByElementsAndRelations <- for orderByItems \OrderByItemG {..} -> do
let orderDirection = maybe API.Ascending Witch.from obiType
translateOrderByElement sessionVariables sourceName orderDirection [] obiColumn
relations <- lift . mergeOrderByRelations $ snd <$> orderByElementsAndRelations
translateOrderByElement sourceName orderDirection [] obiColumn
relations <- mergeOrderByRelations $ snd <$> orderByElementsAndRelations
pure
API.OrderBy
{ _obRelations = relations,
@ -220,34 +251,35 @@ translateOrderBy sessionVariables sourceName orderByItems = do
}
translateOrderByElement ::
( Has TableRelationships writerOutput,
Monoid writerOutput,
( MonadState state m,
Has TableRelationships state,
Has RedactionExpressionState state,
MonadError QErr m,
MonadReader r m,
Has API.ScalarTypesCapabilities r
Has API.ScalarTypesCapabilities r,
Has SessionVariables r
) =>
SessionVariables ->
TableRelationshipsKey ->
API.TargetName ->
API.OrderDirection ->
[API.RelationshipName] ->
AnnotatedOrderByElement 'DataConnector (UnpreparedValue 'DataConnector) ->
CPS.WriterT writerOutput m (API.OrderByElement, HashMap API.RelationshipName API.OrderByRelation)
translateOrderByElement sessionVariables sourceName orderDirection targetReversePath = \case
-- TODO(redactionExp): Deal with this redaction expressions
AOCColumn ColumnInfo {..} _redactionExp ->
m (API.OrderByElement, HashMap API.RelationshipName API.OrderByRelation)
translateOrderByElement sourceName orderDirection targetReversePath = \case
AOCColumn ColumnInfo {..} redactionExp -> do
redactionExpName <- recordRedactionExpression sourceName redactionExp
pure
( API.OrderByElement
{ _obeTargetPath = reverse targetReversePath,
_obeTarget = API.OrderByColumn $ Witch.from ciColumn,
_obeTarget = API.OrderByColumn (Witch.from ciColumn) redactionExpName,
_obeOrderDirection = orderDirection
},
mempty
)
AOCObjectRelation relationshipInfo filterExp orderByElement -> do
(relationshipName, API.Relationship {..}) <- recordTableRelationshipFromRelInfo sourceName relationshipInfo
(translatedOrderByElement, subOrderByRelations) <- translateOrderByElement sessionVariables (TableNameKey _rTargetTable) orderDirection (relationshipName : targetReversePath) orderByElement
(translatedOrderByElement, subOrderByRelations) <- translateOrderByElement (API.TNTable _rTargetTable) orderDirection (relationshipName : targetReversePath) orderByElement
targetTableWhereExp <- translateBoolExpToExpression sessionVariables (TableNameKey _rTargetTable) filterExp
targetTableWhereExp <- translateBoolExpToExpression (API.TNTable _rTargetTable) filterExp
let orderByRelations = HashMap.fromList [(relationshipName, API.OrderByRelation targetTableWhereExp subOrderByRelations)]
pure (translatedOrderByElement, orderByRelations)
@ -257,10 +289,10 @@ translateOrderByElement sessionVariables sourceName orderDirection targetReverse
AAOCount ->
pure API.OrderByStarCountAggregate
AAOOp AggregateOrderByColumn {_aobcColumn = ColumnInfo {..}, ..} -> do
-- TODO(redactionExp): Deal with the redaction expression: _aobcRedactionExpression
aggFunction <- lift $ translateSingleColumnAggregateFunction _aobcAggregateFunctionName
redactionExpName <- recordRedactionExpression sourceName _aobcRedactionExpression
aggFunction <- translateSingleColumnAggregateFunction _aobcAggregateFunctionName
let resultScalarType = Witch.from $ columnTypeToScalarType _aobcAggregateFunctionReturnType
pure . API.OrderBySingleColumnAggregate $ API.SingleColumnAggregate aggFunction (Witch.from ciColumn) resultScalarType
pure . API.OrderBySingleColumnAggregate $ API.SingleColumnAggregate aggFunction (Witch.from ciColumn) redactionExpName resultScalarType
let translatedOrderByElement =
API.OrderByElement
@ -269,7 +301,7 @@ translateOrderByElement sessionVariables sourceName orderDirection targetReverse
_obeOrderDirection = orderDirection
}
targetTableWhereExp <- translateBoolExpToExpression sessionVariables (TableNameKey _rTargetTable) filterExp
targetTableWhereExp <- translateBoolExpToExpression (API.TNTable _rTargetTable) filterExp
let orderByRelations = HashMap.fromList [(relationshipName, API.OrderByRelation targetTableWhereExp mempty)]
pure (translatedOrderByElement, orderByRelations)
@ -293,71 +325,73 @@ mergeOrderByRelations orderByRelationsList =
else throw500 "mergeOrderByRelations: Differing filter expressions found for the same table"
translateAnnFieldsWithNoAggregates ::
( Has TableRelationships writerOutput,
Monoid writerOutput,
( MonadState state m,
Has TableRelationships state,
Has RedactionExpressionState state,
MonadError QErr m,
MonadReader r m,
Has API.ScalarTypesCapabilities r
Has API.ScalarTypesCapabilities r,
Has SessionVariables r
) =>
SessionVariables ->
FieldPrefix ->
TableRelationshipsKey ->
API.TargetName ->
AnnFieldsG 'DataConnector Void (UnpreparedValue 'DataConnector) ->
CPS.WriterT writerOutput m FieldsAndAggregates
translateAnnFieldsWithNoAggregates sessionVariables fieldNamePrefix sourceName fields =
(\fields' -> FieldsAndAggregates (Just fields') Nothing) <$> translateAnnFields sessionVariables fieldNamePrefix sourceName fields
m FieldsAndAggregates
translateAnnFieldsWithNoAggregates fieldNamePrefix sourceName fields =
(\fields' -> FieldsAndAggregates (Just fields') Nothing) <$> translateAnnFields fieldNamePrefix sourceName fields
translateAnnFields ::
( Has TableRelationships writerOutput,
Monoid writerOutput,
( MonadState state m,
Has TableRelationships state,
Has RedactionExpressionState state,
MonadError QErr m,
MonadReader r m,
Has API.ScalarTypesCapabilities r
Has API.ScalarTypesCapabilities r,
Has SessionVariables r
) =>
SessionVariables ->
FieldPrefix ->
TableRelationshipsKey ->
API.TargetName ->
AnnFieldsG 'DataConnector Void (UnpreparedValue 'DataConnector) ->
CPS.WriterT writerOutput m (HashMap FieldName API.Field)
translateAnnFields sessionVariables fieldNamePrefix sourceName fields = do
translatedFields <- traverse (traverse (translateAnnField sessionVariables sourceName)) fields
m (HashMap FieldName API.Field)
translateAnnFields fieldNamePrefix sourceName fields = do
translatedFields <- traverse (traverse (translateAnnField sourceName)) fields
pure $ HashMap.fromList (mapMaybe (\(fieldName, field) -> (applyPrefix fieldNamePrefix fieldName,) <$> field) translatedFields)
translateAnnField ::
( Has TableRelationships writerOutput,
Monoid writerOutput,
( MonadState state m,
Has TableRelationships state,
Has RedactionExpressionState state,
MonadError QErr m,
MonadReader r m,
Has API.ScalarTypesCapabilities r
Has API.ScalarTypesCapabilities r,
Has SessionVariables r
) =>
SessionVariables ->
TableRelationshipsKey ->
API.TargetName ->
AnnFieldG 'DataConnector Void (UnpreparedValue 'DataConnector) ->
CPS.WriterT writerOutput m (Maybe API.Field)
translateAnnField sessionVariables sourceTableName = \case
m (Maybe API.Field)
translateAnnField sourceTableName = \case
AFNestedObject nestedObj ->
Just
. API.NestedObjField (Witch.from $ _anosColumn nestedObj)
<$> translateNestedObjectSelect sessionVariables sourceTableName nestedObj
<$> translateNestedObjectSelect sourceTableName nestedObj
AFNestedArray _ (ANASSimple field) ->
fmap mkArrayField <$> translateAnnField sessionVariables sourceTableName field
fmap mkArrayField <$> translateAnnField sourceTableName field
where
mkArrayField nestedField =
API.NestedArrayField (API.ArrayField nestedField Nothing Nothing Nothing Nothing)
-- TODO(dmoverton): support limit, offset, where and order_by in ArrayField
AFNestedArray _ (ANASAggregate _) ->
pure Nothing -- TODO(dmoverton): support nested array aggregates
AFColumn colField ->
-- TODO(redactionExp): Deal with the redaction expression: _acfRedactionExpression
-- TODO: make sure certain fields in colField are not in use, since we don't support them
pure . Just $ API.ColumnField (Witch.from $ _acfColumn colField) (Witch.from . columnTypeToScalarType $ _acfType colField)
AFColumn AnnColumnField {..} -> do
redactionExpName <- recordRedactionExpression sourceTableName _acfRedactionExpression
pure . Just $ API.ColumnField (Witch.from _acfColumn) (Witch.from $ columnTypeToScalarType _acfType) redactionExpName
AFObjectRelation objRel ->
case _aosTarget (_aarAnnSelect objRel) of
FromTable tableName -> do
let targetTable = Witch.from tableName
let relationshipName = mkRelationshipName $ _aarRelationshipName objRel
fields <- translateAnnFields sessionVariables noPrefix (TableNameKey targetTable) (_aosFields (_aarAnnSelect objRel))
whereClause <- translateBoolExpToExpression sessionVariables (TableNameKey targetTable) (_aosTargetFilter (_aarAnnSelect objRel))
fields <- translateAnnFields noPrefix (API.TNTable targetTable) (_aosFields (_aarAnnSelect objRel))
whereClause <- translateBoolExpToExpression (API.TNTable targetTable) (_aosTargetFilter (_aarAnnSelect objRel))
recordTableRelationship
sourceTableName
@ -385,9 +419,9 @@ translateAnnField sessionVariables sourceTableName = \case
)
other -> error $ "translateAnnField: " <> show other
AFArrayRelation (ASSimple arrayRelationSelect) -> do
Just <$> translateArrayRelationSelect sessionVariables sourceTableName (translateAnnFieldsWithNoAggregates sessionVariables noPrefix) arrayRelationSelect
Just <$> translateArrayRelationSelect sourceTableName (translateAnnFieldsWithNoAggregates noPrefix) arrayRelationSelect
AFArrayRelation (ASAggregate arrayRelationSelect) ->
Just <$> translateArrayRelationSelect sessionVariables sourceTableName (translateTableAggregateFields sessionVariables) arrayRelationSelect
Just <$> translateArrayRelationSelect sourceTableName translateTableAggregateFields arrayRelationSelect
AFExpression _literal ->
-- We ignore literal text fields (we don't send them to the data connector agent)
-- and add them back to the response JSON when we reshape what the agent returns
@ -395,25 +429,26 @@ translateAnnField sessionVariables sourceTableName = \case
pure Nothing
translateArrayRelationSelect ::
( Has TableRelationships writerOutput,
Monoid writerOutput,
( MonadState state m,
Has TableRelationships state,
Has RedactionExpressionState state,
MonadError QErr m,
MonadReader r m,
Has API.ScalarTypesCapabilities r
Has API.ScalarTypesCapabilities r,
Has SessionVariables r
) =>
SessionVariables ->
TableRelationshipsKey ->
(TableRelationshipsKey -> Fields (fieldType (UnpreparedValue 'DataConnector)) -> CPS.WriterT writerOutput m FieldsAndAggregates) ->
API.TargetName ->
(API.TargetName -> Fields (fieldType (UnpreparedValue 'DataConnector)) -> m FieldsAndAggregates) ->
AnnRelationSelectG 'DataConnector (AnnSelectG 'DataConnector fieldType (UnpreparedValue 'DataConnector)) ->
CPS.WriterT writerOutput m API.Field
translateArrayRelationSelect sessionVariables sourceName translateFieldsAndAggregates arrRel = do
m API.Field
translateArrayRelationSelect sourceName translateFieldsAndAggregates arrRel = do
case _asnFrom (_aarAnnSelect arrRel) of
FromIdentifier _ -> lift $ throw400 NotSupported "AnnSelectG: FromIdentifier not supported"
FromNativeQuery {} -> lift $ throw400 NotSupported "AnnSelectG: FromNativeQuery not supported"
FromStoredProcedure {} -> lift $ throw400 NotSupported "AnnSelectG: FromStoredProcedure not supported"
FromFunction {} -> lift $ throw400 NotSupported "translateArrayRelationSelect: FromFunction not currently supported"
FromIdentifier _ -> throw400 NotSupported "AnnSelectG: FromIdentifier not supported"
FromNativeQuery {} -> throw400 NotSupported "AnnSelectG: FromNativeQuery not supported"
FromStoredProcedure {} -> throw400 NotSupported "AnnSelectG: FromStoredProcedure not supported"
FromFunction {} -> throw400 NotSupported "translateArrayRelationSelect: FromFunction not currently supported"
FromTable targetTable -> do
query <- translateAnnSelect sessionVariables translateFieldsAndAggregates (TableNameKey (Witch.into targetTable)) (_aarAnnSelect arrRel)
query <- translateAnnSelect translateFieldsAndAggregates (API.TNTable (Witch.into targetTable)) (_aarAnnSelect arrRel)
let relationshipName = mkRelationshipName $ _aarRelationshipName arrRel
recordTableRelationship
@ -432,41 +467,43 @@ translateArrayRelationSelect sessionVariables sourceName translateFieldsAndAggre
query
translateTableAggregateFields ::
( Has TableRelationships writerOutput,
Monoid writerOutput,
( MonadState state m,
Has TableRelationships state,
Has RedactionExpressionState state,
MonadError QErr m,
MonadReader r m,
Has API.ScalarTypesCapabilities r
Has API.ScalarTypesCapabilities r,
Has SessionVariables r
) =>
SessionVariables ->
TableRelationshipsKey ->
API.TargetName ->
TableAggregateFieldsG 'DataConnector Void (UnpreparedValue 'DataConnector) ->
CPS.WriterT writerOutput m FieldsAndAggregates
translateTableAggregateFields sessionVariables sourceName fields = do
mconcat <$> traverse (uncurry (translateTableAggregateField sessionVariables sourceName)) fields
m FieldsAndAggregates
translateTableAggregateFields sourceName fields = do
mconcat <$> traverse (uncurry (translateTableAggregateField sourceName)) fields
translateTableAggregateField ::
( Has TableRelationships writerOutput,
Monoid writerOutput,
( MonadState state m,
Has TableRelationships state,
Has RedactionExpressionState state,
MonadError QErr m,
MonadReader r m,
Has API.ScalarTypesCapabilities r
Has API.ScalarTypesCapabilities r,
Has SessionVariables r
) =>
SessionVariables ->
TableRelationshipsKey ->
API.TargetName ->
FieldName ->
TableAggregateFieldG 'DataConnector Void (UnpreparedValue 'DataConnector) ->
CPS.WriterT writerOutput m FieldsAndAggregates
translateTableAggregateField sessionVariables sourceName fieldName = \case
m FieldsAndAggregates
translateTableAggregateField sourceName fieldName = \case
TAFAgg aggregateFields -> do
let fieldNamePrefix = prefixWith fieldName
translatedAggregateFields <- lift $ mconcat <$> traverse (uncurry (translateAggregateField fieldNamePrefix)) aggregateFields
translatedAggregateFields <- mconcat <$> traverse (uncurry (translateAggregateField sourceName fieldNamePrefix)) aggregateFields
pure
$ FieldsAndAggregates
Nothing
(Just translatedAggregateFields)
TAFNodes _ fields ->
translateAnnFieldsWithNoAggregates sessionVariables (prefixWith fieldName) sourceName fields
translateAnnFieldsWithNoAggregates (prefixWith fieldName) sourceName fields
TAFExp _txt ->
-- We ignore literal text fields (we don't send them to the data connector agent)
-- and add them back to the response JSON when we reshape what the agent returns
@ -474,30 +511,41 @@ translateTableAggregateField sessionVariables sourceName fieldName = \case
pure mempty
translateAggregateField ::
(MonadError QErr m) =>
( MonadState state m,
Has TableRelationships state,
Has RedactionExpressionState state,
MonadError QErr m,
MonadReader r m,
Has API.ScalarTypesCapabilities r,
Has SessionVariables r
) =>
API.TargetName ->
FieldPrefix ->
FieldName ->
AggregateField 'DataConnector (UnpreparedValue 'DataConnector) ->
m (HashMap FieldName API.Aggregate)
translateAggregateField fieldPrefix fieldName = \case
AFCount countAggregate ->
let aggregate =
case countAggregate of
StarCount -> API.StarCount
-- TODO(redactionExp): Do something with these redaction expressions
ColumnCount (column, _redactionExp) -> API.ColumnCount $ API.ColumnCountAggregate {_ccaColumn = Witch.from column, _ccaDistinct = False}
ColumnDistinctCount (column, _redactionExp) -> API.ColumnCount $ API.ColumnCountAggregate {_ccaColumn = Witch.from column, _ccaDistinct = True}
in pure $ HashMap.singleton (applyPrefix fieldPrefix fieldName) aggregate
translateAggregateField sourceName fieldPrefix fieldName = \case
AFCount countAggregate -> do
aggregate <-
case countAggregate of
StarCount -> pure API.StarCount
ColumnCount (column, redactionExp) -> do
redactionExpName <- recordRedactionExpression sourceName redactionExp
pure $ API.ColumnCount $ API.ColumnCountAggregate {_ccaColumn = Witch.from column, _ccaRedactionExpression = redactionExpName, _ccaDistinct = False}
ColumnDistinctCount (column, redactionExp) -> do
redactionExpName <- recordRedactionExpression sourceName redactionExp
pure $ API.ColumnCount $ API.ColumnCountAggregate {_ccaColumn = Witch.from column, _ccaRedactionExpression = redactionExpName, _ccaDistinct = True}
pure $ HashMap.singleton (applyPrefix fieldPrefix fieldName) aggregate
AFOp AggregateOp {..} -> do
let fieldPrefix' = fieldPrefix <> prefixWith fieldName
aggFunction <- translateSingleColumnAggregateFunction _aoOp
fmap (HashMap.fromList . catMaybes) . forM _aoFields $ \(columnFieldName, columnField) ->
case columnField of
-- TODO(redactionExp): Do something with the redactionExp
SFCol column resultType _redactionExp ->
SFCol column resultType redactionExp -> do
redactionExpName <- recordRedactionExpression sourceName redactionExp
let resultScalarType = Witch.from $ columnTypeToScalarType resultType
in pure . Just $ (applyPrefix fieldPrefix' columnFieldName, API.SingleColumn $ API.SingleColumnAggregate aggFunction (Witch.from column) resultScalarType)
pure $ Just (applyPrefix fieldPrefix' columnFieldName, API.SingleColumn $ API.SingleColumnAggregate aggFunction (Witch.from column) redactionExpName resultScalarType)
SFExp _txt ->
-- We ignore literal text fields (we don't send them to the data connector agent)
-- and add them back to the response JSON when we reshape what the agent returns
@ -517,18 +565,19 @@ translateSingleColumnAggregateFunction functionName =
`onNothing` throw500 ("translateSingleColumnAggregateFunction: Invalid aggregate function encountered: " <> functionName)
translateNestedObjectSelect ::
( Has TableRelationships writerOutput,
Monoid writerOutput,
( MonadState state m,
Has TableRelationships state,
Has RedactionExpressionState state,
MonadError QErr m,
MonadReader r m,
Has API.ScalarTypesCapabilities r
Has API.ScalarTypesCapabilities r,
Has SessionVariables r
) =>
SessionVariables ->
TableRelationshipsKey ->
API.TargetName ->
AnnNestedObjectSelectG 'DataConnector Void (UnpreparedValue 'DataConnector) ->
CPS.WriterT writerOutput m API.Query
translateNestedObjectSelect sessionVariables relationshipKey selectG = do
FieldsAndAggregates {..} <- translateAnnFieldsWithNoAggregates sessionVariables noPrefix relationshipKey $ _anosFields selectG
m API.Query
translateNestedObjectSelect relationshipKey selectG = do
FieldsAndAggregates {..} <- translateAnnFieldsWithNoAggregates noPrefix relationshipKey $ _anosFields selectG
pure
API.Query
{ _qFields = mapFieldNameHashMap <$> _faaFields,

View File

@ -6,7 +6,6 @@ where
--------------------------------------------------------------------------------
import Control.Lens ((?~))
import Control.Monad.Trans.Writer.CPS qualified as CPS
import Data.Aeson qualified as J
import Data.Aeson.Encoding qualified as JE
import Data.Aeson.Key qualified as K
@ -34,8 +33,11 @@ import Witch qualified
mkRemoteRelationshipPlan ::
forall m r.
(MonadError QErr m, MonadReader r m, Has API.ScalarTypesCapabilities r) =>
SessionVariables ->
( MonadError QErr m,
MonadReader r m,
Has API.ScalarTypesCapabilities r,
Has SessionVariables r
) =>
SourceConfig ->
-- | List of join json objects, each of which contains IDs to be laterally-joined against
-- as well as an argument ID that identifies the particular set of IDs (ie 'row' to join against).
@ -52,7 +54,7 @@ mkRemoteRelationshipPlan ::
FieldName ->
SourceRelationshipSelection 'DataConnector Void UnpreparedValue ->
m (Plan API.QueryRequest API.QueryResponse)
mkRemoteRelationshipPlan sessionVariables _sourceConfig joinIds joinIdsSchema argumentIdFieldName resultFieldName ir = do
mkRemoteRelationshipPlan _sourceConfig joinIds joinIdsSchema argumentIdFieldName resultFieldName ir = do
foreachRowFilter <- traverse (translateForeachRowFilter argumentIdFieldName joinIdsSchema) joinIds
argumentIds <- extractArgumentIds argumentIdFieldName joinIds
queryRequest <- translateSourceRelationshipSelection foreachRowFilter ir
@ -66,10 +68,10 @@ mkRemoteRelationshipPlan sessionVariables _sourceConfig joinIds joinIdsSchema ar
SourceRelationshipObject objectSelect ->
translateAnnObjectSelectToQueryRequest foreachRowFilter objectSelect
SourceRelationshipArray simpleSelect ->
QueryPlan.translateAnnSimpleSelectToQueryRequest sessionVariables simpleSelect
QueryPlan.translateAnnSimpleSelectToQueryRequest simpleSelect
<&> (API.qrForeach ?~ foreachRowFilter)
SourceRelationshipArrayAggregate aggregateSelect ->
QueryPlan.translateAnnAggregateSelectToQueryRequest sessionVariables aggregateSelect
QueryPlan.translateAnnAggregateSelectToQueryRequest aggregateSelect
<&> (API.qrForeach ?~ foreachRowFilter)
translateAnnObjectSelectToQueryRequest ::
@ -80,16 +82,18 @@ mkRemoteRelationshipPlan sessionVariables _sourceConfig joinIds joinIdsSchema ar
let tableName = case _aosTarget of
FromTable table -> Witch.from table
other -> error $ "translateAnnObjectSelectToQueryRequest: " <> show other
((fields, whereClause), (TableRelationships tableRelationships)) <- CPS.runWriterT $ do
fields <- QueryPlan.translateAnnFields sessionVariables noPrefix (TableNameKey tableName) _aosFields
whereClause <- translateBoolExpToExpression sessionVariables (TableNameKey tableName) _aosTargetFilter
pure (fields, whereClause)
((fields, whereClause), (TableRelationships tableRelationships, redactionExpressionState)) <-
flip runStateT (mempty, RedactionExpressionState mempty) do
fields <- QueryPlan.translateAnnFields noPrefix (API.TNTable tableName) _aosFields
whereClause <- translateBoolExpToExpression (API.TNTable tableName) _aosTargetFilter
pure (fields, whereClause)
let apiTableRelationships = Set.fromList $ tableRelationshipsToList tableRelationships
pure
$ API.QRTable
$ API.TableRequest
{ _trTable = tableName,
_trRelationships = apiTableRelationships,
_trRedactionExpressions = translateRedactionExpressions redactionExpressionState,
_trQuery =
API.Query
{ _qFields = Just $ mapFieldNameHashMap fields,
@ -103,12 +107,12 @@ mkRemoteRelationshipPlan sessionVariables _sourceConfig joinIds joinIdsSchema ar
_trForeach = Just foreachRowFilter
}
tableRelationshipsToList :: HashMap TableRelationshipsKey (HashMap API.RelationshipName API.Relationship) -> [API.Relationships]
tableRelationshipsToList :: HashMap API.TargetName (HashMap API.RelationshipName API.Relationship) -> [API.Relationships]
tableRelationshipsToList m = map (either (API.RFunction . uncurry API.FunctionRelationships) (API.RTable . uncurry API.TableRelationships) . tableRelationshipsKeyToEither) (HashMap.toList m)
tableRelationshipsKeyToEither :: (TableRelationshipsKey, c) -> Either (API.FunctionName, c) (API.TableName, c)
tableRelationshipsKeyToEither (FunctionNameKey f, x) = Left (f, x)
tableRelationshipsKeyToEither (TableNameKey t, x) = Right (t, x)
tableRelationshipsKeyToEither :: (API.TargetName, c) -> Either (API.FunctionName, c) (API.TableName, c)
tableRelationshipsKeyToEither (API.TNFunction f, x) = Left (f, x)
tableRelationshipsKeyToEither (API.TNTable t, x) = Right (t, x)
translateForeachRowFilter :: (MonadError QErr m) => FieldName -> HashMap FieldName (ColumnName, ScalarType) -> J.Object -> m (HashMap API.ColumnName API.ScalarValue)
translateForeachRowFilter argumentIdFieldName joinIdsSchema joinIds =

View File

@ -11,6 +11,7 @@ where
import Data.Aeson.QQ.Simple (aesonQQ)
import Hasura.Backends.DataConnector.API.V0
import Hasura.Backends.DataConnector.API.V0.ColumnSpec (genColumnName)
import Hasura.Backends.DataConnector.API.V0.ExpressionSpec (genRedactionExpressionName)
import Hasura.Backends.DataConnector.API.V0.ScalarSpec (genScalarType)
import Hasura.Prelude
import Hedgehog
@ -25,20 +26,22 @@ spec = do
describe "Aggregate" $ do
describe "SingleColumn" $ do
testToFromJSONToSchema
(SingleColumn $ SingleColumnAggregate (SingleColumnAggregateFunction [G.name|avg|]) (ColumnName "my_column_name") (ScalarType "number"))
(SingleColumn $ SingleColumnAggregate (SingleColumnAggregateFunction [G.name|avg|]) (ColumnName "my_column_name") (Just $ RedactionExpressionName "RedactionExp2") (ScalarType "number"))
[aesonQQ|
{ "type": "single_column",
"function": "avg",
"column": "my_column_name",
"redaction_expression": "RedactionExp2",
"result_type": "number"
}
|]
describe "ColumnCount" $ do
testToFromJSONToSchema
(ColumnCount $ ColumnCountAggregate (ColumnName "my_column_name") True)
(ColumnCount $ ColumnCountAggregate (ColumnName "my_column_name") (Just $ RedactionExpressionName "RedactionExp2") True)
[aesonQQ|
{ "type": "column_count",
"column": "my_column_name",
"redaction_expression": "RedactionExp2",
"distinct": true
}
|]
@ -68,12 +71,14 @@ genSingleColumnAggregate =
SingleColumnAggregate
<$> genSingleColumnAggregateFunction
<*> genColumnName
<*> Gen.maybe genRedactionExpressionName
<*> genScalarType
genColumnCountAggregate :: Gen ColumnCountAggregate
genColumnCountAggregate =
ColumnCountAggregate
<$> genColumnName
<*> Gen.maybe genRedactionExpressionName
<*> Gen.bool
genSingleColumnAggregateFunction :: Gen SingleColumnAggregateFunction

View File

@ -69,11 +69,14 @@ genColumnNullability =
Gen.element [NullableAndNonNullableColumns, OnlyNullableColumns]
genQueryCapabilities :: (MonadGen m) => m QueryCapabilities
genQueryCapabilities = QueryCapabilities <$> Gen.maybe genForeachCapabilities
genQueryCapabilities = QueryCapabilities <$> Gen.maybe genForeachCapabilities <*> Gen.maybe genRedactionCapabilities
genForeachCapabilities :: (MonadGen m) => m ForeachCapabilities
genForeachCapabilities = pure ForeachCapabilities
genRedactionCapabilities :: (MonadGen m) => m RedactionCapabilities
genRedactionCapabilities = pure RedactionCapabilities
genMutationCapabilities :: (MonadGen m) => m MutationCapabilities
genMutationCapabilities =
MutationCapabilities

View File

@ -8,6 +8,8 @@ module Hasura.Backends.DataConnector.API.V0.ExpressionSpec
genUnaryComparisonOperator,
genComparisonValue,
genExpression,
genRedactionExpressionName,
genTargetRedactionExpressions,
)
where
@ -18,7 +20,8 @@ import Hasura.Backends.DataConnector.API.V0.ColumnSpec (genColumnName)
import Hasura.Backends.DataConnector.API.V0.RelationshipsSpec (genRelationshipName)
import Hasura.Backends.DataConnector.API.V0.ScalarSpec (genScalarType, genScalarValue)
import Hasura.Backends.DataConnector.API.V0.TableSpec (genTableName)
import Hasura.Generator.Common (defaultRange, genArbitraryAlphaNumTextExcluding)
import Hasura.Backends.DataConnector.API.V0.TargetSpec (genTargetName)
import Hasura.Generator.Common (defaultRange, genArbitraryAlphaNumText, genArbitraryAlphaNumTextExcluding, genHashMap)
import Hasura.Prelude
import Hedgehog
import Hedgehog.Gen qualified as Gen
@ -68,8 +71,8 @@ spec = do
describe "ComparisonColumn" $ do
testToFromJSONToSchema
(ComparisonColumn QueryTable (mkColumnSelector $ ColumnName "column_name") (ScalarType "string"))
[aesonQQ|{"path": ["$"], "name": "column_name", "column_type": "string"}|]
(ComparisonColumn QueryTable (mkColumnSelector $ ColumnName "column_name") (ScalarType "string") (Just $ RedactionExpressionName "RedactionExp1"))
[aesonQQ|{"path": ["$"], "name": "column_name", "column_type": "string", "redaction_expression": "RedactionExp1"}|]
jsonOpenApiProperties genComparisonColumn
@ -90,7 +93,7 @@ spec = do
describe "ComparisonValue" $ do
describe "AnotherColumnComparison"
$ testToFromJSONToSchema
(AnotherColumnComparison $ ComparisonColumn CurrentTable (mkColumnSelector $ ColumnName "my_column_name") (ScalarType "string"))
(AnotherColumnComparison $ ComparisonColumn CurrentTable (mkColumnSelector $ ColumnName "my_column_name") (ScalarType "string") Nothing)
[aesonQQ|{"type": "column", "column": {"name": "my_column_name", "column_type": "string"}}|]
describe "ScalarValueComparison"
$ testToFromJSONToSchema
@ -119,7 +122,7 @@ spec = do
jsonOpenApiProperties genExistsInTable
describe "Expression" $ do
let comparisonColumn = ComparisonColumn CurrentTable (mkColumnSelector $ ColumnName "my_column_name") (ScalarType "string")
let comparisonColumn = ComparisonColumn CurrentTable (mkColumnSelector $ ColumnName "my_column_name") (ScalarType "string") Nothing
let scalarValue = ScalarValueComparison $ ScalarValue (String "scalar value") (ScalarType "string")
let scalarValues = [String "scalar value"]
let unaryComparisonExpression = ApplyUnaryComparisonOperator IsNull comparisonColumn
@ -226,6 +229,30 @@ spec = do
jsonOpenApiProperties genExpression
describe "RedactionExpressionName" $ do
testToFromJSONToSchema (RedactionExpressionName "foo") [aesonQQ|"foo"|]
jsonOpenApiProperties genRedactionExpressionName
describe "TargetRedactionExpressions" $ do
testToFromJSONToSchema
(TargetRedactionExpressions (TNTable $ TableName ["my_table_name"]) mempty)
[aesonQQ|
{ "target": { "type": "table", "table": ["my_table_name"] },
"expressions": {}
}
|]
jsonOpenApiProperties genTargetRedactionExpressions
describe "RedactionExpression" $ do
testToFromJSONToSchema
(RedactionExpression $ And [])
[aesonQQ|
{ "type": "and",
"expressions": []
}
|]
jsonOpenApiProperties genRedactionExpression
genBinaryComparisonOperator :: (MonadGen m, GenBase m ~ Identity) => m BinaryComparisonOperator
genBinaryComparisonOperator =
Gen.choice
@ -256,6 +283,7 @@ genComparisonColumn =
<$> genColumnPath
<*> genColumnSelector
<*> genScalarType
<*> Gen.maybe genRedactionExpressionName
genColumnPath :: (MonadGen m) => m ColumnPath
genColumnPath =
@ -294,3 +322,15 @@ genExpression =
]
where
genExpressions = Gen.set defaultRange genExpression
genRedactionExpressionName :: (MonadGen m) => m RedactionExpressionName
genRedactionExpressionName = RedactionExpressionName <$> genArbitraryAlphaNumText defaultRange
genTargetRedactionExpressions :: (MonadGen m, GenBase m ~ Identity) => m TargetRedactionExpressions
genTargetRedactionExpressions =
TargetRedactionExpressions
<$> genTargetName
<*> genHashMap genRedactionExpressionName genRedactionExpression defaultRange
genRedactionExpression :: (MonadGen m, GenBase m ~ Identity) => m RedactionExpression
genRedactionExpression = RedactionExpression <$> genExpression

View File

@ -0,0 +1,42 @@
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE QuasiQuotes #-}
module Hasura.Backends.DataConnector.API.V0.FunctionSpec
( spec,
genFunctionName,
genFunctionArgument,
)
where
import Data.Aeson.QQ.Simple (aesonQQ)
import Hasura.Backends.DataConnector.API.V0
import Hasura.Backends.DataConnector.API.V0.ScalarSpec (genScalarType)
import Hasura.Generator.Common (defaultRange, genArbitraryAlphaNumText)
import Hasura.Prelude
import Hedgehog
import Hedgehog.Gen qualified as Gen
import Hedgehog.Internal.Range (linear)
import Test.Aeson.Utils (genValue, jsonOpenApiProperties, testToFromJSONToSchema)
import Test.Hspec
spec :: Spec
spec = do
describe "FunctionName" $ do
testToFromJSONToSchema (TableName ["my_function_name"]) [aesonQQ|["my_function_name"]|]
jsonOpenApiProperties genFunctionName
genFunctionName :: (MonadGen m) => m FunctionName
genFunctionName = FunctionName <$> Gen.nonEmpty (linear 1 3) (genArbitraryAlphaNumText defaultRange)
genFunctionArgument :: Gen FunctionArgument
genFunctionArgument =
NamedArgument
<$> genArbitraryAlphaNumText defaultRange
<*> genArgumentValue
genArgumentValue :: Gen ArgumentValue
genArgumentValue =
fmap ScalarArgumentValue
$ ScalarValue
<$> genValue
<*> genScalarType

View File

@ -11,7 +11,7 @@ import Data.Aeson.QQ.Simple (aesonQQ)
import Hasura.Backends.DataConnector.API.V0
import Hasura.Backends.DataConnector.API.V0.CapabilitiesSpec (genUpdateColumnOperatorName)
import Hasura.Backends.DataConnector.API.V0.ColumnSpec (genColumnName, genColumnType, genColumnValueGenerationStrategy)
import Hasura.Backends.DataConnector.API.V0.ExpressionSpec (genExpression)
import Hasura.Backends.DataConnector.API.V0.ExpressionSpec (genExpression, genTargetRedactionExpressions)
import Hasura.Backends.DataConnector.API.V0.QuerySpec (genField, genFieldMap, genFieldValue)
import Hasura.Backends.DataConnector.API.V0.RelationshipsSpec (genRelationshipName, genTableRelationships)
import Hasura.Backends.DataConnector.API.V0.ScalarSpec (genScalarType)
@ -28,7 +28,7 @@ spec :: Spec
spec = do
describe "MutationRequest" $ do
testToFromJSONToSchema
(MutationRequest [] [] [])
(MutationRequest [] [] [] [])
[aesonQQ|
{ "table_relationships": [],
"insert_schema": [],
@ -93,7 +93,7 @@ spec = do
jsonOpenApiProperties genInsertFieldSchema
describe "MutationOperation" $ do
let returningFields = [(FieldName "field", ColumnField (ColumnName "my_column") (ScalarType "string"))]
let returningFields = [(FieldName "field", ColumnField (ColumnName "my_column") (ScalarType "string") (Just $ RedactionExpressionName "RedactionExp1"))]
describe "InsertOperation" $ do
testToFromJSONToSchema
(InsertOperation (InsertMutationOperation (TableName ["my_table"]) [] (Just $ And []) returningFields))
@ -106,7 +106,8 @@ spec = do
"field": {
"type": "column",
"column": "my_column",
"column_type": "string"
"column_type": "string",
"redaction_expression": "RedactionExp1"
}
}
}
@ -124,7 +125,8 @@ spec = do
"field": {
"type": "column",
"column": "my_column",
"column_type": "string"
"column_type": "string",
"redaction_expression": "RedactionExp1"
}
}
}
@ -140,7 +142,8 @@ spec = do
"field": {
"type": "column",
"column": "my_column",
"column_type": "string"
"column_type": "string",
"redaction_expression": "RedactionExp1"
}
}
}
@ -250,6 +253,7 @@ genMutationRequest :: Gen MutationRequest
genMutationRequest =
MutationRequest
<$> Gen.set defaultRange genTableRelationships
<*> Gen.set defaultRange genTargetRedactionExpressions
<*> Gen.set defaultRange genTableInsertSchema
<*> Gen.list defaultRange genMutationOperation

View File

@ -13,7 +13,7 @@ import Data.HashMap.Strict qualified as HashMap
import Hasura.Backends.DataConnector.API.V0
import Hasura.Backends.DataConnector.API.V0.AggregateSpec (genSingleColumnAggregate)
import Hasura.Backends.DataConnector.API.V0.ColumnSpec (genColumnName)
import Hasura.Backends.DataConnector.API.V0.ExpressionSpec (genExpression)
import Hasura.Backends.DataConnector.API.V0.ExpressionSpec (genExpression, genRedactionExpressionName)
import Hasura.Backends.DataConnector.API.V0.RelationshipsSpec (genRelationshipName)
import Hasura.Generator.Common (defaultRange)
import Hasura.Prelude
@ -28,10 +28,11 @@ spec = do
describe "OrderByTarget" $ do
describe "OrderByColumn"
$ testToFromJSONToSchema
(OrderByColumn (ColumnName "test_column"))
(OrderByColumn (ColumnName "test_column") (Just $ RedactionExpressionName "RedactionExp2"))
[aesonQQ|
{ "type": "column",
"column": "test_column"
"column": "test_column",
"redaction_expression": "RedactionExp2"
}
|]
describe "OrderByStarCountAggregate"
@ -42,12 +43,13 @@ spec = do
|]
describe "OrderBySingleColumnAggregate"
$ testToFromJSONToSchema
(OrderBySingleColumnAggregate (SingleColumnAggregate (SingleColumnAggregateFunction [G.name|sum|]) (ColumnName "test_column") (ScalarType "number")))
(OrderBySingleColumnAggregate (SingleColumnAggregate (SingleColumnAggregateFunction [G.name|sum|]) (ColumnName "test_column") (Just $ RedactionExpressionName "RedactionExp2") (ScalarType "number")))
[aesonQQ|
{ "type": "single_column_aggregate",
"function": "sum",
"column": "test_column",
"result_type": "number"
"result_type": "number",
"redaction_expression": "RedactionExp2"
}
|]
jsonOpenApiProperties genOrderByTarget
@ -56,14 +58,15 @@ spec = do
testToFromJSONToSchema
( OrderByElement
[RelationshipName "relation1", RelationshipName "relation2"]
(OrderByColumn (ColumnName "my_column_name"))
(OrderByColumn (ColumnName "my_column_name") (Just $ RedactionExpressionName "RedactionExp2"))
Ascending
)
[aesonQQ|
{ "target_path": ["relation1", "relation2"],
"target": {
"type": "column",
"column": "my_column_name"
"column": "my_column_name",
"redaction_expression": "RedactionExp2"
},
"order_direction": "asc"
}
@ -145,7 +148,7 @@ genOrderByElement =
genOrderByTarget :: Gen OrderByTarget
genOrderByTarget =
Gen.choice
[ OrderByColumn <$> genColumnName,
[ OrderByColumn <$> genColumnName <*> Gen.maybe genRedactionExpressionName,
pure OrderByStarCountAggregate,
OrderBySingleColumnAggregate <$> genSingleColumnAggregate
]

View File

@ -16,7 +16,8 @@ import Data.HashMap.Strict qualified as HashMap
import Hasura.Backends.DataConnector.API.V0
import Hasura.Backends.DataConnector.API.V0.AggregateSpec (genAggregate)
import Hasura.Backends.DataConnector.API.V0.ColumnSpec (genColumnName)
import Hasura.Backends.DataConnector.API.V0.ExpressionSpec (genExpression)
import Hasura.Backends.DataConnector.API.V0.ExpressionSpec (genExpression, genRedactionExpressionName, genTargetRedactionExpressions)
import Hasura.Backends.DataConnector.API.V0.FunctionSpec (genFunctionArgument, genFunctionName)
import Hasura.Backends.DataConnector.API.V0.OrderBySpec (genOrderBy)
import Hasura.Backends.DataConnector.API.V0.RelationshipsSpec (genRelationshipName, genRelationships)
import Hasura.Backends.DataConnector.API.V0.ScalarSpec (genScalarType, genScalarValue)
@ -25,7 +26,6 @@ import Hasura.Generator.Common (defaultRange, genArbitraryAlphaNumText, genHashM
import Hasura.Prelude
import Hedgehog
import Hedgehog.Gen qualified as Gen
import Hedgehog.Internal.Range (linear)
import Test.Aeson.Utils (genValue, jsonOpenApiProperties, testToFromJSONToSchema)
import Test.Hspec
@ -34,11 +34,12 @@ spec = do
describe "Field" $ do
describe "ColumnField"
$ testToFromJSONToSchema
(ColumnField (ColumnName "my_column_name") (ScalarType "string"))
(ColumnField (ColumnName "my_column_name") (ScalarType "string") (Just $ RedactionExpressionName "RedactionExp0"))
[aesonQQ|
{ "type": "column",
"column": "my_column_name",
"column_type": "string"
"column_type": "string",
"redaction_expression": "RedactionExp0"
}
|]
describe "RelationshipField" $ do
@ -56,13 +57,13 @@ spec = do
describe "Query" $ do
let query =
Query
{ _qFields = Just $ HashMap.fromList [(FieldName "my_field_alias", ColumnField (ColumnName "my_field_name") (ScalarType "string"))],
{ _qFields = Just $ HashMap.fromList [(FieldName "my_field_alias", ColumnField (ColumnName "my_field_name") (ScalarType "string") Nothing)],
_qAggregates = Just $ HashMap.fromList [(FieldName "my_aggregate", StarCount)],
_qAggregatesLimit = Just 5,
_qLimit = Just 10,
_qOffset = Just 20,
_qWhere = Just $ And [],
_qOrderBy = Just $ OrderBy [] (OrderByElement [] (OrderByColumn (ColumnName "my_column_name")) Ascending :| [])
_qOrderBy = Just $ OrderBy [] (OrderByElement [] (OrderByColumn (ColumnName "my_column_name") Nothing) Ascending :| [])
}
testToFromJSONToSchema
query
@ -95,6 +96,7 @@ spec = do
$ TableRequest
{ _trTable = TableName ["my_table"],
_trRelationships = [],
_trRedactionExpressions = [],
_trQuery = Query (Just mempty) Nothing Nothing Nothing Nothing Nothing Nothing,
_trForeach = Just (HashMap.fromList [(ColumnName "my_id", ScalarValue (J.Number 666) (ScalarType "number"))] :| [])
}
@ -119,6 +121,7 @@ spec = do
{ _frFunction = FunctionName ["my_function"],
_frFunctionArguments = [],
_frRelationships = [],
_frRedactionExpressions = [],
_frQuery = Query (Just mempty) Nothing Nothing Nothing Nothing Nothing Nothing
}
testToFromJSONToSchema
@ -185,7 +188,7 @@ genField :: Gen Field
genField =
Gen.recursive
Gen.choice
[ColumnField <$> genColumnName <*> genScalarType]
[ColumnField <$> genColumnName <*> genScalarType <*> Gen.maybe genRedactionExpressionName]
[RelField <$> genRelationshipField]
genFieldName :: Gen FieldName
@ -220,29 +223,15 @@ genFunctionRequest =
<$> genFunctionName
<*> Gen.list defaultRange genFunctionArgument
<*> Gen.set defaultRange genRelationships
<*> Gen.set defaultRange genTargetRedactionExpressions
<*> genQuery
genFunctionName :: (MonadGen m) => m FunctionName
genFunctionName = FunctionName <$> Gen.nonEmpty (linear 1 3) (genArbitraryAlphaNumText defaultRange)
genFunctionArgument :: Gen FunctionArgument
genFunctionArgument =
NamedArgument
<$> genArbitraryAlphaNumText defaultRange
<*> genArgumentValue
genArgumentValue :: Gen ArgumentValue
genArgumentValue =
fmap ScalarArgumentValue
$ ScalarValue
<$> genValue
<*> genScalarType
genTableRequest :: Gen QueryRequest
genTableRequest =
TableQueryRequest
<$> genTableName
<*> Gen.set defaultRange genRelationships
<*> Gen.set defaultRange genTargetRedactionExpressions
<*> genQuery
<*> Gen.maybe (Gen.nonEmpty defaultRange (genHashMap genColumnName genScalarValue defaultRange))

View File

@ -0,0 +1,47 @@
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE QuasiQuotes #-}
module Hasura.Backends.DataConnector.API.V0.TargetSpec
( spec,
genTargetName,
)
where
import Data.Aeson.QQ.Simple (aesonQQ)
import Hasura.Backends.DataConnector.API.V0
import Hasura.Backends.DataConnector.API.V0.FunctionSpec (genFunctionName)
import Hasura.Backends.DataConnector.API.V0.TableSpec (genTableName)
import Hasura.Prelude
import Hedgehog
import Hedgehog.Gen qualified as Gen
import Test.Aeson.Utils (jsonOpenApiProperties, testToFromJSONToSchema)
import Test.Hspec
spec :: Spec
spec = do
describe "TargetName" $ do
describe "TNTable"
$ testToFromJSONToSchema
(TNTable $ TableName ["my_table"])
[aesonQQ|
{ "type": "table",
"table": ["my_table"]
}
|]
describe "TNFunction"
$ testToFromJSONToSchema
(TNFunction $ FunctionName ["my_function"])
[aesonQQ|
{ "type": "function",
"function": ["my_function"]
}
|]
jsonOpenApiProperties genTargetName
genTargetName :: (MonadGen m) => m TargetName
genTargetName =
Gen.choice
[ TNTable <$> genTableName,
TNFunction <$> genFunctionName
]