Skip to content

Commit

Permalink
feat(add support for primary keys): add support for primary keys. Add…
Browse files Browse the repository at this point in the history
… generatePrimaryKeys option
  • Loading branch information
Jmurp11 committed Sep 3, 2021
1 parent cc605b5 commit d2cdf74
Show file tree
Hide file tree
Showing 6 changed files with 190 additions and 47 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Supports two options, both of which are optional:

* *createDatabase* - _true | false_ (Defaults to false)
* *createTables* - _true | false_ (Defaults to false)

* *generatePrimaryKeys* - _true | false_ (Defaults to false. Supports multiple primary keys. Append '_pk' to the column name in the workbook that will be the primary key)
# Testing

This package's tests are written using [Jest](https://jestjs.io/). To execute, run:
Expand Down
71 changes: 59 additions & 12 deletions src/etl-processes.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
enum SQLType {
VARCHAR = 'VARCHAR',
BOOLEAN = 'BOOLEAN',
FLOAT = 'FLOAT',
INT = 'INT'
export enum SQLType {
VARCHAR = 'VARCHAR',
BOOLEAN = 'BOOLEAN',
FLOAT = 'FLOAT',
INT = 'INT'
}

export enum SQLKeyword {
PRIMARY_KEY = 'PRIMARY KEY'
}
export interface Column {
name: string;
type: string;
name: string;
type: string;
}

export interface Fields<T> {
names: string[];
values: T[];
names: string[];
values: T[];
}

interface FormatColumnsResult {
formattedColumns: string[];
primaryKeyIndex: number[];
}

export function getFields<T>(data: T): Fields<T> {
Expand Down Expand Up @@ -53,13 +61,52 @@ export function getColumns<T>(fields: Fields<T>): Column[] {
return tableColumns;
}

export function formatColumns(columns: Column[]): string[] {
export function formatColumns(columns: Column[]): FormatColumnsResult {
const formattedColumns: string[] = [];

columns.forEach((col: Column) => {
const primaryKeyIndex: number [] = [];

columns.forEach((col: Column, index: number) => {
const formatted = `${col.name.replace(/\s/g, '')} ${col.type}`;

if (checkPrimaryKey(col.name)) {
primaryKeyIndex.push(index);
}

formattedColumns.push(formatted);
});

return formattedColumns;
return { formattedColumns, primaryKeyIndex: primaryKeyIndex };
}

export function checkPrimaryKey(col: string): boolean {
const primaryKeyIndicator = '_pk';

if (col.substring(col.length, col.length - 3).toUpperCase() === primaryKeyIndicator.toUpperCase()) {
return true;
}

return false;
}

export function formatPrimaryKey(formatColumnsResult: FormatColumnsResult): string[] {
if (formatColumnsResult.primaryKeyIndex.length === 1) {
const primaryColumn = formatColumnsResult.formattedColumns[formatColumnsResult.primaryKeyIndex[0]].concat(` ${SQLKeyword.PRIMARY_KEY}`);

formatColumnsResult.formattedColumns[formatColumnsResult.primaryKeyIndex[0]] = primaryColumn;

return formatColumnsResult.formattedColumns;
}

const primaryKeys: string[] = [];

formatColumnsResult.primaryKeyIndex.forEach(index => {
primaryKeys.push(formatColumnsResult.formattedColumns[index].substring(0, formatColumnsResult.formattedColumns[index].indexOf(' ')));
});

const primaryColumns = `${SQLKeyword.PRIMARY_KEY} (${primaryKeys})`;

formatColumnsResult.formattedColumns.push(primaryColumns);

return formatColumnsResult.formattedColumns;
}
9 changes: 5 additions & 4 deletions src/sql.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Pool } from 'pg';
import { getFields, Fields, Column, getColumns, formatColumns } from './etl-processes';
import { getFields, Fields, Column, getColumns, formatColumns, formatPrimaryKey } from './etl-processes';
import { readExcel } from './excel';

export interface Connection {
Expand All @@ -13,16 +13,17 @@ export interface Connection {
export interface Options {
createDatabase?: boolean;
createTables?: boolean;
generatePrimaryKeys?: boolean;
}

export function createDatabase(dbName: string): string {
return `CREATE DATABASE ${dbName};`;
}

export function createTable<T>(tableName: string, data: T): string {
export function createTable<T>(tableName: string, data: T, generatePrimaryKeys?: boolean): string {
const fields: Fields<T> = getFields(data);
const columns: Column[] = getColumns(fields);
const formattedColumns: string[] = formatColumns(columns);
const formattedColumns: string[] = generatePrimaryKeys ? formatPrimaryKey(formatColumns(columns)) : formatColumns(columns).formattedColumns;

return `CREATE TABLE ${tableName.replace(/\s/g, '')} (
${formattedColumns}
Expand Down Expand Up @@ -77,7 +78,7 @@ export async function excelToPostgresDb(connectionInfo: Connection, filePath: st
let tableQuery = '';

sheets.forEach(async (sheet) => {
tableQuery = tableQuery.concat(createTable(sheet.title, sheet.data[0]));
tableQuery = tableQuery.concat(createTable(sheet.title, sheet.data[0], options?.generatePrimaryKeys));
insertQuery = insertQuery.concat(insert(sheet.title, sheet.data));
});

Expand Down
42 changes: 36 additions & 6 deletions src/tests/etl-processes.test.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,46 @@
import { getFields, getColumns, formatColumns } from '../etl-processes';
import { columns, etlProcesses } from './test-data';
import { getFields, getColumns, formatColumns, checkPrimaryKey, formatPrimaryKey } from '../etl-processes';
import { columns_one_pk, columns_multiple_pk, etlProcesses } from './test-data';

describe('ETL processes tests', () => {
describe('ETL processes tests one primary key', () => {
test('getFields', () => {
expect(getFields(etlProcesses.sheet)).toEqual(etlProcesses.fields);
expect(getFields(etlProcesses.sheet_one_pk)).toEqual(etlProcesses.fields_one_pk);
});

test('getColumns', () => {
expect(getColumns(etlProcesses.fields)).toEqual(columns);
expect(getColumns(etlProcesses.fields_one_pk)).toEqual(columns_one_pk);
});

test('formatColumns', () => {
expect(formatColumns(columns)).toEqual(etlProcesses.formattedColumns);
expect(formatColumns(columns_one_pk)).toEqual({formattedColumns: [...etlProcesses.formattedColumns], primaryKeyIndex: [0]});
});

test('checkPrimaryKey', () => {
expect(checkPrimaryKey(columns_one_pk[0].name)).toEqual(true);
});

test('formatPrimaryKey', () => {
expect(formatPrimaryKey({ formattedColumns: etlProcesses.formattedColumns, primaryKeyIndex: [0] })).toEqual(etlProcesses.formattedColumnsOnePrimaryKey);
});
});

describe('ETL processes tests multiple primary key', () => {
test('getFields', () => {
expect(getFields(etlProcesses.sheet_multiple_pk)).toEqual(etlProcesses.fields_multiple_pks);
});

test('getColumns', () => {
expect(getColumns(etlProcesses.fields_multiple_pks)).toEqual(columns_multiple_pk);
});

test('formatColumns', () => {
expect(formatColumns(columns_multiple_pk)).toEqual({ formattedColumns: [...etlProcesses.formattedColumns_multiple_pk], primaryKeyIndex: [0,1] });
});

test('checkPrimaryKey', () => {
expect(checkPrimaryKey(columns_multiple_pk[0].name)).toEqual(true);
});

test('formatPrimaryKey', () => {
expect(formatPrimaryKey({ formattedColumns: etlProcesses.formattedColumns_multiple_pk, primaryKeyIndex: [0, 1] })).toEqual(etlProcesses.formattedColumnsMultiplePrimaryKeys);
});
});
10 changes: 7 additions & 3 deletions src/tests/sql.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@ describe('SQL tests', () => {
expect(createDatabase(sqlInfo.database)).toEqual(sqlResults.createDatabase);
});

test('create table', () => {
expect(createTable(sqlInfo.tableName, etlProcesses.sheet)).toEqual(sqlResults.createTable);
test('create table one primary key', () => {
expect(createTable(sqlInfo.tableName, etlProcesses.sheet_one_pk, true)).toEqual(sqlResults.createTableOnePrimaryKey);
});

test('create table multiple primary key', () => {
expect(createTable(sqlInfo.tableName, etlProcesses.sheet_multiple_pk, true)).toEqual(sqlResults.createTableMultiplePrimaryKeys);
});

test('insert', () => {
expect(insert(sqlInfo.tableName, [etlProcesses.sheet])).toEqual(sqlResults.insert);
expect(insert(sqlInfo.tableName, [etlProcesses.sheet_one_pk])).toEqual(sqlResults.insert);
});
});
103 changes: 82 additions & 21 deletions src/tests/test-data.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,105 @@
import { SQLKeyword, SQLType } from '../etl-processes';

const col_names_one_pk = [
'name_pk',
'age',
'isDev'
];

const col_names_multiple_pk = [
'name_pk',
'age_pk',
'isDev'
];

const mockJSON = {
name: 'Person 1',
name_pk: 'Person 1',
age: 18,
isDev: true
};

export const columns = [
export const columns_one_pk = [
{
name: col_names_one_pk[0],
type: SQLType.VARCHAR
},
{
name: col_names_one_pk[1],
type: SQLType.FLOAT
},
{
name: col_names_one_pk[2],
type: SQLType.BOOLEAN
}
];

export const columns_multiple_pk = [
{
name: 'name',
type: 'VARCHAR'
name: col_names_multiple_pk[0],
type: SQLType.VARCHAR
},
{
name: 'age',
type: 'FLOAT'
name: col_names_multiple_pk[1],
type: SQLType.FLOAT
},
{
name: 'isDev',
type: 'BOOLEAN'
name: col_names_multiple_pk[2],
type: SQLType.BOOLEAN
}
];

export const etlProcesses = {
sheet: {
name: mockJSON.name,
sheet_one_pk: {
name_pk: mockJSON.name_pk,
age: mockJSON.age,
isDev: mockJSON.isDev
},
fields: {
sheet_multiple_pk: {
name_pk: mockJSON.name_pk,
age_pk: mockJSON.age,
isDev: mockJSON.isDev
},
fields_one_pk: {
names: [
...col_names_one_pk
],
values: [
mockJSON.name_pk,
mockJSON.age,
mockJSON.isDev
]
},
fields_multiple_pks: {
names: [
'name',
'age',
'isDev'
...col_names_multiple_pk
],
values: [
mockJSON.name,
mockJSON.name_pk,
mockJSON.age,
mockJSON.isDev
]
},
formattedColumns: [
`${columns[0].name} ${columns[0].type}`,
`${columns[1].name} ${columns[1].type}`,
`${columns[2].name} ${columns[2].type}`
`${columns_one_pk[0].name} ${columns_one_pk[0].type}`,
`${columns_one_pk[1].name} ${columns_one_pk[1].type}`,
`${columns_one_pk[2].name} ${columns_one_pk[2].type}`
],
formattedColumns_multiple_pk: [
`${columns_multiple_pk[0].name} ${columns_multiple_pk[0].type}`,
`${columns_multiple_pk[1].name} ${columns_multiple_pk[1].type}`,
`${columns_multiple_pk[2].name} ${columns_multiple_pk[2].type}`
],
formattedColumnsOnePrimaryKey: [
`${columns_one_pk[0].name} ${columns_one_pk[0].type} ${SQLKeyword.PRIMARY_KEY}`,
`${columns_one_pk[1].name} ${columns_one_pk[1].type}`,
`${columns_one_pk[2].name} ${columns_one_pk[2].type}`
],
formattedColumnsMultiplePrimaryKeys: [
`${columns_multiple_pk[0].name} ${columns_multiple_pk[0].type}`,
`${columns_multiple_pk[1].name} ${columns_multiple_pk[1].type}`,
`${columns_multiple_pk[2].name} ${columns_multiple_pk[2].type}`,
`${SQLKeyword.PRIMARY_KEY} (${columns_multiple_pk[0].name},${columns_multiple_pk[1].name})`
]
};

export const sqlInfo = {
Expand All @@ -51,8 +109,11 @@ export const sqlInfo = {

export const sqlResults = {
createDatabase: `CREATE DATABASE ${sqlInfo.database};`,
createTable: `CREATE TABLE ${sqlInfo.tableName} (
${etlProcesses.formattedColumns}
createTableOnePrimaryKey: `CREATE TABLE ${sqlInfo.tableName} (
${etlProcesses.formattedColumnsOnePrimaryKey}
);`,
createTableMultiplePrimaryKeys: `CREATE TABLE ${sqlInfo.tableName} (
${etlProcesses.formattedColumnsMultiplePrimaryKeys}
);`,
insert: `INSERT INTO ${sqlInfo.tableName.replace(/\s/g, '')}(name,age,isDev) VALUES ('${mockJSON.name}',${mockJSON.age},${mockJSON.isDev});`
insert: `INSERT INTO ${sqlInfo.tableName.replace(/\s/g, '')}(${col_names_one_pk[0]},${col_names_one_pk[1]},${col_names_one_pk[2]}) VALUES ('${mockJSON.name_pk}',${mockJSON.age},${mockJSON.isDev});`
};

0 comments on commit d2cdf74

Please sign in to comment.