oracle analyser per schema

This commit is contained in:
Jan Prochazka
2024-05-31 14:35:35 +02:00
parent a2102a51a1
commit 2723c41832
18 changed files with 58 additions and 72 deletions

View File

@@ -61,6 +61,7 @@ function getPortalCollections() {
useDatabaseUrl: !!process.env[`URL_${id}`], useDatabaseUrl: !!process.env[`URL_${id}`],
databaseFile: process.env[`FILE_${id}`], databaseFile: process.env[`FILE_${id}`],
socketPath: process.env[`SOCKET_PATH_${id}`], socketPath: process.env[`SOCKET_PATH_${id}`],
serviceName: process.env[`SERVICE_NAME_${id}`],
authType: process.env[`AUTH_TYPE_${id}`] || (process.env[`SOCKET_PATH_${id}`] ? 'socket' : undefined), authType: process.env[`AUTH_TYPE_${id}`] || (process.env[`SOCKET_PATH_${id}`] ? 'socket' : undefined),
defaultDatabase: defaultDatabase:
process.env[`DATABASE_${id}`] || process.env[`DATABASE_${id}`] ||

View File

@@ -180,8 +180,15 @@ export class DatabaseAnalyser {
// return this.createQueryCore('=OBJECT_ID_CONDITION', typeFields) != ' is not null'; // return this.createQueryCore('=OBJECT_ID_CONDITION', typeFields) != ' is not null';
// } // }
createQuery(template, typeFields) { createQuery(template, typeFields, replacements = {}) {
return this.createQueryCore(template, typeFields); return this.createQueryCore(this.processQueryReplacements(template, replacements), typeFields);
}
processQueryReplacements(query, replacements) {
for (const repl in replacements) {
query = query.replaceAll(repl, replacements[repl]);
}
return query;
} }
createQueryCore(template, typeFields) { createQueryCore(template, typeFields) {
@@ -302,8 +309,8 @@ export class DatabaseAnalyser {
return [..._compact(res), ...this.getDeletedObjects(snapshot)]; return [..._compact(res), ...this.getDeletedObjects(snapshot)];
} }
async analyserQuery(template, typeFields) { async analyserQuery(template, typeFields, replacements = {}) {
const sql = this.createQuery(template, typeFields); const sql = this.createQuery(template, typeFields, replacements);
if (!sql) { if (!sql) {
return { return {
@@ -311,7 +318,9 @@ export class DatabaseAnalyser {
}; };
} }
try { try {
return await this.driver.query(this.pool, sql); const res = await this.driver.query(this.pool, sql);
this.logger.debug({ rows: res.rows.length, template }, `Loaded analyser query`);
return res;
} catch (err) { } catch (err) {
logger.error({ err }, 'Error running analyser query'); logger.error({ err }, 'Error running analyser query');
return { return {

View File

@@ -123,6 +123,10 @@
{/if} {/if}
{/if} {/if}
{#if driver?.showConnectionField('serviceName', $values)}
<FormTextField label="Service name" name="serviceName" disabled={isConnected} />
{/if}
{#if driver?.showConnectionField('socketPath', $values)} {#if driver?.showConnectionField('socketPath', $values)}
<FormTextField <FormTextField
label="Socket path" label="Socket path"

View File

@@ -84,6 +84,7 @@
'defaultDatabase', 'defaultDatabase',
'singleDatabase', 'singleDatabase',
'socketPath', 'socketPath',
'serviceName',
]; ];
const visibleProps = allProps.filter(x => driver?.showConnectionField(x, $values)); const visibleProps = allProps.filter(x => driver?.showConnectionField(x, $values));
const omitProps = _.difference(allProps, visibleProps); const omitProps = _.difference(allProps, visibleProps);

View File

@@ -24,6 +24,9 @@ function getConnectionLabelCore(connection, { allowExplicitDatabase = true } = {
if (connection.singleDatabase && connection.defaultDatabase) { if (connection.singleDatabase && connection.defaultDatabase) {
return `${connection.defaultDatabase}`; return `${connection.defaultDatabase}`;
} }
if (connection.useDatabaseUrl) {
return `${connection.databaseUrl}`;
}
return ''; return '';
} }

View File

@@ -55,8 +55,8 @@ class Analyser extends DatabaseAnalyser {
super(pool, driver, version); super(pool, driver, version);
} }
createQuery(resFileName, typeFields) { createQuery(resFileName, typeFields, replacements = {}) {
const query = super.createQuery(sql[resFileName], typeFields); const query = super.createQuery(sql[resFileName], typeFields, replacements);
//if (query) return query.replace('#REFTABLECOND#', this.driver.__analyserInternals.refTableCond); //if (query) return query.replace('#REFTABLECOND#', this.driver.__analyserInternals.refTableCond);
return query; return query;
} }
@@ -68,40 +68,39 @@ class Analyser extends DatabaseAnalyser {
async _runAnalysis() { async _runAnalysis() {
this.feedback({ analysingMessage: 'Loading tables' }); this.feedback({ analysingMessage: 'Loading tables' });
const tables = await this.analyserQuery(this.driver.dialect.stringAgg ? 'tableList' : 'tableList', ['tables']); const tables = await this.analyserQuery('tableList', ['tables'], { $owner: this.pool._schema_name });
this.feedback({ analysingMessage: 'Loading columns' }); this.feedback({ analysingMessage: 'Loading columns' });
const columns = await this.analyserQuery('columns', ['tables', 'views']); const columns = await this.analyserQuery('columns', ['tables', 'views'], { $owner: this.pool._schema_name });
this.feedback({ analysingMessage: 'Loading primary keys' }); this.feedback({ analysingMessage: 'Loading primary keys' });
const pkColumns = await this.analyserQuery('primaryKeys', ['tables']); const pkColumns = await this.analyserQuery('primaryKeys', ['tables'], { $owner: this.pool._schema_name });
//let fkColumns = null; //let fkColumns = null;
this.feedback({ analysingMessage: 'Loading foreign keys' }); this.feedback({ analysingMessage: 'Loading foreign keys' });
const fkColumns = await this.analyserQuery('foreignKeys', ['tables']); const fkColumns = await this.analyserQuery('foreignKeys', ['tables'], { $owner: this.pool._schema_name });
this.feedback({ analysingMessage: 'Loading views' }); this.feedback({ analysingMessage: 'Loading views' });
const views = await this.analyserQuery('views', ['views']); const views = await this.analyserQuery('views', ['views'], { $owner: this.pool._schema_name });
let geometryColumns = { rows: [] }; let geometryColumns = { rows: [] };
let geographyColumns = { rows: [] }; let geographyColumns = { rows: [] };
this.feedback({ analysingMessage: 'Loading materialized views' }); this.feedback({ analysingMessage: 'Loading materialized views' });
const matviews = this.driver.dialect.materializedViews ? await this.analyserQuery('matviews', ['matviews']) : null; const matviews = this.driver.dialect.materializedViews
? await this.analyserQuery('matviews', ['matviews'], { $owner: this.pool._schema_name })
: null;
this.feedback({ analysingMessage: 'Loading materialized view columns' }); this.feedback({ analysingMessage: 'Loading materialized view columns' });
const matviewColumns = this.driver.dialect.materializedViews const matviewColumns = this.driver.dialect.materializedViews
? await this.analyserQuery('matviewColumns', ['matviews']) ? await this.analyserQuery('matviewColumns', ['matviews'], { $owner: this.pool._schema_name })
: null; : null;
this.feedback({ analysingMessage: 'Loading routines' }); this.feedback({ analysingMessage: 'Loading routines' });
const routines = await this.analyserQuery('routines', ['procedures', 'functions']); const routines = await this.analyserQuery('routines', ['procedures', 'functions'], {
$owner: this.pool._schema_name,
});
this.feedback({ analysingMessage: 'Loading indexes' }); this.feedback({ analysingMessage: 'Loading indexes' });
const indexes = this.driver.__analyserInternals.skipIndexes const indexes = await this.analyserQuery('indexes', ['tables'], { $owner: this.pool._schema_name });
? { rows: [] }
: await this.analyserQuery('indexes', ['tables']);
this.feedback({ analysingMessage: 'Loading index columns' }); this.feedback({ analysingMessage: 'Loading index columns' });
// const indexcols = this.driver.__analyserInternals.skipIndexes
// ? { rows: [] }
// : await this.driver.query(this.pool, this.createQuery('indexcols', ['tables']));
this.feedback({ analysingMessage: 'Loading unique names' }); this.feedback({ analysingMessage: 'Loading unique names' });
const uniqueNames = await this.analyserQuery('uniqueNames', ['tables']); const uniqueNames = await this.analyserQuery('uniqueNames', ['tables'], { $owner: this.pool._schema_name });
this.feedback({ analysingMessage: 'Finalizing DB structure' }); this.feedback({ analysingMessage: 'Finalizing DB structure' });
const fkColumnsMapped = fkColumns.rows.map(x => ({ const fkColumnsMapped = fkColumns.rows.map(x => ({

View File

@@ -7,7 +7,6 @@ const Analyser = require('./Analyser');
const oracledb = require('oracledb'); const oracledb = require('oracledb');
const { createBulkInsertStreamBase, makeUniqueColumnNames } = require('dbgate-tools'); const { createBulkInsertStreamBase, makeUniqueColumnNames } = require('dbgate-tools');
/* /*
pg.types.setTypeParser(1082, 'text', val => val); // date pg.types.setTypeParser(1082, 'text', val => val); // date
pg.types.setTypeParser(1114, 'text', val => val); // timestamp without timezone pg.types.setTypeParser(1114, 'text', val => val); // timestamp without timezone
@@ -47,6 +46,7 @@ const drivers = driverBases.map(driverBase => ({
database, database,
databaseUrl, databaseUrl,
useDatabaseUrl, useDatabaseUrl,
serviceName,
ssl, ssl,
isReadOnly, isReadOnly,
authType, authType,
@@ -55,8 +55,9 @@ const drivers = driverBases.map(driverBase => ({
client = await oracledb.getConnection({ client = await oracledb.getConnection({
user, user,
password, password,
connectString: useDatabaseUrl ? databaseUrl : port ? `${server}:${port}` : server, connectString: useDatabaseUrl ? databaseUrl : port ? `${server}:${port}/${serviceName}` : server,
}); });
client._schema_name = database;
return client; return client;
}, },
async close(pool) { async close(pool) {
@@ -64,7 +65,7 @@ const drivers = driverBases.map(driverBase => ({
}, },
async query(client, sql) { async query(client, sql) {
//console.log('query sql', sql); //console.log('query sql', sql);
if (sql == null) { if (sql == null) {a
return { return {
rows: [], rows: [],
columns: [], columns: [],
@@ -250,12 +251,12 @@ const drivers = driverBases.map(driverBase => ({
return pass; return pass;
}, },
async writeTable(pool, name, options) { async writeTable(pootl, name, options) {
// @ts-ignore // @ts-ignore
return createBulkInsertStreamBase(this, stream, pool, name, options); return createBulkInsertStreamBase(this, stream, pool, name, options);
}, },
async listDatabases(client) { async listDatabases(client) {
const { rows } = await this.query(client, 'SELECT instance_name AS "name" FROM v$instance'); const { rows } = await this.query(client, 'SELECT username as "name" from all_users order by username');
return rows; return rows;
}, },

View File

@@ -10,6 +10,6 @@ select
data_scale as "numeric_scale", data_scale as "numeric_scale",
data_default as "default_value" data_default as "default_value"
FROM all_tab_columns av FROM all_tab_columns av
where TABLE_NAME =OBJECT_ID_CONDITION where OWNER='$owner' AND TABLE_NAME =OBJECT_ID_CONDITION
order by column_id order by column_id
`; `;

View File

@@ -10,7 +10,7 @@ select fk.constraint_name as "constraint_name",
basecol.column_name as "column_name", basecol.column_name as "column_name",
refcol.column_name as "ref_column_name" refcol.column_name as "ref_column_name"
from all_cons_columns refcol, all_cons_columns basecol, all_constraints ref, all_constraints fk from all_cons_columns refcol, all_cons_columns basecol, all_constraints ref, all_constraints fk
where fk.constraint_type = 'R' where fk.OWNER = '$owner' AND fk.constraint_type = 'R'
and ref.owner = fk.r_owner and ref.owner = fk.r_owner
and ref.constraint_name = fk.r_constraint_name and ref.constraint_name = fk.r_constraint_name
and basecol.owner = fk.owner and basecol.owner = fk.owner

View File

@@ -8,7 +8,7 @@ select i.table_name as "tableName",
ic.column_position as "postion", ic.column_position as "postion",
ic.descend as "descending" ic.descend as "descending"
from all_ind_columns ic, all_indexes i from all_ind_columns ic, all_indexes i
where ic.index_owner = i.owner where INDEX_OWNER = '$owner' AND ic.index_owner = i.owner
and ic.index_name = i.index_name and ic.index_name = i.index_name
and i.index_name =OBJECT_ID_CONDITION and i.index_name =OBJECT_ID_CONDITION
order by i.table_owner, order by i.table_owner,

View File

@@ -4,6 +4,6 @@ SELECT owner "schema_name"
, column_name "column_name" , column_name "column_name"
, data_type "data_type" , data_type "data_type"
FROM all_tab_columns av FROM all_tab_columns av
where table_name =OBJECT_ID_CONDITION where OWNER = '$owner' AND table_name =OBJECT_ID_CONDITION
order by column_id order by column_id
`; `;

View File

@@ -14,6 +14,6 @@ SELECT owner as schema_name,
'//text()' '//text()'
)) definition )) definition
FROM all_mviews FROM all_mviews
where mview_name=OBJECT_ID_CONDITION where OWNER = '$owner' AND mview_name=OBJECT_ID_CONDITION
order by owner, mview_name order by owner, mview_name
`; `;

View File

@@ -32,6 +32,7 @@ from (select
all_procedures ap, all_procedures ap,
all_objects ao all_objects ao
where where
ap.owner = '$owner' and
ap.owner = ao.owner and ap.owner = ao.owner and
ap.object_name = ao.object_name and ap.object_name = ao.object_name and
ao.object_type in ('PACKAGE', 'PROCEDURE', 'FUNCTION') ao.object_type in ('PACKAGE', 'PROCEDURE', 'FUNCTION')

View File

@@ -4,6 +4,6 @@ select
table_name "pure_name" table_name "pure_name"
from from
all_tables all_tables
where TABLE_NAME =OBJECT_ID_CONDITION where OWNER='$owner' AND TABLE_NAME =OBJECT_ID_CONDITION
`; `;

View File

@@ -1,6 +1,6 @@
module.exports = ` module.exports = `
select constraint_name select constraint_name
from all_constraints from all_constraints
where constraint_type = 'U' where OWNER='$owner' and constraint_type = 'U'
and constraint_name =OBJECT_ID_CONDITION and constraint_name =OBJECT_ID_CONDITION
`; `;

View File

@@ -6,7 +6,7 @@ from (select
owner as "schema_name", owner as "schema_name",
SUBSTR(text_vc, 1, 3900) AS "create_sql" SUBSTR(text_vc, 1, 3900) AS "create_sql"
from all_views av from all_views av
where text_vc is not null where owner = '$owner' and text_vc is not null
) avv ) avv
where "pure_name" =OBJECT_ID_CONDITION where "pure_name" =OBJECT_ID_CONDITION
`; `;

View File

@@ -19,7 +19,6 @@ const dialect = {
quoteIdentifier(s) { quoteIdentifier(s) {
return '"' + s + '"'; return '"' + s + '"';
}, },
stringAgg: true,
createColumn: true, createColumn: true,
dropColumn: true, dropColumn: true,
@@ -110,32 +109,15 @@ const oracleDriverBase = {
getQuerySplitterOptions: () => oracleSplitterOptions, getQuerySplitterOptions: () => oracleSplitterOptions,
readOnlySessions: true, readOnlySessions: true,
databaseUrlPlaceholder: 'e.g. oracledb://user:password@localhost:1521', databaseUrlPlaceholder: 'e.g. localhost:1521/orcl',
showConnectionField: (field, values) => { showConnectionField: (field, values) => {
if (field == 'useDatabaseUrl') return true; if (field == 'useDatabaseUrl') return true;
if (values.useDatabaseUrl) { if (values.useDatabaseUrl) {
return ['databaseUrl', 'isReadOnly'].includes(field); return ['databaseUrl', 'user', 'password'].includes(field);
} }
return ['user', 'password', 'defaultDatabase', 'singleDatabase', 'isReadOnly', 'server', 'port'].includes(field); return ['user', 'password', 'server', 'port', 'serviceName'].includes(field);
},
beforeConnectionSave: connection => {
const { databaseUrl } = connection;
if (databaseUrl) {
const m = databaseUrl.match(/\/([^/]+)($|\?)/);
return {
...connection,
singleDatabase: !!m,
defaultDatabase: m ? m[1] : null,
};
}
return connection;
},
__analyserInternals: {
refTableCond: '',
}, },
getNewObjectTemplates() { getNewObjectTemplates() {
@@ -189,7 +171,7 @@ const oracleDriver = {
return dialect; return dialect;
}, },
showConnectionTab: (field) => field == 'sshTunnel', showConnectionTab: field => field == 'sshTunnel',
}; };
module.exports = [oracleDriver]; module.exports = [oracleDriver];

View File

@@ -70,29 +70,23 @@ class Analyser extends DatabaseAnalyser {
const tables = await this.analyserQuery(this.driver.dialect.stringAgg ? 'tableModifications' : 'tableList', [ const tables = await this.analyserQuery(this.driver.dialect.stringAgg ? 'tableModifications' : 'tableList', [
'tables', 'tables',
]); ]);
this.logger.debug({ count: tables.rows.length }, 'Tables loaded');
this.feedback({ analysingMessage: 'Loading columns' }); this.feedback({ analysingMessage: 'Loading columns' });
const columns = await this.analyserQuery('columns', ['tables', 'views']); const columns = await this.analyserQuery('columns', ['tables', 'views']);
this.logger.debug({ count: columns.rows.length }, 'Columns loaded');
this.feedback({ analysingMessage: 'Loading primary keys' }); this.feedback({ analysingMessage: 'Loading primary keys' });
const pkColumns = await this.analyserQuery('primaryKeys', ['tables']); const pkColumns = await this.analyserQuery('primaryKeys', ['tables']);
this.logger.debug({ count: pkColumns.rows.length }, 'Primary keys loaded');
let fkColumns = null; let fkColumns = null;
this.feedback({ analysingMessage: 'Loading foreign key constraints' }); this.feedback({ analysingMessage: 'Loading foreign key constraints' });
const fk_tableConstraints = await this.analyserQuery('fk_tableConstraints', ['tables']); const fk_tableConstraints = await this.analyserQuery('fk_tableConstraints', ['tables']);
this.logger.debug({ count: fk_tableConstraints.rows.length }, 'Foreign keys loaded');
this.feedback({ analysingMessage: 'Loading foreign key refs' }); this.feedback({ analysingMessage: 'Loading foreign key refs' });
const fk_referentialConstraints = await this.analyserQuery('fk_referentialConstraints', ['tables']); const fk_referentialConstraints = await this.analyserQuery('fk_referentialConstraints', ['tables']);
this.logger.debug({ count: fk_referentialConstraints.rows.length }, 'Foreign key refs loaded');
this.feedback({ analysingMessage: 'Loading foreign key columns' }); this.feedback({ analysingMessage: 'Loading foreign key columns' });
const fk_keyColumnUsage = await this.analyserQuery('fk_keyColumnUsage', ['tables']); const fk_keyColumnUsage = await this.analyserQuery('fk_keyColumnUsage', ['tables']);
this.logger.debug({ count: fk_keyColumnUsage.rows.length }, 'Foreign key columns loaded');
const cntKey = x => `${x.constraint_name}|${x.constraint_schema}`; const cntKey = x => `${x.constraint_name}|${x.constraint_schema}`;
const fkRows = []; const fkRows = [];
@@ -134,50 +128,41 @@ class Analyser extends DatabaseAnalyser {
this.feedback({ analysingMessage: 'Loading views' }); this.feedback({ analysingMessage: 'Loading views' });
const views = await this.analyserQuery('views', ['views']); const views = await this.analyserQuery('views', ['views']);
this.logger.debug({ count: views.rows.length }, 'Views loaded');
this.feedback({ analysingMessage: 'Loading materialized views' }); this.feedback({ analysingMessage: 'Loading materialized views' });
const matviews = this.driver.dialect.materializedViews ? await this.analyserQuery('matviews', ['matviews']) : null; const matviews = this.driver.dialect.materializedViews ? await this.analyserQuery('matviews', ['matviews']) : null;
this.logger.debug({ count: matviews.rows.length }, 'Materialized views loaded');
this.feedback({ analysingMessage: 'Loading materialized view columns' }); this.feedback({ analysingMessage: 'Loading materialized view columns' });
const matviewColumns = this.driver.dialect.materializedViews const matviewColumns = this.driver.dialect.materializedViews
? await this.analyserQuery('matviewColumns', ['matviews']) ? await this.analyserQuery('matviewColumns', ['matviews'])
: null; : null;
this.logger.debug({ count: matviewColumns.rows.length }, 'Materialized view columns loaded');
this.feedback({ analysingMessage: 'Loading routines' }); this.feedback({ analysingMessage: 'Loading routines' });
const routines = await this.analyserQuery('routines', ['procedures', 'functions']); const routines = await this.analyserQuery('routines', ['procedures', 'functions']);
this.logger.debug({ count: routines.rows.length }, 'Routines loaded');
this.feedback({ analysingMessage: 'Loading indexes' }); this.feedback({ analysingMessage: 'Loading indexes' });
const indexes = this.driver.__analyserInternals.skipIndexes const indexes = this.driver.__analyserInternals.skipIndexes
? { rows: [] } ? { rows: [] }
: await this.analyserQuery('indexes', ['tables']); : await this.analyserQuery('indexes', ['tables']);
this.logger.debug({ count: indexes.rows.length }, 'Indexes loaded');
this.feedback({ analysingMessage: 'Loading index columns' }); this.feedback({ analysingMessage: 'Loading index columns' });
const indexcols = this.driver.__analyserInternals.skipIndexes const indexcols = this.driver.__analyserInternals.skipIndexes
? { rows: [] } ? { rows: [] }
: await this.analyserQuery('indexcols', ['tables']); : await this.analyserQuery('indexcols', ['tables']);
this.logger.debug({ count: indexcols.rows.length }, 'Indexes columns loaded');
this.feedback({ analysingMessage: 'Loading unique names' }); this.feedback({ analysingMessage: 'Loading unique names' });
const uniqueNames = await this.analyserQuery('uniqueNames', ['tables']); const uniqueNames = await this.analyserQuery('uniqueNames', ['tables']);
this.logger.debug({ count: uniqueNames.rows.length }, 'Uniques loaded');
let geometryColumns = { rows: [] }; let geometryColumns = { rows: [] };
if (views.rows.find(x => x.pure_name == 'geometry_columns' && x.schema_name == 'public')) { if (views.rows.find(x => x.pure_name == 'geometry_columns' && x.schema_name == 'public')) {
this.feedback({ analysingMessage: 'Loading geometry columns' }); this.feedback({ analysingMessage: 'Loading geometry columns' });
geometryColumns = await this.analyserQuery('geometryColumns', ['tables']); geometryColumns = await this.analyserQuery('geometryColumns', ['tables']);
this.logger.debug({ count: geometryColumns.rows.length }, 'Geometry columns loaded');
} }
let geographyColumns = { rows: [] }; let geographyColumns = { rows: [] };
if (views.rows.find(x => x.pure_name == 'geography_columns' && x.schema_name == 'public')) { if (views.rows.find(x => x.pure_name == 'geography_columns' && x.schema_name == 'public')) {
this.feedback({ analysingMessage: 'Loading geography columns' }); this.feedback({ analysingMessage: 'Loading geography columns' });
geographyColumns = await this.analyserQuery('geographyColumns', ['tables']); geographyColumns = await this.analyserQuery('geographyColumns', ['tables']);
this.logger.debug({ count: geographyColumns.rows.length }, 'Geography columns loaded');
} }
this.feedback({ analysingMessage: 'Finalizing DB structure' }); this.feedback({ analysingMessage: 'Finalizing DB structure' });