From c02900f0de8dc5d11c7429ae51a84dad5d56e724 Mon Sep 17 00:00:00 2001 From: adrinr Date: Wed, 22 Feb 2023 22:40:50 +0100 Subject: [PATCH] Setup o2m and m2m relationships --- .tool-versions | 2 +- .../api/controllers/row/ExternalRequest.ts | 32 ++--- .../src/integration-test/postgres.spec.ts | 134 +++++++++++------- 3 files changed, 100 insertions(+), 68 deletions(-) diff --git a/.tool-versions b/.tool-versions index 8a1af3c071..6ee8cc60be 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ nodejs 14.19.3 -python 3.11.1 \ No newline at end of file +python 3.10.0 \ No newline at end of file diff --git a/packages/server/src/api/controllers/row/ExternalRequest.ts b/packages/server/src/api/controllers/row/ExternalRequest.ts index 1510e31881..79a598edf0 100644 --- a/packages/server/src/api/controllers/row/ExternalRequest.ts +++ b/packages/server/src/api/controllers/row/ExternalRequest.ts @@ -142,7 +142,11 @@ function cleanupConfig(config: RunConfig, table: Table): RunConfig { return config } -function generateIdForRow(row: Row | undefined, table: Table): string { +function generateIdForRow( + row: Row | undefined, + table: Table, + isLinked: boolean = false +): string { const primary = table.primary if (!row || !primary) { return "" @@ -151,7 +155,10 @@ function generateIdForRow(row: Row | undefined, table: Table): string { let idParts = [] for (let field of primary) { // need to handle table name + field or just field, depending on if relationships used - const fieldValue = row[`${table.name}.${field}`] || row[field] + let fieldValue = row[`${table.name}.${field}`] + if (!fieldValue && !isLinked) { + fieldValue = row[field] + } if (fieldValue) { idParts.push(fieldValue) } @@ -174,23 +181,20 @@ function getEndpoint(tableId: string | undefined, operation: string) { } } -function basicProcessing(row: Row, table: Table): Row { +function basicProcessing(row: Row, table: Table, isLinked: boolean): Row { const thisRow: Row = {} // filter the row down to what is actually the row (not joined) for (let field of Object.values(table.schema)) { const fieldName = field.name const pathValue = row[`${table.name}.${fieldName}`] - const value = - pathValue != null || field.type === FieldTypes.LINK - ? pathValue - : row[fieldName] + const value = pathValue != null || isLinked ? pathValue : row[fieldName] // all responses include "select col as table.col" so that overlaps are handled if (value != null) { thisRow[fieldName] = value } } - thisRow._id = generateIdForRow(row, table) + thisRow._id = generateIdForRow(row, table, isLinked) thisRow.tableId = table._id thisRow._rev = "rev" return processFormulas(table, thisRow) @@ -385,15 +389,7 @@ export class ExternalRequest { continue } - if ( - relationship.from && - row[fromColumn] === undefined && - row[relationship.from] === null - ) { - continue - } - - let linked = basicProcessing(row, linkedTable) + let linked = basicProcessing(row, linkedTable, true) if (!linked._id) { continue } @@ -441,7 +437,7 @@ export class ExternalRequest { ) continue } - const thisRow = fixArrayTypes(basicProcessing(row, table), table) + const thisRow = fixArrayTypes(basicProcessing(row, table, false), table) if (thisRow._id == null) { throw "Unable to generate row ID for SQL rows" } diff --git a/packages/server/src/integration-test/postgres.spec.ts b/packages/server/src/integration-test/postgres.spec.ts index c688600e8d..cc8760023f 100644 --- a/packages/server/src/integration-test/postgres.spec.ts +++ b/packages/server/src/integration-test/postgres.spec.ts @@ -23,11 +23,19 @@ jest.setTimeout(30000) jest.unmock("pg") +interface RandomForeignKeyConfig { + createOne2Many?: boolean + createMany2One?: number + createMany2Many?: number +} + describe("row api - postgres", () => { let makeRequest: MakeRequestResponse, postgresDatasource: Datasource, primaryPostgresTable: Table, - auxPostgresTable: Table + o2mInfo: { table: Table; fieldName: string }, + m2oInfo: { table: Table; fieldName: string }, + m2mInfo: { table: Table; fieldName: string } let host: string let port: number @@ -67,31 +75,46 @@ describe("row api - postgres", () => { }, }) - auxPostgresTable = await config.createTable({ - name: generator.word({ length: 10 }), - type: "external", - primary: ["id"], - schema: { - id: { - name: "id", - type: FieldType.AUTO, - constraints: { - presence: true, + async function createAuxTable(prefix: string) { + return await config.createTable({ + name: `${prefix}_${generator.word({ length: 6 })}`, + type: "external", + primary: ["id"], + schema: { + id: { + name: "id", + type: FieldType.AUTO, + constraints: { + presence: true, + }, + }, + title: { + name: "title", + type: FieldType.STRING, + constraints: { + presence: true, + }, }, }, - title: { - name: "title", - type: FieldType.STRING, - constraints: { - presence: true, - }, - }, - }, - sourceId: postgresDatasource._id, - }) + sourceId: postgresDatasource._id, + }) + } + + o2mInfo = { + table: await createAuxTable("o2m"), + fieldName: "oneToManyRelation", + } + m2oInfo = { + table: await createAuxTable("m2o"), + fieldName: "manyToOneRelation", + } + m2mInfo = { + table: await createAuxTable("m2m"), + fieldName: "manyToManyRelation", + } primaryPostgresTable = await config.createTable({ - name: generator.word({ length: 10 }), + name: `p_${generator.word({ length: 6 })}`, type: "external", primary: ["id"], schema: { @@ -117,25 +140,45 @@ describe("row api - postgres", () => { name: "value", type: FieldType.NUMBER, }, - linkedField: { + oneToManyRelation: { type: FieldType.LINK, constraints: { type: "array", presence: false, }, - fieldName: "foreignField", - name: "linkedField", + fieldName: o2mInfo.fieldName, + name: "oneToManyRelation", relationshipType: RelationshipTypes.ONE_TO_MANY, - tableId: auxPostgresTable._id, + tableId: o2mInfo.table._id, + }, + manyToOneRelation: { + type: FieldType.LINK, + constraints: { + type: "array", + presence: false, + }, + fieldName: m2oInfo.fieldName, + name: "manyToOneRelation", + relationshipType: RelationshipTypes.MANY_TO_ONE, + tableId: m2oInfo.table._id, + }, + manyToManyRelation: { + type: FieldType.LINK, + constraints: { + type: "array", + presence: false, + }, + fieldName: m2mInfo.fieldName, + name: "manyToManyRelation", + relationshipType: RelationshipTypes.MANY_TO_MANY, + tableId: m2mInfo.table._id, }, }, sourceId: postgresDatasource._id, }) }) - afterAll(async () => { - await config.end() - }) + afterAll(config.end) function generateRandomPrimaryRowData() { return { @@ -153,19 +196,20 @@ describe("row api - postgres", () => { async function createPrimaryRow(opts: { rowData: PrimaryRowData - createForeignRow?: boolean + createForeignRows?: RandomForeignKeyConfig }) { let { rowData } = opts let foreignRow: Row | undefined - if (opts?.createForeignRow) { + + if (opts?.createForeignRows?.createOne2Many) { foreignRow = await config.createRow({ - tableId: auxPostgresTable._id, + tableId: o2mInfo.table._id, title: generator.name(), }) rowData = { ...rowData, - [`fk_${auxPostgresTable.name}_foreignField`]: foreignRow.id, + [`fk_${o2mInfo.table.name}_${o2mInfo.fieldName}`]: foreignRow.id, } } @@ -197,9 +241,7 @@ describe("row api - postgres", () => { async function populatePrimaryRows( count: number, - opts?: { - createForeignRow?: boolean - } + opts?: RandomForeignKeyConfig ) { return await Promise.all( Array(count) @@ -210,7 +252,7 @@ describe("row api - postgres", () => { rowData, ...(await createPrimaryRow({ rowData, - createForeignRow: opts?.createForeignRow, + createForeignRows: opts, })), } }) @@ -295,7 +337,7 @@ describe("row api - postgres", () => { describe("given than a row exists", () => { let row: Row beforeEach(async () => { - let rowResponse = _.sample(await populatePrimaryRows(10))! + let rowResponse = _.sample(await populatePrimaryRows(1))! row = rowResponse.row }) @@ -422,7 +464,7 @@ describe("row api - postgres", () => { let foreignRow: Row beforeEach(async () => { let [createdRow] = await populatePrimaryRows(1, { - createForeignRow: true, + createOne2Many: true, }) row = createdRow.row foreignRow = createdRow.foreignRow! @@ -437,16 +479,10 @@ describe("row api - postgres", () => { ...row, _id: expect.any(String), _rev: expect.any(String), + [`fk_${o2mInfo.table.name}_${o2mInfo.fieldName}`]: foreignRow.id, }) - expect(res.body.foreignField).toBeUndefined() - - expect( - res.body[`fk_${auxPostgresTable.name}_foreignField`] - ).toBeDefined() - expect(res.body[`fk_${auxPostgresTable.name}_foreignField`]).toBe( - foreignRow.id - ) + expect(res.body[o2mInfo.fieldName]).toBeUndefined() }) }) }) @@ -672,7 +708,7 @@ describe("row api - postgres", () => { beforeEach(async () => { const rowsInfo = await createPrimaryRow({ rowData: generateRandomPrimaryRowData(), - createForeignRow: true, + createForeignRows: { createOne2Many: true }, }) row = rowsInfo.row @@ -687,7 +723,7 @@ describe("row api - postgres", () => { expect(foreignRow).toBeDefined() expect(res.body).toEqual({ ...row, - linkedField: [ + [o2mInfo.fieldName]: [ { ...foreignRow, },