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

feat: add upsert methods for the drivers that support onUpdate #8104

Merged
merged 7 commits into from Nov 9, 2021

Conversation

joeflateau
Copy link
Contributor

Description of change

This adds EntityManager#upsert, Repository#upsert and BaseEntity.upsert

Closes: #2363

Only implements upsert for drivers that InsertQueryBuilder supports onUpdate for.

Pull-Request Checklist

  • Code is up-to-date with the master branch
  • npm run lint passes with this change
  • npm run test passes with this change
  • This pull request links relevant issues as Fixes #0000
  • There are new or updated unit tests validating the change
  • Documentation has been updated to reflect this change
  • The new commits follow conventions explained in CONTRIBUTING.md

This adds EntityManager#upsert, BaseEntity#upsert and EntityManager#upsert

Closes: typeorm#2363
@jspizziri
Copy link

@imnotjames @pleerock ,

Could someone take a look at this? Would be super useful to have upsert built in.

@@ -147,6 +147,29 @@ await manager.update(User, 1, { firstName: "Rizzrak" });
// executes UPDATE user SET firstName = Rizzrak WHERE id = 1
```

* `upsert` - Inserts a new entity or array of entities unless they already exist in which case they are updated instead.
Copy link
Member

Choose a reason for hiding this comment

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

I think we need to left a note what databases support this method

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@pleerock I've added that

* `upsert` - Inserts a new entity or array of entities unless they already exist in which case they are updated instead. Supported by AuroraDataApi, Cockroach, Mysql, Postgres, and Sqlite database drivers.

```typescript
await manager.upsert(User, { externalId: "abc123" }, { firstName: "Rizzrak" });
Copy link
Member

Choose a reason for hiding this comment

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

TBH I find this signature confusing. I prefer to have only second signature:

await manager.update(User, ["externalId"], [
    { externalId:"abc123", firstName: "Rizzrak" },
    { externalId:"bca321", firstName: "Karzzir" },
    ]);

Copy link
Member

Choose a reason for hiding this comment

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

Also I would swap parameters order, it makes more logically sense to have:

await manager.update(
  User, 
  [
      { externalId:"abc123", firstName: "Rizzrak" },
      { externalId:"bca321", firstName: "Karzzir" },
  ], 
  ["externalId"]
);

Copy link
Contributor Author

@joeflateau joeflateau Nov 6, 2021

Choose a reason for hiding this comment

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

I put a lot of thought into the signature and think it is best to match the style used by https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndUpdate/. In addition, since the definition is essentially upsert(Entity, ${things to match/conflict on}, ${things to update}) I felt that parameter order made more sense for the second signature. I feel strongly about keeping the first signature, but a little less strongly about the order of the second signature.

Copy link
Member

Choose a reason for hiding this comment

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

${things to match/conflict on} - but when there is no conflict, this value will be used for insertion, right? So it's actually ${things to match/conflict on/insert if no conflict merged with next parameter values} - and this is what it makes this signature complicated in understanding.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is where i'm hung up:

await postRepository.upsert({ externalId }, { title: "Post title initial" });

vs

await postRepository.upsert({ externalId, title: "Post title initial" }, ["externalId"]);

I feel that the first signature flows significantly better than the second. The second feels unnecessarily redundant. MongoRepository#updateOne has the same basic signature and I don't believe it's complicated to understand: await mongoRepository.updateOne({ externalId }, { title: "Post title initial" }, { upsert: true });

Perhaps there's a middle ground here? Maybe rather than 1 upsert method with 2 signatures I should introduce 2 upsert methods? Repository#upsertOne(conditions, partial), a signature similar to MongoRepository#updateOne(conditions, partial, { upsert: true }) and also Repository#upsertMany(conflictPaths: string[], partialEntities: DeepPartial<Entity>[])

I really don't want to lose the MongoDb style signature.

Copy link
Member

Choose a reason for hiding this comment

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

I'm looking for both simplicity and obviousness. Adding new method won't help. Following MongoDB convention doesn't have to be there, maybe some people can appreciate it, but most prefer simplicity and obviousness. And I don't find ${things to match/conflict on/insert if no conflict merged with next parameter values} parameter obvious. Maybe because I'm not MongoDB user. I would prefer to type extra code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, I don't like it, but you're in charge here. I'll update the PR with your preferred style.

Copy link
Member

Choose a reason for hiding this comment

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

sorry about that =( If you want we can wait for other people who are interested in this feature to provide their feedback.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

no worries, I'm going to make the change. we can always reintroduce the second signature if feedback warrants it. thanks for everything you do for typeorm.

Copy link
Member

Choose a reason for hiding this comment

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

thank you 🤞

* ON CONFLICT (externalId) DO UPDATE firstName = EXCLUDED.firstName
**/

await manager.update(User, ["externalId"], [
Copy link
Member

Choose a reason for hiding this comment

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

typo, update must be upsert

Copy link
Contributor Author

Choose a reason for hiding this comment

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

all set here

@@ -154,6 +154,22 @@ await repository.update(1, { firstName: "Rizzrak" });
// executes UPDATE user SET firstName = Rizzrak WHERE id = 1
```

* `upsert` - Inserts a new entity or array of entities unless they already exist in which case they are updated instead.
Copy link
Member

Choose a reason for hiding this comment

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

We also need to specify list of supported drivers here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

incoming

@@ -0,0 +1,35 @@
export type MaxPropertyPathRecursionDepth = 5;
Copy link
Member

Choose a reason for hiding this comment

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

This is going to be a breaking change for users since we support TypeScript versions far behind 4 where this syntax is supported. We have to use string for now, but in the future we have a strategy to use this syntax everywhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oh, didn't realize pre-4 was still supported :(

Copy link
Member

Choose a reason for hiding this comment

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

yeah 0.2.x is quite old and we are trying to avoid breaking changes. I hope when we finally release 0.3.0 it will use the latest TypeScript version.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

let me know if I can be of assistance with 0.3.0 👍

);
};
}
it("should first create then update an entity", asyncMapper(upsertableConnections(true), async (connection) => {
Copy link
Member

Choose a reason for hiding this comment

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

we need to simplify code. Instead of quite complex for understanding asyncMapper and upsertableConnections we can have a simple checks like:

if (!connection.driver.supportedUpsertType) return

Copy link
Contributor Author

Choose a reason for hiding this comment

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

incoming

@pleerock pleerock merged commit 3f98197 into typeorm:master Nov 9, 2021
@pleerock
Copy link
Member

pleerock commented Nov 9, 2021

Thank you for your hard work!

@joeflateau joeflateau deleted the squash-for-upsert-pr branch November 9, 2021 19:53
HeartPattern pushed a commit to HeartPattern/typeorm that referenced this pull request Nov 29, 2021
…rm#8104)

* feat: add upsert methods for the drivers that support it

This adds EntityManager#upsert, BaseEntity#upsert and EntityManager#upsert

Closes: typeorm#2363

* docs: Document which drivers support upsert operations

* docs: fix typo in entity manager upsert many example

* refactor: remove mongodb style upsert signature, enforce types of conflict paths

* docs: add note to repository docs specifying which drivers support upsert

* refactor: cannot staticly type conflict paths because that would break typescript pre-4

* refactor: remove test utility methods in favor of some repeated checks
@AndrewKiri
Copy link

Is upsert only added to the docs but not to the source code of the library? upsert is reported to have not been found in the library.

package version - "typeorm": "^0.2.34"

Screenshot 2021-11-30 at 21 51 51

@joeflateau
Copy link
Contributor Author

@AndrewKiri it was added in 0.2.40

@AndrewKiri
Copy link

AndrewKiri commented Nov 30, 2021

@AndrewKiri it was added in 0.2.40

Ah sorry, thanks 🙏
Thanks for this amazing addition & hard work 👍

@joeflateau
Copy link
Contributor Author

no prob and you're welcome! I'm happy to be able to give back to the project

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.

[Feature Request] Upsert generation
4 participants