Skip to content

Commit d2cdf74

Browse files
committed
feat(add support for primary keys): add support for primary keys. Add generatePrimaryKeys option
1 parent cc605b5 commit d2cdf74

File tree

6 files changed

+190
-47
lines changed

6 files changed

+190
-47
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Supports two options, both of which are optional:
3434

3535
* *createDatabase* - _true | false_ (Defaults to false)
3636
* *createTables* - _true | false_ (Defaults to false)
37-
37+
* *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)
3838
# Testing
3939

4040
This package's tests are written using [Jest](https://jestjs.io/). To execute, run:

src/etl-processes.ts

Lines changed: 59 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,26 @@
1-
enum SQLType {
2-
VARCHAR = 'VARCHAR',
3-
BOOLEAN = 'BOOLEAN',
4-
FLOAT = 'FLOAT',
5-
INT = 'INT'
1+
export enum SQLType {
2+
VARCHAR = 'VARCHAR',
3+
BOOLEAN = 'BOOLEAN',
4+
FLOAT = 'FLOAT',
5+
INT = 'INT'
66
}
77

8+
export enum SQLKeyword {
9+
PRIMARY_KEY = 'PRIMARY KEY'
10+
}
811
export interface Column {
9-
name: string;
10-
type: string;
12+
name: string;
13+
type: string;
1114
}
1215

1316
export interface Fields<T> {
14-
names: string[];
15-
values: T[];
17+
names: string[];
18+
values: T[];
19+
}
20+
21+
interface FormatColumnsResult {
22+
formattedColumns: string[];
23+
primaryKeyIndex: number[];
1624
}
1725

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

56-
export function formatColumns(columns: Column[]): string[] {
64+
export function formatColumns(columns: Column[]): FormatColumnsResult {
5765
const formattedColumns: string[] = [];
5866

59-
columns.forEach((col: Column) => {
67+
const primaryKeyIndex: number [] = [];
68+
69+
columns.forEach((col: Column, index: number) => {
6070
const formatted = `${col.name.replace(/\s/g, '')} ${col.type}`;
71+
72+
if (checkPrimaryKey(col.name)) {
73+
primaryKeyIndex.push(index);
74+
}
75+
6176
formattedColumns.push(formatted);
6277
});
6378

64-
return formattedColumns;
79+
return { formattedColumns, primaryKeyIndex: primaryKeyIndex };
80+
}
81+
82+
export function checkPrimaryKey(col: string): boolean {
83+
const primaryKeyIndicator = '_pk';
84+
85+
if (col.substring(col.length, col.length - 3).toUpperCase() === primaryKeyIndicator.toUpperCase()) {
86+
return true;
87+
}
88+
89+
return false;
90+
}
91+
92+
export function formatPrimaryKey(formatColumnsResult: FormatColumnsResult): string[] {
93+
if (formatColumnsResult.primaryKeyIndex.length === 1) {
94+
const primaryColumn = formatColumnsResult.formattedColumns[formatColumnsResult.primaryKeyIndex[0]].concat(` ${SQLKeyword.PRIMARY_KEY}`);
95+
96+
formatColumnsResult.formattedColumns[formatColumnsResult.primaryKeyIndex[0]] = primaryColumn;
97+
98+
return formatColumnsResult.formattedColumns;
99+
}
100+
101+
const primaryKeys: string[] = [];
102+
103+
formatColumnsResult.primaryKeyIndex.forEach(index => {
104+
primaryKeys.push(formatColumnsResult.formattedColumns[index].substring(0, formatColumnsResult.formattedColumns[index].indexOf(' ')));
105+
});
106+
107+
const primaryColumns = `${SQLKeyword.PRIMARY_KEY} (${primaryKeys})`;
108+
109+
formatColumnsResult.formattedColumns.push(primaryColumns);
110+
111+
return formatColumnsResult.formattedColumns;
65112
}

src/sql.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Pool } from 'pg';
2-
import { getFields, Fields, Column, getColumns, formatColumns } from './etl-processes';
2+
import { getFields, Fields, Column, getColumns, formatColumns, formatPrimaryKey } from './etl-processes';
33
import { readExcel } from './excel';
44

55
export interface Connection {
@@ -13,16 +13,17 @@ export interface Connection {
1313
export interface Options {
1414
createDatabase?: boolean;
1515
createTables?: boolean;
16+
generatePrimaryKeys?: boolean;
1617
}
1718

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

22-
export function createTable<T>(tableName: string, data: T): string {
23+
export function createTable<T>(tableName: string, data: T, generatePrimaryKeys?: boolean): string {
2324
const fields: Fields<T> = getFields(data);
2425
const columns: Column[] = getColumns(fields);
25-
const formattedColumns: string[] = formatColumns(columns);
26+
const formattedColumns: string[] = generatePrimaryKeys ? formatPrimaryKey(formatColumns(columns)) : formatColumns(columns).formattedColumns;
2627

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

7980
sheets.forEach(async (sheet) => {
80-
tableQuery = tableQuery.concat(createTable(sheet.title, sheet.data[0]));
81+
tableQuery = tableQuery.concat(createTable(sheet.title, sheet.data[0], options?.generatePrimaryKeys));
8182
insertQuery = insertQuery.concat(insert(sheet.title, sheet.data));
8283
});
8384

src/tests/etl-processes.test.ts

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,46 @@
1-
import { getFields, getColumns, formatColumns } from '../etl-processes';
2-
import { columns, etlProcesses } from './test-data';
1+
import { getFields, getColumns, formatColumns, checkPrimaryKey, formatPrimaryKey } from '../etl-processes';
2+
import { columns_one_pk, columns_multiple_pk, etlProcesses } from './test-data';
33

4-
describe('ETL processes tests', () => {
4+
describe('ETL processes tests one primary key', () => {
55
test('getFields', () => {
6-
expect(getFields(etlProcesses.sheet)).toEqual(etlProcesses.fields);
6+
expect(getFields(etlProcesses.sheet_one_pk)).toEqual(etlProcesses.fields_one_pk);
77
});
88

99
test('getColumns', () => {
10-
expect(getColumns(etlProcesses.fields)).toEqual(columns);
10+
expect(getColumns(etlProcesses.fields_one_pk)).toEqual(columns_one_pk);
1111
});
1212

1313
test('formatColumns', () => {
14-
expect(formatColumns(columns)).toEqual(etlProcesses.formattedColumns);
14+
expect(formatColumns(columns_one_pk)).toEqual({formattedColumns: [...etlProcesses.formattedColumns], primaryKeyIndex: [0]});
15+
});
16+
17+
test('checkPrimaryKey', () => {
18+
expect(checkPrimaryKey(columns_one_pk[0].name)).toEqual(true);
19+
});
20+
21+
test('formatPrimaryKey', () => {
22+
expect(formatPrimaryKey({ formattedColumns: etlProcesses.formattedColumns, primaryKeyIndex: [0] })).toEqual(etlProcesses.formattedColumnsOnePrimaryKey);
23+
});
24+
});
25+
26+
describe('ETL processes tests multiple primary key', () => {
27+
test('getFields', () => {
28+
expect(getFields(etlProcesses.sheet_multiple_pk)).toEqual(etlProcesses.fields_multiple_pks);
29+
});
30+
31+
test('getColumns', () => {
32+
expect(getColumns(etlProcesses.fields_multiple_pks)).toEqual(columns_multiple_pk);
33+
});
34+
35+
test('formatColumns', () => {
36+
expect(formatColumns(columns_multiple_pk)).toEqual({ formattedColumns: [...etlProcesses.formattedColumns_multiple_pk], primaryKeyIndex: [0,1] });
37+
});
38+
39+
test('checkPrimaryKey', () => {
40+
expect(checkPrimaryKey(columns_multiple_pk[0].name)).toEqual(true);
41+
});
42+
43+
test('formatPrimaryKey', () => {
44+
expect(formatPrimaryKey({ formattedColumns: etlProcesses.formattedColumns_multiple_pk, primaryKeyIndex: [0, 1] })).toEqual(etlProcesses.formattedColumnsMultiplePrimaryKeys);
1545
});
1646
});

src/tests/sql.test.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@ describe('SQL tests', () => {
66
expect(createDatabase(sqlInfo.database)).toEqual(sqlResults.createDatabase);
77
});
88

9-
test('create table', () => {
10-
expect(createTable(sqlInfo.tableName, etlProcesses.sheet)).toEqual(sqlResults.createTable);
9+
test('create table one primary key', () => {
10+
expect(createTable(sqlInfo.tableName, etlProcesses.sheet_one_pk, true)).toEqual(sqlResults.createTableOnePrimaryKey);
11+
});
12+
13+
test('create table multiple primary key', () => {
14+
expect(createTable(sqlInfo.tableName, etlProcesses.sheet_multiple_pk, true)).toEqual(sqlResults.createTableMultiplePrimaryKeys);
1115
});
1216

1317
test('insert', () => {
14-
expect(insert(sqlInfo.tableName, [etlProcesses.sheet])).toEqual(sqlResults.insert);
18+
expect(insert(sqlInfo.tableName, [etlProcesses.sheet_one_pk])).toEqual(sqlResults.insert);
1519
});
1620
});

src/tests/test-data.ts

Lines changed: 82 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,105 @@
1+
import { SQLKeyword, SQLType } from '../etl-processes';
2+
3+
const col_names_one_pk = [
4+
'name_pk',
5+
'age',
6+
'isDev'
7+
];
8+
9+
const col_names_multiple_pk = [
10+
'name_pk',
11+
'age_pk',
12+
'isDev'
13+
];
14+
115
const mockJSON = {
2-
name: 'Person 1',
16+
name_pk: 'Person 1',
317
age: 18,
418
isDev: true
519
};
620

7-
export const columns = [
21+
export const columns_one_pk = [
22+
{
23+
name: col_names_one_pk[0],
24+
type: SQLType.VARCHAR
25+
},
26+
{
27+
name: col_names_one_pk[1],
28+
type: SQLType.FLOAT
29+
},
30+
{
31+
name: col_names_one_pk[2],
32+
type: SQLType.BOOLEAN
33+
}
34+
];
35+
36+
export const columns_multiple_pk = [
837
{
9-
name: 'name',
10-
type: 'VARCHAR'
38+
name: col_names_multiple_pk[0],
39+
type: SQLType.VARCHAR
1140
},
1241
{
13-
name: 'age',
14-
type: 'FLOAT'
42+
name: col_names_multiple_pk[1],
43+
type: SQLType.FLOAT
1544
},
1645
{
17-
name: 'isDev',
18-
type: 'BOOLEAN'
46+
name: col_names_multiple_pk[2],
47+
type: SQLType.BOOLEAN
1948
}
2049
];
2150

2251
export const etlProcesses = {
23-
sheet: {
24-
name: mockJSON.name,
52+
sheet_one_pk: {
53+
name_pk: mockJSON.name_pk,
2554
age: mockJSON.age,
2655
isDev: mockJSON.isDev
2756
},
28-
fields: {
57+
sheet_multiple_pk: {
58+
name_pk: mockJSON.name_pk,
59+
age_pk: mockJSON.age,
60+
isDev: mockJSON.isDev
61+
},
62+
fields_one_pk: {
63+
names: [
64+
...col_names_one_pk
65+
],
66+
values: [
67+
mockJSON.name_pk,
68+
mockJSON.age,
69+
mockJSON.isDev
70+
]
71+
},
72+
fields_multiple_pks: {
2973
names: [
30-
'name',
31-
'age',
32-
'isDev'
74+
...col_names_multiple_pk
3375
],
3476
values: [
35-
mockJSON.name,
77+
mockJSON.name_pk,
3678
mockJSON.age,
3779
mockJSON.isDev
3880
]
3981
},
4082
formattedColumns: [
41-
`${columns[0].name} ${columns[0].type}`,
42-
`${columns[1].name} ${columns[1].type}`,
43-
`${columns[2].name} ${columns[2].type}`
83+
`${columns_one_pk[0].name} ${columns_one_pk[0].type}`,
84+
`${columns_one_pk[1].name} ${columns_one_pk[1].type}`,
85+
`${columns_one_pk[2].name} ${columns_one_pk[2].type}`
4486
],
87+
formattedColumns_multiple_pk: [
88+
`${columns_multiple_pk[0].name} ${columns_multiple_pk[0].type}`,
89+
`${columns_multiple_pk[1].name} ${columns_multiple_pk[1].type}`,
90+
`${columns_multiple_pk[2].name} ${columns_multiple_pk[2].type}`
91+
],
92+
formattedColumnsOnePrimaryKey: [
93+
`${columns_one_pk[0].name} ${columns_one_pk[0].type} ${SQLKeyword.PRIMARY_KEY}`,
94+
`${columns_one_pk[1].name} ${columns_one_pk[1].type}`,
95+
`${columns_one_pk[2].name} ${columns_one_pk[2].type}`
96+
],
97+
formattedColumnsMultiplePrimaryKeys: [
98+
`${columns_multiple_pk[0].name} ${columns_multiple_pk[0].type}`,
99+
`${columns_multiple_pk[1].name} ${columns_multiple_pk[1].type}`,
100+
`${columns_multiple_pk[2].name} ${columns_multiple_pk[2].type}`,
101+
`${SQLKeyword.PRIMARY_KEY} (${columns_multiple_pk[0].name},${columns_multiple_pk[1].name})`
102+
]
45103
};
46104

47105
export const sqlInfo = {
@@ -51,8 +109,11 @@ export const sqlInfo = {
51109

52110
export const sqlResults = {
53111
createDatabase: `CREATE DATABASE ${sqlInfo.database};`,
54-
createTable: `CREATE TABLE ${sqlInfo.tableName} (
55-
${etlProcesses.formattedColumns}
112+
createTableOnePrimaryKey: `CREATE TABLE ${sqlInfo.tableName} (
113+
${etlProcesses.formattedColumnsOnePrimaryKey}
114+
);`,
115+
createTableMultiplePrimaryKeys: `CREATE TABLE ${sqlInfo.tableName} (
116+
${etlProcesses.formattedColumnsMultiplePrimaryKeys}
56117
);`,
57-
insert: `INSERT INTO ${sqlInfo.tableName.replace(/\s/g, '')}(name,age,isDev) VALUES ('${mockJSON.name}',${mockJSON.age},${mockJSON.isDev});`
118+
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});`
58119
};

0 commit comments

Comments
 (0)