Browse Source

feat: table delete with relation - wrap in transaction

Signed-off-by: Pranav C <pranavxc@gmail.com>
test/reset-fail
Pranav C 1 year ago
parent
commit
1d45a501f3
  1. 6
      packages/nc-gui/composables/useTable.ts
  2. 2
      packages/nocodb/src/db/sql-client/lib/sqlite/SqliteClient.ts
  3. 23
      packages/nocodb/src/db/sql-mgr/v2/ProjectMgrv2.ts
  4. 12
      packages/nocodb/src/db/sql-mgr/v2/SqlMgrv2.ts
  5. 1
      packages/nocodb/src/meta/meta.service.ts
  6. 6
      packages/nocodb/src/models/Model.ts
  7. 10
      packages/nocodb/src/models/View.ts
  8. 90
      packages/nocodb/src/services/columns.service.ts
  9. 28
      packages/nocodb/src/services/tables.service.ts

6
packages/nc-gui/composables/useTable.ts

@ -30,7 +30,7 @@ export function useTable(onTableCreate?: (tableMeta: TableType) => void, baseId?
const { getMeta, removeMeta } = useMetas()
const { loadTables } = useProject()
const { loadTables, isXcdbBase } = useProject()
const { closeTab } = useTabs()
const projectStore = useProject()
@ -88,7 +88,9 @@ export function useTable(onTableCreate?: (tableMeta: TableType) => void, baseId?
const meta = (await getMeta(table.id as string, true)) as TableType
const relationColumns = meta?.columns?.filter((c) => c.uidt === UITypes.LinkToAnotherRecord && !isSystemColumn(c))
if (relationColumns?.length) {
// Check if table has any relation columns and show notification
// skip for xcdb base
if (relationColumns?.length && !isXcdbBase(table.base_id)) {
const refColMsgs = await Promise.all(
relationColumns.map(async (c, i) => {
const refMeta = (await getMeta(

2
packages/nocodb/src/db/sql-client/lib/sqlite/SqliteClient.ts

@ -22,7 +22,7 @@ class SqliteClient extends KnexClient {
// sqlite does not support inserting default values and knex fires a warning without this flag
connectionConfig.connection.useNullAsDefault = true;
super(connectionConfig);
this.sqlClient = knex(connectionConfig.connection);
this.sqlClient = connectionConfig?.knex || knex(connectionConfig.connection);
this.queries = queries;
this._version = {};
}

23
packages/nocodb/src/db/sql-mgr/v2/ProjectMgrv2.ts

@ -1,18 +1,21 @@
import SqlMgrv2 from './SqlMgrv2';
import SqlMgrv2Trans from './SqlMgrv2Trans';
import { MetaService } from '../../../meta/meta.service'
import SqlMgrv2 from './SqlMgrv2'
import SqlMgrv2Trans from './SqlMgrv2Trans'
// import type NcMetaIO from '../../../meta/NcMetaIO';
import type Base from '../../../models/Base';
import type Base from '../../../models/Base'
export default class ProjectMgrv2 {
private static sqlMgrMap: {
[key: string]: SqlMgrv2;
} = {};
} = {}
public static getSqlMgr(project: { id: string }, ncMeta: MetaService = null): SqlMgrv2 {
if (ncMeta) return new SqlMgrv2(project, ncMeta)
public static getSqlMgr(project: { id: string }): SqlMgrv2 {
if (!this.sqlMgrMap[project.id]) {
this.sqlMgrMap[project.id] = new SqlMgrv2(project);
this.sqlMgrMap[project.id] = new SqlMgrv2(project)
}
return this.sqlMgrMap[project.id];
return this.sqlMgrMap[project.id]
}
public static async getSqlMgrTrans(
@ -21,8 +24,8 @@ export default class ProjectMgrv2 {
ncMeta: any,
base: Base,
): Promise<SqlMgrv2Trans> {
const sqlMgr = new SqlMgrv2Trans(project, ncMeta, base);
await sqlMgr.startTransaction(base);
return sqlMgr;
const sqlMgr = new SqlMgrv2Trans(project, ncMeta, base)
await sqlMgr.startTransaction(base)
return sqlMgr
}
}

12
packages/nocodb/src/db/sql-mgr/v2/SqlMgrv2.ts

@ -5,12 +5,14 @@ import NcConnectionMgrv2 from '../../../utils/common/NcConnectionMgrv2';
import SqlClientFactory from '../../sql-client/lib/SqlClientFactory';
import KnexMigratorv2 from '../../sql-migrator/lib/KnexMigratorv2';
import Debug from '../../util/Debug';
import type { MetaService } from '../../../meta/meta.service';
import type Base from '../../../models/Base';
const log = new Debug('SqlMgr');
export default class SqlMgrv2 {
protected _migrator: KnexMigratorv2;
protected ncMeta?: MetaService;
// @ts-ignore
private currentProjectFolder: any;
@ -20,18 +22,18 @@ export default class SqlMgrv2 {
* @param {String} args.toolDbPath - path to sqlite file that sql mgr will use
* @memberof SqlMgr
*/
constructor(args: { id: string }) {
constructor(args: { id: string }, ncMeta = null) {
const func = 'constructor';
log.api(`${func}:args:`, args);
// this.metaDb = args.metaDb;
this._migrator = new KnexMigratorv2(args);
return this;
this.ncMeta = ncMeta;
}
public async migrator(_base: Base) {
return this._migrator;
}
public static async testConnection(args = {}) {
const client = await SqlClientFactory.create(args);
return client.testConnection();
@ -119,6 +121,10 @@ export default class SqlMgrv2 {
}
protected async getSqlClient(base: Base) {
if (base.is_meta && this.ncMeta) {
return NcConnectionMgrv2.getSqlClient(base, this.ncMeta.knex);
}
return NcConnectionMgrv2.getSqlClient(base);
}
}

1
packages/nocodb/src/meta/meta.service.ts

@ -532,7 +532,6 @@ export class MetaService {
} else {
query.where(idOrCondition);
}
return query.first();
}

6
packages/nocodb/src/models/Model.ts

@ -368,10 +368,10 @@ export default class Model implements TableType {
}
async delete(ncMeta = Noco.ncMeta, force = false): Promise<boolean> {
await Audit.deleteRowComments(this.id);
await Audit.deleteRowComments(this.id, ncMeta);
for (const view of await this.getViews(true)) {
await view.delete();
for (const view of await this.getViews(true, ncMeta)) {
await view.delete(ncMeta);
}
for (const col of await this.getColumns(ncMeta)) {

10
packages/nocodb/src/models/View.ts

@ -986,9 +986,9 @@ export default class View implements ViewType {
// @ts-ignore
static async delete(viewId, ncMeta = Noco.ncMeta) {
const view = await this.get(viewId);
await Sort.deleteAll(viewId);
await Filter.deleteAll(viewId);
const view = await this.get(viewId, ncMeta);
await Sort.deleteAll(viewId, ncMeta);
await Filter.deleteAll(viewId, ncMeta);
const table = this.extractViewTableName(view);
const tableScope = this.extractViewTableNameScope(view);
const columnTable = this.extractViewColumnsTableName(view);
@ -1258,8 +1258,8 @@ export default class View implements ViewType {
);
}
async delete() {
await View.delete(this.id);
async delete(ncMeta = Noco.ncMeta){
await View.delete(this.id, ncMeta);
}
static async shareViewList(tableId, ncMeta = Noco.ncMeta) {

90
packages/nocodb/src/services/columns.service.ts

@ -1157,7 +1157,10 @@ export class ColumnsService {
);
const base = await Base.get(table.base_id, ncMeta);
const sqlMgr = await ProjectMgrv2.getSqlMgr({ id: base.project_id });
const sqlMgr = await ProjectMgrv2.getSqlMgr(
{ id: base.project_id },
ncMeta,
);
switch (column.uidt) {
case UITypes.Lookup:
@ -1271,7 +1274,7 @@ export class ColumnsService {
const colOpt =
await c.getColOptions<LinkToAnotherRecordColumn>(ncMeta);
if (colOpt.fk_related_model_id === mmTable.id) {
await Column.delete(c.id);
await Column.delete(c.id, ncMeta);
}
}
@ -1403,35 +1406,37 @@ export class ColumnsService {
if (!relationColOpt) {
foreignKeyName = (
(
await childTable.getColumns(ncMeta).then((cols) => {
return cols?.find((c) => {
return (
c.uidt === UITypes.LinkToAnotherRecord &&
c.colOptions.fk_related_model_id === parentTable.id &&
(c.colOptions as LinkToAnotherRecordType).fk_child_column_id ===
childColumn.id &&
(c.colOptions as LinkToAnotherRecordType)
.fk_parent_column_id === parentColumn.id
);
});
await childTable.getColumns(ncMeta).then(async (cols) => {
for (const col of cols) {
if (col.uidt === UITypes.LinkToAnotherRecord) {
const colOptions =
await col.getColOptions<LinkToAnotherRecordColumn>(ncMeta);
console.log(colOptions);
if (colOptions.fk_related_model_id === parentTable.id) {
return { colOptions };
}
}
}
})
).colOptions as LinkToAnotherRecordType
)?.colOptions as LinkToAnotherRecordType
).fk_index_name;
} else {
foreignKeyName = relationColOpt.fk_index_name;
}
// todo: handle relation delete exception
try {
await sqlMgr.sqlOpPlus(base, 'relationDelete', {
childColumn: childColumn.column_name,
childTable: childTable.table_name,
parentTable: parentTable.table_name,
parentColumn: parentColumn.column_name,
foreignKeyName,
});
} catch (e) {
console.log(e);
if (!relationColOpt?.virtual) {
// todo: handle relation delete exception
try {
await sqlMgr.sqlOpPlus(base, 'relationDelete', {
childColumn: childColumn.column_name,
childTable: childTable.table_name,
parentTable: parentTable.table_name,
parentColumn: parentColumn.column_name,
foreignKeyName,
});
} catch (e) {
console.log(e);
}
}
if (!relationColOpt) return;
@ -1462,6 +1467,28 @@ export class ColumnsService {
},
ncMeta,
);
// if virtual column delete all index before deleting the column
if (relationColOpt?.virtual) {
const indexes =
(
await sqlMgr.sqlOp(base, 'indexList', {
tn: cTable.table_name,
})
)?.data?.list ?? [];
for (const index of indexes) {
if (index.cn !== childColumn.column_name) continue;
await sqlMgr.sqlOpPlus(base, 'indexDelete', {
...index,
tn: cTable.table_name,
columns: [childColumn.column_name],
indexName: index.index_name,
});
}
}
const tableUpdateBody = {
...cTable,
tn: cTable.table_name,
@ -1511,6 +1538,12 @@ export class ColumnsService {
const sqlMgr = await ProjectMgrv2.getSqlMgr({
id: param.base.project_id,
});
// if xcdb base then treat as virtual relation to avoid creating foreign key
if (param.base.is_meta) {
(param.column as LinkToAnotherColumnReqType).virtual = true;
}
if (
(param.column as LinkToAnotherColumnReqType).type === 'hm' ||
(param.column as LinkToAnotherColumnReqType).type === 'bt'
@ -1565,11 +1598,6 @@ export class ColumnsService {
childColumn = await Column.get({ colId: id });
// if xcdb base then treat as virtual relation to avoid creating foreign key
if (param.base.is_meta) {
(param.column as LinkToAnotherColumnReqType).virtual = true;
}
// ignore relation creation if virtual
if (!(param.column as LinkToAnotherColumnReqType).virtual) {
foreignKeyName = generateFkName(parent, child);
@ -1746,6 +1774,7 @@ export class ColumnsService {
fk_mm_child_column_id: childCol.id,
fk_mm_parent_column_id: parentCol.id,
fk_related_model_id: parent.id,
virtual: (param.column as LinkToAnotherColumnReqType).virtual,
});
await Column.insert({
title: getUniqueColumnAliasName(
@ -1765,6 +1794,7 @@ export class ColumnsService {
fk_mm_child_column_id: parentCol.id,
fk_mm_parent_column_id: childCol.id,
fk_related_model_id: child.id,
virtual: (param.column as LinkToAnotherColumnReqType).virtual,
});
// todo: create index for virtual relations as well

28
packages/nocodb/src/services/tables.service.ts

@ -184,6 +184,11 @@ export class TablesService {
// delete all relations
await Promise.all(
relationColumns.map((c) => {
// skip if column is hasmany relation to mm table
if (c.system) {
return;
}
return this.columnsService.columnDelete(
{
req: param.req,
@ -194,7 +199,7 @@ export class TablesService {
}),
);
const sqlMgr = await ProjectMgrv2.getSqlMgr(project);
const sqlMgr = await ProjectMgrv2.getSqlMgr(project, ncMeta);
(table as any).tn = table.table_name;
table.columns = table.columns.filter((c) => !isVirtualCol(c));
table.columns.forEach((c) => {
@ -210,15 +215,18 @@ export class TablesService {
});
}
await Audit.insert({
project_id: project.id,
base_id: base.id,
op_type: AuditOperationTypes.TABLE,
op_sub_type: AuditOperationSubTypes.DELETE,
user: param.user?.email,
description: `Deleted ${table.type} ${table.table_name} with alias ${table.title} `,
ip: param.req?.clientIp,
}).then(() => {});
await Audit.insert(
{
project_id: project.id,
base_id: base.id,
op_type: AuditOperationTypes.TABLE,
op_sub_type: AuditOperationSubTypes.DELETE,
user: param.user?.email,
description: `Deleted ${table.type} ${table.table_name} with alias ${table.title} `,
ip: param.req?.clientIp,
},
ncMeta,
).then(() => {});
T.emit('evt', { evt_type: 'table:deleted' });

Loading…
Cancel
Save