Skip to content

Commit

Permalink
Databases: Support postgres schema in dev environment.
Browse files Browse the repository at this point in the history
Fixes boxyhq#1818.
This commit adds postgres schema support to the app logic.
The dev environment uses synchronize function to create tables,
and does not run the explicit migrations. We will add schema support
for production in the next commit.
  • Loading branch information
shubham-padia committed Apr 6, 2024
1 parent 8b29dbf commit 68d44ba
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 5 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ DB_PAGE_LIMIT=50
DB_ENCRYPTION_KEY=
# Uncomment below if you wish to run DB migrations manually.
#DB_MANUAL_MIGRATION=true
# Specify postgres schema to use. In production, you will need to create the schema first
# to use this option. Jackson will not create it for you.
POSTGRES_SCHEMA=

# Admin Portal settings
# SMTP details for Magic Links
Expand Down
3 changes: 3 additions & 0 deletions lib/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ const db: DatabaseOption = {
writeCapacityUnits: process.env.DB_DYNAMODB_RCUS ? Number(process.env.DB_DYNAMODB_WCUS) : undefined,
},
manualMigration: process.env.DB_MANUAL_MIGRATION === 'true',
postgres: {
schema: process.env.POSTGRES_SCHEMA,
},
};

/** Indicates if the Jackson instance is hosted (i.e. not self-hosted) */
Expand Down
1 change: 1 addition & 0 deletions npm/src/db/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const DEFAULT_POSTGRES_SCHEMA = 'public';
3 changes: 3 additions & 0 deletions npm/src/db/defaultDb.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { JacksonOption } from '../typings';
import { DEFAULT_POSTGRES_SCHEMA } from './constants';

export default function defaultDb(opts: JacksonOption) {
opts.db = opts.db || {};
Expand All @@ -12,6 +13,8 @@ export default function defaultDb(opts: JacksonOption) {
opts.db.dynamodb.readCapacityUnits = opts.db.dynamodb.readCapacityUnits || 5;
opts.db.dynamodb.writeCapacityUnits = opts.db.dynamodb.writeCapacityUnits || 5;
opts.db.manualMigration = opts.db.manualMigration || false;
opts.db.postgres = opts.db.postgres || {};
opts.db.postgres.schema = opts.db.postgres.schema || DEFAULT_POSTGRES_SCHEMA;

return opts;
}
24 changes: 21 additions & 3 deletions npm/src/db/sql/sql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import { DatabaseDriver, DatabaseOption, Index, Encrypted, Records, SortOrder }
import { DataSource, DataSourceOptions, In, IsNull } from 'typeorm';
import * as dbutils from '../utils';
import * as mssql from './mssql';
import { DEFAULT_POSTGRES_SCHEMA } from '../constants';

class Sql implements DatabaseDriver {
export class Sql implements DatabaseDriver {
private options: DatabaseOption;
private dataSource!: DataSource;
private storeRepository;
Expand All @@ -26,6 +27,7 @@ class Sql implements DatabaseDriver {

async init({ JacksonStore, JacksonIndex, JacksonTTL }): Promise<Sql> {
const sqlType = this.options.engine === 'planetscale' ? 'mysql' : this.options.type!;
const postgresSchema = this.options.postgres?.schema || DEFAULT_POSTGRES_SCHEMA;
// Synchronize by default for non-planetscale engines only if migrations are not set to run
let synchronize = !this.options.manualMigration;
if (this.options.engine === 'planetscale') {
Expand Down Expand Up @@ -53,14 +55,30 @@ class Sql implements DatabaseDriver {
...baseOpts,
});
} else {
this.dataSource = new DataSource(<DataSourceOptions>{
const dataSourceOptions = {
url: this.options.url,
ssl: this.options.ssl,
...baseOpts,
});
};

if (sqlType === 'postgres') {
dataSourceOptions['synchronize'] = false;
dataSourceOptions['schema'] = postgresSchema;
}
this.dataSource = new DataSource(<DataSourceOptions>dataSourceOptions);
}

await this.dataSource.initialize();

if (sqlType === 'postgres' && synchronize) {
// We skip synchronization for postgres databases because TypeORM
// does not create schemas if they don't exist, we manually run
// synchronize here if it is set to true.
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.query(`CREATE SCHEMA IF NOT EXISTS ${postgresSchema}`);
this.dataSource.synchronize();
}

break;
} catch (err) {
console.error(`error connecting to engine: ${this.options.engine}, type: ${sqlType} db: ${err}`);
Expand Down
3 changes: 3 additions & 0 deletions npm/src/typings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,9 @@ export interface DatabaseOption {
writeCapacityUnits?: number;
};
manualMigration?: boolean;
postgres?: {
schema?: string;
};
}

export interface JacksonOption {
Expand Down
37 changes: 35 additions & 2 deletions npm/test/db/db.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const dbObjs: { [key: string]: DatabaseDriver } = {};
const connectionStores: Storable[] = [];
const ttlStores: Storable[] = [];
const ttl = 2;
const non_default_schema = 'non_default';

const record1 = {
id: '1',
Expand Down Expand Up @@ -130,6 +131,12 @@ const dbs = [
...postgresDbConfig,
encryptionKey,
},
{
...postgresDbConfig,
postgres: {
schema: non_default_schema,
},
},
{
...mongoDbConfig,
},
Expand Down Expand Up @@ -188,7 +195,11 @@ tap.before(async () => {
for (const idx in dbs) {
const opts = dbs[idx];
const db = await DB.new(opts, true);
dbObjs[opts.engine! + (opts.type ? opts.type : '')] = db;
if (opts.type === 'postgres' && opts['schema'] === non_default_schema) {
dbObjs[opts['schema'] + opts.engine! + (opts.type ? opts.type : '')] = db;
} else {
dbObjs[opts.engine! + (opts.type ? opts.type : '')] = db;
}

const randomSession = Date.now();
connectionStores.push(db.store('saml:config:' + randomSession + randomBytes(4).toString('hex')));
Expand All @@ -201,15 +212,32 @@ tap.teardown(async () => {
});

tap.test('dbs', async () => {
// We need this to ensure that the test runs atleast once.
// It is quite easy to skip the test by mistake in the future
// if one of the conditions change and it goes unnoticed.
let has_non_default_postgres_schema_test_ran = false;
for (const idx in connectionStores) {
const connectionStore = connectionStores[idx];
const ttlStore = ttlStores[idx];
const dbEngine = dbs[idx].engine!;
let dbType = dbEngine;
let dbType = dbEngine.toString();
if (dbs[idx].type) {
dbType += ': ' + dbs[idx].type;
}

tap.test('Test non default postgres schema', (t) => {
if (dbType === 'sql: postgres' && dbs[idx].postgres?.schema === non_default_schema) {
t.same(
connectionStore['db']['db']['dataSource']['createQueryBuilder']()['connection']['options'][
'schema'
],
non_default_schema
);
}
has_non_default_postgres_schema_test_ran = true;
t.end();
});

tap.test('put(): ' + dbType, async () => {
await connectionStore.put(
record1.id,
Expand Down Expand Up @@ -527,4 +555,9 @@ tap.test('dbs', async () => {
await value.close();
}
});

tap.test('Ensure that the test for non default postgres schema has ran atleast once', (t) => {
t.same(has_non_default_postgres_schema_test_ran, true);
t.end();
});
});

0 comments on commit 68d44ba

Please sign in to comment.