Setup o2m and m2m relationships

This commit is contained in:
adrinr 2023-02-22 22:40:50 +01:00
parent d0fe2691ca
commit c02900f0de
3 changed files with 100 additions and 68 deletions

View File

@ -1,2 +1,2 @@
nodejs 14.19.3 nodejs 14.19.3
python 3.11.1 python 3.10.0

View File

@ -142,7 +142,11 @@ function cleanupConfig(config: RunConfig, table: Table): RunConfig {
return config 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 const primary = table.primary
if (!row || !primary) { if (!row || !primary) {
return "" return ""
@ -151,7 +155,10 @@ function generateIdForRow(row: Row | undefined, table: Table): string {
let idParts = [] let idParts = []
for (let field of primary) { for (let field of primary) {
// need to handle table name + field or just field, depending on if relationships used // 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) { if (fieldValue) {
idParts.push(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 = {} const thisRow: Row = {}
// filter the row down to what is actually the row (not joined) // filter the row down to what is actually the row (not joined)
for (let field of Object.values(table.schema)) { for (let field of Object.values(table.schema)) {
const fieldName = field.name const fieldName = field.name
const pathValue = row[`${table.name}.${fieldName}`] const pathValue = row[`${table.name}.${fieldName}`]
const value = const value = pathValue != null || isLinked ? pathValue : row[fieldName]
pathValue != null || field.type === FieldTypes.LINK
? pathValue
: row[fieldName]
// all responses include "select col as table.col" so that overlaps are handled // all responses include "select col as table.col" so that overlaps are handled
if (value != null) { if (value != null) {
thisRow[fieldName] = value thisRow[fieldName] = value
} }
} }
thisRow._id = generateIdForRow(row, table) thisRow._id = generateIdForRow(row, table, isLinked)
thisRow.tableId = table._id thisRow.tableId = table._id
thisRow._rev = "rev" thisRow._rev = "rev"
return processFormulas(table, thisRow) return processFormulas(table, thisRow)
@ -385,15 +389,7 @@ export class ExternalRequest {
continue continue
} }
if ( let linked = basicProcessing(row, linkedTable, true)
relationship.from &&
row[fromColumn] === undefined &&
row[relationship.from] === null
) {
continue
}
let linked = basicProcessing(row, linkedTable)
if (!linked._id) { if (!linked._id) {
continue continue
} }
@ -441,7 +437,7 @@ export class ExternalRequest {
) )
continue continue
} }
const thisRow = fixArrayTypes(basicProcessing(row, table), table) const thisRow = fixArrayTypes(basicProcessing(row, table, false), table)
if (thisRow._id == null) { if (thisRow._id == null) {
throw "Unable to generate row ID for SQL rows" throw "Unable to generate row ID for SQL rows"
} }

View File

@ -23,11 +23,19 @@ jest.setTimeout(30000)
jest.unmock("pg") jest.unmock("pg")
interface RandomForeignKeyConfig {
createOne2Many?: boolean
createMany2One?: number
createMany2Many?: number
}
describe("row api - postgres", () => { describe("row api - postgres", () => {
let makeRequest: MakeRequestResponse, let makeRequest: MakeRequestResponse,
postgresDatasource: Datasource, postgresDatasource: Datasource,
primaryPostgresTable: Table, primaryPostgresTable: Table,
auxPostgresTable: Table o2mInfo: { table: Table; fieldName: string },
m2oInfo: { table: Table; fieldName: string },
m2mInfo: { table: Table; fieldName: string }
let host: string let host: string
let port: number let port: number
@ -67,31 +75,46 @@ describe("row api - postgres", () => {
}, },
}) })
auxPostgresTable = await config.createTable({ async function createAuxTable(prefix: string) {
name: generator.word({ length: 10 }), return await config.createTable({
type: "external", name: `${prefix}_${generator.word({ length: 6 })}`,
primary: ["id"], type: "external",
schema: { primary: ["id"],
id: { schema: {
name: "id", id: {
type: FieldType.AUTO, name: "id",
constraints: { type: FieldType.AUTO,
presence: true, constraints: {
presence: true,
},
},
title: {
name: "title",
type: FieldType.STRING,
constraints: {
presence: true,
},
}, },
}, },
title: { sourceId: postgresDatasource._id,
name: "title", })
type: FieldType.STRING, }
constraints: {
presence: true, o2mInfo = {
}, table: await createAuxTable("o2m"),
}, fieldName: "oneToManyRelation",
}, }
sourceId: postgresDatasource._id, m2oInfo = {
}) table: await createAuxTable("m2o"),
fieldName: "manyToOneRelation",
}
m2mInfo = {
table: await createAuxTable("m2m"),
fieldName: "manyToManyRelation",
}
primaryPostgresTable = await config.createTable({ primaryPostgresTable = await config.createTable({
name: generator.word({ length: 10 }), name: `p_${generator.word({ length: 6 })}`,
type: "external", type: "external",
primary: ["id"], primary: ["id"],
schema: { schema: {
@ -117,25 +140,45 @@ describe("row api - postgres", () => {
name: "value", name: "value",
type: FieldType.NUMBER, type: FieldType.NUMBER,
}, },
linkedField: { oneToManyRelation: {
type: FieldType.LINK, type: FieldType.LINK,
constraints: { constraints: {
type: "array", type: "array",
presence: false, presence: false,
}, },
fieldName: "foreignField", fieldName: o2mInfo.fieldName,
name: "linkedField", name: "oneToManyRelation",
relationshipType: RelationshipTypes.ONE_TO_MANY, 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, sourceId: postgresDatasource._id,
}) })
}) })
afterAll(async () => { afterAll(config.end)
await config.end()
})
function generateRandomPrimaryRowData() { function generateRandomPrimaryRowData() {
return { return {
@ -153,19 +196,20 @@ describe("row api - postgres", () => {
async function createPrimaryRow(opts: { async function createPrimaryRow(opts: {
rowData: PrimaryRowData rowData: PrimaryRowData
createForeignRow?: boolean createForeignRows?: RandomForeignKeyConfig
}) { }) {
let { rowData } = opts let { rowData } = opts
let foreignRow: Row | undefined let foreignRow: Row | undefined
if (opts?.createForeignRow) {
if (opts?.createForeignRows?.createOne2Many) {
foreignRow = await config.createRow({ foreignRow = await config.createRow({
tableId: auxPostgresTable._id, tableId: o2mInfo.table._id,
title: generator.name(), title: generator.name(),
}) })
rowData = { rowData = {
...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( async function populatePrimaryRows(
count: number, count: number,
opts?: { opts?: RandomForeignKeyConfig
createForeignRow?: boolean
}
) { ) {
return await Promise.all( return await Promise.all(
Array(count) Array(count)
@ -210,7 +252,7 @@ describe("row api - postgres", () => {
rowData, rowData,
...(await createPrimaryRow({ ...(await createPrimaryRow({
rowData, rowData,
createForeignRow: opts?.createForeignRow, createForeignRows: opts,
})), })),
} }
}) })
@ -295,7 +337,7 @@ describe("row api - postgres", () => {
describe("given than a row exists", () => { describe("given than a row exists", () => {
let row: Row let row: Row
beforeEach(async () => { beforeEach(async () => {
let rowResponse = _.sample(await populatePrimaryRows(10))! let rowResponse = _.sample(await populatePrimaryRows(1))!
row = rowResponse.row row = rowResponse.row
}) })
@ -422,7 +464,7 @@ describe("row api - postgres", () => {
let foreignRow: Row let foreignRow: Row
beforeEach(async () => { beforeEach(async () => {
let [createdRow] = await populatePrimaryRows(1, { let [createdRow] = await populatePrimaryRows(1, {
createForeignRow: true, createOne2Many: true,
}) })
row = createdRow.row row = createdRow.row
foreignRow = createdRow.foreignRow! foreignRow = createdRow.foreignRow!
@ -437,16 +479,10 @@ describe("row api - postgres", () => {
...row, ...row,
_id: expect.any(String), _id: expect.any(String),
_rev: expect.any(String), _rev: expect.any(String),
[`fk_${o2mInfo.table.name}_${o2mInfo.fieldName}`]: foreignRow.id,
}) })
expect(res.body.foreignField).toBeUndefined() expect(res.body[o2mInfo.fieldName]).toBeUndefined()
expect(
res.body[`fk_${auxPostgresTable.name}_foreignField`]
).toBeDefined()
expect(res.body[`fk_${auxPostgresTable.name}_foreignField`]).toBe(
foreignRow.id
)
}) })
}) })
}) })
@ -672,7 +708,7 @@ describe("row api - postgres", () => {
beforeEach(async () => { beforeEach(async () => {
const rowsInfo = await createPrimaryRow({ const rowsInfo = await createPrimaryRow({
rowData: generateRandomPrimaryRowData(), rowData: generateRandomPrimaryRowData(),
createForeignRow: true, createForeignRows: { createOne2Many: true },
}) })
row = rowsInfo.row row = rowsInfo.row
@ -687,7 +723,7 @@ describe("row api - postgres", () => {
expect(foreignRow).toBeDefined() expect(foreignRow).toBeDefined()
expect(res.body).toEqual({ expect(res.body).toEqual({
...row, ...row,
linkedField: [ [o2mInfo.fieldName]: [
{ {
...foreignRow, ...foreignRow,
}, },