Skip to content

Commit cb8f038

Browse files
paulovitinvitaly-t
authored andcommitted
#4338 pg schema upgrade (#4375)
* Method to upgrade schemas in Postgres; Adds: - schemaUpgrade method which check the fields to add and delete from old schema; * Remove the columns delete in schemaUpgrade method; * ESLint fix and PostgresStorageAdapter.schemaUpgrade spec test Adds: - Add PostgresStorageAdapter.schemaUpgrade spec tests: creates a table, simulates the addition of a new field and checks if its present in the database Chores: - ESLint eol-last fix; * Add check columns before and after schema upgrade, and remove the unnecessary piece of code Add: - Check the right columns is present before schema upgrade and the new field is not, then check if the right columns is present and the new field; Remove: - Piece of code unnecessary because it not need to remove diff columns; * Optimize the schemaUpgrade method following @vitaly-t instructions, and more tests; * If the class does not have any columns and needs an upgrade the code would return without doing so. Fixed this. Chore: - Allows class with no column to be upgraded; - Test for class with no columns being upgraded; * Update PostgresStorageAdapter.js rewriting method schemaUpgrade * Update PostgresStorageAdapter.spec.js * Update PostgresStorageAdapter.spec.js
1 parent fc6a2fd commit cb8f038

File tree

2 files changed

+127
-8
lines changed

2 files changed

+127
-8
lines changed

spec/PostgresStorageAdapter.spec.js

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import PostgresStorageAdapter from '../src/Adapters/Storage/Postgres/PostgresStorageAdapter';
22
const databaseURI = 'postgres://localhost:5432/parse_server_postgres_adapter_test_database';
33

4+
const getColumns = (client, className) => {
5+
return client.map('SELECT column_name FROM information_schema.columns WHERE table_name = $<className>', { className }, a => a.column_name);
6+
};
7+
48
describe_only_db('postgres')('PostgresStorageAdapter', () => {
59
beforeEach(done => {
610
const adapter = new PostgresStorageAdapter({ uri: databaseURI })
@@ -19,4 +23,100 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => {
1923
expect(adapter._client.$pool.ending).toEqual(true);
2024
done();
2125
});
26+
27+
it('schemaUpgrade, upgrade the database schema when schema changes', done => {
28+
const adapter = new PostgresStorageAdapter({ uri: databaseURI });
29+
const client = adapter._client;
30+
const className = '_PushStatus';
31+
const schema = {
32+
fields: {
33+
"pushTime": { type: 'String' },
34+
"source": { type: 'String' },
35+
"query": { type: 'String' },
36+
},
37+
};
38+
39+
adapter.createTable(className, schema)
40+
.then(() => getColumns(client, className))
41+
.then(columns => {
42+
expect(columns).toContain('pushTime');
43+
expect(columns).toContain('source');
44+
expect(columns).toContain('query');
45+
expect(columns).not.toContain('expiration_interval');
46+
47+
schema.fields.expiration_interval = { type:'Number' };
48+
return adapter.schemaUpgrade(className, schema);
49+
})
50+
.then(() => getColumns(client, className))
51+
.then(columns => {
52+
expect(columns).toContain('pushTime');
53+
expect(columns).toContain('source');
54+
expect(columns).toContain('query');
55+
expect(columns).toContain('expiration_interval');
56+
done();
57+
})
58+
.catch(error => done.fail(error));
59+
});
60+
61+
it('schemaUpgrade, maintain correct schema', done => {
62+
const adapter = new PostgresStorageAdapter({ uri: databaseURI });
63+
const client = adapter._client;
64+
const className = 'Table';
65+
const schema = {
66+
fields: {
67+
"columnA": { type: 'String' },
68+
"columnB": { type: 'String' },
69+
"columnC": { type: 'String' },
70+
},
71+
};
72+
73+
adapter.createTable(className, schema)
74+
.then(() => getColumns(client, className))
75+
.then(columns => {
76+
expect(columns).toContain('columnA');
77+
expect(columns).toContain('columnB');
78+
expect(columns).toContain('columnC');
79+
80+
return adapter.schemaUpgrade(className, schema);
81+
})
82+
.then(() => getColumns(client, className))
83+
.then(columns => {
84+
expect(columns.length).toEqual(3);
85+
expect(columns).toContain('columnA');
86+
expect(columns).toContain('columnB');
87+
expect(columns).toContain('columnC');
88+
done();
89+
})
90+
.catch(error => done.fail(error));
91+
});
92+
93+
it('Create a table without columns and upgrade with columns', done => {
94+
const adapter = new PostgresStorageAdapter({ uri: databaseURI });
95+
const client = adapter._client;
96+
const className = 'EmptyTable';
97+
let schema = {};
98+
99+
adapter.createTable(className, schema)
100+
.then(() => getColumns(client, className))
101+
.then(columns => {
102+
expect(columns.length).toBe(0);
103+
104+
schema = {
105+
fields: {
106+
"columnA": { type: 'String' },
107+
"columnB": { type: 'String' }
108+
},
109+
};
110+
111+
return adapter.schemaUpgrade(className, schema);
112+
})
113+
.then(() => getColumns(client, className))
114+
.then(columns => {
115+
expect(columns.length).toEqual(2);
116+
expect(columns).toContain('columnA');
117+
expect(columns).toContain('columnB');
118+
done();
119+
})
120+
.catch(error => done.fail(error));
121+
})
22122
});

src/Adapters/Storage/Postgres/PostgresStorageAdapter.js

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -750,11 +750,27 @@ export class PostgresStorageAdapter implements StorageAdapter {
750750
});
751751
}
752752

753-
addFieldIfNotExists(className: string, fieldName: string, type: any) {
753+
schemaUpgrade(className: string, schema: SchemaType, conn: any) {
754+
debug('schemaUpgrade', { className, schema });
755+
conn = conn || this._client;
756+
const self = this;
757+
758+
return conn.tx('schema-upgrade', function * (t) {
759+
const columns = yield t.map('SELECT column_name FROM information_schema.columns WHERE table_name = $<className>', { className }, a => a.column_name);
760+
const newColumns = Object.keys(schema.fields)
761+
.filter(item => columns.indexOf(item) === -1)
762+
.map(fieldName => self.addFieldIfNotExists(className, fieldName, schema.fields[fieldName], t));
763+
764+
yield t.batch(newColumns);
765+
});
766+
}
767+
768+
addFieldIfNotExists(className: string, fieldName: string, type: any, conn: any) {
754769
// TODO: Must be revised for invalid logic...
755770
debug('addFieldIfNotExists', {className, fieldName, type});
771+
conn = conn || this._client;
756772
const self = this;
757-
return this._client.tx('add-field-if-not-exists', function * (t) {
773+
return conn.tx('add-field-if-not-exists', function * (t) {
758774
if (type.type !== 'Relation') {
759775
try {
760776
yield t.none('ALTER TABLE $<className:name> ADD COLUMN $<fieldName:name> $<postgresType:raw>', {
@@ -1591,14 +1607,17 @@ export class PostgresStorageAdapter implements StorageAdapter {
15911607
}
15921608

15931609
performInitialization({ VolatileClassesSchemas }: any) {
1610+
// TODO: This method needs to be rewritten to make proper use of connections (@vitaly-t)
15941611
debug('performInitialization');
15951612
const promises = VolatileClassesSchemas.map((schema) => {
1596-
return this.createTable(schema.className, schema).catch((err) => {
1597-
if (err.code === PostgresDuplicateRelationError || err.code === Parse.Error.INVALID_CLASS_NAME) {
1598-
return Promise.resolve();
1599-
}
1600-
throw err;
1601-
});
1613+
return this.createTable(schema.className, schema)
1614+
.catch((err) => {
1615+
if (err.code === PostgresDuplicateRelationError || err.code === Parse.Error.INVALID_CLASS_NAME) {
1616+
return Promise.resolve();
1617+
}
1618+
throw err;
1619+
})
1620+
.then(() => this.schemaUpgrade(schema.className, schema));
16021621
});
16031622
return Promise.all(promises)
16041623
.then(() => {

0 commit comments

Comments
 (0)