preloaded data works

This commit is contained in:
Jan Prochazka
2021-11-27 17:49:02 +01:00
parent d4f4211ee4
commit 40d53275e3
12 changed files with 177 additions and 5 deletions

View File

@@ -221,4 +221,33 @@ describe('Deploy database', () => {
); );
}) })
); );
test.each(engines.map(engine => [engine.label, engine]))(
'Deploy preloaded data - %s',
testWrapper(async (conn, driver, engine) => {
await testDatabaseDeploy(conn, driver, [
[
{
name: 't1.table.yaml',
json: {
name: 't1',
columns: [
{ name: 'id', type: 'int' },
{ name: 'value', type: 'int' },
],
primaryKey: ['id'],
data: [
{ id: 1, value: 1 },
{ id: 2, value: 2 },
{ id: 3, value: 3 },
],
},
},
],
]);
const res = await driver.query(conn, `select count(*) as cnt from t1`);
expect(res.rows[0].cnt.toString()).toEqual('3');
})
);
}); });

View File

@@ -118,7 +118,7 @@ const filterLocal = [
// filter local testing // filter local testing
'MySQL', 'MySQL',
'PostgreSQL', 'PostgreSQL',
'SQL Server', '-SQL Server',
'SQLite', 'SQLite',
'-CockroachDB', '-CockroachDB',
]; ];

View File

@@ -5,6 +5,7 @@ const {
databaseInfoFromYamlModel, databaseInfoFromYamlModel,
extendDatabaseInfo, extendDatabaseInfo,
modelCompareDbDiffOptions, modelCompareDbDiffOptions,
enrichWithPreloadedRows,
} = require('dbgate-tools'); } = require('dbgate-tools');
const importDbModel = require('../utility/importDbModel'); const importDbModel = require('../utility/importDbModel');
const requireEngineDriver = require('../utility/requireEngineDriver'); const requireEngineDriver = require('../utility/requireEngineDriver');
@@ -19,8 +20,9 @@ async function generateDeploySql({
loadedDbModel = undefined, loadedDbModel = undefined,
}) { }) {
if (!driver) driver = requireEngineDriver(connection); if (!driver) driver = requireEngineDriver(connection);
const pool = systemConnection || (await connectUtility(driver, connection));
if (!analysedStructure) { if (!analysedStructure) {
const pool = systemConnection || (await connectUtility(driver, connection));
analysedStructure = await driver.analyseFull(pool); analysedStructure = await driver.analyseFull(pool);
} }
@@ -39,6 +41,12 @@ async function generateDeploySql({
noRenameColumn: true, noRenameColumn: true,
}; };
const currentModelPaired = matchPairedObjects(deployedModel, currentModel, opts); const currentModelPaired = matchPairedObjects(deployedModel, currentModel, opts);
const currentModelPairedPreloaded = await enrichWithPreloadedRows(
deployedModel,
currentModelPaired,
pool,
driver
);
// console.log('deployedModel', deployedModel.tables[0]); // console.log('deployedModel', deployedModel.tables[0]);
// console.log('currentModel', currentModel.tables[0]); // console.log('currentModel', currentModel.tables[0]);
// console.log('currentModelPaired', currentModelPaired.tables[0]); // console.log('currentModelPaired', currentModelPaired.tables[0]);
@@ -46,7 +54,7 @@ async function generateDeploySql({
currentModelPaired, currentModelPaired,
deployedModel, deployedModel,
opts, opts,
currentModelPaired, currentModelPairedPreloaded,
deployedModel, deployedModel,
driver driver
); );

View File

@@ -1,3 +1,4 @@
import _ from 'lodash';
import { import {
ColumnInfo, ColumnInfo,
ConstraintInfo, ConstraintInfo,
@@ -604,4 +605,40 @@ export class SqlDumper implements AlterProcessor {
dropSqlObject(obj: SqlObjectInfo) { dropSqlObject(obj: SqlObjectInfo) {
this.putCmd('^drop %s %f', this.getSqlObjectSqlName(obj.objectTypeField), obj); this.putCmd('^drop %s %f', this.getSqlObjectSqlName(obj.objectTypeField), obj);
} }
fillPreloadedRows(table: NamedObjectInfo, oldRows: any[], newRows: any[], key: string[]) {
let was = false;
for (const row of newRows) {
const old = oldRows?.find(r => key.every(col => r[col] == row[col]));
const rowKeys = _.keys(row);
if (old) {
const updated = [];
for (const col of rowKeys) {
if (row[col] != old[col]) {
updated.push(col);
}
}
if (updated.length > 0) {
if (was) this.put(';\n');
was = true;
this.put('^update %f ^set ', table);
this.putCollection(', ', updated, col => this.put('%i=%v', col, row[col]));
this.put(' ^ where ');
this.putCollection(' ^and ', key, col => this.put('%i=%v', col, row[col]));
}
} else {
if (was) this.put(';\n');
was = true;
this.put(
'^insert ^into %f (%,i) ^values (%,v)',
table,
rowKeys,
rowKeys.map(x => row[x])
);
}
}
if (was) {
this.endCommand();
}
}
} }

View File

@@ -8,6 +8,7 @@ import {
SqlObjectInfo, SqlObjectInfo,
SqlDialect, SqlDialect,
TableInfo, TableInfo,
NamedObjectInfo,
} from '../../types'; } from '../../types';
import { DatabaseInfoAlterProcessor } from './database-info-alter-processor'; import { DatabaseInfoAlterProcessor } from './database-info-alter-processor';
import { DatabaseAnalyser } from './DatabaseAnalyser'; import { DatabaseAnalyser } from './DatabaseAnalyser';
@@ -86,6 +87,13 @@ interface AlterOperation_RecreateTable {
table: TableInfo; table: TableInfo;
operations: AlterOperation[]; operations: AlterOperation[];
} }
interface AlterOperation_FillPreloadedRows {
operationType: 'fillPreloadedRows';
table: NamedObjectInfo;
oldRows: any[];
newRows: any[];
key: string[];
}
type AlterOperation = type AlterOperation =
| AlterOperation_CreateColumn | AlterOperation_CreateColumn
@@ -101,7 +109,8 @@ type AlterOperation =
| AlterOperation_RenameConstraint | AlterOperation_RenameConstraint
| AlterOperation_CreateSqlObject | AlterOperation_CreateSqlObject
| AlterOperation_DropSqlObject | AlterOperation_DropSqlObject
| AlterOperation_RecreateTable; | AlterOperation_RecreateTable
| AlterOperation_FillPreloadedRows;
export class AlterPlan { export class AlterPlan {
recreates = { recreates = {
@@ -223,6 +232,16 @@ export class AlterPlan {
this.recreates.tables += 1; this.recreates.tables += 1;
} }
fillPreloadedRows(table: NamedObjectInfo, oldRows: any[], newRows: any[], key: string[]) {
this.operations.push({
operationType: 'fillPreloadedRows',
table,
oldRows,
newRows,
key,
});
}
run(processor: AlterProcessor) { run(processor: AlterProcessor) {
for (const op of this.operations) { for (const op of this.operations) {
runAlterOperation(op, processor); runAlterOperation(op, processor);
@@ -545,6 +564,9 @@ export function runAlterOperation(op: AlterOperation, processor: AlterProcessor)
case 'dropSqlObject': case 'dropSqlObject':
processor.dropSqlObject(op.oldObject); processor.dropSqlObject(op.oldObject);
break; break;
case 'fillPreloadedRows':
processor.fillPreloadedRows(op.table, op.oldRows, op.newRows, op.key);
break;
case 'recreateTable': case 'recreateTable':
{ {
const oldTable = generateTablePairingId(op.table); const oldTable = generateTablePairingId(op.table);

View File

@@ -10,6 +10,7 @@ import {
CheckInfo, CheckInfo,
UniqueInfo, UniqueInfo,
SqlObjectInfo, SqlObjectInfo,
NamedObjectInfo,
} from '../../types'; } from '../../types';
export class DatabaseInfoAlterProcessor { export class DatabaseInfoAlterProcessor {
@@ -114,4 +115,10 @@ export class DatabaseInfoAlterProcessor {
recreateTable(oldTable: TableInfo, newTable: TableInfo) { recreateTable(oldTable: TableInfo, newTable: TableInfo) {
throw new Error('recreateTable not implemented for DatabaseInfoAlterProcessor'); throw new Error('recreateTable not implemented for DatabaseInfoAlterProcessor');
} }
fillPreloadedRows(table: NamedObjectInfo, oldRows: any[], newRows: any[], key: string[]) {
const tableInfo = this.db.tables.find(x => x.pureName == table.pureName && x.schemaName == table.schemaName);
tableInfo.preloadedRows = newRows;
tableInfo.preloadedRowsKey = key;
}
} }

View File

@@ -325,6 +325,13 @@ function createPairs(oldList, newList, additionalCondition = null) {
return res; return res;
} }
function planTablePreload(plan: AlterPlan, oldTable: TableInfo, newTable: TableInfo) {
const key = newTable.preloadedRowsKey || newTable.primaryKey?.columns?.map(x => x.columnName);
if (newTable.preloadedRows?.length > 0 && key?.length > 0) {
plan.fillPreloadedRows(newTable, oldTable?.preloadedRows, newTable.preloadedRows, key);
}
}
function planAlterTable(plan: AlterPlan, oldTable: TableInfo, newTable: TableInfo, opts: DbDiffOptions) { function planAlterTable(plan: AlterPlan, oldTable: TableInfo, newTable: TableInfo, opts: DbDiffOptions) {
// if (oldTable.primaryKey) // if (oldTable.primaryKey)
@@ -374,6 +381,8 @@ function planAlterTable(plan: AlterPlan, oldTable: TableInfo, newTable: TableInf
}); });
constraintPairs.filter(x => x[0] == null).forEach(x => plan.createConstraint(x[1])); constraintPairs.filter(x => x[0] == null).forEach(x => plan.createConstraint(x[1]));
planTablePreload(plan, oldTable, newTable);
} }
export function testEqualTables( export function testEqualTables(
@@ -405,6 +414,7 @@ export function createAlterTablePlan(
const plan = new AlterPlan(wholeOldDb, wholeNewDb, driver.dialect, opts); const plan = new AlterPlan(wholeOldDb, wholeNewDb, driver.dialect, opts);
if (oldTable == null) { if (oldTable == null) {
plan.createTable(newTable); plan.createTable(newTable);
planTablePreload(plan, null, newTable);
} else if (newTable == null) { } else if (newTable == null) {
plan.dropTable(oldTable); plan.dropTable(oldTable);
} else { } else {
@@ -452,7 +462,10 @@ export function createAlterDatabasePlan(
for (const newobj of newDb[objectTypeField] || []) { for (const newobj of newDb[objectTypeField] || []) {
const oldobj = (oldDb[objectTypeField] || []).find(x => x.pairingId == newobj.pairingId); const oldobj = (oldDb[objectTypeField] || []).find(x => x.pairingId == newobj.pairingId);
if (objectTypeField == 'tables') { if (objectTypeField == 'tables') {
if (oldobj == null) plan.createTable(newobj); if (oldobj == null) {
plan.createTable(newobj);
planTablePreload(plan, null, newobj);
}
} else { } else {
if (oldobj == null) plan.createSqlObject(newobj); if (oldobj == null) plan.createSqlObject(newobj);
} }

View File

@@ -16,3 +16,4 @@ export * from './schemaEditorTools';
export * from './yamlModelConv'; export * from './yamlModelConv';
export * from './stringTools'; export * from './stringTools';
export * from './computeDiffRows'; export * from './computeDiffRows';
export * from './preloadedRowsTools';

View File

@@ -0,0 +1,47 @@
import _ from 'lodash';
import { DatabaseInfo, EngineDriver } from 'dbgate-types';
export async function enrichWithPreloadedRows(
dbModel: DatabaseInfo,
dbTarget: DatabaseInfo,
conn,
driver: EngineDriver
): Promise<DatabaseInfo> {
// const res = { ...dbTarget, tables: [...(dbTarget.tables || [])] };
const repl = {};
for (const tableTarget of dbTarget.tables) {
const tableModel = dbModel.tables.find(x => x.pairingId == tableTarget.pairingId);
if (tableModel.preloadedRows?.length || 0 == 0) continue;
const keyColumns = tableModel.preloadedRowsKey || tableModel.primaryKey?.columns?.map(x => x.columnName);
if (keyColumns?.length || 0 == 0) continue;
const dmp = driver.createDumper();
if (keyColumns.length == 1) {
dmp.putCmd(
'^select * ^from %f ^where %i ^in (%,v)',
tableTarget,
keyColumns[0],
tableModel.preloadedRows.map(x => x[keyColumns[0]])
);
} else {
dmp.put('^select * ^from %f ^where', tableTarget);
dmp.putCollection(' ^or ', tableTarget.preloadedRows, row => {
dmp.put('(');
dmp.putCollection(' ^and ', keyColumns, col => dmp.put('%i=%v', col, row[col]));
dmp.put(')');
});
dmp.endCommand();
}
const resp = await driver.query(conn, dmp.s);
repl[tableTarget.pairingId] = {
...tableTarget,
preloadedRows: resp.rows,
preloadedRowsKey: keyColumns,
};
}
if (_.isEmpty(repl)) return dbTarget;
return {
...dbTarget,
tables: dbTarget.tables.map(x => repl[x.pairingId] || x),
};
}

View File

@@ -22,6 +22,9 @@ export interface TableInfoYaml {
// schema?: string; // schema?: string;
columns: ColumnInfoYaml[]; columns: ColumnInfoYaml[];
primaryKey?: string[]; primaryKey?: string[];
insertKey?: string[];
data?: any[];
} }
export interface ForeignKeyInfoYaml { export interface ForeignKeyInfoYaml {
@@ -119,6 +122,8 @@ export function tableInfoFromYaml(table: TableInfoYaml, allTables: TableInfoYaml
columns: table.primaryKey.map(columnName => ({ columnName })), columns: table.primaryKey.map(columnName => ({ columnName })),
}; };
} }
res.preloadedRows = table.data;
res.preloadedRowsKey = table.insertKey;
return res; return res;
} }

View File

@@ -15,4 +15,5 @@ export interface AlterProcessor {
recreateTable(oldTable: TableInfo, newTable: TableInfo); recreateTable(oldTable: TableInfo, newTable: TableInfo);
createSqlObject(obj: SqlObjectInfo); createSqlObject(obj: SqlObjectInfo);
dropSqlObject(obj: SqlObjectInfo); dropSqlObject(obj: SqlObjectInfo);
fillPreloadedRows(table: NamedObjectInfo, oldRows: any[], newRows: any[], key: string[]);
} }

View File

@@ -78,6 +78,8 @@ export interface TableInfo extends DatabaseObjectInfo {
indexes?: IndexInfo[]; indexes?: IndexInfo[];
uniques?: UniqueInfo[]; uniques?: UniqueInfo[];
checks?: CheckInfo[]; checks?: CheckInfo[];
preloadedRows?: any[];
preloadedRowsKey?: string[];
} }
export interface CollectionInfo extends DatabaseObjectInfo {} export interface CollectionInfo extends DatabaseObjectInfo {}