mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
fix: fix object comparison util method used in DB alteration CI (#6562)
This commit is contained in:
parent
862d27dee4
commit
8b19004102
1 changed files with 84 additions and 26 deletions
|
@ -1,8 +1,12 @@
|
||||||
import pg from 'pg';
|
import pg from 'pg';
|
||||||
import assert from 'node:assert';
|
import assert from 'node:assert';
|
||||||
|
|
||||||
const omit = (object, ...keys) => Object.fromEntries(Object.entries(object).filter(([key]) => !keys.includes(key)));
|
const omit = (object, ...keys) =>
|
||||||
const omitArray = (arrayOfObjects, ...keys) => arrayOfObjects.map((value) => omit(value, ...keys));
|
Object.fromEntries(
|
||||||
|
Object.entries(object).filter(([key]) => !keys.includes(key))
|
||||||
|
);
|
||||||
|
const omitArray = (arrayOfObjects, ...keys) =>
|
||||||
|
arrayOfObjects.map((value) => omit(value, ...keys));
|
||||||
|
|
||||||
const schemas = ['cloud', 'public'];
|
const schemas = ['cloud', 'public'];
|
||||||
const schemasArray = `(${schemas.map((schema) => `'${schema}'`).join(', ')})`;
|
const schemasArray = `(${schemas.map((schema) => `'${schema}'`).join(', ')})`;
|
||||||
|
@ -17,23 +21,27 @@ const tryCompare = (a, b) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const queryDatabaseManifest = async (database) => {
|
const queryDatabaseManifest = async (database) => {
|
||||||
const pool = new pg.Pool({ database, user: 'postgres', password: 'postgres' });
|
const pool = new pg.Pool({
|
||||||
|
database,
|
||||||
|
user: 'postgres',
|
||||||
|
password: 'postgres',
|
||||||
|
});
|
||||||
|
|
||||||
const { rows: tables } = await pool.query(/* sql */`
|
const { rows: tables } = await pool.query(/* sql */ `
|
||||||
select *
|
select *
|
||||||
from information_schema.tables
|
from information_schema.tables
|
||||||
where table_schema in ${schemasArray}
|
where table_schema in ${schemasArray}
|
||||||
order by table_schema, table_name asc;
|
order by table_schema, table_name asc;
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const { rows: columns } = await pool.query(/* sql */`
|
const { rows: columns } = await pool.query(/* sql */ `
|
||||||
select *
|
select *
|
||||||
from information_schema.columns
|
from information_schema.columns
|
||||||
where table_schema in ${schemasArray}
|
where table_schema in ${schemasArray}
|
||||||
order by table_schema, table_name, column_name asc;
|
order by table_schema, table_name, column_name asc;
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const { rows: enums } = await pool.query(/* sql */`
|
const { rows: enums } = await pool.query(/* sql */ `
|
||||||
select pg_type.typname, pg_enum.enumlabel
|
select pg_type.typname, pg_enum.enumlabel
|
||||||
from pg_type
|
from pg_type
|
||||||
join pg_enum
|
join pg_enum
|
||||||
|
@ -41,7 +49,7 @@ const queryDatabaseManifest = async (database) => {
|
||||||
order by pg_type.typname, pg_enum.enumlabel asc;
|
order by pg_type.typname, pg_enum.enumlabel asc;
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const { rows: constraints } = await pool.query(/* sql */`
|
const { rows: constraints } = await pool.query(/* sql */ `
|
||||||
select conrelid::regclass as r_table, con.*, pg_get_constraintdef(con.oid) as def
|
select conrelid::regclass as r_table, con.*, pg_get_constraintdef(con.oid) as def
|
||||||
from pg_catalog.pg_constraint con
|
from pg_catalog.pg_constraint con
|
||||||
inner join pg_catalog.pg_class rel
|
inner join pg_catalog.pg_class rel
|
||||||
|
@ -52,14 +60,14 @@ const queryDatabaseManifest = async (database) => {
|
||||||
order by conname asc, def asc;
|
order by conname asc, def asc;
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const { rows: indexes } = await pool.query(/* sql */`
|
const { rows: indexes } = await pool.query(/* sql */ `
|
||||||
select *
|
select *
|
||||||
from pg_indexes
|
from pg_indexes
|
||||||
where schemaname in ${schemasArray}
|
where schemaname in ${schemasArray}
|
||||||
order by schemaname, indexname asc;
|
order by schemaname, indexname asc;
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const { rows: funcs } = await pool.query(/* sql */`
|
const { rows: funcs } = await pool.query(/* sql */ `
|
||||||
select n.nspname as schema_name,
|
select n.nspname as schema_name,
|
||||||
p.proname as specific_name,
|
p.proname as specific_name,
|
||||||
case p.prokind
|
case p.prokind
|
||||||
|
@ -83,15 +91,19 @@ const queryDatabaseManifest = async (database) => {
|
||||||
order by schema_name, specific_name;
|
order by schema_name, specific_name;
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const { rows: triggers } = await pool.query(/* sql */`select * from information_schema.triggers;`);
|
const { rows: triggers } = await pool.query(
|
||||||
const { rows: policies } = await pool.query(/* sql */`select * from pg_policies order by tablename, policyname;`);
|
/* sql */ `select * from information_schema.triggers;`
|
||||||
const { rows: columnGrants } = await pool.query(/* sql */`
|
);
|
||||||
|
const { rows: policies } = await pool.query(
|
||||||
|
/* sql */ `select * from pg_policies order by tablename, policyname;`
|
||||||
|
);
|
||||||
|
const { rows: columnGrants } = await pool.query(/* sql */ `
|
||||||
select * from information_schema.role_column_grants
|
select * from information_schema.role_column_grants
|
||||||
where table_schema in ${schemasArray}
|
where table_schema in ${schemasArray}
|
||||||
and grantee != 'postgres'
|
and grantee != 'postgres'
|
||||||
order by table_schema, grantee, table_name, column_name, privilege_type;
|
order by table_schema, grantee, table_name, column_name, privilege_type;
|
||||||
`);
|
`);
|
||||||
const { rows: tableGrants } = await pool.query(/* sql */`
|
const { rows: tableGrants } = await pool.query(/* sql */ `
|
||||||
select * from information_schema.role_table_grants
|
select * from information_schema.role_table_grants
|
||||||
where table_schema in ${schemasArray}
|
where table_schema in ${schemasArray}
|
||||||
and grantee != 'postgres'
|
and grantee != 'postgres'
|
||||||
|
@ -130,10 +142,19 @@ const queryDatabaseManifest = async (database) => {
|
||||||
const normalizePolicyname = ({ policyname, ...rest }) => {
|
const normalizePolicyname = ({ policyname, ...rest }) => {
|
||||||
const prefix = 'allow_';
|
const prefix = 'allow_';
|
||||||
const suffix = '_access';
|
const suffix = '_access';
|
||||||
if (policyname && policyname.startsWith(prefix) && policyname.endsWith(suffix)) {
|
if (
|
||||||
|
policyname &&
|
||||||
|
policyname.startsWith(prefix) &&
|
||||||
|
policyname.endsWith(suffix)
|
||||||
|
) {
|
||||||
// This is a naming convention in Logto cloud, it is formatted as `allow_{role_name}_access`, we need to normalize the role name part for the convenience of comparing DB updates.
|
// This is a naming convention in Logto cloud, it is formatted as `allow_{role_name}_access`, we need to normalize the role name part for the convenience of comparing DB updates.
|
||||||
// Ref: https://github.com/logto-io/cloud/pull/738
|
// Ref: https://github.com/logto-io/cloud/pull/738
|
||||||
return { policyname: `${prefix}${normalizeRoleName(policyname.slice(prefix.length, -suffix.length))}${suffix}`, ...rest };
|
return {
|
||||||
|
policyname: `${prefix}${normalizeRoleName(
|
||||||
|
policyname.slice(prefix.length, -suffix.length)
|
||||||
|
)}${suffix}`,
|
||||||
|
...rest,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return { policyname, ...rest };
|
return { policyname, ...rest };
|
||||||
|
@ -142,12 +163,18 @@ const queryDatabaseManifest = async (database) => {
|
||||||
// Omit generated ids and values
|
// Omit generated ids and values
|
||||||
return {
|
return {
|
||||||
tables: omitArray(tables, 'table_catalog'),
|
tables: omitArray(tables, 'table_catalog'),
|
||||||
columns: omitArray(columns, 'table_catalog', 'udt_catalog', 'ordinal_position', 'dtd_identifier'),
|
columns: omitArray(
|
||||||
|
columns,
|
||||||
|
'table_catalog',
|
||||||
|
'udt_catalog',
|
||||||
|
'ordinal_position',
|
||||||
|
'dtd_identifier'
|
||||||
|
),
|
||||||
enums,
|
enums,
|
||||||
constraints: omitArray(
|
constraints: omitArray(
|
||||||
constraints,
|
constraints,
|
||||||
'oid',
|
'oid',
|
||||||
/**
|
/**
|
||||||
* See https://www.postgresql.org/docs/current/catalog-pg-constraint.html, better to use `pg_get_constraintdef()`
|
* See https://www.postgresql.org/docs/current/catalog-pg-constraint.html, better to use `pg_get_constraintdef()`
|
||||||
* to extract the definition of check constraint, so this can be omitted since conbin changes with the status of the computing resources.
|
* to extract the definition of check constraint, so this can be omitted since conbin changes with the status of the computing resources.
|
||||||
*/
|
*/
|
||||||
|
@ -164,18 +191,20 @@ const queryDatabaseManifest = async (database) => {
|
||||||
'conppeqop',
|
'conppeqop',
|
||||||
'conffeqop',
|
'conffeqop',
|
||||||
'confdelsetcols',
|
'confdelsetcols',
|
||||||
'conexclop',
|
'conexclop'
|
||||||
),
|
),
|
||||||
indexes,
|
indexes,
|
||||||
funcs,
|
funcs,
|
||||||
triggers: omitArray(triggers, 'trigger_catalog', 'event_object_catalog'),
|
triggers: omitArray(triggers, 'trigger_catalog', 'event_object_catalog'),
|
||||||
policies: policies.map(normalizeRoles).map(normalizePolicyname),
|
policies: policies.map(normalizeRoles).map(normalizePolicyname),
|
||||||
columnGrants: omitArray(columnGrants, 'table_catalog').map(normalizeGrantee),
|
columnGrants: omitArray(columnGrants, 'table_catalog').map(
|
||||||
|
normalizeGrantee
|
||||||
|
),
|
||||||
tableGrants: omitArray(tableGrants, 'table_catalog').map(normalizeGrantee),
|
tableGrants: omitArray(tableGrants, 'table_catalog').map(normalizeGrantee),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const [,, database1, database2] = process.argv;
|
const [, , database1, database2] = process.argv;
|
||||||
|
|
||||||
console.log('Compare database manifest between', database1, 'and', database2);
|
console.log('Compare database manifest between', database1, 'and', database2);
|
||||||
|
|
||||||
|
@ -191,6 +220,23 @@ const autoCompare = (a, b) => {
|
||||||
return (typeof a).localeCompare(typeof b);
|
return (typeof a).localeCompare(typeof b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof a === 'object' && a !== null && b !== null) {
|
||||||
|
const aKeys = Object.keys(a).sort();
|
||||||
|
const bKeys = Object.keys(b).sort();
|
||||||
|
|
||||||
|
for (let i = 0; i < Math.min(aKeys.length, bKeys.length); i++) {
|
||||||
|
if (aKeys[i] !== bKeys[i]) {
|
||||||
|
return aKeys[i].localeCompare(bKeys[i]);
|
||||||
|
}
|
||||||
|
const comparison = autoCompare(a[aKeys[i]], b[bKeys[i]]);
|
||||||
|
if (comparison !== 0) {
|
||||||
|
return comparison;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return aKeys.length - bKeys.length;
|
||||||
|
}
|
||||||
|
|
||||||
return String(a).localeCompare(String(b));
|
return String(a).localeCompare(String(b));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -200,15 +246,24 @@ const buildSortByKeys = (keys) => (a, b) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const queryDatabaseData = async (database) => {
|
const queryDatabaseData = async (database) => {
|
||||||
const pool = new pg.Pool({ database, user: 'postgres', password: 'postgres' });
|
const pool = new pg.Pool({
|
||||||
const result = await Promise.all(manifests[0].tables
|
database,
|
||||||
.map(async ({ table_schema, table_name }) => {
|
user: 'postgres',
|
||||||
const { rows } = await pool.query(/* sql */`select * from ${table_schema}.${table_name};`);
|
password: 'postgres',
|
||||||
|
});
|
||||||
|
const result = await Promise.all(
|
||||||
|
manifests[0].tables.map(async ({ table_schema, table_name }) => {
|
||||||
|
const { rows } = await pool.query(
|
||||||
|
/* sql */ `select * from ${table_schema}.${table_name};`
|
||||||
|
);
|
||||||
|
|
||||||
// check config rows except the value column
|
// check config rows except the value column
|
||||||
if (['logto_configs', '_logto_configs', 'systems'].includes(table_name)) {
|
if (['logto_configs', '_logto_configs', 'systems'].includes(table_name)) {
|
||||||
const data = omitArray(rows, 'value');
|
const data = omitArray(rows, 'value');
|
||||||
return [table_name, data.sort(buildSortByKeys(Object.keys(data[0] ?? {})))];
|
return [
|
||||||
|
table_name,
|
||||||
|
data.sort(buildSortByKeys(Object.keys(data[0] ?? {}))),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = omitArray(
|
const data = omitArray(
|
||||||
|
@ -234,4 +289,7 @@ const queryDatabaseData = async (database) => {
|
||||||
|
|
||||||
console.log('Compare database data between', database1, 'and', database2);
|
console.log('Compare database data between', database1, 'and', database2);
|
||||||
|
|
||||||
tryCompare(await queryDatabaseData(database1), await queryDatabaseData(database2));
|
tryCompare(
|
||||||
|
await queryDatabaseData(database1),
|
||||||
|
await queryDatabaseData(database2)
|
||||||
|
);
|
||||||
|
|
Loading…
Reference in a new issue