diff --git a/packages/nocodb/src/lib/dataMapper/lib/BaseModel.ts b/packages/nocodb/src/lib/dataMapper/lib/BaseModel.ts
index 4505d6bd3a..b5a887cc94 100644
--- a/packages/nocodb/src/lib/dataMapper/lib/BaseModel.ts
+++ b/packages/nocodb/src/lib/dataMapper/lib/BaseModel.ts
@@ -60,6 +60,7 @@ abstract class BaseModel {
protected pks: any[];
protected hasManyRelations: any;
protected belongsToRelations: any;
+ protected manyToManyRelations: any;
protected config: any;
protected clientType: string;
public readonly type: string;
diff --git a/packages/nocodb/src/lib/dataMapper/lib/sql/BaseModelSql.ts b/packages/nocodb/src/lib/dataMapper/lib/sql/BaseModelSql.ts
index 1b51618234..8277df2407 100644
--- a/packages/nocodb/src/lib/dataMapper/lib/sql/BaseModelSql.ts
+++ b/packages/nocodb/src/lib/dataMapper/lib/sql/BaseModelSql.ts
@@ -39,6 +39,7 @@ class BaseModelSql extends BaseModel {
columns,
hasMany = [],
belongsTo = [],
+ manyToMany = [],
type,
dbModels
}: {
@@ -63,6 +64,7 @@ class BaseModelSql extends BaseModel {
this.pks = columns.filter(c => c.pk === true);
this.hasManyRelations = hasMany;
this.belongsToRelations = belongsTo;
+ this.manyToManyRelations = manyToMany;
this.config = {
limitDefault: process.env.DB_QUERY_LIMIT_DEFAULT || 10,
limitMax: process.env.DB_QUERY_LIMIT_MAX || 500,
diff --git a/packages/nocodb/src/lib/noco/common/BaseApiBuilder.ts b/packages/nocodb/src/lib/noco/common/BaseApiBuilder.ts
index b67dd085b1..ee779d9fb4 100644
--- a/packages/nocodb/src/lib/noco/common/BaseApiBuilder.ts
+++ b/packages/nocodb/src/lib/noco/common/BaseApiBuilder.ts
@@ -737,8 +737,6 @@ export default abstract class BaseApiBuilder implements XcDynami
protected initDbDriver(): void {
if (!this.dbDriver) {
-
-
if (this.connectionConfig?.connection?.ssl && typeof this.connectionConfig?.connection?.ssl === 'object') {
if (this.connectionConfig.connection.ssl.caFilePath) {
this.connectionConfig.connection.ssl.ca = fs
@@ -1497,6 +1495,64 @@ export default abstract class BaseApiBuilder implements XcDynami
public getProjectId() {
return this.projectId;
}
+
+
+ protected async getManyToManyRelations() {
+ const metas = new Set();
+
+ for (const meta of Object.values(this.metas)) {
+
+ // check if table is a Bridge table(or Associative Table) by checking
+ // number of foreign keys and columns
+ if (meta.belongsTo?.length === 2 && meta.columns.length < 4) {
+ const tableMetaA = this.metas[meta.belongsTo[0].rtn];
+ const tableMetaB = this.metas[meta.belongsTo[1].rtn];
+
+ // add manytomany data under metadata of both related columns
+ tableMetaA.manyToMany = tableMetaA.manyToMany || [];
+ tableMetaA.manyToMany.push({
+ "tn": tableMetaA.tn,
+ "cn": meta.belongsTo[0].rcn,
+ "vtn": meta.tn,
+ "vcn": meta.belongsTo[0].cn,
+ "vrcn": meta.belongsTo[1].cn,
+ "rtn": meta.belongsTo[1].rtn,
+ "rcn": meta.belongsTo[1].rcn,
+ "_tn": tableMetaA._tn,
+ "_cn": meta.belongsTo[0]._rcn,
+ "_rtn": meta.belongsTo[1]._rtn,
+ "_rcn": meta.belongsTo[1]._rcn
+ })
+ tableMetaB.manyToMany = tableMetaB.manyToMany || [];
+ tableMetaB.manyToMany.push({
+ "tn": tableMetaB.tn,
+ "cn": meta.belongsTo[1].rcn,
+ "vtn": meta.tn,
+ "vcn": meta.belongsTo[1].cn,
+ "vrcn": meta.belongsTo[0].cn,
+ "rtn": meta.belongsTo[0].rtn,
+ "rcn": meta.belongsTo[0].rcn,
+ "_tn": tableMetaB._tn,
+ "_cn": meta.belongsTo[1]._rcn,
+ "_rtn": meta.belongsTo[0]._rtn,
+ "_rcn": meta.belongsTo[0]._rcn
+ })
+ metas.add(tableMetaA)
+ metas.add(tableMetaB)
+ }
+ }
+
+ // Update metadata of tables which have manytomany relation
+ // and recreate basemodel with new meta information
+ for (const meta of metas) {
+ await this.xcMeta.metaUpdate(this.projectId, this.dbAlias, 'nc_models', {
+ meta: JSON.stringify(meta)
+ }, {title: meta.tn})
+ XcCache.del([this.projectId, this.dbAlias, 'table', meta.tn].join('::'));
+ this.models[meta.tn] = this.getBaseModel(meta)
+ }
+ }
+
}
export {IGNORE_TABLES};
diff --git a/packages/nocodb/src/lib/noco/gql/GqlApiBuilder.ts b/packages/nocodb/src/lib/noco/gql/GqlApiBuilder.ts
index a455e45bcb..ba27738b29 100644
--- a/packages/nocodb/src/lib/noco/gql/GqlApiBuilder.ts
+++ b/packages/nocodb/src/lib/noco/gql/GqlApiBuilder.ts
@@ -620,12 +620,10 @@ export class GqlApiBuilder extends BaseApiBuilder implements XcMetaMgr {
await NcHelp.executeOperations(insertResolvers, this.connectionConfig.client);
}
}
-
});
await NcHelp.executeOperations(tableResolvers, this.connectionConfig.client);
-
await Promise.all(Object.entries(this.metas).map(async ([tn, schema]) => {
for (const hm of schema.hasMany) {
@@ -757,6 +755,8 @@ export class GqlApiBuilder extends BaseApiBuilder implements XcMetaMgr {
}
}));
+
+ await this.getManyToManyRelations();
}
}
diff --git a/packages/nocodb/src/lib/noco/rest/RestApiBuilder.ts b/packages/nocodb/src/lib/noco/rest/RestApiBuilder.ts
index 49081350eb..3784456d25 100644
--- a/packages/nocodb/src/lib/noco/rest/RestApiBuilder.ts
+++ b/packages/nocodb/src/lib/noco/rest/RestApiBuilder.ts
@@ -53,14 +53,10 @@ export class RestApiBuilder extends BaseApiBuilder {
constructor(app: Noco, projectBuilder: NcProjectBuilder, config: NcConfig, connectionConfig: DbConfig, xcMeta?: NcMetaIO) {
super(app, projectBuilder, config, connectionConfig);
autoBind(this)
-
- this.models = {};
this.controllers = {};
this.routers = {};
this.hooks = {};
this.xcMeta = xcMeta;
-
-
}
@@ -292,7 +288,6 @@ export class RestApiBuilder extends BaseApiBuilder {
let tables;
const swaggerRefs: { [table: string]: any[] } = {};
-
/* Get all relations */
const relations = await this.relationsSyncAndGet();
this.relationsCount = relations.length;
@@ -545,11 +540,7 @@ export class RestApiBuilder extends BaseApiBuilder {
this.controllers[name].mapRoutes(router, this.customRoutes);
}
});
-
-
}
-
-
});
/* handle xc_tables update in parallel */
@@ -557,6 +548,9 @@ export class RestApiBuilder extends BaseApiBuilder {
await NcHelp.executeOperations(relationRoutes, this.connectionConfig.client);
+ await this.getManyToManyRelations();
+
+
const swaggerDoc = {
tags: [],
paths: {},
diff --git a/packages/nocodb/src/lib/noco/rest/RestCtrl.ts b/packages/nocodb/src/lib/noco/rest/RestCtrl.ts
index a01fd6a983..1bb169b531 100644
--- a/packages/nocodb/src/lib/noco/rest/RestCtrl.ts
+++ b/packages/nocodb/src/lib/noco/rest/RestCtrl.ts
@@ -135,7 +135,15 @@ export class RestCtrl extends RestBaseCtrl {
const data = await req.model.delb(req.body)
res.json(data);
}
-
+ public async nestedList(req: Request | any, res): Promise {
+ const startTime = process.hrtime();
+ const data = await req.model.nestedList({
+ ...req.query
+ } as any);
+ const elapsedSeconds = parseHrtimeToSeconds(process.hrtime(startTime));
+ res.setHeader('xc-db-response', elapsedSeconds);
+ res.xcJson(data);
+ }
protected async middleware(req: Request | any, res: Response, next: NextFunction): Promise {
diff --git a/packages/nocodb/src/lib/sqlMgr/code/routes/xc-ts/ExpressXcTsRoutes.ts b/packages/nocodb/src/lib/sqlMgr/code/routes/xc-ts/ExpressXcTsRoutes.ts
index 2050514f47..15a8717aee 100644
--- a/packages/nocodb/src/lib/sqlMgr/code/routes/xc-ts/ExpressXcTsRoutes.ts
+++ b/packages/nocodb/src/lib/sqlMgr/code/routes/xc-ts/ExpressXcTsRoutes.ts
@@ -12,7 +12,7 @@ class ExpressXcTsRoutes extends BaseRender {
* @param ctx.columns
* @param ctx.relations
*/
- constructor({dir, filename, ctx}:any) {
+ constructor({dir, filename, ctx}: any) {
super({dir, filename, ctx});
}
@@ -21,7 +21,7 @@ class ExpressXcTsRoutes extends BaseRender {
*/
prepare() {
- let data:any = {};
+ let data: any = {};
/* example of simple variable */
data = this.ctx;
@@ -31,9 +31,8 @@ class ExpressXcTsRoutes extends BaseRender {
}
-
getObject() {
- const ejsData:any = this.prepare();
+ const ejsData: any = this.prepare();
const routes = [
{
path: `/api/${this.ctx.routeVersionLetter}/${ejsData._tn}`,
@@ -65,6 +64,23 @@ async function(req, res){
res.json(data);
}
`]
+ }, {
+ path: `/api/${this.ctx.routeVersionLetter}/${ejsData._tn}/nestedList`,
+ type: 'get',
+ handler: ['nestedList'],
+ acl: {
+ admin: true,
+ user: true,
+ guest: true
+ },
+ functions: [`
+async function(req, res){
+ const data = await req.parentModel.hasManyList({
+ ...req.query
+ });
+ res.json(data);
+}
+ `]
}, {
path: `/api/${this.ctx.routeVersionLetter}/${ejsData._tn}/groupby/:column_name`,
type: 'get',
@@ -290,7 +306,7 @@ async function(req, res){
res.json(data);
}
`]
- },
+ }
];
if (this.ctx.type === 'view') {
|