dbgate-datalib 6.3.3 → 6.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/ChangeSet.d.ts +1 -0
- package/lib/ChangeSet.js +17 -1
- package/lib/DataReplicator.d.ts +89 -0
- package/lib/DataReplicator.js +430 -0
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/package.json +5 -5
- package/lib/DataDuplicator.d.ts +0 -70
- package/lib/DataDuplicator.js +0 -274
package/lib/ChangeSet.d.ts
CHANGED
|
@@ -52,6 +52,7 @@ export declare function deleteChangeSetRows(changeSet: ChangeSet, definition: Ch
|
|
|
52
52
|
export declare function getChangeSetInsertedRows(changeSet: ChangeSet, name?: NamedObjectInfo): any[];
|
|
53
53
|
export declare function changeSetInsertNewRow(changeSet: ChangeSet, name?: NamedObjectInfo): ChangeSet;
|
|
54
54
|
export declare function changeSetInsertDocuments(changeSet: ChangeSet, documents: any[], name?: NamedObjectInfo, insertIfNotExistsFieldNames?: string[]): ChangeSet;
|
|
55
|
+
export declare function createMergedRowsChangeSet(table: TableInfo, updatedRows: any[], insertedRows: any[], mergeKey: string[]): ChangeSet;
|
|
55
56
|
export declare function changeSetContainsChanges(changeSet: ChangeSet): boolean;
|
|
56
57
|
export declare function changeSetChangedCount(changeSet: ChangeSet): number;
|
|
57
58
|
export declare function removeSchemaFromChangeSet(changeSet: ChangeSet): {
|
package/lib/ChangeSet.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.removeSchemaFromChangeSet = exports.changeSetChangedCount = exports.changeSetContainsChanges = exports.changeSetInsertDocuments = exports.changeSetInsertNewRow = exports.getChangeSetInsertedRows = exports.deleteChangeSetRows = exports.revertChangeSetRowChanges = exports.changeSetToSql = exports.extractChangeSetCondition = exports.batchUpdateChangeSet = exports.setChangeSetRowData = exports.setChangeSetValue = exports.findExistingChangeSetItem = exports.createChangeSet = void 0;
|
|
6
|
+
exports.removeSchemaFromChangeSet = exports.changeSetChangedCount = exports.changeSetContainsChanges = exports.createMergedRowsChangeSet = exports.changeSetInsertDocuments = exports.changeSetInsertNewRow = exports.getChangeSetInsertedRows = exports.deleteChangeSetRows = exports.revertChangeSetRowChanges = exports.changeSetToSql = exports.extractChangeSetCondition = exports.batchUpdateChangeSet = exports.setChangeSetRowData = exports.setChangeSetValue = exports.findExistingChangeSetItem = exports.createChangeSet = void 0;
|
|
7
7
|
const lodash_1 = __importDefault(require("lodash"));
|
|
8
8
|
function createChangeSet() {
|
|
9
9
|
return {
|
|
@@ -405,6 +405,22 @@ function changeSetInsertDocuments(changeSet, documents, name, insertIfNotExistsF
|
|
|
405
405
|
] });
|
|
406
406
|
}
|
|
407
407
|
exports.changeSetInsertDocuments = changeSetInsertDocuments;
|
|
408
|
+
function createMergedRowsChangeSet(table, updatedRows, insertedRows, mergeKey) {
|
|
409
|
+
const res = createChangeSet();
|
|
410
|
+
res.updates = updatedRows.map(row => ({
|
|
411
|
+
pureName: table.pureName,
|
|
412
|
+
schemaName: table.schemaName,
|
|
413
|
+
fields: lodash_1.default.omit(row, mergeKey),
|
|
414
|
+
condition: lodash_1.default.pick(row, mergeKey),
|
|
415
|
+
}));
|
|
416
|
+
res.inserts = insertedRows.map(row => ({
|
|
417
|
+
pureName: table.pureName,
|
|
418
|
+
schemaName: table.schemaName,
|
|
419
|
+
fields: row,
|
|
420
|
+
}));
|
|
421
|
+
return res;
|
|
422
|
+
}
|
|
423
|
+
exports.createMergedRowsChangeSet = createMergedRowsChangeSet;
|
|
408
424
|
function changeSetContainsChanges(changeSet) {
|
|
409
425
|
var _a;
|
|
410
426
|
if (!changeSet)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/// <reference types="lodash" />
|
|
2
|
+
import { SqlDumper } from 'dbgate-tools';
|
|
3
|
+
import { DatabaseInfo, EngineDriver, ForeignKeyInfo, NamedObjectInfo, QueryResult, TableInfo } from 'dbgate-types';
|
|
4
|
+
export interface DataReplicatorItem {
|
|
5
|
+
openStream: () => Promise<ReadableStream>;
|
|
6
|
+
name: string;
|
|
7
|
+
findExisting: (row: any) => boolean;
|
|
8
|
+
createNew: (row: any) => boolean;
|
|
9
|
+
updateExisting: (row: any) => boolean;
|
|
10
|
+
deleteMissing: boolean;
|
|
11
|
+
deleteRestrictionColumns: string[];
|
|
12
|
+
matchColumns: string[];
|
|
13
|
+
}
|
|
14
|
+
export interface DataReplicatorOptions {
|
|
15
|
+
rollbackAfterFinish?: boolean;
|
|
16
|
+
skipRowsWithUnresolvedRefs?: boolean;
|
|
17
|
+
setNullForUnresolvedNullableRefs?: boolean;
|
|
18
|
+
generateSqlScript?: boolean;
|
|
19
|
+
runid?: string;
|
|
20
|
+
}
|
|
21
|
+
declare class ReplicatorReference {
|
|
22
|
+
base: ReplicatorItemHolder;
|
|
23
|
+
ref: ReplicatorItemHolder;
|
|
24
|
+
isMandatory: boolean;
|
|
25
|
+
foreignKey: ForeignKeyInfo;
|
|
26
|
+
constructor(base: ReplicatorItemHolder, ref: ReplicatorItemHolder, isMandatory: boolean, foreignKey: ForeignKeyInfo);
|
|
27
|
+
get columnName(): string;
|
|
28
|
+
}
|
|
29
|
+
declare class ReplicatorWeakReference {
|
|
30
|
+
base: ReplicatorItemHolder;
|
|
31
|
+
ref: TableInfo;
|
|
32
|
+
foreignKey: ForeignKeyInfo;
|
|
33
|
+
constructor(base: ReplicatorItemHolder, ref: TableInfo, foreignKey: ForeignKeyInfo);
|
|
34
|
+
get columnName(): string;
|
|
35
|
+
}
|
|
36
|
+
declare class ReplicatorItemHolder {
|
|
37
|
+
item: DataReplicatorItem;
|
|
38
|
+
replicator: DataReplicator;
|
|
39
|
+
references: ReplicatorReference[];
|
|
40
|
+
backReferences: ReplicatorReference[];
|
|
41
|
+
weakReferences: ReplicatorWeakReference[];
|
|
42
|
+
table: TableInfo;
|
|
43
|
+
isPlanned: boolean;
|
|
44
|
+
idMap: {};
|
|
45
|
+
autoColumn: string;
|
|
46
|
+
isManualAutoColumn: boolean;
|
|
47
|
+
refByColumn: {
|
|
48
|
+
[columnName: string]: ReplicatorReference;
|
|
49
|
+
};
|
|
50
|
+
isReferenced: boolean;
|
|
51
|
+
get name(): string;
|
|
52
|
+
constructor(item: DataReplicatorItem, replicator: DataReplicator);
|
|
53
|
+
initializeReferences(): void;
|
|
54
|
+
createInsertObject(chunk: any, weakrefcols?: string[]): import("lodash").Omit<Pick<any, string>, string>;
|
|
55
|
+
createUpdateObject(chunk: any): import("lodash").Omit<Pick<any, string>, string>;
|
|
56
|
+
getMissingWeakRefsForRow(row: any): Promise<string[]>;
|
|
57
|
+
runImport(): Promise<{
|
|
58
|
+
inserted: number;
|
|
59
|
+
mapped: number;
|
|
60
|
+
missing: number;
|
|
61
|
+
skipped: number;
|
|
62
|
+
updated: number;
|
|
63
|
+
deleted: number;
|
|
64
|
+
}>;
|
|
65
|
+
}
|
|
66
|
+
export declare class DataReplicator {
|
|
67
|
+
pool: any;
|
|
68
|
+
driver: EngineDriver;
|
|
69
|
+
db: DatabaseInfo;
|
|
70
|
+
items: DataReplicatorItem[];
|
|
71
|
+
stream: any;
|
|
72
|
+
copyStream: (input: any, output: any, options: any) => Promise<void>;
|
|
73
|
+
options: DataReplicatorOptions;
|
|
74
|
+
itemHolders: ReplicatorItemHolder[];
|
|
75
|
+
itemPlan: ReplicatorItemHolder[];
|
|
76
|
+
result: string;
|
|
77
|
+
dumper: SqlDumper;
|
|
78
|
+
identityValues: {
|
|
79
|
+
[fullTableName: string]: number;
|
|
80
|
+
};
|
|
81
|
+
constructor(pool: any, driver: EngineDriver, db: DatabaseInfo, items: DataReplicatorItem[], stream: any, copyStream: (input: any, output: any, options: any) => Promise<void>, options?: DataReplicatorOptions);
|
|
82
|
+
findItemToPlan(): ReplicatorItemHolder;
|
|
83
|
+
createPlan(): void;
|
|
84
|
+
runDumperCommand(cmd: (dmp: SqlDumper) => void | string): Promise<void>;
|
|
85
|
+
runDumperQuery(cmd: (dmp: SqlDumper) => void | string): Promise<QueryResult>;
|
|
86
|
+
generateIdentityValue(column: string, table: NamedObjectInfo): Promise<number>;
|
|
87
|
+
run(): Promise<void>;
|
|
88
|
+
}
|
|
89
|
+
export {};
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.DataReplicator = void 0;
|
|
16
|
+
const dbgate_tools_1 = require("dbgate-tools");
|
|
17
|
+
const pick_1 = __importDefault(require("lodash/pick"));
|
|
18
|
+
const omit_1 = __importDefault(require("lodash/omit"));
|
|
19
|
+
const json_stable_stringify_1 = __importDefault(require("json-stable-stringify"));
|
|
20
|
+
const logger = (0, dbgate_tools_1.getLogger)('dataReplicator');
|
|
21
|
+
class ReplicatorReference {
|
|
22
|
+
constructor(base, ref, isMandatory, foreignKey) {
|
|
23
|
+
this.base = base;
|
|
24
|
+
this.ref = ref;
|
|
25
|
+
this.isMandatory = isMandatory;
|
|
26
|
+
this.foreignKey = foreignKey;
|
|
27
|
+
}
|
|
28
|
+
get columnName() {
|
|
29
|
+
return this.foreignKey.columns[0].columnName;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
class ReplicatorWeakReference {
|
|
33
|
+
constructor(base, ref, foreignKey) {
|
|
34
|
+
this.base = base;
|
|
35
|
+
this.ref = ref;
|
|
36
|
+
this.foreignKey = foreignKey;
|
|
37
|
+
}
|
|
38
|
+
get columnName() {
|
|
39
|
+
return this.foreignKey.columns[0].columnName;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
class ReplicatorItemHolder {
|
|
43
|
+
get name() {
|
|
44
|
+
return this.item.name;
|
|
45
|
+
}
|
|
46
|
+
constructor(item, replicator) {
|
|
47
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
48
|
+
this.item = item;
|
|
49
|
+
this.replicator = replicator;
|
|
50
|
+
this.references = [];
|
|
51
|
+
this.backReferences = [];
|
|
52
|
+
// not mandatory references to entities out of the model
|
|
53
|
+
this.weakReferences = [];
|
|
54
|
+
this.isPlanned = false;
|
|
55
|
+
this.idMap = {};
|
|
56
|
+
this.refByColumn = {};
|
|
57
|
+
this.table = replicator.db.tables.find(x => x.pureName.toUpperCase() == item.name.toUpperCase());
|
|
58
|
+
this.autoColumn = (_a = this.table.columns.find(x => x.autoIncrement)) === null || _a === void 0 ? void 0 : _a.columnName;
|
|
59
|
+
if (((_c = (_b = this.table.primaryKey) === null || _b === void 0 ? void 0 : _b.columns) === null || _c === void 0 ? void 0 : _c.length) != 1 ||
|
|
60
|
+
((_f = (_e = (_d = this.table.primaryKey) === null || _d === void 0 ? void 0 : _d.columns) === null || _e === void 0 ? void 0 : _e[0]) === null || _f === void 0 ? void 0 : _f.columnName) != this.autoColumn) {
|
|
61
|
+
this.autoColumn = null;
|
|
62
|
+
}
|
|
63
|
+
if (!this.autoColumn && ((_h = (_g = this.table.primaryKey) === null || _g === void 0 ? void 0 : _g.columns) === null || _h === void 0 ? void 0 : _h.length) == 1) {
|
|
64
|
+
const name = this.table.primaryKey.columns[0].columnName;
|
|
65
|
+
const column = this.table.columns.find(x => x.columnName == name);
|
|
66
|
+
if ((0, dbgate_tools_1.isTypeNumber)(column === null || column === void 0 ? void 0 : column.dataType)) {
|
|
67
|
+
this.autoColumn = name;
|
|
68
|
+
this.isManualAutoColumn = true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (this.autoColumn && this.replicator.options.generateSqlScript) {
|
|
72
|
+
this.isManualAutoColumn = true;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
initializeReferences() {
|
|
76
|
+
var _a, _b;
|
|
77
|
+
for (const fk of this.table.foreignKeys) {
|
|
78
|
+
if (((_a = fk.columns) === null || _a === void 0 ? void 0 : _a.length) != 1)
|
|
79
|
+
continue;
|
|
80
|
+
const refHolder = this.replicator.itemHolders.find(y => y.name.toUpperCase() == fk.refTableName.toUpperCase());
|
|
81
|
+
const isMandatory = (_b = this.table.columns.find(x => { var _a; return x.columnName == ((_a = fk.columns[0]) === null || _a === void 0 ? void 0 : _a.columnName); })) === null || _b === void 0 ? void 0 : _b.notNull;
|
|
82
|
+
if (refHolder == null) {
|
|
83
|
+
if (!isMandatory) {
|
|
84
|
+
const weakref = new ReplicatorWeakReference(this, this.replicator.db.tables.find(x => x.pureName == fk.refTableName), fk);
|
|
85
|
+
this.weakReferences.push(weakref);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
const newref = new ReplicatorReference(this, refHolder, isMandatory, fk);
|
|
90
|
+
this.references.push(newref);
|
|
91
|
+
this.refByColumn[newref.columnName] = newref;
|
|
92
|
+
refHolder.isReferenced = true;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
createInsertObject(chunk, weakrefcols) {
|
|
97
|
+
const res = (0, omit_1.default)((0, pick_1.default)(chunk, this.table.columns.map(x => x.columnName)), [this.autoColumn, ...this.backReferences.map(x => x.columnName), ...(weakrefcols ? weakrefcols : [])]);
|
|
98
|
+
for (const key in res) {
|
|
99
|
+
const ref = this.refByColumn[key];
|
|
100
|
+
if (ref) {
|
|
101
|
+
// remap id
|
|
102
|
+
const oldId = res[key];
|
|
103
|
+
res[key] = ref.ref.idMap[oldId];
|
|
104
|
+
if (ref.isMandatory && res[key] == null) {
|
|
105
|
+
// mandatory refertence not matched
|
|
106
|
+
if (this.replicator.options.skipRowsWithUnresolvedRefs) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
throw new Error(`Unresolved reference, base=${ref.base.name}, ref=${ref.ref.name}, ${key}=${chunk[key]}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return res;
|
|
114
|
+
}
|
|
115
|
+
createUpdateObject(chunk) {
|
|
116
|
+
const res = (0, omit_1.default)((0, pick_1.default)(chunk, this.table.columns.map(x => x.columnName)), [this.autoColumn, ...this.backReferences.map(x => x.columnName), ...this.references.map(x => x.columnName)]);
|
|
117
|
+
return res;
|
|
118
|
+
}
|
|
119
|
+
// returns list of columns that are weak references and are not resolved
|
|
120
|
+
getMissingWeakRefsForRow(row) {
|
|
121
|
+
var _a;
|
|
122
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
123
|
+
if (!this.replicator.options.setNullForUnresolvedNullableRefs || !((_a = this.weakReferences) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
126
|
+
const qres = yield (0, dbgate_tools_1.runQueryOnDriver)(this.replicator.pool, this.replicator.driver, dmp => {
|
|
127
|
+
dmp.put('^select ');
|
|
128
|
+
dmp.putCollection(',', this.weakReferences, weakref => {
|
|
129
|
+
dmp.put('(^case ^when ^exists (^select * ^from %f where %i = %v) ^then 1 ^else 0 ^end) as %i', weakref.ref, weakref.foreignKey.columns[0].refColumnName, row[weakref.foreignKey.columns[0].columnName], weakref.foreignKey.columns[0].columnName);
|
|
130
|
+
});
|
|
131
|
+
if (this.replicator.driver.dialect.requireFromDual) {
|
|
132
|
+
dmp.put(' ^from ^dual');
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
const qrow = qres.rows[0];
|
|
136
|
+
return this.weakReferences.filter(x => qrow[x.columnName] == 0).map(x => x.columnName);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
runImport() {
|
|
140
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
141
|
+
const readStream = yield this.item.openStream();
|
|
142
|
+
const driver = this.replicator.driver;
|
|
143
|
+
const pool = this.replicator.pool;
|
|
144
|
+
let inserted = 0;
|
|
145
|
+
let mapped = 0;
|
|
146
|
+
let updated = 0;
|
|
147
|
+
let deleted = 0;
|
|
148
|
+
let missing = 0;
|
|
149
|
+
let skipped = 0;
|
|
150
|
+
let lastLogged = new Date();
|
|
151
|
+
const { deleteMissing, deleteRestrictionColumns } = this.item;
|
|
152
|
+
const deleteRestrictions = {};
|
|
153
|
+
const usedKeyRows = {};
|
|
154
|
+
const writeStream = (0, dbgate_tools_1.createAsyncWriteStream)(this.replicator.stream, {
|
|
155
|
+
processItem: (chunk) => __awaiter(this, void 0, void 0, function* () {
|
|
156
|
+
if (chunk.__isStreamHeader) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const doFind = () => __awaiter(this, void 0, void 0, function* () {
|
|
160
|
+
var _a, _b, _c;
|
|
161
|
+
let insertedObj = this.createInsertObject(chunk);
|
|
162
|
+
const res = yield (0, dbgate_tools_1.runQueryOnDriver)(pool, driver, dmp => {
|
|
163
|
+
dmp.put('^select %i ^from %f ^where ', this.autoColumn, this.table);
|
|
164
|
+
dmp.putCollection(' and ', this.item.matchColumns, x => {
|
|
165
|
+
dmp.put('%i = %v', x, insertedObj[x]);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
const resId = (_c = (_b = Object.entries(((_a = res === null || res === void 0 ? void 0 : res.rows) === null || _a === void 0 ? void 0 : _a[0]) || {})) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c[1];
|
|
169
|
+
if (resId != null) {
|
|
170
|
+
mapped += 1;
|
|
171
|
+
this.idMap[chunk[this.autoColumn]] = resId;
|
|
172
|
+
}
|
|
173
|
+
return resId;
|
|
174
|
+
});
|
|
175
|
+
const doUpdate = (recordId) => __awaiter(this, void 0, void 0, function* () {
|
|
176
|
+
const updateObj = this.createUpdateObject(chunk);
|
|
177
|
+
if (Object.keys(updateObj).length == 0) {
|
|
178
|
+
skipped += 1;
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
yield this.replicator.runDumperCommand(dmp => {
|
|
182
|
+
dmp.put('^update %f ^ set ', this.table);
|
|
183
|
+
dmp.putCollection(',', Object.keys(updateObj), x => {
|
|
184
|
+
dmp.put('%i = %v', x, updateObj[x]);
|
|
185
|
+
});
|
|
186
|
+
dmp.put(' ^where %i = %v', this.autoColumn, recordId);
|
|
187
|
+
dmp.endCommand();
|
|
188
|
+
});
|
|
189
|
+
updated += 1;
|
|
190
|
+
});
|
|
191
|
+
const doInsert = () => __awaiter(this, void 0, void 0, function* () {
|
|
192
|
+
var _d, _e, _f;
|
|
193
|
+
// console.log('chunk', this.name, JSON.stringify(chunk));
|
|
194
|
+
const weakrefcols = yield this.getMissingWeakRefsForRow(chunk);
|
|
195
|
+
let insertedObj = this.createInsertObject(chunk, weakrefcols);
|
|
196
|
+
// console.log('insertedObj', this.name, JSON.stringify(insertedObj));
|
|
197
|
+
if (insertedObj == null) {
|
|
198
|
+
skipped += 1;
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (this.isManualAutoColumn) {
|
|
202
|
+
const maxId = yield this.replicator.generateIdentityValue(this.autoColumn, this.table);
|
|
203
|
+
insertedObj = Object.assign(Object.assign({}, insertedObj), { [this.autoColumn]: maxId });
|
|
204
|
+
this.idMap[chunk[this.autoColumn]] = maxId;
|
|
205
|
+
}
|
|
206
|
+
let res = yield this.replicator.runDumperQuery(dmp => {
|
|
207
|
+
dmp.put('^insert ^into %f (%,i) ^values (%,v)', this.table, Object.keys(insertedObj), Object.values(insertedObj));
|
|
208
|
+
dmp.endCommand();
|
|
209
|
+
if (this.autoColumn &&
|
|
210
|
+
this.isReferenced &&
|
|
211
|
+
!this.replicator.driver.dialect.requireStandaloneSelectForScopeIdentity &&
|
|
212
|
+
!this.isManualAutoColumn) {
|
|
213
|
+
dmp.selectScopeIdentity(this.table);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
inserted += 1;
|
|
217
|
+
if (this.autoColumn && this.isReferenced && !this.isManualAutoColumn) {
|
|
218
|
+
if (this.replicator.driver.dialect.requireStandaloneSelectForScopeIdentity) {
|
|
219
|
+
res = yield (0, dbgate_tools_1.runQueryOnDriver)(pool, driver, dmp => dmp.selectScopeIdentity(this.table));
|
|
220
|
+
}
|
|
221
|
+
// console.log('IDRES', JSON.stringify(res));
|
|
222
|
+
// console.log('*********** ENTRIES OF', res?.rows?.[0]);
|
|
223
|
+
const resId = (_f = (_e = Object.entries((_d = res === null || res === void 0 ? void 0 : res.rows) === null || _d === void 0 ? void 0 : _d[0])) === null || _e === void 0 ? void 0 : _e[0]) === null || _f === void 0 ? void 0 : _f[1];
|
|
224
|
+
if (resId != null) {
|
|
225
|
+
this.idMap[chunk[this.autoColumn]] = resId;
|
|
226
|
+
}
|
|
227
|
+
return resId;
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
const doMarkDelete = () => {
|
|
231
|
+
const insertedObj = this.createInsertObject(chunk);
|
|
232
|
+
if ((deleteRestrictionColumns === null || deleteRestrictionColumns === void 0 ? void 0 : deleteRestrictionColumns.length) > 0) {
|
|
233
|
+
const restriction = (0, pick_1.default)(insertedObj, deleteRestrictionColumns);
|
|
234
|
+
const key = (0, json_stable_stringify_1.default)(restriction);
|
|
235
|
+
deleteRestrictions[key] = restriction;
|
|
236
|
+
}
|
|
237
|
+
const usedKey = (0, pick_1.default)(insertedObj, this.item.matchColumns);
|
|
238
|
+
usedKeyRows[(0, json_stable_stringify_1.default)(usedKey)] = usedKey;
|
|
239
|
+
};
|
|
240
|
+
const findExisting = this.item.findExisting(chunk);
|
|
241
|
+
const updateExisting = this.item.updateExisting(chunk);
|
|
242
|
+
const createNew = this.item.createNew(chunk);
|
|
243
|
+
if (deleteMissing) {
|
|
244
|
+
doMarkDelete();
|
|
245
|
+
}
|
|
246
|
+
let recordId = null;
|
|
247
|
+
if (findExisting) {
|
|
248
|
+
recordId = yield doFind();
|
|
249
|
+
}
|
|
250
|
+
if (updateExisting && recordId != null) {
|
|
251
|
+
yield doUpdate(recordId);
|
|
252
|
+
}
|
|
253
|
+
if (createNew && recordId == null) {
|
|
254
|
+
recordId = yield doInsert();
|
|
255
|
+
}
|
|
256
|
+
if (recordId == null && findExisting) {
|
|
257
|
+
missing += 1;
|
|
258
|
+
}
|
|
259
|
+
if (new Date().getTime() - lastLogged.getTime() > 5000) {
|
|
260
|
+
logger.info(`Replicating ${this.item.name} in progress, inserted ${inserted} rows, mapped ${mapped} rows, missing ${missing} rows, skipped ${skipped} rows, updated ${updated} rows`);
|
|
261
|
+
lastLogged = new Date();
|
|
262
|
+
}
|
|
263
|
+
// this.idMap[oldId] = newId;
|
|
264
|
+
}),
|
|
265
|
+
});
|
|
266
|
+
const dumpConditionArray = (dmp, array, positive) => {
|
|
267
|
+
dmp.putCollection(positive ? ' or ' : ' and ', array, x => {
|
|
268
|
+
dmp.put('(');
|
|
269
|
+
dmp.putCollection(positive ? ' and ' : ' or ', Object.keys(x), y => {
|
|
270
|
+
dmp.put(positive ? '%i = %v' : 'not (%i = %v)', y, x[y]);
|
|
271
|
+
});
|
|
272
|
+
dmp.put(')');
|
|
273
|
+
});
|
|
274
|
+
};
|
|
275
|
+
const dumpDeleteCondition = (dmp) => {
|
|
276
|
+
const deleteRestrictionValues = Object.values(deleteRestrictions);
|
|
277
|
+
const usedKeyRowsValues = Object.values(usedKeyRows);
|
|
278
|
+
if (deleteRestrictionValues.length == 0 && usedKeyRowsValues.length == 0) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
dmp.put(' ^where ');
|
|
282
|
+
if ((deleteRestrictionColumns === null || deleteRestrictionColumns === void 0 ? void 0 : deleteRestrictionColumns.length) > 0) {
|
|
283
|
+
dmp.put('(');
|
|
284
|
+
dumpConditionArray(dmp, deleteRestrictionValues, true);
|
|
285
|
+
dmp.put(')');
|
|
286
|
+
if (usedKeyRowsValues.length > 0) {
|
|
287
|
+
dmp.put(' ^and ');
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
dumpConditionArray(dmp, Object.values(usedKeyRows), false);
|
|
291
|
+
};
|
|
292
|
+
const doDelete = () => __awaiter(this, void 0, void 0, function* () {
|
|
293
|
+
const countRes = yield (0, dbgate_tools_1.runQueryOnDriver)(pool, driver, dmp => {
|
|
294
|
+
dmp.put('^select count(*) as ~cnt ^from %f', this.table);
|
|
295
|
+
dumpDeleteCondition(dmp);
|
|
296
|
+
dmp.endCommand();
|
|
297
|
+
});
|
|
298
|
+
const count = parseInt(countRes.rows[0].cnt);
|
|
299
|
+
if (count > 0) {
|
|
300
|
+
yield this.replicator.runDumperCommand(dmp => {
|
|
301
|
+
dmp.put('^delete ^from %f', this.table);
|
|
302
|
+
dumpDeleteCondition(dmp);
|
|
303
|
+
dmp.endCommand();
|
|
304
|
+
});
|
|
305
|
+
deleted += count;
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
yield this.replicator.copyStream(readStream, writeStream, {});
|
|
309
|
+
if (deleteMissing) {
|
|
310
|
+
yield doDelete();
|
|
311
|
+
}
|
|
312
|
+
// await this.replicator.driver.writeQueryStream(this.replicator.pool, {
|
|
313
|
+
// mapResultId: (oldId, newId) => {
|
|
314
|
+
// this.idMap[oldId] = newId;
|
|
315
|
+
// },
|
|
316
|
+
// });
|
|
317
|
+
return { inserted, mapped, missing, skipped, updated, deleted };
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
class DataReplicator {
|
|
322
|
+
constructor(pool, driver, db, items, stream, copyStream, options = {}) {
|
|
323
|
+
this.pool = pool;
|
|
324
|
+
this.driver = driver;
|
|
325
|
+
this.db = db;
|
|
326
|
+
this.items = items;
|
|
327
|
+
this.stream = stream;
|
|
328
|
+
this.copyStream = copyStream;
|
|
329
|
+
this.options = options;
|
|
330
|
+
this.itemPlan = [];
|
|
331
|
+
this.result = '';
|
|
332
|
+
this.identityValues = {};
|
|
333
|
+
this.itemHolders = items.map(x => new ReplicatorItemHolder(x, this));
|
|
334
|
+
this.itemHolders.forEach(x => x.initializeReferences());
|
|
335
|
+
// @ts-ignore
|
|
336
|
+
this.dumper = driver.createDumper();
|
|
337
|
+
}
|
|
338
|
+
findItemToPlan() {
|
|
339
|
+
for (const item of this.itemHolders) {
|
|
340
|
+
if (item.isPlanned)
|
|
341
|
+
continue;
|
|
342
|
+
if (item.references.every(x => x.ref.isPlanned)) {
|
|
343
|
+
return item;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
for (const item of this.itemHolders) {
|
|
347
|
+
if (item.isPlanned)
|
|
348
|
+
continue;
|
|
349
|
+
if (item.references.every(x => x.ref.isPlanned || !x.isMandatory)) {
|
|
350
|
+
const backReferences = item.references.filter(x => !x.ref.isPlanned);
|
|
351
|
+
item.backReferences = backReferences;
|
|
352
|
+
return item;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
throw new Error('Cycle in mandatory references');
|
|
356
|
+
}
|
|
357
|
+
createPlan() {
|
|
358
|
+
while (this.itemPlan.length < this.itemHolders.length) {
|
|
359
|
+
const item = this.findItemToPlan();
|
|
360
|
+
item.isPlanned = true;
|
|
361
|
+
this.itemPlan.push(item);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
runDumperCommand(cmd) {
|
|
365
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
366
|
+
if (this.options.generateSqlScript) {
|
|
367
|
+
cmd(this.dumper);
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
yield (0, dbgate_tools_1.runCommandOnDriver)(this.pool, this.driver, cmd);
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
runDumperQuery(cmd) {
|
|
375
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
376
|
+
if (this.options.generateSqlScript) {
|
|
377
|
+
cmd(this.dumper);
|
|
378
|
+
return {
|
|
379
|
+
rows: [],
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
383
|
+
return yield (0, dbgate_tools_1.runQueryOnDriver)(this.pool, this.driver, cmd);
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
generateIdentityValue(column, table) {
|
|
388
|
+
var _a;
|
|
389
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
390
|
+
const tableKey = `${table.schemaName}.${table.pureName}`;
|
|
391
|
+
if (!(tableKey in this.identityValues)) {
|
|
392
|
+
const max = yield (0, dbgate_tools_1.runQueryOnDriver)(this.pool, this.driver, dmp => {
|
|
393
|
+
dmp.put('^select max(%i) as ~maxid ^from %f', column, table);
|
|
394
|
+
});
|
|
395
|
+
const maxId = Math.max((_a = max.rows[0]['maxid']) !== null && _a !== void 0 ? _a : 0, 0) + 1;
|
|
396
|
+
this.identityValues[tableKey] = maxId;
|
|
397
|
+
return maxId;
|
|
398
|
+
}
|
|
399
|
+
this.identityValues[tableKey] += 1;
|
|
400
|
+
return this.identityValues[tableKey];
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
run() {
|
|
404
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
405
|
+
this.createPlan();
|
|
406
|
+
yield this.runDumperCommand(dmp => dmp.beginTransaction());
|
|
407
|
+
try {
|
|
408
|
+
for (const item of this.itemPlan) {
|
|
409
|
+
const stats = yield item.runImport();
|
|
410
|
+
logger.info(`Replicated ${item.name}, inserted ${stats.inserted} rows, mapped ${stats.mapped} rows, missing ${stats.missing} rows, skipped ${stats.skipped} rows, updated ${stats.updated} rows, deleted ${stats.deleted} rows`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
catch (err) {
|
|
414
|
+
logger.error((0, dbgate_tools_1.extractErrorLogData)(err), `Failed replicator job, rollbacking. ${err.message}`);
|
|
415
|
+
yield this.runDumperCommand(dmp => dmp.rollbackTransaction());
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
if (this.options.rollbackAfterFinish) {
|
|
419
|
+
logger.info('Rollbacking transaction, nothing was changed');
|
|
420
|
+
yield this.runDumperCommand(dmp => dmp.rollbackTransaction());
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
logger.info('Committing replicator transaction');
|
|
424
|
+
yield this.runDumperCommand(dmp => dmp.commitTransaction());
|
|
425
|
+
}
|
|
426
|
+
this.result = this.dumper.s;
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
exports.DataReplicator = DataReplicator;
|
package/lib/index.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export * from './processPerspectiveDefaultColunns';
|
|
|
18
18
|
export * from './PerspectiveDataPattern';
|
|
19
19
|
export * from './PerspectiveDataLoader';
|
|
20
20
|
export * from './perspectiveTools';
|
|
21
|
-
export * from './
|
|
21
|
+
export * from './DataReplicator';
|
|
22
22
|
export * from './FreeTableGridDisplay';
|
|
23
23
|
export * from './FreeTableModel';
|
|
24
24
|
export * from './CustomGridDisplay';
|
package/lib/index.js
CHANGED
|
@@ -34,7 +34,7 @@ __exportStar(require("./processPerspectiveDefaultColunns"), exports);
|
|
|
34
34
|
__exportStar(require("./PerspectiveDataPattern"), exports);
|
|
35
35
|
__exportStar(require("./PerspectiveDataLoader"), exports);
|
|
36
36
|
__exportStar(require("./perspectiveTools"), exports);
|
|
37
|
-
__exportStar(require("./
|
|
37
|
+
__exportStar(require("./DataReplicator"), exports);
|
|
38
38
|
__exportStar(require("./FreeTableGridDisplay"), exports);
|
|
39
39
|
__exportStar(require("./FreeTableModel"), exports);
|
|
40
40
|
__exportStar(require("./CustomGridDisplay"), exports);
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "6.
|
|
2
|
+
"version": "6.4.0",
|
|
3
3
|
"name": "dbgate-datalib",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"typings": "lib/index.d.ts",
|
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
"lib"
|
|
14
14
|
],
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"dbgate-sqltree": "^6.
|
|
17
|
-
"dbgate-tools": "^6.
|
|
18
|
-
"dbgate-filterparser": "^6.
|
|
16
|
+
"dbgate-sqltree": "^6.4.0",
|
|
17
|
+
"dbgate-tools": "^6.4.0",
|
|
18
|
+
"dbgate-filterparser": "^6.4.0",
|
|
19
19
|
"uuid": "^3.4.0"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
|
-
"dbgate-types": "^6.
|
|
22
|
+
"dbgate-types": "^6.4.0",
|
|
23
23
|
"@types/node": "^13.7.0",
|
|
24
24
|
"jest": "^28.1.3",
|
|
25
25
|
"ts-jest": "^28.0.7",
|
package/lib/DataDuplicator.d.ts
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
/// <reference types="lodash" />
|
|
2
|
-
import { DatabaseInfo, EngineDriver, ForeignKeyInfo, TableInfo } from 'dbgate-types';
|
|
3
|
-
export interface DataDuplicatorItem {
|
|
4
|
-
openStream: () => Promise<ReadableStream>;
|
|
5
|
-
name: string;
|
|
6
|
-
operation: 'copy' | 'lookup' | 'insertMissing';
|
|
7
|
-
matchColumns: string[];
|
|
8
|
-
}
|
|
9
|
-
export interface DataDuplicatorOptions {
|
|
10
|
-
rollbackAfterFinish?: boolean;
|
|
11
|
-
skipRowsWithUnresolvedRefs?: boolean;
|
|
12
|
-
setNullForUnresolvedNullableRefs?: boolean;
|
|
13
|
-
}
|
|
14
|
-
declare class DuplicatorReference {
|
|
15
|
-
base: DuplicatorItemHolder;
|
|
16
|
-
ref: DuplicatorItemHolder;
|
|
17
|
-
isMandatory: boolean;
|
|
18
|
-
foreignKey: ForeignKeyInfo;
|
|
19
|
-
constructor(base: DuplicatorItemHolder, ref: DuplicatorItemHolder, isMandatory: boolean, foreignKey: ForeignKeyInfo);
|
|
20
|
-
get columnName(): string;
|
|
21
|
-
}
|
|
22
|
-
declare class DuplicatorWeakReference {
|
|
23
|
-
base: DuplicatorItemHolder;
|
|
24
|
-
ref: TableInfo;
|
|
25
|
-
foreignKey: ForeignKeyInfo;
|
|
26
|
-
constructor(base: DuplicatorItemHolder, ref: TableInfo, foreignKey: ForeignKeyInfo);
|
|
27
|
-
get columnName(): string;
|
|
28
|
-
}
|
|
29
|
-
declare class DuplicatorItemHolder {
|
|
30
|
-
item: DataDuplicatorItem;
|
|
31
|
-
duplicator: DataDuplicator;
|
|
32
|
-
references: DuplicatorReference[];
|
|
33
|
-
backReferences: DuplicatorReference[];
|
|
34
|
-
weakReferences: DuplicatorWeakReference[];
|
|
35
|
-
table: TableInfo;
|
|
36
|
-
isPlanned: boolean;
|
|
37
|
-
idMap: {};
|
|
38
|
-
autoColumn: string;
|
|
39
|
-
refByColumn: {
|
|
40
|
-
[columnName: string]: DuplicatorReference;
|
|
41
|
-
};
|
|
42
|
-
isReferenced: boolean;
|
|
43
|
-
get name(): string;
|
|
44
|
-
constructor(item: DataDuplicatorItem, duplicator: DataDuplicator);
|
|
45
|
-
initializeReferences(): void;
|
|
46
|
-
createInsertObject(chunk: any, weakrefcols: string[]): import("lodash").Omit<Pick<any, string>, string>;
|
|
47
|
-
getMissingWeakRefsForRow(row: any): Promise<string[]>;
|
|
48
|
-
runImport(): Promise<{
|
|
49
|
-
inserted: number;
|
|
50
|
-
mapped: number;
|
|
51
|
-
missing: number;
|
|
52
|
-
skipped: number;
|
|
53
|
-
}>;
|
|
54
|
-
}
|
|
55
|
-
export declare class DataDuplicator {
|
|
56
|
-
pool: any;
|
|
57
|
-
driver: EngineDriver;
|
|
58
|
-
db: DatabaseInfo;
|
|
59
|
-
items: DataDuplicatorItem[];
|
|
60
|
-
stream: any;
|
|
61
|
-
copyStream: (input: any, output: any) => Promise<void>;
|
|
62
|
-
options: DataDuplicatorOptions;
|
|
63
|
-
itemHolders: DuplicatorItemHolder[];
|
|
64
|
-
itemPlan: DuplicatorItemHolder[];
|
|
65
|
-
constructor(pool: any, driver: EngineDriver, db: DatabaseInfo, items: DataDuplicatorItem[], stream: any, copyStream: (input: any, output: any) => Promise<void>, options?: DataDuplicatorOptions);
|
|
66
|
-
findItemToPlan(): DuplicatorItemHolder;
|
|
67
|
-
createPlan(): void;
|
|
68
|
-
run(): Promise<void>;
|
|
69
|
-
}
|
|
70
|
-
export {};
|
package/lib/DataDuplicator.js
DELETED
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
-
};
|
|
14
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.DataDuplicator = void 0;
|
|
16
|
-
const dbgate_tools_1 = require("dbgate-tools");
|
|
17
|
-
const pick_1 = __importDefault(require("lodash/pick"));
|
|
18
|
-
const omit_1 = __importDefault(require("lodash/omit"));
|
|
19
|
-
const logger = (0, dbgate_tools_1.getLogger)('dataDuplicator');
|
|
20
|
-
class DuplicatorReference {
|
|
21
|
-
constructor(base, ref, isMandatory, foreignKey) {
|
|
22
|
-
this.base = base;
|
|
23
|
-
this.ref = ref;
|
|
24
|
-
this.isMandatory = isMandatory;
|
|
25
|
-
this.foreignKey = foreignKey;
|
|
26
|
-
}
|
|
27
|
-
get columnName() {
|
|
28
|
-
return this.foreignKey.columns[0].columnName;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
class DuplicatorWeakReference {
|
|
32
|
-
constructor(base, ref, foreignKey) {
|
|
33
|
-
this.base = base;
|
|
34
|
-
this.ref = ref;
|
|
35
|
-
this.foreignKey = foreignKey;
|
|
36
|
-
}
|
|
37
|
-
get columnName() {
|
|
38
|
-
return this.foreignKey.columns[0].columnName;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
class DuplicatorItemHolder {
|
|
42
|
-
get name() {
|
|
43
|
-
return this.item.name;
|
|
44
|
-
}
|
|
45
|
-
constructor(item, duplicator) {
|
|
46
|
-
var _a, _b, _c, _d, _e, _f;
|
|
47
|
-
this.item = item;
|
|
48
|
-
this.duplicator = duplicator;
|
|
49
|
-
this.references = [];
|
|
50
|
-
this.backReferences = [];
|
|
51
|
-
// not mandatory references to entities out of the model
|
|
52
|
-
this.weakReferences = [];
|
|
53
|
-
this.isPlanned = false;
|
|
54
|
-
this.idMap = {};
|
|
55
|
-
this.refByColumn = {};
|
|
56
|
-
this.table = duplicator.db.tables.find(x => x.pureName.toUpperCase() == item.name.toUpperCase());
|
|
57
|
-
this.autoColumn = (_a = this.table.columns.find(x => x.autoIncrement)) === null || _a === void 0 ? void 0 : _a.columnName;
|
|
58
|
-
if (((_c = (_b = this.table.primaryKey) === null || _b === void 0 ? void 0 : _b.columns) === null || _c === void 0 ? void 0 : _c.length) != 1 ||
|
|
59
|
-
((_f = (_e = (_d = this.table.primaryKey) === null || _d === void 0 ? void 0 : _d.columns) === null || _e === void 0 ? void 0 : _e[0]) === null || _f === void 0 ? void 0 : _f.columnName) != this.autoColumn) {
|
|
60
|
-
this.autoColumn = null;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
initializeReferences() {
|
|
64
|
-
var _a, _b;
|
|
65
|
-
for (const fk of this.table.foreignKeys) {
|
|
66
|
-
if (((_a = fk.columns) === null || _a === void 0 ? void 0 : _a.length) != 1)
|
|
67
|
-
continue;
|
|
68
|
-
const refHolder = this.duplicator.itemHolders.find(y => y.name.toUpperCase() == fk.refTableName.toUpperCase());
|
|
69
|
-
const isMandatory = (_b = this.table.columns.find(x => { var _a; return x.columnName == ((_a = fk.columns[0]) === null || _a === void 0 ? void 0 : _a.columnName); })) === null || _b === void 0 ? void 0 : _b.notNull;
|
|
70
|
-
if (refHolder == null) {
|
|
71
|
-
if (!isMandatory) {
|
|
72
|
-
const weakref = new DuplicatorWeakReference(this, this.duplicator.db.tables.find(x => x.pureName == fk.refTableName), fk);
|
|
73
|
-
this.weakReferences.push(weakref);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
77
|
-
const newref = new DuplicatorReference(this, refHolder, isMandatory, fk);
|
|
78
|
-
this.references.push(newref);
|
|
79
|
-
this.refByColumn[newref.columnName] = newref;
|
|
80
|
-
refHolder.isReferenced = true;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
createInsertObject(chunk, weakrefcols) {
|
|
85
|
-
const res = (0, omit_1.default)((0, pick_1.default)(chunk, this.table.columns.map(x => x.columnName)), [this.autoColumn, ...this.backReferences.map(x => x.columnName), ...weakrefcols]);
|
|
86
|
-
for (const key in res) {
|
|
87
|
-
const ref = this.refByColumn[key];
|
|
88
|
-
if (ref) {
|
|
89
|
-
// remap id
|
|
90
|
-
res[key] = ref.ref.idMap[res[key]];
|
|
91
|
-
if (ref.isMandatory && res[key] == null) {
|
|
92
|
-
// mandatory refertence not matched
|
|
93
|
-
if (this.duplicator.options.skipRowsWithUnresolvedRefs) {
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
96
|
-
throw new Error(`Unresolved reference, base=${ref.base.name}, ref=${ref.ref.name}, ${key}=${chunk[key]}`);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
return res;
|
|
101
|
-
}
|
|
102
|
-
// returns list of columns that are weak references and are not resolved
|
|
103
|
-
getMissingWeakRefsForRow(row) {
|
|
104
|
-
var _a;
|
|
105
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
106
|
-
if (!this.duplicator.options.setNullForUnresolvedNullableRefs || !((_a = this.weakReferences) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
107
|
-
return [];
|
|
108
|
-
}
|
|
109
|
-
const qres = yield (0, dbgate_tools_1.runQueryOnDriver)(this.duplicator.pool, this.duplicator.driver, dmp => {
|
|
110
|
-
dmp.put('^select ');
|
|
111
|
-
dmp.putCollection(',', this.weakReferences, weakref => {
|
|
112
|
-
dmp.put('(^case ^when ^exists (^select * ^from %f where %i = %v) ^then 1 ^else 0 ^end) as %i', weakref.ref, weakref.foreignKey.columns[0].refColumnName, row[weakref.foreignKey.columns[0].columnName], weakref.foreignKey.columns[0].columnName);
|
|
113
|
-
});
|
|
114
|
-
if (this.duplicator.driver.dialect.requireFromDual) {
|
|
115
|
-
dmp.put(' ^from ^dual');
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
const qrow = qres.rows[0];
|
|
119
|
-
return this.weakReferences.filter(x => qrow[x.columnName] == 0).map(x => x.columnName);
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
runImport() {
|
|
123
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
124
|
-
const readStream = yield this.item.openStream();
|
|
125
|
-
const driver = this.duplicator.driver;
|
|
126
|
-
const pool = this.duplicator.pool;
|
|
127
|
-
let inserted = 0;
|
|
128
|
-
let mapped = 0;
|
|
129
|
-
let missing = 0;
|
|
130
|
-
let skipped = 0;
|
|
131
|
-
let lastLogged = new Date();
|
|
132
|
-
const existingWeakRefs = {};
|
|
133
|
-
const writeStream = (0, dbgate_tools_1.createAsyncWriteStream)(this.duplicator.stream, {
|
|
134
|
-
processItem: (chunk) => __awaiter(this, void 0, void 0, function* () {
|
|
135
|
-
var _a, _b, _c;
|
|
136
|
-
if (chunk.__isStreamHeader) {
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
const doCopy = () => __awaiter(this, void 0, void 0, function* () {
|
|
140
|
-
var _d, _e, _f;
|
|
141
|
-
// console.log('chunk', this.name, JSON.stringify(chunk));
|
|
142
|
-
const weakrefcols = yield this.getMissingWeakRefsForRow(chunk);
|
|
143
|
-
const insertedObj = this.createInsertObject(chunk, weakrefcols);
|
|
144
|
-
// console.log('insertedObj', this.name, JSON.stringify(insertedObj));
|
|
145
|
-
if (insertedObj == null) {
|
|
146
|
-
skipped += 1;
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
let res = yield (0, dbgate_tools_1.runQueryOnDriver)(pool, driver, dmp => {
|
|
150
|
-
dmp.put('^insert ^into %f (%,i) ^values (%,v)', this.table, Object.keys(insertedObj), Object.values(insertedObj));
|
|
151
|
-
if (this.autoColumn &&
|
|
152
|
-
this.isReferenced &&
|
|
153
|
-
!this.duplicator.driver.dialect.requireStandaloneSelectForScopeIdentity) {
|
|
154
|
-
dmp.selectScopeIdentity(this.table);
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
inserted += 1;
|
|
158
|
-
if (this.autoColumn && this.isReferenced) {
|
|
159
|
-
if (this.duplicator.driver.dialect.requireStandaloneSelectForScopeIdentity) {
|
|
160
|
-
res = yield (0, dbgate_tools_1.runQueryOnDriver)(pool, driver, dmp => dmp.selectScopeIdentity(this.table));
|
|
161
|
-
}
|
|
162
|
-
// console.log('IDRES', JSON.stringify(res));
|
|
163
|
-
// console.log('*********** ENTRIES OF', res?.rows?.[0]);
|
|
164
|
-
const resId = (_f = (_e = Object.entries((_d = res === null || res === void 0 ? void 0 : res.rows) === null || _d === void 0 ? void 0 : _d[0])) === null || _e === void 0 ? void 0 : _e[0]) === null || _f === void 0 ? void 0 : _f[1];
|
|
165
|
-
if (resId != null) {
|
|
166
|
-
this.idMap[chunk[this.autoColumn]] = resId;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
switch (this.item.operation) {
|
|
171
|
-
case 'copy': {
|
|
172
|
-
yield doCopy();
|
|
173
|
-
break;
|
|
174
|
-
}
|
|
175
|
-
case 'insertMissing':
|
|
176
|
-
case 'lookup': {
|
|
177
|
-
const res = yield (0, dbgate_tools_1.runQueryOnDriver)(pool, driver, dmp => dmp.put('^select %i ^from %f ^where %i = %v', this.autoColumn, this.table, this.item.matchColumns[0], chunk[this.item.matchColumns[0]]));
|
|
178
|
-
const resId = (_c = (_b = Object.entries((_a = res === null || res === void 0 ? void 0 : res.rows) === null || _a === void 0 ? void 0 : _a[0])) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c[1];
|
|
179
|
-
if (resId != null) {
|
|
180
|
-
mapped += 1;
|
|
181
|
-
this.idMap[chunk[this.autoColumn]] = resId;
|
|
182
|
-
}
|
|
183
|
-
else if (this.item.operation == 'insertMissing') {
|
|
184
|
-
yield doCopy();
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
missing += 1;
|
|
188
|
-
}
|
|
189
|
-
break;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
if (new Date().getTime() - lastLogged.getTime() > 5000) {
|
|
193
|
-
logger.info(`Duplicating ${this.item.name} in progress, inserted ${inserted} rows, mapped ${mapped} rows, missing ${missing} rows, skipped ${skipped} rows`);
|
|
194
|
-
lastLogged = new Date();
|
|
195
|
-
}
|
|
196
|
-
// this.idMap[oldId] = newId;
|
|
197
|
-
}),
|
|
198
|
-
});
|
|
199
|
-
yield this.duplicator.copyStream(readStream, writeStream);
|
|
200
|
-
// await this.duplicator.driver.writeQueryStream(this.duplicator.pool, {
|
|
201
|
-
// mapResultId: (oldId, newId) => {
|
|
202
|
-
// this.idMap[oldId] = newId;
|
|
203
|
-
// },
|
|
204
|
-
// });
|
|
205
|
-
return { inserted, mapped, missing, skipped };
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
class DataDuplicator {
|
|
210
|
-
constructor(pool, driver, db, items, stream, copyStream, options = {}) {
|
|
211
|
-
this.pool = pool;
|
|
212
|
-
this.driver = driver;
|
|
213
|
-
this.db = db;
|
|
214
|
-
this.items = items;
|
|
215
|
-
this.stream = stream;
|
|
216
|
-
this.copyStream = copyStream;
|
|
217
|
-
this.options = options;
|
|
218
|
-
this.itemPlan = [];
|
|
219
|
-
this.itemHolders = items.map(x => new DuplicatorItemHolder(x, this));
|
|
220
|
-
this.itemHolders.forEach(x => x.initializeReferences());
|
|
221
|
-
}
|
|
222
|
-
findItemToPlan() {
|
|
223
|
-
for (const item of this.itemHolders) {
|
|
224
|
-
if (item.isPlanned)
|
|
225
|
-
continue;
|
|
226
|
-
if (item.references.every(x => x.ref.isPlanned)) {
|
|
227
|
-
return item;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
for (const item of this.itemHolders) {
|
|
231
|
-
if (item.isPlanned)
|
|
232
|
-
continue;
|
|
233
|
-
if (item.references.every(x => x.ref.isPlanned || !x.isMandatory)) {
|
|
234
|
-
const backReferences = item.references.filter(x => !x.ref.isPlanned);
|
|
235
|
-
item.backReferences = backReferences;
|
|
236
|
-
return item;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
throw new Error('Cycle in mandatory references');
|
|
240
|
-
}
|
|
241
|
-
createPlan() {
|
|
242
|
-
while (this.itemPlan.length < this.itemHolders.length) {
|
|
243
|
-
const item = this.findItemToPlan();
|
|
244
|
-
item.isPlanned = true;
|
|
245
|
-
this.itemPlan.push(item);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
run() {
|
|
249
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
250
|
-
this.createPlan();
|
|
251
|
-
yield (0, dbgate_tools_1.runCommandOnDriver)(this.pool, this.driver, dmp => dmp.beginTransaction());
|
|
252
|
-
try {
|
|
253
|
-
for (const item of this.itemPlan) {
|
|
254
|
-
const stats = yield item.runImport();
|
|
255
|
-
logger.info(`Duplicated ${item.name}, inserted ${stats.inserted} rows, mapped ${stats.mapped} rows, missing ${stats.missing} rows, skipped ${stats.skipped} rows`);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
catch (err) {
|
|
259
|
-
logger.error((0, dbgate_tools_1.extractErrorLogData)(err), `Failed duplicator job, rollbacking. ${err.message}`);
|
|
260
|
-
yield (0, dbgate_tools_1.runCommandOnDriver)(this.pool, this.driver, dmp => dmp.rollbackTransaction());
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
if (this.options.rollbackAfterFinish) {
|
|
264
|
-
logger.info('Rollbacking transaction, nothing was changed');
|
|
265
|
-
yield (0, dbgate_tools_1.runCommandOnDriver)(this.pool, this.driver, dmp => dmp.rollbackTransaction());
|
|
266
|
-
}
|
|
267
|
-
else {
|
|
268
|
-
logger.info('Committing duplicator transaction');
|
|
269
|
-
yield (0, dbgate_tools_1.runCommandOnDriver)(this.pool, this.driver, dmp => dmp.commitTransaction());
|
|
270
|
-
}
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
exports.DataDuplicator = DataDuplicator;
|