mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-13 19:33:55 +03:00
server: Support the namespacing of table names in Data Connectors
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/5329 GitOrigin-RevId: 5cf492bc2b09fef6250f4dd50f74f750f55ebe6a
This commit is contained in:
parent
bbafd0339c
commit
c209b60239
@ -40,13 +40,13 @@ POST /v1/metadata
|
||||
"kind": "reference",
|
||||
"tables": [
|
||||
{
|
||||
"table": "Album",
|
||||
"table": ["Album"],
|
||||
"object_relationships": [
|
||||
{
|
||||
"name": "Artist",
|
||||
"using": {
|
||||
"manual_configuration": {
|
||||
"remote_table": "Artist",
|
||||
"remote_table": ["Artist"],
|
||||
"column_mapping": {
|
||||
"ArtistId": "ArtistId"
|
||||
}
|
||||
@ -56,13 +56,13 @@ POST /v1/metadata
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "Artist",
|
||||
"table": ["Artist"],
|
||||
"array_relationships": [
|
||||
{
|
||||
"name": "Album",
|
||||
"using": {
|
||||
"manual_configuration": {
|
||||
"remote_table": "Album",
|
||||
"remote_table": ["Album"],
|
||||
"column_mapping": {
|
||||
"ArtistId": "ArtistId"
|
||||
}
|
||||
@ -73,7 +73,9 @@ POST /v1/metadata
|
||||
}
|
||||
],
|
||||
"configuration": {
|
||||
"tables": [ "Artist", "Album" ]
|
||||
"value": {
|
||||
"tables": [ "Artist", "Album" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -165,7 +167,7 @@ The `GET /schema` endpoint is called whenever the metadata is (re)loaded by `gra
|
||||
{
|
||||
"tables": [
|
||||
{
|
||||
"name": "Artist",
|
||||
"name": ["Artist"],
|
||||
"primary_key": ["ArtistId"],
|
||||
"description": "Collection of artists of music",
|
||||
"columns": [
|
||||
@ -184,7 +186,7 @@ The `GET /schema` endpoint is called whenever the metadata is (re)loaded by `gra
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Album",
|
||||
"name": ["Album"],
|
||||
"primary_key": ["AlbumId"],
|
||||
"description": "Collection of music albums created by artists",
|
||||
"columns": [
|
||||
@ -216,6 +218,8 @@ The `tables` section describes the two available tables, as well as their column
|
||||
|
||||
Notice that the names of tables and columns are used in the metadata document to describe tracked tables and relationships.
|
||||
|
||||
Table names are described as an array of strings. This allows agents to fully qualify their table names with whatever namespacing requirements they have. For example, if the agent connects to a database that puts tables inside schemas, the agent could use table names such as `["my_schema", "my_table"]`.
|
||||
|
||||
#### Type definitions
|
||||
|
||||
The `SchemaResponse` TypeScript type from [the reference implementation](./reference/src/types/index.ts) describes the valid response body for the `GET /schema` endpoint.
|
||||
@ -239,7 +243,7 @@ and here is the resulting query request payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"table": "Artist",
|
||||
"table": ["Artist"],
|
||||
"table_relationships": [],
|
||||
"query": {
|
||||
"where": {
|
||||
@ -267,7 +271,7 @@ The implementation of the service is responsible for intepreting this data struc
|
||||
|
||||
Let's break down the request:
|
||||
|
||||
- The `table` field tells us which table to fetch the data from, namely the `Artist` table.
|
||||
- The `table` field tells us which table to fetch the data from, namely the `Artist` table. The table name (ie. the array of strings) must be one that was returned previously by the `/schema` endpoint.
|
||||
- The `table_relationships` field that lists any relationships used to join between tables in the query. This query does not use any relationships, so this is just an empty list here.
|
||||
- The `query` field contains further information about how to query the specified table:
|
||||
- The `where` field tells us that there is currently no (interesting) predicate being applied to the rows of the data set (just an empty conjunction, which ought to return every row).
|
||||
@ -458,13 +462,13 @@ This will generate the following JSON query if the agent supports relationships:
|
||||
|
||||
```json
|
||||
{
|
||||
"table": "Artist",
|
||||
"table": ["Artist"],
|
||||
"table_relationships": [
|
||||
{
|
||||
"source_table": "Artist",
|
||||
"source_table": ["Artist"],
|
||||
"relationships": {
|
||||
"ArtistAlbums": {
|
||||
"target_table": "Album",
|
||||
"target_table": ["Album"],
|
||||
"relationship_type": "array",
|
||||
"column_mapping": {
|
||||
"ArtistId": "ArtistId"
|
||||
@ -491,7 +495,6 @@ This will generate the following JSON query if the agent supports relationships:
|
||||
"type": "and"
|
||||
},
|
||||
"offset": null,
|
||||
"from": "albums",
|
||||
"order_by": [],
|
||||
"limit": null,
|
||||
"fields": {
|
||||
@ -523,7 +526,6 @@ Note the `Albums` field in particular, which traverses the `Artists` -> `Albums`
|
||||
"type": "and"
|
||||
},
|
||||
"offset": null,
|
||||
"from": "albums",
|
||||
"order_by": [],
|
||||
"limit": null,
|
||||
"fields": {
|
||||
@ -602,13 +604,13 @@ POST /v1/metadata
|
||||
"kind": "reference",
|
||||
"tables": [
|
||||
{
|
||||
"table": "Customer",
|
||||
"table": ["Customer"],
|
||||
"object_relationships": [
|
||||
{
|
||||
"name": "SupportRep",
|
||||
"using": {
|
||||
"manual_configuration": {
|
||||
"remote_table": "Employee",
|
||||
"remote_table": ["Employee"],
|
||||
"column_mapping": {
|
||||
"SupportRepId": "EmployeeId"
|
||||
}
|
||||
@ -639,7 +641,7 @@ POST /v1/metadata
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "Employee"
|
||||
"table": ["Employee"]
|
||||
}
|
||||
],
|
||||
"configuration": {}
|
||||
@ -668,13 +670,13 @@ We would get the following query request JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"table": "Customer",
|
||||
"table": ["Customer"],
|
||||
"table_relationships": [
|
||||
{
|
||||
"source_table": "Customer",
|
||||
"source_table": ["Customer"],
|
||||
"relationships": {
|
||||
"SupportRep": {
|
||||
"target_table": "Employee",
|
||||
"target_table": ["Employee"],
|
||||
"relationship_type": "object",
|
||||
"column_mapping": {
|
||||
"SupportRepId": "EmployeeId"
|
||||
@ -753,7 +755,7 @@ This would cause the following query request to be performed:
|
||||
|
||||
```json
|
||||
{
|
||||
"table": "Artist",
|
||||
"table": ["Artist"],
|
||||
"table_relationships": [],
|
||||
"query": {
|
||||
"aggregates": {
|
||||
@ -796,7 +798,7 @@ query {
|
||||
|
||||
```json
|
||||
{
|
||||
"table": "Album",
|
||||
"table": ["Album"],
|
||||
"table_relationships": [],
|
||||
"query": {
|
||||
"aggregates": {
|
||||
@ -848,7 +850,7 @@ The `nodes` part of the query ends up as standard `fields` in the `Query`, and t
|
||||
|
||||
```json
|
||||
{
|
||||
"table": "Artist",
|
||||
"table": ["Artist"],
|
||||
"table_relationships": [],
|
||||
"query": {
|
||||
"aggregates": {
|
||||
@ -917,13 +919,13 @@ This would generate the following `QueryRequest`:
|
||||
|
||||
```json
|
||||
{
|
||||
"table": "Artist",
|
||||
"table": ["Artist"],
|
||||
"table_relationships": [
|
||||
{
|
||||
"source_table": "Artist",
|
||||
"source_table": ["Artist"],
|
||||
"relationships": {
|
||||
"Albums": {
|
||||
"target_table": "Album",
|
||||
"target_table": ["Album"],
|
||||
"relationship_type": "array",
|
||||
"column_mapping": {
|
||||
"ArtistId": "ArtistId"
|
||||
|
@ -17,9 +17,32 @@ This directory contains a barebones implementation of the Data Connector agent s
|
||||
> docker run -it --rm -p 8100:8100 dc-reference-agent:latest
|
||||
```
|
||||
|
||||
# Dataset
|
||||
## Dataset
|
||||
The dataset exposed by the reference agent is sourced from https://github.com/lerocha/chinook-database/
|
||||
|
||||
More specifically, the `ChinookData.xml.gz` file is a GZipped version of https://raw.githubusercontent.com/lerocha/chinook-database/ce27c48d9f375f81b7b68bacdfddf3c4458acc49/ChinookDatabase/DataSources/_Xml/ChinookData.xml
|
||||
|
||||
The `schema-tables.json` is manually derived from the schema of the data as can be seen from the `CREATE TABLE` etc DML statements in the various per-database-vendor SQL scripts that can be found in `/ChinookDatabase/DataSources` in that repo.
|
||||
|
||||
## Configuration
|
||||
The reference agent supports some configuration properties that can be set via the `value` property of `configuration` on a source in Hasura metadata. The configuration is passed to the agent on each request via the `X-Hasura-DataConnector-Config` header.
|
||||
|
||||
The configuration that the reference agent can take supports two properties:
|
||||
|
||||
* `tables`: This is a list of table names that should be exposed by the agent. If omitted all Chinook dataset tables are exposed. If specified, it filters all available table names by the specified list.
|
||||
* `schema`: If specified, this places all the tables within a schema of the specified name. For example, if `schema` is set to `my_schema`, all table names will be namespaced under `my_schema`, for example `["my_schema","Album"]`. If not specified, then tables are not namespaced, for example `["Album"]`.
|
||||
|
||||
Here's an example configuration that only exposes the Artist and Album tables, and namespaces them under `my_schema`:
|
||||
|
||||
```json
|
||||
{
|
||||
"tables": ["Artist", "Album"],
|
||||
"schema": "my_schema"
|
||||
}
|
||||
```
|
||||
|
||||
Here's an example configuration that exposes all tables, un-namespaced:
|
||||
|
||||
```json
|
||||
{}
|
||||
```
|
||||
|
@ -2,7 +2,8 @@ import { FastifyRequest } from "fastify"
|
||||
import { ConfigSchemaResponse } from "./types"
|
||||
|
||||
export type Config = {
|
||||
tables: String[] | null
|
||||
tables: string[] | null
|
||||
schema: string | null
|
||||
}
|
||||
|
||||
export const getConfig = (request: FastifyRequest): Config => {
|
||||
@ -10,7 +11,8 @@ export const getConfig = (request: FastifyRequest): Config => {
|
||||
const rawConfigJson = Array.isArray(configHeader) ? configHeader[0] : configHeader ?? "{}";
|
||||
const config = JSON.parse(rawConfigJson);
|
||||
return {
|
||||
tables: config.tables ?? null
|
||||
tables: config.tables ?? null,
|
||||
schema: config.schema ?? null
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,6 +26,11 @@ export const configSchema: ConfigSchemaResponse = {
|
||||
type: "array",
|
||||
items: { $ref: "#/otherSchemas/TableName" },
|
||||
nullable: true
|
||||
},
|
||||
schema: {
|
||||
description: "Name of the schema to place the tables in. Omit to have no schema for the tables",
|
||||
type: "string",
|
||||
nullable: true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { SchemaResponse } from "../types"
|
||||
import { SchemaResponse, TableName } from "../types"
|
||||
import { Config } from "../config";
|
||||
import xml2js from "xml2js"
|
||||
import fs from "fs"
|
||||
import stream from "stream"
|
||||
import zlib from "zlib"
|
||||
import { parseNumbers } from "xml2js/lib/processors";
|
||||
import { tableNameEquals } from "../util";
|
||||
|
||||
export type StaticData = {
|
||||
[tableName: string]: Record<string, string | number | boolean | null>[]
|
||||
@ -40,16 +41,28 @@ export const loadStaticData = async (): Promise<StaticData> => {
|
||||
return await data as StaticData;
|
||||
}
|
||||
|
||||
export const filterAvailableTables = (staticData: StaticData, config : Config): StaticData => {
|
||||
export const filterAvailableTables = (staticData: StaticData, config: Config): StaticData => {
|
||||
return Object.fromEntries(
|
||||
Object.entries(staticData).filter(([name, _]) => config.tables === null ? true : config.tables.indexOf(name) >= 0)
|
||||
);
|
||||
}
|
||||
|
||||
export const getTable = (staticData: StaticData, config: Config) => (tableName: TableName): Record<string, string | number | boolean | null>[] | undefined => {
|
||||
if (config.schema) {
|
||||
return tableName.length === 2 && tableName[0] === config.schema
|
||||
? staticData[tableName[1]]
|
||||
: undefined;
|
||||
} else {
|
||||
return tableName.length === 1
|
||||
? staticData[tableName[0]]
|
||||
: undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const schema: SchemaResponse = {
|
||||
tables: [
|
||||
{
|
||||
name: "Artist",
|
||||
name: ["Artist"],
|
||||
primary_key: ["ArtistId"],
|
||||
description: "Collection of artists of music",
|
||||
columns: [
|
||||
@ -68,7 +81,7 @@ const schema: SchemaResponse = {
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Album",
|
||||
name: ["Album"],
|
||||
primary_key: ["AlbumId"],
|
||||
description: "Collection of music albums created by artists",
|
||||
columns: [
|
||||
@ -93,7 +106,7 @@ const schema: SchemaResponse = {
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Customer",
|
||||
name: ["Customer"],
|
||||
primary_key: ["CustomerId"],
|
||||
description: "Collection of customers who can buy tracks",
|
||||
columns: [
|
||||
@ -178,7 +191,7 @@ const schema: SchemaResponse = {
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Employee",
|
||||
name: ["Employee"],
|
||||
primary_key: ["EmployeeId"],
|
||||
description: "Collection of employees who work for the business",
|
||||
columns: [
|
||||
@ -275,7 +288,7 @@ const schema: SchemaResponse = {
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Genre",
|
||||
name: ["Genre"],
|
||||
primary_key: ["GenreId"],
|
||||
description: "Genres of music",
|
||||
columns: [
|
||||
@ -294,7 +307,7 @@ const schema: SchemaResponse = {
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Invoice",
|
||||
name: ["Invoice"],
|
||||
primary_key: ["InvoiceId"],
|
||||
description: "Collection of invoices of music purchases by a customer",
|
||||
columns: [
|
||||
@ -355,7 +368,7 @@ const schema: SchemaResponse = {
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "InvoiceLine",
|
||||
name: ["InvoiceLine"],
|
||||
primary_key: ["InvoiceLineId"],
|
||||
description: "Collection of track purchasing line items of invoices",
|
||||
columns: [
|
||||
@ -392,7 +405,7 @@ const schema: SchemaResponse = {
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "MediaType",
|
||||
name: ["MediaType"],
|
||||
primary_key: ["MediaTypeId"],
|
||||
description: "Collection of media types that tracks can be encoded in",
|
||||
columns: [
|
||||
@ -411,7 +424,7 @@ const schema: SchemaResponse = {
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Playlist",
|
||||
name: ["Playlist"],
|
||||
primary_key: ["PlaylistId"],
|
||||
description: "Collection of playlists",
|
||||
columns: [
|
||||
@ -430,7 +443,7 @@ const schema: SchemaResponse = {
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "PlaylistTrack",
|
||||
name: ["PlaylistTrack"],
|
||||
primary_key: ["PlaylistId", "TrackId"],
|
||||
description: "Associations between playlists and tracks",
|
||||
columns: [
|
||||
@ -449,7 +462,7 @@ const schema: SchemaResponse = {
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Track",
|
||||
name: ["Track"],
|
||||
primary_key: ["TrackId"],
|
||||
description: "Collection of music tracks",
|
||||
columns: [
|
||||
@ -513,8 +526,22 @@ const schema: SchemaResponse = {
|
||||
};
|
||||
|
||||
export const getSchema = (config: Config): SchemaResponse => {
|
||||
const prefixSchemaToTableName = (tableName: TableName) =>
|
||||
config.schema
|
||||
? [config.schema, ...tableName]
|
||||
: tableName;
|
||||
|
||||
const filteredTables = schema.tables.filter(table =>
|
||||
config.tables === null ? true : config.tables.map(n => [n]).find(tableNameEquals(table.name)) !== undefined
|
||||
);
|
||||
|
||||
const prefixedTables = filteredTables.map(table => ({
|
||||
...table,
|
||||
name: prefixSchemaToTableName(table.name),
|
||||
}));
|
||||
|
||||
return {
|
||||
...schema,
|
||||
tables: schema.tables.filter(table => config.tables === null ? true : config.tables.indexOf(table.name) >= 0)
|
||||
tables: prefixedTables
|
||||
};
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import Fastify from 'fastify';
|
||||
import FastifyCors from '@fastify/cors';
|
||||
import { filterAvailableTables, getSchema, loadStaticData } from './data';
|
||||
import { filterAvailableTables, getSchema, getTable, loadStaticData } from './data';
|
||||
import { queryData } from './query';
|
||||
import { getConfig } from './config';
|
||||
import { capabilitiesResponse } from './capabilities';
|
||||
@ -34,7 +34,7 @@ server.post<{ Body: QueryRequest, Reply: QueryResponse }>("/query", async (reque
|
||||
server.log.info({ headers: request.headers, query: request.body, }, "query.request");
|
||||
const config = getConfig(request);
|
||||
const data = filterAvailableTables(staticData, config);
|
||||
return queryData(data, request.body);
|
||||
return queryData(getTable(data, config), request.body);
|
||||
});
|
||||
|
||||
server.get("/health", async (request, response) => {
|
||||
|
@ -1,12 +1,7 @@
|
||||
import { QueryRequest, TableRelationships, Relationship, Query, Field, OrderBy, Expression, BinaryComparisonOperator, UnaryComparisonOperator, BinaryArrayComparisonOperator, ComparisonColumn, ComparisonValue, ScalarValue, QueryResponse, Aggregate, SingleColumnAggregate, ColumnCountAggregate } from "./types";
|
||||
import { coerceUndefinedToNull, crossProduct, unreachable, zip } from "./util";
|
||||
import { QueryRequest, TableRelationships, Relationship, Query, Field, OrderBy, Expression, BinaryComparisonOperator, UnaryComparisonOperator, BinaryArrayComparisonOperator, ComparisonColumn, ComparisonValue, ScalarValue, QueryResponse, Aggregate, SingleColumnAggregate, ColumnCountAggregate, TableName } from "./types";
|
||||
import { coerceUndefinedToNull, crossProduct, tableNameEquals, unreachable, zip } from "./util";
|
||||
import * as math from "mathjs";
|
||||
|
||||
type StaticData = {
|
||||
[tableName: string]: Record<string, ScalarValue>[]
|
||||
}
|
||||
|
||||
type TableName = string
|
||||
type RelationshipName = string
|
||||
|
||||
type ProjectedRow = {
|
||||
@ -187,7 +182,7 @@ const paginateRows = (rows: Record<string, ScalarValue>[], offset: number | null
|
||||
};
|
||||
|
||||
const makeFindRelationship = (allTableRelationships: TableRelationships[], tableName: TableName) => (relationshipName: RelationshipName): Relationship => {
|
||||
const relationship = allTableRelationships.find(r => r.source_table === tableName)?.relationships?.[relationshipName];
|
||||
const relationship = allTableRelationships.find(r => tableNameEquals(r.source_table)(tableName))?.relationships?.[relationshipName];
|
||||
if (relationship === undefined)
|
||||
throw `No relationship named ${relationshipName} found for table ${tableName}`;
|
||||
else
|
||||
@ -377,9 +372,9 @@ const calculateAggregates = (rows: Record<string, ScalarValue>[], aggregateReque
|
||||
}));
|
||||
};
|
||||
|
||||
export const queryData = (staticData: StaticData, queryRequest: QueryRequest) => {
|
||||
export const queryData = (getTable: (tableName: TableName) => Record<string, ScalarValue>[] | undefined, queryRequest: QueryRequest) => {
|
||||
const performQuery = (tableName: TableName, query: Query): QueryResponse => {
|
||||
const rows = staticData[tableName];
|
||||
const rows = getTable(tableName);
|
||||
if (rows === undefined) {
|
||||
throw `${tableName} is not a valid table`;
|
||||
}
|
||||
|
@ -524,6 +524,13 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"TableName": {
|
||||
"description": "The fully qualified name of a table, where the last item in the array is the table name and any earlier items represent the namespacing of the table name",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"ScalarType": {
|
||||
"enum": [
|
||||
"string",
|
||||
@ -573,8 +580,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "The name of the table",
|
||||
"type": "string"
|
||||
"$ref": "#/components/schemas/TableName"
|
||||
},
|
||||
"primary_key": {
|
||||
"description": "The primary key of the table",
|
||||
@ -654,8 +660,7 @@
|
||||
"$ref": "#/components/schemas/Query"
|
||||
},
|
||||
"table": {
|
||||
"description": "The name of the table to query",
|
||||
"type": "string"
|
||||
"$ref": "#/components/schemas/TableName"
|
||||
},
|
||||
"table_relationships": {
|
||||
"description": "The relationships between tables involved in the entire query request",
|
||||
@ -692,8 +697,7 @@
|
||||
"$ref": "#/components/schemas/RelationshipType"
|
||||
},
|
||||
"target_table": {
|
||||
"description": "The name of the target table in the relationship",
|
||||
"type": "string"
|
||||
"$ref": "#/components/schemas/TableName"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@ -713,8 +717,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"source_table": {
|
||||
"description": "The name of the source table in the relationship",
|
||||
"type": "string"
|
||||
"$ref": "#/components/schemas/TableName"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -52,5 +52,6 @@ export type { SingleColumnAggregateFunction } from './models/SingleColumnAggrega
|
||||
export type { StarCountAggregate } from './models/StarCountAggregate';
|
||||
export type { SubscriptionCapabilities } from './models/SubscriptionCapabilities';
|
||||
export type { TableInfo } from './models/TableInfo';
|
||||
export type { TableName } from './models/TableName';
|
||||
export type { TableRelationships } from './models/TableRelationships';
|
||||
export type { UnaryComparisonOperator } from './models/UnaryComparisonOperator';
|
||||
|
@ -3,14 +3,12 @@
|
||||
/* eslint-disable */
|
||||
|
||||
import type { Query } from './Query';
|
||||
import type { TableName } from './TableName';
|
||||
import type { TableRelationships } from './TableRelationships';
|
||||
|
||||
export type QueryRequest = {
|
||||
query: Query;
|
||||
/**
|
||||
* The name of the table to query
|
||||
*/
|
||||
table: string;
|
||||
table: TableName;
|
||||
/**
|
||||
* The relationships between tables involved in the entire query request
|
||||
*/
|
||||
|
@ -3,6 +3,7 @@
|
||||
/* eslint-disable */
|
||||
|
||||
import type { RelationshipType } from './RelationshipType';
|
||||
import type { TableName } from './TableName';
|
||||
|
||||
export type Relationship = {
|
||||
/**
|
||||
@ -10,9 +11,6 @@ export type Relationship = {
|
||||
*/
|
||||
column_mapping: Record<string, string>;
|
||||
relationship_type: RelationshipType;
|
||||
/**
|
||||
* The name of the target table in the relationship
|
||||
*/
|
||||
target_table: string;
|
||||
target_table: TableName;
|
||||
};
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
/* eslint-disable */
|
||||
|
||||
import type { ColumnInfo } from './ColumnInfo';
|
||||
import type { TableName } from './TableName';
|
||||
|
||||
export type TableInfo = {
|
||||
/**
|
||||
@ -13,10 +14,7 @@ export type TableInfo = {
|
||||
* Description of the table
|
||||
*/
|
||||
description?: string | null;
|
||||
/**
|
||||
* The name of the table
|
||||
*/
|
||||
name: string;
|
||||
name: TableName;
|
||||
/**
|
||||
* The primary key of the table
|
||||
*/
|
||||
|
8
dc-agents/reference/src/types/models/TableName.ts
Normal file
8
dc-agents/reference/src/types/models/TableName.ts
Normal file
@ -0,0 +1,8 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
/**
|
||||
* The fully qualified name of a table, where the last item in the array is the table name and any earlier items represent the namespacing of the table name
|
||||
*/
|
||||
export type TableName = Array<string>;
|
@ -3,15 +3,13 @@
|
||||
/* eslint-disable */
|
||||
|
||||
import type { Relationship } from './Relationship';
|
||||
import type { TableName } from './TableName';
|
||||
|
||||
export type TableRelationships = {
|
||||
/**
|
||||
* A map of relationships from the source table to target tables. The key of the map is the relationship name
|
||||
*/
|
||||
relationships: Record<string, Relationship>;
|
||||
/**
|
||||
* The name of the source table in the relationship
|
||||
*/
|
||||
source_table: string;
|
||||
source_table: TableName;
|
||||
};
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
export const coerceUndefinedToNull = <T>(v: T | undefined): T | null => v === undefined ? null : v;
|
||||
import { TableName } from "./types";
|
||||
|
||||
export const coerceUndefinedToNull = <T>(v: T | undefined): T | null => v === undefined ? null : v;
|
||||
|
||||
export const unreachable = (x: never): never => { throw new Error(`Unreachable code reached! The types lied! 😭 Unexpected value: ${x}`) };
|
||||
|
||||
@ -14,3 +16,10 @@ export const zip = <T, U>(arr1: T[], arr2: U[]): [T, U][] => {
|
||||
export const crossProduct = <T, U>(arr1: T[], arr2: U[]): [T, U][] => {
|
||||
return arr1.flatMap(a1 => arr2.map(a2 => [a1, a2]) as [T, U][]);
|
||||
};
|
||||
|
||||
export const tableNameEquals = (tableName1: TableName) => (tableName2: TableName): boolean => {
|
||||
if (tableName1.length !== tableName2.length)
|
||||
return false;
|
||||
|
||||
return zip(tableName1, tableName2).every(([n1, n2]) => n1 === n2);
|
||||
}
|
||||
|
@ -9,13 +9,13 @@
|
||||
"kind": "reference",
|
||||
"tables": [
|
||||
{
|
||||
"table": "Album",
|
||||
"table": ["Album"],
|
||||
"object_relationships": [
|
||||
{
|
||||
"name": "Artist",
|
||||
"using": {
|
||||
"manual_configuration": {
|
||||
"remote_table": "Artist",
|
||||
"remote_table": ["Artist"],
|
||||
"column_mapping": {
|
||||
"ArtistId": "ArtistId"
|
||||
}
|
||||
@ -28,7 +28,7 @@
|
||||
"name": "Tracks",
|
||||
"using": {
|
||||
"manual_configuration": {
|
||||
"remote_table": "Track",
|
||||
"remote_table": ["Track"],
|
||||
"column_mapping": {
|
||||
"AlbumId": "AlbumId"
|
||||
}
|
||||
@ -38,13 +38,13 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "Artist",
|
||||
"table": ["Artist"],
|
||||
"array_relationships": [
|
||||
{
|
||||
"name": "Albums",
|
||||
"using": {
|
||||
"manual_configuration": {
|
||||
"remote_table": "Album",
|
||||
"remote_table": ["Album"],
|
||||
"column_mapping": {
|
||||
"ArtistId": "ArtistId"
|
||||
}
|
||||
@ -54,13 +54,13 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "Customer",
|
||||
"table": ["Customer"],
|
||||
"object_relationships": [
|
||||
{
|
||||
"name": "SupportRep",
|
||||
"using": {
|
||||
"manual_configuration": {
|
||||
"remote_table": "Employee",
|
||||
"remote_table": ["Employee"],
|
||||
"column_mapping": {
|
||||
"SupportRepId": "EmployeeId"
|
||||
}
|
||||
@ -73,7 +73,7 @@
|
||||
"name": "Invoices",
|
||||
"using": {
|
||||
"manual_configuration": {
|
||||
"remote_table": "Invoice",
|
||||
"remote_table": ["Invoice"],
|
||||
"column_mapping": {
|
||||
"CustomerId": "CustomerId"
|
||||
}
|
||||
@ -83,13 +83,13 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "Employee",
|
||||
"table": ["Employee"],
|
||||
"object_relationships": [
|
||||
{
|
||||
"name": "ReportsTo",
|
||||
"using": {
|
||||
"manual_configuration": {
|
||||
"remote_table": "Employee",
|
||||
"remote_table": ["Employee"],
|
||||
"column_mapping": {
|
||||
"ReportsTo": "EmployeeId"
|
||||
}
|
||||
@ -102,7 +102,7 @@
|
||||
"name": "SupportRepForCustomers",
|
||||
"using": {
|
||||
"manual_configuration": {
|
||||
"remote_table": "Customer",
|
||||
"remote_table": ["Customer"],
|
||||
"column_mapping": {
|
||||
"EmployeeId": "SupportRepId"
|
||||
}
|
||||
@ -113,7 +113,7 @@
|
||||
"name": "DirectReports",
|
||||
"using": {
|
||||
"manual_configuration": {
|
||||
"remote_table": "Employee",
|
||||
"remote_table": ["Employee"],
|
||||
"column_mapping": {
|
||||
"EmployeeId": "ReportsTo"
|
||||
}
|
||||
@ -123,13 +123,13 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "Genre",
|
||||
"table": ["Genre"],
|
||||
"array_relationships": [
|
||||
{
|
||||
"name": "Tracks",
|
||||
"using": {
|
||||
"manual_configuration": {
|
||||
"remote_table": "Track",
|
||||
"remote_table": ["Track"],
|
||||
"column_mapping": {
|
||||
"GenreId": "GenreId"
|
||||
}
|
||||
@ -139,13 +139,13 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "Invoice",
|
||||
"table": ["Invoice"],
|
||||
"object_relationships": [
|
||||
{
|
||||
"name": "Customer",
|
||||
"using": {
|
||||
"manual_configuration": {
|
||||
"remote_table": "Customer",
|
||||
"remote_table": ["Customer"],
|
||||
"column_mapping": {
|
||||
"CustomerId": "CustomerId"
|
||||
}
|
||||
@ -158,7 +158,7 @@
|
||||
"name": "InvoiceLines",
|
||||
"using": {
|
||||
"manual_configuration": {
|
||||
"remote_table": "InvoiceLine",
|
||||
"remote_table": ["InvoiceLine"],
|
||||
"column_mapping": {
|
||||
"InvoiceId": "InvoiceId"
|
||||
}
|
||||
@ -168,13 +168,13 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "InvoiceLine",
|
||||
"table": ["InvoiceLine"],
|
||||
"object_relationships": [
|
||||
{
|
||||
"name": "Invoice",
|
||||
"using": {
|
||||
"manual_configuration": {
|
||||
"remote_table": "Invoice",
|
||||
"remote_table": ["Invoice"],
|
||||
"column_mapping": {
|
||||
"InvoiceId": "InvoiceId"
|
||||
}
|
||||
@ -185,7 +185,7 @@
|
||||
"name": "Track",
|
||||
"using": {
|
||||
"manual_configuration": {
|
||||
"remote_table": "Track",
|
||||
"remote_table": ["Track"],
|
||||
"column_mapping": {
|
||||
"TrackId": "TrackId"
|
||||
}
|
||||
@ -195,13 +195,13 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "MediaType",
|
||||
"table": ["MediaType"],
|
||||
"array_relationships": [
|
||||
{
|
||||
"name": "Tracks",
|
||||
"using": {
|
||||
"manual_configuration": {
|
||||
"remote_table": "Track",
|
||||
"remote_table": ["Track"],
|
||||
"column_mapping": {
|
||||
"MediaTypeId": "MediaTypeId"
|
||||
}
|
||||
@ -211,13 +211,13 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "Playlist",
|
||||
"table": ["Playlist"],
|
||||
"array_relationships": [
|
||||
{
|
||||
"name": "PlaylistTracks",
|
||||
"using": {
|
||||
"manual_configuration": {
|
||||
"remote_table": "PlaylistTrack",
|
||||
"remote_table": ["PlaylistTrack"],
|
||||
"column_mapping": {
|
||||
"PlaylistId": "PlaylistId"
|
||||
}
|
||||
@ -227,13 +227,13 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "PlaylistTrack",
|
||||
"table": ["PlaylistTrack"],
|
||||
"object_relationships": [
|
||||
{
|
||||
"name": "Playlist",
|
||||
"using": {
|
||||
"manual_configuration": {
|
||||
"remote_table": "Playlist",
|
||||
"remote_table": ["Playlist"],
|
||||
"column_mapping": {
|
||||
"PlaylistId": "PlaylistId"
|
||||
}
|
||||
@ -244,7 +244,7 @@
|
||||
"name": "Track",
|
||||
"using": {
|
||||
"manual_configuration": {
|
||||
"remote_table": "Track",
|
||||
"remote_table": ["Track"],
|
||||
"column_mapping": {
|
||||
"TrackId": "TrackId"
|
||||
}
|
||||
@ -254,13 +254,13 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "Track",
|
||||
"table": ["Track"],
|
||||
"object_relationships": [
|
||||
{
|
||||
"name": "Album",
|
||||
"using": {
|
||||
"manual_configuration": {
|
||||
"remote_table": "Album",
|
||||
"remote_table": ["Album"],
|
||||
"column_mapping": {
|
||||
"AlbumId": "AlbumId"
|
||||
}
|
||||
@ -271,7 +271,7 @@
|
||||
"name": "MediaType",
|
||||
"using": {
|
||||
"manual_configuration": {
|
||||
"remote_table": "MediaType",
|
||||
"remote_table": ["MediaType"],
|
||||
"column_mapping": {
|
||||
"MediaTypeId": "MediaTypeId"
|
||||
}
|
||||
@ -282,7 +282,7 @@
|
||||
"name": "Genre",
|
||||
"using": {
|
||||
"manual_configuration": {
|
||||
"remote_table": "Genre",
|
||||
"remote_table": ["Genre"],
|
||||
"column_mapping": {
|
||||
"GenreId": "GenreId"
|
||||
}
|
||||
@ -295,7 +295,7 @@
|
||||
"name": "InvoiceLines",
|
||||
"using": {
|
||||
"manual_configuration": {
|
||||
"remote_table": "InvoiceLine",
|
||||
"remote_table": ["InvoiceLine"],
|
||||
"column_mapping": {
|
||||
"TrackId": "TrackId"
|
||||
}
|
||||
@ -306,7 +306,7 @@
|
||||
"name": "PlaylistTracks",
|
||||
"using": {
|
||||
"manual_configuration": {
|
||||
"remote_table": "PlaylistTrack",
|
||||
"remote_table": ["PlaylistTrack"],
|
||||
"column_mapping": {
|
||||
"TrackId": "TrackId"
|
||||
}
|
||||
|
@ -12,9 +12,10 @@ where
|
||||
import Autodocodec
|
||||
import Autodocodec.OpenAPI ()
|
||||
import Control.DeepSeq (NFData)
|
||||
import Data.Aeson (FromJSON, FromJSONKey, ToJSON, ToJSONKey)
|
||||
import Data.Aeson (FromJSON, ToJSON)
|
||||
import Data.Data (Data)
|
||||
import Data.Hashable (Hashable)
|
||||
import Data.List.NonEmpty (NonEmpty)
|
||||
import Data.OpenApi (ToSchema)
|
||||
import Data.Text (Text)
|
||||
import GHC.Generics (Generic)
|
||||
@ -23,14 +24,16 @@ import Prelude
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
newtype TableName = TableName {unTableName :: Text}
|
||||
newtype TableName = TableName {unTableName :: NonEmpty Text}
|
||||
deriving stock (Eq, Ord, Show, Generic, Data)
|
||||
deriving anyclass (NFData, Hashable)
|
||||
deriving newtype (FromJSONKey, ToJSONKey)
|
||||
deriving (FromJSON, ToJSON, ToSchema) via Autodocodec TableName
|
||||
|
||||
instance HasCodec TableName where
|
||||
codec = dimapCodec TableName unTableName textCodec
|
||||
codec =
|
||||
named "TableName" $
|
||||
dimapCodec TableName unTableName codec
|
||||
<?> "The fully qualified name of a table, where the last item in the array is the table name and any earlier items represent the namespacing of the table name"
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
|
@ -5,12 +5,14 @@ module Hasura.Backends.DataConnector.Adapter.Backend (CustomBooleanOperator (..)
|
||||
import Data.Aeson qualified as J (ToJSON (..), Value)
|
||||
import Data.Aeson.Extended (ToJSONKeyValue (..))
|
||||
import Data.Aeson.Key (fromText)
|
||||
import Data.List.NonEmpty qualified as NonEmpty
|
||||
import Data.Text qualified as Text
|
||||
import Data.Text.Casing qualified as C
|
||||
import Data.Text.Extended ((<<>))
|
||||
import Hasura.Backends.DataConnector.Adapter.Types qualified as Adapter
|
||||
import Hasura.Backends.DataConnector.IR.Aggregate qualified as IR.A
|
||||
import Hasura.Backends.DataConnector.IR.Column qualified as IR.C
|
||||
import Hasura.Backends.DataConnector.IR.Function qualified as IR.F
|
||||
import Hasura.Backends.DataConnector.IR.Name qualified as IR.N
|
||||
import Hasura.Backends.DataConnector.IR.OrderBy qualified as IR.O
|
||||
import Hasura.Backends.DataConnector.IR.Scalar.Type qualified as IR.S.T
|
||||
import Hasura.Backends.DataConnector.IR.Scalar.Value qualified as IR.S.V
|
||||
@ -104,22 +106,21 @@ instance Backend 'DataConnector where
|
||||
tableToFunction = coerce
|
||||
|
||||
tableGraphQLName :: TableName 'DataConnector -> Either QErr G.Name
|
||||
tableGraphQLName name =
|
||||
G.mkName (IR.N.unName name)
|
||||
`onNothing` throw400 ValidationFailed ("TableName " <> IR.N.unName name <> " is not a valid GraphQL identifier")
|
||||
tableGraphQLName name = do
|
||||
let snakedName = snakeCaseTableName @'DataConnector name
|
||||
G.mkName snakedName
|
||||
`onNothing` throw400 ValidationFailed ("TableName " <> snakedName <> " is not a valid GraphQL identifier")
|
||||
|
||||
functionGraphQLName :: FunctionName 'DataConnector -> Either QErr G.Name
|
||||
functionGraphQLName = error "functionGraphQLName: not implemented for the Data Connector backend."
|
||||
|
||||
snakeCaseTableName :: TableName 'DataConnector -> Text
|
||||
snakeCaseTableName = IR.N.unName
|
||||
snakeCaseTableName = Text.intercalate "_" . NonEmpty.toList . IR.T.unName
|
||||
|
||||
getTableIdentifier :: TableName 'DataConnector -> Either QErr C.GQLNameIdentifier
|
||||
getTableIdentifier name = do
|
||||
gqlTableName <-
|
||||
G.mkName (IR.N.unName name)
|
||||
`onNothing` throw400 ValidationFailed ("TableName " <> IR.N.unName name <> " is not a valid GraphQL identifier")
|
||||
pure $ C.Identifier gqlTableName []
|
||||
getTableIdentifier name@(IR.T.Name (prefix :| suffixes)) = do
|
||||
(C.Identifier <$> G.mkName prefix <*> traverse G.mkNameSuffix suffixes)
|
||||
`onNothing` throw400 ValidationFailed ("TableName " <> name <<> " is not a valid GraphQL identifier")
|
||||
|
||||
namingConventionSupport :: SupportedNamingCase
|
||||
namingConventionSupport = OnlyHasuraCase
|
||||
|
@ -1,22 +1,33 @@
|
||||
module Hasura.Backends.DataConnector.IR.Function (Name) where
|
||||
module Hasura.Backends.DataConnector.IR.Function
|
||||
( Name (..),
|
||||
)
|
||||
where
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
import Data.Aeson (FromJSON, ToJSON, ToJSONKey (..))
|
||||
import Data.Aeson.Types (toJSONKeyText)
|
||||
import Data.List.NonEmpty qualified as NonEmpty
|
||||
import Data.Text qualified as Text
|
||||
import Data.Text.Extended (ToTxt (..))
|
||||
import Hasura.Base.ErrorValue qualified as ErrorValue
|
||||
import Hasura.Base.ToErrorValue
|
||||
import Hasura.Incremental (Cacheable)
|
||||
import Hasura.Prelude
|
||||
|
||||
import Hasura.Backends.DataConnector.IR.Name qualified as IR.N
|
||||
newtype Name = Name {unName :: NonEmpty Text}
|
||||
deriving stock (Data, Eq, Generic, Ord, Show)
|
||||
deriving newtype
|
||||
( Cacheable,
|
||||
FromJSON,
|
||||
Hashable,
|
||||
NFData,
|
||||
ToJSON
|
||||
)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
instance ToJSONKey Name where
|
||||
toJSONKey = toJSONKeyText toTxt
|
||||
|
||||
-- | An alias for 'Name.Function' 'Name.Name's.
|
||||
--
|
||||
-- This alias is defined in its own module primarily for the convenience of
|
||||
-- importing it qualified.
|
||||
--
|
||||
-- For example:
|
||||
-- @
|
||||
-- import Data.Coerce (coerce)
|
||||
-- import Hasura.Experimental.IR.Function qualified as Function (Name)
|
||||
--
|
||||
-- example :: Function.Name
|
||||
-- example = coerce @Text @Function.Name "function_name"
|
||||
-- @
|
||||
type Name = IR.N.Name 'IR.N.Function
|
||||
instance ToTxt Name where
|
||||
toTxt = Text.intercalate "." . NonEmpty.toList . unName
|
||||
|
||||
instance ToErrorValue Name where
|
||||
toErrorValue = ErrorValue.squote . toTxt
|
||||
|
@ -43,12 +43,6 @@ newtype Name ty = Name {unName :: Text}
|
||||
instance ToErrorValue (Name ty) where
|
||||
toErrorValue = ErrorValue.squote . unName
|
||||
|
||||
instance Witch.From API.TableName (Name 'Table) where
|
||||
from (API.TableName n) = Name n
|
||||
|
||||
instance Witch.From (Name 'Table) API.TableName where
|
||||
from (Name n) = API.TableName n
|
||||
|
||||
instance Witch.From API.ColumnName (Name 'Column) where
|
||||
from (API.ColumnName n) = Name n
|
||||
|
||||
@ -68,6 +62,4 @@ instance Witch.From (Name 'Relationship) API.RelationshipName where
|
||||
-- shared abstraction.
|
||||
data NameType
|
||||
= Column
|
||||
| Function
|
||||
| Table
|
||||
| Relationship
|
||||
|
@ -1,25 +1,49 @@
|
||||
module Hasura.Backends.DataConnector.IR.Table
|
||||
( Name,
|
||||
( Name (..),
|
||||
)
|
||||
where
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
import Data.Aeson (FromJSON (..), ToJSON, ToJSONKey (..), withText)
|
||||
import Data.Aeson.Types (toJSONKeyText)
|
||||
import Data.List.NonEmpty qualified as NonEmpty
|
||||
import Data.Text qualified as Text
|
||||
import Data.Text.Extended (ToTxt (..))
|
||||
import Hasura.Backends.DataConnector.API qualified as API
|
||||
import Hasura.Base.ErrorValue qualified as ErrorValue
|
||||
import Hasura.Base.ToErrorValue
|
||||
import Hasura.Incremental (Cacheable)
|
||||
import Hasura.Prelude
|
||||
import Witch.From qualified as Witch
|
||||
|
||||
import Hasura.Backends.DataConnector.IR.Name qualified as IR.N
|
||||
-- | The fully qualified name of a table. The last element in the list is the table name
|
||||
-- and all other elements represent namespacing of the table name.
|
||||
-- For example, for a database that has schemas, the name would be '[<schema>,<table name>]'
|
||||
newtype Name = Name {unName :: NonEmpty Text}
|
||||
deriving stock (Data, Eq, Generic, Ord, Show)
|
||||
deriving newtype
|
||||
( Cacheable,
|
||||
Hashable,
|
||||
NFData,
|
||||
ToJSON
|
||||
)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
instance FromJSON Name where
|
||||
parseJSON value =
|
||||
Name <$> parseJSON value
|
||||
-- Fallback parsing of a single string to support older metadata
|
||||
<|> withText "Name" (\text -> pure . Name $ text :| []) value
|
||||
|
||||
-- | An alias for 'Name.Table' 'Name.Name's.
|
||||
--
|
||||
-- This alias is defined in its own module primarily for the convenience of
|
||||
-- importing it qualified.
|
||||
--
|
||||
-- For example:
|
||||
-- @
|
||||
-- import Data.Coerce (coerce)
|
||||
-- import Hasura.Experimental.IR.Table qualified as Table (Name)
|
||||
--
|
||||
-- example :: Table.Name
|
||||
-- example = coerce @Text @Table.Name "table_name"
|
||||
-- @
|
||||
type Name = IR.N.Name 'IR.N.Table
|
||||
instance ToJSONKey Name where
|
||||
toJSONKey = toJSONKeyText toTxt
|
||||
|
||||
instance Witch.From API.TableName Name where
|
||||
from (API.TableName n) = Name n
|
||||
|
||||
instance Witch.From Name API.TableName where
|
||||
from (Name n) = API.TableName n
|
||||
|
||||
instance ToTxt Name where
|
||||
toTxt = Text.intercalate "." . NonEmpty.toList . unName
|
||||
|
||||
instance ToErrorValue Name where
|
||||
toErrorValue = ErrorValue.squote . toTxt
|
||||
|
@ -70,14 +70,14 @@ spec = do
|
||||
describe "QueryRequest" $ do
|
||||
let queryRequest =
|
||||
QueryRequest
|
||||
{ _qrTable = TableName "my_table",
|
||||
{ _qrTable = TableName ["my_table"],
|
||||
_qrTableRelationships = [],
|
||||
_qrQuery = Query (Just mempty) Nothing Nothing Nothing Nothing Nothing
|
||||
}
|
||||
testToFromJSONToSchema
|
||||
queryRequest
|
||||
[aesonQQ|
|
||||
{ "table": "my_table",
|
||||
{ "table": ["my_table"],
|
||||
"table_relationships": [],
|
||||
"query": { "fields": {} } }
|
||||
|]
|
||||
|
@ -34,14 +34,14 @@ spec = do
|
||||
describe "Relationship" $ do
|
||||
let relationship =
|
||||
Relationship
|
||||
{ _rTargetTable = TableName "target_table_name",
|
||||
{ _rTargetTable = TableName ["target_table_name"],
|
||||
_rRelationshipType = ObjectRelationship,
|
||||
_rColumnMapping = [(ColumnName "outer_column", ColumnName "inner_column")]
|
||||
}
|
||||
testToFromJSONToSchema
|
||||
relationship
|
||||
[aesonQQ|
|
||||
{ "target_table": "target_table_name",
|
||||
{ "target_table": ["target_table_name"],
|
||||
"relationship_type": "object",
|
||||
"column_mapping": {
|
||||
"outer_column": "inner_column"
|
||||
@ -52,19 +52,19 @@ spec = do
|
||||
describe "TableRelationships" $ do
|
||||
let relationshipA =
|
||||
Relationship
|
||||
{ _rTargetTable = TableName "target_table_name_a",
|
||||
{ _rTargetTable = TableName ["target_table_name_a"],
|
||||
_rRelationshipType = ObjectRelationship,
|
||||
_rColumnMapping = [(ColumnName "outer_column_a", ColumnName "inner_column_a")]
|
||||
}
|
||||
let relationshipB =
|
||||
Relationship
|
||||
{ _rTargetTable = TableName "target_table_name_b",
|
||||
{ _rTargetTable = TableName ["target_table_name_b"],
|
||||
_rRelationshipType = ArrayRelationship,
|
||||
_rColumnMapping = [(ColumnName "outer_column_b", ColumnName "inner_column_b")]
|
||||
}
|
||||
let tableRelationships =
|
||||
TableRelationships
|
||||
{ _trSourceTable = TableName "source_table_name",
|
||||
{ _trSourceTable = TableName ["source_table_name"],
|
||||
_trRelationships =
|
||||
[ (RelationshipName "relationship_a", relationshipA),
|
||||
(RelationshipName "relationship_b", relationshipB)
|
||||
@ -73,17 +73,17 @@ spec = do
|
||||
testToFromJSONToSchema
|
||||
tableRelationships
|
||||
[aesonQQ|
|
||||
{ "source_table": "source_table_name",
|
||||
{ "source_table": ["source_table_name"],
|
||||
"relationships": {
|
||||
"relationship_a": {
|
||||
"target_table": "target_table_name_a",
|
||||
"target_table": ["target_table_name_a"],
|
||||
"relationship_type": "object",
|
||||
"column_mapping": {
|
||||
"outer_column_a": "inner_column_a"
|
||||
}
|
||||
},
|
||||
"relationship_b": {
|
||||
"target_table": "target_table_name_b",
|
||||
"target_table": ["target_table_name_b"],
|
||||
"relationship_type": "array",
|
||||
"column_mapping": {
|
||||
"outer_column_b": "inner_column_b"
|
||||
|
@ -17,27 +17,27 @@ import Test.Hspec
|
||||
spec :: Spec
|
||||
spec = do
|
||||
describe "TableName" $ do
|
||||
testToFromJSONToSchema (TableName "my_table_name") [aesonQQ|"my_table_name"|]
|
||||
testToFromJSONToSchema (TableName ["my_table_name"]) [aesonQQ|["my_table_name"]|]
|
||||
jsonOpenApiProperties genTableName
|
||||
describe "TableInfo" $ do
|
||||
describe "minimal" $
|
||||
testToFromJSONToSchema
|
||||
(TableInfo (TableName "my_table_name") [] Nothing Nothing)
|
||||
(TableInfo (TableName ["my_table_name"]) [] Nothing Nothing)
|
||||
[aesonQQ|
|
||||
{ "name": "my_table_name",
|
||||
{ "name": ["my_table_name"],
|
||||
"columns": []
|
||||
}
|
||||
|]
|
||||
describe "non-minimal" $
|
||||
testToFromJSONToSchema
|
||||
( TableInfo
|
||||
(TableName "my_table_name")
|
||||
(TableName ["my_table_name"])
|
||||
[ColumnInfo (ColumnName "id") StringTy False Nothing]
|
||||
(Just [ColumnName "id"])
|
||||
(Just "my description")
|
||||
)
|
||||
[aesonQQ|
|
||||
{ "name": "my_table_name",
|
||||
{ "name": ["my_table_name"],
|
||||
"columns": [{"name": "id", "type": "string", "nullable": false}],
|
||||
"primary_key": ["id"],
|
||||
"description": "my description"
|
||||
@ -46,7 +46,7 @@ spec = do
|
||||
jsonOpenApiProperties genTableInfo
|
||||
|
||||
genTableName :: MonadGen m => m TableName
|
||||
genTableName = TableName <$> text (linear 0 10) unicode
|
||||
genTableName = TableName <$> Gen.nonEmpty (linear 1 3) (text (linear 0 10) unicode)
|
||||
|
||||
genTableInfo :: MonadGen m => m TableInfo
|
||||
genTableInfo =
|
||||
|
@ -12,8 +12,10 @@ module Hasura.Backends.DataConnector.RQLGenerator.GenCommon
|
||||
where
|
||||
|
||||
import Data.Functor.Const
|
||||
import Hasura.Backends.DataConnector.IR.Function qualified as FunctionName
|
||||
import Hasura.Backends.DataConnector.IR.Name qualified as Name
|
||||
import Hasura.Backends.DataConnector.IR.Scalar.Type qualified as ScalarType
|
||||
import Hasura.Backends.DataConnector.IR.Table qualified as TableName
|
||||
import Hasura.Generator.Common (defaultRange, genArbitraryUnicodeText, genHashMap)
|
||||
import Hasura.Prelude (coerce, fmap, pure, ($), (<$>), (<*>))
|
||||
import Hasura.RQL.IR
|
||||
@ -22,7 +24,8 @@ import Hasura.RQL.Types.Backend
|
||||
import Hasura.RQL.Types.Relationships.Local
|
||||
import Hasura.SQL.Backend
|
||||
import Hedgehog (MonadGen)
|
||||
import Hedgehog.Gen (bool_, choice, element, list)
|
||||
import Hedgehog.Gen (bool_, choice, element, list, nonEmpty)
|
||||
import Hedgehog.Internal.Range (linear)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@ -83,7 +86,7 @@ genColumn :: MonadGen m => m (Column 'DataConnector)
|
||||
genColumn = coerce <$> genArbitraryUnicodeText defaultRange
|
||||
|
||||
genTableName :: MonadGen m => m (TableName 'DataConnector)
|
||||
genTableName = coerce <$> genArbitraryUnicodeText defaultRange
|
||||
genTableName = coerce <$> nonEmpty (linear 1 3) (genArbitraryUnicodeText defaultRange)
|
||||
|
||||
genScalarType :: MonadGen m => m (ScalarType 'DataConnector)
|
||||
genScalarType =
|
||||
@ -94,7 +97,7 @@ genScalarType =
|
||||
]
|
||||
|
||||
genFunctionName :: MonadGen m => m (FunctionName 'DataConnector)
|
||||
genFunctionName = coerce <$> genArbitraryUnicodeText defaultRange
|
||||
genFunctionName = coerce <$> nonEmpty (linear 1 3) (genArbitraryUnicodeText defaultRange)
|
||||
|
||||
genFunctionArgumentExp :: MonadGen m => m (FunctionArgumentExp 'DataConnector a)
|
||||
genFunctionArgumentExp = pure (Const ())
|
||||
|
@ -74,11 +74,14 @@ import Data.Aeson.Lens (_Bool, _Number, _String)
|
||||
import Data.Bifunctor (bimap)
|
||||
import Data.ByteString (ByteString)
|
||||
import Data.ByteString.Lazy qualified as BSL
|
||||
import Data.CaseInsensitive (CI)
|
||||
import Data.CaseInsensitive qualified as CI
|
||||
import Data.FileEmbed (embedFile, makeRelativeToProject)
|
||||
import Data.HashMap.Strict (HashMap)
|
||||
import Data.HashMap.Strict qualified as HashMap
|
||||
import Data.List (find, sortOn)
|
||||
import Data.List.NonEmpty (NonEmpty (..))
|
||||
import Data.List.NonEmpty qualified as NonEmpty
|
||||
import Data.Maybe (fromMaybe, mapMaybe)
|
||||
import Data.Scientific (Scientific)
|
||||
import Data.Text (Text)
|
||||
@ -105,11 +108,14 @@ chinookXml :: XML.Document
|
||||
chinookXml = XML.parseLBS_ XML.def . GZip.decompress $ BSL.fromStrict chinookXmlBS
|
||||
|
||||
readTableFromXmlIntoRows :: API.TableName -> [KeyMap API.FieldValue]
|
||||
readTableFromXmlIntoRows (API.TableName tableName) =
|
||||
readTableFromXmlIntoRows tableName =
|
||||
rowToJsonObject <$> tableRows
|
||||
where
|
||||
tableNameToXmlTag :: API.TableName -> CI Text
|
||||
tableNameToXmlTag (API.TableName names) = CI.mk . Text.intercalate "_" $ NonEmpty.toList names
|
||||
|
||||
tableRows :: [XML.Element]
|
||||
tableRows = chinookXml ^.. XML.root . XML.nodes . traverse . XML._Element . XML.named (CI.mk tableName)
|
||||
tableRows = chinookXml ^.. XML.root . XML.nodes . traverse . XML._Element . XML.named (tableNameToXmlTag tableName)
|
||||
|
||||
rowToJsonObject :: XML.Element -> KeyMap API.FieldValue
|
||||
rowToJsonObject element =
|
||||
@ -131,8 +137,11 @@ readTableFromXmlIntoRows (API.TableName tableName) =
|
||||
else API.mkColumnFieldValue $ J.String textValue
|
||||
in (name, value)
|
||||
|
||||
mkTableName :: Text -> API.TableName
|
||||
mkTableName = API.TableName . (:| [])
|
||||
|
||||
artistsTableName :: API.TableName
|
||||
artistsTableName = API.TableName "Artist"
|
||||
artistsTableName = mkTableName "Artist"
|
||||
|
||||
artistsRows :: [KeyMap API.FieldValue]
|
||||
artistsRows = sortBy "ArtistId" $ readTableFromXmlIntoRows artistsTableName
|
||||
@ -155,7 +164,7 @@ artistsTableRelationships =
|
||||
)
|
||||
|
||||
albumsTableName :: API.TableName
|
||||
albumsTableName = API.TableName "Album"
|
||||
albumsTableName = mkTableName "Album"
|
||||
|
||||
albumsRows :: [KeyMap API.FieldValue]
|
||||
albumsRows = sortBy "AlbumId" $ readTableFromXmlIntoRows albumsTableName
|
||||
@ -179,7 +188,7 @@ tracksRelationshipName :: API.RelationshipName
|
||||
tracksRelationshipName = API.RelationshipName "Tracks"
|
||||
|
||||
customersTableName :: API.TableName
|
||||
customersTableName = API.TableName "Customer"
|
||||
customersTableName = mkTableName "Customer"
|
||||
|
||||
customersRows :: [KeyMap API.FieldValue]
|
||||
customersRows = sortBy "CustomerId" $ readTableFromXmlIntoRows customersTableName
|
||||
@ -198,7 +207,7 @@ supportRepRelationshipName :: API.RelationshipName
|
||||
supportRepRelationshipName = API.RelationshipName "SupportRep"
|
||||
|
||||
employeesTableName :: API.TableName
|
||||
employeesTableName = API.TableName "Employee"
|
||||
employeesTableName = mkTableName "Employee"
|
||||
|
||||
employeesRows :: [KeyMap API.FieldValue]
|
||||
employeesRows = sortBy "EmployeeId" $ readTableFromXmlIntoRows employeesTableName
|
||||
@ -221,25 +230,25 @@ supportRepForCustomersRelationshipName :: API.RelationshipName
|
||||
supportRepForCustomersRelationshipName = API.RelationshipName "SupportRepForCustomers"
|
||||
|
||||
invoicesTableName :: API.TableName
|
||||
invoicesTableName = API.TableName "Invoice"
|
||||
invoicesTableName = mkTableName "Invoice"
|
||||
|
||||
invoicesRows :: [KeyMap API.FieldValue]
|
||||
invoicesRows = sortBy "InvoiceId" $ readTableFromXmlIntoRows invoicesTableName
|
||||
|
||||
invoiceLinesTableName :: API.TableName
|
||||
invoiceLinesTableName = API.TableName "InvoiceLine"
|
||||
invoiceLinesTableName = mkTableName "InvoiceLine"
|
||||
|
||||
invoiceLinesRows :: [KeyMap API.FieldValue]
|
||||
invoiceLinesRows = sortBy "InvoiceLineId" $ readTableFromXmlIntoRows invoiceLinesTableName
|
||||
|
||||
mediaTypesTableName :: API.TableName
|
||||
mediaTypesTableName = API.TableName "MediaType"
|
||||
mediaTypesTableName = mkTableName "MediaType"
|
||||
|
||||
mediaTypesRows :: [KeyMap API.FieldValue]
|
||||
mediaTypesRows = sortBy "MediaTypeId" $ readTableFromXmlIntoRows mediaTypesTableName
|
||||
|
||||
tracksTableName :: API.TableName
|
||||
tracksTableName = API.TableName "Track"
|
||||
tracksTableName = mkTableName "Track"
|
||||
|
||||
tracksRows :: [KeyMap API.FieldValue]
|
||||
tracksRows = sortBy "TrackId" $ readTableFromXmlIntoRows tracksTableName
|
||||
|
@ -1,6 +1,6 @@
|
||||
[
|
||||
{
|
||||
"name": "Artist",
|
||||
"name": ["Artist"],
|
||||
"primary_key": ["ArtistId"],
|
||||
"description": "Collection of artists of music",
|
||||
"columns": [
|
||||
@ -19,7 +19,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Album",
|
||||
"name": ["Album"],
|
||||
"primary_key": ["AlbumId"],
|
||||
"description": "Collection of music albums created by artists",
|
||||
"columns": [
|
||||
@ -44,7 +44,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Customer",
|
||||
"name": ["Customer"],
|
||||
"primary_key": ["CustomerId"],
|
||||
"description": "Collection of customers who can buy tracks",
|
||||
"columns": [
|
||||
@ -129,7 +129,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Employee",
|
||||
"name": ["Employee"],
|
||||
"primary_key": ["EmployeeId"],
|
||||
"description": "Collection of employees who work for the business",
|
||||
"columns": [
|
||||
@ -226,7 +226,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Genre",
|
||||
"name": ["Genre"],
|
||||
"primary_key": ["GenreId"],
|
||||
"description": "Genres of music",
|
||||
"columns": [
|
||||
@ -245,7 +245,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Invoice",
|
||||
"name": ["Invoice"],
|
||||
"primary_key": ["InvoiceId"],
|
||||
"description": "Collection of invoices of music purchases by a customer",
|
||||
"columns": [
|
||||
@ -306,7 +306,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "InvoiceLine",
|
||||
"name": ["InvoiceLine"],
|
||||
"primary_key": ["InvoiceLineId"],
|
||||
"description": "Collection of track purchasing line items of invoices",
|
||||
"columns": [
|
||||
@ -343,7 +343,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MediaType",
|
||||
"name": ["MediaType"],
|
||||
"primary_key": ["MediaTypeId"],
|
||||
"description": "Collection of media types that tracks can be encoded in",
|
||||
"columns": [
|
||||
@ -362,7 +362,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Playlist",
|
||||
"name": ["Playlist"],
|
||||
"primary_key": ["PlaylistId"],
|
||||
"description": "Collection of playlists",
|
||||
"columns": [
|
||||
@ -381,7 +381,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "PlaylistTrack",
|
||||
"name": ["PlaylistTrack"],
|
||||
"primary_key": ["PlaylistId", "TrackId"],
|
||||
"description": "Associations between playlists and tracks",
|
||||
"columns": [
|
||||
@ -400,7 +400,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Track",
|
||||
"name": ["Track"],
|
||||
"primary_key": ["TrackId"],
|
||||
"description": "Collection of music tracks",
|
||||
"columns": [
|
||||
|
@ -23,6 +23,9 @@ data MockConfig = MockConfig
|
||||
_queryResponse :: API.QueryRequest -> API.QueryResponse
|
||||
}
|
||||
|
||||
mkTableName :: Text -> API.TableName
|
||||
mkTableName = API.TableName . (:| [])
|
||||
|
||||
-- | Stock Capabilities for a Chinook Agent
|
||||
capabilities :: API.CapabilitiesResponse
|
||||
capabilities =
|
||||
@ -62,7 +65,7 @@ schema =
|
||||
API.SchemaResponse
|
||||
{ API.srTables =
|
||||
[ API.TableInfo
|
||||
{ API.dtiName = API.TableName "Artist",
|
||||
{ API.dtiName = mkTableName "Artist",
|
||||
API.dtiColumns =
|
||||
[ API.ColumnInfo
|
||||
{ API.dciName = API.ColumnName "ArtistId",
|
||||
@ -81,7 +84,7 @@ schema =
|
||||
API.dtiDescription = Just "Collection of artists of music"
|
||||
},
|
||||
API.TableInfo
|
||||
{ API.dtiName = API.TableName "Album",
|
||||
{ API.dtiName = mkTableName "Album",
|
||||
API.dtiColumns =
|
||||
[ API.ColumnInfo
|
||||
{ API.dciName = API.ColumnName "AlbumId",
|
||||
@ -106,7 +109,7 @@ schema =
|
||||
API.dtiDescription = Just "Collection of music albums created by artists"
|
||||
},
|
||||
API.TableInfo
|
||||
{ API.dtiName = API.TableName "Genre",
|
||||
{ API.dtiName = mkTableName "Genre",
|
||||
API.dtiColumns =
|
||||
[ API.ColumnInfo
|
||||
{ API.dciName = API.ColumnName "GenreId",
|
||||
@ -125,7 +128,7 @@ schema =
|
||||
API.dtiDescription = Just "Genres of music"
|
||||
},
|
||||
API.TableInfo
|
||||
{ API.dtiName = API.TableName "Invoice",
|
||||
{ API.dtiName = mkTableName "Invoice",
|
||||
API.dtiColumns =
|
||||
[ API.ColumnInfo
|
||||
{ API.dciName = API.ColumnName "InvoiceId",
|
||||
@ -186,7 +189,7 @@ schema =
|
||||
API.dtiDescription = Just "Collection of invoices of music purchases by a customer"
|
||||
},
|
||||
API.TableInfo
|
||||
{ API.dtiName = API.TableName "InvoiceLine",
|
||||
{ API.dtiName = mkTableName "InvoiceLine",
|
||||
API.dtiColumns =
|
||||
[ API.ColumnInfo
|
||||
{ API.dciName = API.ColumnName "InvoiceLineId",
|
||||
@ -223,7 +226,7 @@ schema =
|
||||
API.dtiDescription = Just "Collection of track purchasing line items of invoices"
|
||||
},
|
||||
API.TableInfo
|
||||
{ API.dtiName = API.TableName "MediaType",
|
||||
{ API.dtiName = mkTableName "MediaType",
|
||||
API.dtiColumns =
|
||||
[ API.ColumnInfo
|
||||
{ API.dciName = API.ColumnName "MediaTypeId",
|
||||
@ -242,7 +245,7 @@ schema =
|
||||
API.dtiDescription = Just "Collection of media types that tracks can be encoded in"
|
||||
},
|
||||
API.TableInfo
|
||||
{ API.dtiName = API.TableName "Track",
|
||||
{ API.dtiName = mkTableName "Track",
|
||||
API.dtiColumns =
|
||||
[ API.ColumnInfo
|
||||
{ API.dciName = API.ColumnName "TrackId",
|
||||
|
@ -40,36 +40,36 @@ sourceMetadata =
|
||||
name : *source
|
||||
kind: *backendType
|
||||
tables:
|
||||
- table: Album
|
||||
- table: [Album]
|
||||
object_relationships:
|
||||
- name: Artist
|
||||
using:
|
||||
manual_configuration:
|
||||
remote_table: Artist
|
||||
remote_table: [Artist]
|
||||
column_mapping:
|
||||
ArtistId: ArtistId
|
||||
- table: Artist
|
||||
- table: [Artist]
|
||||
array_relationships:
|
||||
- name: Albums
|
||||
using:
|
||||
manual_configuration:
|
||||
remote_table: Album
|
||||
remote_table: [Album]
|
||||
column_mapping:
|
||||
ArtistId: ArtistId
|
||||
- table: Invoice
|
||||
- table: [Invoice]
|
||||
array_relationships:
|
||||
- name: InvoiceLines
|
||||
using:
|
||||
manual_configuration:
|
||||
remote_table: InvoiceLine
|
||||
remote_table: [InvoiceLine]
|
||||
column_mapping:
|
||||
InvoiceId: InvoiceId
|
||||
- table: InvoiceLine
|
||||
- table: [InvoiceLine]
|
||||
object_relationships:
|
||||
- name: Invoice
|
||||
using:
|
||||
manual_configuration:
|
||||
remote_table: Invoice
|
||||
remote_table: [Invoice]
|
||||
column_mapping:
|
||||
InvoiceId: InvoiceId
|
||||
configuration: {}
|
||||
|
@ -43,36 +43,36 @@ sourceMetadata =
|
||||
name : *source
|
||||
kind: *backendType
|
||||
tables:
|
||||
- table: Album
|
||||
- table: [Album]
|
||||
object_relationships:
|
||||
- name: Artist
|
||||
using:
|
||||
manual_configuration:
|
||||
remote_table: Artist
|
||||
remote_table: [Artist]
|
||||
column_mapping:
|
||||
ArtistId: ArtistId
|
||||
- table: Artist
|
||||
- table: [Artist]
|
||||
array_relationships:
|
||||
- name: Albums
|
||||
using:
|
||||
manual_configuration:
|
||||
remote_table: Album
|
||||
remote_table: [Album]
|
||||
column_mapping:
|
||||
ArtistId: ArtistId
|
||||
- table: Invoice
|
||||
- table: [Invoice]
|
||||
array_relationships:
|
||||
- name: InvoiceLines
|
||||
using:
|
||||
manual_configuration:
|
||||
remote_table: InvoiceLine
|
||||
remote_table: [InvoiceLine]
|
||||
column_mapping:
|
||||
InvoiceId: InvoiceId
|
||||
- table: InvoiceLine
|
||||
- table: [InvoiceLine]
|
||||
object_relationships:
|
||||
- name: Invoice
|
||||
using:
|
||||
manual_configuration:
|
||||
remote_table: Invoice
|
||||
remote_table: [Invoice]
|
||||
column_mapping:
|
||||
InvoiceId: InvoiceId
|
||||
configuration: {}
|
||||
@ -139,15 +139,15 @@ tests opts = describe "Aggregate Query Tests" $ do
|
||||
{ _whenQuery =
|
||||
Just
|
||||
( API.QueryRequest
|
||||
{ _qrTable = API.TableName "Artist",
|
||||
{ _qrTable = API.TableName ("Artist" :| []),
|
||||
_qrTableRelationships =
|
||||
[ API.TableRelationships
|
||||
{ _trSourceTable = API.TableName "Artist",
|
||||
{ _trSourceTable = API.TableName ("Artist" :| []),
|
||||
_trRelationships =
|
||||
HashMap.fromList
|
||||
[ ( API.RelationshipName "Albums",
|
||||
API.Relationship
|
||||
{ _rTargetTable = API.TableName "Album",
|
||||
{ _rTargetTable = API.TableName ("Album" :| []),
|
||||
_rRelationshipType = API.ArrayRelationship,
|
||||
_rColumnMapping = HashMap.fromList [(API.ColumnName "ArtistId", API.ColumnName "ArtistId")]
|
||||
}
|
||||
@ -270,15 +270,15 @@ tests opts = describe "Aggregate Query Tests" $ do
|
||||
{ _whenQuery =
|
||||
Just
|
||||
( API.QueryRequest
|
||||
{ _qrTable = API.TableName "Invoice",
|
||||
{ _qrTable = API.TableName ("Invoice" :| []),
|
||||
_qrTableRelationships =
|
||||
[ API.TableRelationships
|
||||
{ _trSourceTable = API.TableName "Invoice",
|
||||
{ _trSourceTable = API.TableName ("Invoice" :| []),
|
||||
_trRelationships =
|
||||
HashMap.fromList
|
||||
[ ( API.RelationshipName "InvoiceLines",
|
||||
API.Relationship
|
||||
{ _rTargetTable = API.TableName "InvoiceLine",
|
||||
{ _rTargetTable = API.TableName ("InvoiceLine" :| []),
|
||||
_rRelationshipType = API.ArrayRelationship,
|
||||
_rColumnMapping = HashMap.fromList [(API.ColumnName "InvoiceId", API.ColumnName "InvoiceId")]
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ sourceMetadata =
|
||||
name : *source
|
||||
kind: *backendType
|
||||
tables:
|
||||
- table: Album
|
||||
- table: [Album]
|
||||
configuration:
|
||||
custom_root_fields:
|
||||
select: albums
|
||||
@ -63,10 +63,10 @@ sourceMetadata =
|
||||
- name: artist
|
||||
using:
|
||||
manual_configuration:
|
||||
remote_table: Artist
|
||||
remote_table: [Artist]
|
||||
column_mapping:
|
||||
ArtistId: ArtistId
|
||||
- table: Artist
|
||||
- table: [Artist]
|
||||
configuration:
|
||||
custom_root_fields:
|
||||
select: artists
|
||||
@ -80,7 +80,7 @@ sourceMetadata =
|
||||
- name: albums
|
||||
using:
|
||||
manual_configuration:
|
||||
remote_table: Album
|
||||
remote_table: [Album]
|
||||
column_mapping:
|
||||
ArtistId: ArtistId
|
||||
configuration: {}
|
||||
@ -123,7 +123,7 @@ tests opts = do
|
||||
{ _whenQuery =
|
||||
Just
|
||||
( API.QueryRequest
|
||||
{ _qrTable = API.TableName "Album",
|
||||
{ _qrTable = API.TableName ("Album" :| []),
|
||||
_qrTableRelationships = [],
|
||||
_qrQuery =
|
||||
API.Query
|
||||
@ -185,7 +185,7 @@ tests opts = do
|
||||
{ _whenQuery =
|
||||
Just
|
||||
( API.QueryRequest
|
||||
{ _qrTable = API.TableName "Album",
|
||||
{ _qrTable = API.TableName ("Album" :| []),
|
||||
_qrTableRelationships = [],
|
||||
_qrQuery =
|
||||
API.Query
|
||||
|
@ -43,20 +43,20 @@ sourceMetadata =
|
||||
name : *source
|
||||
kind: *backendType
|
||||
tables:
|
||||
- table: Genre
|
||||
- table: MediaType
|
||||
- table: Track
|
||||
- table: [Genre]
|
||||
- table: [MediaType]
|
||||
- table: [Track]
|
||||
object_relationships:
|
||||
- name: Genre
|
||||
using:
|
||||
manual_configuration:
|
||||
remote_table: Genre
|
||||
remote_table: [Genre]
|
||||
column_mapping:
|
||||
GenreId: GenreId
|
||||
- name: MediaType
|
||||
using:
|
||||
manual_configuration:
|
||||
remote_table: MediaType
|
||||
remote_table: [MediaType]
|
||||
column_mapping:
|
||||
MediaTypeId: MediaTypeId
|
||||
configuration: {}
|
||||
@ -118,22 +118,22 @@ tests opts = do
|
||||
{ _whenQuery =
|
||||
Just
|
||||
( API.QueryRequest
|
||||
{ _qrTable = API.TableName "Track",
|
||||
{ _qrTable = API.TableName ("Track" :| []),
|
||||
_qrTableRelationships =
|
||||
[ API.TableRelationships
|
||||
{ _trSourceTable = API.TableName "Track",
|
||||
{ _trSourceTable = API.TableName ("Track" :| []),
|
||||
_trRelationships =
|
||||
HashMap.fromList
|
||||
[ ( API.RelationshipName "Genre",
|
||||
API.Relationship
|
||||
{ _rTargetTable = API.TableName "Genre",
|
||||
{ _rTargetTable = API.TableName ("Genre" :| []),
|
||||
_rRelationshipType = API.ObjectRelationship,
|
||||
_rColumnMapping = HashMap.fromList [(API.ColumnName "GenreId", API.ColumnName "GenreId")]
|
||||
}
|
||||
),
|
||||
( API.RelationshipName "MediaType",
|
||||
API.Relationship
|
||||
{ _rTargetTable = API.TableName "MediaType",
|
||||
{ _rTargetTable = API.TableName ("MediaType" :| []),
|
||||
_rRelationshipType = API.ObjectRelationship,
|
||||
_rColumnMapping =
|
||||
HashMap.fromList
|
||||
|
@ -47,7 +47,7 @@ sourceMetadata =
|
||||
name : *source
|
||||
kind: *backendType
|
||||
tables:
|
||||
- table: Album
|
||||
- table: [Album]
|
||||
configuration:
|
||||
custom_root_fields:
|
||||
select: albums
|
||||
@ -63,10 +63,10 @@ sourceMetadata =
|
||||
- name: artist
|
||||
using:
|
||||
manual_configuration:
|
||||
remote_table: Artist
|
||||
remote_table: [Artist]
|
||||
column_mapping:
|
||||
ArtistId: ArtistId
|
||||
- table: Artist
|
||||
- table: [Artist]
|
||||
configuration:
|
||||
custom_root_fields:
|
||||
select: artists
|
||||
@ -80,7 +80,7 @@ sourceMetadata =
|
||||
- name: albums
|
||||
using:
|
||||
manual_configuration:
|
||||
remote_table: Album
|
||||
remote_table: [Album]
|
||||
column_mapping:
|
||||
ArtistId: ArtistId
|
||||
configuration:
|
||||
@ -132,7 +132,7 @@ tests opts = do
|
||||
{ _whenQuery =
|
||||
Just
|
||||
( API.QueryRequest
|
||||
{ _qrTable = API.TableName "Album",
|
||||
{ _qrTable = API.TableName ("Album" :| []),
|
||||
_qrTableRelationships = [],
|
||||
_qrQuery =
|
||||
API.Query
|
||||
|
@ -46,7 +46,7 @@ sourceMetadata =
|
||||
name : *source
|
||||
kind: *backendType
|
||||
tables:
|
||||
- table: Album
|
||||
- table: [Album]
|
||||
configuration:
|
||||
custom_root_fields:
|
||||
select: albums
|
||||
@ -62,10 +62,10 @@ tables:
|
||||
- name: artist
|
||||
using:
|
||||
manual_configuration:
|
||||
remote_table: Artist
|
||||
remote_table: [Artist]
|
||||
column_mapping:
|
||||
ArtistId: ArtistId
|
||||
- table: Artist
|
||||
- table: [Artist]
|
||||
configuration:
|
||||
custom_root_fields:
|
||||
select: artists
|
||||
@ -79,22 +79,22 @@ tables:
|
||||
- name: albums
|
||||
using:
|
||||
manual_configuration:
|
||||
remote_table: Album
|
||||
remote_table: [Album]
|
||||
column_mapping:
|
||||
ArtistId: ArtistId
|
||||
- table: Playlist
|
||||
- table: PlaylistTrack
|
||||
- table: [Playlist]
|
||||
- table: [PlaylistTrack]
|
||||
object_relationships:
|
||||
- name: Playlist
|
||||
using:
|
||||
manual_configuration:
|
||||
remote_table: Playlist
|
||||
remote_table: [Playlist]
|
||||
column_mapping:
|
||||
PlaylistId: PlaylistId
|
||||
- name: Track
|
||||
using:
|
||||
manual_configuration:
|
||||
remote_table: Track
|
||||
remote_table: [Track]
|
||||
column_mapping:
|
||||
TrackId: TrackId
|
||||
- table: Track
|
||||
|
@ -49,12 +49,12 @@ sourceMetadata =
|
||||
name : *source
|
||||
kind: *backendType
|
||||
tables:
|
||||
- table: Employee
|
||||
- table: [Employee]
|
||||
array_relationships:
|
||||
- name: SupportRepForCustomers
|
||||
using:
|
||||
manual_configuration:
|
||||
remote_table: Customer
|
||||
remote_table: [Customer]
|
||||
column_mapping:
|
||||
EmployeeId: SupportRepId
|
||||
select_permissions:
|
||||
@ -69,12 +69,12 @@ sourceMetadata =
|
||||
SupportRepForCustomers:
|
||||
Country:
|
||||
_ceq: [ "$", "Country" ]
|
||||
- table: Customer
|
||||
- table: [Customer]
|
||||
object_relationships:
|
||||
- name: SupportRep
|
||||
using:
|
||||
manual_configuration:
|
||||
remote_table: Employee
|
||||
remote_table: [Employee]
|
||||
column_mapping:
|
||||
SupportRepId: EmployeeId
|
||||
select_permissions:
|
||||
|
Loading…
Reference in New Issue
Block a user