column.value && sqlUi.value.getAbstractType(column.value))
diff --git a/packages/nc-gui/composables/useColumnCreateStore.ts b/packages/nc-gui/composables/useColumnCreateStore.ts
index 4fb1311aa2..de061e29dc 100644
--- a/packages/nc-gui/composables/useColumnCreateStore.ts
+++ b/packages/nc-gui/composables/useColumnCreateStore.ts
@@ -79,6 +79,7 @@ const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState
const formState = ref>({
title: '',
uidt: fromTableExplorer?.value ? defaultType : null,
+ custom: {},
...clone(column.value || {}),
})
@@ -89,6 +90,7 @@ const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState
const colProp = sqlUi.value.getDataTypeForUiType(formState.value as { uidt: UITypes }, idType ?? undefined)
formState.value = {
+ custom: {},
...(!isEdit.value && {
// only take title, column_name and uidt when creating a column
// to avoid the extra props from being taken (e.g. SingleLineText -> LTAR -> SingleLineText)
@@ -284,6 +286,9 @@ const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState
try {
formState.value.table_name = meta.value?.table_name
+
+ const refModelId = formState.value.custom?.ref_model_id
+
// formState.value.title = formState.value.column_name
if (column.value) {
// reset column validation if column is not to be validated
@@ -337,7 +342,11 @@ const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState
/** if LTAR column then force reload related table meta */
if (isLinksOrLTAR(formState.value) && meta.value?.id !== formState.value.childId) {
- getMeta(formState.value.childId, true).then(() => {})
+ if (refModelId) {
+ getMeta(refModelId, true).then(() => {})
+ } else {
+ getMeta(formState.value.childId, true).then(() => {})
+ }
}
// Column created
diff --git a/packages/nc-gui/composables/useExpandedFormStore.ts b/packages/nc-gui/composables/useExpandedFormStore.ts
index 5c7674b0b5..12704c4377 100644
--- a/packages/nc-gui/composables/useExpandedFormStore.ts
+++ b/packages/nc-gui/composables/useExpandedFormStore.ts
@@ -331,7 +331,7 @@ const [useProvideExpandedFormStore, useExpandedFormStore] = useInjectionState((m
if (missingRequiredColumns.size) return
- data = await $api.dbTableRow.create('noco', base.value.id as string, meta.value.id, {
+ data = await $api.dbTableRow.create('noco', meta.value.base_id, meta.value.id, {
...insertObj,
...(ltarState || {}),
})
diff --git a/packages/nc-gui/composables/useViewColumns.ts b/packages/nc-gui/composables/useViewColumns.ts
index c257b1b0e6..75c40af61f 100644
--- a/packages/nc-gui/composables/useViewColumns.ts
+++ b/packages/nc-gui/composables/useViewColumns.ts
@@ -136,7 +136,7 @@ const [useProvideViewColumns, useViewColumns] = useInjectionState(
if (isLocalMode.value) {
const fieldById = (fields.value || []).reduce>((acc, curr) => {
if (curr.fk_column_id) {
- curr.show = curr.initialShow ? true : false
+ curr.show = !!curr.initialShow
acc[curr.fk_column_id] = curr
}
return acc
diff --git a/packages/nc-gui/store/sidebar.ts b/packages/nc-gui/store/sidebar.ts
index 926888a84f..f8e51cfb5e 100644
--- a/packages/nc-gui/store/sidebar.ts
+++ b/packages/nc-gui/store/sidebar.ts
@@ -1,5 +1,5 @@
import { acceptHMRUpdate, defineStore } from 'pinia'
-import { MAX_WIDTH_FOR_MOBILE_MODE, INITIAL_LEFT_SIDEBAR_WIDTH } from '~/lib/constants'
+import { INITIAL_LEFT_SIDEBAR_WIDTH, MAX_WIDTH_FOR_MOBILE_MODE } from '~/lib/constants'
export const useSidebarStore = defineStore('sidebarStore', () => {
const { width } = useWindowSize()
diff --git a/packages/nocodb-sdk/src/lib/globals.ts b/packages/nocodb-sdk/src/lib/globals.ts
index 186f6d8686..36850cf709 100644
--- a/packages/nocodb-sdk/src/lib/globals.ts
+++ b/packages/nocodb-sdk/src/lib/globals.ts
@@ -190,6 +190,8 @@ export enum NcErrorType {
UNKNOWN_ERROR = 'UNKNOWN_ERROR',
BAD_JSON = 'BAD_JSON',
INVALID_PK_VALUE = 'INVALID_PK_VALUE',
+ COLUMN_ASSOCIATED_WITH_LINK = 'COLUMN_ASSOCIATED_WITH_LINK',
+ TABLE_ASSOCIATED_WITH_LINK = 'TABLE_ASSOCIATED_WITH_LINK',
}
type Roles = OrgUserRoles | ProjectRoles | WorkspaceUserRoles;
diff --git a/packages/nocodb-sdk/src/lib/sqlUi/DatabricksUi.ts b/packages/nocodb-sdk/src/lib/sqlUi/DatabricksUi.ts
index 5ac2d83aaf..33559953a1 100644
--- a/packages/nocodb-sdk/src/lib/sqlUi/DatabricksUi.ts
+++ b/packages/nocodb-sdk/src/lib/sqlUi/DatabricksUi.ts
@@ -531,7 +531,7 @@ export class DatabricksUi {
}
}
- static getDataTypeForUiType(col: { uidt: UITypes; }) {
+ static getDataTypeForUiType(col: { uidt: UITypes }) {
const colProp: any = {};
switch (col.uidt) {
case 'ID':
@@ -667,7 +667,7 @@ export class DatabricksUi {
return colProp;
}
- static getDataTypeListForUiType(col: { uidt: UITypes; }, idType?: IDType) {
+ static getDataTypeListForUiType(col: { uidt: UITypes }, idType?: IDType) {
switch (col.uidt) {
case 'ID':
if (idType === 'AG') {
@@ -696,9 +696,7 @@ export class DatabricksUi {
return ['string'];
case 'Checkbox':
- return [
- 'boolean',
- ];
+ return ['boolean'];
case 'MultiSelect':
return ['string'];
@@ -707,14 +705,10 @@ export class DatabricksUi {
return ['string'];
case 'Year':
- return [
- 'int',
- ];
+ return ['int'];
case 'Time':
- return [
- 'string',
- ];
+ return ['string'];
case 'PhoneNumber':
case 'Email':
@@ -724,32 +718,22 @@ export class DatabricksUi {
return ['string'];
case 'Number':
- return [
- 'int',
- ];
+ return ['int'];
case 'Decimal':
return ['decimal', 'float', 'double'];
case 'Currency':
- return [
- 'decimal',
- ];
+ return ['decimal'];
case 'Percent':
- return [
- 'decimal',
- ];
+ return ['decimal'];
case 'Duration':
- return [
- 'decimal',
- ];
+ return ['decimal'];
case 'Rating':
- return [
- 'int',
- ];
+ return ['int'];
case 'Formula':
return ['string'];
@@ -758,9 +742,7 @@ export class DatabricksUi {
return ['string'];
case 'Count':
- return [
- 'int',
- ];
+ return ['int'];
case 'Lookup':
return ['string'];
@@ -774,9 +756,7 @@ export class DatabricksUi {
return ['datetime'];
case 'AutoNumber':
- return [
- 'int',
- ];
+ return ['int'];
case 'Barcode':
return ['string'];
@@ -813,4 +793,20 @@ export class DatabricksUi {
'HOUR',
];
}
+
+ static isEqual(dataType1: string, dataType2: string) {
+ if (dataType1 === dataType2) return true;
+
+ const abstractType1 = this.getAbstractType({ dt: dataType1 });
+ const abstractType2 = this.getAbstractType({ dt: dataType2 });
+
+ if (
+ abstractType1 &&
+ abstractType1 === abstractType2 &&
+ ['integer', 'float'].includes(abstractType1)
+ )
+ return true;
+
+ return false;
+ }
}
diff --git a/packages/nocodb-sdk/src/lib/sqlUi/MssqlUi.ts b/packages/nocodb-sdk/src/lib/sqlUi/MssqlUi.ts
index 688d1c84a4..3beded0e6f 100644
--- a/packages/nocodb-sdk/src/lib/sqlUi/MssqlUi.ts
+++ b/packages/nocodb-sdk/src/lib/sqlUi/MssqlUi.ts
@@ -968,4 +968,20 @@ export class MssqlUi {
'DATESTR',
];
}
+
+ static isEqual(dataType1: string, dataType2: string) {
+ if (dataType1 === dataType2) return true;
+
+ const abstractType1 = this.getAbstractType({ dt: dataType1 });
+ const abstractType2 = this.getAbstractType({ dt: dataType2 });
+
+ if (
+ abstractType1 &&
+ abstractType1 === abstractType2 &&
+ ['integer', 'float'].includes(abstractType1)
+ )
+ return true;
+
+ return false;
+ }
}
diff --git a/packages/nocodb-sdk/src/lib/sqlUi/MysqlUi.ts b/packages/nocodb-sdk/src/lib/sqlUi/MysqlUi.ts
index 29e925626c..2c55d232fc 100644
--- a/packages/nocodb-sdk/src/lib/sqlUi/MysqlUi.ts
+++ b/packages/nocodb-sdk/src/lib/sqlUi/MysqlUi.ts
@@ -1334,4 +1334,20 @@ export class MysqlUi {
static getUnsupportedFnList() {
return ['COUNTA', 'COUNT', 'DATESTR'];
}
+
+ static isEqual(dataType1: string, dataType2: string) {
+ if (dataType1 === dataType2) return true;
+
+ const abstractType1 = this.getAbstractType({ dt: dataType1 });
+ const abstractType2 = this.getAbstractType({ dt: dataType2 });
+
+ if (
+ abstractType1 &&
+ abstractType1 === abstractType2 &&
+ ['integer', 'float'].includes(abstractType1)
+ )
+ return true;
+
+ return false;
+ }
}
diff --git a/packages/nocodb-sdk/src/lib/sqlUi/OracleUi.ts b/packages/nocodb-sdk/src/lib/sqlUi/OracleUi.ts
index dda16b2cf3..8bec5598c6 100644
--- a/packages/nocodb-sdk/src/lib/sqlUi/OracleUi.ts
+++ b/packages/nocodb-sdk/src/lib/sqlUi/OracleUi.ts
@@ -951,6 +951,22 @@ export class OracleUi {
'DATESTR',
];
}
+
+ static isEqual(dataType1: string, dataType2: string) {
+ if (dataType1 === dataType2) return true;
+
+ const abstractType1 = this.getAbstractType({ dt: dataType1 });
+ const abstractType2 = this.getAbstractType({ dt: dataType2 });
+
+ if (
+ abstractType1 &&
+ abstractType1 === abstractType2 &&
+ ['integer', 'float'].includes(abstractType1)
+ )
+ return true;
+
+ return false;
+ }
}
// module.exports = PgUiHelp;
diff --git a/packages/nocodb-sdk/src/lib/sqlUi/PgUi.ts b/packages/nocodb-sdk/src/lib/sqlUi/PgUi.ts
index 7ac63ae415..64367876dd 100644
--- a/packages/nocodb-sdk/src/lib/sqlUi/PgUi.ts
+++ b/packages/nocodb-sdk/src/lib/sqlUi/PgUi.ts
@@ -1613,7 +1613,7 @@ export class PgUi {
}
}
- static getDataTypeForUiType(col: { uidt: UITypes; }, idType?: IDType) {
+ static getDataTypeForUiType(col: { uidt: UITypes }, idType?: IDType) {
const colProp: any = {};
switch (col.uidt) {
case 'ID':
@@ -1754,7 +1754,7 @@ export class PgUi {
return colProp;
}
- static getDataTypeListForUiType(col: { uidt?: UITypes; }, idType: IDType) {
+ static getDataTypeListForUiType(col: { uidt?: UITypes }, idType: IDType) {
switch (col.uidt) {
case 'ID':
if (idType === 'AG') {
@@ -2031,6 +2031,22 @@ export class PgUi {
static getUnsupportedFnList() {
return [];
}
+
+ static isEqual(dataType1: string, dataType2: string) {
+ if (dataType1?.toLowerCase() === dataType2?.toLowerCase()) return true;
+
+ const abstractType1 = this.getAbstractType({ dt: dataType1 });
+ const abstractType2 = this.getAbstractType({ dt: dataType2 });
+
+ if (
+ abstractType1 &&
+ abstractType1 === abstractType2 &&
+ ['integer', 'float'].includes(abstractType1)
+ )
+ return true;
+
+ return false;
+ }
}
// module.exports = PgUiHelp;
diff --git a/packages/nocodb-sdk/src/lib/sqlUi/SnowflakeUi.ts b/packages/nocodb-sdk/src/lib/sqlUi/SnowflakeUi.ts
index 275c060fa0..34f26327ee 100644
--- a/packages/nocodb-sdk/src/lib/sqlUi/SnowflakeUi.ts
+++ b/packages/nocodb-sdk/src/lib/sqlUi/SnowflakeUi.ts
@@ -707,7 +707,7 @@ export class SnowflakeUi {
}
}
- static getDataTypeForUiType(col: { uidt: UITypes; }, idType?: IDType) {
+ static getDataTypeForUiType(col: { uidt: UITypes }, idType?: IDType) {
const colProp: any = {};
switch (col.uidt) {
case 'ID':
@@ -847,7 +847,7 @@ export class SnowflakeUi {
return colProp;
}
- static getDataTypeListForUiType(col: { uidt: UITypes; }, idType: IDType) {
+ static getDataTypeListForUiType(col: { uidt: UITypes }, idType: IDType) {
switch (col.uidt) {
case 'ID':
if (idType === 'AG') {
@@ -1034,6 +1034,22 @@ export class SnowflakeUi {
'DATESTR',
];
}
+
+ static isEqual(dataType1: string, dataType2: string) {
+ if (dataType1 === dataType2) return true;
+
+ const abstractType1 = this.getAbstractType({ dt: dataType1 });
+ const abstractType2 = this.getAbstractType({ dt: dataType2 });
+
+ if (
+ abstractType1 &&
+ abstractType1 === abstractType2 &&
+ ['integer', 'float'].includes(abstractType1)
+ )
+ return true;
+
+ return false;
+ }
}
// module.exports = SnowflakeUiHelp;
diff --git a/packages/nocodb-sdk/src/lib/sqlUi/SqliteUi.ts b/packages/nocodb-sdk/src/lib/sqlUi/SqliteUi.ts
index ac32a09bfe..e07f83441c 100644
--- a/packages/nocodb-sdk/src/lib/sqlUi/SqliteUi.ts
+++ b/packages/nocodb-sdk/src/lib/sqlUi/SqliteUi.ts
@@ -556,7 +556,7 @@ export class SqliteUi {
}
}
- static getDataTypeForUiType(col: { uidt: UITypes; }, idType?: IDType) {
+ static getDataTypeForUiType(col: { uidt: UITypes }, idType?: IDType) {
const colProp: any = {};
switch (col.uidt) {
case 'ID':
@@ -697,7 +697,7 @@ export class SqliteUi {
return colProp;
}
- static getDataTypeListForUiType(col: { uidt: UITypes; }, idType?: IDType) {
+ static getDataTypeListForUiType(col: { uidt: UITypes }, idType?: IDType) {
switch (col.uidt) {
case 'ID':
if (idType === 'AG') {
@@ -928,4 +928,20 @@ export class SqliteUi {
'HOUR',
];
}
+
+ static isEqual(dataType1: string, dataType2: string) {
+ if (dataType1 === dataType2) return true;
+
+ const abstractType1 = this.getAbstractType({ dt: dataType1 });
+ const abstractType2 = this.getAbstractType({ dt: dataType2 });
+
+ if (
+ abstractType1 &&
+ abstractType1 === abstractType2 &&
+ ['integer', 'float'].includes(abstractType1)
+ )
+ return true;
+
+ return false;
+ }
}
diff --git a/packages/nocodb/src/controllers/notifications.controller.ts b/packages/nocodb/src/controllers/notifications.controller.ts
index 446be008e4..d3a423d5de 100644
--- a/packages/nocodb/src/controllers/notifications.controller.ts
+++ b/packages/nocodb/src/controllers/notifications.controller.ts
@@ -45,7 +45,8 @@ export class NotificationsController {
this.notificationsService.addConnection(req.user.id, res);
- let unsubscribeCallback: (keepRedisChannel?: boolean) => Promise = null;
+ let unsubscribeCallback: (keepRedisChannel?: boolean) => Promise =
+ null;
if (PubSubRedis.available) {
unsubscribeCallback = await PubSubRedis.subscribe(
@@ -57,7 +58,11 @@ export class NotificationsController {
}
res.on('close', async () => {
- await this.notificationsService.removeConnection(req.user.id, res, unsubscribeCallback);
+ await this.notificationsService.removeConnection(
+ req.user.id,
+ res,
+ unsubscribeCallback,
+ );
});
setTimeout(() => {
diff --git a/packages/nocodb/src/controllers/users/users.controller.ts b/packages/nocodb/src/controllers/users/users.controller.ts
index 2204aedec7..9ea6c9655b 100644
--- a/packages/nocodb/src/controllers/users/users.controller.ts
+++ b/packages/nocodb/src/controllers/users/users.controller.ts
@@ -15,8 +15,6 @@ import { GlobalGuard } from '~/guards/global/global.guard';
import { AppHooksService } from '~/services/app-hooks/app-hooks.service';
import { UsersService } from '~/services/users/users.service';
import { MetaApiLimiterGuard } from '~/guards/meta-api-limiter.guard';
-import { TenantContext } from '~/decorators/tenant-context.decorator';
-import { NcContext, NcRequest } from '~/interface/config';
@Controller()
export class UsersController {
diff --git a/packages/nocodb/src/db/BaseModelSqlv2.ts b/packages/nocodb/src/db/BaseModelSqlv2.ts
index f49561788b..ac057e1e17 100644
--- a/packages/nocodb/src/db/BaseModelSqlv2.ts
+++ b/packages/nocodb/src/db/BaseModelSqlv2.ts
@@ -1713,13 +1713,13 @@ class BaseModelSqlv2 {
)) as LinkToAnotherRecordColumn
).getParentColumn(this.context);
const parentTable = await parentCol.getModel(this.context);
- const childModel = await Model.getBaseModelSQL(this.context, {
+ const childBaseModel = await Model.getBaseModelSQL(this.context, {
model: childTable,
dbDriver: this.dbDriver,
});
await parentTable.getColumns(this.context);
- const childTn = this.getTnPath(childTable);
+ const childTn = childBaseModel.getTnPath(childTable);
const parentTn = this.getTnPath(parentTable);
const qb = this.dbDriver(childTn);
@@ -1736,7 +1736,7 @@ class BaseModelSqlv2 {
qb.limit(+rest?.limit || 25);
qb.offset(+rest?.offset || 0);
- await childModel.selectObject({
+ await childBaseModel.selectObject({
qb,
fieldsSet: args.fieldSet,
});
@@ -1790,7 +1790,11 @@ class BaseModelSqlv2 {
const parentTable = await parentCol.getModel(this.context);
await parentTable.getColumns(this.context);
- const childTn = this.getTnPath(childTable);
+ const childBaseModel = await Model.getBaseModelSQL(this.context, {
+ dbDriver: this.dbDriver,
+ model: childTable,
+ });
+ const childTn = childBaseModel.getTnPath(childTable);
const parentTn = this.getTnPath(parentTable);
const query = this.dbDriver(childTn)
@@ -1945,7 +1949,12 @@ class BaseModelSqlv2 {
)) as LinkToAnotherRecordColumn;
const mmTable = await relColOptions.getMMModel(this.context);
- const vtn = this.getTnPath(mmTable);
+ const assocBaseModel = await Model.getBaseModelSQL(this.context, {
+ id: mmTable.id,
+ dbDriver: this.dbDriver,
+ });
+
+ const vtn = assocBaseModel.getTnPath(mmTable);
const vcn = (await relColOptions.getMMChildColumn(this.context))
.column_name;
const vrcn = (await relColOptions.getMMParentColumn(this.context))
@@ -1980,8 +1989,16 @@ class BaseModelSqlv2 {
).getModel(this.context);
await parentTable.getColumns(this.context);
- const childTn = this.getTnPath(childTable);
- const parentTn = this.getTnPath(parentTable);
+ const parentBaseModel = await Model.getBaseModelSQL(this.context, {
+ id: parentTable.id,
+ dbDriver: this.dbDriver,
+ });
+ const childBaseModel = await Model.getBaseModelSQL(this.context, {
+ id: childTable.id,
+ dbDriver: this.dbDriver,
+ });
+ const childTn = childBaseModel.getTnPath(childTable);
+ const parentTn = parentBaseModel.getTnPath(parentTable);
const rtn = childTn;
const qb = this.dbDriver(rtn)
@@ -2096,7 +2113,13 @@ class BaseModelSqlv2 {
)) as LinkToAnotherRecordColumn;
const mmTable = await relColOptions.getMMModel(this.context);
- const vtn = this.getTnPath(mmTable);
+
+ const assocBaseModel = await Model.getBaseModelSQL(this.context, {
+ model: mmTable,
+ dbDriver: this.dbDriver,
+ });
+
+ const vtn = assocBaseModel.getTnPath(mmTable);
const vcn = (await relColOptions.getMMChildColumn(this.context))
.column_name;
const vrcn = (await relColOptions.getMMParentColumn(this.context))
@@ -2112,7 +2135,12 @@ class BaseModelSqlv2 {
).getModel(this.context);
await parentTable.getColumns(this.context);
- const childTn = this.getTnPath(childTable);
+ const childBaseModel = await Model.getBaseModelSQL(this.context, {
+ dbDriver: this.dbDriver,
+ model: childTable,
+ });
+
+ const childTn = childBaseModel.getTnPath(childTable);
const parentTn = this.getTnPath(parentTable);
const rtn = childTn;
@@ -2162,20 +2190,37 @@ class BaseModelSqlv2 {
)) as LinkToAnotherRecordColumn;
const mmTable = await relColOptions.getMMModel(this.context);
- const vtn = this.getTnPath(mmTable);
+ const assocBaseModel = await Model.getBaseModelSQL(this.context, {
+ id: mmTable.id,
+ dbDriver: this.dbDriver,
+ });
+
+ const vtn = assocBaseModel.getTnPath(mmTable);
const vcn = (await relColOptions.getMMChildColumn(this.context))
.column_name;
const vrcn = (await relColOptions.getMMParentColumn(this.context))
.column_name;
const rcn = (await relColOptions.getParentColumn(this.context)).column_name;
const cn = (await relColOptions.getChildColumn(this.context)).column_name;
+
const childTable = await (
await relColOptions.getParentColumn(this.context)
).getModel(this.context);
- const childModel = await Model.getBaseModelSQL(this.context, {
+ const parentTable = await (
+ await relColOptions.getChildColumn(this.context)
+ ).getModel(this.context);
+ await parentTable.getColumns(this.context);
+ const parentBaseModel = await Model.getBaseModelSQL(this.context, {
+ id: parentTable.id,
dbDriver: this.dbDriver,
- model: childTable,
});
+ const childBaseModel = await Model.getBaseModelSQL(this.context, {
+ dbDriver: this.dbDriver,
+ id: childTable.id,
+ });
+ const childTn = childBaseModel.getTnPath(childTable);
+ const parentTn = parentBaseModel.getTnPath(parentTable);
+
const childView = await relColOptions.getChildView(this.context);
let listArgs: any = {};
if (childView) {
@@ -2188,14 +2233,6 @@ class BaseModelSqlv2 {
listArgs = dependencyFields;
}
- const parentTable = await (
- await relColOptions.getChildColumn(this.context)
- ).getModel(this.context);
- await parentTable.getColumns(this.context);
-
- const childTn = this.getTnPath(childTable);
- const parentTn = this.getTnPath(parentTable);
-
const rtn = childTn;
const qb = this.dbDriver(rtn).where((qb) =>
@@ -2220,7 +2257,7 @@ class BaseModelSqlv2 {
await this.shuffle({ qb });
}
- await childModel.selectObject({
+ await childBaseModel.selectObject({
qb,
fieldsSet: listArgs?.fieldsSet,
viewId: childView?.id,
@@ -2248,7 +2285,7 @@ class BaseModelSqlv2 {
applyPaginate(qb, rest);
- const proto = await childModel.getProto();
+ const proto = await childBaseModel.getProto();
const data = await this.execAndParse(
qb,
await childTable.getColumns(this.context),
@@ -2280,16 +2317,20 @@ class BaseModelSqlv2 {
const parentTable = await (
await relColOptions.getParentColumn(this.context)
).getModel(this.context);
- const childModel = await Model.getBaseModelSQL(this.context, {
+ const childBaseModel = await Model.getBaseModelSQL(this.context, {
dbDriver: this.dbDriver,
model: childTable,
});
+ const parentBaseModel = await Model.getBaseModelSQL(this.context, {
+ dbDriver: this.dbDriver,
+ model: parentTable,
+ });
await parentTable.getColumns(this.context);
const childView = await relColOptions.getChildView(this.context);
- const childTn = this.getTnPath(childTable);
- const parentTn = this.getTnPath(parentTable);
+ const childTn = childBaseModel.getTnPath(childTable);
+ const parentTn = parentBaseModel.getTnPath(parentTable);
const tn = childTn;
const rtn = parentTn;
@@ -2308,7 +2349,7 @@ class BaseModelSqlv2 {
await this.shuffle({ qb });
}
- await childModel.selectObject({ qb });
+ await childBaseModel.selectObject({ qb });
const aliasColObjMap = await childTable.getAliasColObjMap(this.context);
const filterObj = extractFilterFromXwhere(where, aliasColObjMap);
@@ -2330,12 +2371,11 @@ class BaseModelSqlv2 {
applyPaginate(qb, rest);
- const proto = await childModel.getProto();
+ const proto = await childBaseModel.getProto();
const data = await this.execAndParse(
qb,
await childTable.getColumns(this.context),
);
-
return data.map((c) => {
c.__proto__ = proto;
return c;
@@ -2367,7 +2407,12 @@ class BaseModelSqlv2 {
const childView = await relColOptions.getChildView(this.context);
- const childTn = this.getTnPath(childTable);
+ const childBaseModel = await Model.getBaseModelSQL(this.context, {
+ dbDriver: this.dbDriver,
+ model: childTable,
+ });
+
+ const childTn = childBaseModel.getTnPath(childTable);
const parentTn = this.getTnPath(parentTable);
const tn = childTn;
@@ -2530,8 +2575,13 @@ class BaseModelSqlv2 {
await relColOptions.getChildColumn(this.context)
).getModel(this.context);
+ const parentBaseModel = await Model.getBaseModelSQL(this.context, {
+ dbDriver: this.dbDriver,
+ model: parentTable,
+ });
+
const childTn = this.getTnPath(childTable);
- const parentTn = this.getTnPath(parentTable);
+ const parentTn = parentBaseModel.getTnPath(parentTable);
const rtn = parentTn;
const tn = childTn;
@@ -2591,9 +2641,16 @@ class BaseModelSqlv2 {
).getModel(this.context);
const childView = await relColOptions.getChildView(this.context);
-
- const childTn = this.getTnPath(childTable);
- const parentTn = this.getTnPath(parentTable);
+ const parentBaseModel = await Model.getBaseModelSQL(this.context, {
+ dbDriver: this.dbDriver,
+ model: parentTable,
+ });
+ const childBaseModel = await Model.getBaseModelSQL(this.context, {
+ dbDriver: this.dbDriver,
+ model: childTable,
+ });
+ const childTn = childBaseModel.getTnPath(childTable);
+ const parentTn = parentBaseModel.getTnPath(parentTable);
const rtn = parentTn;
const tn = childTn;
@@ -2652,13 +2709,13 @@ class BaseModelSqlv2 {
const childTable = await (
await relColOptions.getChildColumn(this.context)
).getModel(this.context);
- const parentModel = await Model.getBaseModelSQL(this.context, {
+ const parentBaseModel = await Model.getBaseModelSQL(this.context, {
dbDriver: this.dbDriver,
model: parentTable,
});
const childTn = this.getTnPath(childTable);
- const parentTn = this.getTnPath(parentTable);
+ const parentTn = parentBaseModel.getTnPath(parentTable);
const rtn = parentTn;
const tn = childTn;
@@ -2679,7 +2736,7 @@ class BaseModelSqlv2 {
await this.shuffle({ qb });
}
- await parentModel.selectObject({ qb });
+ await parentBaseModel.selectObject({ qb });
const aliasColObjMap = await parentTable.getAliasColObjMap(this.context);
const filterObj = extractFilterFromXwhere(where, aliasColObjMap);
@@ -2706,7 +2763,7 @@ class BaseModelSqlv2 {
applyPaginate(qb, rest);
- const proto = await parentModel.getProto();
+ const proto = await parentBaseModel.getProto();
const data = await this.execAndParse(
qb,
await parentTable.getColumns(this.context),
@@ -5525,8 +5582,18 @@ class BaseModelSqlv2 {
await childTable.getColumns(this.context);
await parentTable.getColumns(this.context);
- const childTn = this.getTnPath(childTable);
- const parentTn = this.getTnPath(parentTable);
+ const parentBaseModel = await Model.getBaseModelSQL(this.context, {
+ model: parentTable,
+ dbDriver: this.dbDriver,
+ });
+
+ const childBaseModel = await Model.getBaseModelSQL(this.context, {
+ dbDriver: this.dbDriver,
+ model: childTable,
+ });
+
+ const childTn = childBaseModel.getTnPath(childTable);
+ const parentTn = parentBaseModel.getTnPath(parentTable);
const relatedChildCol = getRelatedLinksColumn(
column,
@@ -5582,15 +5649,22 @@ class BaseModelSqlv2 {
const vParentCol = await colOptions.getMMParentColumn(this.context);
const vTable = await colOptions.getMMModel(this.context);
- const vTn = this.getTnPath(vTable);
+ const assocBaseModel = await Model.getBaseModelSQL(this.context, {
+ model: vTable,
+ dbDriver: this.dbDriver,
+ });
+
+ const vTn = assocBaseModel.getTnPath(vTable);
if (this.isSnowflake || this.isDatabricks) {
- const parentPK = this.dbDriver(parentTn)
+ const parentPK = parentBaseModel
+ .dbDriver(parentTn)
.select(parentColumn.column_name)
.where(_wherePk(parentTable.primaryKeys, childId))
.first();
- const childPK = this.dbDriver(childTn)
+ const childPK = childBaseModel
+ .dbDriver(childTn)
.select(childColumn.column_name)
.where(_wherePk(childTable.primaryKeys, rowId))
.first();
@@ -5621,11 +5695,13 @@ class BaseModelSqlv2 {
}
await this.updateLastModified({
+ baseModel: parentBaseModel,
model: parentTable,
rowIds: [childId],
cookie,
});
await this.updateLastModified({
+ baseModel: childBaseModel,
model: childTable,
rowIds: [rowId],
cookie,
@@ -5641,14 +5717,20 @@ class BaseModelSqlv2 {
{
const linkedHmRowObj = await this.execAndParse(
this.dbDriver(childTn)
- .select(`${childTable.table_name}.${childColumn.column_name}`)
+ .select(
+ ...new Set(
+ [childColumn, ...childTable.primaryKeys].map(
+ (col) => `${childTable.table_name}.${col.column_name}`,
+ ),
+ ),
+ )
.where(_wherePk(childTable.primaryKeys, childId)),
null,
{ raw: true, first: true },
);
const oldRowId = linkedHmRowObj
- ? Object.values(linkedHmRowObj)?.[0]
+ ? linkedHmRowObj?.[childTable.primaryKey?.column_name]
: null;
if (oldRowId) {
@@ -5699,6 +5781,7 @@ class BaseModelSqlv2 {
await triggerAfterRemoveChild();
await this.updateLastModified({
+ baseModel: parentBaseModel,
model: parentTable,
rowIds: [rowId],
cookie,
@@ -5752,14 +5835,20 @@ class BaseModelSqlv2 {
} else {
const linkedHmRowObj = await this.execAndParse(
this.dbDriver(childTn)
- .select(childColumn.column_name)
+ .select(
+ ...new Set(
+ [childColumn, ...childTable.primaryKeys].map(
+ (col) => col.column_name,
+ ),
+ ),
+ )
.where(_wherePk(childTable.primaryKeys, rowId)),
null,
{ raw: true, first: true },
);
const oldChildRowId = linkedHmRowObj
- ? Object.values(linkedHmRowObj)?.[0]
+ ? linkedHmRowObj[childTable.primaryKeys[0]?.column_name]
: null;
if (oldChildRowId) {
@@ -5811,6 +5900,7 @@ class BaseModelSqlv2 {
await triggerAfterRemoveChild();
await this.updateLastModified({
+ baseModel: parentBaseModel,
model: parentTable,
rowIds: [childId],
cookie,
@@ -5830,14 +5920,20 @@ class BaseModelSqlv2 {
// 1. check current row is linked with another child
linkedCurrentOoRowObj = await this.execAndParse(
this.dbDriver(childTn)
- .select(childColumn.column_name)
+ .select(
+ ...new Set(
+ [childColumn, ...childTable.primaryKeys].map(
+ (col) => col.column_name,
+ ),
+ ),
+ )
.where(_wherePk(childTable.primaryKeys, rowId)),
null,
{ raw: true, first: true },
);
const oldChildRowId = linkedCurrentOoRowObj
- ? Object.values(linkedCurrentOoRowObj)?.[0]
+ ? linkedCurrentOoRowObj[childTable.primaryKeys[0]?.column_name]
: null;
if (oldChildRowId) {
@@ -5979,14 +6075,20 @@ class BaseModelSqlv2 {
// 2. check current child is linked with another row cell
linkedOoRowObj = await this.execAndParse(
this.dbDriver(childTn)
- .select(childColumn.column_name)
+ .select(
+ ...new Set(
+ [childColumn, ...childTable.primaryKeys].map(
+ (col) => `${childTable.table_name}.${col.column_name}`,
+ ),
+ ),
+ )
.where(_wherePk(childTable.primaryKeys, childId)),
null,
{ raw: true, first: true },
);
const oldRowId = linkedOoRowObj
- ? Object.values(linkedOoRowObj)?.[0]
+ ? linkedOoRowObj[childTable.primaryKeys[0]?.column_name]
: null;
if (oldRowId) {
const [parentRelatedPkValue, childRelatedPkValue] =
@@ -6060,6 +6162,7 @@ class BaseModelSqlv2 {
);
await this.updateLastModified({
+ baseModel: parentBaseModel,
model: parentTable,
rowIds: [childId],
cookie,
@@ -6176,8 +6279,18 @@ class BaseModelSqlv2 {
await childTable.getColumns(this.context);
await parentTable.getColumns(this.context);
- const childTn = this.getTnPath(childTable);
- const parentTn = this.getTnPath(parentTable);
+ const parentBaseModel = await Model.getBaseModelSQL(this.context, {
+ model: parentTable,
+ dbDriver: this.dbDriver,
+ });
+
+ const childBaseModel = await Model.getBaseModelSQL(this.context, {
+ dbDriver: this.dbDriver,
+ model: childTable,
+ });
+
+ const childTn = childBaseModel.getTnPath(childTable);
+ const parentTn = parentBaseModel.getTnPath(parentTable);
const relatedChildCol = getRelatedLinksColumn(
column,
@@ -6210,8 +6323,11 @@ class BaseModelSqlv2 {
const vChildCol = await colOptions.getMMChildColumn(this.context);
const vParentCol = await colOptions.getMMParentColumn(this.context);
const vTable = await colOptions.getMMModel(this.context);
-
- const vTn = this.getTnPath(vTable);
+ const assocBaseModel = await Model.getBaseModelSQL(this.context, {
+ model: vTable,
+ dbDriver: this.dbDriver,
+ });
+ const vTn = assocBaseModel.getTnPath(vTable);
await this.execAndParse(
this.dbDriver(vTn)
@@ -6231,11 +6347,13 @@ class BaseModelSqlv2 {
);
await this.updateLastModified({
+ baseModel: parentBaseModel,
model: parentTable,
rowIds: [childId],
cookie,
});
await this.updateLastModified({
+ baseModel: childBaseModel,
model: childTable,
rowIds: [rowId],
cookie,
@@ -6264,6 +6382,7 @@ class BaseModelSqlv2 {
);
await this.updateLastModified({
+ baseModel: parentBaseModel,
model: parentTable,
rowIds: [rowId],
cookie,
@@ -6290,6 +6409,7 @@ class BaseModelSqlv2 {
);
await this.updateLastModified({
+ baseModel: parentBaseModel,
model: parentTable,
rowIds: [childId],
cookie,
@@ -6312,6 +6432,7 @@ class BaseModelSqlv2 {
);
await this.updateLastModified({
+ baseModel: parentBaseModel,
model: parentTable,
rowIds: [childId],
cookie,
@@ -6791,7 +6912,7 @@ class BaseModelSqlv2 {
colId: k,
})
.then((col) => {
- return col.title;
+ return col?.title;
})
.catch((e) => {
return Promise.resolve(e);
@@ -6806,7 +6927,7 @@ class BaseModelSqlv2 {
colId: col.id,
})
.then((col) => {
- return col.title;
+ return col?.title;
})
.catch((e) => {
return Promise.resolve(e);
@@ -7265,8 +7386,18 @@ class BaseModelSqlv2 {
await childTable.getColumns(this.context);
await parentTable.getColumns(this.context);
- const childTn = this.getTnPath(childTable);
- const parentTn = this.getTnPath(parentTable);
+ const childBaseModel = await Model.getBaseModelSQL(this.context, {
+ model: childTable,
+ dbDriver: this.dbDriver,
+ });
+
+ const parentBaseModel = await Model.getBaseModelSQL(this.context, {
+ model: parentTable,
+ dbDriver: this.dbDriver,
+ });
+
+ const childTn = childBaseModel.getTnPath(childTable);
+ const parentTn = parentBaseModel.getTnPath(parentTable);
let relationType = colOptions.type;
let childIds = _childIds;
@@ -7338,7 +7469,12 @@ class BaseModelSqlv2 {
const vParentCol = await colOptions.getMMParentColumn(this.context);
const vTable = await colOptions.getMMModel(this.context);
- const vTn = this.getTnPath(vTable);
+ const assocBaseModel = await Model.getBaseModelSQL(this.context, {
+ model: vTable,
+ dbDriver: this.dbDriver,
+ });
+
+ const vTn = assocBaseModel.getTnPath(vTable);
let insertData: Record[];
@@ -7640,8 +7776,18 @@ class BaseModelSqlv2 {
await childTable.getColumns(this.context);
await parentTable.getColumns(this.context);
- const childTn = this.getTnPath(childTable);
- const parentTn = this.getTnPath(parentTable);
+ const childBaseModel = await Model.getBaseModelSQL(this.context, {
+ model: childTable,
+ dbDriver: this.dbDriver,
+ });
+
+ const parentBaseModel = await Model.getBaseModelSQL(this.context, {
+ model: parentTable,
+ dbDriver: this.dbDriver,
+ });
+
+ const childTn = childBaseModel.getTnPath(childTable);
+ const parentTn = parentBaseModel.getTnPath(parentTable);
const relatedChildCol = getRelatedLinksColumn(
column,
@@ -7678,6 +7824,11 @@ class BaseModelSqlv2 {
const vParentCol = await colOptions.getMMParentColumn(this.context);
const vTable = await colOptions.getMMModel(this.context);
+ const assocBaseModel = await Model.getBaseModelSQL(this.context, {
+ model: vTable,
+ dbDriver: this.dbDriver,
+ });
+
// validate Ids
{
const childRowsQb = this.dbDriver(parentTn).select(
@@ -7732,7 +7883,7 @@ class BaseModelSqlv2 {
}
}
- const vTn = this.getTnPath(vTable);
+ const vTn = assocBaseModel.getTnPath(vTable);
const delQb = this.dbDriver(vTn)
.where({
@@ -8027,11 +8178,13 @@ class BaseModelSqlv2 {
cookie,
model = this.model,
knex = this.dbDriver,
+ baseModel = this,
}: {
rowIds: any | any[];
cookie?: { user?: any };
model?: Model;
knex?: XKnex;
+ baseModel?: BaseModelSqlv2;
}) {
const columns = await model.getColumns(this.context);
@@ -8055,7 +8208,7 @@ class BaseModelSqlv2 {
if (Object.keys(updateObject).length === 0) return;
- const qb = knex(this.getTnPath(model.table_name)).update(updateObject);
+ const qb = knex(baseModel.getTnPath(model.table_name)).update(updateObject);
for (const rowId of Array.isArray(rowIds) ? rowIds : [rowIds]) {
qb.orWhere(_wherePk(model.primaryKeys, rowId));
diff --git a/packages/nocodb/src/db/genRollupSelectv2.ts b/packages/nocodb/src/db/genRollupSelectv2.ts
index 16965c5ed0..cba41408be 100644
--- a/packages/nocodb/src/db/genRollupSelectv2.ts
+++ b/packages/nocodb/src/db/genRollupSelectv2.ts
@@ -7,6 +7,7 @@ import type {
} from '~/models';
import type { XKnex } from '~/db/CustomKnex';
import type { Knex } from 'knex';
+import { Model } from '~/models';
export default async function ({
baseModelSqlv2,
@@ -33,11 +34,20 @@ export default async function ({
const parentModel = await parentCol?.getModel(context);
const refTableAlias = `__nc_rollup`;
+ const parentBaseModel = await Model.getBaseModelSQL(context, {
+ model: parentModel,
+ dbDriver: knex,
+ });
+ const childBaseModel = await Model.getBaseModelSQL(context, {
+ model: childModel,
+ dbDriver: knex,
+ });
+
switch (relationColumnOption.type) {
case RelationTypes.HAS_MANY: {
const queryBuilder: any = knex(
knex.raw(`?? as ??`, [
- baseModelSqlv2.getTnPath(childModel?.table_name),
+ childBaseModel.getTnPath(childModel),
refTableAlias,
]),
)
@@ -46,7 +56,7 @@ export default async function ({
)
.where(
knex.ref(
- `${alias || baseModelSqlv2.getTnPath(parentModel.table_name)}.${
+ `${alias || parentBaseModel.getTnPath(parentModel.table_name)}.${
parentCol.column_name
}`,
),
@@ -62,7 +72,7 @@ export default async function ({
case RelationTypes.ONE_TO_ONE: {
const qb = knex(
knex.raw(`?? as ??`, [
- baseModelSqlv2.getTnPath(childModel?.table_name),
+ childBaseModel.getTnPath(childModel?.table_name),
refTableAlias,
]),
)
@@ -71,7 +81,7 @@ export default async function ({
)
.where(
knex.ref(
- `${alias || baseModelSqlv2.getTnPath(parentModel.table_name)}.${
+ `${alias || parentBaseModel.getTnPath(parentModel.table_name)}.${
parentCol.column_name
}`,
),
@@ -88,7 +98,10 @@ export default async function ({
const mmModel = await relationColumnOption.getMMModel(context);
const mmChildCol = await relationColumnOption.getMMChildColumn(context);
const mmParentCol = await relationColumnOption.getMMParentColumn(context);
-
+ const assocBaseModel = await Model.getBaseModelSQL(context, {
+ id: mmModel.id,
+ dbDriver: knex,
+ });
if (!mmModel) {
return this.dbDriver.raw(`?`, [
NcDataErrorCodes.NC_ERR_MM_MODEL_NOT_FOUND,
@@ -97,7 +110,7 @@ export default async function ({
const qb = knex(
knex.raw(`?? as ??`, [
- baseModelSqlv2.getTnPath(parentModel?.table_name),
+ parentBaseModel.getTnPath(parentModel?.table_name),
refTableAlias,
]),
)
@@ -105,9 +118,9 @@ export default async function ({
knex.ref(`${refTableAlias}.${rollupColumn.column_name}`),
)
.innerJoin(
- baseModelSqlv2.getTnPath(mmModel.table_name),
+ assocBaseModel.getTnPath(mmModel.table_name),
knex.ref(
- `${baseModelSqlv2.getTnPath(mmModel.table_name)}.${
+ `${assocBaseModel.getTnPath(mmModel.table_name)}.${
mmParentCol.column_name
}`,
),
@@ -116,13 +129,13 @@ export default async function ({
)
.where(
knex.ref(
- `${baseModelSqlv2.getTnPath(mmModel.table_name)}.${
+ `${assocBaseModel.getTnPath(mmModel.table_name)}.${
mmChildCol.column_name
}`,
),
'=',
knex.ref(
- `${alias || baseModelSqlv2.getTnPath(childModel.table_name)}.${
+ `${alias || childBaseModel.getTnPath(childModel.table_name)}.${
childCol.column_name
}`,
),
diff --git a/packages/nocodb/src/db/generateLookupSelectQuery.ts b/packages/nocodb/src/db/generateLookupSelectQuery.ts
index 025ab085f6..f40f5499c2 100644
--- a/packages/nocodb/src/db/generateLookupSelectQuery.ts
+++ b/packages/nocodb/src/db/generateLookupSelectQuery.ts
@@ -1,4 +1,4 @@
-import { isVirtualCol, RelationTypes, UITypes } from 'nocodb-sdk';
+import { RelationTypes, UITypes } from 'nocodb-sdk';
import type LookupColumn from '../models/LookupColumn';
import type { BaseModelSqlv2 } from '~/db/BaseModelSqlv2';
import type {
@@ -92,9 +92,14 @@ export default async function generateLookupSelectQuery({
const parentModel = await parentColumn.getModel(context);
await parentModel.getColumns(context);
+ const parentBaseModel = await Model.getBaseModelSQL(context, {
+ model: parentModel,
+ dbDriver: knex,
+ });
+
selectQb = knex(
knex.raw(`?? as ??`, [
- baseModelSqlv2.getTnPath(parentModel.table_name),
+ parentBaseModel.getTnPath(parentModel.table_name),
alias,
]),
).where(
@@ -113,10 +118,14 @@ export default async function generateLookupSelectQuery({
await childModel.getColumns(context);
const parentModel = await parentColumn.getModel(context);
await parentModel.getColumns(context);
+ const parentBaseModel = await Model.getBaseModelSQL(context, {
+ model: parentModel,
+ dbDriver: knex,
+ });
selectQb = knex(
knex.raw(`?? as ??`, [
- baseModelSqlv2.getTnPath(childModel.table_name),
+ parentBaseModel.getTnPath(childModel.table_name),
alias,
]),
).where(
@@ -136,9 +145,14 @@ export default async function generateLookupSelectQuery({
const parentModel = await parentColumn.getModel(context);
await parentModel.getColumns(context);
+ const parentBaseModel = await Model.getBaseModelSQL(context, {
+ model: parentModel,
+ dbDriver: knex,
+ });
+
selectQb = knex(
knex.raw(`?? as ??`, [
- baseModelSqlv2.getTnPath(parentModel.table_name),
+ parentBaseModel.getTnPath(parentModel.table_name),
alias,
]),
);
@@ -149,9 +163,14 @@ export default async function generateLookupSelectQuery({
const mmChildCol = await relation.getMMChildColumn(context);
const mmParentCol = await relation.getMMParentColumn(context);
+ const associatedBaseModel = await Model.getBaseModelSQL(context, {
+ model: mmModel,
+ dbDriver: knex,
+ });
+
selectQb
.innerJoin(
- baseModelSqlv2.getTnPath(mmModel.table_name, mmTableAlias),
+ associatedBaseModel.getTnPath(mmModel.table_name, mmTableAlias),
knex.ref(`${mmTableAlias}.${mmParentCol.column_name}`),
'=',
knex.ref(`${alias}.${parentColumn.column_name}`),
@@ -217,10 +236,14 @@ export default async function generateLookupSelectQuery({
await childModel.getColumns(context);
const parentModel = await parentColumn.getModel(context);
await parentModel.getColumns(context);
+ const parentBaseModel = await Model.getBaseModelSQL(context, {
+ model: parentModel,
+ dbDriver: knex,
+ });
selectQb.join(
knex.raw(`?? as ??`, [
- baseModelSqlv2.getTnPath(parentModel.table_name),
+ parentBaseModel.getTnPath(parentModel.table_name),
nestedAlias,
]),
`${nestedAlias}.${parentColumn.column_name}`,
@@ -234,10 +257,14 @@ export default async function generateLookupSelectQuery({
await childModel.getColumns(context);
const parentModel = await parentColumn.getModel(context);
await parentModel.getColumns(context);
+ const childBaseModel = await Model.getBaseModelSQL(context, {
+ model: childModel,
+ dbDriver: knex,
+ });
selectQb.join(
knex.raw(`?? as ??`, [
- baseModelSqlv2.getTnPath(childModel.table_name),
+ childBaseModel.getTnPath(childModel.table_name),
nestedAlias,
]),
`${nestedAlias}.${childColumn.column_name}`,
@@ -251,6 +278,10 @@ export default async function generateLookupSelectQuery({
await childModel.getColumns(context);
const parentModel = await parentColumn.getModel(context);
await parentModel.getColumns(context);
+ const parentBaseModel = await Model.getBaseModelSQL(context, {
+ model: parentModel,
+ dbDriver: knex,
+ });
const mmTableAlias = getAlias();
@@ -258,16 +289,21 @@ export default async function generateLookupSelectQuery({
const mmChildCol = await relation.getMMChildColumn(context);
const mmParentCol = await relation.getMMParentColumn(context);
+ const associatedBaseModel = await Model.getBaseModelSQL(context, {
+ model: mmModel,
+ dbDriver: knex,
+ });
+
selectQb
.innerJoin(
- baseModelSqlv2.getTnPath(mmModel.table_name, mmTableAlias),
+ associatedBaseModel.getTnPath(mmModel.table_name, mmTableAlias),
knex.ref(`${mmTableAlias}.${mmChildCol.column_name}`),
'=',
knex.ref(`${prevAlias}.${childColumn.column_name}`),
)
.innerJoin(
knex.raw('?? as ??', [
- baseModelSqlv2.getTnPath(parentModel.table_name),
+ parentBaseModel.getTnPath(parentModel.table_name),
nestedAlias,
]),
knex.ref(`${mmTableAlias}.${mmParentCol.column_name}`),
diff --git a/packages/nocodb/src/helpers/catchError.ts b/packages/nocodb/src/helpers/catchError.ts
index 2994434409..71a566c2b9 100644
--- a/packages/nocodb/src/helpers/catchError.ts
+++ b/packages/nocodb/src/helpers/catchError.ts
@@ -558,6 +558,14 @@ const errorHelpers: {
message: 'Invalid JSON in request body',
code: 400,
},
+ [NcErrorType.COLUMN_ASSOCIATED_WITH_LINK]: {
+ message: 'Column is associated with a link, please remove the link first',
+ code: 400,
+ },
+ [NcErrorType.TABLE_ASSOCIATED_WITH_LINK]: {
+ message: 'Table is associated with a link, please remove the link first',
+ code: 400,
+ },
};
function generateError(
@@ -631,6 +639,14 @@ export class NcError {
});
}
+ static columnAssociatedWithLink(_id: string, args: NcErrorArgs) {
+ throw new NcBaseErrorv2(NcErrorType.COLUMN_ASSOCIATED_WITH_LINK, args);
+ }
+
+ static tableAssociatedWithLink(_id: string, args: NcErrorArgs) {
+ throw new NcBaseErrorv2(NcErrorType.TABLE_ASSOCIATED_WITH_LINK, args);
+ }
+
static baseNotFound(id: string, args?: NcErrorArgs) {
throw new NcBaseErrorv2(NcErrorType.BASE_NOT_FOUND, {
params: id,
diff --git a/packages/nocodb/src/helpers/columnHelpers.ts b/packages/nocodb/src/helpers/columnHelpers.ts
index 35d2031dfa..2d83aa774d 100644
--- a/packages/nocodb/src/helpers/columnHelpers.ts
+++ b/packages/nocodb/src/helpers/columnHelpers.ts
@@ -43,6 +43,8 @@ export async function createHmAndBtColumn(
columnMeta = null,
isLinks = false,
colExtra?: any,
+ parentColumn?: Column,
+ isCustom = false,
) {
// save bt column
{
@@ -60,7 +62,7 @@ export async function createHmAndBtColumn(
// db_type:
fk_child_column_id: childColumn.id,
- fk_parent_column_id: parent.primaryKey.id,
+ fk_parent_column_id: parentColumn?.id || parent.primaryKey.id,
fk_related_model_id: parent.id,
virtual,
// if self referencing treat it as system field to hide from ui
@@ -68,6 +70,10 @@ export async function createHmAndBtColumn(
fk_col_name: fkColName,
fk_index_name: fkColName,
...(type === 'bt' ? colExtra : {}),
+ meta: {
+ ...(colExtra?.meta || {}),
+ custom: isCustom,
+ },
});
}
// save hm column
@@ -80,6 +86,7 @@ export async function createHmAndBtColumn(
...(columnMeta || {}),
plural: columnMeta?.plural || pluralize(child.title),
singular: columnMeta?.singular || singularize(child.title),
+ custom: isCustom,
};
await Column.insert(context, {
@@ -89,7 +96,7 @@ export async function createHmAndBtColumn(
type: 'hm',
fk_target_view_id: childView?.id,
fk_child_column_id: childColumn.id,
- fk_parent_column_id: parent.primaryKey.id,
+ fk_parent_column_id: parentColumn?.id || parent.primaryKey.id,
fk_related_model_id: child.id,
virtual,
system: isSystemCol,
@@ -128,6 +135,8 @@ export async function createOOColumn(
isSystemCol = false,
columnMeta = null,
colExtra?: any,
+ parentColumn?: Column,
+ isCustom = false,
) {
// save bt column
{
@@ -144,7 +153,7 @@ export async function createOOColumn(
// Child View ID is given for relation from parent to child. not for child to parent
fk_target_view_id: null,
fk_child_column_id: childColumn.id,
- fk_parent_column_id: parent.primaryKey.id,
+ fk_parent_column_id: parentColumn?.id || parent.primaryKey.id,
fk_related_model_id: parent.id,
virtual,
// if self referencing treat it as system field to hide from ui
@@ -157,6 +166,7 @@ export async function createOOColumn(
// one-to-one relation is combination of both hm and bt to identify table which have
// foreign key column(similar to bt) we are adding a boolean flag `bt` under meta
bt: true,
+ custom: isCustom,
},
});
}
@@ -176,6 +186,7 @@ export async function createOOColumn(
...(columnMeta || {}),
plural: columnMeta?.plural || pluralize(child.title),
singular: columnMeta?.singular || singularize(child.title),
+ custom: isCustom,
};
await Column.insert(context, {
@@ -185,7 +196,7 @@ export async function createOOColumn(
type: 'oo',
fk_target_view_id: childView?.id,
fk_child_column_id: childColumn.id,
- fk_parent_column_id: parent.primaryKey.id,
+ fk_parent_column_id: parentColumn?.id || parent.primaryKey.id,
fk_related_model_id: child.id,
virtual,
system: isSystemCol,
diff --git a/packages/nocodb/src/modules/jobs/jobs/at-import/at-import.processor.ts b/packages/nocodb/src/modules/jobs/jobs/at-import/at-import.processor.ts
index e011cff638..ac014a0491 100644
--- a/packages/nocodb/src/modules/jobs/jobs/at-import/at-import.processor.ts
+++ b/packages/nocodb/src/modules/jobs/jobs/at-import/at-import.processor.ts
@@ -15,6 +15,7 @@ import FetchAT from './helpers/fetchAT';
import { importData } from './helpers/readAndProcessData';
import EntityMap from './helpers/EntityMap';
import type { UserType } from 'nocodb-sdk';
+import type { AtImportJobData } from '~/interface/Jobs';
import { type Base, Model, Source } from '~/models';
import { sanitizeColumnName } from '~/helpers';
import { AttachmentsService } from '~/services/attachments.service';
@@ -31,7 +32,7 @@ import { TablesService } from '~/services/tables.service';
import { ViewColumnsService } from '~/services/view-columns.service';
import { ViewsService } from '~/services/views.service';
import { FormsService } from '~/services/forms.service';
-import { AtImportJobData, JOBS_QUEUE, JobTypes } from '~/interface/Jobs';
+import { JOBS_QUEUE, JobTypes } from '~/interface/Jobs';
import { GridColumnsService } from '~/services/grid-columns.service';
import { TelemetryService } from '~/services/telemetry.service';
import NcConnectionMgrv2 from '~/utils/common/NcConnectionMgrv2';
diff --git a/packages/nocodb/src/redis/pubsub-redis.ts b/packages/nocodb/src/redis/pubsub-redis.ts
index dd699afa63..d9e10a1dd5 100644
--- a/packages/nocodb/src/redis/pubsub-redis.ts
+++ b/packages/nocodb/src/redis/pubsub-redis.ts
@@ -42,9 +42,9 @@ export class PubSubRedis {
}
/**
- *
- * @param channel
- * @param callback
+ *
+ * @param channel
+ * @param callback
* @returns Returns a callback to unsubscribe
*/
static async subscribe(
@@ -74,7 +74,8 @@ export class PubSubRedis {
PubSubRedis.redisSubscriber.on('message', onMessage);
return async (keepRedisChannel = false) => {
// keepRedisChannel is used to keep the channel open for other subscribers
- if (!keepRedisChannel) await PubSubRedis.redisSubscriber.unsubscribe(channel);
+ if (!keepRedisChannel)
+ await PubSubRedis.redisSubscriber.unsubscribe(channel);
PubSubRedis.redisSubscriber.off('message', onMessage);
};
}
diff --git a/packages/nocodb/src/services/columns.service.ts b/packages/nocodb/src/services/columns.service.ts
index 7a6707dca8..32c50303d2 100644
--- a/packages/nocodb/src/services/columns.service.ts
+++ b/packages/nocodb/src/services/columns.service.ts
@@ -72,7 +72,7 @@ export enum Altered {
UPDATE_COLUMN = 8,
}
-interface ReusableParams {
+export interface ReusableParams {
table?: Model;
source?: Source;
base?: Base;
@@ -2099,6 +2099,59 @@ export class ColumnsService {
ProjectMgrv2.getSqlMgr(context, { id: source.base_id }, ncMeta),
);
+ // check column association with any custom links or LTAR
+ if (!isVirtualCol(column)) {
+ const columns = await table.getColumns(context, ncMeta);
+
+ let link = columns.find((c) => {
+ return (
+ isLinksOrLTAR(c.uidt) &&
+ ((c.colOptions as LinkToAnotherRecordColumn)?.fk_child_column_id ===
+ param.columnId ||
+ (c.colOptions as LinkToAnotherRecordColumn)?.fk_parent_column_id ===
+ param.columnId ||
+ (c.colOptions as LinkToAnotherRecordColumn)
+ ?.fk_mm_child_column_id === param.columnId ||
+ (c.colOptions as LinkToAnotherRecordColumn)
+ ?.fk_mm_parent_column_id === param.columnId)
+ );
+ })?.colOptions as LinkToAnotherRecordColumn;
+ if (!link) {
+ link = await ncMeta.metaGet2(
+ table.fk_workspace_id,
+ table.base_id,
+ MetaTable.COL_RELATIONS,
+ {},
+ null,
+ {
+ _or: [
+ { fk_child_column_id: { eq: param.columnId } },
+ { fk_parent_column_id: { eq: param.columnId } },
+ { fk_mm_child_column_id: { eq: param.columnId } },
+ { fk_mm_parent_column_id: { eq: param.columnId } },
+ ],
+ },
+ );
+ }
+
+ // if relation found then throw error
+ if (link) {
+ const linkCol = await Column.get(
+ context,
+ { colId: link.fk_column_id },
+ ncMeta,
+ );
+ const table = await linkCol.getModel(context, ncMeta);
+ NcError.columnAssociatedWithLink(column.id, {
+ customMessage: `Column is associated with Link column '${
+ linkCol.title || linkCol.column_name
+ }' (${
+ table.title || table.table_name
+ }). Please delete the link column first.`,
+ });
+ }
+ }
+
/**
* @Note: When using 'falls through to default' cases in a switch statement,
* it is crucial to place them after cases with break statements.
@@ -2178,6 +2231,7 @@ export class ColumnsService {
ncMeta,
);
const parentTable = await parentColumn.getModel(context, ncMeta);
+ const custom = column.meta?.custom;
switch (relationColOpt.type) {
case 'bt':
@@ -2192,6 +2246,7 @@ export class ColumnsService {
parentTable,
sqlMgr,
ncMeta,
+ custom,
});
}
break;
@@ -2206,6 +2261,7 @@ export class ColumnsService {
parentTable,
sqlMgr,
ncMeta,
+ custom,
});
}
break;
@@ -2224,37 +2280,39 @@ export class ColumnsService {
ncMeta,
);
- await this.deleteHmOrBtRelation(
- context,
- {
- relationColOpt: null,
- parentColumn: parentColumn,
- childTable: mmTable,
- sqlMgr,
- parentTable: parentTable,
- childColumn: mmParentCol,
- source,
- ncMeta,
- virtual: !!relationColOpt.virtual,
- },
- true,
- );
+ if (!custom) {
+ await this.deleteHmOrBtRelation(
+ context,
+ {
+ relationColOpt: null,
+ parentColumn: parentColumn,
+ childTable: mmTable,
+ sqlMgr,
+ parentTable: parentTable,
+ childColumn: mmParentCol,
+ source,
+ ncMeta,
+ virtual: !!relationColOpt.virtual,
+ },
+ true,
+ );
- await this.deleteHmOrBtRelation(
- context,
- {
- relationColOpt: null,
- parentColumn: childColumn,
- childTable: mmTable,
- sqlMgr,
- parentTable: childTable,
- childColumn: mmChildCol,
- source,
- ncMeta,
- virtual: !!relationColOpt.virtual,
- },
- true,
- );
+ await this.deleteHmOrBtRelation(
+ context,
+ {
+ relationColOpt: null,
+ parentColumn: childColumn,
+ childTable: mmTable,
+ sqlMgr,
+ parentTable: childTable,
+ childColumn: mmChildCol,
+ source,
+ ncMeta,
+ virtual: !!relationColOpt.virtual,
+ },
+ true,
+ );
+ }
const columnsInRelatedTable: Column[] = await relationColOpt
.getRelatedTable(context, ncMeta)
.then((m) => m.getColumns(context, ncMeta));
@@ -2287,64 +2345,68 @@ export class ColumnsService {
ncMeta,
);
- if (mmTable) {
- // delete bt columns in m2m table
- await mmTable.getColumns(context, ncMeta);
- for (const c of mmTable.columns) {
+ if (!custom) {
+ if (mmTable) {
+ // delete bt columns in m2m table
+ await mmTable.getColumns(context, ncMeta);
+ for (const c of mmTable.columns) {
+ if (!isLinksOrLTAR(c.uidt)) continue;
+ const colOpt =
+ await c.getColOptions(
+ context,
+ ncMeta,
+ );
+ if (colOpt.type === 'bt') {
+ await Column.delete(context, c.id, ncMeta);
+ }
+ }
+ }
+
+ // delete hm columns in parent table
+ await parentTable.getColumns(context, ncMeta);
+ for (const c of parentTable.columns) {
if (!isLinksOrLTAR(c.uidt)) continue;
const colOpt =
await c.getColOptions(
context,
ncMeta,
);
- if (colOpt.type === 'bt') {
+ if (
+ colOpt.fk_related_model_id ===
+ relationColOpt.fk_mm_model_id
+ ) {
await Column.delete(context, c.id, ncMeta);
}
}
- }
-
- // delete hm columns in parent table
- await parentTable.getColumns(context, ncMeta);
- for (const c of parentTable.columns) {
- if (!isLinksOrLTAR(c.uidt)) continue;
- const colOpt =
- await c.getColOptions(
- context,
- ncMeta,
- );
- if (
- colOpt.fk_related_model_id === relationColOpt.fk_mm_model_id
- ) {
- await Column.delete(context, c.id, ncMeta);
- }
- }
- // delete hm columns in child table
- await childTable.getColumns(context, ncMeta);
- for (const c of childTable.columns) {
- if (!isLinksOrLTAR(c.uidt)) continue;
- const colOpt =
- await c.getColOptions(
- context,
- ncMeta,
- );
- if (
- colOpt.fk_related_model_id === relationColOpt.fk_mm_model_id
- ) {
- await Column.delete(context, c.id, ncMeta);
+ // delete hm columns in child table
+ await childTable.getColumns(context, ncMeta);
+ for (const c of childTable.columns) {
+ if (!isLinksOrLTAR(c.uidt)) continue;
+ const colOpt =
+ await c.getColOptions(
+ context,
+ ncMeta,
+ );
+ if (
+ colOpt.fk_related_model_id ===
+ relationColOpt.fk_mm_model_id
+ ) {
+ await Column.delete(context, c.id, ncMeta);
+ }
}
- }
- if (mmTable) {
- // retrieve columns in m2m table again
- await mmTable.getColumns(context, ncMeta);
+ if (mmTable) {
+ // retrieve columns in m2m table again
+ await mmTable.getColumns(context, ncMeta);
- // ignore deleting table if it has more than 2 columns
- // the expected 2 columns would be table1_id & table2_id
- if (mmTable.columns.length === 2) {
- (mmTable as any).tn = mmTable.table_name;
- await sqlMgr.sqlOpPlus(source, 'tableDelete', mmTable);
- await mmTable.delete(context, ncMeta);
+ // ignore deleting table if it has more than 2 columns
+ // the expected 2 columns would be table1_id & table2_id
+ if (mmTable.columns.length === 2) {
+ (mmTable as any).tn = mmTable.table_name;
+ await sqlMgr.sqlOpPlus(source, 'tableDelete', mmTable);
+ await mmTable.delete(context, ncMeta);
+ }
}
}
}
@@ -2450,6 +2512,7 @@ export class ColumnsService {
sqlMgr,
ncMeta = Noco.ncMeta,
virtual,
+ custom = false,
}: {
relationColOpt: LinkToAnotherRecordColumn;
source: Source;
@@ -2460,10 +2523,11 @@ export class ColumnsService {
sqlMgr: SqlMgrv2;
ncMeta?: MetaService;
virtual?: boolean;
+ custom?: boolean;
},
ignoreFkDelete = false,
) => {
- if (childTable) {
+ if (childTable && !custom) {
let foreignKeyName;
// if relationColOpt is not provided, extract it from child table
@@ -2514,7 +2578,7 @@ export class ColumnsService {
.then((m) => m.getColumns(context, ncMeta));
const relType = relationColOpt.type === 'bt' ? 'hm' : 'bt';
for (const c of columnsInRelatedTable) {
- if (c.uidt !== UITypes.LinkToAnotherRecord) continue;
+ if (!isLinksOrLTAR(c.uidt)) continue;
const colOpt = await c.getColOptions(
context,
ncMeta,
@@ -2532,6 +2596,7 @@ export class ColumnsService {
// delete virtual columns
await Column.delete(context, relationColOpt.fk_column_id, ncMeta);
+ if (custom) return;
if (!ignoreFkDelete) {
const cTable = await Model.getWithInfo(
context,
@@ -2603,6 +2668,7 @@ export class ColumnsService {
sqlMgr,
ncMeta = Noco.ncMeta,
virtual,
+ custom = false,
}: {
relationColOpt: LinkToAnotherRecordColumn;
source: Source;
@@ -2613,50 +2679,55 @@ export class ColumnsService {
sqlMgr: SqlMgrv2;
ncMeta?: MetaService;
virtual?: boolean;
+ custom?: boolean;
},
ignoreFkDelete = false,
) => {
if (childTable) {
- let foreignKeyName;
-
- // if relationColOpt is not provided, extract it from child table
- // and get the foreign key name for dropping the foreign key
- if (!relationColOpt) {
- foreignKeyName = (
- (
- await childTable.getColumns(context, ncMeta).then(async (cols) => {
- for (const col of cols) {
- if (col.uidt === UITypes.LinkToAnotherRecord) {
- const colOptions =
- await col.getColOptions(
- context,
- ncMeta,
- );
- if (colOptions.fk_related_model_id === parentTable.id) {
- return { colOptions };
+ if (!custom) {
+ let foreignKeyName;
+
+ // if relationColOpt is not provided, extract it from child table
+ // and get the foreign key name for dropping the foreign key
+ if (!relationColOpt) {
+ foreignKeyName = (
+ (
+ await childTable
+ .getColumns(context, ncMeta)
+ .then(async (cols) => {
+ for (const col of cols) {
+ if (col.uidt === UITypes.LinkToAnotherRecord) {
+ const colOptions =
+ await col.getColOptions(
+ context,
+ ncMeta,
+ );
+ if (colOptions.fk_related_model_id === parentTable.id) {
+ return { colOptions };
+ }
+ }
}
- }
- }
- })
- )?.colOptions as LinkToAnotherRecordType
- ).fk_index_name;
- } else {
- foreignKeyName = relationColOpt.fk_index_name;
- }
+ })
+ )?.colOptions as LinkToAnotherRecordType
+ ).fk_index_name;
+ } else {
+ foreignKeyName = relationColOpt.fk_index_name;
+ }
- if (!relationColOpt?.virtual && !virtual) {
- // Ensure relation deletion is not attempted for virtual relations
- try {
- // Attempt to delete the foreign key constraint from the database
- await sqlMgr.sqlOpPlus(source, 'relationDelete', {
- childColumn: childColumn.column_name,
- childTable: childTable.table_name,
- parentTable: parentTable.table_name,
- parentColumn: parentColumn.column_name,
- foreignKeyName,
- });
- } catch (e) {
- console.log(e.message);
+ if (!relationColOpt?.virtual && !virtual) {
+ // Ensure relation deletion is not attempted for virtual relations
+ try {
+ // Attempt to delete the foreign key constraint from the database
+ await sqlMgr.sqlOpPlus(source, 'relationDelete', {
+ childColumn: childColumn.column_name,
+ childTable: childTable.table_name,
+ parentTable: parentTable.table_name,
+ parentColumn: parentColumn.column_name,
+ foreignKeyName,
+ });
+ } catch (e) {
+ console.log(e.message);
+ }
}
}
}
@@ -2685,6 +2756,8 @@ export class ColumnsService {
// delete virtual columns
await Column.delete(context, relationColOpt.fk_column_id, ncMeta);
+ if (custom) return;
+
if (!ignoreFkDelete) {
const cTable = await Model.getWithInfo(
context,
@@ -2739,7 +2812,6 @@ export class ColumnsService {
};
await sqlMgr.sqlOpPlus(source, 'tableUpdate', tableUpdateBody);
-
// delete foreign key column
await Column.delete(context, childColumn.id, ncMeta);
}
diff --git a/packages/nocodb/src/services/tables.service.ts b/packages/nocodb/src/services/tables.service.ts
index adedb45d3d..b08fc4c3a7 100644
--- a/packages/nocodb/src/services/tables.service.ts
+++ b/packages/nocodb/src/services/tables.service.ts
@@ -37,6 +37,7 @@ import {
getUniqueColumnAliasName,
getUniqueColumnName,
} from '~/helpers/getUniqueName';
+import { MetaTable } from '~/utils/globals';
@Injectable()
export class TablesService {
@@ -224,6 +225,28 @@ export class TablesService {
NcError.badRequest(
`This is a many to many table for ${tables[0]?.title} (${relColumns[0]?.title}) & ${tables[1]?.title} (${relColumns[1]?.title}). You can disable "Show M2M tables" in base settings to avoid seeing this.`,
);
+ } else {
+ // if table is using in custom relation as junction table then delete all the relation
+ const relations = await Noco.ncMeta.metaList2(
+ table.fk_workspace_id,
+ table.base_id,
+ MetaTable.COL_RELATIONS,
+ {
+ condition: {
+ fk_mm_model_id: table.id,
+ },
+ },
+ );
+
+ if (relations.length) {
+ const relCol = await Column.get(context, {
+ colId: relations[0].fk_column_id,
+ });
+ const relTable = await Model.get(context, relCol.fk_model_id);
+ NcError.tableAssociatedWithLink(table.id, {
+ customMessage: `This is a many to many table for '${relTable?.title}' (${relTable?.title}), please delete the column before deleting the table.`,
+ });
+ }
}
const base = await Base.getWithInfo(context, table.base_id);
diff --git a/packages/nocodb/tests/unit/factory/table.ts b/packages/nocodb/tests/unit/factory/table.ts
index e8e1acb1d9..37b2bcdf06 100644
--- a/packages/nocodb/tests/unit/factory/table.ts
+++ b/packages/nocodb/tests/unit/factory/table.ts
@@ -14,7 +14,8 @@ const createTable = async (context, base, args = {}) => {
const response = await request(context.app)
.post(`/api/v1/db/meta/projects/${base.id}/tables`)
.set('xc-auth', context.token)
- .send({ ...defaultValue, ...args });
+ .send({ ...defaultValue, ...args })
+ .expect(200);
const table: Model = await Model.get(
{
diff --git a/packages/nocodb/tests/unit/rest/index.test.ts b/packages/nocodb/tests/unit/rest/index.test.ts
index a9208b7be4..328b23676a 100644
--- a/packages/nocodb/tests/unit/rest/index.test.ts
+++ b/packages/nocodb/tests/unit/rest/index.test.ts
@@ -19,11 +19,13 @@ let workspaceTest = () => {};
let ssoTest = () => {};
let cloudOrgTest = () => {};
let bulkAggregationTest = () => {};
+let columnTest = () => {};
if (process.env.EE === 'true') {
workspaceTest = require('./tests/ee/workspace.test').default;
ssoTest = require('./tests/ee/sso.test').default;
cloudOrgTest = require('./tests/ee/cloud-org.test').default;
bulkAggregationTest = require('./tests/ee/bulkAggregation.test').default;
+ columnTest = require('./tests/ee/column.test').default;
}
// import layoutTests from './tests/layout.test';
// import widgetTest from './tests/widget.test';
@@ -48,6 +50,7 @@ function restTests() {
readOnlyTest();
aggregationTest();
bulkAggregationTest();
+ columnTest();
// Enable for dashboard feature
// widgetTest();
diff --git a/packages/nocodb/tsconfig.json b/packages/nocodb/tsconfig.json
index e406799ca1..3c4c58a783 100644
--- a/packages/nocodb/tsconfig.json
+++ b/packages/nocodb/tsconfig.json
@@ -41,5 +41,5 @@
]
},
"include": ["src/**/*"],
- "exclude": ["src/ee", "src/ee-on-prem"]
+ "exclude": ["src/ee", "src/ee-on-prem", "src/ee-cloud"]
}
diff --git a/tests/playwright/pages/Dashboard/Details/FieldsPage.ts b/tests/playwright/pages/Dashboard/Details/FieldsPage.ts
index 286122cc21..decaa28068 100644
--- a/tests/playwright/pages/Dashboard/Details/FieldsPage.ts
+++ b/tests/playwright/pages/Dashboard/Details/FieldsPage.ts
@@ -208,6 +208,10 @@ export class FieldsPage extends BasePage {
break;
case 'Links':
await this.addOrEditColumn.locator('.nc-ltar-relation-type').getByTestId(relationType).click();
+ // await this.addOrEditColumn
+ // .locator('.nc-ltar-relation-type >> .ant-radio')
+ // .nth(relationType === 'Has Many' ? 1 : 0)
+ // .click();
await this.addOrEditColumn.locator('.ant-select-single').nth(1).click();
await this.rootPage.locator(`.nc-ltar-child-table >> input[type="search"]`).first().fill(childTable);
await this.rootPage
diff --git a/tests/playwright/pages/Dashboard/Grid/Column/index.ts b/tests/playwright/pages/Dashboard/Grid/Column/index.ts
index 4a5c5ba258..02eb2e1b84 100644
--- a/tests/playwright/pages/Dashboard/Grid/Column/index.ts
+++ b/tests/playwright/pages/Dashboard/Grid/Column/index.ts
@@ -73,6 +73,8 @@ export class ColumnPageObject extends BasePage {
isDisplayValue = false,
ltarFilters,
ltarView,
+ custom = false,
+ refColumn,
}: {
title: string;
type?: string;
@@ -92,6 +94,8 @@ export class ColumnPageObject extends BasePage {
isDisplayValue?: boolean;
ltarFilters?: any[];
ltarView?: string;
+ custom?: boolean;
+ refColumn?: string;
}) {
if (insertBeforeColumnTitle) {
await this.grid.get().locator(`th[data-title="${insertBeforeColumnTitle}"]`).scrollIntoViewIfNeeded();
@@ -197,6 +201,10 @@ export class ColumnPageObject extends BasePage {
await this.rootPage.waitForTimeout(2000);
await this.get().locator('.nc-ltar-relation-type').getByTestId(relationType).click();
+ // await this.get()
+ // .locator('.nc-ltar-relation-type >> .ant-radio')
+ // .nth(relationType === 'Has Many' ? 1 : 0)
+ // .click();
await this.get().locator('.ant-select-single').nth(1).click();
await this.rootPage.locator(`.nc-ltar-child-table >> input[type="search"]`).fill(childTable);
await this.rootPage
@@ -214,6 +222,31 @@ export class ColumnPageObject extends BasePage {
await this.ltarOption.addFilters(ltarFilters);
}
+
+ if (custom) {
+ // enable advance options
+ await this.get().locator('.nc-ltar-relation-type >> .ant-radio').nth(1).dblclick();
+ await this.get().locator('.nc-ltar-relation-type >> .ant-radio').nth(2).dblclick();
+
+ await this.get().locator(':has(:has-text("Advanced Link")) > button.ant-switch').click();
+
+ // data-testid="custom-link-source-base-id"
+ // data-testid="custom-link-source-table-id"
+ // data-testid="custom-link-source-column-id"
+ // data-testid="custom-link-junction-base-id"
+ // data-testid="custom-link-junction-table-id"
+ // data-testid="custom-link-junction-source-column-id"
+ // data-testid="custom-link-junction-target-column-id"
+ // data-testid="custom-link-target-base-id"
+ // data-testid="custom-link-target-table-id"
+ // data-testid="custom-link-target-column-id"
+
+ // select target table and column
+ // await this.get().get('').nth(2).click();
+
+ // select referenced base, column and column
+ }
+
break;
case 'User':
break;
diff --git a/tests/playwright/tests/db/columns/columnLinkToAnotherRecordAdvanceOptions.spec.ts b/tests/playwright/tests/db/columns/columnLinkToAnotherRecordAdvanceOptions.spec.ts
new file mode 100644
index 0000000000..bf2e5cd734
--- /dev/null
+++ b/tests/playwright/tests/db/columns/columnLinkToAnotherRecordAdvanceOptions.spec.ts
@@ -0,0 +1,349 @@
+import { test } from '@playwright/test';
+import { DashboardPage } from '../../../pages/Dashboard';
+import setup, { unsetup } from '../../../setup';
+import { enableQuickRun } from '../../../setup/db';
+import { UITypes } from 'nocodb-sdk';
+
+test.describe('LTAR create & update', () => {
+ // force disabled temporarily
+ // to be re-visited after advance options menu is finalised
+ test.skip();
+
+ if (enableQuickRun()) test.skip();
+
+ let dashboard: DashboardPage;
+ let context: any;
+
+ // todo: Break the test into smaller tests
+ test.setTimeout(150000);
+
+ test.beforeEach(async ({ page }) => {
+ context = await setup({ page, isEmptyProject: true });
+ dashboard = new DashboardPage(page, context.base);
+ });
+
+ test.afterEach(async () => {
+ await unsetup(context);
+ });
+
+ test('LTAR', async () => {
+ await dashboard.treeView.createTable({ title: 'Sheet1', baseTitle: context.base.title });
+ // subsequent table creation fails; hence delay
+ await dashboard.rootPage.waitForTimeout(1000);
+ await dashboard.treeView.createTable({ title: 'Sheet2', baseTitle: context.base.title });
+
+ await dashboard.grid.column.create({
+ title: 'Sheet1Id',
+ type: UITypes.Number,
+ });
+
+ await dashboard.treeView.openTable({ title: 'Sheet1' });
+ await dashboard.grid.addNewRow({ index: 0, value: '1a' });
+ await dashboard.grid.addNewRow({ index: 1, value: '1b' });
+ await dashboard.grid.addNewRow({ index: 2, value: '1c' });
+
+ await dashboard.treeView.openTable({ title: 'Sheet2' });
+ await dashboard.grid.addNewRow({ index: 0, value: '2a' });
+ await dashboard.grid.addNewRow({ index: 1, value: '2b' });
+ await dashboard.grid.addNewRow({ index: 2, value: '2c' });
+ await dashboard.grid.editRow({ index: 0, columnHeader: 'Sheet1Id', value: '1' });
+ await dashboard.grid.editRow({ index: 1, columnHeader: 'Sheet1Id', value: '1' });
+ await dashboard.grid.editRow({ index: 2, columnHeader: 'Sheet1Id', value: '1' });
+
+ // Create LTAR-HM column
+ await dashboard.grid.column.create({
+ title: 'Link1-2hm',
+ type: UITypes.Links,
+ childTable: 'Sheet2',
+ relationType: 'Has Many',
+ custom: true,
+ refColumn: 'Sheet1Id',
+ });
+
+ // Sheet2 now has all 3 column categories : HM, BT, MM
+
+ // Verify fields and toggle the visibility
+ await dashboard.grid.toolbar.clickFields();
+ for (const title of ['Sheet1', 'Sheet1s']) {
+ await dashboard.grid.toolbar.fields.verify({ title, checked: false });
+ await dashboard.grid.toolbar.fields.click({ title, isLocallySaved: false });
+ }
+ await dashboard.grid.toolbar.clickFields();
+
+ // Expanded form insert
+ await dashboard.grid.footbar.clickAddRecordFromForm();
+ await dashboard.expandedForm.fillField({
+ columnTitle: 'Title',
+ value: '2a',
+ });
+ await dashboard.expandedForm.fillField({
+ columnTitle: 'Sheet1',
+ value: '1a',
+ type: 'belongsTo',
+ });
+ await dashboard.expandedForm.fillField({
+ columnTitle: 'Sheet1s',
+ value: '1a',
+ type: 'manyToMany',
+ });
+ await dashboard.expandedForm.fillField({
+ columnTitle: 'Link2-1hm',
+ value: '1a',
+ type: 'hasMany',
+ });
+ await dashboard.expandedForm.save();
+
+ // In cell insert
+ await dashboard.grid.addNewRow({ index: 1, value: '2b' });
+ await dashboard.grid.cell.inCellAdd({ index: 1, columnHeader: 'Sheet1' });
+ await dashboard.linkRecord.select('1b');
+ await dashboard.grid.cell.inCellAdd({
+ index: 1,
+ columnHeader: 'Sheet1s',
+ });
+ await dashboard.linkRecord.select('1b');
+ await dashboard.grid.cell.inCellAdd({
+ index: 1,
+ columnHeader: 'Link2-1hm',
+ });
+ await dashboard.linkRecord.select('1b');
+
+ // Expand record insert
+ await dashboard.grid.addNewRow({ index: 2, value: '2c-temp' });
+ await dashboard.grid.openExpandedRow({ index: 2 });
+
+ await dashboard.expandedForm.fillField({
+ columnTitle: 'Sheet1',
+ value: '1c',
+ type: 'belongsTo',
+ });
+ await dashboard.expandedForm.fillField({
+ columnTitle: 'Sheet1s',
+ value: '1c',
+ type: 'manyToMany',
+ });
+ await dashboard.expandedForm.fillField({
+ columnTitle: 'Link2-1hm',
+ value: '1c',
+ type: 'hasMany',
+ });
+ await dashboard.expandedForm.fillField({
+ columnTitle: 'Title',
+ value: '2c',
+ type: 'text',
+ });
+
+ await dashboard.rootPage.waitForTimeout(1000);
+
+ await dashboard.expandedForm.save();
+
+ const expected = [
+ [['1a'], ['1b'], ['1c']],
+ [['1 Sheet1'], ['1 Sheet1'], ['1 Sheet1']],
+ [['1 Sheet1'], ['1 Sheet1'], ['1 Sheet1']],
+ ];
+ const colHeaders = ['Sheet1', 'Sheet1s', 'Link2-1hm'];
+
+ // verify LTAR cell values
+ for (let i = 0; i < expected.length; i++) {
+ for (let j = 0; j < expected[i].length; j++) {
+ await dashboard.grid.cell.verifyVirtualCell({
+ index: j,
+ columnHeader: colHeaders[i],
+ count: 1,
+ value: expected[i][j],
+ type: i === 0 ? 'bt' : undefined,
+ options: { singular: 'Sheet1', plural: 'Sheet1s' },
+ });
+ }
+ }
+
+ await dashboard.closeTab({ title: 'Sheet2' });
+ await dashboard.treeView.openTable({ title: 'Sheet1' });
+
+ // Verify fields and toggle the visibility
+ await dashboard.grid.toolbar.clickFields();
+ await dashboard.grid.toolbar.fields.verify({ title: 'Sheet2', checked: false });
+ await dashboard.grid.toolbar.fields.click({ title: 'Sheet2', isLocallySaved: false });
+ await dashboard.grid.toolbar.clickFields();
+
+ const expected2 = [
+ [['1 Sheet2'], ['1 Sheet2'], ['1 Sheet2']],
+ [['1 Sheet2'], ['1 Sheet2'], ['1 Sheet2']],
+ [['2a'], ['2b'], ['2c']],
+ ];
+ const colHeaders2 = ['Link1-2hm', 'Link1-2mm', 'Sheet2'];
+
+ // verify LTAR cell values
+ for (let i = 0; i < expected2.length; i++) {
+ for (let j = 0; j < expected2[i].length; j++) {
+ await dashboard.grid.cell.verifyVirtualCell({
+ index: j,
+ columnHeader: colHeaders2[i],
+ count: 1,
+ value: expected2[i][j],
+ type: i === 2 ? 'bt' : undefined,
+ options: { singular: 'Sheet2', plural: 'Sheet2s' },
+ });
+ }
+ }
+
+ // Unlink LTAR cells
+ for (let i = 0; i < expected2.length; i++) {
+ for (let j = 0; j < expected2[i].length; j++) {
+ await dashboard.rootPage.waitForTimeout(500);
+ await dashboard.grid.cell.unlinkVirtualCell({
+ index: j,
+ columnHeader: colHeaders2[i],
+ });
+ }
+ }
+
+ // delete columns
+ await dashboard.grid.column.delete({ title: 'Link1-2hm' });
+ await dashboard.grid.column.delete({ title: 'Link1-2mm' });
+ await dashboard.grid.column.delete({ title: 'Sheet2' });
+
+ // delete table
+ await dashboard.treeView.deleteTable({ title: 'Sheet1' });
+ await dashboard.treeView.deleteTable({ title: 'Sheet2' });
+ });
+});
+
+test.describe('Links after edit record', () => {
+ let dashboard: DashboardPage;
+ let context: any;
+
+ test.beforeEach(async ({ page }) => {
+ context = await setup({ page, isEmptyProject: false });
+ dashboard = new DashboardPage(page, context.base);
+ });
+
+ test.afterEach(async () => {
+ await unsetup(context);
+ });
+
+ async function verifyRow(param: {
+ index: number;
+ value: {
+ Country: string;
+ formula?: string;
+ SLT?: string;
+ Cities: string[];
+ };
+ }) {
+ await dashboard.grid.cell.verify({
+ index: param.index,
+ columnHeader: 'Country',
+ value: param.value.Country,
+ });
+ if (param.value.formula) {
+ await dashboard.grid.cell.verify({
+ index: param.index,
+ columnHeader: 'formula',
+ value: param.value.formula,
+ });
+ }
+ await dashboard.grid.cell.verifyVirtualCell({
+ index: param.index,
+ columnHeader: 'Cities',
+ count: param.value.Cities.length,
+ options: { singular: 'City', plural: 'Cities' },
+ });
+
+ if (param.value.SLT) {
+ await dashboard.grid.cell.verify({
+ index: param.index,
+ columnHeader: 'SLT',
+ value: param.value.SLT,
+ });
+ }
+ }
+
+ /**
+ * Scope:
+ * - Verify LTAR and lookup cell after updating any non-virtual column
+ * - Verify the formula cell in which the updated cell is referring
+ * - Verify other non-virtual cells
+ *
+ * https://github.com/nocodb/nocodb/issues/4220
+ *
+ */
+ test('Existing LTAR table verification', async () => {
+ // open table
+ await dashboard.treeView.openTable({ title: 'Country' });
+ await verifyRow({
+ index: 0,
+ value: {
+ Country: 'Afghanistan',
+ Cities: ['Kabul'],
+ },
+ });
+ await verifyRow({
+ index: 1,
+ value: {
+ Country: 'Algeria',
+ Cities: ['Batna', 'Bchar', 'Skikda'],
+ },
+ });
+
+ // create new columns
+ await dashboard.grid.column.create({
+ title: 'SLT',
+ type: 'SingleLineText',
+ });
+ await dashboard.grid.column.create({
+ title: 'formula',
+ type: 'Formula',
+ formula: "CONCAT({Country}, ' ', {SLT})",
+ });
+
+ // insert new content into a cell
+ await dashboard.grid.editRow({
+ index: 0,
+ columnHeader: 'SLT',
+ value: 'test',
+ });
+
+ await verifyRow({
+ index: 0,
+ value: {
+ Country: 'Afghanistan',
+ Cities: ['Kabul'],
+ SLT: 'test',
+ formula: 'Afghanistan test',
+ },
+ });
+
+ // edit record
+ await dashboard.grid.editRow({
+ index: 0,
+ columnHeader: 'Country',
+ value: 'Afghanistan2',
+ });
+ await verifyRow({
+ index: 0,
+ value: {
+ Country: 'Afghanistan2',
+ Cities: ['Kabul'],
+ SLT: 'test',
+ formula: 'Afghanistan2 test',
+ },
+ });
+
+ // Delete cell contents and verify
+ await dashboard.grid.cell.click({ index: 0, columnHeader: 'SLT' });
+ // trigger delete button key
+ await dashboard.rootPage.keyboard.press('Delete');
+ // Verify other non-virtual cells
+ await verifyRow({
+ index: 0,
+ value: {
+ Country: 'Afghanistan2',
+ Cities: ['Kabul'],
+ SLT: '',
+ formula: 'Afghanistan2',
+ },
+ });
+ });
+});