diff --git a/packages/nocodb/src/lib/noco-models/Column.ts b/packages/nocodb/src/lib/noco-models/Column.ts index bd1b06f3c3..f4e6cb1c3d 100644 --- a/packages/nocodb/src/lib/noco-models/Column.ts +++ b/packages/nocodb/src/lib/noco-models/Column.ts @@ -207,7 +207,9 @@ export default class Column implements ColumnType { dr: column.dr, fk_index_name: column.fk_index_name, - fk_related_model_id: column.fk_related_model_id + fk_related_model_id: column.fk_related_model_id, + + virtual: column.virtual }, ncMeta ); diff --git a/packages/nocodb/src/lib/noco-models/LinkToAnotherRecordColumn.ts b/packages/nocodb/src/lib/noco-models/LinkToAnotherRecordColumn.ts index 2d206e8c5c..8064288c1f 100644 --- a/packages/nocodb/src/lib/noco-models/LinkToAnotherRecordColumn.ts +++ b/packages/nocodb/src/lib/noco-models/LinkToAnotherRecordColumn.ts @@ -107,7 +107,8 @@ export default class LinkToAnotherRecordColumn { dr: data.dr, fk_index_name: data.fk_index_name, - fk_related_model_id: data.fk_related_model_id + fk_related_model_id: data.fk_related_model_id, + virtual: data.virtual }); return this.read(data.fk_column_id, ncMeta); } diff --git a/packages/nocodb/src/lib/noco/meta/api/columnApis.ts b/packages/nocodb/src/lib/noco/meta/api/columnApis.ts index 04137359c0..4b1d8dc84d 100644 --- a/packages/nocodb/src/lib/noco/meta/api/columnApis.ts +++ b/packages/nocodb/src/lib/noco/meta/api/columnApis.ts @@ -45,6 +45,7 @@ async function createHmAndBtColumn( childColumn: Column, type?: RelationTypes, alias?: string, + virtual = false, isSystemCol = false ) { // save bt column @@ -65,6 +66,7 @@ async function createHmAndBtColumn( fk_child_column_id: childColumn.id, fk_parent_column_id: parent.primaryKey.id, fk_related_model_id: parent.id, + virtual, system: isSystemCol }); } @@ -82,6 +84,7 @@ async function createHmAndBtColumn( fk_child_column_id: childColumn.id, fk_parent_column_id: parent.primaryKey.id, fk_related_model_id: child.id, + virtual, system: isSystemCol }); } @@ -275,23 +278,27 @@ export async function columnAdd(req: Request, res: Response) { childColumn = await Column.get({ colId: id }); - // create relation - await sqlMgr.sqlOpPlus(base, 'relationCreate', { - childColumn: fkColName, - childTable: child.table_name, - parentTable: parent.table_name, - onDelete: 'NO ACTION', - onUpdate: 'NO ACTION', - type: 'real', - parentColumn: parent.primaryKey.column_name - }); + // ignore relation creation if virtual + if (!req.body.virtual) { + // create relation + await sqlMgr.sqlOpPlus(base, 'relationCreate', { + childColumn: fkColName, + childTable: child.table_name, + parentTable: parent.table_name, + onDelete: 'NO ACTION', + onUpdate: 'NO ACTION', + type: 'real', + parentColumn: parent.primaryKey.column_name + }); + } } await createHmAndBtColumn( child, parent, childColumn, req.body.type, - req.body.title + req.body.title, + req.body.virtual ); } else if (req.body.type === 'mm') { const aTn = `${project?.prefix ?? ''}_nc_m2m_${randomID()}`; @@ -352,26 +359,27 @@ export async function columnAdd(req: Request, res: Response) { columns: associateTableCols }); - const rel1Args = { - ...req.body, - childTable: aTn, - childColumn: parentCn, - parentTable: parent.table_name, - parentColumn: parentPK.column_name, - type: 'real' - }; - const rel2Args = { - ...req.body, - childTable: aTn, - childColumn: childCn, - parentTable: child.table_name, - parentColumn: childPK.column_name, - type: 'real' - }; - - await sqlMgr.sqlOpPlus(base, 'relationCreate', rel1Args); - await sqlMgr.sqlOpPlus(base, 'relationCreate', rel2Args); + if (!req.body.virtual) { + const rel1Args = { + ...req.body, + childTable: aTn, + childColumn: parentCn, + parentTable: parent.table_name, + parentColumn: parentPK.column_name, + type: 'real' + }; + const rel2Args = { + ...req.body, + childTable: aTn, + childColumn: childCn, + parentTable: child.table_name, + parentColumn: childPK.column_name, + type: 'real' + }; + await sqlMgr.sqlOpPlus(base, 'relationCreate', rel1Args); + await sqlMgr.sqlOpPlus(base, 'relationCreate', rel2Args); + } const parentCol = (await assocModel.getColumns())?.find( c => c.column_name === parentCn ); @@ -385,6 +393,7 @@ export async function columnAdd(req: Request, res: Response) { childCol, null, null, + req.body.virtual, true ); await createHmAndBtColumn( @@ -393,6 +402,7 @@ export async function columnAdd(req: Request, res: Response) { parentCol, null, null, + req.body.virtual, true ); diff --git a/packages/nocodb/src/lib/noco/meta/api/dataApis/dataAliasNestedApis.ts b/packages/nocodb/src/lib/noco/meta/api/dataApis/dataAliasNestedApis.ts index e8641fe556..e68288d0e9 100644 --- a/packages/nocodb/src/lib/noco/meta/api/dataApis/dataAliasNestedApis.ts +++ b/packages/nocodb/src/lib/noco/meta/api/dataApis/dataAliasNestedApis.ts @@ -191,7 +191,7 @@ export async function hmList(req: Request, res: Response, next) { } //@ts-ignore -async function relationDataDelete(req, res) { +async function relationDataRemove(req, res) { const { model, view } = await getViewAndModelFromRequestByAliasOrId(req); if (!model) NcError.notFound('Table not found'); @@ -273,7 +273,7 @@ router.post( ); router.delete( '/api/v1/db/data/:orgs/:projectName/:tableName/:rowId/:relationType/:columnName/:refRowId', - ncMetaAclMw(relationDataDelete, 'relationDataDelete') + ncMetaAclMw(relationDataRemove, 'relationDataRemove') ); router.get( diff --git a/packages/nocodb/src/lib/noco/meta/api/metaDiffApis.ts b/packages/nocodb/src/lib/noco/meta/api/metaDiffApis.ts index df0411c293..b1a3c5909f 100644 --- a/packages/nocodb/src/lib/noco/meta/api/metaDiffApis.ts +++ b/packages/nocodb/src/lib/noco/meta/api/metaDiffApis.ts @@ -31,8 +31,6 @@ export enum MetaDiffType { VIEW_COLUMN_REMOVE = 'VIEW_COLUMN_REMOVE', TABLE_RELATION_ADD = 'TABLE_RELATION_ADD', TABLE_RELATION_REMOVE = 'TABLE_RELATION_REMOVE', - // TABLE_VIRTUAL_RELATION_ADD = 'TABLE_VIRTUAL_RELATION_ADD', - // TABLE_VIRTUAL_RELATION_REMOVE = 'TABLE_VIRTUAL_RELATION_REMOVE', TABLE_VIRTUAL_M2M_REMOVE = 'TABLE_VIRTUAL_M2M_REMOVE' } @@ -718,12 +716,12 @@ export async function metaDiffSync(req, res) { res.json({ msg: 'success' }); } -async function isMMRelationAvailable( +async function isMMRelationExist( model: Model, assocModel: Model, belongsToCol: Column ) { - let isAvail = false; + let isExist = false; const colChildOpt = await belongsToCol.getColOptions< LinkToAnotherRecordColumn >(); @@ -737,12 +735,12 @@ async function isMMRelationAvailable( colOpt.fk_child_column_id === colChildOpt.fk_parent_column_id && colOpt.fk_mm_child_column_id === colChildOpt.fk_child_column_id ) { - isAvail = true; + isExist = true; break; } } } - return isAvail; + return isExist; } // @ts-ignore @@ -772,12 +770,12 @@ export async function extractAndGenerateManyToManyRelations( await modelB.getColumns(); // check tableA already have the relation or not - const isRelationAvailInA = await isMMRelationAvailable( + const isRelationAvailInA = await isMMRelationExist( modelA, assocModel, belongsToCols[0] ); - const isRelationAvailInB = await isMMRelationAvailable( + const isRelationAvailInB = await isMMRelationExist( modelB, assocModel, belongsToCols[1] diff --git a/packages/nocodb/src/lib/noco/upgrader/jobs/ncProjectUpgraderV2_0090000.ts b/packages/nocodb/src/lib/noco/upgrader/jobs/ncProjectUpgraderV2_0090000.ts index 0dc5d15298..1e5d6d7517 100644 --- a/packages/nocodb/src/lib/noco/upgrader/jobs/ncProjectUpgraderV2_0090000.ts +++ b/packages/nocodb/src/lib/noco/upgrader/jobs/ncProjectUpgraderV2_0090000.ts @@ -312,12 +312,36 @@ const filterV1toV2CompOpMap = { 'is not like': 'nlike' }; +interface Relationv1 { + project_id?: string; + db_alias?: string; + tn?: string; + rtn?: string; + _tn?: string; + _rtn?: string; + cn?: string; + rcn?: string; + _cn?: string; + _rcn?: string; + referenced_db_alias?: string; + type?: string; + db_type?: string; + ur?: string; + dr?: string; +} + async function migrateProjectModels( ncMeta = Noco.ncMeta ): Promise { // @ts-ignore const metas: ModelMetav1[] = await ncMeta.metaList(null, null, 'nc_models'); + // @ts-ignore + const relations: Relationv1[] = await ncMeta.metaList( + null, + null, + 'nc_relations' + ); const models: Model[] = []; // variable for keeping all @@ -443,6 +467,37 @@ async function migrateProjectModels( fk_mm_parent_column_id = projectModelColumnRefs[rel.vtn][rel.vrcn].id; } + + let virtual = false; + if (columnMeta.mm) { + const relation = relations.find( + r => + r.rtn === columnMeta.mm.tn && + r.rcn === columnMeta.mm.cn && + r.tn === columnMeta.mm.vtn && + r.cn === columnMeta.mm.vcn + ); + virtual = relation?.type === 'virtual'; + } else if (columnMeta.hm) { + virtual = + relations.find( + r => + r.rtn === columnMeta.hm.rtn && + r.tn === columnMeta.hm.tn && + r.rcn === columnMeta.hm.rcn && + r.cn === columnMeta.hm.cn + )?.type === 'virtual'; + } else if (columnMeta.bt) { + virtual = + relations.find( + r => + r.rtn === columnMeta.bt.rtn && + r.tn === columnMeta.bt.tn && + r.rcn === columnMeta.bt.rcn && + r.cn === columnMeta.bt.cn + )?.type === 'virtual'; + } + const column = await Column.insert( { project_id: project.id, @@ -460,7 +515,8 @@ async function migrateProjectModels( fk_mm_model_id, fk_mm_child_column_id, fk_mm_parent_column_id, - fk_related_model_id: columnMeta.hm ? tnId : rtnId + fk_related_model_id: columnMeta.hm ? tnId : rtnId, + virtual }, ncMeta ); @@ -636,6 +692,15 @@ async function migrateProjectModels( // const rtnId = projectModelRefs?.[rel.rtn]?.id; + const virtual = + relations.find( + r => + r.rtn === rel.rtn && + r.tn === rel.tn && + r.rcn === rel.rcn && + r.cn === rel.cn + )?.type === 'virtual'; + const column = await Column.insert( { project_id: project.id, @@ -651,7 +716,8 @@ async function migrateProjectModels( ur: rel.ur, dr: rel.dr, fk_related_model_id: tnId, - system: true + system: true, + virtual }, ncMeta ); diff --git a/packages/nocodb/src/lib/utils/projectAcl.ts b/packages/nocodb/src/lib/utils/projectAcl.ts index 59e15758b2..068694b6be 100644 --- a/packages/nocodb/src/lib/utils/projectAcl.ts +++ b/packages/nocodb/src/lib/utils/projectAcl.ts @@ -122,7 +122,9 @@ export default { bulkDataUpdate: true, bulkDataUpdateAll: true, bulkDataDelete: true, - bulkDataDeleteAll: true + bulkDataDeleteAll: true, + relationDataRemove: true, + relationDataAdd: true }, commenter: { formViewGet: true,