mirror of https://github.com/nocodb/nocodb
Mert E.
4 weeks ago
committed by
GitHub
4 changed files with 318 additions and 3 deletions
@ -0,0 +1,297 @@ |
|||||||
|
import { RelationTypes, UITypes } from 'nocodb-sdk'; |
||||||
|
import type { NcUpgraderCtx } from '~/version-upgrader/NcUpgrader'; |
||||||
|
import type { MetaService } from '~/meta/meta.service'; |
||||||
|
import { MetaTable } from '~/utils/globals'; |
||||||
|
import { Column } from '~/models'; |
||||||
|
import { isEE } from '~/utils'; |
||||||
|
|
||||||
|
/** |
||||||
|
* This upgrader look for any broken link and try to recover it |
||||||
|
* using the existing data |
||||||
|
*/ |
||||||
|
|
||||||
|
const logger = { |
||||||
|
log: (message: string) => { |
||||||
|
console.log(`[0227002_ncBrokenLinkRecovery ${Date.now()}] ` + message); |
||||||
|
}, |
||||||
|
error: (message: string) => { |
||||||
|
console.error(`[0227002_ncBrokenLinkRecovery ${Date.now()}] ` + message); |
||||||
|
}, |
||||||
|
}; |
||||||
|
|
||||||
|
export default async function ({ ncMeta }: NcUpgraderCtx) { |
||||||
|
// Get all broken link columns which doesn't have colOptions
|
||||||
|
const columns = await ncMeta |
||||||
|
.knex(MetaTable.COLUMNS) |
||||||
|
.select(`${MetaTable.COLUMNS}.*`) |
||||||
|
.leftJoin( |
||||||
|
MetaTable.COL_RELATIONS, |
||||||
|
`${MetaTable.COLUMNS}.id`, |
||||||
|
`${MetaTable.COL_RELATIONS}.fk_column_id`, |
||||||
|
) |
||||||
|
.where(`${MetaTable.COLUMNS}.uidt`, UITypes.LinkToAnotherRecord) |
||||||
|
.whereNull(`${MetaTable.COL_RELATIONS}.id`); |
||||||
|
|
||||||
|
// Recover broken link
|
||||||
|
for (const column of columns) { |
||||||
|
logger.log(`Recovering column '${column.title}' (ID: '${column.id}')`); |
||||||
|
|
||||||
|
let relatedTableId; |
||||||
|
|
||||||
|
// check any lookup or rollup column is using this column
|
||||||
|
const lookupColumns = await ncMeta |
||||||
|
.knex(MetaTable.COL_LOOKUP) |
||||||
|
.select(`${MetaTable.COL_LOOKUP}.*`) |
||||||
|
.where(`${MetaTable.COL_LOOKUP}.fk_relation_column_id`, column.id); |
||||||
|
|
||||||
|
for (const lookupColumn of lookupColumns) { |
||||||
|
const lookupCol = await ncMeta |
||||||
|
.knex(MetaTable.COLUMNS) |
||||||
|
.select(`${MetaTable.COLUMNS}.fk_model_id`) |
||||||
|
.where(`${MetaTable.COLUMNS}.id`, lookupColumn.fk_lookup_column_id) |
||||||
|
.first(); |
||||||
|
if (lookupCol) { |
||||||
|
relatedTableId = lookupCol.fk_model_id; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (!relatedTableId) { |
||||||
|
const rollupColumns = await ncMeta |
||||||
|
.knex(MetaTable.COL_ROLLUP) |
||||||
|
.select(`${MetaTable.COL_ROLLUP}.*`) |
||||||
|
.where(`${MetaTable.COL_ROLLUP}.fk_relation_column_id`, column.id); |
||||||
|
|
||||||
|
for (const rollupColumn of rollupColumns) { |
||||||
|
const rollupCol = await ncMeta |
||||||
|
.knex(MetaTable.COLUMNS) |
||||||
|
.select(`${MetaTable.COLUMNS}.fk_model_id`) |
||||||
|
.where(`${MetaTable.COLUMNS}.id`, rollupColumn.fk_rollup_column_id) |
||||||
|
.first(); |
||||||
|
if (rollupCol) { |
||||||
|
relatedTableId = rollupCol.fk_model_id; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// if related table is not found then iterate over all links which is related to current table
|
||||||
|
const linksQb = ncMeta |
||||||
|
.knex(MetaTable.COL_RELATIONS) |
||||||
|
.select(`${MetaTable.COL_RELATIONS}.*`) |
||||||
|
.select(`${MetaTable.COLUMNS}.fk_model_id`) |
||||||
|
.where( |
||||||
|
`${MetaTable.COL_RELATIONS}.fk_related_model_id`, |
||||||
|
column.fk_model_id, |
||||||
|
) |
||||||
|
.join( |
||||||
|
MetaTable.COLUMNS, |
||||||
|
`${MetaTable.COL_RELATIONS}.fk_column_id`, |
||||||
|
`${MetaTable.COLUMNS}.id`, |
||||||
|
); |
||||||
|
if (relatedTableId) { |
||||||
|
linksQb.where(`${MetaTable.COLUMNS}.fk_model_id`, relatedTableId); |
||||||
|
} |
||||||
|
|
||||||
|
const links = await linksQb; |
||||||
|
let foundAndMapped = false; |
||||||
|
|
||||||
|
// iterate over all links which is related to current table and if found relation which doesn't have link in the related table then use it to populate colOptions
|
||||||
|
for (const link of links) { |
||||||
|
const relatedTableId = link.fk_model_id; |
||||||
|
let columnInCurrTable = null; |
||||||
|
if (link.type === RelationTypes.HAS_MANY) { |
||||||
|
// check for bt column in current table
|
||||||
|
columnInCurrTable = await ncMeta |
||||||
|
.knex(MetaTable.COL_RELATIONS) |
||||||
|
.join( |
||||||
|
MetaTable.COLUMNS, |
||||||
|
`${MetaTable.COL_RELATIONS}.fk_column_id`, |
||||||
|
`${MetaTable.COLUMNS}.id`, |
||||||
|
) |
||||||
|
.where( |
||||||
|
`${MetaTable.COL_RELATIONS}.fk_related_model_id`, |
||||||
|
relatedTableId, |
||||||
|
) |
||||||
|
.where(`${MetaTable.COL_RELATIONS}.type`, RelationTypes.BELONGS_TO) |
||||||
|
.where( |
||||||
|
`${MetaTable.COL_RELATIONS}.fk_child_column_id`, |
||||||
|
link.fk_child_column_id, |
||||||
|
) |
||||||
|
.where( |
||||||
|
`${MetaTable.COL_RELATIONS}.fk_parent_column_id`, |
||||||
|
link.fk_parent_column_id, |
||||||
|
) |
||||||
|
.first(); |
||||||
|
} else if (link.type === RelationTypes.ONE_TO_ONE) { |
||||||
|
// check for one to one column in current table and confirm type in meta
|
||||||
|
columnInCurrTable = await ncMeta |
||||||
|
.knex(MetaTable.COL_RELATIONS) |
||||||
|
.join( |
||||||
|
MetaTable.COLUMNS, |
||||||
|
`${MetaTable.COL_RELATIONS}.fk_column_id`, |
||||||
|
`${MetaTable.COLUMNS}.id`, |
||||||
|
) |
||||||
|
.where( |
||||||
|
`${MetaTable.COL_RELATIONS}.fk_related_model_id`, |
||||||
|
relatedTableId, |
||||||
|
) |
||||||
|
.where(`${MetaTable.COL_RELATIONS}.type`, RelationTypes.ONE_TO_ONE) |
||||||
|
.where( |
||||||
|
`${MetaTable.COL_RELATIONS}.fk_child_column_id`, |
||||||
|
link.fk_child_column_id, |
||||||
|
) |
||||||
|
.where( |
||||||
|
`${MetaTable.COL_RELATIONS}.fk_parent_column_id`, |
||||||
|
link.fk_parent_column_id, |
||||||
|
) |
||||||
|
.first(); |
||||||
|
} else if (link.type === RelationTypes.BELONGS_TO) { |
||||||
|
// check for hm column in current table
|
||||||
|
columnInCurrTable = await ncMeta |
||||||
|
.knex(MetaTable.COL_RELATIONS) |
||||||
|
.join( |
||||||
|
MetaTable.COLUMNS, |
||||||
|
`${MetaTable.COL_RELATIONS}.fk_column_id`, |
||||||
|
`${MetaTable.COLUMNS}.id`, |
||||||
|
) |
||||||
|
.where( |
||||||
|
`${MetaTable.COL_RELATIONS}.fk_related_model_id`, |
||||||
|
relatedTableId, |
||||||
|
) |
||||||
|
.where(`${MetaTable.COL_RELATIONS}.type`, RelationTypes.HAS_MANY) |
||||||
|
.where( |
||||||
|
`${MetaTable.COL_RELATIONS}.fk_child_column_id`, |
||||||
|
link.fk_child_column_id, |
||||||
|
) |
||||||
|
.where( |
||||||
|
`${MetaTable.COL_RELATIONS}.fk_parent_column_id`, |
||||||
|
link.fk_parent_column_id, |
||||||
|
) |
||||||
|
.first(); |
||||||
|
} else if (link.type === RelationTypes.MANY_TO_MANY) { |
||||||
|
// check for mtm column in current table
|
||||||
|
columnInCurrTable = await ncMeta |
||||||
|
.knex(MetaTable.COL_RELATIONS) |
||||||
|
.join( |
||||||
|
MetaTable.COLUMNS, |
||||||
|
`${MetaTable.COL_RELATIONS}.fk_column_id`, |
||||||
|
`${MetaTable.COLUMNS}.id`, |
||||||
|
) |
||||||
|
.where( |
||||||
|
`${MetaTable.COL_RELATIONS}.fk_related_model_id`, |
||||||
|
relatedTableId, |
||||||
|
) |
||||||
|
.where(`${MetaTable.COL_RELATIONS}.type`, RelationTypes.BELONGS_TO) |
||||||
|
.where( |
||||||
|
`${MetaTable.COL_RELATIONS}.fk_child_column_id`, |
||||||
|
link.fk_parent_column_id, |
||||||
|
) |
||||||
|
.where( |
||||||
|
`${MetaTable.COL_RELATIONS}.fk_parent_column_id`, |
||||||
|
link.fk_parent_column_id, |
||||||
|
) |
||||||
|
.where( |
||||||
|
`${MetaTable.COL_RELATIONS}.fk_mm_model_id`, |
||||||
|
link.fk_mm_model_id, |
||||||
|
) |
||||||
|
.where( |
||||||
|
`${MetaTable.COL_RELATIONS}.fk_mm_child_column_id`, |
||||||
|
link.fk_mm_parent_column_id, |
||||||
|
) |
||||||
|
.where( |
||||||
|
`${MetaTable.COL_RELATIONS}.fk_mm_parent_column_id`, |
||||||
|
link.fk_mm_child_column_id, |
||||||
|
) |
||||||
|
.first(); |
||||||
|
} |
||||||
|
|
||||||
|
if (!columnInCurrTable) { |
||||||
|
// generate meta and insert into colOptions
|
||||||
|
|
||||||
|
const commonProps: Record<string, unknown> = { |
||||||
|
id: await (ncMeta as MetaService).genNanoid(MetaTable.COL_RELATIONS), |
||||||
|
fk_column_id: column.id, |
||||||
|
fk_related_model_id: relatedTableId, |
||||||
|
created_at: link.created_at, |
||||||
|
updated_at: link.updated_at, |
||||||
|
virtual: link.virtual, |
||||||
|
base_id: link.base_id, |
||||||
|
}; |
||||||
|
|
||||||
|
if (isEE) { |
||||||
|
commonProps.fk_workspace_id = link.fk_workspace_id; |
||||||
|
} |
||||||
|
|
||||||
|
// based on type insert data into colOptions
|
||||||
|
switch (link.type) { |
||||||
|
case RelationTypes.HAS_MANY: |
||||||
|
// insert data into colOptions
|
||||||
|
await ncMeta.knex(MetaTable.COL_RELATIONS).insert({ |
||||||
|
...commonProps, |
||||||
|
type: RelationTypes.BELONGS_TO, |
||||||
|
fk_child_column_id: link.fk_child_column_id, |
||||||
|
fk_parent_column_id: link.fk_parent_column_id, |
||||||
|
}); |
||||||
|
break; |
||||||
|
case RelationTypes.ONE_TO_ONE: |
||||||
|
{ |
||||||
|
// insert data into colOptions
|
||||||
|
await ncMeta.knex(MetaTable.COL_RELATIONS).insert({ |
||||||
|
...commonProps, |
||||||
|
type: RelationTypes.ONE_TO_ONE, |
||||||
|
fk_child_column_id: link.fk_child_column_id, |
||||||
|
fk_parent_column_id: link.fk_parent_column_id, |
||||||
|
}); |
||||||
|
} |
||||||
|
break; |
||||||
|
case RelationTypes.BELONGS_TO: |
||||||
|
// insert data into colOptions
|
||||||
|
|
||||||
|
await ncMeta.knex(MetaTable.COL_RELATIONS).insert({ |
||||||
|
...commonProps, |
||||||
|
type: RelationTypes.HAS_MANY, |
||||||
|
fk_child_column_id: link.fk_child_column_id, |
||||||
|
fk_parent_column_id: link.fk_parent_column_id, |
||||||
|
}); |
||||||
|
break; |
||||||
|
case RelationTypes.MANY_TO_MANY: |
||||||
|
// insert data into colOptions
|
||||||
|
|
||||||
|
await ncMeta.knex(MetaTable.COL_RELATIONS).insert({ |
||||||
|
...commonProps, |
||||||
|
type: RelationTypes.MANY_TO_MANY, |
||||||
|
fk_child_column_id: link.fk_parent_column_id, |
||||||
|
fk_parent_column_id: link.fk_child_column_id, |
||||||
|
|
||||||
|
fk_mm_model_id: link.fk_mm_model_id, |
||||||
|
fk_mm_child_column_id: link.fk_mm_parent_column_id, |
||||||
|
fk_mm_parent_column_id: link.fk_mm_child_column_id, |
||||||
|
}); |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
foundAndMapped = true; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (!foundAndMapped) { |
||||||
|
logger.error( |
||||||
|
`No related column found for link column '${column.title}' (ID: '${column.id}'). Deleting it.`, |
||||||
|
); |
||||||
|
|
||||||
|
// delete the link column since it's not useful anymore and not recoverable
|
||||||
|
await Column.delete( |
||||||
|
{ |
||||||
|
workspace_id: column.workspace_id, |
||||||
|
base_id: column.base_id, |
||||||
|
}, |
||||||
|
column.id, |
||||||
|
ncMeta, |
||||||
|
); |
||||||
|
} else { |
||||||
|
logger.log(`Recovered column '${column.title}' (ID: '${column.id}')`); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue