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
feat: add upsert methods for the drivers that support onUpdate #8104
Conversation
This adds EntityManager#upsert, BaseEntity#upsert and EntityManager#upsert Closes: typeorm#2363
ab02d6a
to
5f37460
Compare
Could someone take a look at this? Would be super useful to have upsert built in. |
docs/entity-manager-api.md
Outdated
@@ -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. |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
docs/entity-manager-api.md
Outdated
* `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" }); |
There was a problem hiding this comment.
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" },
]);
There was a problem hiding this comment.
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"]
);
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thank you 🤞
docs/entity-manager-api.md
Outdated
* ON CONFLICT (externalId) DO UPDATE firstName = EXCLUDED.firstName | ||
**/ | ||
|
||
await manager.update(User, ["externalId"], [ |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
all set here
docs/repository-api.md
Outdated
@@ -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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
incoming
src/util/PropertyPath.ts
Outdated
@@ -0,0 +1,35 @@ | |||
export type MaxPropertyPathRecursionDepth = 5; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 :(
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) => { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
incoming
Thank you for your hard work! |
…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 it was added in 0.2.40 |
Ah sorry, thanks 🙏 |
no prob and you're welcome! I'm happy to be able to give back to the project |
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
master
branchnpm run lint
passes with this changenpm run test
passes with this changeFixes #0000