Skip to content

Commit

Permalink
Tidier property passing (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
plumdog authored Oct 19, 2021
1 parent b5a1265 commit 0d2d733
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 89 deletions.
166 changes: 85 additions & 81 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
// Maybe split to have {Role,Database}Properties and
// {Role,Database}Options. The former for actual things that get set
// in PG, the latter for things about how this client handles changes.
// Maybe add {Role,Database}Options for things about how this client handles changes.

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type QueryResultRow = Record<string, any>;
Expand All @@ -16,7 +14,7 @@ export interface Config {
useTransactions?: boolean;
}

interface DatabaseOptions {
interface DatabaseProperties {
owner?: string;
template?: string;
encoding?: string;
Expand All @@ -26,11 +24,12 @@ interface DatabaseOptions {
connectionLimit?: number;
}

export interface Database extends DatabaseOptions {
export interface Database {
name: string;
properties?: DatabaseProperties;
}

interface RoleOptions {
interface RoleProperties {
isSuperuser?: boolean;
canCreateDb?: boolean;
canCreateRole?: boolean;
Expand All @@ -45,8 +44,9 @@ interface RoleOptions {
adminRoles?: Array<string>;
}

export interface Role extends RoleOptions {
export interface Role {
name: string;
properties?: RoleProperties;
}

interface ExecuteQueriesProps {
Expand Down Expand Up @@ -86,35 +86,38 @@ export const pg_applier = (config: Config): PgApplier => ({
const result = await config.query(
`SELECT *, pg_encoding_to_char(encoding) as encoding_name, rolname as owner_role_name, spcname as tablespace_name FROM pg_database JOIN pg_authid ON pg_database.datdba = pg_authid.oid JOIN pg_tablespace ON pg_database.dattablespace = pg_tablespace.oid WHERE datname = '${database.name}'`,
);

const { properties = {} } = database;

if (result.rows.length) {
// Database already exists

const currentDbConfig = result.rows[0];

const modifyChanges: DatabaseOptions = {};
const modifyChanges: DatabaseProperties = {};

if (database.owner && currentDbConfig.owner_role_name !== database.owner) {
modifyChanges.owner = database.owner;
if (properties.owner && currentDbConfig.owner_role_name !== properties.owner) {
modifyChanges.owner = properties.owner;
}

if (database.encoding && currentDbConfig.encoding_name !== database.encoding) {
modifyChanges.encoding = database.encoding;
if (properties.encoding && currentDbConfig.encoding_name !== properties.encoding) {
modifyChanges.encoding = properties.encoding;
}

if (database.lcCollate && currentDbConfig.datcollate !== database.lcCollate) {
modifyChanges.lcCollate = database.lcCollate;
if (properties.lcCollate && currentDbConfig.datcollate !== properties.lcCollate) {
modifyChanges.lcCollate = properties.lcCollate;
}

if (database.lcCtype && currentDbConfig.datctype !== database.lcCtype) {
modifyChanges.lcCtype = database.lcCtype;
if (properties.lcCtype && currentDbConfig.datctype !== properties.lcCtype) {
modifyChanges.lcCtype = properties.lcCtype;
}

if (database.tablespace && currentDbConfig.tablespace_name !== database.tablespace) {
modifyChanges.tablespace = database.tablespace;
if (properties.tablespace && currentDbConfig.tablespace_name !== properties.tablespace) {
modifyChanges.tablespace = properties.tablespace;
}

if (database.connectionLimit && currentDbConfig.datconnlimit !== database.connectionLimit) {
modifyChanges.connectionLimit = database.connectionLimit;
if (properties.connectionLimit && currentDbConfig.datconnlimit !== properties.connectionLimit) {
modifyChanges.connectionLimit = properties.connectionLimit;
}

// Check for invalid changes
Expand All @@ -134,15 +137,15 @@ export const pg_applier = (config: Config): PgApplier => ({
const queries: Array<string> = [];
// Apply changes
if (modifyChanges.owner) {
queries.push(`ALTER DATABASE ${database.name} OWNER TO ${database.owner}`);
queries.push(`ALTER DATABASE ${database.name} OWNER TO ${properties.owner}`);
}

if (modifyChanges.tablespace) {
queries.push(`ALTER DATABASE ${database.name} TABLESPACE ${database.tablespace}`);
queries.push(`ALTER DATABASE ${database.name} TABLESPACE ${properties.tablespace}`);
}

if (modifyChanges.connectionLimit) {
queries.push(`ALTER DATABASE ${database.name} CONNECTION LIMIT ${database.connectionLimit}`);
queries.push(`ALTER DATABASE ${database.name} CONNECTION LIMIT ${properties.connectionLimit}`);
}

await executeQueries({
Expand All @@ -155,26 +158,26 @@ export const pg_applier = (config: Config): PgApplier => ({

const createConfig: Array<string> = [];

if (database.owner) {
createConfig.push(`OWNER = ${database.owner}`);
if (properties.owner) {
createConfig.push(`OWNER = ${properties.owner}`);
}
if (database.template) {
createConfig.push(`TEMPLATE = ${database.template}`);
if (properties.template) {
createConfig.push(`TEMPLATE = ${properties.template}`);
}
if (database.encoding) {
createConfig.push(`ENCODING = ${database.encoding}`);
if (properties.encoding) {
createConfig.push(`ENCODING = ${properties.encoding}`);
}
if (database.lcCollate) {
createConfig.push(`LC_COLLATE = ${database.lcCollate}`);
if (properties.lcCollate) {
createConfig.push(`LC_COLLATE = ${properties.lcCollate}`);
}
if (database.lcCtype) {
createConfig.push(`LC_CTYPE = ${database.lcCtype}`);
if (properties.lcCtype) {
createConfig.push(`LC_CTYPE = ${properties.lcCtype}`);
}
if (database.tablespace) {
createConfig.push(`TABLESPACE = ${database.tablespace}`);
if (properties.tablespace) {
createConfig.push(`TABLESPACE = ${properties.tablespace}`);
}
if (database.connectionLimit) {
createConfig.push(`CONNECTION LIMIT = ${database.connectionLimit}`);
if (properties.connectionLimit) {
createConfig.push(`CONNECTION LIMIT = ${properties.connectionLimit}`);
}

const configQueryFragment = createConfig.join(' ');
Expand All @@ -192,38 +195,39 @@ export const pg_applier = (config: Config): PgApplier => ({

async role(role: Role): Promise<void> {
const result = await config.query(`SELECT * FROM pg_roles WHERE rolname = '${role.name}'`);
const { properties = {} } = role;
if (result.rows.length) {
// Already exists
const currentRoleConfig = result.rows[0];
const modifyChanges: RoleOptions = {};
const modifyChanges: RoleProperties = {};

if (typeof role.isSuperuser !== 'undefined' && currentRoleConfig.rolsuper !== role.isSuperuser) {
modifyChanges.isSuperuser = role.isSuperuser;
if (typeof properties.isSuperuser !== 'undefined' && currentRoleConfig.rolsuper !== properties.isSuperuser) {
modifyChanges.isSuperuser = properties.isSuperuser;
}
if (typeof role.canCreateDb !== 'undefined' && currentRoleConfig.rolcreatedb !== role.canCreateDb) {
modifyChanges.canCreateDb = role.canCreateDb;
if (typeof properties.canCreateDb !== 'undefined' && currentRoleConfig.rolcreatedb !== properties.canCreateDb) {
modifyChanges.canCreateDb = properties.canCreateDb;
}
if (typeof role.canCreateRole !== 'undefined' && currentRoleConfig.rolcreatedb !== role.canCreateRole) {
modifyChanges.canCreateRole = role.canCreateRole;
if (typeof properties.canCreateRole !== 'undefined' && currentRoleConfig.rolcreatedb !== properties.canCreateRole) {
modifyChanges.canCreateRole = properties.canCreateRole;
}
if (typeof role.inherit !== 'undefined' && currentRoleConfig.rolinherit !== role.inherit) {
modifyChanges.inherit = role.inherit;
if (typeof properties.inherit !== 'undefined' && currentRoleConfig.rolinherit !== properties.inherit) {
modifyChanges.inherit = properties.inherit;
}
if (typeof role.login !== 'undefined' && currentRoleConfig.rolcanlogin !== role.login) {
modifyChanges.login = role.login;
if (typeof properties.login !== 'undefined' && currentRoleConfig.rolcanlogin !== properties.login) {
modifyChanges.login = properties.login;
}
if (typeof role.connectionLimit !== 'undefined' && currentRoleConfig.rolconnlimit !== role.connectionLimit) {
modifyChanges.connectionLimit = role.connectionLimit;
if (typeof properties.connectionLimit !== 'undefined' && currentRoleConfig.rolconnlimit !== properties.connectionLimit) {
modifyChanges.connectionLimit = properties.connectionLimit;
}
// TODO join with pg_auth to check the password and whether it is encrypted
if (typeof role.password !== 'undefined') {
modifyChanges.password = role.password;
if (typeof role.passwordEncrypted !== 'undefined') {
modifyChanges.passwordEncrypted = role.passwordEncrypted;
if (typeof properties.password !== 'undefined') {
modifyChanges.password = properties.password;
if (typeof properties.passwordEncrypted !== 'undefined') {
modifyChanges.passwordEncrypted = properties.passwordEncrypted;
}
}
if (typeof role.passwordValidUntil !== 'undefined' && currentRoleConfig.rolvaliduntil !== role.passwordValidUntil) {
modifyChanges.passwordValidUntil = role.passwordValidUntil;
if (typeof properties.passwordValidUntil !== 'undefined' && currentRoleConfig.rolvaliduntil !== properties.passwordValidUntil) {
modifyChanges.passwordValidUntil = properties.passwordValidUntil;
}
// TODO: check pg_auth_members for inRoles, roles and adminRoles config

Expand All @@ -245,7 +249,7 @@ export const pg_applier = (config: Config): PgApplier => ({
queries.push(`ALTER ROLE ${role.name} ${modifyChanges.login ? '' : 'NO'}LOGIN`);
}
if (typeof modifyChanges.connectionLimit !== 'undefined') {
queries.push(`ALTER ROLE ${role.name} CONNECTION LIMIT ${role.connectionLimit}`);
queries.push(`ALTER ROLE ${role.name} CONNECTION LIMIT ${properties.connectionLimit}`);
}
if (typeof modifyChanges.password !== 'undefined') {
const encryptedQueryFragment = typeof modifyChanges.passwordEncrypted === 'undefined' ? '' : `${modifyChanges.passwordEncrypted ? '' : 'UN'}ENCRYPTED `;
Expand All @@ -263,41 +267,41 @@ export const pg_applier = (config: Config): PgApplier => ({

const configFragments: Array<string> = [];

if (typeof role.isSuperuser !== 'undefined') {
configFragments.push(`${role.isSuperuser ? '' : 'NO'}SUPERUSER`);
if (typeof properties.isSuperuser !== 'undefined') {
configFragments.push(`${properties.isSuperuser ? '' : 'NO'}SUPERUSER`);
}
if (typeof role.canCreateDb !== 'undefined') {
configFragments.push(`${role.canCreateDb ? '' : 'NO'}CREATEDB`);
if (typeof properties.canCreateDb !== 'undefined') {
configFragments.push(`${properties.canCreateDb ? '' : 'NO'}CREATEDB`);
}
if (typeof role.canCreateRole !== 'undefined') {
configFragments.push(`${role.canCreateRole ? '' : 'NO'}CREATEROLE`);
if (typeof properties.canCreateRole !== 'undefined') {
configFragments.push(`${properties.canCreateRole ? '' : 'NO'}CREATEROLE`);
}
if (typeof role.inherit !== 'undefined') {
configFragments.push(`${role.inherit ? '' : 'NO'}INHERIT`);
if (typeof properties.inherit !== 'undefined') {
configFragments.push(`${properties.inherit ? '' : 'NO'}INHERIT`);
}
if (typeof role.login !== 'undefined') {
configFragments.push(`${role.login ? '' : 'NO'}LOGIN`);
if (typeof properties.login !== 'undefined') {
configFragments.push(`${properties.login ? '' : 'NO'}LOGIN`);
}
if (typeof role.connectionLimit !== 'undefined') {
configFragments.push(`CONNECTION LIMIT ${role.connectionLimit}`);
if (typeof properties.connectionLimit !== 'undefined') {
configFragments.push(`CONNECTION LIMIT ${properties.connectionLimit}`);
}
if (typeof role.password !== 'undefined') {
if (typeof role.passwordEncrypted !== 'undefined') {
configFragments.push(`${role.passwordEncrypted ? '' : 'UN'}ENCRYPTED`);
if (typeof properties.password !== 'undefined') {
if (typeof properties.passwordEncrypted !== 'undefined') {
configFragments.push(`${properties.passwordEncrypted ? '' : 'UN'}ENCRYPTED`);
}
configFragments.push(`PASSWORD '${role.password}'`);
configFragments.push(`PASSWORD '${properties.password}'`);
}
if (typeof role.passwordValidUntil !== 'undefined') {
configFragments.push(`VALID UNTIL '${role.passwordValidUntil.toISOString()}'`);
if (typeof properties.passwordValidUntil !== 'undefined') {
configFragments.push(`VALID UNTIL '${properties.passwordValidUntil.toISOString()}'`);
}
if (typeof role.inRoles !== 'undefined' && role.inRoles.length > 0) {
configFragments.push(`IN ROLE ${role.inRoles.join(', ')}`);
if (typeof properties.inRoles !== 'undefined' && properties.inRoles.length > 0) {
configFragments.push(`IN ROLE ${properties.inRoles.join(', ')}`);
}
if (typeof role.roles !== 'undefined' && role.roles.length > 0) {
configFragments.push(`ROLE ${role.roles.join(', ')}`);
if (typeof properties.roles !== 'undefined' && properties.roles.length > 0) {
configFragments.push(`ROLE ${properties.roles.join(', ')}`);
}
if (typeof role.adminRoles !== 'undefined' && role.adminRoles.length > 0) {
configFragments.push(`ADMIN ${role.adminRoles.join(', ')}`);
if (typeof properties.adminRoles !== 'undefined' && properties.adminRoles.length > 0) {
configFragments.push(`ADMIN ${properties.adminRoles.join(', ')}`);
}

const query = `CREATE ROLE ${role.name} ${configFragments.join(' ')}`.trim();
Expand Down
32 changes: 24 additions & 8 deletions test/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,15 +119,19 @@ describe('database', () => {

await applier.database({
name: databaseName,
connectionLimit: 10,
properties: {
connectionLimit: 10,
},
});

const db = (await client.query(`SELECT * FROM pg_database WHERE datname = '${databaseName}'`)).rows[0];
expect(db.datconnlimit).toEqual(10);

await applier.database({
name: databaseName,
connectionLimit: 20,
properties: {
connectionLimit: 20,
},
});
const db2 = (await client.query(`SELECT * FROM pg_database WHERE datname = '${databaseName}'`)).rows[0];
expect(db2.datconnlimit).toEqual(20);
Expand All @@ -150,15 +154,19 @@ describe('database', () => {

await applier.database({
name: databaseName,
connectionLimit: 10,
properties: {
connectionLimit: 10,
},
});

const db = (await client.query(`SELECT * FROM pg_database WHERE datname = '${databaseName}'`)).rows[0];
expect(db.datconnlimit).toEqual(10);

await applier.database({
name: databaseName,
connectionLimit: 20,
properties: {
connectionLimit: 20,
},
});
const db2 = (await client.query(`SELECT * FROM pg_database WHERE datname = '${databaseName}'`)).rows[0];
expect(db2.datconnlimit).toEqual(20);
Expand Down Expand Up @@ -237,15 +245,19 @@ describe('role', () => {

await applier.role({
name: roleName,
connectionLimit: 10,
properties: {
connectionLimit: 10,
},
});

const role = (await client.query(`SELECT * FROM pg_roles WHERE rolname = '${roleName}'`)).rows[0];
expect(role.rolconnlimit).toEqual(10);

await applier.role({
name: roleName,
connectionLimit: 20,
properties: {
connectionLimit: 20,
},
});

const role2 = (await client.query(`SELECT * FROM pg_roles WHERE rolname = '${roleName}'`)).rows[0];
Expand All @@ -269,15 +281,19 @@ describe('role', () => {

await applier.role({
name: roleName,
connectionLimit: 10,
properties: {
connectionLimit: 10,
},
});

const role = (await client.query(`SELECT * FROM pg_roles WHERE rolname = '${roleName}'`)).rows[0];
expect(role.rolconnlimit).toEqual(10);

await applier.role({
name: roleName,
connectionLimit: 20,
properties: {
connectionLimit: 20,
},
});

const role2 = (await client.query(`SELECT * FROM pg_roles WHERE rolname = '${roleName}'`)).rows[0];
Expand Down

0 comments on commit 0d2d733

Please sign in to comment.