|
|
|
@ -2,6 +2,7 @@ import { Readable } from 'stream';
|
|
|
|
|
import { Process, Processor } from '@nestjs/bull'; |
|
|
|
|
import { Job } from 'bull'; |
|
|
|
|
import papaparse from 'papaparse'; |
|
|
|
|
import { UITypes } from 'nocodb-sdk'; |
|
|
|
|
import { Base, Column, Model, Project } from '../../../models'; |
|
|
|
|
import { ProjectsService } from '../../../services/projects.service'; |
|
|
|
|
import { findWithIdentifier } from '../../../helpers/exportImportHelpers'; |
|
|
|
@ -13,6 +14,28 @@ import type { LinkToAnotherRecordColumn } from '../../../models';
|
|
|
|
|
|
|
|
|
|
const DEBUG = false; |
|
|
|
|
|
|
|
|
|
const debugLog = function (...args: any[]) { |
|
|
|
|
if (DEBUG) { |
|
|
|
|
console.log(...args); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const initTime = function () { |
|
|
|
|
return { |
|
|
|
|
hrTime: process.hrtime(), |
|
|
|
|
}; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const elapsedTime = function ( |
|
|
|
|
time: { hrTime: [number, number] }, |
|
|
|
|
label?: string, |
|
|
|
|
) { |
|
|
|
|
const elapsedS = process.hrtime(time.hrTime)[0].toFixed(3); |
|
|
|
|
const elapsedMs = process.hrtime(time.hrTime)[1] / 1000000; |
|
|
|
|
if (label) debugLog(`${label}: ${elapsedS}s ${elapsedMs}ms`); |
|
|
|
|
time.hrTime = process.hrtime(); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
@Processor(JOBS_QUEUE) |
|
|
|
|
export class DuplicateProcessor { |
|
|
|
|
constructor( |
|
|
|
@ -24,6 +47,8 @@ export class DuplicateProcessor {
|
|
|
|
|
|
|
|
|
|
@Process(JobTypes.DuplicateBase) |
|
|
|
|
async duplicateBase(job: Job) { |
|
|
|
|
const hrTime = initTime(); |
|
|
|
|
|
|
|
|
|
const { projectId, baseId, dupProjectId, req } = job.data; |
|
|
|
|
|
|
|
|
|
const project = await Project.get(projectId); |
|
|
|
@ -35,21 +60,6 @@ export class DuplicateProcessor {
|
|
|
|
|
throw new Error(`Project or base not found!`); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
let start = process.hrtime(); |
|
|
|
|
|
|
|
|
|
const debugLog = function (...args: any[]) { |
|
|
|
|
if (DEBUG) { |
|
|
|
|
console.log(...args); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const elapsedTime = function (label?: string) { |
|
|
|
|
const elapsedS = process.hrtime(start)[0].toFixed(3); |
|
|
|
|
const elapsedMs = process.hrtime(start)[1] / 1000000; |
|
|
|
|
if (label) debugLog(`${label}: ${elapsedS}s ${elapsedMs}ms`); |
|
|
|
|
start = process.hrtime(); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const user = (req as any).user; |
|
|
|
|
|
|
|
|
|
const models = (await base.getModels()).filter( |
|
|
|
@ -61,7 +71,7 @@ export class DuplicateProcessor {
|
|
|
|
|
modelIds: models.map((m) => m.id), |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
elapsedTime('serializeModels'); |
|
|
|
|
elapsedTime(hrTime, 'serializeModels'); |
|
|
|
|
|
|
|
|
|
if (!exportedModels) { |
|
|
|
|
throw new Error(`Export failed for base '${base.id}'`); |
|
|
|
@ -69,48 +79,384 @@ export class DuplicateProcessor {
|
|
|
|
|
|
|
|
|
|
await dupProject.getBases(); |
|
|
|
|
|
|
|
|
|
const dupBaseId = dupProject.bases[0].id; |
|
|
|
|
const dupBase = dupProject.bases[0]; |
|
|
|
|
|
|
|
|
|
elapsedTime('projectCreate'); |
|
|
|
|
elapsedTime(hrTime, 'projectCreate'); |
|
|
|
|
|
|
|
|
|
const idMap = await this.importService.importModels({ |
|
|
|
|
user, |
|
|
|
|
projectId: dupProject.id, |
|
|
|
|
baseId: dupBaseId, |
|
|
|
|
baseId: dupBase.id, |
|
|
|
|
data: exportedModels, |
|
|
|
|
req: req, |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
elapsedTime('importModels'); |
|
|
|
|
elapsedTime(hrTime, 'importModels'); |
|
|
|
|
|
|
|
|
|
if (!idMap) { |
|
|
|
|
throw new Error(`Import failed for base '${base.id}'`); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const handledLinks = []; |
|
|
|
|
const lChunks: Record<string, any[]> = {}; // fk_mm_model_id: { rowId, childId }[]
|
|
|
|
|
|
|
|
|
|
const insertChunks = async () => { |
|
|
|
|
for (const [k, v] of Object.entries(lChunks)) { |
|
|
|
|
try { |
|
|
|
|
if (v.length === 0) continue; |
|
|
|
|
await this.bulkDataService.bulkDataInsert({ |
|
|
|
|
projectName: dupProject.id, |
|
|
|
|
tableName: k, |
|
|
|
|
body: v, |
|
|
|
|
cookie: null, |
|
|
|
|
chunkSize: 1000, |
|
|
|
|
foreign_key_checks: false, |
|
|
|
|
raw: true, |
|
|
|
|
}); |
|
|
|
|
lChunks[k] = []; |
|
|
|
|
} catch (e) { |
|
|
|
|
console.log(e); |
|
|
|
|
} |
|
|
|
|
await this.importModelsData({ |
|
|
|
|
idMap, |
|
|
|
|
sourceProject: project, |
|
|
|
|
sourceModels: models, |
|
|
|
|
destProject: dupProject, |
|
|
|
|
destBase: dupBase, |
|
|
|
|
hrTime, |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
await this.projectsService.projectUpdate({ |
|
|
|
|
projectId: dupProject.id, |
|
|
|
|
project: { |
|
|
|
|
status: null, |
|
|
|
|
}, |
|
|
|
|
}); |
|
|
|
|
} catch (e) { |
|
|
|
|
if (dupProject?.id) { |
|
|
|
|
await this.projectsService.projectSoftDelete({ |
|
|
|
|
projectId: dupProject.id, |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
throw e; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Process(JobTypes.DuplicateModel) |
|
|
|
|
async duplicateModel(job: Job) { |
|
|
|
|
const hrTime = initTime(); |
|
|
|
|
|
|
|
|
|
const { projectId, baseId, modelId, title, req } = job.data; |
|
|
|
|
|
|
|
|
|
const project = await Project.get(projectId); |
|
|
|
|
const base = await Base.get(baseId); |
|
|
|
|
|
|
|
|
|
const user = (req as any).user; |
|
|
|
|
|
|
|
|
|
const models = (await base.getModels()).filter( |
|
|
|
|
(m) => !m.mm && m.type === 'table', |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
const sourceModel = models.find((m) => m.id === modelId); |
|
|
|
|
|
|
|
|
|
await sourceModel.getColumns(); |
|
|
|
|
|
|
|
|
|
const relatedModelIds = sourceModel.columns |
|
|
|
|
.filter((col) => col.uidt === UITypes.LinkToAnotherRecord) |
|
|
|
|
.map((col) => col.colOptions.fk_related_model_id) |
|
|
|
|
.filter((id) => id); |
|
|
|
|
|
|
|
|
|
const relatedModels = models.filter((m) => relatedModelIds.includes(m.id)); |
|
|
|
|
|
|
|
|
|
const exportedModel = ( |
|
|
|
|
await this.exportService.serializeModels({ |
|
|
|
|
modelIds: [modelId], |
|
|
|
|
}) |
|
|
|
|
)[0]; |
|
|
|
|
|
|
|
|
|
elapsedTime(hrTime, 'serializeModel'); |
|
|
|
|
|
|
|
|
|
if (!exportedModel) { |
|
|
|
|
throw new Error(`Export failed for base '${base.id}'`); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
exportedModel.model.title = title; |
|
|
|
|
exportedModel.model.table_name = title.toLowerCase().replace(/ /g, '_'); |
|
|
|
|
|
|
|
|
|
const idMap = await this.importService.importModels({ |
|
|
|
|
projectId, |
|
|
|
|
baseId, |
|
|
|
|
data: [exportedModel], |
|
|
|
|
user, |
|
|
|
|
req, |
|
|
|
|
externalModels: relatedModels, |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
elapsedTime(hrTime, 'reimportModelSchema'); |
|
|
|
|
|
|
|
|
|
if (!idMap) { |
|
|
|
|
throw new Error(`Import failed for model '${modelId}'`); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const fields: Record<string, string[]> = {}; |
|
|
|
|
|
|
|
|
|
for (const md of relatedModels) { |
|
|
|
|
const bts = md.columns |
|
|
|
|
.filter( |
|
|
|
|
(c) => |
|
|
|
|
c.uidt === UITypes.LinkToAnotherRecord && |
|
|
|
|
c.colOptions.type === 'bt' && |
|
|
|
|
c.colOptions.fk_related_model_id === modelId, |
|
|
|
|
) |
|
|
|
|
.map((c) => c.id); |
|
|
|
|
|
|
|
|
|
if (bts.length > 0) { |
|
|
|
|
fields[md.id] = [md.primaryKey.id]; |
|
|
|
|
fields[md.id].push(...bts); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
await this.importModelsData({ |
|
|
|
|
idMap, |
|
|
|
|
sourceProject: project, |
|
|
|
|
sourceModels: [sourceModel], |
|
|
|
|
destProject: project, |
|
|
|
|
destBase: base, |
|
|
|
|
hrTime, |
|
|
|
|
modelFieldIds: fields, |
|
|
|
|
externalModels: relatedModels, |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
elapsedTime(hrTime, 'reimportModelData'); |
|
|
|
|
|
|
|
|
|
// console.log('exportedModel', exportedModel);
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async importModelsData(param: { |
|
|
|
|
idMap: Map<string, string>; |
|
|
|
|
sourceProject: Project; |
|
|
|
|
sourceModels: Model[]; |
|
|
|
|
destProject: Project; |
|
|
|
|
destBase: Base; |
|
|
|
|
hrTime: { hrTime: [number, number] }; |
|
|
|
|
modelFieldIds?: Record<string, string[]>; |
|
|
|
|
externalModels?: Model[]; |
|
|
|
|
}) { |
|
|
|
|
const { |
|
|
|
|
idMap, |
|
|
|
|
sourceProject, |
|
|
|
|
sourceModels, |
|
|
|
|
destProject, |
|
|
|
|
destBase, |
|
|
|
|
hrTime, |
|
|
|
|
modelFieldIds, |
|
|
|
|
externalModels, |
|
|
|
|
} = param; |
|
|
|
|
|
|
|
|
|
const handledLinks = []; |
|
|
|
|
const lChunks: Record<string, any[]> = {}; // fk_mm_model_id: { rowId, childId }[]
|
|
|
|
|
|
|
|
|
|
const insertChunks = async () => { |
|
|
|
|
for (const [k, v] of Object.entries(lChunks)) { |
|
|
|
|
try { |
|
|
|
|
if (v.length === 0) continue; |
|
|
|
|
await this.bulkDataService.bulkDataInsert({ |
|
|
|
|
projectName: destProject.id, |
|
|
|
|
tableName: k, |
|
|
|
|
body: v, |
|
|
|
|
cookie: null, |
|
|
|
|
chunkSize: 1000, |
|
|
|
|
foreign_key_checks: false, |
|
|
|
|
raw: true, |
|
|
|
|
}); |
|
|
|
|
lChunks[k] = []; |
|
|
|
|
} catch (e) { |
|
|
|
|
console.log(e); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
for (const sourceModel of sourceModels) { |
|
|
|
|
const dataStream = new Readable({ |
|
|
|
|
read() {}, |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
const linkStream = new Readable({ |
|
|
|
|
read() {}, |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
this.exportService.streamModelData({ |
|
|
|
|
dataStream, |
|
|
|
|
linkStream, |
|
|
|
|
projectId: sourceProject.id, |
|
|
|
|
modelId: sourceModel.id, |
|
|
|
|
handledMmList: handledLinks, |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
const headers: string[] = []; |
|
|
|
|
let chunk = []; |
|
|
|
|
|
|
|
|
|
const model = await Model.get(findWithIdentifier(idMap, sourceModel.id)); |
|
|
|
|
|
|
|
|
|
await new Promise((resolve) => { |
|
|
|
|
papaparse.parse(dataStream, { |
|
|
|
|
newline: '\r\n', |
|
|
|
|
step: async (results, parser) => { |
|
|
|
|
if (!headers.length) { |
|
|
|
|
parser.pause(); |
|
|
|
|
for (const header of results.data) { |
|
|
|
|
const id = idMap.get(header); |
|
|
|
|
if (id) { |
|
|
|
|
const col = await Column.get({ |
|
|
|
|
base_id: destBase.id, |
|
|
|
|
colId: id, |
|
|
|
|
}); |
|
|
|
|
if (col.colOptions?.type === 'bt') { |
|
|
|
|
const childCol = await Column.get({ |
|
|
|
|
base_id: destBase.id, |
|
|
|
|
colId: col.colOptions.fk_child_column_id, |
|
|
|
|
}); |
|
|
|
|
headers.push(childCol.column_name); |
|
|
|
|
} else { |
|
|
|
|
headers.push(col.column_name); |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
debugLog('header not found', header); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
parser.resume(); |
|
|
|
|
} else { |
|
|
|
|
if (results.errors.length === 0) { |
|
|
|
|
const row = {}; |
|
|
|
|
for (let i = 0; i < headers.length; i++) { |
|
|
|
|
if (results.data[i] !== '') { |
|
|
|
|
row[headers[i]] = results.data[i]; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
chunk.push(row); |
|
|
|
|
if (chunk.length > 1000) { |
|
|
|
|
parser.pause(); |
|
|
|
|
try { |
|
|
|
|
await this.bulkDataService.bulkDataInsert({ |
|
|
|
|
projectName: destProject.id, |
|
|
|
|
tableName: model.id, |
|
|
|
|
body: chunk, |
|
|
|
|
cookie: null, |
|
|
|
|
chunkSize: chunk.length + 1, |
|
|
|
|
foreign_key_checks: false, |
|
|
|
|
raw: true, |
|
|
|
|
}); |
|
|
|
|
} catch (e) { |
|
|
|
|
console.log(e); |
|
|
|
|
} |
|
|
|
|
chunk = []; |
|
|
|
|
parser.resume(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
complete: async () => { |
|
|
|
|
if (chunk.length > 0) { |
|
|
|
|
try { |
|
|
|
|
await this.bulkDataService.bulkDataInsert({ |
|
|
|
|
projectName: destProject.id, |
|
|
|
|
tableName: model.id, |
|
|
|
|
body: chunk, |
|
|
|
|
cookie: null, |
|
|
|
|
chunkSize: chunk.length + 1, |
|
|
|
|
foreign_key_checks: false, |
|
|
|
|
raw: true, |
|
|
|
|
}); |
|
|
|
|
} catch (e) { |
|
|
|
|
console.log(e); |
|
|
|
|
} |
|
|
|
|
chunk = []; |
|
|
|
|
} |
|
|
|
|
resolve(null); |
|
|
|
|
}, |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
let headersFound = false; |
|
|
|
|
|
|
|
|
|
let childIndex = -1; |
|
|
|
|
let parentIndex = -1; |
|
|
|
|
let columnIndex = -1; |
|
|
|
|
|
|
|
|
|
const mmColumns: Record<string, Column> = {}; |
|
|
|
|
const mmParentChild: any = {}; |
|
|
|
|
|
|
|
|
|
await new Promise((resolve) => { |
|
|
|
|
papaparse.parse(linkStream, { |
|
|
|
|
newline: '\r\n', |
|
|
|
|
step: async (results, parser) => { |
|
|
|
|
if (!headersFound) { |
|
|
|
|
for (const [i, header] of Object.entries(results.data)) { |
|
|
|
|
if (header === 'child') { |
|
|
|
|
childIndex = parseInt(i); |
|
|
|
|
} else if (header === 'parent') { |
|
|
|
|
parentIndex = parseInt(i); |
|
|
|
|
} else if (header === 'column') { |
|
|
|
|
columnIndex = parseInt(i); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
headersFound = true; |
|
|
|
|
} else { |
|
|
|
|
if (results.errors.length === 0) { |
|
|
|
|
const child = results.data[childIndex]; |
|
|
|
|
const parent = results.data[parentIndex]; |
|
|
|
|
const columnId = results.data[columnIndex]; |
|
|
|
|
if (child && parent && columnId) { |
|
|
|
|
if (mmColumns[columnId]) { |
|
|
|
|
// push to chunk
|
|
|
|
|
const mmModelId = |
|
|
|
|
mmColumns[columnId].colOptions.fk_mm_model_id; |
|
|
|
|
const mm = mmParentChild[mmModelId]; |
|
|
|
|
lChunks[mmModelId].push({ |
|
|
|
|
[mm.parent]: parent, |
|
|
|
|
[mm.child]: child, |
|
|
|
|
}); |
|
|
|
|
} else { |
|
|
|
|
// get column for the first time
|
|
|
|
|
parser.pause(); |
|
|
|
|
|
|
|
|
|
await insertChunks(); |
|
|
|
|
|
|
|
|
|
const col = await Column.get({ |
|
|
|
|
base_id: destBase.id, |
|
|
|
|
colId: findWithIdentifier(idMap, columnId), |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
const colOptions = |
|
|
|
|
await col.getColOptions<LinkToAnotherRecordColumn>(); |
|
|
|
|
|
|
|
|
|
const vChildCol = await colOptions.getMMChildColumn(); |
|
|
|
|
const vParentCol = await colOptions.getMMParentColumn(); |
|
|
|
|
|
|
|
|
|
mmParentChild[col.colOptions.fk_mm_model_id] = { |
|
|
|
|
parent: vParentCol.column_name, |
|
|
|
|
child: vChildCol.column_name, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
mmColumns[columnId] = col; |
|
|
|
|
|
|
|
|
|
handledLinks.push(col.colOptions.fk_mm_model_id); |
|
|
|
|
|
|
|
|
|
const mmModelId = col.colOptions.fk_mm_model_id; |
|
|
|
|
|
|
|
|
|
// create chunk
|
|
|
|
|
lChunks[mmModelId] = []; |
|
|
|
|
|
|
|
|
|
// push to chunk
|
|
|
|
|
const mm = mmParentChild[mmModelId]; |
|
|
|
|
lChunks[mmModelId].push({ |
|
|
|
|
[mm.parent]: parent, |
|
|
|
|
[mm.child]: child, |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
parser.resume(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
complete: async () => { |
|
|
|
|
await insertChunks(); |
|
|
|
|
resolve(null); |
|
|
|
|
}, |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
elapsedTime(hrTime, model.title); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// update external models (has bt to this model)
|
|
|
|
|
if (externalModels) { |
|
|
|
|
for (const sourceModel of externalModels) { |
|
|
|
|
const fields = modelFieldIds?.[sourceModel.id]; |
|
|
|
|
|
|
|
|
|
if (!fields) continue; |
|
|
|
|
|
|
|
|
|
for (const sourceModel of models) { |
|
|
|
|
const dataStream = new Readable({ |
|
|
|
|
read() {}, |
|
|
|
|
}); |
|
|
|
@ -122,17 +468,16 @@ export class DuplicateProcessor {
|
|
|
|
|
this.exportService.streamModelData({ |
|
|
|
|
dataStream, |
|
|
|
|
linkStream, |
|
|
|
|
projectId: project.id, |
|
|
|
|
projectId: sourceProject.id, |
|
|
|
|
modelId: sourceModel.id, |
|
|
|
|
handledMmList: handledLinks, |
|
|
|
|
_fieldIds: fields, |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
const headers: string[] = []; |
|
|
|
|
let chunk = []; |
|
|
|
|
|
|
|
|
|
const model = await Model.get( |
|
|
|
|
findWithIdentifier(idMap, sourceModel.id), |
|
|
|
|
); |
|
|
|
|
const model = await Model.get(sourceModel.id); |
|
|
|
|
|
|
|
|
|
await new Promise((resolve) => { |
|
|
|
|
papaparse.parse(dataStream, { |
|
|
|
@ -144,12 +489,12 @@ export class DuplicateProcessor {
|
|
|
|
|
const id = idMap.get(header); |
|
|
|
|
if (id) { |
|
|
|
|
const col = await Column.get({ |
|
|
|
|
base_id: dupBaseId, |
|
|
|
|
base_id: destBase.id, |
|
|
|
|
colId: id, |
|
|
|
|
}); |
|
|
|
|
if (col.colOptions?.type === 'bt') { |
|
|
|
|
const childCol = await Column.get({ |
|
|
|
|
base_id: dupBaseId, |
|
|
|
|
base_id: destBase.id, |
|
|
|
|
colId: col.colOptions.fk_child_column_id, |
|
|
|
|
}); |
|
|
|
|
headers.push(childCol.column_name); |
|
|
|
@ -173,13 +518,11 @@ export class DuplicateProcessor {
|
|
|
|
|
if (chunk.length > 1000) { |
|
|
|
|
parser.pause(); |
|
|
|
|
try { |
|
|
|
|
await this.bulkDataService.bulkDataInsert({ |
|
|
|
|
projectName: dupProject.id, |
|
|
|
|
await this.bulkDataService.bulkDataUpdate({ |
|
|
|
|
projectName: destProject.id, |
|
|
|
|
tableName: model.id, |
|
|
|
|
body: chunk, |
|
|
|
|
cookie: null, |
|
|
|
|
chunkSize: chunk.length + 1, |
|
|
|
|
foreign_key_checks: false, |
|
|
|
|
raw: true, |
|
|
|
|
}); |
|
|
|
|
} catch (e) { |
|
|
|
@ -193,14 +536,13 @@ export class DuplicateProcessor {
|
|
|
|
|
}, |
|
|
|
|
complete: async () => { |
|
|
|
|
if (chunk.length > 0) { |
|
|
|
|
console.log('chunk', chunk); |
|
|
|
|
try { |
|
|
|
|
await this.bulkDataService.bulkDataInsert({ |
|
|
|
|
projectName: dupProject.id, |
|
|
|
|
await this.bulkDataService.bulkDataUpdate({ |
|
|
|
|
projectName: destProject.id, |
|
|
|
|
tableName: model.id, |
|
|
|
|
body: chunk, |
|
|
|
|
cookie: null, |
|
|
|
|
chunkSize: chunk.length + 1, |
|
|
|
|
foreign_key_checks: false, |
|
|
|
|
raw: true, |
|
|
|
|
}); |
|
|
|
|
} catch (e) { |
|
|
|
@ -213,113 +555,8 @@ export class DuplicateProcessor {
|
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
let headersFound = false; |
|
|
|
|
|
|
|
|
|
let childIndex = -1; |
|
|
|
|
let parentIndex = -1; |
|
|
|
|
let columnIndex = -1; |
|
|
|
|
|
|
|
|
|
const mmColumns: Record<string, Column> = {}; |
|
|
|
|
const mmParentChild: any = {}; |
|
|
|
|
|
|
|
|
|
await new Promise((resolve) => { |
|
|
|
|
papaparse.parse(linkStream, { |
|
|
|
|
newline: '\r\n', |
|
|
|
|
step: async (results, parser) => { |
|
|
|
|
if (!headersFound) { |
|
|
|
|
for (const [i, header] of Object.entries(results.data)) { |
|
|
|
|
if (header === 'child') { |
|
|
|
|
childIndex = parseInt(i); |
|
|
|
|
} else if (header === 'parent') { |
|
|
|
|
parentIndex = parseInt(i); |
|
|
|
|
} else if (header === 'column') { |
|
|
|
|
columnIndex = parseInt(i); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
headersFound = true; |
|
|
|
|
} else { |
|
|
|
|
if (results.errors.length === 0) { |
|
|
|
|
const child = results.data[childIndex]; |
|
|
|
|
const parent = results.data[parentIndex]; |
|
|
|
|
const columnId = results.data[columnIndex]; |
|
|
|
|
if (child && parent && columnId) { |
|
|
|
|
if (mmColumns[columnId]) { |
|
|
|
|
// push to chunk
|
|
|
|
|
const mmModelId = |
|
|
|
|
mmColumns[columnId].colOptions.fk_mm_model_id; |
|
|
|
|
const mm = mmParentChild[mmModelId]; |
|
|
|
|
lChunks[mmModelId].push({ |
|
|
|
|
[mm.parent]: parent, |
|
|
|
|
[mm.child]: child, |
|
|
|
|
}); |
|
|
|
|
} else { |
|
|
|
|
// get column for the first time
|
|
|
|
|
parser.pause(); |
|
|
|
|
|
|
|
|
|
await insertChunks(); |
|
|
|
|
|
|
|
|
|
const col = await Column.get({ |
|
|
|
|
base_id: dupBaseId, |
|
|
|
|
colId: findWithIdentifier(idMap, columnId), |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
const colOptions = |
|
|
|
|
await col.getColOptions<LinkToAnotherRecordColumn>(); |
|
|
|
|
|
|
|
|
|
const vChildCol = await colOptions.getMMChildColumn(); |
|
|
|
|
const vParentCol = await colOptions.getMMParentColumn(); |
|
|
|
|
|
|
|
|
|
mmParentChild[col.colOptions.fk_mm_model_id] = { |
|
|
|
|
parent: vParentCol.column_name, |
|
|
|
|
child: vChildCol.column_name, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
mmColumns[columnId] = col; |
|
|
|
|
|
|
|
|
|
handledLinks.push(col.colOptions.fk_mm_model_id); |
|
|
|
|
|
|
|
|
|
const mmModelId = col.colOptions.fk_mm_model_id; |
|
|
|
|
|
|
|
|
|
// create chunk
|
|
|
|
|
lChunks[mmModelId] = []; |
|
|
|
|
|
|
|
|
|
// push to chunk
|
|
|
|
|
const mm = mmParentChild[mmModelId]; |
|
|
|
|
lChunks[mmModelId].push({ |
|
|
|
|
[mm.parent]: parent, |
|
|
|
|
[mm.child]: child, |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
parser.resume(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
complete: async () => { |
|
|
|
|
await insertChunks(); |
|
|
|
|
resolve(null); |
|
|
|
|
}, |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
elapsedTime(model.title); |
|
|
|
|
elapsedTime(hrTime, `external bt ${model.title}`); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
elapsedTime('links'); |
|
|
|
|
await this.projectsService.projectUpdate({ |
|
|
|
|
projectId: dupProject.id, |
|
|
|
|
project: { |
|
|
|
|
status: null, |
|
|
|
|
}, |
|
|
|
|
}); |
|
|
|
|
} catch (e) { |
|
|
|
|
if (dupProject?.id) { |
|
|
|
|
await this.projectsService.projectSoftDelete({ |
|
|
|
|
projectId: dupProject.id, |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
throw e; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|