Merge pull request #2857 from Budibase/fix/2792

Allow SQL many to many relationships to use arbitrarily named columns in junction table
This commit is contained in:
Martin McKeaveney 2021-10-04 12:42:56 +01:00 committed by GitHub
commit 553bd63842
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 107 additions and 9 deletions

View File

@ -156,6 +156,8 @@
...relateTo, ...relateTo,
through: through._id, through: through._id,
fieldName: fromTable.primary[0], fieldName: fromTable.primary[0],
throughFrom: relateFrom.throughTo,
throughTo: relateFrom.throughFrom,
} }
} else { } else {
// the relateFrom.fieldName should remain the same, as it is the foreignKey in the other // the relateFrom.fieldName should remain the same, as it is the foreignKey in the other
@ -251,6 +253,22 @@
bind:error={errors.through} bind:error={errors.through}
bind:value={fromRelationship.through} bind:value={fromRelationship.through}
/> />
{#if fromTable && toTable && through}
<Select
label={`Foreign Key (${fromTable?.name})`}
options={Object.keys(through?.schema)}
on:change={() => ($touched.fromForeign = true)}
bind:error={errors.fromForeign}
bind:value={fromRelationship.throughTo}
/>
<Select
label={`Foreign Key (${toTable?.name})`}
options={Object.keys(through?.schema)}
on:change={() => ($touched.toForeign = true)}
bind:error={errors.toForeign}
bind:value={fromRelationship.throughFrom}
/>
{/if}
{:else if fromRelationship?.relationshipType && toTable} {:else if fromRelationship?.relationshipType && toTable}
<Select <Select
label={`Foreign Key (${toTable?.name})`} label={`Foreign Key (${toTable?.name})`}

View File

@ -0,0 +1,28 @@
version: "3.8"
services:
db:
container_name: postgres
image: postgres
restart: always
environment:
POSTGRES_USER: root
POSTGRES_PASSWORD: root
POSTGRES_DB: main
ports:
- "5432:5432"
volumes:
#- pg_data:/var/lib/postgresql/data/
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
pgadmin:
container_name: pgadmin-pg
image: dpage/pgadmin4
restart: always
environment:
PGADMIN_DEFAULT_EMAIL: root@root.com
PGADMIN_DEFAULT_PASSWORD: root
ports:
- "5050:80"
#volumes:
# pg_data:

View File

@ -0,0 +1,41 @@
SELECT 'CREATE DATABASE main'
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'main')\gexec
CREATE TABLE categories
(
name text COLLATE pg_catalog."default",
id integer NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1 ),
CONSTRAINT categories_pkey PRIMARY KEY (id)
);
CREATE TABLE customers
(
id integer NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1 ),
name text COLLATE pg_catalog."default",
email text COLLATE pg_catalog."default",
age integer,
"dateOfBirth" date,
CONSTRAINT customers_pkey PRIMARY KEY (id)
);
CREATE TABLE customer_category
(
customer_id integer,
category_id integer,
notes text COLLATE pg_catalog."default",
id integer NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1 ),
CONSTRAINT "Category" FOREIGN KEY (category_id)
REFERENCES public.categories (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
NOT VALID,
CONSTRAINT "Customer" FOREIGN KEY (customer_id)
REFERENCES public.customers (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
NOT VALID
);
INSERT INTO customers (name, email, age) VALUES ('Mike', 'mike@mike.com', 30);
INSERT INTO categories (name) VALUES ('Books');

View File

@ -0,0 +1,3 @@
#!/bin/bash
docker-compose down
docker volume prune -f

View File

@ -205,9 +205,9 @@ module External {
} else { } else {
// we're not inserting a doc, will be a bunch of update calls // we're not inserting a doc, will be a bunch of update calls
const isUpdate = !field.through const isUpdate = !field.through
const thisKey: string = isUpdate ? "id" : linkTablePrimary const thisKey: string = isUpdate ? "id" : (field.throughTo || linkTablePrimary)
// @ts-ignore // @ts-ignore
const otherKey: string = isUpdate ? field.fieldName : tablePrimary const otherKey: string = isUpdate ? field.fieldName : (field.throughFrom || tablePrimary)
row[key].map((relationship: any) => { row[key].map((relationship: any) => {
// we don't really support composite keys for relationships, this is why [0] is used // we don't really support composite keys for relationships, this is why [0] is used
manyRelationships.push({ manyRelationships.push({
@ -328,12 +328,11 @@ module External {
if (!table.primary || !linkTable.primary) { if (!table.primary || !linkTable.primary) {
continue continue
} }
const definition = { const definition: any = {
// if no foreign key specified then use the name of the field in other table // if no foreign key specified then use the name of the field in other table
from: field.foreignKey || table.primary[0], from: field.foreignKey || table.primary[0],
to: field.fieldName, to: field.fieldName,
tableName: linkTableName, tableName: linkTableName,
through: undefined,
// need to specify where to put this back into // need to specify where to put this back into
column: fieldName, column: fieldName,
} }
@ -343,8 +342,10 @@ module External {
) )
definition.through = throughTableName definition.through = throughTableName
// don't support composite keys for relationships // don't support composite keys for relationships
definition.from = table.primary[0] definition.from = field.throughFrom || table.primary[0]
definition.to = linkTable.primary[0] definition.to = field.throughTo || linkTable.primary[0]
definition.fromPrimary = table.primary[0]
definition.toPrimary = linkTable.primary[0]
} }
relationships.push(definition) relationships.push(definition)
} }
@ -369,7 +370,8 @@ module External {
} }
const isMany = field.relationshipType === RelationshipTypes.MANY_TO_MANY const isMany = field.relationshipType === RelationshipTypes.MANY_TO_MANY
const tableId = isMany ? field.through : field.tableId const tableId = isMany ? field.through : field.tableId
const fieldName = isMany ? primaryKey : field.fieldName const manyKey = field.throughFrom || primaryKey
const fieldName = isMany ? manyKey : field.fieldName
const response = await makeExternalQuery(this.appId, { const response = await makeExternalQuery(this.appId, {
endpoint: getEndpoint(tableId, DataSourceOperation.READ), endpoint: getEndpoint(tableId, DataSourceOperation.READ),
filters: { filters: {

View File

@ -15,6 +15,8 @@ export interface FieldSchema {
through?: string through?: string
foreignKey?: string foreignKey?: string
autocolumn?: boolean autocolumn?: boolean
throughFrom?: string
throughTo?: string
constraints?: { constraints?: {
type?: string type?: string
email?: boolean email?: boolean

View File

@ -121,6 +121,8 @@ export interface RelationshipsJson {
through?: string through?: string
from?: string from?: string
to?: string to?: string
fromPrimary?: string
toPrimary?: string
tableName: string tableName: string
column: string column: string
} }

View File

@ -112,14 +112,16 @@ function addRelationships(
) )
} else { } else {
const throughTable = relationship.through const throughTable = relationship.through
const fromPrimary = relationship.fromPrimary
const toPrimary = relationship.toPrimary
query = query query = query
// @ts-ignore // @ts-ignore
.leftJoin( .leftJoin(
throughTable, throughTable,
`${fromTable}.${from}`, `${fromTable}.${fromPrimary}`,
`${throughTable}.${from}` `${throughTable}.${from}`
) )
.leftJoin(toTable, `${toTable}.${to}`, `${throughTable}.${to}`) .leftJoin(toTable, `${toTable}.${toPrimary}`, `${throughTable}.${to}`)
} }
} }
return query return query