Browse Source

refactor: circular reference detection on formula

pull/2090/head
Wing-Kam Wong 3 years ago
parent
commit
b21b0ed7c9
  1. 35
      packages/nc-gui/components/project/spreadsheet/components/editColumn/FormulaOptions.vue

35
packages/nc-gui/components/project/spreadsheet/components/editColumn/FormulaOptions.vue

@ -257,26 +257,28 @@ export default {
} }
// check circular reference // check circular reference
// e.g. formula1 -> formula2 -> formula1 // e.g. formula1 -> formula2 -> formula1 should return circular reference error
const regex = /cl_\w{14}/g const formulaPaths = [
const formulaPaths = this.meta.columns.filter(c => c.uidt === UITypes.Formula).reduce((res, c) => { // all formula fields excluding itself
const neighbours = (c.colOptions.formula.match(regex) || []).filter(colId => (this.meta.columns.filter(col => (col.id === colId && col.uidt === UITypes.Formula)).length)) ...this.meta.columns.filter(c => c.id !== this.column.id && c.uidt === UITypes.Formula),
// include target formula field before saving
...this.meta.columns.filter(c => c.title === pt.name && c.uidt === UITypes.Formula)
].reduce((res, c) => {
// in `formula`, get all the target neighbours
// i.e. all column id (e.g. cl_xxxxxxxxxxxxxx) with formula type
const neighbours = (c.colOptions.formula.match(/cl_\w{14}/g) || []).filter(colId => (this.meta.columns.filter(col => (col.id === colId && col.uidt === UITypes.Formula)).length))
if (neighbours.length > 0) { if (neighbours.length > 0) {
// e.g. formula1 -> [formula2, formula3] // e.g. formula column 1 -> [formula column 2, formula column3]
res.push({ [c.id]: neighbours }) res.push({ [c.id]: neighbours })
} }
return res return res
}, []) }, [])
const targetFormula = this.meta.columns.filter(c => c.title === pt.name && c.uidt === UITypes.Formula)[0]
if (targetFormula) {
formulaPaths.push({
[this.column.id]: [targetFormula.id]
})
}
const vertices = formulaPaths.length const vertices = formulaPaths.length
if (vertices > 0) { if (vertices > 0) {
// perform kahn's algo for cycle detection
const adj = new Map() const adj = new Map()
const inDegrees = new Map() const inDegrees = new Map()
// init adjacency list & indegree
for (const [_, v] of Object.entries(formulaPaths)) { for (const [_, v] of Object.entries(formulaPaths)) {
const src = Object.keys(v)[0] const src = Object.keys(v)[0]
const neighbours = v[src] const neighbours = v[src]
@ -287,25 +289,36 @@ export default {
} }
} }
const queue = [] const queue = []
// put all vertices with in-degree = 0 (i.e. no incoming edges) to queue
inDegrees.forEach((inDegree, col) => { inDegrees.forEach((inDegree, col) => {
if (inDegree === 0) { if (inDegree === 0) {
// in-degree = 0 means we start traversing from this node
queue.push(col) queue.push(col)
} }
}) })
// init count of visited vertices
let visited = 0 let visited = 0
// BFS
while (queue.length !== 0) { while (queue.length !== 0) {
// remove a vertex from the queue
const src = queue.shift() const src = queue.shift()
// if this node has neighbours, increase visited by 1
const neighbours = adj.get(src) || new Set() const neighbours = adj.get(src) || new Set()
if (neighbours.size > 0) { if (neighbours.size > 0) {
visited += 1 visited += 1
} }
// iterate each neighbouring nodes
neighbours.forEach((neighbour) => { neighbours.forEach((neighbour) => {
// decrease in-degree of its neighbours by 1
inDegrees.set(neighbour, inDegrees.get(neighbour) - 1) inDegrees.set(neighbour, inDegrees.get(neighbour) - 1)
// if in-degree becomes 0
if (inDegrees.get(neighbour) === 0) { if (inDegrees.get(neighbour) === 0) {
// then put the neighboring node to the queue
queue.push(neighbour) queue.push(neighbour)
} }
}) })
} }
// vertices not same as visited = cycle found
if (vertices !== visited) { if (vertices !== visited) {
arr.push('Can’t save field because it causes a circular reference') arr.push('Can’t save field because it causes a circular reference')
} }

Loading…
Cancel
Save