Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#4338 pg schema upgrade #4375

Merged
merged 11 commits into from
Jan 3, 2018
Merged

#4338 pg schema upgrade #4375

merged 11 commits into from
Jan 3, 2018

Conversation

paulovitin
Copy link
Contributor

Fix this problem #4338

When the Postgres Storage Adapter perform the initialization, and the "Volatile Classes Schemas" perform the table creation, the schemaUpgrade method execute a diff between database columns and the Schemas in code. If has new columns, create then through method addFieldIfNotExists.

@montymxb
Copy link
Contributor

I just retriggered the build, should be ok. I still need to compare this against the actual upgrade issue and verify that it does in fact rectify that issue.

@codecov
Copy link

codecov bot commented Nov 24, 2017

Codecov Report

Merging #4375 into master will increase coverage by <.01%.
The diff coverage is 93.75%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #4375      +/-   ##
==========================================
+ Coverage   92.78%   92.79%   +<.01%     
==========================================
  Files         118      118              
  Lines        8373     8384      +11     
==========================================
+ Hits         7769     7780      +11     
  Misses        604      604
Impacted Files Coverage Δ
...dapters/Storage/Postgres/PostgresStorageAdapter.js 97.01% <93.75%> (+0.03%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update fc6a2fd...4b348ad. Read the comment docs.

@montymxb montymxb self-assigned this Nov 24, 2017

adapter.createTable(className, schema)
.then(() => {
schema.fields.expiration_interval = { type:'Number' };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to be pedantic can we** getColumns** and check before the schema is upgraded to ensure that it's not present before hand?

return list;
}

const field = schema.fields[fieldName];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you elaborate on this change here?

Copy link
Contributor Author

@paulovitin paulovitin Nov 25, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not understand what you said with "elaborate on this change here".. :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I'll reword it. Could you explain the reason you made the changes above here? I'm not entirely sure why you added this at first glance.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specifically what about the prior functionality without ...!{}.hasOwnProperty.call(schema.fields, fieldName).. wasn't working and needed to be changed?

Copy link
Contributor

@montymxb montymxb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall this is super! I got around to checking the upgrade issue and this handles it without a problem 👍 . Just a test thing and a question from me.

Copy link
Contributor

@montymxb montymxb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent, thanks for making those changes.

I'm going to update against the master to factor in appveyor. No guarantees on that, as it may be still in the works for windows support...

@paulovitin
Copy link
Contributor Author

@montymxb I need to do something here?

@paulovitin
Copy link
Contributor Author

@montymxb How are we staying here?

Copy link
Contributor

@montymxb montymxb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@paulovitin We're looking better now. Got caught up with other things on github and beyond 😄 . Didn't realize that the last merge against master corrected those issues we were seeing.

Two things are left. One is that the reported test coverage is not yet up to spec, currently at 89.47%. We'll need you to account for untested paths to bring that to 100% or as close as you reasonable can.

The second being the note about using transactions. As for those I'm actually not too familiar with whether it would be beneficial to implement them here. @vitaly-t, per @dplewis 's note about potentially utilizing transactions in this PR, do you think there would be an added benefit to doing so?

schemaUpgrade(className, schema) {
debug('schemaUpgrade', { className, schema });
return this._client.tx('schemaUpgrade', t =>
t.any(`SELECT column_name FROM information_schema.columns WHERE table_name = '${className}'`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do not format the values manually, use the query-formatting engine for it

)
.then(columns => {
if (!columns) {
return Promise.resolve();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this condition will never happen, see the API for method any.

return Promise.resolve();
}

const currentColumns = columns.map(item => item.column_name);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps should just use method map of the database instead.


columnsToCreate.forEach(fieldName => {
const type = schema.fields[fieldName];
promise.push(this.addFieldIfNotExists(className, fieldName, type));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is bad connection usage, must use a transaction here.

promise.push(this.addFieldIfNotExists(className, fieldName, type));
});

return Promise.all(promise);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@paulovitin
Copy link
Contributor Author

paulovitin commented Dec 5, 2017

@vitaly-t I updated the code. Can you take a look?

@montymxb coverage achived! :)

@montymxb
Copy link
Contributor

montymxb commented Dec 7, 2017

@paulovitin thanks for getting the coverage up! I'll take a look at these changes now...

return this._client.tx('schemaUpgrade', t => {
return t.any('SELECT column_name FROM information_schema.columns WHERE table_name = $<className>', { className })
.then(columns => {
const currentColumns = columns.map(item => item.column_name);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I see this is the only bit you didn't change here. As is this is passing our tests and looks good. Perhaps it's fine as is. @vitaly-t do you think this is important enough to change or are we good as is?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vitaly-t Ding? Zing? Ping?

Copy link
Contributor

@vitaly-t vitaly-t Dec 30, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code should be replaced with ES6 generators, the way it was done for all tasks and transaction in the code.

@dplewis ping back at ya 😄

Copy link
Contributor

@montymxb montymxb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. I want to double check on the one change that wasn't made before I can consider approving this.

@paulovitin
Copy link
Contributor Author

@montymxb ok. If needs more changes, let me know

const getColumns = (client, className) => {
return client.any('SELECT column_name FROM information_schema.columns WHERE table_name = $<className>', { className })
.then(columns => {
if (!columns.length) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What you do in this .then block is pointless, i.e. if the condition matches, the you already end up with an empty array.

@@ -1,6 +1,17 @@
const PostgresStorageAdapter = require('../src/Adapters/Storage/Postgres/PostgresStorageAdapter');
const databaseURI = 'postgres://localhost:5432/parse_server_postgres_adapter_test_database';

const getColumns = (client, className) => {
Copy link
Contributor

@vitaly-t vitaly-t Dec 30, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whole method can be implemented with a single line, using method map.

return client.map('SELECT column_name FROM information_schema.columns WHERE table_name = $<className>', { className }, a => a.column_name);

Also, I'm not sure we should have methods that take client as parameter like that. See how all the other methods are done, they take an optional connection context as the last parameter.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a pretty simple one. We should setup map and save some lines 😉. You've already done quite a lot so let me know if you can get around to this @paulovitin . If not I can put in an additional commit myself.

debug('schemaUpgrade', { className, schema });

return this._client.tx('schemaUpgrade', t => {
return t.any('SELECT column_name FROM information_schema.columns WHERE table_name = $<className>', { className })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method map should be used here.

addFields.push(this.addFieldIfNotExists(className, fieldName, type));
});

if (!addFields.length) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inefficient use of promises here. Can simply skip returning anything for the same effect.

@vitaly-t
Copy link
Contributor

vitaly-t commented Dec 30, 2017

This needs a few updates + conflicts resolved.

@vitaly-t
Copy link
Contributor

vitaly-t commented Dec 30, 2017

Correction. No, it needs a lot of rewriting, especially in the light of the conflicts.

I have tried to update it, but the conflicts with the later changes stopped me from being able to finish.

@paulovitin You need to resolve the conflicts before I can help with the code refactoring.

Adds:
  - schemaUpgrade method which check the fields to add and delete from old schema;
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;
rewriting method schemaUpgrade
@vitaly-t
Copy link
Contributor

vitaly-t commented Dec 30, 2017

Ok, I'm done here, for now :) @paulovitin I hope I didn't miss anything ;)

@vitaly-t
Copy link
Contributor

vitaly-t commented Dec 30, 2017

@montymxb I've done my part here, do you want to re-review it? 😄

@dplewis
Copy link
Member

dplewis commented Dec 30, 2017

LGTM!

@montymxb
Copy link
Contributor

@vitaly-t absolutely 👍 . Thanks for taking a look, postgres via node isn't exactly one of my strong suites 😆 .

Copy link
Contributor

@montymxb montymxb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Relevant notes are in the review. You've been very helpful with accommodating to our requests, just wanted to thank you on that 😄 . If you think you can get around to these last ones here I believe we should be at a point to take this in. I would be willing to put in the last set of changes, should it be necessary.

Code is functional and tests indicate proper behavior. Anything that is non-critical can be applied in later PRs once we have this in. Big deal here is making sure we get this in place to prevent upgrade crashes as originally noticed.

@@ -1,6 +1,17 @@
const PostgresStorageAdapter = require('../src/Adapters/Storage/Postgres/PostgresStorageAdapter');
const databaseURI = 'postgres://localhost:5432/parse_server_postgres_adapter_test_database';

const getColumns = (client, className) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a pretty simple one. We should setup map and save some lines 😉. You've already done quite a lot so let me know if you can get around to this @paulovitin . If not I can put in an additional commit myself.

@@ -19,4 +30,100 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => {
expect(adapter._client.$pool.ending).toEqual(true);
done();
});

it('schemaUpgrade, upgrade the database schema when schema change', done => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a typo

upgrade the database schema when the schema changes

.catch(error => done.fail(error));
});

it('schemaUpgrade, matain correct schema', done => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo here too

maintains correct schema

@@ -1579,14 +1595,17 @@ export class PostgresStorageAdapter {
}

performInitialization({ VolatileClassesSchemas }) {
// TODO: This method needs to be rewritten to make proper use of connections (@vitaly-t)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding this TODO we'll want to at least rework this in with transactions as mentioned below.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will do it 😉

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DONE!

Copy link
Contributor

@vitaly-t vitaly-t Dec 31, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P.S. As for the last item, the TODO thing, it definitely should be done as a separate PR. I will take care of it after this PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted! @paulovitin you're good for that one then, we'll address that down the road.

@vitaly-t
Copy link
Contributor

vitaly-t commented Dec 31, 2017

A-a-and we're back into conflicts 😢

While we were fixing this PR, the code was turned into some sort of TypeScript, holly 💩 ! 😂

@paulovitin man, your PR is jinxed 😂

@montymxb
Copy link
Contributor

Hmm, probably related to #4349 being recently merged?

@flovilmart
Copy link
Contributor

@vitaly-t yup, intoduced types for the DB adapters and I had many conflicts to survive as well :)

@vitaly-t
Copy link
Contributor

vitaly-t commented Dec 31, 2017

Well, I volunteer @paulovitin to resolve the conflicts yet again 😂 I'm not doing it 😂

I'm drinking to the New Year already, as I am Russian 😄 🍻

@dplewis
Copy link
Member

dplewis commented Dec 31, 2017

@vitaly-t we have a very diverse team. New Years Goal: let’s get the team together sometime. Florent set it up. Let’s go to a conference or something

@flovilmart
Copy link
Contributor

@vitaly-t no need to do it! Just fixed them from the phone :)

@flovilmart
Copy link
Contributor

We have plenty of contrived on open collective, we could probab’y do something to help everyone get there :)

@dplewis
Copy link
Member

dplewis commented Dec 31, 2017

@flovilmart Make it happen captain

@vitaly-t
Copy link
Contributor

I did re-run of those flimsy tests a number of times till they finally all came through 😂

const getColumns = (client, className) => {
return client.map('SELECT column_name FROM information_schema.columns WHERE table_name = $<className>', { className }, a => a.column_name);
};

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't realize the above was finally changed, alright!

@@ -19,4 +23,100 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => {
expect(adapter._client.$pool.ending).toEqual(true);
done();
});

it('schemaUpgrade, upgrade the database schema when schema changes', done => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo fixed...

.catch(error => done.fail(error));
});

it('schemaUpgrade, maintain correct schema', done => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo fixed...

Copy link
Contributor

@montymxb montymxb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sweet, thanks @vitaly-t for those commits. Finally took a look and was going to do it myself, only to see it's already been done!

All the nits from before look to be taken care of.

@vitaly-t vitaly-t merged commit cb8f038 into parse-community:master Jan 3, 2018
@montymxb
Copy link
Contributor

montymxb commented Jan 3, 2018

Thanks again @paulovitin for putting up the PR!

UnderratedDev pushed a commit to UnderratedDev/parse-server that referenced this pull request Mar 21, 2020
* 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
@mtrezza mtrezza mentioned this pull request Jun 25, 2020
9 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants