velocious 1.0.481 → 1.0.483
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/README.md +2 -0
- package/build/cli/commands/db/tenants/drop.js +34 -0
- package/build/cli/tenant-database-command-helper.js +8 -85
- package/build/database/tenants/data-copier.js +269 -0
- package/build/database/tenants/schema-cloner.js +390 -0
- package/build/database/tenants/tenant-table-plan.js +20 -0
- package/build/src/cli/commands/db/tenants/drop.d.ts +13 -0
- package/build/src/cli/commands/db/tenants/drop.d.ts.map +1 -0
- package/build/src/cli/commands/db/tenants/drop.js +31 -0
- package/build/src/cli/tenant-database-command-helper.d.ts +0 -20
- package/build/src/cli/tenant-database-command-helper.d.ts.map +1 -1
- package/build/src/cli/tenant-database-command-helper.js +8 -73
- package/build/src/database/tenants/data-copier.d.ts +125 -0
- package/build/src/database/tenants/data-copier.d.ts.map +1 -0
- package/build/src/database/tenants/data-copier.js +232 -0
- package/build/src/database/tenants/schema-cloner.d.ts +147 -0
- package/build/src/database/tenants/schema-cloner.d.ts.map +1 -0
- package/build/src/database/tenants/schema-cloner.js +326 -0
- package/build/src/database/tenants/tenant-table-plan.d.ts +30 -0
- package/build/src/database/tenants/tenant-table-plan.d.ts.map +1 -0
- package/build/src/database/tenants/tenant-table-plan.js +3 -0
- package/build/src/tenants/tenant-iterator.d.ts +52 -0
- package/build/src/tenants/tenant-iterator.d.ts.map +1 -0
- package/build/src/tenants/tenant-iterator.js +97 -0
- package/build/src/tenants/tenant.d.ts +51 -0
- package/build/src/tenants/tenant.d.ts.map +1 -0
- package/build/src/tenants/tenant.js +76 -0
- package/build/tenants/tenant-iterator.js +111 -0
- package/build/tenants/tenant.js +86 -0
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/cli/commands/db/tenants/drop.js +34 -0
- package/src/cli/tenant-database-command-helper.js +8 -85
- package/src/database/tenants/data-copier.js +269 -0
- package/src/database/tenants/schema-cloner.js +390 -0
- package/src/database/tenants/tenant-table-plan.js +20 -0
- package/src/tenants/tenant-iterator.js +111 -0
- package/src/tenants/tenant.js +86 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copies a single tenant's rows from a source database into a target database, following a
|
|
3
|
+
* table plan that partitions each table either by a direct tenant key column or by
|
|
4
|
+
* parent-id traversal. This is the row-level counterpart to schema cloning: multi-tenant
|
|
5
|
+
* apps that keep each tenant's data in its own database use it to (re)materialise the
|
|
6
|
+
* tenant's rows from a global/template database.
|
|
7
|
+
*
|
|
8
|
+
* The copy is delete-then-reinsert and therefore idempotent, and it mirrors the source
|
|
9
|
+
* snapshot: the rows to delete are the tenant's *current* rows in the target (selected with
|
|
10
|
+
* the same plan traversal run against the target), so a row that was removed from the source
|
|
11
|
+
* since the last copy is dropped from the target too rather than lingering. The deletes run
|
|
12
|
+
* children first and the source inserts parents first, all inside one target transaction with
|
|
13
|
+
* foreign-key enforcement disabled so the ordering never trips a constraint. Reads, deletes
|
|
14
|
+
* and inserts are chunked to bound statement size.
|
|
15
|
+
*
|
|
16
|
+
* The copier is policy-free: the caller supplies the plan, the source/target databases and
|
|
17
|
+
* the tenant key, and {@link DataCopier#copy} returns the loaded rows keyed by table name so
|
|
18
|
+
* the caller can perform any app-specific post-copy work (for example registering record
|
|
19
|
+
* locations) without that policy leaking into the framework.
|
|
20
|
+
*/
|
|
21
|
+
export default class DataCopier {
|
|
22
|
+
/**
|
|
23
|
+
* Creates a copier that moves tenant-owned rows from `sourceDb` into `targetDb`.
|
|
24
|
+
* @param {{
|
|
25
|
+
* sourceDb: import("../drivers/base.js").default,
|
|
26
|
+
* targetDb: import("../drivers/base.js").default,
|
|
27
|
+
* tablePlan: import("./tenant-table-plan.js").TenantTablePlanEntry[],
|
|
28
|
+
* idColumn?: string,
|
|
29
|
+
* insertChunkSize?: number,
|
|
30
|
+
* queryChunkSize?: number,
|
|
31
|
+
* onProgress?: (message: string) => void
|
|
32
|
+
* }} args
|
|
33
|
+
*/
|
|
34
|
+
constructor({ sourceDb, targetDb, tablePlan, idColumn, insertChunkSize, queryChunkSize, onProgress }: {
|
|
35
|
+
sourceDb: import("../drivers/base.js").default;
|
|
36
|
+
targetDb: import("../drivers/base.js").default;
|
|
37
|
+
tablePlan: import("./tenant-table-plan.js").TenantTablePlanEntry[];
|
|
38
|
+
idColumn?: string;
|
|
39
|
+
insertChunkSize?: number;
|
|
40
|
+
queryChunkSize?: number;
|
|
41
|
+
onProgress?: (message: string) => void;
|
|
42
|
+
});
|
|
43
|
+
sourceDb: import("../drivers/base.js").default;
|
|
44
|
+
targetDb: import("../drivers/base.js").default;
|
|
45
|
+
tablePlan: import("./tenant-table-plan.js").TenantTablePlanEntry[];
|
|
46
|
+
idColumn: string;
|
|
47
|
+
insertChunkSize: number;
|
|
48
|
+
queryChunkSize: number;
|
|
49
|
+
onProgress: ((message: string) => void) | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* Copies every plan table's rows for `keyValue` from the source into the target and
|
|
52
|
+
* returns the copied source rows keyed by table name. The target's current tenant rows
|
|
53
|
+
* are deleted (children first) and the source rows inserted (parents first) in a single
|
|
54
|
+
* target transaction with foreign keys disabled.
|
|
55
|
+
* @param {string} keyValue
|
|
56
|
+
* @returns {Promise<Map<string, Record<string, unknown>[]>>}
|
|
57
|
+
*/
|
|
58
|
+
copy(keyValue: string): Promise<Map<string, Record<string, unknown>[]>>;
|
|
59
|
+
/**
|
|
60
|
+
* Loads the rows for `keyValue` for every table in the plan from `db`, resolving
|
|
61
|
+
* parent-scoped tables from the ids already selected for their parent table. Used for
|
|
62
|
+
* both the source rows to copy and the target's current tenant rows to delete.
|
|
63
|
+
* @param {import("../drivers/base.js").default} db
|
|
64
|
+
* @param {string} keyValue
|
|
65
|
+
* @returns {Promise<Map<string, Record<string, unknown>[]>>}
|
|
66
|
+
*/
|
|
67
|
+
loadRows(db: import("../drivers/base.js").default, keyValue: string): Promise<Map<string, Record<string, unknown>[]>>;
|
|
68
|
+
/**
|
|
69
|
+
* Selects all rows of `tableName` in `db` whose `columnName` is in `values`, chunked.
|
|
70
|
+
* @param {{columnName: string, db: import("../drivers/base.js").default, tableName: string, values: string[]}} args
|
|
71
|
+
* @returns {Promise<Record<string, unknown>[]>}
|
|
72
|
+
*/
|
|
73
|
+
queryRowsByColumn({ columnName, db, tableName, values }: {
|
|
74
|
+
columnName: string;
|
|
75
|
+
db: import("../drivers/base.js").default;
|
|
76
|
+
tableName: string;
|
|
77
|
+
values: string[];
|
|
78
|
+
}): Promise<Record<string, unknown>[]>;
|
|
79
|
+
/**
|
|
80
|
+
* Deletes the matching target rows for every plan table, children before parents, so the
|
|
81
|
+
* reinsert that follows starts from a clean slate without violating foreign keys.
|
|
82
|
+
* @param {Map<string, Record<string, unknown>[]>} rowsByTableName
|
|
83
|
+
* @returns {Promise<void>}
|
|
84
|
+
*/
|
|
85
|
+
deleteTargetRows(rowsByTableName: Map<string, Record<string, unknown>[]>): Promise<void>;
|
|
86
|
+
/**
|
|
87
|
+
* Inserts the loaded source rows into the target for every plan table, parents before
|
|
88
|
+
* children, chunked to bound statement size.
|
|
89
|
+
* @param {Map<string, Record<string, unknown>[]>} rowsByTableName
|
|
90
|
+
* @returns {Promise<void>}
|
|
91
|
+
*/
|
|
92
|
+
insertTargetRows(rowsByTableName: Map<string, Record<string, unknown>[]>): Promise<void>;
|
|
93
|
+
/**
|
|
94
|
+
* Quotes and comma-joins values for an SQL `IN (...)` list against the given database.
|
|
95
|
+
* @param {import("../drivers/base.js").default} db
|
|
96
|
+
* @param {string[]} values
|
|
97
|
+
* @returns {string}
|
|
98
|
+
*/
|
|
99
|
+
quotedValuesSql(db: import("../drivers/base.js").default, values: string[]): string;
|
|
100
|
+
/**
|
|
101
|
+
* Runs a query without per-query logging, used for the high-volume copy statements.
|
|
102
|
+
* @param {import("../drivers/base.js").default} db
|
|
103
|
+
* @param {string} sql
|
|
104
|
+
* @returns {Promise<Record<string, unknown>[]>}
|
|
105
|
+
*/
|
|
106
|
+
executeQuietQuery(db: import("../drivers/base.js").default, sql: string): Promise<Record<string, unknown>[]>;
|
|
107
|
+
/**
|
|
108
|
+
* Inserts column-aligned row tuples into a table without per-query logging.
|
|
109
|
+
* @param {{columns: string[], db: import("../drivers/base.js").default, rows: Array<Array<unknown>>, tableName: string}} args
|
|
110
|
+
* @returns {Promise<void>}
|
|
111
|
+
*/
|
|
112
|
+
insertRowsQuietly({ columns, db, rows, tableName }: {
|
|
113
|
+
columns: string[];
|
|
114
|
+
db: import("../drivers/base.js").default;
|
|
115
|
+
rows: Array<Array<unknown>>;
|
|
116
|
+
tableName: string;
|
|
117
|
+
}): Promise<void>;
|
|
118
|
+
/**
|
|
119
|
+
* Forwards a progress message to the optional `onProgress` callback when one was given.
|
|
120
|
+
* @param {string} message
|
|
121
|
+
* @returns {void}
|
|
122
|
+
*/
|
|
123
|
+
reportProgress(message: string): void;
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=data-copier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-copier.d.ts","sourceRoot":"","sources":["../../../../src/database/tenants/data-copier.js"],"names":[],"mappings":"AA+BA;;;;;;;;;;;;;;;;;;;GAmBG;AACH;IACE;;;;;;;;;;;OAWG;IACH,sGAVW;QACN,QAAQ,EAAE,OAAO,oBAAoB,EAAE,OAAO,CAAC;QAC/C,QAAQ,EAAE,OAAO,oBAAoB,EAAE,OAAO,CAAC;QAC/C,SAAS,EAAE,OAAO,wBAAwB,EAAE,oBAAoB,EAAE,CAAC;QACnE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;KACvC,EAUH;IAPC,+CAAwB;IACxB,+CAAwB;IACxB,mEAA0B;IAC1B,iBAAwB;IACxB,wBAAsC;IACtC,uBAAoC;IACpC,uBAV0B,MAAM,KAAK,IAAI,cAUb;IAG9B;;;;;;;OAOG;IACH,eAHW,MAAM,GACJ,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,CAc3D;IAED;;;;;;;OAOG;IACH,aAJW,OAAO,oBAAoB,EAAE,OAAO,YACpC,MAAM,GACJ,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,CA4C3D;IAED;;;;OAIG;IACH,yDAHW;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,OAAO,oBAAoB,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAC,GACjG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAoB9C;IAED;;;;;OAKG;IACH,kCAHW,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,GACpC,OAAO,CAAC,IAAI,CAAC,CAoBzB;IAED;;;;;OAKG;IACH,kCAHW,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,GACpC,OAAO,CAAC,IAAI,CAAC,CAwBzB;IAED;;;;;OAKG;IACH,oBAJW,OAAO,oBAAoB,EAAE,OAAO,UACpC,MAAM,EAAE,GACN,MAAM,CAIlB;IAED;;;;;OAKG;IACH,sBAJW,OAAO,oBAAoB,EAAE,OAAO,OACpC,MAAM,GACJ,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAI9C;IAED;;;;OAIG;IACH,oDAHW;QAAC,OAAO,EAAE,MAAM,EAAE,CAAC;QAAC,EAAE,EAAE,OAAO,oBAAoB,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAC,GAC3G,OAAO,CAAC,IAAI,CAAC,CAIzB;IAED;;;;OAIG;IACH,wBAHW,MAAM,GACJ,IAAI,CAMhB;CACF"}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
const DEFAULT_INSERT_CHUNK_SIZE = 100;
|
|
3
|
+
const DEFAULT_QUERY_CHUNK_SIZE = 500;
|
|
4
|
+
/**
|
|
5
|
+
* Splits an array into chunks of at most `chunkSize` items.
|
|
6
|
+
* @template T
|
|
7
|
+
* @param {T[]} values
|
|
8
|
+
* @param {number} chunkSize
|
|
9
|
+
* @returns {T[][]}
|
|
10
|
+
*/
|
|
11
|
+
function chunks(values, chunkSize) {
|
|
12
|
+
const chunkedValues = [];
|
|
13
|
+
for (let index = 0; index < values.length; index += chunkSize) {
|
|
14
|
+
chunkedValues.push(values.slice(index, index + chunkSize));
|
|
15
|
+
}
|
|
16
|
+
return chunkedValues;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Stringifies values and returns the distinct, non-blank ones, preserving first-seen order.
|
|
20
|
+
* @param {unknown[]} values
|
|
21
|
+
* @returns {string[]}
|
|
22
|
+
*/
|
|
23
|
+
function uniqueStrings(values) {
|
|
24
|
+
return Array.from(new Set(values.map((value) => String(value)).filter((value) => value.trim())));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Copies a single tenant's rows from a source database into a target database, following a
|
|
28
|
+
* table plan that partitions each table either by a direct tenant key column or by
|
|
29
|
+
* parent-id traversal. This is the row-level counterpart to schema cloning: multi-tenant
|
|
30
|
+
* apps that keep each tenant's data in its own database use it to (re)materialise the
|
|
31
|
+
* tenant's rows from a global/template database.
|
|
32
|
+
*
|
|
33
|
+
* The copy is delete-then-reinsert and therefore idempotent, and it mirrors the source
|
|
34
|
+
* snapshot: the rows to delete are the tenant's *current* rows in the target (selected with
|
|
35
|
+
* the same plan traversal run against the target), so a row that was removed from the source
|
|
36
|
+
* since the last copy is dropped from the target too rather than lingering. The deletes run
|
|
37
|
+
* children first and the source inserts parents first, all inside one target transaction with
|
|
38
|
+
* foreign-key enforcement disabled so the ordering never trips a constraint. Reads, deletes
|
|
39
|
+
* and inserts are chunked to bound statement size.
|
|
40
|
+
*
|
|
41
|
+
* The copier is policy-free: the caller supplies the plan, the source/target databases and
|
|
42
|
+
* the tenant key, and {@link DataCopier#copy} returns the loaded rows keyed by table name so
|
|
43
|
+
* the caller can perform any app-specific post-copy work (for example registering record
|
|
44
|
+
* locations) without that policy leaking into the framework.
|
|
45
|
+
*/
|
|
46
|
+
export default class DataCopier {
|
|
47
|
+
/**
|
|
48
|
+
* Creates a copier that moves tenant-owned rows from `sourceDb` into `targetDb`.
|
|
49
|
+
* @param {{
|
|
50
|
+
* sourceDb: import("../drivers/base.js").default,
|
|
51
|
+
* targetDb: import("../drivers/base.js").default,
|
|
52
|
+
* tablePlan: import("./tenant-table-plan.js").TenantTablePlanEntry[],
|
|
53
|
+
* idColumn?: string,
|
|
54
|
+
* insertChunkSize?: number,
|
|
55
|
+
* queryChunkSize?: number,
|
|
56
|
+
* onProgress?: (message: string) => void
|
|
57
|
+
* }} args
|
|
58
|
+
*/
|
|
59
|
+
constructor({ sourceDb, targetDb, tablePlan, idColumn = "id", insertChunkSize = DEFAULT_INSERT_CHUNK_SIZE, queryChunkSize = DEFAULT_QUERY_CHUNK_SIZE, onProgress }) {
|
|
60
|
+
this.sourceDb = sourceDb;
|
|
61
|
+
this.targetDb = targetDb;
|
|
62
|
+
this.tablePlan = tablePlan;
|
|
63
|
+
this.idColumn = idColumn;
|
|
64
|
+
this.insertChunkSize = insertChunkSize;
|
|
65
|
+
this.queryChunkSize = queryChunkSize;
|
|
66
|
+
this.onProgress = onProgress;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Copies every plan table's rows for `keyValue` from the source into the target and
|
|
70
|
+
* returns the copied source rows keyed by table name. The target's current tenant rows
|
|
71
|
+
* are deleted (children first) and the source rows inserted (parents first) in a single
|
|
72
|
+
* target transaction with foreign keys disabled.
|
|
73
|
+
* @param {string} keyValue
|
|
74
|
+
* @returns {Promise<Map<string, Record<string, unknown>[]>>}
|
|
75
|
+
*/
|
|
76
|
+
async copy(keyValue) {
|
|
77
|
+
const sourceRowsByTableName = await this.loadRows(this.sourceDb, keyValue);
|
|
78
|
+
const targetRowsByTableName = await this.loadRows(this.targetDb, keyValue);
|
|
79
|
+
await this.targetDb.withDisabledForeignKeys(async () => {
|
|
80
|
+
await this.targetDb.transaction(async () => {
|
|
81
|
+
await this.deleteTargetRows(targetRowsByTableName);
|
|
82
|
+
await this.insertTargetRows(sourceRowsByTableName);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
return sourceRowsByTableName;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Loads the rows for `keyValue` for every table in the plan from `db`, resolving
|
|
89
|
+
* parent-scoped tables from the ids already selected for their parent table. Used for
|
|
90
|
+
* both the source rows to copy and the target's current tenant rows to delete.
|
|
91
|
+
* @param {import("../drivers/base.js").default} db
|
|
92
|
+
* @param {string} keyValue
|
|
93
|
+
* @returns {Promise<Map<string, Record<string, unknown>[]>>}
|
|
94
|
+
*/
|
|
95
|
+
async loadRows(db, keyValue) {
|
|
96
|
+
/** @type {Map<string, string[]>} */
|
|
97
|
+
const idsByTableName = new Map();
|
|
98
|
+
/** @type {Map<string, Record<string, unknown>[]>} */
|
|
99
|
+
const rowsByTableName = new Map();
|
|
100
|
+
for (const tableConfig of this.tablePlan) {
|
|
101
|
+
let rows;
|
|
102
|
+
if (tableConfig.keyColumn) {
|
|
103
|
+
rows = await this.queryRowsByColumn({
|
|
104
|
+
columnName: tableConfig.keyColumn,
|
|
105
|
+
db,
|
|
106
|
+
tableName: tableConfig.tableName,
|
|
107
|
+
values: [keyValue]
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
if (!tableConfig.parentColumn || !tableConfig.parentTableName) {
|
|
112
|
+
throw new Error(`Expected keyColumn or parentTableName+parentColumn for table ${tableConfig.tableName} in the tenant table plan.`);
|
|
113
|
+
}
|
|
114
|
+
if (!idsByTableName.has(tableConfig.parentTableName)) {
|
|
115
|
+
throw new Error(`Tenant table plan entry ${tableConfig.tableName} references parent table ${tableConfig.parentTableName}, which has not been loaded; parent tables must appear before their children in the plan.`);
|
|
116
|
+
}
|
|
117
|
+
rows = await this.queryRowsByColumn({
|
|
118
|
+
columnName: tableConfig.parentColumn,
|
|
119
|
+
db,
|
|
120
|
+
tableName: tableConfig.tableName,
|
|
121
|
+
values: idsByTableName.get(tableConfig.parentTableName) || []
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
if (rows.length > 0) {
|
|
125
|
+
this.reportProgress(`${tableConfig.tableName}: loaded ${rows.length} row(s)`);
|
|
126
|
+
}
|
|
127
|
+
idsByTableName.set(tableConfig.tableName, uniqueStrings(rows.map((row) => row[this.idColumn])));
|
|
128
|
+
rowsByTableName.set(tableConfig.tableName, rows);
|
|
129
|
+
}
|
|
130
|
+
return rowsByTableName;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Selects all rows of `tableName` in `db` whose `columnName` is in `values`, chunked.
|
|
134
|
+
* @param {{columnName: string, db: import("../drivers/base.js").default, tableName: string, values: string[]}} args
|
|
135
|
+
* @returns {Promise<Record<string, unknown>[]>}
|
|
136
|
+
*/
|
|
137
|
+
async queryRowsByColumn({ columnName, db, tableName, values }) {
|
|
138
|
+
const normalizedValues = uniqueStrings(values);
|
|
139
|
+
if (normalizedValues.length <= 0) {
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
142
|
+
const rows = [];
|
|
143
|
+
const quotedTable = db.quoteTable(tableName);
|
|
144
|
+
const quotedColumn = db.quoteColumn(columnName);
|
|
145
|
+
for (const valuesChunk of chunks(normalizedValues, this.queryChunkSize)) {
|
|
146
|
+
const sql = `SELECT ${quotedTable}.* FROM ${quotedTable} WHERE ${quotedColumn} IN (${this.quotedValuesSql(db, valuesChunk)})`;
|
|
147
|
+
rows.push(...await this.executeQuietQuery(db, sql));
|
|
148
|
+
}
|
|
149
|
+
return rows;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Deletes the matching target rows for every plan table, children before parents, so the
|
|
153
|
+
* reinsert that follows starts from a clean slate without violating foreign keys.
|
|
154
|
+
* @param {Map<string, Record<string, unknown>[]>} rowsByTableName
|
|
155
|
+
* @returns {Promise<void>}
|
|
156
|
+
*/
|
|
157
|
+
async deleteTargetRows(rowsByTableName) {
|
|
158
|
+
for (const tableConfig of [...this.tablePlan].reverse()) {
|
|
159
|
+
const rowIds = uniqueStrings((rowsByTableName.get(tableConfig.tableName) || []).map((row) => row[this.idColumn]));
|
|
160
|
+
if (rowIds.length <= 0) {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
const quotedTable = this.targetDb.quoteTable(tableConfig.tableName);
|
|
164
|
+
const quotedIdColumn = this.targetDb.quoteColumn(this.idColumn);
|
|
165
|
+
for (const rowIdsChunk of chunks(rowIds, this.queryChunkSize)) {
|
|
166
|
+
await this.executeQuietQuery(this.targetDb, `DELETE FROM ${quotedTable} WHERE ${quotedIdColumn} IN (${this.quotedValuesSql(this.targetDb, rowIdsChunk)})`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Inserts the loaded source rows into the target for every plan table, parents before
|
|
172
|
+
* children, chunked to bound statement size.
|
|
173
|
+
* @param {Map<string, Record<string, unknown>[]>} rowsByTableName
|
|
174
|
+
* @returns {Promise<void>}
|
|
175
|
+
*/
|
|
176
|
+
async insertTargetRows(rowsByTableName) {
|
|
177
|
+
for (const tableConfig of this.tablePlan) {
|
|
178
|
+
const rows = rowsByTableName.get(tableConfig.tableName) || [];
|
|
179
|
+
if (rows.length <= 0) {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
const columns = Object.keys(rows[0]);
|
|
183
|
+
const insertChunks = chunks(rows, this.insertChunkSize);
|
|
184
|
+
this.reportProgress(`${tableConfig.tableName}: inserting ${rows.length} row(s) in ${insertChunks.length} chunk(s)`);
|
|
185
|
+
for (const rowsChunk of insertChunks) {
|
|
186
|
+
await this.insertRowsQuietly({
|
|
187
|
+
columns,
|
|
188
|
+
db: this.targetDb,
|
|
189
|
+
rows: rowsChunk.map((row) => columns.map((column) => row[column])),
|
|
190
|
+
tableName: tableConfig.tableName
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Quotes and comma-joins values for an SQL `IN (...)` list against the given database.
|
|
197
|
+
* @param {import("../drivers/base.js").default} db
|
|
198
|
+
* @param {string[]} values
|
|
199
|
+
* @returns {string}
|
|
200
|
+
*/
|
|
201
|
+
quotedValuesSql(db, values) {
|
|
202
|
+
return values.map((value) => db.quote(value)).join(", ");
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Runs a query without per-query logging, used for the high-volume copy statements.
|
|
206
|
+
* @param {import("../drivers/base.js").default} db
|
|
207
|
+
* @param {string} sql
|
|
208
|
+
* @returns {Promise<Record<string, unknown>[]>}
|
|
209
|
+
*/
|
|
210
|
+
async executeQuietQuery(db, sql) {
|
|
211
|
+
return await db.query(sql, { logQuery: false });
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Inserts column-aligned row tuples into a table without per-query logging.
|
|
215
|
+
* @param {{columns: string[], db: import("../drivers/base.js").default, rows: Array<Array<unknown>>, tableName: string}} args
|
|
216
|
+
* @returns {Promise<void>}
|
|
217
|
+
*/
|
|
218
|
+
async insertRowsQuietly({ columns, db, rows, tableName }) {
|
|
219
|
+
await this.executeQuietQuery(db, db.insertSql({ columns, tableName, rows }));
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Forwards a progress message to the optional `onProgress` callback when one was given.
|
|
223
|
+
* @param {string} message
|
|
224
|
+
* @returns {void}
|
|
225
|
+
*/
|
|
226
|
+
reportProgress(message) {
|
|
227
|
+
if (this.onProgress) {
|
|
228
|
+
this.onProgress(message);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGF0YS1jb3BpZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvZGF0YWJhc2UvdGVuYW50cy9kYXRhLWNvcGllci5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxZQUFZO0FBRVosTUFBTSx5QkFBeUIsR0FBRyxHQUFHLENBQUE7QUFDckMsTUFBTSx3QkFBd0IsR0FBRyxHQUFHLENBQUE7QUFFcEM7Ozs7OztHQU1HO0FBQ0gsU0FBUyxNQUFNLENBQUMsTUFBTSxFQUFFLFNBQVM7SUFDL0IsTUFBTSxhQUFhLEdBQUcsRUFBRSxDQUFBO0lBRXhCLEtBQUssSUFBSSxLQUFLLEdBQUcsQ0FBQyxFQUFFLEtBQUssR0FBRyxNQUFNLENBQUMsTUFBTSxFQUFFLEtBQUssSUFBSSxTQUFTLEVBQUUsQ0FBQztRQUM5RCxhQUFhLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsS0FBSyxFQUFFLEtBQUssR0FBRyxTQUFTLENBQUMsQ0FBQyxDQUFBO0lBQzVELENBQUM7SUFFRCxPQUFPLGFBQWEsQ0FBQTtBQUN0QixDQUFDO0FBRUQ7Ozs7R0FJRztBQUNILFNBQVMsYUFBYSxDQUFDLE1BQU07SUFDM0IsT0FBTyxLQUFLLENBQUMsSUFBSSxDQUFDLElBQUksR0FBRyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsS0FBSyxDQUFDLElBQUksRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFBO0FBQ2xHLENBQUM7QUFFRDs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQW1CRztBQUNILE1BQU0sQ0FBQyxPQUFPLE9BQU8sVUFBVTtJQUM3Qjs7Ozs7Ozs7Ozs7T0FXRztJQUNILFlBQVksRUFBQyxRQUFRLEVBQUUsUUFBUSxFQUFFLFNBQVMsRUFBRSxRQUFRLEdBQUcsSUFBSSxF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QU8sTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsRUFBRSxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQTtJQUMxRCxDQUFDO0lBRUQ7Ozs7O09BS0c7SUFDSCxLQUFLLENBQUMsaUJBQWlCLENBQUMsRUFBRSxFQUFFLEdBQUc7UUFDN0IsT0FBTyxNQUFNLEVBQUUsQ0FBQyxLQUFLLENBQUMsR0FBRyxFQUFFLEVBQUMsUUFBUSxFQUFFLEtBQUssRUFBQyxDQUFDLENBQUE7SUFDL0MsQ0FBQztJQUVEOzs7O09BSUc7SUFDSCxLQUFLLENBQUMsaUJBQWlCLENBQUMsRUFBQyxPQUFPLEVBQUUsRUFBRSxFQUFFLElBQUksRUFBRSxTQUFTLEVBQUM7UUFDcEQsTUFBTSxJQUFJLENBQUMsaUJBQWlCLENBQUMsRUFBRSxFQUFFLEVBQUUsQ0FBQyxTQUFTLENBQUMsRUFBQyxPQUFPLEVBQUUsU0FBUyxFQUFFLElBQUksRUFBQyxDQUFDLENBQUMsQ0FBQTtJQUM1RSxDQUFDO0lBRUQ7Ozs7T0FJRztJQUNILGNBQWMsQ0FBQyxPQUFPO1FBQ3BCLElBQUksSUFBSSxDQUFDLFVBQVUsRUFBRSxDQUFDO1lBQ3BCLElBQUksQ0FBQyxVQUFVLENBQUMsT0FBTyxDQUFDLENBQUE7UUFDMUIsQ0FBQztJQUNILENBQUM7Q0FDRiIsInNvdXJjZXNDb250ZW50IjpbIi8vIEB0cy1jaGVja1xuXG5jb25zdCBERUZBVUxUX0lOU0VSVF9DSFVOS19TSVpFID0gMTAwXG5jb25zdCBERUZBVUxUX1FVRVJZX0NIVU5LX1NJWkUgPSA1MDBcblxuLyoqXG4gKiBTcGxpdHMgYW4gYXJyYXkgaW50byBjaHVua3Mgb2YgYXQgbW9zdCBgY2h1bmtTaXplYCBpdGVtcy5cbiAqIEB0ZW1wbGF0ZSBUXG4gKiBAcGFyYW0ge1RbXX0gdmFsdWVzXG4gKiBAcGFyYW0ge251bWJlcn0gY2h1bmtTaXplXG4gKiBAcmV0dXJucyB7VFtdW119XG4gKi9cbmZ1bmN0aW9uIGNodW5rcyh2YWx1ZXMsIGNodW5rU2l6ZSkge1xuICBjb25zdCBjaHVua2VkVmFsdWVzID0gW11cblxuICBmb3IgKGxldCBpbmRleCA9IDA7IGluZGV4IDwgdmFsdWVzLmxlbmd0aDsgaW5kZXggKz0gY2h1bmtTaXplKSB7XG4gICAgY2h1bmtlZFZhbHVlcy5wdXNoKHZhbHVlcy5zbGljZShpbmRleCwgaW5kZXggKyBjaHVua1NpemUpKVxuICB9XG5cbiAgcmV0dXJuIGNodW5rZWRWYWx1ZXNcbn1cblxuLyoqXG4gKiBTdHJpbmdpZmllcyB2YWx1ZXMgYW5kIHJldHVybnMgdGhlIGRpc3RpbmN0LCBub24tYmxhbmsgb25lcywgcHJlc2VydmluZyBmaXJzdC1zZWVuIG9yZGVyLlxuICogQHBhcmFtIHt1bmtub3duW119IHZhbHVlc1xuICogQHJldHVybnMge3N0cmluZ1tdfVxuICovXG5mdW5jdGlvbiB1bmlxdWVTdHJpbmdzKHZhbHVlcykge1xuICByZXR1cm4gQXJyYXkuZnJvbShuZXcgU2V0KHZhbHVlcy5tYXAoKHZhbHVlKSA9PiBTdHJpbmcodmFsdWUpKS5maWx0ZXIoKHZhbHVlKSA9PiB2YWx1ZS50cmltKCkpKSlcbn1cblxuLyoqXG4gKiBDb3BpZXMgYSBzaW5nbGUgdGVuYW50J3Mgcm93cyBmcm9tIGEgc291cmNlIGRhdGFiYXNlIGludG8gYSB0YXJnZXQgZGF0YWJhc2UsIGZvbGxvd2luZyBhXG4gKiB0YWJsZSBwbGFuIHRoYXQgcGFydGl0aW9ucyBlYWNoIHRhYmxlIGVpdGhlciBieSBhIGRpcmVjdCB0ZW5hbnQga2V5IGNvbHVtbiBvciBieVxuICogcGFyZW50LWlkIHRyYXZlcnNhbC4gVGhpcyBpcyB0aGUgcm93LWxldmVsIGNvdW50ZXJwYXJ0IHRvIHNjaGVtYSBjbG9uaW5nOiBtdWx0aS10ZW5hbnRcbiAqIGFwcHMgdGhhdCBrZWVwIGVhY2ggdGVuYW50J3MgZGF0YSBpbiBpdHMgb3duIGRhdGFiYXNlIHVzZSBpdCB0byAocmUpbWF0ZXJpYWxpc2UgdGhlXG4gKiB0ZW5hbnQncyByb3dzIGZyb20gYSBnbG9iYWwvdGVtcGxhdGUgZGF0YWJhc2UuXG4gKlxuICogVGhlIGNvcHkgaXMgZGVsZXRlLXRoZW4tcmVpbnNlcnQgYW5kIHRoZXJlZm9yZSBpZGVtcG90ZW50LCBhbmQgaXQgbWlycm9ycyB0aGUgc291cmNlXG4gKiBzbmFwc2hvdDogdGhlIHJvd3MgdG8gZGVsZXRlIGFyZSB0aGUgdGVuYW50J3MgKmN1cnJlbnQqIHJvd3MgaW4gdGhlIHRhcmdldCAoc2VsZWN0ZWQgd2l0aFxuICogdGhlIHNhbWUgcGxhbiB0cmF2ZXJzYWwgcnVuIGFnYWluc3QgdGhlIHRhcmdldCksIHNvIGEgcm93IHRoYXQgd2FzIHJlbW92ZWQgZnJvbSB0aGUgc291cmNlXG4gKiBzaW5jZSB0aGUgbGFzdCBjb3B5IGlzIGRyb3BwZWQgZnJvbSB0aGUgdGFyZ2V0IHRvbyByYXRoZXIgdGhhbiBsaW5nZXJpbmcuIFRoZSBkZWxldGVzIHJ1blxuICogY2hpbGRyZW4gZmlyc3QgYW5kIHRoZSBzb3VyY2UgaW5zZXJ0cyBwYXJlbnRzIGZpcnN0LCBhbGwgaW5zaWRlIG9uZSB0YXJnZXQgdHJhbnNhY3Rpb24gd2l0aFxuICogZm9yZWlnbi1rZXkgZW5mb3JjZW1lbnQgZGlzYWJsZWQgc28gdGhlIG9yZGVyaW5nIG5ldmVyIHRyaXBzIGEgY29uc3RyYWludC4gUmVhZHMsIGRlbGV0ZXNcbiAqIGFuZCBpbnNlcnRzIGFyZSBjaHVua2VkIHRvIGJvdW5kIHN0YXRlbWVudCBzaXplLlxuICpcbiAqIFRoZSBjb3BpZXIgaXMgcG9saWN5LWZyZWU6IHRoZSBjYWxsZXIgc3VwcGxpZXMgdGhlIHBsYW4sIHRoZSBzb3VyY2UvdGFyZ2V0IGRhdGFiYXNlcyBhbmRcbiAqIHRoZSB0ZW5hbnQga2V5LCBhbmQge0BsaW5rIERhdGFDb3BpZXIjY29weX0gcmV0dXJucyB0aGUgbG9hZGVkIHJvd3Mga2V5ZWQgYnkgdGFibGUgbmFtZSBzb1xuICogdGhlIGNhbGxlciBjYW4gcGVyZm9ybSBhbnkgYXBwLXNwZWNpZmljIHBvc3QtY29weSB3b3JrIChmb3IgZXhhbXBsZSByZWdpc3RlcmluZyByZWNvcmRcbiAqIGxvY2F0aW9ucykgd2l0aG91dCB0aGF0IHBvbGljeSBsZWFraW5nIGludG8gdGhlIGZyYW1ld29yay5cbiAqL1xuZXhwb3J0IGRlZmF1bHQgY2xhc3MgRGF0YUNvcGllciB7XG4gIC8qKlxuICAgKiBDcmVhdGVzIGEgY29waWVyIHRoYXQgbW92ZXMgdGVuYW50LW93bmVkIHJvd3MgZnJvbSBgc291cmNlRGJgIGludG8gYHRhcmdldERiYC5cbiAgICogQHBhcmFtIHt7XG4gICAqICAgc291cmNlRGI6IGltcG9ydChcIi4uL2RyaXZlcnMvYmFzZS5qc1wiKS5kZWZhdWx0LFxuICAgKiAgIHRhcmdldERiOiBpbXBvcnQoXCIuLi9kcml2ZXJzL2Jhc2UuanNcIikuZGVmYXVsdCxcbiAgICogICB0YWJsZVBsYW46IGltcG9ydChcIi4vdGVuYW50LXRhYmxlLXBsYW4uanNcIikuVGVuYW50VGFibGVQbGFuRW50cnlbXSxcbiAgICogICBpZENvbHVtbj86IHN0cmluZyxcbiAgICogICBpbnNlcnRDaHVua1NpemU/OiBudW1iZXIsXG4gICAqICAgcXVlcnlDaHVua1NpemU/OiBudW1iZXIsXG4gICAqICAgb25Qcm9ncmVzcz86IChtZXNzYWdlOiBzdHJpbmcpID0+IHZvaWRcbiAgICogfX0gYXJnc1xuICAgKi9cbiAgY29uc3RydWN0b3Ioe3NvdXJjZURiLCB0YXJnZXREYiwgdGFibGVQbGFuLCBpZENvbHVtbiA9IFwiaWRcIiwgaW5zZXJ0Q2h1bmtTaXplID0gREVGQVVMVF9JTlNFUlRfQ0hVTktfU0laRSwgcXVlcnlDaHVua1NpemUgPSBERUZBVUxUX1FVRVJZX0NIVU5LX1NJWkUsIG9uUHJvZ3Jlc3N9KSB7XG4gICAgdGhpcy5zb3VyY2VEYiA9IHNvdXJjZURiXG4gICAgdGhpcy50YXJnZXREYiA9IHRhcmdldERiXG4gICAgdGhpcy50YWJsZVBsYW4gPSB0YWJsZVBsYW5cbiAgICB0aGlzLmlkQ29sdW1uID0gaWRDb2x1bW5cbiAgICB0aGlzLmluc2VydENodW5rU2l6ZSA9IGluc2VydENodW5rU2l6ZVxuICAgIHRoaXMucXVlcnlDaHVua1NpemUgPSBxdWVyeUNodW5rU2l6ZVxuICAgIHRoaXMub25Qcm9ncmVzcyA9IG9uUHJvZ3Jlc3NcbiAgfVxuXG4gIC8qKlxuICAgKiBDb3BpZXMgZXZlcnkgcGxhbiB0YWJsZSdzIHJvd3MgZm9yIGBrZXlWYWx1ZWAgZnJvbSB0aGUgc291cmNlIGludG8gdGhlIHRhcmdldCBhbmRcbiAgICogcmV0dXJucyB0aGUgY29waWVkIHNvdXJjZSByb3dzIGtleWVkIGJ5IHRhYmxlIG5hbWUuIFRoZSB0YXJnZXQncyBjdXJyZW50IHRlbmFudCByb3dzXG4gICAqIGFyZSBkZWxldGVkIChjaGlsZHJlbiBmaXJzdCkgYW5kIHRoZSBzb3VyY2Ugcm93cyBpbnNlcnRlZCAocGFyZW50cyBmaXJzdCkgaW4gYSBzaW5nbGVcbiAgICogdGFyZ2V0IHRyYW5zYWN0aW9uIHdpdGggZm9yZWlnbiBrZXlzIGRpc2FibGVkLlxuICAgKiBAcGFyYW0ge3N0cmluZ30ga2V5VmFsdWVcbiAgICogQHJldHVybnMge1Byb21pc2U8TWFwPHN0cmluZywgUmVjb3JkPHN0cmluZywgdW5rbm93bj5bXT4+fVxuICAgKi9cbiAgYXN5bmMgY29weShrZXlWYWx1ZSkge1xuICAgIGNvbnN0IHNvdXJjZVJvd3NCeVRhYmxlTmFtZSA9IGF3YWl0IHRoaXMubG9hZFJvd3ModGhpcy5zb3VyY2VEYiwga2V5VmFsdWUpXG4gICAgY29uc3QgdGFyZ2V0Um93c0J5VGFibGVOYW1lID0gYXdhaXQgdGhpcy5sb2FkUm93cyh0aGlzLnRhcmdldERiLCBrZXlWYWx1ZSlcblxuICAgIGF3YWl0IHRoaXMudGFyZ2V0RGIud2l0aERpc2FibGVkRm9yZWlnbktleXMoYXN5bmMgKCkgPT4ge1xuICAgICAgYXdhaXQgdGhpcy50YXJnZXREYi50cmFuc2FjdGlvbihhc3luYyAoKSA9PiB7XG4gICAgICAgIGF3YWl0IHRoaXMuZGVsZXRlVGFyZ2V0Um93cyh0YXJnZXRSb3dzQnlUYWJsZU5hbWUpXG4gICAgICAgIGF3YWl0IHRoaXMuaW5zZXJ0VGFyZ2V0Um93cyhzb3VyY2VSb3dzQnlUYWJsZU5hbWUpXG4gICAgICB9KVxuICAgIH0pXG5cbiAgICByZXR1cm4gc291cmNlUm93c0J5VGFibGVOYW1lXG4gIH1cblxuICAvKipcbiAgICogTG9hZHMgdGhlIHJvd3MgZm9yIGBrZXlWYWx1ZWAgZm9yIGV2ZXJ5IHRhYmxlIGluIHRoZSBwbGFuIGZyb20gYGRiYCwgcmVzb2x2aW5nXG4gICAqIHBhcmVudC1zY29wZWQgdGFibGVzIGZyb20gdGhlIGlkcyBhbHJlYWR5IHNlbGVjdGVkIGZvciB0aGVpciBwYXJlbnQgdGFibGUuIFVzZWQgZm9yXG4gICAqIGJvdGggdGhlIHNvdXJjZSByb3dzIHRvIGNvcHkgYW5kIHRoZSB0YXJnZXQncyBjdXJyZW50IHRlbmFudCByb3dzIHRvIGRlbGV0ZS5cbiAgICogQHBhcmFtIHtpbXBvcnQoXCIuLi9kcml2ZXJzL2Jhc2UuanNcIikuZGVmYXVsdH0gZGJcbiAgICogQHBhcmFtIHtzdHJpbmd9IGtleVZhbHVlXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPE1hcDxzdHJpbmcsIFJlY29yZDxzdHJpbmcsIHVua25vd24+W10+Pn1cbiAgICovXG4gIGFzeW5jIGxvYWRSb3dzKGRiLCBrZXlWYWx1ZSkge1xuICAgIC8qKiBAdHlwZSB7TWFwPHN0cmluZywgc3RyaW5nW10+fSAqL1xuICAgIGNvbnN0IGlkc0J5VGFibGVOYW1lID0gbmV3IE1hcCgpXG4gICAgLyoqIEB0eXBlIHtNYXA8c3RyaW5nLCBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPltdPn0gKi9cbiAgICBjb25zdCByb3dzQnlUYWJsZU5hbWUgPSBuZXcgTWFwKClcblxuICAgIGZvciAoY29uc3QgdGFibGVDb25maWcgb2YgdGhpcy50YWJsZVBsYW4pIHtcbiAgICAgIGxldCByb3dzXG5cbiAgICAgIGlmICh0YWJsZUNvbmZpZy5rZXlDb2x1bW4pIHtcbiAgICAgICAgcm93cyA9IGF3YWl0IHRoaXMucXVlcnlSb3dzQnlDb2x1bW4oe1xuICAgICAgICAgIGNvbHVtbk5hbWU6IHRhYmxlQ29uZmlnLmtleUNvbHVtbixcbiAgICAgICAgICBkYixcbiAgICAgICAgICB0YWJsZU5hbWU6IHRhYmxlQ29uZmlnLnRhYmxlTmFtZSxcbiAgICAgICAgICB2YWx1ZXM6IFtrZXlWYWx1ZV1cbiAgICAgICAgfSlcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGlmICghdGFibGVDb25maWcucGFyZW50Q29sdW1uIHx8ICF0YWJsZUNvbmZpZy5wYXJlbnRUYWJsZU5hbWUpIHtcbiAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEV4cGVjdGVkIGtleUNvbHVtbiBvciBwYXJlbnRUYWJsZU5hbWUrcGFyZW50Q29sdW1uIGZvciB0YWJsZSAke3RhYmxlQ29uZmlnLnRhYmxlTmFtZX0gaW4gdGhlIHRlbmFudCB0YWJsZSBwbGFuLmApXG4gICAgICAgIH1cblxuICAgICAgICBpZiAoIWlkc0J5VGFibGVOYW1lLmhhcyh0YWJsZUNvbmZpZy5wYXJlbnRUYWJsZU5hbWUpKSB7XG4gICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBUZW5hbnQgdGFibGUgcGxhbiBlbnRyeSAke3RhYmxlQ29uZmlnLnRhYmxlTmFtZX0gcmVmZXJlbmNlcyBwYXJlbnQgdGFibGUgJHt0YWJsZUNvbmZpZy5wYXJlbnRUYWJsZU5hbWV9LCB3aGljaCBoYXMgbm90IGJlZW4gbG9hZGVkOyBwYXJlbnQgdGFibGVzIG11c3QgYXBwZWFyIGJlZm9yZSB0aGVpciBjaGlsZHJlbiBpbiB0aGUgcGxhbi5gKVxuICAgICAgICB9XG5cbiAgICAgICAgcm93cyA9IGF3YWl0IHRoaXMucXVlcnlSb3dzQnlDb2x1bW4oe1xuICAgICAgICAgIGNvbHVtbk5hbWU6IHRhYmxlQ29uZmlnLnBhcmVudENvbHVtbixcbiAgICAgICAgICBkYixcbiAgICAgICAgICB0YWJsZU5hbWU6IHRhYmxlQ29uZmlnLnRhYmxlTmFtZSxcbiAgICAgICAgICB2YWx1ZXM6IGlkc0J5VGFibGVOYW1lLmdldCh0YWJsZUNvbmZpZy5wYXJlbnRUYWJsZU5hbWUpIHx8IFtdXG4gICAgICAgIH0pXG4gICAgICB9XG5cbiAgICAgIGlmIChyb3dzLmxlbmd0aCA+IDApIHtcbiAgICAgICAgdGhpcy5yZXBvcnRQcm9ncmVzcyhgJHt0YWJsZUNvbmZpZy50YWJsZU5hbWV9OiBsb2FkZWQgJHtyb3dzLmxlbmd0aH0gcm93KHMpYClcbiAgICAgIH1cblxuICAgICAgaWRzQnlUYWJsZU5hbWUuc2V0KHRhYmxlQ29uZmlnLnRhYmxlTmFtZSwgdW5pcXVlU3RyaW5ncyhyb3dzLm1hcCgocm93KSA9PiByb3dbdGhpcy5pZENvbHVtbl0pKSlcbiAgICAgIHJvd3NCeVRhYmxlTmFtZS5zZXQodGFibGVDb25maWcudGFibGVOYW1lLCByb3dzKVxuICAgIH1cblxuICAgIHJldHVybiByb3dzQnlUYWJsZU5hbWVcbiAgfVxuXG4gIC8qKlxuICAgKiBTZWxlY3RzIGFsbCByb3dzIG9mIGB0YWJsZU5hbWVgIGluIGBkYmAgd2hvc2UgYGNvbHVtbk5hbWVgIGlzIGluIGB2YWx1ZXNgLCBjaHVua2VkLlxuICAgKiBAcGFyYW0ge3tjb2x1bW5OYW1lOiBzdHJpbmcsIGRiOiBpbXBvcnQoXCIuLi9kcml2ZXJzL2Jhc2UuanNcIikuZGVmYXVsdCwgdGFibGVOYW1lOiBzdHJpbmcsIHZhbHVlczogc3RyaW5nW119fSBhcmdzXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPFJlY29yZDxzdHJpbmcsIHVua25vd24+W10+fVxuICAgKi9cbiAgYXN5bmMgcXVlcnlSb3dzQnlDb2x1bW4oe2NvbHVtbk5hbWUsIGRiLCB0YWJsZU5hbWUsIHZhbHVlc30pIHtcbiAgICBjb25zdCBub3JtYWxpemVkVmFsdWVzID0gdW5pcXVlU3RyaW5ncyh2YWx1ZXMpXG5cbiAgICBpZiAobm9ybWFsaXplZFZhbHVlcy5sZW5ndGggPD0gMCkge1xuICAgICAgcmV0dXJuIFtdXG4gICAgfVxuXG4gICAgY29uc3Qgcm93cyA9IFtdXG4gICAgY29uc3QgcXVvdGVkVGFibGUgPSBkYi5xdW90ZVRhYmxlKHRhYmxlTmFtZSlcbiAgICBjb25zdCBxdW90ZWRDb2x1bW4gPSBkYi5xdW90ZUNvbHVtbihjb2x1bW5OYW1lKVxuXG4gICAgZm9yIChjb25zdCB2YWx1ZXNDaHVuayBvZiBjaHVua3Mobm9ybWFsaXplZFZhbHVlcywgdGhpcy5xdWVyeUNodW5rU2l6ZSkpIHtcbiAgICAgIGNvbnN0IHNxbCA9IGBTRUxFQ1QgJHtxdW90ZWRUYWJsZX0uKiBGUk9NICR7cXVvdGVkVGFibGV9IFdIRVJFICR7cXVvdGVkQ29sdW1ufSBJTiAoJHt0aGlzLnF1b3RlZFZhbHVlc1NxbChkYiwgdmFsdWVzQ2h1bmspfSlgXG5cbiAgICAgIHJvd3MucHVzaCguLi5hd2FpdCB0aGlzLmV4ZWN1dGVRdWlldFF1ZXJ5KGRiLCBzcWwpKVxuICAgIH1cblxuICAgIHJldHVybiByb3dzXG4gIH1cblxuICAvKipcbiAgICogRGVsZXRlcyB0aGUgbWF0Y2hpbmcgdGFyZ2V0IHJvd3MgZm9yIGV2ZXJ5IHBsYW4gdGFibGUsIGNoaWxkcmVuIGJlZm9yZSBwYXJlbnRzLCBzbyB0aGVcbiAgICogcmVpbnNlcnQgdGhhdCBmb2xsb3dzIHN0YXJ0cyBmcm9tIGEgY2xlYW4gc2xhdGUgd2l0aG91dCB2aW9sYXRpbmcgZm9yZWlnbiBrZXlzLlxuICAgKiBAcGFyYW0ge01hcDxzdHJpbmcsIFJlY29yZDxzdHJpbmcsIHVua25vd24+W10+fSByb3dzQnlUYWJsZU5hbWVcbiAgICogQHJldHVybnMge1Byb21pc2U8dm9pZD59XG4gICAqL1xuICBhc3luYyBkZWxldGVUYXJnZXRSb3dzKHJvd3NCeVRhYmxlTmFtZSkge1xuICAgIGZvciAoY29uc3QgdGFibGVDb25maWcgb2YgWy4uLnRoaXMudGFibGVQbGFuXS5yZXZlcnNlKCkpIHtcbiAgICAgIGNvbnN0IHJvd0lkcyA9IHVuaXF1ZVN0cmluZ3MoKHJvd3NCeVRhYmxlTmFtZS5nZXQodGFibGVDb25maWcudGFibGVOYW1lKSB8fCBbXSkubWFwKChyb3cpID0+IHJvd1t0aGlzLmlkQ29sdW1uXSkpXG5cbiAgICAgIGlmIChyb3dJZHMubGVuZ3RoIDw9IDApIHtcbiAgICAgICAgY29udGludWVcbiAgICAgIH1cblxuICAgICAgY29uc3QgcXVvdGVkVGFibGUgPSB0aGlzLnRhcmdldERiLnF1b3RlVGFibGUodGFibGVDb25maWcudGFibGVOYW1lKVxuICAgICAgY29uc3QgcXVvdGVkSWRDb2x1bW4gPSB0aGlzLnRhcmdldERiLnF1b3RlQ29sdW1uKHRoaXMuaWRDb2x1bW4pXG5cbiAgICAgIGZvciAoY29uc3Qgcm93SWRzQ2h1bmsgb2YgY2h1bmtzKHJvd0lkcywgdGhpcy5xdWVyeUNodW5rU2l6ZSkpIHtcbiAgICAgICAgYXdhaXQgdGhpcy5leGVjdXRlUXVpZXRRdWVyeShcbiAgICAgICAgICB0aGlzLnRhcmdldERiLFxuICAgICAgICAgIGBERUxFVEUgRlJPTSAke3F1b3RlZFRhYmxlfSBXSEVSRSAke3F1b3RlZElkQ29sdW1ufSBJTiAoJHt0aGlzLnF1b3RlZFZhbHVlc1NxbCh0aGlzLnRhcmdldERiLCByb3dJZHNDaHVuayl9KWBcbiAgICAgICAgKVxuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBJbnNlcnRzIHRoZSBsb2FkZWQgc291cmNlIHJvd3MgaW50byB0aGUgdGFyZ2V0IGZvciBldmVyeSBwbGFuIHRhYmxlLCBwYXJlbnRzIGJlZm9yZVxuICAgKiBjaGlsZHJlbiwgY2h1bmtlZCB0byBib3VuZCBzdGF0ZW1lbnQgc2l6ZS5cbiAgICogQHBhcmFtIHtNYXA8c3RyaW5nLCBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPltdPn0gcm93c0J5VGFibGVOYW1lXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fVxuICAgKi9cbiAgYXN5bmMgaW5zZXJ0VGFyZ2V0Um93cyhyb3dzQnlUYWJsZU5hbWUpIHtcbiAgICBmb3IgKGNvbnN0IHRhYmxlQ29uZmlnIG9mIHRoaXMudGFibGVQbGFuKSB7XG4gICAgICBjb25zdCByb3dzID0gcm93c0J5VGFibGVOYW1lLmdldCh0YWJsZUNvbmZpZy50YWJsZU5hbWUpIHx8IFtdXG5cbiAgICAgIGlmIChyb3dzLmxlbmd0aCA8PSAwKSB7XG4gICAgICAgIGNvbnRpbnVlXG4gICAgICB9XG5cbiAgICAgIGNvbnN0IGNvbHVtbnMgPSBPYmplY3Qua2V5cyhyb3dzWzBdKVxuICAgICAgY29uc3QgaW5zZXJ0Q2h1bmtzID0gY2h1bmtzKHJvd3MsIHRoaXMuaW5zZXJ0Q2h1bmtTaXplKVxuXG4gICAgICB0aGlzLnJlcG9ydFByb2dyZXNzKGAke3RhYmxlQ29uZmlnLnRhYmxlTmFtZX06IGluc2VydGluZyAke3Jvd3MubGVuZ3RofSByb3cocykgaW4gJHtpbnNlcnRDaHVua3MubGVuZ3RofSBjaHVuayhzKWApXG5cbiAgICAgIGZvciAoY29uc3Qgcm93c0NodW5rIG9mIGluc2VydENodW5rcykge1xuICAgICAgICBhd2FpdCB0aGlzLmluc2VydFJvd3NRdWlldGx5KHtcbiAgICAgICAgICBjb2x1bW5zLFxuICAgICAgICAgIGRiOiB0aGlzLnRhcmdldERiLFxuICAgICAgICAgIHJvd3M6IHJvd3NDaHVuay5tYXAoKHJvdykgPT4gY29sdW1ucy5tYXAoKGNvbHVtbikgPT4gcm93W2NvbHVtbl0pKSxcbiAgICAgICAgICB0YWJsZU5hbWU6IHRhYmxlQ29uZmlnLnRhYmxlTmFtZVxuICAgICAgICB9KVxuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBRdW90ZXMgYW5kIGNvbW1hLWpvaW5zIHZhbHVlcyBmb3IgYW4gU1FMIGBJTiAoLi4uKWAgbGlzdCBhZ2FpbnN0IHRoZSBnaXZlbiBkYXRhYmFzZS5cbiAgICogQHBhcmFtIHtpbXBvcnQoXCIuLi9kcml2ZXJzL2Jhc2UuanNcIikuZGVmYXVsdH0gZGJcbiAgICogQHBhcmFtIHtzdHJpbmdbXX0gdmFsdWVzXG4gICAqIEByZXR1cm5zIHtzdHJpbmd9XG4gICAqL1xuICBxdW90ZWRWYWx1ZXNTcWwoZGIsIHZhbHVlcykge1xuICAgIHJldHVybiB2YWx1ZXMubWFwKCh2YWx1ZSkgPT4gZGIucXVvdGUodmFsdWUpKS5qb2luKFwiLCBcIilcbiAgfVxuXG4gIC8qKlxuICAgKiBSdW5zIGEgcXVlcnkgd2l0aG91dCBwZXItcXVlcnkgbG9nZ2luZywgdXNlZCBmb3IgdGhlIGhpZ2gtdm9sdW1lIGNvcHkgc3RhdGVtZW50cy5cbiAgICogQHBhcmFtIHtpbXBvcnQoXCIuLi9kcml2ZXJzL2Jhc2UuanNcIikuZGVmYXVsdH0gZGJcbiAgICogQHBhcmFtIHtzdHJpbmd9IHNxbFxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTxSZWNvcmQ8c3RyaW5nLCB1bmtub3duPltdPn1cbiAgICovXG4gIGFzeW5jIGV4ZWN1dGVRdWlldFF1ZXJ5KGRiLCBzcWwpIHtcbiAgICByZXR1cm4gYXdhaXQgZGIucXVlcnkoc3FsLCB7bG9nUXVlcnk6IGZhbHNlfSlcbiAgfVxuXG4gIC8qKlxuICAgKiBJbnNlcnRzIGNvbHVtbi1hbGlnbmVkIHJvdyB0dXBsZXMgaW50byBhIHRhYmxlIHdpdGhvdXQgcGVyLXF1ZXJ5IGxvZ2dpbmcuXG4gICAqIEBwYXJhbSB7e2NvbHVtbnM6IHN0cmluZ1tdLCBkYjogaW1wb3J0KFwiLi4vZHJpdmVycy9iYXNlLmpzXCIpLmRlZmF1bHQsIHJvd3M6IEFycmF5PEFycmF5PHVua25vd24+PiwgdGFibGVOYW1lOiBzdHJpbmd9fSBhcmdzXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fVxuICAgKi9cbiAgYXN5bmMgaW5zZXJ0Um93c1F1aWV0bHkoe2NvbHVtbnMsIGRiLCByb3dzLCB0YWJsZU5hbWV9KSB7XG4gICAgYXdhaXQgdGhpcy5leGVjdXRlUXVpZXRRdWVyeShkYiwgZGIuaW5zZXJ0U3FsKHtjb2x1bW5zLCB0YWJsZU5hbWUsIHJvd3N9KSlcbiAgfVxuXG4gIC8qKlxuICAgKiBGb3J3YXJkcyBhIHByb2dyZXNzIG1lc3NhZ2UgdG8gdGhlIG9wdGlvbmFsIGBvblByb2dyZXNzYCBjYWxsYmFjayB3aGVuIG9uZSB3YXMgZ2l2ZW4uXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBtZXNzYWdlXG4gICAqIEByZXR1cm5zIHt2b2lkfVxuICAgKi9cbiAgcmVwb3J0UHJvZ3Jlc3MobWVzc2FnZSkge1xuICAgIGlmICh0aGlzLm9uUHJvZ3Jlc3MpIHtcbiAgICAgIHRoaXMub25Qcm9ncmVzcyhtZXNzYWdlKVxuICAgIH1cbiAgfVxufVxuIl19
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clones table structure (columns, text-type widening, indexes) from a source database
|
|
3
|
+
* into a target database for a given set of tables, then baselines the target's
|
|
4
|
+
* `schema_migrations` ledger to the source via {@link MigrationsLedger}. This is the
|
|
5
|
+
* mechanism multi-tenant apps use to provision a tenant database from a template/global
|
|
6
|
+
* database without re-running migrations: the structure is copied and the ledger is
|
|
7
|
+
* recorded as already-applied, so a later `db:tenants:migrate` does not re-run an
|
|
8
|
+
* `addColumn` whose column already exists.
|
|
9
|
+
*
|
|
10
|
+
* The cloner is intentionally policy-free — the caller decides which tables to sync and
|
|
11
|
+
* which databases are source/target. It is idempotent: missing tables are created,
|
|
12
|
+
* missing columns added, too-narrow text columns widened, and missing indexes created;
|
|
13
|
+
* an index whose definition diverges from the source is treated as drift and throws.
|
|
14
|
+
*/
|
|
15
|
+
export default class SchemaCloner {
|
|
16
|
+
/**
|
|
17
|
+
* Creates a cloner that copies table structure from `sourceDb` into `targetDb`.
|
|
18
|
+
* @param {{sourceDb: import("../drivers/base.js").default, targetDb: import("../drivers/base.js").default}} args
|
|
19
|
+
*/
|
|
20
|
+
constructor({ sourceDb, targetDb }: {
|
|
21
|
+
sourceDb: import("../drivers/base.js").default;
|
|
22
|
+
targetDb: import("../drivers/base.js").default;
|
|
23
|
+
});
|
|
24
|
+
sourceDb: import("../drivers/base.js").default;
|
|
25
|
+
targetDb: import("../drivers/base.js").default;
|
|
26
|
+
/**
|
|
27
|
+
* Clones every given table from the source into the target, then baselines the
|
|
28
|
+
* target's ledger so the cloned schema is recorded as already-migrated.
|
|
29
|
+
* @param {string[]} tableNames
|
|
30
|
+
* @returns {Promise<void>}
|
|
31
|
+
*/
|
|
32
|
+
syncTables(tableNames: string[]): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Clones a single table from the source into the target, creating it or adding and
|
|
35
|
+
* widening columns and indexes as needed.
|
|
36
|
+
* @param {string} tableName
|
|
37
|
+
* @returns {Promise<void>}
|
|
38
|
+
*/
|
|
39
|
+
syncTable(tableName: string): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Creates the table in the target from the source table's columns and its
|
|
42
|
+
* non-primary-key indexes.
|
|
43
|
+
* @param {{sourceTable: import("../drivers/base-table.js").default, tableName: string}} args
|
|
44
|
+
* @returns {Promise<void>}
|
|
45
|
+
*/
|
|
46
|
+
createTargetTable({ sourceTable, tableName }: {
|
|
47
|
+
sourceTable: import("../drivers/base-table.js").default;
|
|
48
|
+
tableName: string;
|
|
49
|
+
}): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Adds columns present on the source but missing from the target, and widens
|
|
52
|
+
* too-narrow target text columns.
|
|
53
|
+
* @param {{sourceTable: import("../drivers/base-table.js").default, tableName: string}} args
|
|
54
|
+
* @returns {Promise<boolean>} Whether any column was added or widened.
|
|
55
|
+
*/
|
|
56
|
+
ensureTargetColumns({ sourceTable, tableName }: {
|
|
57
|
+
sourceTable: import("../drivers/base-table.js").default;
|
|
58
|
+
tableName: string;
|
|
59
|
+
}): Promise<boolean>;
|
|
60
|
+
/**
|
|
61
|
+
* Creates non-primary-key indexes present on the source but missing from the target,
|
|
62
|
+
* and throws if an existing target index diverges from the source.
|
|
63
|
+
* @param {{sourceTable: import("../drivers/base-table.js").default, tableName: string}} args
|
|
64
|
+
* @returns {Promise<void>}
|
|
65
|
+
*/
|
|
66
|
+
ensureTargetIndexes({ sourceTable, tableName }: {
|
|
67
|
+
sourceTable: import("../drivers/base-table.js").default;
|
|
68
|
+
tableName: string;
|
|
69
|
+
}): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Baselines the target ledger so the cloned schema is recorded as already-applied.
|
|
72
|
+
* @returns {Promise<string[]>} The versions newly recorded on the target.
|
|
73
|
+
*/
|
|
74
|
+
reconcileLedger(): Promise<string[]>;
|
|
75
|
+
/**
|
|
76
|
+
* Whether the target ledger is missing any version applied on the source — i.e. the
|
|
77
|
+
* target schema may have been advanced out of band without recording it.
|
|
78
|
+
* @returns {Promise<boolean>}
|
|
79
|
+
*/
|
|
80
|
+
ledgerDriftsFromSource(): Promise<boolean>;
|
|
81
|
+
/**
|
|
82
|
+
* Maps a source index into a TableData index for table creation (SQLite omits the
|
|
83
|
+
* index name so the driver can generate a unique one).
|
|
84
|
+
* @param {import("../drivers/base-columns-index.js").default} sourceIndex
|
|
85
|
+
* @returns {TableIndex}
|
|
86
|
+
*/
|
|
87
|
+
tableDataIndexFromSourceIndex(sourceIndex: import("../drivers/base-columns-index.js").default): TableIndex;
|
|
88
|
+
/**
|
|
89
|
+
* Builds driver create-index args from a source index (the index name is omitted on
|
|
90
|
+
* SQLite, where index names are unique per-database rather than per-table).
|
|
91
|
+
* @param {{sourceIndex: import("../drivers/base-columns-index.js").default, tableName: string}} args
|
|
92
|
+
* @returns {{columns: string[], name?: string, tableName: string, unique: boolean}}
|
|
93
|
+
*/
|
|
94
|
+
createIndexArgsFromSourceIndex({ sourceIndex, tableName }: {
|
|
95
|
+
sourceIndex: import("../drivers/base-columns-index.js").default;
|
|
96
|
+
tableName: string;
|
|
97
|
+
}): {
|
|
98
|
+
columns: string[];
|
|
99
|
+
name?: string;
|
|
100
|
+
tableName: string;
|
|
101
|
+
unique: boolean;
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Whether two indexes have the same uniqueness and ordered column list.
|
|
105
|
+
* @param {import("../drivers/base-columns-index.js").default} sourceIndex
|
|
106
|
+
* @param {import("../drivers/base-columns-index.js").default} targetIndex
|
|
107
|
+
* @returns {boolean}
|
|
108
|
+
*/
|
|
109
|
+
indexesMatch(sourceIndex: import("../drivers/base-columns-index.js").default, targetIndex: import("../drivers/base-columns-index.js").default): boolean;
|
|
110
|
+
/**
|
|
111
|
+
* A stable signature for an index, used to match cloned indexes by shape.
|
|
112
|
+
* @param {import("../drivers/base-columns-index.js").default} index
|
|
113
|
+
* @returns {string}
|
|
114
|
+
*/
|
|
115
|
+
indexSignature(index: import("../drivers/base-columns-index.js").default): string;
|
|
116
|
+
/**
|
|
117
|
+
* Normalizes a column type to its canonical lowercase form (`int` becomes `integer`).
|
|
118
|
+
* @param {string} columnType
|
|
119
|
+
* @returns {string}
|
|
120
|
+
*/
|
|
121
|
+
normalizedColumnType(columnType: string): string;
|
|
122
|
+
/**
|
|
123
|
+
* The widening rank of a text column type (0 when not a text type).
|
|
124
|
+
* @param {string} columnType
|
|
125
|
+
* @returns {number}
|
|
126
|
+
*/
|
|
127
|
+
textTypeRank(columnType: string): number;
|
|
128
|
+
/**
|
|
129
|
+
* Whether the target's text column is narrower than the source's and must be widened.
|
|
130
|
+
* @param {import("../drivers/base-column.js").default} sourceColumn
|
|
131
|
+
* @param {import("../drivers/base-column.js").default} targetColumn
|
|
132
|
+
* @returns {boolean}
|
|
133
|
+
*/
|
|
134
|
+
columnNeedsWidening(sourceColumn: import("../drivers/base-column.js").default, targetColumn: import("../drivers/base-column.js").default): boolean;
|
|
135
|
+
/**
|
|
136
|
+
* Builds TableData column args from a source column, copying type, nullability,
|
|
137
|
+
* length, notes, simple defaults and (for full clones) primary-key flag.
|
|
138
|
+
* @param {import("../drivers/base-column.js").default} sourceColumn
|
|
139
|
+
* @param {{isNewColumn: boolean}} args
|
|
140
|
+
* @returns {Record<string, unknown>}
|
|
141
|
+
*/
|
|
142
|
+
columnArgsFromSourceColumn(sourceColumn: import("../drivers/base-column.js").default, { isNewColumn }: {
|
|
143
|
+
isNewColumn: boolean;
|
|
144
|
+
}): Record<string, unknown>;
|
|
145
|
+
}
|
|
146
|
+
import TableIndex from "../table-data/table-index.js";
|
|
147
|
+
//# sourceMappingURL=schema-cloner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-cloner.d.ts","sourceRoot":"","sources":["../../../../src/database/tenants/schema-cloner.js"],"names":[],"mappings":"AAgBA;;;;;;;;;;;;;GAaG;AACH;IACE;;;OAGG;IACH,oCAFW;QAAC,QAAQ,EAAE,OAAO,oBAAoB,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,OAAO,oBAAoB,EAAE,OAAO,CAAA;KAAC,EAK1G;IAFC,+CAAwB;IACxB,+CAAwB;IAG1B;;;;;OAKG;IACH,uBAHW,MAAM,EAAE,GACN,OAAO,CAAC,IAAI,CAAC,CAQzB;IAED;;;;;OAKG;IACH,qBAHW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAqBzB;IAED;;;;;OAKG;IACH,8CAHW;QAAC,WAAW,EAAE,OAAO,0BAA0B,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAC,GAC1E,OAAO,CAAC,IAAI,CAAC,CAiBzB;IAED;;;;;OAKG;IACH,gDAHW;QAAC,WAAW,EAAE,OAAO,0BAA0B,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAC,GAC1E,OAAO,CAAC,OAAO,CAAC,CA0C5B;IAED;;;;;OAKG;IACH,gDAHW;QAAC,WAAW,EAAE,OAAO,0BAA0B,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAC,GAC1E,OAAO,CAAC,IAAI,CAAC,CAiDzB;IAED;;;OAGG;IACH,mBAFa,OAAO,CAAC,MAAM,EAAE,CAAC,CAI7B;IAED;;;;OAIG;IACH,0BAFa,OAAO,CAAC,OAAO,CAAC,CAW5B;IAED;;;;;OAKG;IACH,2CAHW,OAAO,kCAAkC,EAAE,OAAO,GAChD,UAAU,CAetB;IAED;;;;;OAKG;IACH,2DAHW;QAAC,WAAW,EAAE,OAAO,kCAAkC,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAC,GAClF;QAAC,OAAO,EAAE,MAAM,EAAE,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAC,CAelF;IAED;;;;;OAKG;IACH,0BAJW,OAAO,kCAAkC,EAAE,OAAO,eAClD,OAAO,kCAAkC,EAAE,OAAO,GAChD,OAAO,CAqBnB;IAED;;;;OAIG;IACH,sBAHW,OAAO,kCAAkC,EAAE,OAAO,GAChD,MAAM,CAIlB;IAED;;;;OAIG;IACH,iCAHW,MAAM,GACJ,MAAM,CAUlB;IAED;;;;OAIG;IACH,yBAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;;;OAKG;IACH,kCAJW,OAAO,2BAA2B,EAAE,OAAO,gBAC3C,OAAO,2BAA2B,EAAE,OAAO,GACzC,OAAO,CAOnB;IAED;;;;;;OAMG;IACH,yCAJW,OAAO,2BAA2B,EAAE,OAAO,mBAC3C;QAAC,WAAW,EAAE,OAAO,CAAA;KAAC,GACpB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAqCnC;CACF;uBAjYsB,8BAA8B"}
|