Handle relationships properly

This commit is contained in:
Adria Navarro 2024-08-16 09:36:31 +02:00
parent d8462ba961
commit bbf7142bd7
2 changed files with 86 additions and 65 deletions

View File

@ -56,10 +56,10 @@ export const getQueryableFields = async (
fields: string[], fields: string[],
table: Table table: Table
): Promise<string[]> => { ): Promise<string[]> => {
const handledTables = new Set<string>([table._id!])
const extractTableFields = async ( const extractTableFields = async (
table: Table, table: Table,
allowedFields: string[] allowedFields: string[],
fromTables: string[]
): Promise<string[]> => { ): Promise<string[]> => {
const result = [] const result = []
for (const field of Object.keys(table.schema).filter( for (const field of Object.keys(table.schema).filter(
@ -67,15 +67,15 @@ export const getQueryableFields = async (
)) { )) {
const subSchema = table.schema[field] const subSchema = table.schema[field]
if (subSchema.type === FieldType.LINK) { if (subSchema.type === FieldType.LINK) {
if (handledTables.has(`${table._id}_${subSchema.tableId}`)) { if (fromTables.includes(subSchema.tableId)) {
// avoid circular loops // avoid circular loops
continue continue
} }
handledTables.add(`${table._id}_${subSchema.tableId}`)
const relatedTable = await sdk.tables.getTable(subSchema.tableId) const relatedTable = await sdk.tables.getTable(subSchema.tableId)
const relatedFields = await extractTableFields( const relatedFields = await extractTableFields(
relatedTable, relatedTable,
Object.keys(relatedTable.schema) Object.keys(relatedTable.schema),
[...fromTables, subSchema.tableId]
) )
result.push( result.push(
@ -96,7 +96,7 @@ export const getQueryableFields = async (
"_id", // Querying by _id is always allowed, even if it's never part of the schema "_id", // Querying by _id is always allowed, even if it's never part of the schema
] ]
result.push(...(await extractTableFields(table, fields))) result.push(...(await extractTableFields(table, fields, [table._id!])))
return result return result
} }

View File

@ -195,26 +195,26 @@ describe("query utils", () => {
}) })
it("returns table schema fields and _id", async () => { it("returns table schema fields and _id", async () => {
const table: Table = { const table: Table = await config.api.table.save({
...structures.basicTable(), ...structures.basicTable(),
schema: { schema: {
name: { name: "name", type: FieldType.STRING }, name: { name: "name", type: FieldType.STRING },
age: { name: "age", type: FieldType.NUMBER }, age: { name: "age", type: FieldType.NUMBER },
}, },
} })
const result = await getQueryableFields(Object.keys(table.schema), table) const result = await getQueryableFields(Object.keys(table.schema), table)
expect(result).toEqual(["_id", "name", "age"]) expect(result).toEqual(["_id", "name", "age"])
}) })
it("excludes hidden fields", async () => { it("excludes hidden fields", async () => {
const table: Table = { const table: Table = await config.api.table.save({
...structures.basicTable(), ...structures.basicTable(),
schema: { schema: {
name: { name: "name", type: FieldType.STRING }, name: { name: "name", type: FieldType.STRING },
age: { name: "age", type: FieldType.NUMBER, visible: false }, age: { name: "age", type: FieldType.NUMBER, visible: false },
}, },
} })
const result = await getQueryableFields(Object.keys(table.schema), table) const result = await getQueryableFields(Object.keys(table.schema), table)
expect(result).toEqual(["_id", "name"]) expect(result).toEqual(["_id", "name"])
@ -230,7 +230,7 @@ describe("query utils", () => {
}, },
}) })
const table: Table = { const table: Table = await config.api.table.save({
...structures.basicTable(), ...structures.basicTable(),
schema: { schema: {
name: { name: "name", type: FieldType.STRING }, name: { name: "name", type: FieldType.STRING },
@ -242,7 +242,7 @@ describe("query utils", () => {
fieldName: "table", fieldName: "table",
}, },
}, },
} })
const result = await config.doInContext(config.appId, () => { const result = await config.doInContext(config.appId, () => {
return getQueryableFields(Object.keys(table.schema), table) return getQueryableFields(Object.keys(table.schema), table)
@ -267,7 +267,7 @@ describe("query utils", () => {
}, },
}) })
const table: Table = { const table: Table = await config.api.table.save({
...structures.basicTable(), ...structures.basicTable(),
schema: { schema: {
name: { name: "name", type: FieldType.STRING }, name: { name: "name", type: FieldType.STRING },
@ -279,7 +279,7 @@ describe("query utils", () => {
fieldName: "table", fieldName: "table",
}, },
}, },
} })
const result = await config.doInContext(config.appId, () => { const result = await config.doInContext(config.appId, () => {
return getQueryableFields(Object.keys(table.schema), table) return getQueryableFields(Object.keys(table.schema), table)
@ -297,7 +297,7 @@ describe("query utils", () => {
}, },
}) })
const table: Table = { const table: Table = await config.api.table.save({
...structures.basicTable(), ...structures.basicTable(),
schema: { schema: {
name: { name: "name", type: FieldType.STRING }, name: { name: "name", type: FieldType.STRING },
@ -310,7 +310,7 @@ describe("query utils", () => {
visible: false, visible: false,
}, },
}, },
} })
const result = await config.doInContext(config.appId, () => { const result = await config.doInContext(config.appId, () => {
return getQueryableFields(Object.keys(table.schema), table) return getQueryableFields(Object.keys(table.schema), table)
@ -318,61 +318,82 @@ describe("query utils", () => {
expect(result).toEqual(["_id", "name"]) expect(result).toEqual(["_id", "name"])
}) })
it("includes nested relationship fields", async () => { describe("nested relationship", () => {
const aux1: Table = await config.api.table.save({ let table: Table, aux1: Table, aux2: Table
beforeAll(async () => {
aux1 = await config.api.table.save({
...structures.basicTable(), ...structures.basicTable(),
name: "aux1Table", name: "aux1Table",
schema: { schema: {
name: { name: "name", type: FieldType.STRING }, name: { name: "name", type: FieldType.STRING },
}, },
}) })
const aux2: Table = await config.api.table.save({ aux2 = await config.api.table.save({
...structures.basicTable(), ...structures.basicTable(),
name: "aux2Table", name: "aux2Table",
schema: { schema: {
title: { name: "title", type: FieldType.STRING }, title: { name: "title", type: FieldType.STRING },
aux1_1: {
name: "aux1_1",
type: FieldType.LINK,
tableId: aux1._id!,
relationshipType: RelationshipType.ONE_TO_MANY,
fieldName: "aux2_1",
},
},
})
table = await config.api.table.save({
...structures.basicTable(),
schema: {
name: { name: "name", type: FieldType.STRING },
aux1: { aux1: {
name: "aux1", name: "aux1",
type: FieldType.LINK, type: FieldType.LINK,
tableId: aux1._id!, tableId: aux1._id!,
relationshipType: RelationshipType.ONE_TO_MANY, relationshipType: RelationshipType.ONE_TO_MANY,
fieldName: "aux2", fieldName: "table",
}, },
}, aux2: {
}) name: "aux2",
const table: Table = {
...structures.basicTable(),
schema: {
name: { name: "name", type: FieldType.STRING },
aux: {
name: "aux",
type: FieldType.LINK, type: FieldType.LINK,
tableId: aux2._id!, tableId: aux2._id!,
relationshipType: RelationshipType.ONE_TO_MANY, relationshipType: RelationshipType.ONE_TO_MANY,
fieldName: "table", fieldName: "table",
}, },
}, },
} })
})
it("includes nested relationship fields from main table", async () => {
const result = await config.doInContext(config.appId, () => { const result = await config.doInContext(config.appId, () => {
return getQueryableFields(Object.keys(table.schema), table) return getQueryableFields(Object.keys(table.schema), table)
}) })
expect(result).toEqual([ expect(result).toEqual([
"_id", "_id",
"name", "name",
// Aux primitive props // deep 1 aux1 primitive props
"aux.title", "aux1.name",
"aux1Table.name",
// deep 2 aux1 primitive props
"aux1.aux2_1.title",
"aux1Table.aux2_1.title",
"aux1.aux2Table.title",
"aux1Table.aux2Table.title",
// deep 1 aux2 primitive props
"aux2.title",
"aux2Table.title", "aux2Table.title",
// Aux deep 1 primitive props // deep 2 aux2 primitive props
"aux.aux1.name", "aux2.aux1_1.name",
"aux2Table.aux1.name", "aux2Table.aux1_1.name",
"aux2.aux1Table.name",
// Aux deep 2 primitive props
"aux.aux1Table.name",
"aux2Table.aux1Table.name", "aux2Table.aux1Table.name",
]) ])
}) })
}) })
}) })
})