From 82fa13db6e09ffc350bcc0f0aa83eff83ef5adcd Mon Sep 17 00:00:00 2001 From: David Overton Date: Mon, 30 Oct 2023 13:25:57 +1100 Subject: [PATCH] Support joins on nested fields for MongoDB PR-URL: https://github.com/hasura/graphql-engine-mono/pull/10345 GitOrigin-RevId: 1a4886b7ac5110ddf9233596068810963bde3371 --- dc-agents/dc-api-types/package.json | 2 +- dc-agents/dc-api-types/src/agent.openapi.json | 104 ++++-- .../src/models/ComparisonColumn.ts | 2 +- .../dc-api-types/src/models/Constraint.ts | 2 +- .../dc-api-types/src/models/OrderByColumn.ts | 2 +- .../dc-api-types/src/models/Relationship.ts | 2 +- dc-agents/package-lock.json | 10 +- dc-agents/reference/package-lock.json | 4 +- dc-agents/reference/package.json | 2 +- dc-agents/reference/src/data/index.ts | 2 +- dc-agents/reference/src/query.ts | 2 +- dc-agents/sqlite/package-lock.json | 4 +- dc-agents/sqlite/package.json | 2 +- dc-agents/sqlite/src/query.ts | 6 +- metadata.openapi.json | 334 +++++++++++++++--- .../MockAgent/AggregateQuerySpec.hs | 4 +- .../MockAgent/CustomScalarsSpec.hs | 2 +- .../MockAgent/DeleteMutationsSpec.hs | 4 +- .../MockAgent/InsertMutationsSpec.hs | 2 +- .../DataConnector/MockAgent/OrderBySpec.hs | 2 +- .../MockAgent/QueryRelationshipsSpec.hs | 19 +- .../MockAgent/UpdateMutationsSpec.hs | 4 +- .../Backends/DataConnector/API/V0/Column.hs | 64 +++- .../DataConnector/API/V0/Expression.hs | 20 +- .../Backends/DataConnector/API/V0/OrderBy.hs | 1 + .../Backends/DataConnector/API/V0/Query.hs | 3 +- .../DataConnector/API/V0/Relationships.hs | 27 +- .../Backends/DataConnector/API/V0/Table.hs | 6 +- server/lib/dc-api/test/Test/Data.hs | 49 ++- .../dc-api/test/Test/Specs/FunctionsSpec.hs | 4 +- .../Backend/DataConnector/Mock/Server.hs | 55 ++- .../Hasura/Backends/BigQuery/FromIr.hs | 6 +- .../Backends/BigQuery/Instances/Types.hs | 5 + .../Backends/DataConnector/Adapter/Backend.hs | 10 + .../DataConnector/Adapter/Metadata.hs | 7 +- .../Backends/DataConnector/Adapter/Types.hs | 36 ++ .../Backends/DataConnector/Plan/Common.hs | 8 +- .../Backends/DataConnector/Plan/QueryPlan.hs | 8 +- .../Backends/MSSQL/FromIr/Expression.hs | 2 +- .../Hasura/Backends/MSSQL/FromIr/Query.hs | 6 +- .../Hasura/Backends/MSSQL/Instances/Types.hs | 5 + .../Backends/Postgres/Execute/Insert.hs | 8 +- .../Backends/Postgres/Instances/Metadata.hs | 2 +- .../Backends/Postgres/Instances/Types.hs | 5 + .../Backends/Postgres/Translate/BoolExp.hs | 4 +- .../Translate/Select/Internal/OrderBy.hs | 4 +- .../src-lib/Hasura/GraphQL/Execute/Backend.hs | 2 +- .../src-lib/Hasura/GraphQL/Schema/Select.hs | 16 +- server/src-lib/Hasura/NativeQuery/Schema.hs | 10 +- server/src-lib/Hasura/RQL/DDL/Relationship.hs | 53 +-- .../Hasura/RQL/DDL/Relationship/Suggest.hs | 16 +- server/src-lib/Hasura/RQL/DDL/Schema/Diff.hs | 9 +- server/src-lib/Hasura/RQL/DDL/Schema/Enum.hs | 4 +- .../Hasura/RQL/DDL/Schema/LegacyCatalog.hs | 2 +- .../src-lib/Hasura/RQL/DDL/Schema/Rename.hs | 23 +- server/src-lib/Hasura/RQL/DML/Select.hs | 2 +- .../Hasura/RQL/IR/Select/RelationSelect.hs | 2 +- server/src-lib/Hasura/RQL/Types/Backend.hs | 24 ++ .../Hasura/RQL/Types/Relationships/Local.hs | 68 +++- .../src-lib/Hasura/RQL/Types/Source/Table.hs | 3 +- server/src-lib/Hasura/Table/Cache.hs | 2 +- .../DataConnector/API/V0/ColumnSpec.hs | 15 +- .../DataConnector/API/V0/ExpressionSpec.hs | 14 +- .../DataConnector/API/V0/OrderBySpec.hs | 3 +- .../DataConnector/API/V0/RelationshipsSpec.hs | 15 +- .../DataConnector/API/V0/TableSpec.hs | 13 +- .../Postgres/RQLGenerator/GenSelectArgsG.hs | 3 + .../Postgres/RQLGenerator/GenTablePermG.hs | 2 +- .../BoolExp/AggregationPredicatesSpec.hs | 4 +- server/src-test/Hasura/RQL/IR/Generator.hs | 55 ++- 70 files changed, 908 insertions(+), 314 deletions(-) diff --git a/dc-agents/dc-api-types/package.json b/dc-agents/dc-api-types/package.json index 9487dc6d60b..f03c7f1871d 100644 --- a/dc-agents/dc-api-types/package.json +++ b/dc-agents/dc-api-types/package.json @@ -1,6 +1,6 @@ { "name": "@hasura/dc-api-types", - "version": "0.42.0", + "version": "0.43.0", "description": "Hasura GraphQL Engine Data Connector Agent API types", "author": "Hasura (https://github.com/hasura/graphql-engine)", "license": "Apache-2.0", diff --git a/dc-agents/dc-api-types/src/agent.openapi.json b/dc-agents/dc-api-types/src/agent.openapi.json index ab0d1dd045c..88cb24f1101 100644 --- a/dc-agents/dc-api-types/src/agent.openapi.json +++ b/dc-agents/dc-api-types/src/agent.openapi.json @@ -1334,11 +1334,42 @@ "Constraint": { "properties": { "column_mapping": { - "additionalProperties": { - "type": "string" - }, - "description": "The columns on which you want want to define the foreign key.", - "type": "object" + "additionalProperties": true, + "anyOf": [ + { + "additionalProperties": { + "oneOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "type": "object" + }, + { + "additionalProperties": { + "oneOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "type": "object" + } + ], + "description": "The columns on which you want want to define the foreign key." }, "foreign_table": { "$ref": "#/components/schemas/TableName" @@ -1845,11 +1876,42 @@ "Relationship": { "properties": { "column_mapping": { - "additionalProperties": { - "type": "string" - }, - "description": "A mapping between columns on the source table to columns on the target table", - "type": "object" + "additionalProperties": true, + "anyOf": [ + { + "additionalProperties": { + "oneOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "type": "object" + }, + { + "additionalProperties": { + "oneOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "type": "object" + } + ], + "description": "A mapping between columns on the source table to columns on the target table" }, "relationship_type": { "$ref": "#/components/schemas/RelationshipType" @@ -2068,19 +2130,18 @@ "$ref": "#/components/schemas/ScalarType" }, "name": { - "additionalProperties": true, - "anyOf": [ - { - "type": "string" - }, + "description": "The name of the column", + "oneOf": [ { "items": { "type": "string" }, "type": "array" + }, + { + "type": "string" } - ], - "description": "The name of the column" + ] }, "path": { "default": [], @@ -2620,16 +2681,15 @@ "OrderByColumn": { "properties": { "column": { - "additionalProperties": true, - "anyOf": [ - { - "type": "string" - }, + "oneOf": [ { "items": { "type": "string" }, "type": "array" + }, + { + "type": "string" } ] }, diff --git a/dc-agents/dc-api-types/src/models/ComparisonColumn.ts b/dc-agents/dc-api-types/src/models/ComparisonColumn.ts index 0477afe5e61..95fc235aa80 100644 --- a/dc-agents/dc-api-types/src/models/ComparisonColumn.ts +++ b/dc-agents/dc-api-types/src/models/ComparisonColumn.ts @@ -10,7 +10,7 @@ export type ComparisonColumn = { /** * The name of the column */ - name: (string | Array); + name: (Array | string); /** * 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. */ diff --git a/dc-agents/dc-api-types/src/models/Constraint.ts b/dc-agents/dc-api-types/src/models/Constraint.ts index 3b1c2c60b7f..47cb90674db 100644 --- a/dc-agents/dc-api-types/src/models/Constraint.ts +++ b/dc-agents/dc-api-types/src/models/Constraint.ts @@ -8,7 +8,7 @@ export type Constraint = { /** * The columns on which you want want to define the foreign key. */ - column_mapping: Record; + column_mapping: Record | string)>; foreign_table: TableName; }; diff --git a/dc-agents/dc-api-types/src/models/OrderByColumn.ts b/dc-agents/dc-api-types/src/models/OrderByColumn.ts index 95d11949035..16ab531bfc4 100644 --- a/dc-agents/dc-api-types/src/models/OrderByColumn.ts +++ b/dc-agents/dc-api-types/src/models/OrderByColumn.ts @@ -5,7 +5,7 @@ import type { RedactionExpressionName } from './RedactionExpressionName'; export type OrderByColumn = { - column: (string | Array); + column: (Array | string); redaction_expression?: RedactionExpressionName; type: 'column'; }; diff --git a/dc-agents/dc-api-types/src/models/Relationship.ts b/dc-agents/dc-api-types/src/models/Relationship.ts index c02bebe6408..d07459c0e0f 100644 --- a/dc-agents/dc-api-types/src/models/Relationship.ts +++ b/dc-agents/dc-api-types/src/models/Relationship.ts @@ -9,7 +9,7 @@ export type Relationship = { /** * A mapping between columns on the source table to columns on the target table */ - column_mapping: Record; + column_mapping: Record | string)>; relationship_type: RelationshipType; target: Target; }; diff --git a/dc-agents/package-lock.json b/dc-agents/package-lock.json index 357e11b719d..de103363b0d 100644 --- a/dc-agents/package-lock.json +++ b/dc-agents/package-lock.json @@ -24,7 +24,7 @@ }, "dc-api-types": { "name": "@hasura/dc-api-types", - "version": "0.42.0", + "version": "0.43.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.42.0", + "@hasura/dc-api-types": "0.43.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.42.0", + "@hasura/dc-api-types": "0.43.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.42.0", + "@hasura/dc-api-types": "0.43.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.42.0", + "@hasura/dc-api-types": "0.43.0", "@tsconfig/node16": "^1.0.3", "@types/node": "^16.11.49", "@types/sqlite3": "^3.1.8", diff --git a/dc-agents/reference/package-lock.json b/dc-agents/reference/package-lock.json index 7ab0c4df717..99b9245cd5d 100644 --- a/dc-agents/reference/package-lock.json +++ b/dc-agents/reference/package-lock.json @@ -10,7 +10,7 @@ "license": "Apache-2.0", "dependencies": { "@fastify/cors": "^8.1.0", - "@hasura/dc-api-types": "0.42.0", + "@hasura/dc-api-types": "0.43.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.42.0", + "version": "0.43.0", "license": "Apache-2.0", "devDependencies": { "@tsconfig/node16": "^1.0.3", diff --git a/dc-agents/reference/package.json b/dc-agents/reference/package.json index 27f44579405..1c812507878 100644 --- a/dc-agents/reference/package.json +++ b/dc-agents/reference/package.json @@ -22,7 +22,7 @@ }, "dependencies": { "@fastify/cors": "^8.1.0", - "@hasura/dc-api-types": "0.42.0", + "@hasura/dc-api-types": "0.43.0", "fastify": "^4.13.0", "mathjs": "^11.0.0", "pino-pretty": "^8.0.0", diff --git a/dc-agents/reference/src/data/index.ts b/dc-agents/reference/src/data/index.ts index 37f18cb93a8..ca9d8e067bd 100644 --- a/dc-agents/reference/src/data/index.ts +++ b/dc-agents/reference/src/data/index.ts @@ -175,7 +175,7 @@ export const getSchema = (store: Record, config: Config, req ? mapObjectValues(table.foreign_keys, constraint => ({ ...constraint, foreign_table: prefixSchemaToTableName(constraint.foreign_table.map(applyTableNameCasing)), - column_mapping: mapObject(constraint.column_mapping, ([outer, inner]) => [applyColumnNameCasing(outer), applyColumnNameCasing(inner)]) + column_mapping: mapObject(constraint.column_mapping as Record, ([outer, inner]) => [applyColumnNameCasing(outer), applyColumnNameCasing(inner)]) })) : table.foreign_keys, columns: table.columns?.map(column => ({ diff --git a/dc-agents/reference/src/query.ts b/dc-agents/reference/src/query.ts index 5bf32d8ed01..9f67ef9118f 100644 --- a/dc-agents/reference/src/query.ts +++ b/dc-agents/reference/src/query.ts @@ -484,7 +484,7 @@ const makeApplyRedaction = ( const createFilterExpressionForRelationshipJoin = (row: Record, relationship: Relationship): Expression | null => { const columnMappings = Object.entries(relationship.column_mapping); const filterConditions: Expression[] = columnMappings - .map(([outerColumnName, innerColumnName]): [RawScalarValue, string] => [row[outerColumnName], innerColumnName]) + .map(([outerColumnName, innerColumnName]): [RawScalarValue, string] => [row[outerColumnName], innerColumnName as string]) .filter((x): x is [RawScalarValue, string] => { const [outerValue, _] = x; return outerValue !== null; diff --git a/dc-agents/sqlite/package-lock.json b/dc-agents/sqlite/package-lock.json index 9940939b696..6fb00835c44 100644 --- a/dc-agents/sqlite/package-lock.json +++ b/dc-agents/sqlite/package-lock.json @@ -10,7 +10,7 @@ "license": "Apache-2.0", "dependencies": { "@fastify/cors": "^8.1.0", - "@hasura/dc-api-types": "0.42.0", + "@hasura/dc-api-types": "0.43.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.42.0", + "version": "0.43.0", "license": "Apache-2.0", "devDependencies": { "@tsconfig/node16": "^1.0.3", diff --git a/dc-agents/sqlite/package.json b/dc-agents/sqlite/package.json index 2dc626acced..644797396d9 100644 --- a/dc-agents/sqlite/package.json +++ b/dc-agents/sqlite/package.json @@ -22,7 +22,7 @@ }, "dependencies": { "@fastify/cors": "^8.1.0", - "@hasura/dc-api-types": "0.42.0", + "@hasura/dc-api-types": "0.43.0", "fastify-metrics": "^9.2.1", "fastify": "^4.13.0", "nanoid": "^3.3.4", diff --git a/dc-agents/sqlite/src/query.ts b/dc-agents/sqlite/src/query.ts index 31dc6bc65f6..943d4643a6c 100644 --- a/dc-agents/sqlite/src/query.ts +++ b/dc-agents/sqlite/src/query.ts @@ -234,7 +234,7 @@ function generateRelationshipJoinComparisonFragments(relationship: Relationship, return Object .entries(relationship.column_mapping) .map(([sourceColumnName, targetColumnName]) => - `${sourceTablePrefix}${escapeIdentifier(sourceColumnName)} = ${targetTableAlias}.${escapeIdentifier(targetColumnName)}`); + `${sourceTablePrefix}${escapeIdentifier(sourceColumnName)} = ${targetTableAlias}.${escapeIdentifier(targetColumnName as string)}`); } function generateComparisonColumnFragment(comparisonColumn: ComparisonColumn, queryTableAlias: string, currentTableAlias: string): string { @@ -419,7 +419,7 @@ function target_query( // TODO: Rename as `target_query` function relationship(ts: Relationships[], r: Relationship, field: RelationshipField, sourceTableAlias: string): string { const relationshipJoinInfo = { sourceTableAlias, - columnMapping: r.column_mapping, + columnMapping: r.column_mapping as Record, }; // We force a limit of 1 for object relationships in case the user has configured a manual @@ -638,7 +638,7 @@ function generateOrderByAggregateTargetJoinInfo( default: unreachable(element.target["type"]); } }); - const joinColumns = Object.values(relationship.column_mapping).map(escapeIdentifier); + const joinColumns = Object.values(relationship.column_mapping as Record).map(escapeIdentifier); const selectColumns = [...joinColumns, aggregateColumnsFragments]; const whereClause = whereExpression ? `WHERE ${where_clause(allTableRelationships, whereExpression, relationship.target, subqueryTableAlias)}` : ""; const aggregateSubquery = `SELECT ${selectColumns.join(", ")} FROM ${escapeTargetName(relationship.target)} AS ${subqueryTableAlias} ${whereClause} GROUP BY ${joinColumns.join(", ")}` diff --git a/metadata.openapi.json b/metadata.openapi.json index 17628f38b08..812375c8e8e 100644 --- a/metadata.openapi.json +++ b/metadata.openapi.json @@ -965,10 +965,21 @@ "BigqueryRelManualNativeQueryConfig": { "properties": { "column_mapping": { - "additionalProperties": { - "type": "string" - }, - "type": "object" + "additionalProperties": true, + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + ] }, "insertion_order": { "enum": [ @@ -991,10 +1002,21 @@ "BigqueryRelManualTableConfig": { "properties": { "column_mapping": { - "additionalProperties": { - "type": "string" - }, - "type": "object" + "additionalProperties": true, + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + ] }, "insertion_order": { "enum": [ @@ -1946,10 +1968,21 @@ "CitusRelManualNativeQueryConfig": { "properties": { "column_mapping": { - "additionalProperties": { - "type": "string" - }, - "type": "object" + "additionalProperties": true, + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + ] }, "insertion_order": { "enum": [ @@ -1972,10 +2005,21 @@ "CitusRelManualTableConfig": { "properties": { "column_mapping": { - "additionalProperties": { - "type": "string" - }, - "type": "object" + "additionalProperties": true, + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + ] }, "insertion_order": { "enum": [ @@ -2954,10 +2998,21 @@ "CockroachRelManualNativeQueryConfig": { "properties": { "column_mapping": { - "additionalProperties": { - "type": "string" - }, - "type": "object" + "additionalProperties": true, + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + ] }, "insertion_order": { "enum": [ @@ -2980,10 +3035,21 @@ "CockroachRelManualTableConfig": { "properties": { "column_mapping": { - "additionalProperties": { - "type": "string" - }, - "type": "object" + "additionalProperties": true, + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + ] }, "insertion_order": { "enum": [ @@ -3733,7 +3799,17 @@ "properties": { "columns": { "items": { - "type": "string" + "oneOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] }, "type": "array" }, @@ -3753,7 +3829,17 @@ "DataconnectorArrRelUsingFKeyOnSingleColumn": { "properties": { "column": { - "type": "string" + "oneOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] }, "table": { "items": { @@ -4055,7 +4141,17 @@ "properties": { "columns": { "items": { - "type": "string" + "oneOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] }, "type": "array" }, @@ -4075,7 +4171,17 @@ "DataconnectorObjRelRemoteTableSingleColumn": { "properties": { "column": { - "type": "string" + "oneOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] }, "table": { "items": { @@ -4112,10 +4218,41 @@ "DataconnectorRelManualNativeQueryConfig": { "properties": { "column_mapping": { - "additionalProperties": { - "type": "string" - }, - "type": "object" + "additionalProperties": true, + "anyOf": [ + { + "additionalProperties": { + "oneOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "type": "object" + }, + { + "additionalProperties": { + "oneOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "type": "object" + } + ] }, "insertion_order": { "enum": [ @@ -4138,10 +4275,41 @@ "DataconnectorRelManualTableConfig": { "properties": { "column_mapping": { - "additionalProperties": { - "type": "string" - }, - "type": "object" + "additionalProperties": true, + "anyOf": [ + { + "additionalProperties": { + "oneOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "type": "object" + }, + { + "additionalProperties": { + "oneOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "type": "object" + } + ] }, "insertion_order": { "enum": [ @@ -5825,10 +5993,21 @@ "MssqlRelManualNativeQueryConfig": { "properties": { "column_mapping": { - "additionalProperties": { - "type": "string" - }, - "type": "object" + "additionalProperties": true, + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + ] }, "insertion_order": { "enum": [ @@ -5851,10 +6030,21 @@ "MssqlRelManualTableConfig": { "properties": { "column_mapping": { - "additionalProperties": { - "type": "string" - }, - "type": "object" + "additionalProperties": true, + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + ] }, "insertion_order": { "enum": [ @@ -7208,10 +7398,21 @@ "PostgresRelManualNativeQueryConfig": { "properties": { "column_mapping": { - "additionalProperties": { - "type": "string" - }, - "type": "object" + "additionalProperties": true, + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + ] }, "insertion_order": { "enum": [ @@ -7234,10 +7435,21 @@ "PostgresRelManualTableConfig": { "properties": { "column_mapping": { - "additionalProperties": { - "type": "string" - }, - "type": "object" + "additionalProperties": true, + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + ] }, "insertion_order": { "enum": [ @@ -7933,12 +8145,28 @@ "properties": { "foreign_key_constraint_on": { "oneOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, { "type": "string" }, { "items": { - "type": "string" + "oneOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] }, "type": "array" }, diff --git a/server/lib/api-tests/src/Test/DataConnector/MockAgent/AggregateQuerySpec.hs b/server/lib/api-tests/src/Test/DataConnector/MockAgent/AggregateQuerySpec.hs index 90e2c19bbc7..e8a8d888530 100644 --- a/server/lib/api-tests/src/Test/DataConnector/MockAgent/AggregateQuerySpec.hs +++ b/server/lib/api-tests/src/Test/DataConnector/MockAgent/AggregateQuerySpec.hs @@ -177,7 +177,7 @@ tests = describe "Aggregate Query Tests" $ do API.Relationship { _rTarget = mkTableTarget "Album", _rRelationshipType = API.ArrayRelationship, - _rColumnMapping = HashMap.fromList [(API.ColumnName "ArtistId", API.ColumnName "ArtistId")] + _rColumnMapping = API.ColumnPathMapping $ HashMap.fromList [(API.mkColumnSelector $ API.ColumnName "ArtistId", API.mkColumnSelector $ API.ColumnName "ArtistId")] } ) ] @@ -300,7 +300,7 @@ tests = describe "Aggregate Query Tests" $ do API.Relationship { _rTarget = mkTableTarget "InvoiceLine", _rRelationshipType = API.ArrayRelationship, - _rColumnMapping = HashMap.fromList [(API.ColumnName "InvoiceId", API.ColumnName "InvoiceId")] + _rColumnMapping = API.ColumnPathMapping $ HashMap.fromList [(API.mkColumnSelector $ API.ColumnName "InvoiceId", API.mkColumnSelector $ API.ColumnName "InvoiceId")] } ) ] diff --git a/server/lib/api-tests/src/Test/DataConnector/MockAgent/CustomScalarsSpec.hs b/server/lib/api-tests/src/Test/DataConnector/MockAgent/CustomScalarsSpec.hs index f71c4ca85ff..b04b93feca4 100644 --- a/server/lib/api-tests/src/Test/DataConnector/MockAgent/CustomScalarsSpec.hs +++ b/server/lib/api-tests/src/Test/DataConnector/MockAgent/CustomScalarsSpec.hs @@ -18,7 +18,7 @@ import Harness.Test.BackendType qualified as BackendType import Harness.Test.Fixture qualified as Fixture import Harness.TestEnvironment (GlobalTestEnvironment, TestEnvironment) import Harness.Yaml (shouldBeYaml) -import Hasura.Backends.DataConnector.API (ColumnName (..), ScalarType (..), ScalarValue (..)) +import Hasura.Backends.DataConnector.API (ColumnName (..), ScalarType (..), ScalarValue (..), mkColumnSelector) import Hasura.Backends.DataConnector.API qualified as API import Hasura.Backends.DataConnector.API.V0.Expression import Hasura.Prelude diff --git a/server/lib/api-tests/src/Test/DataConnector/MockAgent/DeleteMutationsSpec.hs b/server/lib/api-tests/src/Test/DataConnector/MockAgent/DeleteMutationsSpec.hs index b94379cc3e7..ffa4e923bee 100644 --- a/server/lib/api-tests/src/Test/DataConnector/MockAgent/DeleteMutationsSpec.hs +++ b/server/lib/api-tests/src/Test/DataConnector/MockAgent/DeleteMutationsSpec.hs @@ -189,7 +189,7 @@ tests = do API.Relationship { API._rTarget = mkTableTarget "Artist", API._rRelationshipType = API.ObjectRelationship, - API._rColumnMapping = HashMap.fromList [(API.ColumnName "ArtistId", API.ColumnName "ArtistId")] + API._rColumnMapping = API.ColumnPathMapping $ HashMap.fromList [(API.mkColumnSelector $ API.ColumnName "ArtistId", API.mkColumnSelector $ API.ColumnName "ArtistId")] } ) ] @@ -298,7 +298,7 @@ tests = do API.Relationship { API._rTarget = mkTableTarget "Artist", API._rRelationshipType = API.ObjectRelationship, - API._rColumnMapping = HashMap.fromList [(API.ColumnName "ArtistId", API.ColumnName "ArtistId")] + API._rColumnMapping = API.ColumnPathMapping $ HashMap.fromList [(API.mkColumnSelector $ API.ColumnName "ArtistId", API.mkColumnSelector $ API.ColumnName "ArtistId")] } ) ] diff --git a/server/lib/api-tests/src/Test/DataConnector/MockAgent/InsertMutationsSpec.hs b/server/lib/api-tests/src/Test/DataConnector/MockAgent/InsertMutationsSpec.hs index cc29fe2d5da..49156a890d9 100644 --- a/server/lib/api-tests/src/Test/DataConnector/MockAgent/InsertMutationsSpec.hs +++ b/server/lib/api-tests/src/Test/DataConnector/MockAgent/InsertMutationsSpec.hs @@ -181,7 +181,7 @@ tests = do API.Relationship { API._rTarget = mkTableTarget "Artist", API._rRelationshipType = API.ObjectRelationship, - API._rColumnMapping = HashMap.fromList [(API.ColumnName "ArtistId", API.ColumnName "ArtistId")] + API._rColumnMapping = API.ColumnPathMapping $ HashMap.fromList [(API.mkColumnSelector $ API.ColumnName "ArtistId", API.mkColumnSelector $ API.ColumnName "ArtistId")] } ) ] diff --git a/server/lib/api-tests/src/Test/DataConnector/MockAgent/OrderBySpec.hs b/server/lib/api-tests/src/Test/DataConnector/MockAgent/OrderBySpec.hs index ba9dd138f8b..b6e148982b0 100644 --- a/server/lib/api-tests/src/Test/DataConnector/MockAgent/OrderBySpec.hs +++ b/server/lib/api-tests/src/Test/DataConnector/MockAgent/OrderBySpec.hs @@ -200,7 +200,7 @@ tests = describe "Order By Tests" $ do API.Relationship { _rTarget = mkTableTarget "Album", _rRelationshipType = API.ArrayRelationship, - _rColumnMapping = HashMap.fromList [(API.ColumnName "ArtistId", API.ColumnName "ArtistId")] + _rColumnMapping = API.ColumnPathMapping $ HashMap.fromList [(API.mkColumnSelector $ API.ColumnName "ArtistId", API.mkColumnSelector $ API.ColumnName "ArtistId")] } ) ] diff --git a/server/lib/api-tests/src/Test/DataConnector/MockAgent/QueryRelationshipsSpec.hs b/server/lib/api-tests/src/Test/DataConnector/MockAgent/QueryRelationshipsSpec.hs index df8de7a59ef..ef7f08ebd0a 100644 --- a/server/lib/api-tests/src/Test/DataConnector/MockAgent/QueryRelationshipsSpec.hs +++ b/server/lib/api-tests/src/Test/DataConnector/MockAgent/QueryRelationshipsSpec.hs @@ -228,7 +228,7 @@ tests = describe "Object Relationships Tests" $ do API.Relationship { _rTarget = mkTableTarget "Genre", _rRelationshipType = API.ObjectRelationship, - _rColumnMapping = HashMap.fromList [(API.ColumnName "GenreId", API.ColumnName "GenreId")] + _rColumnMapping = API.ColumnPathMapping $ HashMap.fromList [(API.mkColumnSelector $ API.ColumnName "GenreId", API.mkColumnSelector $ API.ColumnName "GenreId")] } ), ( API.RelationshipName "MediaType", @@ -236,8 +236,9 @@ tests = describe "Object Relationships Tests" $ do { _rTarget = mkTableTarget "MediaType", _rRelationshipType = API.ObjectRelationship, _rColumnMapping = - HashMap.fromList - [(API.ColumnName "MediaTypeId", API.ColumnName "MediaTypeId")] + API.ColumnPathMapping + $ HashMap.fromList + [(API.mkColumnSelector $ API.ColumnName "MediaTypeId", API.mkColumnSelector $ API.ColumnName "MediaTypeId")] } ) ] @@ -354,7 +355,9 @@ tests = describe "Object Relationships Tests" $ do API.Relationship { _rTarget = mkTableTarget "Album", _rRelationshipType = API.ObjectRelationship, - _rColumnMapping = HashMap.fromList [(API.ColumnName "AlbumId", API.ColumnName "AlbumId")] + _rColumnMapping = + API.ColumnPathMapping + $ HashMap.fromList [(API.mkColumnSelector $ API.ColumnName "AlbumId", API.mkColumnSelector $ API.ColumnName "AlbumId")] } ) ] @@ -368,7 +371,9 @@ tests = describe "Object Relationships Tests" $ do API.Relationship { _rTarget = mkTableTarget "Artist", _rRelationshipType = API.ObjectRelationship, - _rColumnMapping = HashMap.fromList [(API.ColumnName "ArtistId", API.ColumnName "ArtistId")] + _rColumnMapping = + API.ColumnPathMapping + $ HashMap.fromList [(API.mkColumnSelector $ API.ColumnName "ArtistId", API.mkColumnSelector $ API.ColumnName "ArtistId")] } ) ] @@ -449,7 +454,7 @@ tests = describe "Object Relationships Tests" $ do API.Relationship { _rTarget = mkTableTarget "Employee", _rRelationshipType = API.ObjectRelationship, - _rColumnMapping = HashMap.fromList [(API.ColumnName "SupportRepId", API.ColumnName "EmployeeId")] + _rColumnMapping = API.ColumnPathMapping $ HashMap.fromList [(API.mkColumnSelector $ API.ColumnName "SupportRepId", API.mkColumnSelector $ API.ColumnName "EmployeeId")] } ) ] @@ -463,7 +468,7 @@ tests = describe "Object Relationships Tests" $ do API.Relationship { _rTarget = mkTableTarget "Customer", _rRelationshipType = API.ArrayRelationship, - _rColumnMapping = HashMap.fromList [(API.ColumnName "EmployeeId", API.ColumnName "SupportRepId")] + _rColumnMapping = API.ColumnPathMapping $ HashMap.fromList [(API.mkColumnSelector $ API.ColumnName "EmployeeId", API.mkColumnSelector $ API.ColumnName "SupportRepId")] } ) ] diff --git a/server/lib/api-tests/src/Test/DataConnector/MockAgent/UpdateMutationsSpec.hs b/server/lib/api-tests/src/Test/DataConnector/MockAgent/UpdateMutationsSpec.hs index b73a1643365..f93a00ace60 100644 --- a/server/lib/api-tests/src/Test/DataConnector/MockAgent/UpdateMutationsSpec.hs +++ b/server/lib/api-tests/src/Test/DataConnector/MockAgent/UpdateMutationsSpec.hs @@ -203,7 +203,7 @@ tests = do API.Relationship { API._rTarget = mkTableTarget "Genre", API._rRelationshipType = API.ObjectRelationship, - API._rColumnMapping = HashMap.fromList [(API.ColumnName "GenreId", API.ColumnName "GenreId")] + API._rColumnMapping = API.ColumnPathMapping $ HashMap.fromList [(API.mkColumnSelector $ API.ColumnName "GenreId", API.mkColumnSelector $ API.ColumnName "GenreId")] } ) ] @@ -394,7 +394,7 @@ tests = do API.Relationship { API._rTarget = mkTableTarget "Genre", API._rRelationshipType = API.ObjectRelationship, - API._rColumnMapping = HashMap.fromList [(API.ColumnName "GenreId", API.ColumnName "GenreId")] + API._rColumnMapping = API.ColumnPathMapping $ HashMap.fromList [(API.mkColumnSelector $ API.ColumnName "GenreId", API.mkColumnSelector $ API.ColumnName "GenreId")] } ) ] diff --git a/server/lib/dc-api/src/Hasura/Backends/DataConnector/API/V0/Column.hs b/server/lib/dc-api/src/Hasura/Backends/DataConnector/API/V0/Column.hs index 001fae8338c..28c28771616 100644 --- a/server/lib/dc-api/src/Hasura/Backends/DataConnector/API/V0/Column.hs +++ b/server/lib/dc-api/src/Hasura/Backends/DataConnector/API/V0/Column.hs @@ -3,6 +3,8 @@ module Hasura.Backends.DataConnector.API.V0.Column ( ColumnName (..), + ColumnSelector (..), + mkColumnSelector, ColumnType (..), ColumnInfo (..), ciName, @@ -13,19 +15,24 @@ module Hasura.Backends.DataConnector.API.V0.Column ciUpdatable, ciValueGenerated, ColumnValueGenerationStrategy (..), + ColumnPathMapping (..), ) where -------------------------------------------------------------------------------- -import Autodocodec +import Autodocodec.Extended import Autodocodec.OpenAPI () import Control.DeepSeq (NFData) import Control.Lens.TH (makeLenses) import Data.Aeson (FromJSON, FromJSONKey, ToJSON, ToJSONKey) +import Data.Bitraversable (bitraverse) import Data.Data (Data) +import Data.Either.Extra (maybeToEither) +import Data.HashMap.Strict (HashMap) import Data.HashMap.Strict qualified as HashMap import Data.Hashable (Hashable) +import Data.List.NonEmpty (NonEmpty (..)) import Data.OpenApi (ToSchema) import Data.Text (Text) import GHC.Generics (Generic) @@ -46,6 +53,31 @@ instance HasCodec ColumnName where -------------------------------------------------------------------------------- +data ColumnSelector + = ColumnSelectorPath (NonEmpty ColumnName) + | ColumnSelectorColumn (ColumnName) + deriving stock (Eq, Ord, Show, Generic) + deriving (FromJSON, ToJSON, ToSchema) via Autodocodec ColumnSelector + deriving anyclass (Hashable, NFData) + +instance HasCodec ColumnSelector where + codec = disjointMatchChoiceCodec pathCodec columnCodec chooser + where + pathCodec = dimapCodec ColumnSelectorPath id (codec @(NonEmpty ColumnName)) + columnCodec = dimapCodec ColumnSelectorColumn id (codec @ColumnName) + chooser = \case + ColumnSelectorPath p -> Left p + ColumnSelectorColumn c -> Right c + +instance FromJSONKey ColumnSelector + +instance ToJSONKey ColumnSelector + +mkColumnSelector :: ColumnName -> ColumnSelector +mkColumnSelector = ColumnSelectorColumn + +-------------------------------------------------------------------------------- + data ColumnType = ColumnTypeScalar API.V0.Scalar.ScalarType | ColumnTypeObject G.Name @@ -135,3 +167,33 @@ instance HasCodec ColumnValueGenerationStrategy where ] $(makeLenses ''ColumnInfo) + +newtype ColumnPathMapping = ColumnPathMapping {unColumnPathMapping :: HashMap ColumnSelector ColumnSelector} + deriving stock (Eq, Ord, Show, Generic) + deriving newtype (NFData, Hashable) + deriving (FromJSON, ToJSON, ToSchema) via Autodocodec ColumnPathMapping + +-- If all keys in the ColumnPathMapping are ColumnSelectors with a single column then we +-- want to represent the mapping as a JSON object with the source columns as properties +-- (using `codec @(HashMap ColumnName ColumnSelector)). +-- This is required for backwards compatibility of the DC API. +-- If some of the keys are not singletons then we can't use a JSON object so we use +-- an array of pairs instead, as provided by `codec @(HashMap ColumnSelector ColumnSelector)`. +instance HasCodec ColumnPathMapping where + codec = + matchChoiceCodec + (dimapCodec ColumnPathMapping unColumnPathMapping $ codec @(HashMap ColumnSelector ColumnSelector)) + (dimapCodec columnMappingToColumnPathMapping id $ codec @(HashMap ColumnName ColumnSelector)) + \m -> maybeToEither m (tryColumnPathMappingToColumnMapping m) + where + columnMappingToColumnPathMapping :: HashMap ColumnName ColumnSelector -> ColumnPathMapping + columnMappingToColumnPathMapping = ColumnPathMapping . HashMap.mapKeys mkColumnSelector + + tryColumnPathMappingToColumnMapping :: ColumnPathMapping -> Maybe (HashMap ColumnName ColumnSelector) + tryColumnPathMappingToColumnMapping = + fmap HashMap.fromList . traverse (bitraverse tryColumnSelectorToColumnName pure) . HashMap.toList . unColumnPathMapping + + tryColumnSelectorToColumnName :: ColumnSelector -> Maybe ColumnName + tryColumnSelectorToColumnName = \case + ColumnSelectorColumn columnName -> Just columnName + _ -> Nothing diff --git a/server/lib/dc-api/src/Hasura/Backends/DataConnector/API/V0/Expression.hs b/server/lib/dc-api/src/Hasura/Backends/DataConnector/API/V0/Expression.hs index 4b4103ffbe2..8216301dc88 100644 --- a/server/lib/dc-api/src/Hasura/Backends/DataConnector/API/V0/Expression.hs +++ b/server/lib/dc-api/src/Hasura/Backends/DataConnector/API/V0/Expression.hs @@ -14,8 +14,6 @@ module Hasura.Backends.DataConnector.API.V0.Expression ccColumnType, ccRedactionExpression, ColumnPath (..), - ColumnSelector (..), - mkColumnSelector, ComparisonValue (..), TargetRedactionExpressions (..), RedactionExpressionName (..), @@ -32,8 +30,6 @@ 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 (..)) -import Data.List.NonEmpty qualified as NonEmpty import Data.OpenApi (ToSchema) import Data.Set (Set) import Data.Text (Text) @@ -258,7 +254,7 @@ data ComparisonColumn = ComparisonColumn { -- | The path to the table that contains the specified column. _ccPath :: ColumnPath, -- | The name of the column - _ccName :: ColumnSelector, + _ccName :: API.V0.ColumnSelector, -- | The scalar type of the column _ccColumnType :: API.V0.ScalarType, -- | If present, the name of the redaction expression to evaluate. @@ -309,20 +305,6 @@ instance HasCodec ColumnPath where CurrentTable -> [] QueryTable -> ["$"] -newtype ColumnSelector = ColumnSelector {unColumnSelector :: NonEmpty API.V0.ColumnName} - deriving stock (Eq, Ord, Show, Generic) - deriving (FromJSON, ToJSON, ToSchema) via Autodocodec ColumnSelector - deriving anyclass (Hashable, NFData) - -instance HasCodec ColumnSelector where - codec = bimapCodec dec enc oneOrManyCodec - where - dec = maybe (Left "Unexpected empty list in ColumnSelector") (Right . ColumnSelector) . NonEmpty.nonEmpty - enc = NonEmpty.toList . unColumnSelector - -mkColumnSelector :: API.V0.ColumnName -> ColumnSelector -mkColumnSelector = ColumnSelector . NonEmpty.singleton - -- | A serializable representation of comparison values used in comparisons inside 'Expression's. data ComparisonValue = -- | Allows a comparison to a column on the current table or another table diff --git a/server/lib/dc-api/src/Hasura/Backends/DataConnector/API/V0/OrderBy.hs b/server/lib/dc-api/src/Hasura/Backends/DataConnector/API/V0/OrderBy.hs index 616f0e7171b..84a58eaab01 100644 --- a/server/lib/dc-api/src/Hasura/Backends/DataConnector/API/V0/OrderBy.hs +++ b/server/lib/dc-api/src/Hasura/Backends/DataConnector/API/V0/OrderBy.hs @@ -30,6 +30,7 @@ import Data.List.NonEmpty (NonEmpty) import Data.OpenApi (ToSchema) import GHC.Generics (Generic) import Hasura.Backends.DataConnector.API.V0.Aggregate qualified as API.V0 +import Hasura.Backends.DataConnector.API.V0.Column qualified as API.V0 import Hasura.Backends.DataConnector.API.V0.Expression qualified as API.V0 import Hasura.Backends.DataConnector.API.V0.Relationships qualified as API.V0 import Prelude diff --git a/server/lib/dc-api/src/Hasura/Backends/DataConnector/API/V0/Query.hs b/server/lib/dc-api/src/Hasura/Backends/DataConnector/API/V0/Query.hs index 91dc5c06b0b..8372627f551 100644 --- a/server/lib/dc-api/src/Hasura/Backends/DataConnector/API/V0/Query.hs +++ b/server/lib/dc-api/src/Hasura/Backends/DataConnector/API/V0/Query.hs @@ -33,6 +33,7 @@ module Hasura.Backends.DataConnector.API.V0.Query qrAggregates, qrInterpolatedQueries, FieldValue, + unFieldValue, mkColumnFieldValue, mkRelationshipFieldValue, mkNestedObjFieldValue, @@ -299,7 +300,7 @@ instance HasStatus QueryResponse where -- field values (mainly for testing purposes), we must compare them using 'QueryResponse', -- since raw JSON comparisons will show up immaterial differences between null properties -- and missing properties (which we consider to be the same thing here). -newtype FieldValue = FieldValue J.Value +newtype FieldValue = FieldValue {unFieldValue :: J.Value} deriving (ToJSON, FromJSON, ToSchema) via Autodocodec FieldValue instance Eq FieldValue where diff --git a/server/lib/dc-api/src/Hasura/Backends/DataConnector/API/V0/Relationships.hs b/server/lib/dc-api/src/Hasura/Backends/DataConnector/API/V0/Relationships.hs index 22321a7b3a0..7c59c9a91d9 100644 --- a/server/lib/dc-api/src/Hasura/Backends/DataConnector/API/V0/Relationships.hs +++ b/server/lib/dc-api/src/Hasura/Backends/DataConnector/API/V0/Relationships.hs @@ -30,6 +30,7 @@ import Control.DeepSeq (NFData) import Control.Lens (makeLenses, makePrisms) import Data.Aeson (FromJSON, FromJSONKey, ToJSON, ToJSONKey) import Data.Data (Data) +import Data.HashMap.Strict (HashMap) import Data.HashMap.Strict qualified as HashMap import Data.Hashable (Hashable) import Data.OpenApi (ToSchema) @@ -43,13 +44,13 @@ import Hasura.Backends.DataConnector.API.V0.Target qualified as API.V0 import Prelude data Relationships = RTable TableRelationships | RFunction FunctionRelationships | RInterpolated InterpolatedRelationships - deriving stock (Eq, Ord, Show, Generic, Data) + deriving stock (Eq, Ord, Show, Generic) deriving (FromJSON, ToJSON, ToSchema) via Autodocodec Relationships -pattern RTableRelationships :: API.V0.TableName -> HashMap.HashMap RelationshipName Relationship -> Relationships +pattern RTableRelationships :: API.V0.TableName -> HashMap RelationshipName Relationship -> Relationships pattern RTableRelationships source rels = RTable (TableRelationships source rels) -pattern RFunctionRelationships :: API.V0.FunctionName -> HashMap.HashMap RelationshipName Relationship -> Relationships +pattern RFunctionRelationships :: API.V0.FunctionName -> HashMap RelationshipName Relationship -> Relationships pattern RFunctionRelationships source rels = RFunction (FunctionRelationships source rels) instance HasCodec Relationships where @@ -71,9 +72,9 @@ instance HasCodec Relationships where data InterpolatedRelationships = InterpolatedRelationships { _irSource :: API.V0.InterpolatedQueryId, - _irRelationships :: HashMap.HashMap RelationshipName Relationship + _irRelationships :: HashMap RelationshipName Relationship } - deriving stock (Eq, Ord, Show, Generic, Data) + deriving stock (Eq, Ord, Show, Generic) instance HasObjectCodec InterpolatedRelationships where objectCodec = @@ -84,9 +85,9 @@ instance HasObjectCodec InterpolatedRelationships where -- NOTE: Prefix is `trel` due to TableRequest conflicting with `tr` prefix. data TableRelationships = TableRelationships { _trelSourceTable :: API.V0.TableName, - _trelRelationships :: HashMap.HashMap RelationshipName Relationship + _trelRelationships :: HashMap RelationshipName Relationship } - deriving stock (Eq, Ord, Show, Generic, Data) + deriving stock (Eq, Ord, Show, Generic) instance HasObjectCodec TableRelationships where objectCodec = @@ -96,9 +97,9 @@ instance HasObjectCodec TableRelationships where data FunctionRelationships = FunctionRelationships { _frelSourceFunction :: API.V0.FunctionName, - _frelRelationships :: HashMap.HashMap RelationshipName Relationship + _frelRelationships :: HashMap RelationshipName Relationship } - deriving stock (Eq, Ord, Show, Generic, Data) + deriving stock (Eq, Ord, Show, Generic) instance HasObjectCodec FunctionRelationships where objectCodec = @@ -110,9 +111,9 @@ instance HasObjectCodec FunctionRelationships where data Relationship = Relationship { _rTarget :: API.V0.Target, _rRelationshipType :: RelationshipType, - _rColumnMapping :: HashMap.HashMap SourceColumnName TargetColumnName + _rColumnMapping :: API.V0.ColumnPathMapping } - deriving stock (Eq, Ord, Show, Generic, Data) + deriving stock (Eq, Ord, Show, Generic) deriving (FromJSON, ToJSON, ToSchema) via Autodocodec Relationship instance HasCodec Relationship where @@ -140,9 +141,9 @@ instance HasCodec RelationshipType where named "RelationshipType" $ stringConstCodec [(ObjectRelationship, "object"), (ArrayRelationship, "array")] -type SourceColumnName = API.V0.ColumnName +type SourceColumnName = API.V0.ColumnSelector -type TargetColumnName = API.V0.ColumnName +type TargetColumnName = API.V0.ColumnSelector $(makeLenses 'TableRelationships) $(makeLenses 'Relationship) diff --git a/server/lib/dc-api/src/Hasura/Backends/DataConnector/API/V0/Table.hs b/server/lib/dc-api/src/Hasura/Backends/DataConnector/API/V0/Table.hs index 2308fd164b7..efa12b727c7 100644 --- a/server/lib/dc-api/src/Hasura/Backends/DataConnector/API/V0/Table.hs +++ b/server/lib/dc-api/src/Hasura/Backends/DataConnector/API/V0/Table.hs @@ -122,7 +122,7 @@ instance HasCodec TableType where -------------------------------------------------------------------------------- newtype ForeignKeys = ForeignKeys {_unForeignKeys :: HashMap ConstraintName Constraint} - deriving stock (Eq, Ord, Show, Generic, Data) + deriving stock (Eq, Ord, Show, Generic) deriving anyclass (NFData, Hashable) deriving (FromJSON, ToJSON) via Autodocodec ForeignKeys @@ -138,9 +138,9 @@ newtype ConstraintName = ConstraintName {unConstraintName :: Text} data Constraint = Constraint { _cForeignTable :: TableName, - _cColumnMapping :: HashMap API.V0.ColumnName API.V0.ColumnName + _cColumnMapping :: API.V0.ColumnPathMapping } - deriving stock (Eq, Ord, Show, Generic, Data) + deriving stock (Eq, Ord, Show, Generic) deriving anyclass (NFData, Hashable) deriving (FromJSON, ToJSON) via Autodocodec Constraint diff --git a/server/lib/dc-api/test/Test/Data.hs b/server/lib/dc-api/test/Test/Data.hs index 556d20d21d8..8db2477ab6f 100644 --- a/server/lib/dc-api/test/Test/Data.hs +++ b/server/lib/dc-api/test/Test/Data.hs @@ -155,7 +155,7 @@ albumsRelationshipName = API.RelationshipName "Albums" artistsTableRelationships :: API.TableRelationships artistsTableRelationships = - let joinFieldMapping = HashMap.fromList [(API.ColumnName "ArtistId", API.ColumnName "ArtistId")] + let joinFieldMapping = API.ColumnPathMapping $ HashMap.fromList [(API.mkColumnSelector $ API.ColumnName "ArtistId", API.mkColumnSelector $ API.ColumnName "ArtistId")] in API.TableRelationships artistsTableName ( HashMap.fromList @@ -175,8 +175,8 @@ albumsRowsById = albumsTableRelationships :: API.TableRelationships albumsTableRelationships = - let artistsJoinFieldMapping = HashMap.fromList [(API.ColumnName "ArtistId", API.ColumnName "ArtistId")] - tracksJoinFieldMapping = HashMap.fromList [(API.ColumnName "AlbumId", API.ColumnName "AlbumId")] + let artistsJoinFieldMapping = API.ColumnPathMapping $ HashMap.fromList [(API.mkColumnSelector $ API.ColumnName "ArtistId", API.mkColumnSelector $ API.ColumnName "ArtistId")] + tracksJoinFieldMapping = API.ColumnPathMapping $ HashMap.fromList [(API.mkColumnSelector $ API.ColumnName "AlbumId", API.mkColumnSelector $ API.ColumnName "AlbumId")] in API.TableRelationships albumsTableName ( HashMap.fromList @@ -203,8 +203,8 @@ customersRowsById = customersTableRelationships :: API.TableRelationships customersTableRelationships = - let supportRepJoinFieldMapping = HashMap.fromList [(API.ColumnName "SupportRepId", API.ColumnName "EmployeeId")] - invoicesJoinFieldMapping = HashMap.fromList [(API.ColumnName "CustomerId", API.ColumnName "CustomerId")] + let supportRepJoinFieldMapping = API.ColumnPathMapping $ HashMap.fromList [(API.mkColumnSelector $ API.ColumnName "SupportRepId", API.mkColumnSelector $ API.ColumnName "EmployeeId")] + invoicesJoinFieldMapping = API.ColumnPathMapping $ HashMap.fromList [(API.mkColumnSelector $ API.ColumnName "CustomerId", API.mkColumnSelector $ API.ColumnName "CustomerId")] in API.TableRelationships customersTableName ( HashMap.fromList @@ -231,8 +231,8 @@ employeesRowsById = employeesTableRelationships :: API.TableRelationships employeesTableRelationships = - let supportRepJoinFieldMapping = HashMap.fromList [(API.ColumnName "EmployeeId", API.ColumnName "SupportRepId")] - reportsToEmployeeJoinFieldMapping = HashMap.fromList [(API.ColumnName "ReportsTo", API.ColumnName "EmployeeId")] + let supportRepJoinFieldMapping = API.ColumnPathMapping $ HashMap.fromList [(API.mkColumnSelector $ API.ColumnName "EmployeeId", API.mkColumnSelector $ API.ColumnName "SupportRepId")] + reportsToEmployeeJoinFieldMapping = API.ColumnPathMapping $ HashMap.fromList [(API.mkColumnSelector $ API.ColumnName "ReportsTo", API.mkColumnSelector $ API.ColumnName "EmployeeId")] in API.TableRelationships employeesTableName ( HashMap.fromList @@ -259,8 +259,8 @@ invoicesRowsById = invoicesTableRelationships :: API.TableRelationships invoicesTableRelationships = - let invoiceLinesJoinFieldMapping = HashMap.fromList [(API.ColumnName "InvoiceId", API.ColumnName "InvoiceId")] - customersJoinFieldMapping = HashMap.fromList [(API.ColumnName "CustomerId", API.ColumnName "CustomerId")] + let invoiceLinesJoinFieldMapping = API.ColumnPathMapping $ HashMap.fromList [(API.mkColumnSelector $ API.ColumnName "InvoiceId", API.mkColumnSelector $ API.ColumnName "InvoiceId")] + customersJoinFieldMapping = API.ColumnPathMapping $ HashMap.fromList [(API.mkColumnSelector $ API.ColumnName "CustomerId", API.mkColumnSelector $ API.ColumnName "CustomerId")] in API.TableRelationships invoicesTableName ( HashMap.fromList @@ -280,8 +280,8 @@ invoiceLinesRows = sortBy (API.FieldName "InvoiceLineId") $ readTableFromXmlInto invoiceLinesTableRelationships :: API.TableRelationships invoiceLinesTableRelationships = - let invoiceJoinFieldMapping = HashMap.fromList [(API.ColumnName "InvoiceId", API.ColumnName "InvoiceId")] - tracksJoinFieldMapping = HashMap.fromList [(API.ColumnName "TrackId", API.ColumnName "TrackId")] + let invoiceJoinFieldMapping = API.ColumnPathMapping $ HashMap.fromList [(API.mkColumnSelector $ API.ColumnName "InvoiceId", API.mkColumnSelector $ API.ColumnName "InvoiceId")] + tracksJoinFieldMapping = API.ColumnPathMapping $ HashMap.fromList [(API.mkColumnSelector $ API.ColumnName "TrackId", API.mkColumnSelector $ API.ColumnName "TrackId")] in API.TableRelationships invoiceLinesTableName ( HashMap.fromList @@ -314,11 +314,11 @@ tracksRowsById = tracksTableRelationships :: API.TableRelationships tracksTableRelationships = - let invoiceLinesJoinFieldMapping = HashMap.fromList [(API.ColumnName "TrackId", API.ColumnName "TrackId")] - mediaTypeJoinFieldMapping = HashMap.fromList [(API.ColumnName "MediaTypeId", API.ColumnName "MediaTypeId")] - albumJoinFieldMapping = HashMap.fromList [(API.ColumnName "AlbumId", API.ColumnName "AlbumId")] - genreJoinFieldMapping = HashMap.fromList [(API.ColumnName "GenreId", API.ColumnName "GenreId")] - playlistTracksJoinFieldMapping = HashMap.fromList [(API.ColumnName "TrackId", API.ColumnName "TrackId")] + let invoiceLinesJoinFieldMapping = API.ColumnPathMapping $ HashMap.fromList [(API.mkColumnSelector $ API.ColumnName "TrackId", API.mkColumnSelector $ API.ColumnName "TrackId")] + mediaTypeJoinFieldMapping = API.ColumnPathMapping $ HashMap.fromList [(API.mkColumnSelector $ API.ColumnName "MediaTypeId", API.mkColumnSelector $ API.ColumnName "MediaTypeId")] + albumJoinFieldMapping = API.ColumnPathMapping $ HashMap.fromList [(API.mkColumnSelector $ API.ColumnName "AlbumId", API.mkColumnSelector $ API.ColumnName "AlbumId")] + genreJoinFieldMapping = API.ColumnPathMapping $ HashMap.fromList [(API.mkColumnSelector $ API.ColumnName "GenreId", API.mkColumnSelector $ API.ColumnName "GenreId")] + playlistTracksJoinFieldMapping = API.ColumnPathMapping $ HashMap.fromList [(API.mkColumnSelector $ API.ColumnName "TrackId", API.mkColumnSelector $ API.ColumnName "TrackId")] in API.TableRelationships tracksTableName ( HashMap.fromList @@ -359,7 +359,7 @@ mkFibonacciRows n = take n $ fibonacciRow <$> fibs genresTableRelationships :: API.TableRelationships genresTableRelationships = - let joinFieldMapping = HashMap.fromList [(API.ColumnName "GenreId", API.ColumnName "GenreId")] + let joinFieldMapping = API.ColumnPathMapping $ HashMap.fromList [(API.mkColumnSelector $ API.ColumnName "GenreId", API.mkColumnSelector $ API.ColumnName "GenreId")] in API.TableRelationships genresTableName ( HashMap.fromList @@ -538,7 +538,13 @@ mkTestData schemaResponse testConfig = formatTableRelationships :: API.TableRelationships -> API.TableRelationships formatTableRelationships = prefixTableRelationships - >>> API.trelRelationships . traverse . API.rColumnMapping %~ (HashMap.toList >>> fmap (bimap (formatColumnName testConfig) (formatColumnName testConfig)) >>> HashMap.fromList) + >>> API.trelRelationships . traverse . API.rColumnMapping + %~ ( API.unColumnPathMapping + >>> HashMap.toList + >>> fmap (bimap (formatColumnSelector testConfig) (formatColumnSelector testConfig)) + >>> HashMap.fromList + >>> API.ColumnPathMapping + ) prefixTableRelationships :: API.TableRelationships -> API.TableRelationships prefixTableRelationships = @@ -623,7 +629,7 @@ formatTableInfo testConfig = >>> API.tiPrimaryKey . _Just . traverse %~ formatColumnName testConfig >>> API.tiForeignKeys . API.unForeignKeys . traverse %~ ( API.cForeignTable %~ formatTableName testConfig - >>> API.cColumnMapping %~ (HashMap.toList >>> fmap (bimap (formatColumnName testConfig) (formatColumnName testConfig)) >>> HashMap.fromList) + >>> API.cColumnMapping %~ (API.unColumnPathMapping >>> HashMap.toList >>> fmap (bimap (formatColumnSelector testConfig) (formatColumnSelector testConfig)) >>> HashMap.fromList >>> API.ColumnPathMapping) ) applyTableNamePrefix :: [Text] -> API.TableName -> API.TableName @@ -647,6 +653,11 @@ applyNameCasing casing text = case casing of formatColumnName :: TestConfig -> API.ColumnName -> API.ColumnName formatColumnName TestConfig {..} = API.ColumnName . applyNameCasing _tcColumnNameCasing . API.unColumnName +formatColumnSelector :: TestConfig -> API.ColumnSelector -> API.ColumnSelector +formatColumnSelector testConfig = \case + API.ColumnSelectorPath p -> API.ColumnSelectorPath $ formatColumnName testConfig <$> p + API.ColumnSelectorColumn c -> API.ColumnSelectorColumn $ formatColumnName testConfig c + columnField :: API.SchemaResponse -> TestConfig -> API.TableName -> Text -> API.Field columnField schemaResponse testConfig tableName columnName = API.ColumnField columnName' scalarType Nothing diff --git a/server/lib/dc-api/test/Test/Specs/FunctionsSpec.hs b/server/lib/dc-api/test/Test/Specs/FunctionsSpec.hs index 6a270e110f4..c367ed367e4 100644 --- a/server/lib/dc-api/test/Test/Specs/FunctionsSpec.hs +++ b/server/lib/dc-api/test/Test/Specs/FunctionsSpec.hs @@ -116,7 +116,7 @@ spec testConfig API.Capabilities {} = describe "supports functions" $ preloadAge ] query' = Data.emptyQuery & qFields ?~ fields authorRelationship = - let authorsJoinFieldMapping = HashMap.fromList [(API.ColumnName "author_id", API.ColumnName "id")] + let authorsJoinFieldMapping = API.ColumnPathMapping $ HashMap.fromList [(API.mkColumnSelector $ API.ColumnName "author_id", API.mkColumnSelector $ API.ColumnName "id")] in API.FunctionRelationships _ftdSearchArticlesFunctionName ( HashMap.fromList @@ -164,7 +164,7 @@ spec testConfig API.Capabilities {} = describe "supports functions" $ preloadAge (API.ScalarValueComparison (API.ScalarValue (Number 10) (API.ScalarType "number"))) query' = Data.emptyQuery & qFields ?~ fields & qWhere ?~ whereClause & qLimit ?~ 2 authorRelationship = - let authorsJoinFieldMapping = HashMap.fromList [(API.ColumnName "author_id", API.ColumnName "id")] + let authorsJoinFieldMapping = API.ColumnPathMapping $ HashMap.fromList [(API.mkColumnSelector $ API.ColumnName "author_id", API.mkColumnSelector $ API.ColumnName "id")] in API.FunctionRelationships _ftdSearchArticlesFunctionName ( HashMap.fromList diff --git a/server/lib/test-harness/src/Harness/Backend/DataConnector/Mock/Server.hs b/server/lib/test-harness/src/Harness/Backend/DataConnector/Mock/Server.hs index e4567b3dbdf..eea2f7fd34c 100644 --- a/server/lib/test-harness/src/Harness/Backend/DataConnector/Mock/Server.hs +++ b/server/lib/test-harness/src/Harness/Backend/DataConnector/Mock/Server.hs @@ -220,7 +220,12 @@ schema = API._tiDescription = Just "Collection of music albums created by artists", API._tiForeignKeys = API.ForeignKeys - $ HashMap.singleton (API.ConstraintName "Artist") (API.Constraint (mkTableName "Artist") (HashMap.singleton (API.ColumnName "ArtistId") (API.ColumnName "ArtistId"))), + $ HashMap.singleton + (API.ConstraintName "Artist") + ( API.Constraint + (mkTableName "Artist") + (API.ColumnPathMapping $ HashMap.singleton (API.mkColumnSelector $ API.ColumnName "ArtistId") (API.mkColumnSelector $ API.ColumnName "ArtistId")) + ), API._tiInsertable = True, API._tiUpdatable = True, API._tiDeletable = True @@ -351,7 +356,12 @@ schema = API._tiDescription = Just "Collection of customers who can buy tracks", API._tiForeignKeys = API.ForeignKeys - $ HashMap.singleton (API.ConstraintName "CustomerSupportRep") (API.Constraint (mkTableName "Employee") (HashMap.singleton (API.ColumnName "SupportRepId") (API.ColumnName "EmployeeId"))), + $ HashMap.singleton + (API.ConstraintName "CustomerSupportRep") + ( API.Constraint + (mkTableName "Employee") + (API.ColumnPathMapping $ HashMap.singleton (API.mkColumnSelector $ API.ColumnName "SupportRepId") (API.mkColumnSelector $ API.ColumnName "EmployeeId")) + ), API._tiInsertable = True, API._tiUpdatable = True, API._tiDeletable = True @@ -500,7 +510,12 @@ schema = API._tiDescription = Just "Collection of employees who work for the business", API._tiForeignKeys = API.ForeignKeys - $ HashMap.singleton (API.ConstraintName "EmployeeReportsTo") (API.Constraint (mkTableName "Employee") (HashMap.singleton (API.ColumnName "ReportsTo") (API.ColumnName "EmployeeId"))), + $ HashMap.singleton + (API.ConstraintName "EmployeeReportsTo") + ( API.Constraint + (mkTableName "Employee") + (API.ColumnPathMapping $ HashMap.singleton (API.mkColumnSelector $ API.ColumnName "ReportsTo") (API.mkColumnSelector $ API.ColumnName "EmployeeId")) + ), API._tiInsertable = True, API._tiUpdatable = True, API._tiDeletable = True @@ -626,7 +641,9 @@ schema = API._tiForeignKeys = API.ForeignKeys $ HashMap.singleton (API.ConstraintName "InvoiceCustomer") - $ API.Constraint (mkTableName "Customer") (HashMap.singleton (API.ColumnName "CustomerId") (API.ColumnName "CustomerId")), + $ API.Constraint + (mkTableName "Customer") + (API.ColumnPathMapping $ HashMap.singleton (API.mkColumnSelector $ API.ColumnName "CustomerId") (API.mkColumnSelector $ API.ColumnName "CustomerId")), API._tiInsertable = True, API._tiUpdatable = True, API._tiDeletable = True @@ -686,8 +703,16 @@ schema = API._tiForeignKeys = API.ForeignKeys $ HashMap.fromList - [ (API.ConstraintName "Invoice", API.Constraint (mkTableName "Invoice") (HashMap.singleton (API.ColumnName "InvoiceId") (API.ColumnName "InvoiceId"))), - (API.ConstraintName "Track", API.Constraint (mkTableName "Track") (HashMap.singleton (API.ColumnName "TrackId") (API.ColumnName "TrackId"))) + [ ( API.ConstraintName "Invoice", + API.Constraint + (mkTableName "Invoice") + (API.ColumnPathMapping $ HashMap.singleton (API.mkColumnSelector $ API.ColumnName "InvoiceId") (API.mkColumnSelector $ API.ColumnName "InvoiceId")) + ), + ( API.ConstraintName "Track", + API.Constraint + (mkTableName "Track") + (API.ColumnPathMapping $ HashMap.singleton (API.mkColumnSelector $ API.ColumnName "TrackId") (API.mkColumnSelector $ API.ColumnName "TrackId")) + ) ], API._tiInsertable = True, API._tiUpdatable = True, @@ -814,9 +839,21 @@ schema = API._tiForeignKeys = API.ForeignKeys $ HashMap.fromList - [ (API.ConstraintName "Album", API.Constraint (mkTableName "Album") (HashMap.singleton (API.ColumnName "AlbumId") (API.ColumnName "AlbumId"))), - (API.ConstraintName "Genre", API.Constraint (mkTableName "Genre") (HashMap.singleton (API.ColumnName "GenreId") (API.ColumnName "GenreId"))), - (API.ConstraintName "MediaType", API.Constraint (mkTableName "MediaType") (HashMap.singleton (API.ColumnName "MediaTypeId") (API.ColumnName "MediaTypeId"))) + [ ( API.ConstraintName "Album", + API.Constraint + (mkTableName "Album") + (API.ColumnPathMapping $ HashMap.singleton (API.mkColumnSelector $ API.ColumnName "AlbumId") (API.mkColumnSelector $ API.ColumnName "AlbumId")) + ), + ( API.ConstraintName "Genre", + API.Constraint + (mkTableName "Genre") + (API.ColumnPathMapping $ HashMap.singleton (API.mkColumnSelector $ API.ColumnName "GenreId") (API.mkColumnSelector $ API.ColumnName "GenreId")) + ), + ( API.ConstraintName "MediaType", + API.Constraint + (mkTableName "MediaType") + (API.ColumnPathMapping $ HashMap.singleton (API.mkColumnSelector $ API.ColumnName "MediaTypeId") (API.mkColumnSelector $ API.ColumnName "MediaTypeId")) + ) ], API._tiInsertable = True, API._tiUpdatable = True, diff --git a/server/src-lib/Hasura/Backends/BigQuery/FromIr.hs b/server/src-lib/Hasura/Backends/BigQuery/FromIr.hs index 1b1117e5364..d38b8b22769 100644 --- a/server/src-lib/Hasura/Backends/BigQuery/FromIr.hs +++ b/server/src-lib/Hasura/Backends/BigQuery/FromIr.hs @@ -632,7 +632,7 @@ unfurlAnnotatedOrderByElement = pure (fieldName, expression) Ir.AOCObjectRelation Rql.RelInfo {riTarget = Rql.RelTargetNativeQuery _} _annBoolExp _annOrderByElementG -> error "unfurlAnnotatedOrderByElement RelTargetNativeQuery" - Ir.AOCObjectRelation Rql.RelInfo {riMapping = mapping, riTarget = Rql.RelTargetTable tableName} annBoolExp annOrderByElementG -> do + Ir.AOCObjectRelation Rql.RelInfo {riMapping = Rql.RelMapping mapping, riTarget = Rql.RelTargetTable tableName} annBoolExp annOrderByElementG -> do selectFrom <- lift (lift (fromQualifiedTable tableName)) joinAliasEntity <- lift (lift (generateEntityAlias (ForOrderAlias (tableNameText tableName)))) @@ -674,7 +674,7 @@ unfurlAnnotatedOrderByElement = local (const joinAliasEntity) (unfurlAnnotatedOrderByElement annOrderByElementG) Ir.AOCArrayAggregation Rql.RelInfo {riTarget = Rql.RelTargetNativeQuery _} _annBoolExp _annAggregateOrderBy -> error "unfurlAnnotatedOrderByElement RelTargetNativeQuery" - Ir.AOCArrayAggregation Rql.RelInfo {riMapping = mapping, riTarget = Rql.RelTargetTable tableName} annBoolExp annAggregateOrderBy -> do + Ir.AOCArrayAggregation Rql.RelInfo {riMapping = Rql.RelMapping mapping, riTarget = Rql.RelTargetTable tableName} annBoolExp annAggregateOrderBy -> do selectFrom <- lift (lift (fromQualifiedTable tableName)) let alias = aggFieldName joinAlias <- @@ -835,7 +835,7 @@ fromAnnBoolExpFld = error "fromAnnBoolExpFld RelTargetNativeQuery" Ir.AVRemoteRelationship _ -> error "fromAnnBoolExpFld RemoteRelationship" - Ir.AVRelationship Rql.RelInfo {riMapping = mapping, riTarget = Rql.RelTargetTable table} (Ir.RelationshipFilters tablePerms annBoolExp) -> do + Ir.AVRelationship Rql.RelInfo {riMapping = Rql.RelMapping mapping, riTarget = Rql.RelTargetTable table} (Ir.RelationshipFilters tablePerms annBoolExp) -> do selectFrom <- lift (fromQualifiedTable table) foreignKeyConditions <- fromMapping selectFrom mapping whereExpression <- diff --git a/server/src-lib/Hasura/Backends/BigQuery/Instances/Types.hs b/server/src-lib/Hasura/Backends/BigQuery/Instances/Types.hs index 445883f5e52..02218fbf9e8 100644 --- a/server/src-lib/Hasura/Backends/BigQuery/Instances/Types.hs +++ b/server/src-lib/Hasura/Backends/BigQuery/Instances/Types.hs @@ -28,6 +28,7 @@ instance Backend 'BigQuery where type NullsOrderType 'BigQuery = BigQuery.NullsOrder type CountType 'BigQuery = BigQuery.CountType type Column 'BigQuery = BigQuery.ColumnName + type ColumnPath 'BigQuery = BigQuery.ColumnName type ScalarValue 'BigQuery = BigQuery.Value type ScalarType 'BigQuery = BigQuery.ScalarType type SQLExpression 'BigQuery = BigQuery.Expression @@ -117,6 +118,10 @@ instance Backend 'BigQuery where getColVals _ _ _ _ _ _ = throw500 "getColVals: not implemented for the BigQuery backend" + getColumnPathColumn = id + + tryColumnPathToColumn = Just + instance HasSourceConfiguration 'BigQuery where type SourceConfig 'BigQuery = BigQuery.BigQuerySourceConfig type SourceConnConfiguration 'BigQuery = BigQuery.BigQueryConnSourceConfig diff --git a/server/src-lib/Hasura/Backends/DataConnector/Adapter/Backend.hs b/server/src-lib/Hasura/Backends/DataConnector/Adapter/Backend.hs index 9bfb0f2e44a..e6bcc0f8c7b 100644 --- a/server/src-lib/Hasura/Backends/DataConnector/Adapter/Backend.hs +++ b/server/src-lib/Hasura/Backends/DataConnector/Adapter/Backend.hs @@ -56,6 +56,7 @@ instance Backend 'DataConnector where type NullsOrderType 'DataConnector = Unimplemented type CountType 'DataConnector = DC.CountAggregate type Column 'DataConnector = DC.ColumnName + type ColumnPath 'DataConnector = DC.ColumnPath type ScalarValue 'DataConnector = J.Value type ScalarType 'DataConnector = DC.ScalarType @@ -167,6 +168,15 @@ instance Backend 'DataConnector where getColVals _ _ _ _ _ _ = throw500 "getColVals: not implemented for the Data Connector backend" + getColumnPathColumn = \case + DC.CPPath p -> NonEmpty.head p + DC.CPColumn c -> c + + tryColumnPathToColumn = \case + DC.CPPath (column :| []) -> Just column + DC.CPColumn column -> Just column + _ -> Nothing + backendSupportsNestedObjects = pure () sourceSupportsSchemalessTables = diff --git a/server/src-lib/Hasura/Backends/DataConnector/Adapter/Metadata.hs b/server/src-lib/Hasura/Backends/DataConnector/Adapter/Metadata.hs index 2b11a35979c..5f526e0eed9 100644 --- a/server/src-lib/Hasura/Backends/DataConnector/Adapter/Metadata.hs +++ b/server/src-lib/Hasura/Backends/DataConnector/Adapter/Metadata.hs @@ -8,7 +8,6 @@ import Control.Monad.Trans.Control import Data.Aeson qualified as J import Data.Aeson.Key qualified as K import Data.Aeson.KeyMap qualified as KM -import Data.Bifunctor (bimap) import Data.Environment (Environment) import Data.Has (Has (getter)) import Data.HashMap.Strict qualified as HashMap @@ -71,7 +70,7 @@ import Hasura.RQL.Types.Metadata (SourceMetadata (..)) import Hasura.RQL.Types.Metadata.Backend (BackendMetadata (..)) import Hasura.RQL.Types.Metadata.Object import Hasura.RQL.Types.NamingCase (NamingCase) -import Hasura.RQL.Types.Relationships.Local (ArrRelDef, ObjRelDef, RelInfo ()) +import Hasura.RQL.Types.Relationships.Local (ArrRelDef, ObjRelDef, RelInfo (), RelMapping (..)) import Hasura.RQL.Types.SchemaCache (CacheRM, askSourceConfig, askSourceInfo) import Hasura.RQL.Types.SchemaCache.Build import Hasura.RQL.Types.SchemaCacheTypes (DependencyReason (DRTable), SchemaDependency (SchemaDependency), SchemaObjId (SOSourceObj), SourceObjId (SOITable)) @@ -474,7 +473,7 @@ buildForeignKeySet (API.ForeignKeys constraints) = $ constraints & HashMap.foldMapWithKey @[RQL.T.T.ForeignKeyMetadata 'DataConnector] \constraintName API.Constraint {..} -> maybeToList do - let columnMapAssocList = HashMap.foldrWithKey' (\(API.ColumnName k) (API.ColumnName v) acc -> (DC.ColumnName k, DC.ColumnName v) : acc) [] _cColumnMapping + let columnMapAssocList = HashMap.foldrWithKey' (\k v acc -> (Witch.from k, Witch.from v) : acc) [] $ API.unColumnPathMapping _cColumnMapping columnMapping <- NEHashMap.fromList columnMapAssocList let foreignKey = RQL.T.T.ForeignKey @@ -786,7 +785,7 @@ convertTableMetadataToTableInfo tableName logicalModelCache RQL.T.T.DBTableMetad constraint = SourceConstraint { _scForeignTable = Witch.from _fkForeignTable, - _scColumnMapping = HashMap.fromList $ bimap Witch.from Witch.from <$> NEHashMap.toList _fkColumnMapping + _scColumnMapping = RelMapping $ NEHashMap.toHashMap _fkColumnMapping } in (constraintName, constraint) ) diff --git a/server/src-lib/Hasura/Backends/DataConnector/Adapter/Types.hs b/server/src-lib/Hasura/Backends/DataConnector/Adapter/Types.hs index 22d57c9a76b..44c0ca2f7cc 100644 --- a/server/src-lib/Hasura/Backends/DataConnector/Adapter/Types.hs +++ b/server/src-lib/Hasura/Backends/DataConnector/Adapter/Types.hs @@ -23,6 +23,7 @@ module Hasura.Backends.DataConnector.Adapter.Types TableName (..), ConstraintName (..), ColumnName (..), + ColumnPath (..), FunctionName (..), FunctionReturnType (..), CountAggregate (..), @@ -414,6 +415,41 @@ instance ToTxt ColumnName where instance ToErrorValue ColumnName where toErrorValue = ErrorValue.squote . unColumnName +instance Witch.From ColumnName ColumnPath where + from = CPColumn + +-------------------------------------------------------------------------------- + +data ColumnPath + = CPPath (NonEmpty ColumnName) + | CPColumn (ColumnName) + deriving stock (Eq, Ord, Show, Generic) + deriving (FromJSON, ToJSON, ToSchema) via AC.Autodocodec ColumnPath + deriving anyclass (Hashable, NFData) + +instance HasCodec ColumnPath where + codec = AC.disjointMatchChoiceCodec pathCodec columnCodec chooser + where + pathCodec = AC.dimapCodec CPPath id (codec @(NonEmpty ColumnName)) + columnCodec = AC.dimapCodec CPColumn id (codec @ColumnName) + chooser = \case + CPPath p -> Left p + CPColumn c -> Right c + +instance ToJSONKey ColumnPath + +instance FromJSONKey ColumnPath + +instance Witch.From API.ColumnSelector ColumnPath where + from = \case + API.ColumnSelectorPath p -> CPPath $ Witch.from <$> p + API.ColumnSelectorColumn c -> CPColumn $ Witch.from c + +instance Witch.From ColumnPath API.ColumnSelector where + from = \case + CPPath p -> API.ColumnSelectorPath $ Witch.from <$> p + CPColumn c -> API.ColumnSelectorColumn $ Witch.from c + -------------------------------------------------------------------------------- newtype FunctionName = FunctionName {unFunctionName :: NonEmpty Text} diff --git a/server/src-lib/Hasura/Backends/DataConnector/Plan/Common.hs b/server/src-lib/Hasura/Backends/DataConnector/Plan/Common.hs index 46befd66c84..72e542884ca 100644 --- a/server/src-lib/Hasura/Backends/DataConnector/Plan/Common.hs +++ b/server/src-lib/Hasura/Backends/DataConnector/Plan/Common.hs @@ -68,7 +68,7 @@ import Hasura.RQL.Types.Backend (Backend, SessionVarType, getColVals) import Hasura.RQL.Types.BackendType import Hasura.RQL.Types.Column import Hasura.RQL.Types.Common -import Hasura.RQL.Types.Relationships.Local (RelInfo (..), RelTarget (..)) +import Hasura.RQL.Types.Relationships.Local (RelInfo (..), RelMapping (..), RelTarget (..)) import Hasura.SQL.AnyBackend qualified as AB import Hasura.SQL.Types (CollectableType (..)) import Hasura.Session @@ -151,7 +151,7 @@ recordTableRelationshipFromRelInfo sourceTableName RelInfo {..} = do API.Relationship { _rTarget = API.TTable (API.TargetTable (Witch.from targetTableName)), _rRelationshipType = relationshipType, - _rColumnMapping = HashMap.fromList $ bimap Witch.from Witch.from <$> HashMap.toList riMapping + _rColumnMapping = API.ColumnPathMapping $ HashMap.fromList $ bimap Witch.from Witch.from <$> HashMap.toList (unRelMapping riMapping) } recordRelationship sourceTableName @@ -346,8 +346,10 @@ pushColumn :: ColumnStack -> ColumnName -> ColumnStack pushColumn (ColumnStack stack) columnName = ColumnStack $ columnName : stack toColumnSelector :: ColumnStack -> ColumnName -> API.ColumnSelector +toColumnSelector (ColumnStack []) columnName = + API.ColumnSelectorColumn $ Witch.from columnName toColumnSelector (ColumnStack stack) columnName = - API.ColumnSelector $ NonEmpty.reverse $ Witch.from columnName :| fmap Witch.from stack + API.ColumnSelectorPath $ NonEmpty.reverse $ Witch.from columnName :| fmap Witch.from stack -------------------------------------------------------------------------------- diff --git a/server/src-lib/Hasura/Backends/DataConnector/Plan/QueryPlan.hs b/server/src-lib/Hasura/Backends/DataConnector/Plan/QueryPlan.hs index fac67fa2f82..a472936ca4d 100644 --- a/server/src-lib/Hasura/Backends/DataConnector/Plan/QueryPlan.hs +++ b/server/src-lib/Hasura/Backends/DataConnector/Plan/QueryPlan.hs @@ -214,7 +214,7 @@ fromNativeQueryArray arrRel translateFieldsAndAggregates sourceTargetName native API.Relationship { _rTarget = TInterpolated (API.TargetInterpolatedQuery nqid), _rRelationshipType = API.ArrayRelationship, - _rColumnMapping = HashMap.fromList $ bimap Witch.from Witch.from <$> HashMap.toList (_aarColumnMapping arrRel) + _rColumnMapping = API.ColumnPathMapping $ HashMap.fromList $ bimap Witch.from Witch.from <$> HashMap.toList (_aarColumnMapping arrRel) } pure @@ -252,7 +252,7 @@ fromNativeQueryObject objRel sourceTargetName nativeQuery = do API.Relationship { _rTarget = TInterpolated (API.TargetInterpolatedQuery nqid), _rRelationshipType = API.ObjectRelationship, - _rColumnMapping = HashMap.fromList $ bimap Witch.from Witch.from <$> HashMap.toList (_aarColumnMapping objRel) + _rColumnMapping = API.ColumnPathMapping $ HashMap.fromList $ bimap Witch.from Witch.from <$> HashMap.toList (_aarColumnMapping objRel) } pure @@ -526,7 +526,7 @@ translateAnnField targetName = \case API.Relationship { _rTarget = API.TTable (API.TargetTable targetTable), _rRelationshipType = API.ObjectRelationship, - _rColumnMapping = HashMap.fromList $ bimap Witch.from Witch.from <$> HashMap.toList (_aarColumnMapping objRel) + _rColumnMapping = API.ColumnPathMapping $ HashMap.fromList $ bimap Witch.from Witch.from <$> HashMap.toList (_aarColumnMapping objRel) } pure @@ -586,7 +586,7 @@ translateArrayRelationSelect targetName translateFieldsAndAggregates arrRel = do API.Relationship { _rTarget = API.TTable (API.TargetTable (Witch.into targetTable)), _rRelationshipType = API.ArrayRelationship, - _rColumnMapping = HashMap.fromList $ bimap Witch.from Witch.from <$> HashMap.toList (_aarColumnMapping arrRel) + _rColumnMapping = API.ColumnPathMapping $ HashMap.fromList $ bimap Witch.from Witch.from <$> HashMap.toList (_aarColumnMapping arrRel) } pure diff --git a/server/src-lib/Hasura/Backends/MSSQL/FromIr/Expression.hs b/server/src-lib/Hasura/Backends/MSSQL/FromIr/Expression.hs index b56ee37051c..f3e9dbbab57 100644 --- a/server/src-lib/Hasura/Backends/MSSQL/FromIr/Expression.hs +++ b/server/src-lib/Hasura/Backends/MSSQL/FromIr/Expression.hs @@ -86,7 +86,7 @@ fromAnnBoolExpFld = expressions <- traverse (fromOpExpG columnInfo) opExpGs potentiallyRedacted redactionExp (AndExpression expressions) IR.AVRemoteRelationship _ -> error "fromAnnBoolExpFld RemoteRelationship" - IR.AVRelationship IR.RelInfo {riMapping = mapping, riTarget = target} (IR.RelationshipFilters tablePerm annBoolExp) -> do + IR.AVRelationship IR.RelInfo {riMapping = IR.RelMapping mapping, riTarget = target} (IR.RelationshipFilters tablePerm annBoolExp) -> do case target of IR.RelTargetNativeQuery _ -> error "fromAnnBoolExpFld RelTargetNativeQuery" IR.RelTargetTable table -> do diff --git a/server/src-lib/Hasura/Backends/MSSQL/FromIr/Query.hs b/server/src-lib/Hasura/Backends/MSSQL/FromIr/Query.hs index 31d3410c619..fc813be425c 100644 --- a/server/src-lib/Hasura/Backends/MSSQL/FromIr/Query.hs +++ b/server/src-lib/Hasura/Backends/MSSQL/FromIr/Query.hs @@ -991,20 +991,20 @@ unfurlAnnotatedOrderByElement = -- text/ntext/image. See ToQuery for more explanation. _ -> Nothing ) - IR.AOCObjectRelation IR.RelInfo {riMapping = mapping, riTarget = IR.RelTargetNativeQuery nativeQueryName} annBoolExp annOrderByElementG -> do + IR.AOCObjectRelation IR.RelInfo {riMapping = IR.RelMapping mapping, riTarget = IR.RelTargetNativeQuery nativeQueryName} annBoolExp annOrderByElementG -> do let name = T.toTxt (getNativeQueryName nativeQueryName) selectFrom = TSQL.FromIdentifier name joinAliasEntity <- lift (lift (generateAlias (ForOrderAlias name))) genObjectRelation mapping annBoolExp annOrderByElementG joinAliasEntity selectFrom (Left nativeQueryName) - IR.AOCObjectRelation IR.RelInfo {riMapping = mapping, riTarget = IR.RelTargetTable table} annBoolExp annOrderByElementG -> do + IR.AOCObjectRelation IR.RelInfo {riMapping = IR.RelMapping mapping, riTarget = IR.RelTargetTable table} annBoolExp annOrderByElementG -> do selectFrom <- lift (lift (fromQualifiedTable table)) joinAliasEntity <- lift (lift (generateAlias (ForOrderAlias (tableNameText table)))) genObjectRelation mapping annBoolExp annOrderByElementG joinAliasEntity selectFrom (Right table) IR.AOCArrayAggregation IR.RelInfo {riTarget = IR.RelTargetNativeQuery _} _annBoolExp _annAggregateOrderBy -> error "unfurlAnnotatedOrderByElement RelTargetNativeQuery" - IR.AOCArrayAggregation IR.RelInfo {riMapping = mapping, riTarget = IR.RelTargetTable tableName} annBoolExp annAggregateOrderBy -> do + IR.AOCArrayAggregation IR.RelInfo {riMapping = IR.RelMapping mapping, riTarget = IR.RelTargetTable tableName} annBoolExp annAggregateOrderBy -> do selectFrom <- lift (lift (fromQualifiedTable tableName)) let alias = aggFieldName joinAliasEntity <- diff --git a/server/src-lib/Hasura/Backends/MSSQL/Instances/Types.hs b/server/src-lib/Hasura/Backends/MSSQL/Instances/Types.hs index 64e15d019c1..77a15af7bab 100644 --- a/server/src-lib/Hasura/Backends/MSSQL/Instances/Types.hs +++ b/server/src-lib/Hasura/Backends/MSSQL/Instances/Types.hs @@ -40,6 +40,7 @@ instance Backend 'MSSQL where type NullsOrderType 'MSSQL = MSSQL.NullsOrder type CountType 'MSSQL = MSSQL.CountType type Column 'MSSQL = MSSQL.ColumnName + type ColumnPath 'MSSQL = MSSQL.ColumnName type ScalarValue 'MSSQL = MSSQL.Value type ScalarType 'MSSQL = MSSQL.ScalarType type BooleanOperators 'MSSQL = MSSQL.BooleanOperators @@ -123,6 +124,10 @@ instance Backend 'MSSQL where getColVals _ _ _ _ _ _ = throw500 "getColVals: not implemented for the MSSQL backend" + getColumnPathColumn = id + + tryColumnPathToColumn = Just + instance HasSourceConfiguration 'MSSQL where type SourceConfig 'MSSQL = MSSQL.MSSQLSourceConfig type SourceConnConfiguration 'MSSQL = MSSQL.MSSQLConnConfiguration diff --git a/server/src-lib/Hasura/Backends/Postgres/Execute/Insert.hs b/server/src-lib/Hasura/Backends/Postgres/Execute/Insert.hs index 7657ad98ab2..2feafc23d7a 100644 --- a/server/src-lib/Hasura/Backends/Postgres/Execute/Insert.hs +++ b/server/src-lib/Hasura/Backends/Postgres/Execute/Insert.hs @@ -195,7 +195,7 @@ insertObject singleObjIns additionalColumns userInfo planVars stringifyNum tCase afterInsertDepCols :: [ColumnInfo ('Postgres pgKind)] afterInsertDepCols = flip (getColInfos @('Postgres pgKind)) allColumns - $ concatMap (HashMap.keys . riMapping . IR._riRelationInfo) allAfterInsertRels + $ concatMap (HashMap.keys . unRelMapping . riMapping . IR._riRelationInfo) allAfterInsertRels withArrRels :: Maybe (ColumnValues ('Postgres pgKind) TxtEncodedVal) -> @@ -258,7 +258,7 @@ insertObjRel planVars userInfo stringifyNum tCase objRelIns = table = case riTarget relInfo of RelTargetNativeQuery _ -> error "insertObjRel RelTargetNativeQuery" RelTargetTable tn -> tn - mapCols = riMapping relInfo + mapCols = unRelMapping $ riMapping relInfo allCols = IR._aiTableColumns singleObjIns rCols = HashMap.elems mapCols rColInfos = getColInfos rCols allCols @@ -298,7 +298,7 @@ insertArrRel resCols userInfo planVars stringifyNum tCase arrRelIns = $ throw500 "affected_rows not returned in array rel insert" where IR.RelationInsert multiObjIns relInfo = arrRelIns - mapping = riMapping relInfo + mapping = unRelMapping $ riMapping relInfo mutOutput = IR.MOutMultirowFields [("affected_rows", IR.MCount)] -- | Validate an insert object based on insert columns, @@ -325,7 +325,7 @@ validateInsert insCols objRels addCols = do <> " columns as their values are already being determined by parent insert" forM_ objRels $ \relInfo -> do - let lCols = HashMap.keys $ riMapping relInfo + let lCols = HashMap.keys $ unRelMapping $ riMapping relInfo relName = riName relInfo relNameTxt = relNameToTxt relName lColConflicts = lCols `intersect` (addCols <> insCols) diff --git a/server/src-lib/Hasura/Backends/Postgres/Instances/Metadata.hs b/server/src-lib/Hasura/Backends/Postgres/Instances/Metadata.hs index f6424969e1b..460446677b5 100644 --- a/server/src-lib/Hasura/Backends/Postgres/Instances/Metadata.hs +++ b/server/src-lib/Hasura/Backends/Postgres/Instances/Metadata.hs @@ -323,7 +323,7 @@ instance let mappings :: HashMap Postgres.PGCol Postgres.PGCol mappings = NEHashMap.toHashMap _fkColumnMapping - pure (_cName _fkConstraint, SourceConstraint _fkForeignTable mappings) + pure (_cName _fkConstraint, SourceConstraint _fkForeignTable $ RelMapping mappings) pure SourceTableInfo diff --git a/server/src-lib/Hasura/Backends/Postgres/Instances/Types.hs b/server/src-lib/Hasura/Backends/Postgres/Instances/Types.hs index c8fbaeaab00..80209289a97 100644 --- a/server/src-lib/Hasura/Backends/Postgres/Instances/Types.hs +++ b/server/src-lib/Hasura/Backends/Postgres/Instances/Types.hs @@ -103,6 +103,7 @@ instance type NullsOrderType ('Postgres pgKind) = Postgres.NullsOrder type CountType ('Postgres pgKind) = Postgres.CountAggregate pgKind type Column ('Postgres pgKind) = Postgres.PGCol + type ColumnPath ('Postgres pgKind) = Postgres.PGCol type ScalarValue ('Postgres pgKind) = Postgres.PGScalarValue type ScalarType ('Postgres pgKind) = Postgres.PGScalarType type BooleanOperators ('Postgres pgKind) = Postgres.BooleanOperators @@ -177,6 +178,10 @@ instance getColVals = Postgres.getPGColValues + getColumnPathColumn = id + + tryColumnPathToColumn = Just + instance ( HasTag ('Postgres pgKind) ) => diff --git a/server/src-lib/Hasura/Backends/Postgres/Translate/BoolExp.hs b/server/src-lib/Hasura/Backends/Postgres/Translate/BoolExp.hs index f4e6c583d9e..71f15d7c3ec 100644 --- a/server/src-lib/Hasura/Backends/Postgres/Translate/BoolExp.hs +++ b/server/src-lib/Hasura/Backends/Postgres/Translate/BoolExp.hs @@ -375,12 +375,12 @@ translateAggPredArguments predArgs relTableNameIdentifier userInfo = ) cols -translateTableRelationship :: (Monad m) => HashMap PGCol PGCol -> TableIdentifier -> BoolExpM m S.BoolExp +translateTableRelationship :: (Monad m) => RelMapping ('Postgres pgKind) -> TableIdentifier -> BoolExpM m S.BoolExp translateTableRelationship colMapping relTableNameIdentifier = do BoolExpCtx {currTableReference} <- ask pure $ sqlAnd - $ flip map (HashMap.toList colMapping) + $ flip map (HashMap.toList $ unRelMapping colMapping) $ \(lCol, rCol) -> S.BECompare S.SEQ diff --git a/server/src-lib/Hasura/Backends/Postgres/Translate/Select/Internal/OrderBy.hs b/server/src-lib/Hasura/Backends/Postgres/Translate/Select/Internal/OrderBy.hs index ce5c79761f3..26be4ab5640 100644 --- a/server/src-lib/Hasura/Backends/Postgres/Translate/Select/Internal/OrderBy.hs +++ b/server/src-lib/Hasura/Backends/Postgres/Translate/Select/Internal/OrderBy.hs @@ -132,7 +132,7 @@ processOrderByItems userInfo sourcePrefix' selectSourceQual fieldAlias' similarA $ S.mkQIdenExp baseTableIdentifier $ ciColumn pgColInfo AOCObjectRelation relInfo relFilter rest -> withWriteObjectRelation $ do - let RelInfo {riName = relName, riMapping = colMapping, riTarget = relTarget} = relInfo + let RelInfo {riName = relName, riMapping = RelMapping colMapping, riTarget = relTarget} = relInfo relSourcePrefix = mkObjectRelationTableAlias sourcePrefix relName fieldName = mkOrderByFieldName relName case relTarget of @@ -153,7 +153,7 @@ processOrderByItems userInfo sourcePrefix' selectSourceQual fieldAlias' similarA S.mkQIdenExp relSourcePrefix relOrderByAlias ) AOCArrayAggregation relInfo relFilter aggOrderBy -> withWriteArrayRelation $ do - let RelInfo {riName = relName, riMapping = colMapping, riTarget = relTarget} = relInfo + let RelInfo {riName = relName, riMapping = RelMapping colMapping, riTarget = relTarget} = relInfo case relTarget of RelTargetNativeQuery _ -> error "processAnnotatedOrderByElement RelTargetNativeQuery (AOCArrayAggregation)" RelTargetTable relTable -> do diff --git a/server/src-lib/Hasura/GraphQL/Execute/Backend.hs b/server/src-lib/Hasura/GraphQL/Execute/Backend.hs index ebc1db02f41..2e36773cdff 100644 --- a/server/src-lib/Hasura/GraphQL/Execute/Backend.hs +++ b/server/src-lib/Hasura/GraphQL/Execute/Backend.hs @@ -187,7 +187,7 @@ convertRemoteSourceRelationship :: forall b. (Backend b) => -- | Join columns for the relationship - HashMap (Column b) (Column b) -> + HashMap (ColumnPath b) (ColumnPath b) -> -- | The LHS of the join, this is the expression which selects from json -- objects SelectFromG b (UnpreparedValue b) -> diff --git a/server/src-lib/Hasura/GraphQL/Schema/Select.hs b/server/src-lib/Hasura/GraphQL/Schema/Select.hs index 5410f8a67c7..d5ce382cc18 100644 --- a/server/src-lib/Hasura/GraphQL/Schema/Select.hs +++ b/server/src-lib/Hasura/GraphQL/Schema/Select.hs @@ -1560,7 +1560,7 @@ relationshipField table ri@RelInfo {riTarget = RelTargetTable otherTableName} = RelTargetTable tn -> Just tn _ -> Nothing in if (remoteTableName == Just table) - && (riMapping remoteRI `HashMap.isInverseOf` riMapping ri) + && (unRelMapping (riMapping remoteRI) `HashMap.isInverseOf` unRelMapping (riMapping ri)) && (thisTablePerm == rfFilter) then BoolAnd [] else x @@ -1610,7 +1610,7 @@ relationshipField table ri@RelInfo {riTarget = RelTargetTable otherTableName} = nullable <- case (riIsManual ri, riInsertOrder ri) of -- Automatically generated forward relationship (False, BeforeParent) -> do - let columns = HashMap.keys $ riMapping ri + let columns = fmap (getColumnPathColumn @b) $ HashMap.keys $ unRelMapping $ riMapping ri fieldInfoMap = _tciFieldInfoMap $ _tiCoreInfo tableInfo findColumn col = HashMap.lookup (fromCol @b col) fieldInfoMap ^? _Just . _FIColumn . _SCIScalarColumn -- Fetch information about the referencing columns of the foreign key @@ -1627,7 +1627,7 @@ relationshipField table ri@RelInfo {riTarget = RelTargetTable otherTableName} = $ P.subselection_ relFieldName desc selectionSetParser <&> \fields -> IR.AFObjectRelation - $ IR.AnnRelationSelectG (riName ri) (riMapping ri) Nullable + $ IR.AnnRelationSelectG (riName ri) (unRelMapping $ riMapping ri) Nullable $ IR.AnnObjectSelectG fields (IR.FromTable otherTableName) $ deduplicatePermissions $ IR._tpFilter @@ -1639,7 +1639,7 @@ relationshipField table ri@RelInfo {riTarget = RelTargetTable otherTableName} = otherTableParser <&> \selectExp -> IR.AFArrayRelation $ IR.ASSimple - $ IR.AnnRelationSelectG (riName ri) (riMapping ri) Nullable + $ IR.AnnRelationSelectG (riName ri) (unRelMapping $ riMapping ri) Nullable $ deduplicatePermissions' selectExp relAggFieldName = applyFieldNameCaseCust tCase $ relFieldName <> Name.__aggregate relAggDesc = Just $ G.Description "An aggregate relationship" @@ -1658,8 +1658,8 @@ relationshipField table ri@RelInfo {riTarget = RelTargetTable otherTableName} = pure $ catMaybes [ Just arrayRelField, - fmap (IR.AFArrayRelation . IR.ASAggregate . IR.AnnRelationSelectG (riName ri) (riMapping ri) Nullable) <$> remoteAggField, - fmap (IR.AFArrayRelation . IR.ASConnection . IR.AnnRelationSelectG (riName ri) (riMapping ri) Nullable) <$> remoteConnectionField + fmap (IR.AFArrayRelation . IR.ASAggregate . IR.AnnRelationSelectG (riName ri) (unRelMapping $ riMapping ri) Nullable) <$> remoteAggField, + fmap (IR.AFArrayRelation . IR.ASConnection . IR.AnnRelationSelectG (riName ri) (unRelMapping $ riMapping ri) Nullable) <$> remoteConnectionField ] relationshipField _table ri@RelInfo {riTarget = RelTargetNativeQuery nativeQueryName} = runMaybeT do relFieldName <- lift $ textToName $ relNameToTxt $ riName ri @@ -1680,7 +1680,7 @@ relationshipField _table ri@RelInfo {riTarget = RelTargetNativeQuery nativeQuery $ pure $ nativeQueryParser <&> \selectExp -> - IR.AFObjectRelation (IR.AnnRelationSelectG (riName ri) (riMapping ri) nullability selectExp) + IR.AFObjectRelation (IR.AnnRelationSelectG (riName ri) (unRelMapping $ riMapping ri) nullability selectExp) ArrRel -> do nativeQueryInfo <- askNativeQueryInfo nativeQueryName @@ -1697,4 +1697,4 @@ relationshipField _table ri@RelInfo {riTarget = RelTargetNativeQuery nativeQuery <&> \selectExp -> IR.AFArrayRelation $ IR.ASSimple - $ IR.AnnRelationSelectG (riName ri) (riMapping ri) innerNullability selectExp + $ IR.AnnRelationSelectG (riName ri) (unRelMapping $ riMapping ri) innerNullability selectExp diff --git a/server/src-lib/Hasura/NativeQuery/Schema.hs b/server/src-lib/Hasura/NativeQuery/Schema.hs index 6ac97d9380a..4cc4442411c 100644 --- a/server/src-lib/Hasura/NativeQuery/Schema.hs +++ b/server/src-lib/Hasura/NativeQuery/Schema.hs @@ -48,7 +48,7 @@ import Hasura.RQL.IR.Value (Provenance (FreshVar), UnpreparedValue (UVParameter) import Hasura.RQL.Types.Column qualified as Column import Hasura.RQL.Types.Common (RelType (..), relNameToTxt) import Hasura.RQL.Types.Metadata.Object qualified as MO -import Hasura.RQL.Types.Relationships.Local (Nullable (..), RelInfo (..), RelTarget (..)) +import Hasura.RQL.Types.Relationships.Local (Nullable (..), RelInfo (..), RelMapping (..), RelTarget (..)) import Hasura.RQL.Types.Schema.Options qualified as Options import Hasura.RQL.Types.Source ( SourceInfo (_siCustomization, _siName), @@ -328,7 +328,7 @@ nativeQueryRelationshipField ri | riType ri == ObjRel = runMaybeT do pure $ nativeQueryParser <&> \selectExp -> - IR.AFObjectRelation (IR.AnnRelationSelectG (riName ri) (riMapping ri) nullability selectExp) + IR.AFObjectRelation (IR.AnnRelationSelectG (riName ri) (unRelMapping $ riMapping ri) nullability selectExp) RelTargetTable otherTableName -> do let desc = Just $ G.Description "An object relationship" roleName <- retrieve scRole @@ -339,7 +339,7 @@ nativeQueryRelationshipField ri | riType ri == ObjRel = runMaybeT do $ P.subselection_ relFieldName desc selectionSetParser <&> \fields -> IR.AFObjectRelation - $ IR.AnnRelationSelectG (riName ri) (riMapping ri) Nullable + $ IR.AnnRelationSelectG (riName ri) (unRelMapping $ riMapping ri) Nullable $ IR.AnnObjectSelectG fields (IR.FromTable otherTableName) $ IR._tpFilter $ tablePermissionsInfo remotePerms @@ -361,7 +361,7 @@ nativeQueryRelationshipField ri = do <&> \selectExp -> IR.AFArrayRelation $ IR.ASSimple - $ IR.AnnRelationSelectG (riName ri) (riMapping ri) innerNullability selectExp + $ IR.AnnRelationSelectG (riName ri) (unRelMapping $ riMapping ri) innerNullability selectExp RelTargetTable otherTableName -> runMaybeT $ do let arrayRelDesc = Just $ G.Description "An array relationship" @@ -371,6 +371,6 @@ nativeQueryRelationshipField ri = do otherTableParser <&> \selectExp -> IR.AFArrayRelation $ IR.ASSimple - $ IR.AnnRelationSelectG (riName ri) (riMapping ri) Nullable + $ IR.AnnRelationSelectG (riName ri) (unRelMapping $ riMapping ri) Nullable $ selectExp pure arrayRelField diff --git a/server/src-lib/Hasura/RQL/DDL/Relationship.hs b/server/src-lib/Hasura/RQL/DDL/Relationship.hs index a007ca8d0d0..71c157cf0ec 100644 --- a/server/src-lib/Hasura/RQL/DDL/Relationship.hs +++ b/server/src-lib/Hasura/RQL/DDL/Relationship.hs @@ -135,14 +135,15 @@ defaultBuildObjectRelationshipInfo :: m (RelInfo b, Seq SchemaDependency) defaultBuildObjectRelationshipInfo source foreignKeys qt (RelDef rn ru _) = case ru of RUManual (RelManualNativeQueryConfig (RelManualNativeQueryConfigC {rmnNativeQueryName = refqt, rmnCommon = common})) -> do - let (lCols, rCols) = unzip $ HashMap.toList $ rmColumns common + let (lCols, rCols) = unzip $ HashMap.toList $ unRelMapping $ rmColumns common io = fromMaybe BeforeParent $ rmInsertOrder common mkNativeQueryDependency nativeQueryName reason col = SchemaDependency ( SOSourceObj source $ AB.mkAnyBackend $ SOINativeQueryObj @b nativeQueryName - $ NQOCol @b col + $ NQOCol @b + $ getColumnPathColumn @b col ) reason mkDependency tableName reason col = @@ -150,7 +151,8 @@ defaultBuildObjectRelationshipInfo source foreignKeys qt (RelDef rn ru _) = case ( SOSourceObj source $ AB.mkAnyBackend $ SOITableObj @b tableName - $ TOCol @b col + $ TOCol @b + $ getColumnPathColumn @b col ) reason dependencies = @@ -158,14 +160,15 @@ defaultBuildObjectRelationshipInfo source foreignKeys qt (RelDef rn ru _) = case <> (mkNativeQueryDependency refqt DRRightColumn <$> Seq.fromList rCols) pure (RelInfo rn ObjRel (rmColumns common) (RelTargetNativeQuery refqt) True io, dependencies) RUManual (RelManualTableConfig (RelManualTableConfigC {rmtTable = refqt, rmtCommon = common})) -> do - let (lCols, rCols) = unzip $ HashMap.toList $ rmColumns common + let (lCols, rCols) = unzip $ HashMap.toList $ unRelMapping $ rmColumns common io = fromMaybe BeforeParent $ rmInsertOrder common mkDependency tableName reason col = SchemaDependency ( SOSourceObj source $ AB.mkAnyBackend $ SOITableObj @b tableName - $ TOCol @b col + $ TOCol @b + $ getColumnPathColumn @b col ) reason dependencies = @@ -195,8 +198,8 @@ defaultBuildObjectRelationshipInfo source foreignKeys qt (RelDef rn ru _) = case ) DRRemoteTable ] - <> (drUsingColumnDep @b source qt <$> Seq.fromList (toList columns)) - pure (RelInfo rn ObjRel (NEHashMap.toHashMap colMap) (RelTargetTable foreignTable) False BeforeParent, dependencies) + <> (drUsingColumnDep @b source qt <$> Seq.fromList (toList $ getColumnPathColumn @b <$> columns)) + pure (RelInfo rn ObjRel (RelMapping $ NEHashMap.toHashMap colMap) (RelTargetTable foreignTable) False BeforeParent, dependencies) RUFKeyOn (RemoteTable remoteTable remoteCols) -> mkFkeyRel ObjRel AfterParent source rn qt remoteTable remoteCols foreignKeys @@ -219,7 +222,8 @@ nativeQueryRelationshipSetup sourceName nativeQueryName relType (RelDef relName ( SOSourceObj sourceName $ AB.mkAnyBackend $ SOINativeQueryObj @b refqt - $ NQOCol @b c + $ NQOCol @b + $ getColumnPathColumn @b c ) DRRightColumn ) @@ -231,12 +235,13 @@ nativeQueryRelationshipSetup sourceName nativeQueryName relType (RelDef relName ( SOSourceObj sourceName $ AB.mkAnyBackend $ SOITableObj @b refqt - $ TOCol @b c + $ TOCol @b + $ getColumnPathColumn @b c ) DRRightColumn ) - let (lCols, rCols) = unzip $ HashMap.toList $ rmColumns common + let (lCols, rCols) = unzip $ HashMap.toList $ unRelMapping $ rmColumns common io = case relType of ObjRel -> fromMaybe BeforeParent $ rmInsertOrder common ArrRel -> AfterParent @@ -247,7 +252,8 @@ nativeQueryRelationshipSetup sourceName nativeQueryName relType (RelDef relName ( SOSourceObj sourceName $ AB.mkAnyBackend $ SOINativeQueryObj @b nativeQueryName - $ NQOCol @b c + $ NQOCol @b + $ getColumnPathColumn @b c ) DRLeftColumn ) @@ -266,7 +272,7 @@ defaultBuildArrayRelationshipInfo :: m (RelInfo b, Seq SchemaDependency) defaultBuildArrayRelationshipInfo source foreignKeys qt (RelDef rn ru _) = case ru of RUManual (RelManualNativeQueryConfig (RelManualNativeQueryConfigC {rmnNativeQueryName = refqt, rmnCommon = common})) -> do - let (lCols, rCols) = unzip $ HashMap.toList $ rmColumns common + let (lCols, rCols) = unzip $ HashMap.toList $ unRelMapping $ rmColumns common deps = ( fmap ( \c -> @@ -274,7 +280,8 @@ defaultBuildArrayRelationshipInfo source foreignKeys qt (RelDef rn ru _) = case ( SOSourceObj source $ AB.mkAnyBackend $ SOITableObj @b qt - $ TOCol @b c + $ TOCol @b + $ getColumnPathColumn @b c ) DRLeftColumn ) @@ -286,14 +293,15 @@ defaultBuildArrayRelationshipInfo source foreignKeys qt (RelDef rn ru _) = case ( SOSourceObj source $ AB.mkAnyBackend $ SOINativeQueryObj @b refqt - $ NQOCol @b c + $ NQOCol @b + $ getColumnPathColumn @b c ) DRRightColumn ) (Seq.fromList rCols) pure (RelInfo rn ArrRel (rmColumns common) (RelTargetNativeQuery refqt) True AfterParent, deps) RUManual (RelManualTableConfig (RelManualTableConfigC {rmtTable = refqt, rmtCommon = common})) -> do - let (lCols, rCols) = unzip $ HashMap.toList $ rmColumns common + let (lCols, rCols) = unzip $ HashMap.toList $ unRelMapping $ rmColumns common deps = ( fmap ( \c -> @@ -301,7 +309,8 @@ defaultBuildArrayRelationshipInfo source foreignKeys qt (RelDef rn ru _) = case ( SOSourceObj source $ AB.mkAnyBackend $ SOITableObj @b qt - $ TOCol @b c + $ TOCol @b + $ getColumnPathColumn @b c ) DRLeftColumn ) @@ -313,7 +322,8 @@ defaultBuildArrayRelationshipInfo source foreignKeys qt (RelDef rn ru _) = case ( SOSourceObj source $ AB.mkAnyBackend $ SOITableObj @b refqt - $ TOCol @b c + $ TOCol @b + $ getColumnPathColumn @b c ) DRRightColumn ) @@ -332,7 +342,7 @@ mkFkeyRel :: RelName -> TableName b -> TableName b -> - NonEmpty (Column b) -> + NonEmpty (ColumnPath b) -> HashMap (TableName b) (HashSet (ForeignKey b)) -> m (RelInfo b, Seq SchemaDependency) mkFkeyRel relType io source rn sourceTable remoteTable remoteColumns foreignKeys = do @@ -358,17 +368,18 @@ mkFkeyRel relType io source rn sourceTable remoteTable remoteColumns foreignKeys DRRemoteTable ] <> ( drUsingColumnDep @b source remoteTable - <$> Seq.fromList (toList remoteColumns) + <$> Seq.fromList (toList $ getColumnPathColumn @b <$> remoteColumns) ) - pure (RelInfo rn relType (reverseMap (NEHashMap.toHashMap colMap)) (RelTargetTable remoteTable) False io, dependencies) + pure (RelInfo rn relType (RelMapping $ reverseMap $ NEHashMap.toHashMap colMap) (RelTargetTable remoteTable) False io, dependencies) where reverseMap :: (Hashable y) => HashMap x y -> HashMap y x reverseMap = HashMap.fromList . fmap swap . HashMap.toList -- | Try to find a foreign key constraint, identifying a constraint by its set of columns getRequiredFkey :: + forall m b. (QErrM m, Backend b) => - NonEmpty (Column b) -> + NonEmpty (ColumnPath b) -> [ForeignKey b] -> m (ForeignKey b) getRequiredFkey cols fkeys = diff --git a/server/src-lib/Hasura/RQL/DDL/Relationship/Suggest.hs b/server/src-lib/Hasura/RQL/DDL/Relationship/Suggest.hs index cda00928a65..1aac067eeae 100644 --- a/server/src-lib/Hasura/RQL/DDL/Relationship/Suggest.hs +++ b/server/src-lib/Hasura/RQL/DDL/Relationship/Suggest.hs @@ -36,10 +36,11 @@ import Hasura.Prelude import Hasura.RQL.Types.Backend import Hasura.RQL.Types.Common import Hasura.RQL.Types.Metadata.Backend -import Hasura.RQL.Types.Relationships.Local (RelInfo (riMapping, riTarget), RelTarget (..)) +import Hasura.RQL.Types.Relationships.Local (RelInfo (riMapping, riTarget), RelMapping (..), RelTarget (..)) import Hasura.RQL.Types.SchemaCache import Hasura.RQL.Types.SchemaCache.Build import Hasura.Table.Cache (ForeignKey, UniqueConstraint, _cName, _fkColumnMapping, _fkConstraint, _fkForeignTable, _ucColumns) +import Witch qualified -- | Datatype used by Metadata API to represent Request for Suggested Relationships data SuggestRels b = SuggestRels @@ -102,7 +103,7 @@ instance (Backend b) => HasCodec (Relationship b) where data Mapping b = Mapping { mTable :: TableName b, - mColumns :: [Column b], + mColumns :: [ColumnPath b], mConstraintName :: Maybe J.Value } deriving (Generic) @@ -133,7 +134,7 @@ suggestRelsFK :: HashMap (TableName b) (TableCoreInfo b) -> TableName b -> HashSet (UniqueConstraint b) -> - H.HashSet (TableName b, HashMap (Column b) (Column b)) -> + H.HashSet (TableName b, HashMap (ColumnPath b) (ColumnPath b)) -> (TableName b -> Bool) -> ForeignKey b -> [Relationship b] @@ -145,6 +146,7 @@ suggestRelsFK omitTracked tables name uniqueConstraints tracked predicate foreig where toTracked = H.member (relatedTableName, columnRelationships) tracked fromTracked = H.member (name, invert columnRelationships) trackedBack + toRelationship, fromRelationship :: Relationship b toRelationship = Relationship { rType = ObjRel, @@ -157,10 +159,10 @@ suggestRelsFK omitTracked tables name uniqueConstraints tracked predicate foreig rTo = Mapping {mTable = name, mColumns = localColumns, mConstraintName = Just constraintName}, rFrom = Mapping {mTable = relatedTableName, mColumns = relatedColumns, mConstraintName = Nothing} } - columnRelationships = MapNE.toHashMap (_fkColumnMapping foreignKey) + columnRelationships = MapNE.toHashMap $ _fkColumnMapping foreignKey localColumns = HashMap.keys columnRelationships relatedColumns = HashMap.elems columnRelationships - uniqueConstraintColumns = H.map _ucColumns uniqueConstraints + uniqueConstraintColumns = H.map (H.map Witch.from . _ucColumns) uniqueConstraints relatedTableName = _fkForeignTable foreignKey relatedTable = HashMap.lookup relatedTableName tables constraintName = J.toJSON (_cName (_fkConstraint foreignKey)) @@ -174,10 +176,10 @@ suggestRelsFK omitTracked tables name uniqueConstraints tracked predicate foreig -- we're only interested in suggesting table-based relationships for now getRelationshipsInputs :: RelInfo b -> - Maybe (TableName b, HashMap (Column b) (Column b)) + Maybe (TableName b, HashMap (ColumnPath b) (ColumnPath b)) getRelationshipsInputs ri = case riTarget ri of - RelTargetTable tn -> Just (tn, riMapping ri) + RelTargetTable tn -> Just (tn, unRelMapping $ riMapping ri) _ -> Nothing suggestRelsTable :: diff --git a/server/src-lib/Hasura/RQL/DDL/Schema/Diff.hs b/server/src-lib/Hasura/RQL/DDL/Schema/Diff.hs index b3d951bf588..ec03bc53435 100644 --- a/server/src-lib/Hasura/RQL/DDL/Schema/Diff.hs +++ b/server/src-lib/Hasura/RQL/DDL/Schema/Diff.hs @@ -289,7 +289,8 @@ processTablesDiff :: ( MonadError QErr m, CacheRM m, MonadWriter MetadataModifier m, - BackendMetadata b + BackendMetadata b, + Column b ~ ColumnPath b ) => SourceName -> TableCache b -> @@ -313,7 +314,8 @@ alterTableInMetadata :: ( MonadError QErr m, CacheRM m, MonadWriter MetadataModifier m, - BackendMetadata b + BackendMetadata b, + Column b ~ ColumnPath b ) => SourceName -> TableCoreInfo b -> @@ -387,7 +389,8 @@ alterColumnsInMetadata :: ( MonadError QErr m, CacheRM m, MonadWriter MetadataModifier m, - BackendMetadata b + BackendMetadata b, + Column b ~ ColumnPath b ) => SourceName -> [(RawColumnInfo b, RawColumnInfo b)] -> diff --git a/server/src-lib/Hasura/RQL/DDL/Schema/Enum.hs b/server/src-lib/Hasura/RQL/DDL/Schema/Enum.hs index 9e416f35170..7d6ca7ecd2b 100644 --- a/server/src-lib/Hasura/RQL/DDL/Schema/Enum.hs +++ b/server/src-lib/Hasura/RQL/DDL/Schema/Enum.hs @@ -40,7 +40,9 @@ resolveEnumReferences enumTables = where resolveEnumReference :: ForeignKey b -> Maybe (Column b, EnumReference b) resolveEnumReference foreignKey = do - [(localColumn, foreignColumn)] <- pure $ NEHashMap.toList (_fkColumnMapping @b foreignKey) + [(localColumnPath, foreignColumnPath)] <- pure $ NEHashMap.toList (_fkColumnMapping @b foreignKey) + localColumn <- tryColumnPathToColumn @b localColumnPath + foreignColumn <- tryColumnPathToColumn @b foreignColumnPath let foreignKeyTableName = _fkForeignTable foreignKey (primaryKey, tConfig, enumValues) <- HashMap.lookup foreignKeyTableName enumTables let tableCustomName = _tcCustomName tConfig diff --git a/server/src-lib/Hasura/RQL/DDL/Schema/LegacyCatalog.hs b/server/src-lib/Hasura/RQL/DDL/Schema/LegacyCatalog.hs index 8be28b7146f..0ea5a70a70d 100644 --- a/server/src-lib/Hasura/RQL/DDL/Schema/LegacyCatalog.hs +++ b/server/src-lib/Hasura/RQL/DDL/Schema/LegacyCatalog.hs @@ -933,4 +933,4 @@ recreateSystemMetadata = do $ RelManualTableConfig $ RelManualTableConfigC (QualifiedObject schemaName tableName) - (RelManualCommon (HashMap.fromList columns) Nothing) + (RelManualCommon (RelMapping $ HashMap.fromList columns) Nothing) diff --git a/server/src-lib/Hasura/RQL/DDL/Schema/Rename.hs b/server/src-lib/Hasura/RQL/DDL/Schema/Rename.hs index 5f458f48cac..7051a46e3cb 100644 --- a/server/src-lib/Hasura/RQL/DDL/Schema/Rename.hs +++ b/server/src-lib/Hasura/RQL/DDL/Schema/Rename.hs @@ -162,7 +162,8 @@ renameColumnInMetadata :: ( MonadError QErr m, CacheRM m, MonadWriter MetadataModifier m, - BackendMetadata b + BackendMetadata b, + Column b ~ ColumnPath b ) => Column b -> Column b -> @@ -532,7 +533,7 @@ updateColExp qt rf (ColExp fld val) = -- rename columns in relationship definitions updateColInRel :: forall b m. - (CacheRM m, MonadWriter MetadataModifier m, BackendMetadata b) => + (CacheRM m, MonadWriter MetadataModifier m, BackendMetadata b, Column b ~ ColumnPath b) => SourceName -> TableName b -> RelName -> @@ -681,7 +682,7 @@ updateTableInRemoteRelationshipRHS source tableName remoteRelationshipName (_, n .~ toJSON newTableName updateColInObjRel :: - (Backend b) => + (Backend b, Column b ~ ColumnPath b) => TableName b -> TableName b -> RenameCol b -> @@ -694,7 +695,7 @@ updateColInObjRel fromQT toQT rnCol = \case RUManual $ updateRelManualConfig fromQT toQT rnCol manConfig updateRelChoice :: - (Backend b) => + (Backend b, Column b ~ ColumnPath b) => TableName b -> TableName b -> RenameCol b -> @@ -706,7 +707,7 @@ updateRelChoice fromQT toQT rnCol = RemoteTable t c -> RemoteTable t (getNewCol rnCol toQT c) updateColInArrRel :: - (Backend b) => + (Backend b, Column b ~ ColumnPath b) => TableName b -> TableName b -> RenameCol b -> @@ -718,8 +719,6 @@ updateColInArrRel fromQT toQT rnCol = \case in RUFKeyOn $ ArrRelUsingFKeyOn t updCol RUManual manConfig -> RUManual $ updateRelManualConfig fromQT toQT rnCol manConfig -type ColMap b = HashMap (Column b) (Column b) - getNewCol :: forall b f. (Backend b) => @@ -741,7 +740,7 @@ getNewCol rnCol qt cols = updateRelManualConfig :: forall b. - (Backend b) => + (Backend b, Column b ~ ColumnPath b) => TableName b -> TableName b -> RenameCol b -> @@ -754,14 +753,14 @@ updateRelManualConfig fromQT toQT rnCol (RelManualNativeQueryConfig (RelManualNa updateColMap :: forall b. - (Backend b) => + (Backend b, Column b ~ ColumnPath b) => TableName b -> TableName b -> RenameCol b -> - ColMap b -> - ColMap b + RelMapping b -> + RelMapping b updateColMap fromQT toQT rnCol = - HashMap.fromList . map (modCol fromQT *** modCol toQT) . HashMap.toList + RelMapping . HashMap.fromList . map (modCol fromQT *** modCol toQT) . HashMap.toList . unRelMapping where RenameItem qt oCol nCol = rnCol modCol colQt col = if colQt == qt && col == oCol then nCol else col diff --git a/server/src-lib/Hasura/RQL/DML/Select.hs b/server/src-lib/Hasura/RQL/DML/Select.hs index 5abd594affc..c4615a3c259 100644 --- a/server/src-lib/Hasura/RQL/DML/Select.hs +++ b/server/src-lib/Hasura/RQL/DML/Select.hs @@ -286,7 +286,7 @@ convExtRel sqlGen fieldInfoMap relName mAlias selQ sessVarBldr prepValBldr = do relInfo <- withPathK "name" $ askRelType fieldInfoMap relName pgWhenRelErr - let (RelInfo {riType = relTy, riMapping = colMapping, riTarget = relTarget}) = relInfo + let (RelInfo {riType = relTy, riMapping = RelMapping colMapping, riTarget = relTarget}) = relInfo relTableName <- case relTarget of RelTargetNativeQuery _ -> error "convExtRel RelTargetNativeQuery" RelTargetTable tn -> pure tn diff --git a/server/src-lib/Hasura/RQL/IR/Select/RelationSelect.hs b/server/src-lib/Hasura/RQL/IR/Select/RelationSelect.hs index b4ae0d3dca4..9080bfac31f 100644 --- a/server/src-lib/Hasura/RQL/IR/Select/RelationSelect.hs +++ b/server/src-lib/Hasura/RQL/IR/Select/RelationSelect.hs @@ -18,7 +18,7 @@ import Hasura.RQL.Types.Relationships.Local (Nullable) data AnnRelationSelectG (b :: BackendType) a = AnnRelationSelectG { _aarRelationshipName :: RelName, -- Relationship name - _aarColumnMapping :: HashMap (Column b) (Column b), -- Column of left table to join with + _aarColumnMapping :: HashMap (ColumnPath b) (ColumnPath b), -- Column of left table to join with _aarNullable :: Nullable, -- is the target object allowed to be missing? _aarAnnSelect :: a -- Current table. Almost ~ to SQL Select } diff --git a/server/src-lib/Hasura/RQL/Types/Backend.hs b/server/src-lib/Hasura/RQL/Types/Backend.hs index 6471663d469..e64ae0362ff 100644 --- a/server/src-lib/Hasura/RQL/Types/Backend.hs +++ b/server/src-lib/Hasura/RQL/Types/Backend.hs @@ -40,6 +40,7 @@ import Hasura.RQL.Types.Session (SessionVariables) import Hasura.RQL.Types.SourceConfiguration import Hasura.SQL.Types import Language.GraphQL.Draft.Syntax qualified as G +import Witch (From) type SessionVarType b = CollectableType (ScalarType b) @@ -81,6 +82,7 @@ class ( HasSourceConfiguration b, Representable (BasicOrderType b), Representable (Column b), + Representable (ColumnPath b), Representable (ComputedFieldDefinition b), Representable (ComputedFieldImplicitArguments b), Representable (ComputedFieldReturn b), @@ -104,14 +106,18 @@ class Ord (FunctionName b), Ord (ScalarType b), Ord (Column b), + Ord (ColumnPath b), Ord (ComputedFieldReturn b), Ord (ComputedFieldImplicitArguments b), Ord (ConstraintName b), Ord (FunctionArgument b), Ord (XComputedField b), Data (TableName b), + From (Column b) (ColumnPath b), FromJSON (BackendConfig b), FromJSON (Column b), + FromJSON (ColumnPath b), + FromJSON (ColumnPath b), FromJSON (ComputedFieldDefinition b), FromJSON (ConnectionTemplateRequestContext b), FromJSON (ConstraintName b), @@ -123,17 +129,22 @@ class FromJSON (ScalarType b), FromJSON (TableName b), FromJSONKey (Column b), + FromJSONKey (ColumnPath b), FromJSONKey (ConstraintName b), HasCodec (BackendConfig b), HasCodec (BackendSourceKind b), HasCodec (Column b), + HasCodec (ColumnPath b), HasCodec (ComputedFieldDefinition b), HasCodec (FunctionName b), HasCodec (FunctionReturnType b), HasCodec (ScalarType b), HasCodec (TableName b), + Hashable (Column b), + Hashable (ColumnPath b), ToJSON (BackendConfig b), ToJSON (Column b), + ToJSON (ColumnPath b), ToJSON (ConstraintName b), ToJSON (ExecutionStatistics b), ToJSON (FunctionArgument b), @@ -150,6 +161,7 @@ class ToJSON (HealthCheckTest b), ToJSON (ResolvedConnectionTemplate b), ToJSONKey (Column b), + ToJSONKey (ColumnPath b), ToJSONKey (ConstraintName b), ToJSONKey (ScalarType b), ToTxt (Column b), @@ -160,6 +172,7 @@ class ToErrorValue (Column b), ToErrorValue (TableName b), Typeable (Column b), + Typeable (ColumnPath b), Typeable b, HasTag b, Traversable (CountType b), @@ -232,6 +245,9 @@ class -- Name of a 'column' type Column b :: Type + -- Path to a column + type ColumnPath b :: Type + type ScalarValue b :: Type type ScalarType b :: Type @@ -443,6 +459,14 @@ class (Column b, [RemoteRelSupportedOp RemoteRelSessionVariableORLiteralValue]) -> m [Text] + -- | Get the top-level column from a ColumnPath + -- For backends that don't support nested objects (i.e. where ColumnPath b = Column b) this will be `id`. + getColumnPathColumn :: ColumnPath b -> Column b + + -- | Convert a singleton ColumnPath to a Column + -- Should return Nothing for paths to nested fields + tryColumnPathToColumn :: ColumnPath b -> Maybe (Column b) + backendSupportsNestedObjects :: Either QErr (XNestedObjects b) default backendSupportsNestedObjects :: (XNestedObjects b ~ XDisable) => Either QErr (XNestedObjects b) backendSupportsNestedObjects = throw400 InvalidConfiguration "Nested objects not supported" diff --git a/server/src-lib/Hasura/RQL/Types/Relationships/Local.hs b/server/src-lib/Hasura/RQL/Types/Relationships/Local.hs index 173aaf9e72a..40eea7f1a8f 100644 --- a/server/src-lib/Hasura/RQL/Types/Relationships/Local.hs +++ b/server/src-lib/Hasura/RQL/Types/Relationships/Local.hs @@ -10,6 +10,7 @@ module Hasura.RQL.Types.Relationships.Local ObjRelUsingChoice (..), RelDef (..), RelTarget (..), + RelMapping (..), RelInfo (..), RelManualConfig (..), RelManualTableConfig (..), @@ -31,6 +32,8 @@ import Autodocodec.Extended (optionalFieldOrIncludedNull', typeableName) import Control.Lens (makeLenses) import Data.Aeson.KeyMap qualified as KM import Data.Aeson.Types +import Data.Bitraversable +import Data.HashMap.Strict qualified as HashMap import Data.Text qualified as T import Data.Typeable (Typeable) import Hasura.NativeQuery.Types (NativeQueryName) @@ -39,6 +42,7 @@ import Hasura.RQL.Types.Backend import Hasura.RQL.Types.BackendTag (backendPrefix) import Hasura.RQL.Types.BackendType import Hasura.RQL.Types.Common +import Witch qualified data RelDef a = RelDef { _rdName :: RelName, @@ -112,7 +116,7 @@ deriving instance (Backend b) => Eq (RelManualNativeQueryConfig b) deriving instance (Backend b) => Show (RelManualNativeQueryConfig b) data RelManualCommon (b :: BackendType) = RelManualCommon - { rmColumns :: HashMap (Column b) (Column b), + { rmColumns :: RelMapping b, rmInsertOrder :: Maybe InsertOrder } deriving (Generic) @@ -188,7 +192,7 @@ instance (FromJSON a, Backend b) => FromJSON (RelUsing b a) where data ArrRelUsingFKeyOn (b :: BackendType) = ArrRelUsingFKeyOn { arufTable :: TableName b, - arufColumns :: NonEmpty (Column b) + arufColumns :: NonEmpty (ColumnPath b) } deriving (Generic) @@ -224,8 +228,8 @@ instance (ToAesonPairs a, Backend b) => ToJSON (WithTable b a) where object $ ("source" .= sourceName) : ("table" .= tn) : toAesonPairs rel data ObjRelUsingChoice b - = SameTable (NonEmpty (Column b)) - | RemoteTable (TableName b) (NonEmpty (Column b)) + = SameTable (NonEmpty (ColumnPath b)) + | RemoteTable (TableName b) (NonEmpty (ColumnPath b)) deriving (Generic) deriving instance (Backend b) => Eq (ObjRelUsingChoice b) @@ -235,10 +239,10 @@ deriving instance (Backend b) => Show (ObjRelUsingChoice b) instance (Backend b) => HasCodec (ObjRelUsingChoice b) where codec = dimapCodec dec enc $ disjointEitherCodec sameTableCodec remoteTableCodec where - sameTableCodec :: AC.JSONCodec (Either (Column b) (NonEmpty (Column b))) + sameTableCodec :: AC.JSONCodec (Either (ColumnPath b) (NonEmpty (ColumnPath b))) sameTableCodec = disjointEitherCodec codec codec - remoteTableCodec :: AC.JSONCodec (Either (TableName b, Column b) (TableName b, NonEmpty (Column b))) + remoteTableCodec :: AC.JSONCodec (Either (TableName b, ColumnPath b) (TableName b, NonEmpty (ColumnPath b))) remoteTableCodec = singleOrMultipleRelColumnsCodec @b $ backendPrefix @b @@ -262,8 +266,8 @@ singleOrMultipleRelColumnsCodec :: Text -> AC.JSONCodec ( Either - (TableName b, Column b) - (TableName b, NonEmpty (Column b)) + (TableName b, ColumnPath b) + (TableName b, NonEmpty (ColumnPath b)) ) singleOrMultipleRelColumnsCodec codecName = disjointEitherCodec @@ -310,7 +314,7 @@ instance (Backend b) => FromJSON (ObjRelUsingChoice b) where pure $ RemoteTable table cols val -> SameTable <$> parseColumns val where - parseColumns :: Value -> Parser (NonEmpty (Column b)) + parseColumns :: Value -> Parser (NonEmpty (ColumnPath b)) parseColumns = \case v@(String _) -> pure <$> parseJSON v v@(Array _) -> parseJSON v @@ -321,12 +325,12 @@ instance (Backend b) => HasCodec (ArrRelUsingFKeyOn b) where dimapCodec dec enc $ singleOrMultipleRelColumnsCodec @b (backendPrefix @b <> "ArrRelUsingFKeyOn") where - dec :: (Either (TableName b, Column b) (TableName b, NonEmpty (Column b))) -> ArrRelUsingFKeyOn b + dec :: (Either (TableName b, ColumnPath b) (TableName b, NonEmpty (ColumnPath b))) -> ArrRelUsingFKeyOn b dec = \case Left (qt, col) -> ArrRelUsingFKeyOn qt (pure col) Right (qt, cols) -> ArrRelUsingFKeyOn qt cols - enc :: ArrRelUsingFKeyOn b -> (Either (TableName b, Column b) (TableName b, NonEmpty (Column b))) + enc :: ArrRelUsingFKeyOn b -> (Either (TableName b, ColumnPath b) (TableName b, NonEmpty (ColumnPath b))) enc = \case ArrRelUsingFKeyOn qt (col :| []) -> Left (qt, col) ArrRelUsingFKeyOn qt cols -> Right (qt, cols) @@ -352,7 +356,7 @@ instance (Backend b) => FromJSON (ArrRelUsingFKeyOn b) where pure $ ArrRelUsingFKeyOn table cols _ -> fail "Expecting object { table, columns }." where - parseColumns :: Value -> Parser (NonEmpty (Column b)) + parseColumns :: Value -> Parser (NonEmpty (ColumnPath b)) parseColumns = \case v@(String _) -> pure <$> parseJSON v v@(Array _) -> parseJSON v @@ -391,10 +395,48 @@ instance (Backend b) => ToJSON (RelTarget b) where --- +newtype RelMapping (b :: BackendType) = RelMapping {unRelMapping :: HashMap (ColumnPath b) (ColumnPath b)} + deriving (Generic) + +deriving instance (Backend b) => Show (RelMapping b) + +deriving instance (Backend b) => Eq (RelMapping b) + +deriving instance (Backend b) => Ord (RelMapping b) + +deriving via AC.Autodocodec (RelMapping b) instance (Backend b) => FromJSON (RelMapping b) + +deriving via AC.Autodocodec (RelMapping b) instance (Backend b) => ToJSON (RelMapping b) + +instance (Backend b) => NFData (RelMapping b) + +instance (Backend b) => Hashable (RelMapping b) + +-- If all keys in the RelMapping have a singleton ColumnPath then we want to +-- want to represent the mapping as a JSON object with the source columns as properties +-- (using `codec @(HashMap (Column b) (ColumnPath b))`). +-- This is required for metadata and API backwards compatibilty. +-- If some of the keys are not singletons then we can't use a JSON object so we use +-- an array of pairs instead, as provided by `codec @(HashMap (ColumnPath b) (ColumnPath b))` +instance (Backend b) => HasCodec (RelMapping b) where + codec = + AC.matchChoiceCodec + (AC.dimapCodec RelMapping unRelMapping $ codec @(HashMap (ColumnPath b) (ColumnPath b))) + (AC.dimapCodec toRelMapping id $ codec @(HashMap (Column b) (ColumnPath b))) + \m -> maybeToEither m (tryFromRelMapping m) + where + toRelMapping :: HashMap (Column b) (ColumnPath b) -> RelMapping b + toRelMapping = RelMapping . HashMap.mapKeys Witch.from + + tryFromRelMapping :: RelMapping b -> Maybe (HashMap (Column b) (ColumnPath b)) + tryFromRelMapping = fmap HashMap.fromList . traverse (bitraverse (tryColumnPathToColumn @b) pure) . HashMap.toList . unRelMapping + +--- + data RelInfo (b :: BackendType) = RelInfo { riName :: RelName, riType :: RelType, - riMapping :: HashMap (Column b) (Column b), + riMapping :: RelMapping b, riTarget :: RelTarget b, riIsManual :: Bool, riInsertOrder :: InsertOrder diff --git a/server/src-lib/Hasura/RQL/Types/Source/Table.hs b/server/src-lib/Hasura/RQL/Types/Source/Table.hs index 293af9289a4..4114e50ac11 100644 --- a/server/src-lib/Hasura/RQL/Types/Source/Table.hs +++ b/server/src-lib/Hasura/RQL/Types/Source/Table.hs @@ -38,6 +38,7 @@ import Data.Text (Text) import GHC.Generics (Generic) import Hasura.LogicalModel.Metadata import Hasura.RQL.Types.Backend (Backend (..), ConstraintName) +import Hasura.RQL.Types.Relationships.Local (RelMapping (..)) import Hasura.RQL.Types.Source.Column (SourceColumnInfo) import Hasura.RQL.Types.Source.TableType import Prelude @@ -102,7 +103,7 @@ instance (Backend b) => HasCodec (SourceForeignKeys b) where data SourceConstraint b = SourceConstraint { _scForeignTable :: TableName b, - _scColumnMapping :: HashMap (Column b) (Column b) + _scColumnMapping :: RelMapping b } deriving stock (Generic) deriving anyclass (Hashable) diff --git a/server/src-lib/Hasura/Table/Cache.hs b/server/src-lib/Hasura/Table/Cache.hs index 8bbed26b025..7734b29bcaf 100644 --- a/server/src-lib/Hasura/Table/Cache.hs +++ b/server/src-lib/Hasura/Table/Cache.hs @@ -984,7 +984,7 @@ instance (Backend b) => FromJSON (UniqueConstraint b) where data ForeignKey (b :: BackendType) = ForeignKey { _fkConstraint :: Constraint b, _fkForeignTable :: TableName b, - _fkColumnMapping :: NEHashMap (Column b) (Column b) + _fkColumnMapping :: NEHashMap (ColumnPath b) (ColumnPath b) } deriving (Generic) diff --git a/server/src-test/Hasura/Backends/DataConnector/API/V0/ColumnSpec.hs b/server/src-test/Hasura/Backends/DataConnector/API/V0/ColumnSpec.hs index b51878852c4..a52de6b1da7 100644 --- a/server/src-test/Hasura/Backends/DataConnector/API/V0/ColumnSpec.hs +++ b/server/src-test/Hasura/Backends/DataConnector/API/V0/ColumnSpec.hs @@ -1,7 +1,7 @@ {-# LANGUAGE OverloadedLists #-} {-# LANGUAGE QuasiQuotes #-} -module Hasura.Backends.DataConnector.API.V0.ColumnSpec (spec, genColumnName, genColumnType, genColumnInfo, genColumnValueGenerationStrategy) where +module Hasura.Backends.DataConnector.API.V0.ColumnSpec (spec, genColumnName, genColumnSelector, genColumnType, genColumnInfo, genColumnValueGenerationStrategy) where import Data.Aeson.QQ.Simple (aesonQQ) import Hasura.Backends.DataConnector.API.V0 @@ -19,6 +19,12 @@ spec = do describe "ColumnName" $ do testToFromJSONToSchema (ColumnName "my_column_name") [aesonQQ|"my_column_name"|] jsonOpenApiProperties genColumnName + describe "ColumnSelector" $ do + describe "single column selector" + $ testToFromJSONToSchema (ColumnSelectorColumn $ ColumnName "foo") [aesonQQ|"foo"|] + describe "nested path selector" + $ testToFromJSONToSchema (ColumnSelectorPath [ColumnName "foo", ColumnName "bar"]) [aesonQQ|["foo","bar"]|] + jsonOpenApiProperties genColumnSelector describe "ColumnInfo" $ do describe "minimal" $ testFromJSON @@ -56,6 +62,13 @@ spec = do genColumnName :: (MonadGen m) => m ColumnName genColumnName = ColumnName <$> genArbitraryAlphaNumText defaultRange +genColumnSelector :: (MonadGen m) => m ColumnSelector +genColumnSelector = + Gen.choice + [ ColumnSelectorPath <$> Gen.nonEmpty defaultRange genColumnName, + ColumnSelectorColumn <$> genColumnName + ] + genColumnType :: Gen ColumnType genColumnType = Gen.choice diff --git a/server/src-test/Hasura/Backends/DataConnector/API/V0/ExpressionSpec.hs b/server/src-test/Hasura/Backends/DataConnector/API/V0/ExpressionSpec.hs index c20d85eab6d..f900eb00cf0 100644 --- a/server/src-test/Hasura/Backends/DataConnector/API/V0/ExpressionSpec.hs +++ b/server/src-test/Hasura/Backends/DataConnector/API/V0/ExpressionSpec.hs @@ -10,14 +10,13 @@ module Hasura.Backends.DataConnector.API.V0.ExpressionSpec genExpression, genRedactionExpressionName, genTargetRedactionExpressions, - genColumnSelector, ) where import Data.Aeson 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.ColumnSpec (genColumnSelector) 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) @@ -84,13 +83,6 @@ spec = do $ testToFromJSONToSchema CurrentTable [aesonQQ|[]|] jsonOpenApiProperties genColumnPath - describe "ColumnSelector" $ do - describe "single column selector" - $ testToFromJSONToSchema (ColumnSelector [ColumnName "foo"]) [aesonQQ|"foo"|] - describe "nested path selector" - $ testToFromJSONToSchema (ColumnSelector [ColumnName "foo", ColumnName "bar"]) [aesonQQ|["foo","bar"]|] - jsonOpenApiProperties genColumnSelector - describe "ComparisonValue" $ do describe "AnotherColumnComparison" $ testToFromJSONToSchema @@ -290,10 +282,6 @@ genColumnPath :: (MonadGen m) => m ColumnPath genColumnPath = Gen.element [CurrentTable, QueryTable] -genColumnSelector :: (MonadGen m) => m ColumnSelector -genColumnSelector = - ColumnSelector <$> Gen.nonEmpty defaultRange genColumnName - genComparisonValue :: (MonadGen m, GenBase m ~ Identity) => m ComparisonValue genComparisonValue = Gen.choice diff --git a/server/src-test/Hasura/Backends/DataConnector/API/V0/OrderBySpec.hs b/server/src-test/Hasura/Backends/DataConnector/API/V0/OrderBySpec.hs index 4ec813fe8c0..71817d29cc4 100644 --- a/server/src-test/Hasura/Backends/DataConnector/API/V0/OrderBySpec.hs +++ b/server/src-test/Hasura/Backends/DataConnector/API/V0/OrderBySpec.hs @@ -12,7 +12,8 @@ import Data.Aeson.QQ.Simple (aesonQQ) 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.ExpressionSpec (genColumnSelector, genExpression, genRedactionExpressionName) +import Hasura.Backends.DataConnector.API.V0.ColumnSpec (genColumnSelector) +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 diff --git a/server/src-test/Hasura/Backends/DataConnector/API/V0/RelationshipsSpec.hs b/server/src-test/Hasura/Backends/DataConnector/API/V0/RelationshipsSpec.hs index ecdeda040ed..67ab59b4788 100644 --- a/server/src-test/Hasura/Backends/DataConnector/API/V0/RelationshipsSpec.hs +++ b/server/src-test/Hasura/Backends/DataConnector/API/V0/RelationshipsSpec.hs @@ -12,7 +12,7 @@ where import Data.Aeson.QQ.Simple (aesonQQ) import Data.HashMap.Strict qualified as HashMap import Hasura.Backends.DataConnector.API.V0 -import Hasura.Backends.DataConnector.API.V0.ColumnSpec (genColumnName) +import Hasura.Backends.DataConnector.API.V0.ColumnSpec (genColumnSelector) import Hasura.Backends.DataConnector.API.V0.TableSpec (genTableName, genTableTarget) import Hasura.Generator.Common (defaultRange, genArbitraryAlphaNumText) import Hasura.Prelude @@ -38,7 +38,7 @@ spec = do Relationship { _rTarget = TTable (TargetTable (TableName ["target_table_name"])), _rRelationshipType = ObjectRelationship, - _rColumnMapping = [(ColumnName "outer_column", ColumnName "inner_column")] + _rColumnMapping = ColumnPathMapping [(mkColumnSelector $ ColumnName "outer_column", mkColumnSelector $ ColumnName "inner_column")] } testToFromJSONToSchema relationship @@ -51,18 +51,20 @@ spec = do } |] jsonOpenApiProperties genRelationship + describe "ColumnPathMapping" + $ jsonOpenApiProperties genColumnPathMapping describe "TableRelationships" $ do let relationshipA = Relationship { _rTarget = TTable (TargetTable (TableName ["target_table_name_a"])), _rRelationshipType = ObjectRelationship, - _rColumnMapping = [(ColumnName "outer_column_a", ColumnName "inner_column_a")] + _rColumnMapping = ColumnPathMapping [(mkColumnSelector $ ColumnName "outer_column_a", mkColumnSelector $ ColumnName "inner_column_a")] } let relationshipB = Relationship { _rTarget = TTable (TargetTable (TableName ["target_table_name_b"])), _rRelationshipType = ArrayRelationship, - _rColumnMapping = [(ColumnName "outer_column_b", ColumnName "inner_column_b")] + _rColumnMapping = ColumnPathMapping [(mkColumnSelector $ ColumnName "outer_column_b", mkColumnSelector $ ColumnName "inner_column_b")] } let tableRelationships = TableRelationships @@ -109,7 +111,7 @@ genRelationship = Relationship <$> genTableTarget <*> genRelationshipType - <*> (HashMap.fromList <$> Gen.list defaultRange ((,) <$> genColumnName <*> genColumnName)) + <*> genColumnPathMapping genRelationships :: Gen Relationships genRelationships = (RTable <$> genTableRelationships) <|> (RFunction <$> genFunctionRelationships) @@ -128,3 +130,6 @@ genFunctionRelationships = genFunctionName :: (MonadGen m) => m FunctionName genFunctionName = FunctionName <$> Gen.nonEmpty (linear 1 3) (genArbitraryAlphaNumText defaultRange) + +genColumnPathMapping :: (MonadGen m) => m ColumnPathMapping +genColumnPathMapping = ColumnPathMapping . HashMap.fromList <$> Gen.list defaultRange ((,) <$> genColumnSelector <*> genColumnSelector) diff --git a/server/src-test/Hasura/Backends/DataConnector/API/V0/TableSpec.hs b/server/src-test/Hasura/Backends/DataConnector/API/V0/TableSpec.hs index 8ca2cc0e53f..dbec2852aab 100644 --- a/server/src-test/Hasura/Backends/DataConnector/API/V0/TableSpec.hs +++ b/server/src-test/Hasura/Backends/DataConnector/API/V0/TableSpec.hs @@ -6,7 +6,7 @@ module Hasura.Backends.DataConnector.API.V0.TableSpec (spec, genTableName, genTa import Data.Aeson.QQ.Simple (aesonQQ) import Data.HashMap.Strict qualified as HashMap import Hasura.Backends.DataConnector.API.V0 -import Hasura.Backends.DataConnector.API.V0.ColumnSpec (genColumnInfo, genColumnName) +import Hasura.Backends.DataConnector.API.V0.ColumnSpec (genColumnInfo, genColumnName, genColumnSelector) import Hasura.Generator.Common import Hasura.Prelude import Hedgehog @@ -60,7 +60,14 @@ spec = do Table [ColumnInfo (ColumnName "id") (ColumnTypeScalar $ ScalarType "string") False Nothing False False Nothing] (Just $ ColumnName "id" :| []) - (ForeignKeys $ HashMap.singleton (ConstraintName "Artist") (Constraint (TableName ["artist_table"]) (HashMap.singleton (ColumnName "ArtistId") (ColumnName "ArtistId")))) + ( ForeignKeys + $ HashMap.singleton + (ConstraintName "Artist") + ( Constraint + (TableName ["artist_table"]) + (ColumnPathMapping $ HashMap.singleton (mkColumnSelector $ ColumnName "ArtistId") (mkColumnSelector $ ColumnName "ArtistId")) + ) + ) (Just "my description") False False @@ -101,7 +108,7 @@ genConstraintName = ConstraintName <$> genArbitraryAlphaNumText defaultRange genConstraint :: (MonadGen m) => m Constraint genConstraint = - let mapping = genHashMap genColumnName genColumnName defaultRange + let mapping = ColumnPathMapping <$> genHashMap genColumnSelector genColumnSelector defaultRange in Constraint <$> genTableName <*> mapping genTableType :: (MonadGen m) => m TableType diff --git a/server/src-test/Hasura/Backends/Postgres/RQLGenerator/GenSelectArgsG.hs b/server/src-test/Hasura/Backends/Postgres/RQLGenerator/GenSelectArgsG.hs index 0076933b02e..900c8d5959f 100644 --- a/server/src-test/Hasura/Backends/Postgres/RQLGenerator/GenSelectArgsG.hs +++ b/server/src-test/Hasura/Backends/Postgres/RQLGenerator/GenSelectArgsG.hs @@ -47,6 +47,7 @@ genSelectArgsG genA = do Gen.maybe $ genAnnBoolExp ( genAnnBoolExpFld + genColumn genColumn genTableName genScalarType @@ -66,6 +67,7 @@ genSelectArgsG genA = do genBasicOrderType genNullsOrderType ( genAnnotatedOrderByElement @_ @('Postgres 'Vanilla) + genColumn genColumn genTableName genScalarType @@ -89,6 +91,7 @@ genSelectArgsG genA = do $ AnnDistinctColumn <$> genColumn <*> genAnnRedactionExp + genColumn genColumn genTableName genScalarType diff --git a/server/src-test/Hasura/Backends/Postgres/RQLGenerator/GenTablePermG.hs b/server/src-test/Hasura/Backends/Postgres/RQLGenerator/GenTablePermG.hs index 4a60790e1c6..bbbaa491d6e 100644 --- a/server/src-test/Hasura/Backends/Postgres/RQLGenerator/GenTablePermG.hs +++ b/server/src-test/Hasura/Backends/Postgres/RQLGenerator/GenTablePermG.hs @@ -24,7 +24,7 @@ import Hedgehog.Gen qualified as Gen genTablePermG :: (MonadGen m) => m a -> m (TablePermG ('Postgres 'Vanilla) a) genTablePermG genA = do - let genV = genAnnBoolExpFld @_ @('Postgres 'Vanilla) genColumn genTableName genScalarType genFunctionName genXComputedField (genBooleanOperators genA) (genFunctionArgumentExp genA) genA + let genV = genAnnBoolExpFld @_ @('Postgres 'Vanilla) genColumn genColumn genTableName genScalarType genFunctionName genXComputedField (genBooleanOperators genA) (genFunctionArgumentExp genA) genA gBoolExp <- genAnnBoolExp @_ @_ @('Postgres 'Vanilla) genV genTableName limit <- Gen.maybe (Gen.integral defaultRange) pure $ TablePerm gBoolExp limit diff --git a/server/src-test/Hasura/GraphQL/Schema/BoolExp/AggregationPredicatesSpec.hs b/server/src-test/Hasura/GraphQL/Schema/BoolExp/AggregationPredicatesSpec.hs index 6f9d4d4c4c0..6e340925ef1 100644 --- a/server/src-test/Hasura/GraphQL/Schema/BoolExp/AggregationPredicatesSpec.hs +++ b/server/src-test/Hasura/GraphQL/Schema/BoolExp/AggregationPredicatesSpec.hs @@ -29,7 +29,7 @@ import Hasura.RQL.Types.BackendType (BackendSourceKind (PostgresVanillaKind), Ba import Hasura.RQL.Types.Column (ColumnType (ColumnScalar), ColumnValue (..)) import Hasura.RQL.Types.Common (InsertOrder (..), RelName (..), RelType (..), SourceName (..)) import Hasura.RQL.Types.NamingCase (NamingCase (..)) -import Hasura.RQL.Types.Relationships.Local (RelInfo (..), RelTarget (..)) +import Hasura.RQL.Types.Relationships.Local (RelInfo (..), RelMapping (..), RelTarget (..)) import Hasura.RQL.Types.Schema.Options qualified as Options import Hasura.RQL.Types.Source (DBObjectsIntrospection (..), SourceInfo (..)) import Hasura.RQL.Types.SourceCustomization (ResolvedSourceCustomization (..)) @@ -307,7 +307,7 @@ spec = do RelInfo { riName = RelName [nonEmptyTextQQ|tracks|], riType = ArrRel, - riMapping = HashMap.fromList [("id", "album_id")], + riMapping = RelMapping $ HashMap.fromList [("id", "album_id")], riTarget = RelTargetTable (mkTable "track"), riIsManual = False, riInsertOrder = AfterParent diff --git a/server/src-test/Hasura/RQL/IR/Generator.hs b/server/src-test/Hasura/RQL/IR/Generator.hs index c16c11b47fb..1852fbb173d 100644 --- a/server/src-test/Hasura/RQL/IR/Generator.hs +++ b/server/src-test/Hasura/RQL/IR/Generator.hs @@ -62,7 +62,9 @@ genAnnRedactionExp :: (MonadGen m) => (Hashable (ScalarType b)) => (Hashable (Column b)) => + (Hashable (ColumnPath b)) => m (Column b) -> + m (ColumnPath b) -> m (TableName b) -> m (ScalarType b) -> m (FunctionName b) -> @@ -73,6 +75,7 @@ genAnnRedactionExp :: m (AnnRedactionExp b a) genAnnRedactionExp genColumn + genColumnPath genTableName genScalarType genFunctionName @@ -87,6 +90,7 @@ genAnnRedactionExp <$> genAnnBoolExp ( genAnnBoolExpFld genColumn + genColumnPath genTableName genScalarType genFunctionName @@ -129,7 +133,9 @@ genAnnBoolExpFld :: (MonadGen m) => (Hashable (ScalarType b)) => (Hashable (Column b)) => + (Hashable (ColumnPath b)) => m (Column b) -> + m (ColumnPath b) -> m (TableName b) -> m (ScalarType b) -> m (FunctionName b) -> @@ -140,6 +146,7 @@ genAnnBoolExpFld :: m (AnnBoolExpFld b a) genAnnBoolExpFld genColumn + genColumnPath genTableName genScalarType genFunctionName @@ -157,6 +164,7 @@ genAnnBoolExpFld genScalarType <*> genAnnRedactionExp genColumn + genColumnPath genTableName genScalarType genFunctionName @@ -175,11 +183,12 @@ genAnnBoolExpFld ) relationship = AVRelationship - <$> genRelInfo genTableName genColumn + <$> genRelInfo genTableName genColumnPath <*> ( RelationshipFilters <$> genAnnBoolExp ( genAnnBoolExpFld genColumn + genColumnPath genTableName genScalarType genFunctionName @@ -192,6 +201,7 @@ genAnnBoolExpFld <*> genAnnBoolExp ( genAnnBoolExpFld genColumn + genColumnPath genTableName genScalarType genFunctionName @@ -207,6 +217,7 @@ genAnnBoolExpFld <$> genAnnComputedFieldBolExp genTableName genColumn + genColumnPath genScalarType genBooleanOperators genXComputedField @@ -216,15 +227,15 @@ genAnnBoolExpFld genRelInfo :: (MonadGen m) => - (Hashable (Column b)) => + (Hashable (ColumnPath b)) => m (TableName b) -> - m (Column b) -> + m (ColumnPath b) -> m (RelInfo b) genRelInfo genTableName genColumn = RelInfo <$> genRelName <*> genRelType - <*> genHashMap genColumn genColumn defaultRange + <*> (RelMapping <$> genHashMap genColumn genColumn defaultRange) <*> genRelTarget genTableName <*> bool_ <*> genInsertOrder @@ -252,8 +263,10 @@ genAnnComputedFieldBolExp :: (MonadGen m) => (Hashable (ScalarType b)) => (Hashable (Column b)) => + (Hashable (ColumnPath b)) => m (TableName b) -> m (Column b) -> + m (ColumnPath b) -> m (ScalarType b) -> m (BooleanOperators b a) -> m (XComputedField b) -> @@ -264,6 +277,7 @@ genAnnComputedFieldBolExp :: genAnnComputedFieldBolExp genTableName genColumn + genColumnPath genScalarType genBooleanOperators genXComputedField @@ -278,6 +292,7 @@ genAnnComputedFieldBolExp <*> genComputedFieldBoolExp genTableName genColumn + genColumnPath genScalarType genFunctionName genXComputedField @@ -289,8 +304,10 @@ genComputedFieldBoolExp :: (MonadGen m) => (Hashable (ScalarType b)) => (Hashable (Column b)) => + (Hashable (ColumnPath b)) => m (TableName b) -> m (Column b) -> + m (ColumnPath b) -> m (ScalarType b) -> m (FunctionName b) -> m (XComputedField b) -> @@ -301,6 +318,7 @@ genComputedFieldBoolExp :: genComputedFieldBoolExp genTableName genColumn + genColumnPath genScalarType genFunctionName genXComputedField @@ -311,6 +329,7 @@ genComputedFieldBoolExp [ CFBEScalar <$> genAnnRedactionExp genColumn + genColumnPath genTableName genScalarType genFunctionName @@ -332,6 +351,7 @@ genComputedFieldBoolExp <*> genAnnBoolExp ( genAnnBoolExpFld genColumn + genColumnPath genTableName genScalarType genFunctionName @@ -492,7 +512,9 @@ genAnnotatedOrderByElement :: (MonadGen m) => (Hashable (ScalarType b)) => (Hashable (Column b)) => + (Hashable (ColumnPath b)) => m (Column b) -> + m (ColumnPath b) -> m (TableName b) -> m (ScalarType b) -> m (FunctionName b) -> @@ -503,6 +525,7 @@ genAnnotatedOrderByElement :: m (AnnotatedOrderByElement b a) genAnnotatedOrderByElement genColumn + genColumnPath genTableName genScalarType genFunctionName @@ -525,6 +548,7 @@ genAnnotatedOrderByElement genScalarType <*> genAnnRedactionExp genColumn + genColumnPath genTableName genScalarType genFunctionName @@ -534,10 +558,11 @@ genAnnotatedOrderByElement genA objectRelation = AOCObjectRelation - <$> genRelInfo genTableName genColumn + <$> genRelInfo genTableName genColumnPath <*> genAnnBoolExp ( genAnnBoolExpFld genColumn + genColumnPath genTableName genScalarType genFunctionName @@ -549,6 +574,7 @@ genAnnotatedOrderByElement genTableName <*> genAnnotatedOrderByElement genColumn + genColumnPath genTableName genScalarType genFunctionName @@ -558,10 +584,11 @@ genAnnotatedOrderByElement genA arrayAggregation = AOCArrayAggregation - <$> genRelInfo genTableName genColumn + <$> genRelInfo genTableName genColumnPath <*> genAnnBoolExp ( genAnnBoolExpFld genColumn + genColumnPath genTableName genScalarType genFunctionName @@ -573,6 +600,7 @@ genAnnotatedOrderByElement genTableName <*> genAnnotatedAggregateOrderBy genColumn + genColumnPath genTableName genScalarType genFunctionName @@ -584,6 +612,7 @@ genAnnotatedOrderByElement AOCComputedField <$> genComputedFieldOrderBy genColumn + genColumnPath genScalarType genTableName genFunctionName @@ -596,7 +625,9 @@ genAnnotatedAggregateOrderBy :: (MonadGen m) => (Hashable (ScalarType b)) => (Hashable (Column b)) => + (Hashable (ColumnPath b)) => m (Column b) -> + m (ColumnPath b) -> m (TableName b) -> m (ScalarType b) -> m (FunctionName b) -> @@ -607,6 +638,7 @@ genAnnotatedAggregateOrderBy :: m (AnnotatedAggregateOrderBy b a) genAnnotatedAggregateOrderBy genColumn + genColumnPath genTableName genScalarType genFunctionName @@ -626,6 +658,7 @@ genAnnotatedAggregateOrderBy genScalarType <*> genAnnRedactionExp genColumn + genColumnPath genTableName genScalarType genFunctionName @@ -640,7 +673,9 @@ genComputedFieldOrderBy :: (MonadGen m) => (Hashable (ScalarType b)) => (Hashable (Column b)) => + (Hashable (ColumnPath b)) => m (Column b) -> + m (ColumnPath b) -> m (ScalarType b) -> m (TableName b) -> m (FunctionName b) -> @@ -651,6 +686,7 @@ genComputedFieldOrderBy :: m (ComputedFieldOrderBy b a) genComputedFieldOrderBy genColumn + genColumnPath genScalarType genTableName genFunctionName @@ -665,6 +701,7 @@ genComputedFieldOrderBy <*> genFunctionArgsExpG genFunctionArgumentExp <*> genComputedFieldOrderByElement genColumn + genColumnPath genScalarType genTableName genFunctionName @@ -677,7 +714,9 @@ genComputedFieldOrderByElement :: (MonadGen m) => (Hashable (ScalarType b)) => (Hashable (Column b)) => + (Hashable (ColumnPath b)) => m (Column b) -> + m (ColumnPath b) -> m (ScalarType b) -> m (TableName b) -> m (FunctionName b) -> @@ -688,6 +727,7 @@ genComputedFieldOrderByElement :: m (ComputedFieldOrderByElement b a) genComputedFieldOrderByElement genColumn + genColumnPath genScalarType genTableName genFunctionName @@ -700,6 +740,7 @@ genComputedFieldOrderByElement <$> genScalarType <*> genAnnRedactionExp genColumn + genColumnPath genTableName genScalarType genFunctionName @@ -712,6 +753,7 @@ genComputedFieldOrderByElement <*> genAnnBoolExp ( genAnnBoolExpFld genColumn + genColumnPath genTableName genScalarType genFunctionName @@ -723,6 +765,7 @@ genComputedFieldOrderByElement genTableName <*> genAnnotatedAggregateOrderBy genColumn + genColumnPath genTableName genScalarType genFunctionName